CANCERYS\kw093 преди 17 часа
родител
ревизия
c544c00d35
променени са 9 файла, в които са добавени 242 реда и са изтрити 189 реда
  1. +3
    -2
      src/app/api/jo/actions.ts
  2. +15
    -10
      src/components/Jodetail/JobPickExecutionForm.tsx
  3. +4
    -1
      src/components/Jodetail/JobPickExecutionsecondscan.tsx
  4. +87
    -79
      src/components/Jodetail/JodetailSearch.tsx
  5. +29
    -66
      src/components/Jodetail/completeJobOrderRecord.tsx
  6. +99
    -30
      src/components/Jodetail/newJobPickExecution.tsx
  7. +2
    -1
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  8. +1
    -0
      src/i18n/zh/common.json
  9. +2
    -0
      src/i18n/zh/jo.json

+ 3
- 2
src/app/api/jo/actions.ts Целия файл

@@ -524,6 +524,7 @@ export interface PickOrderLineWithLotsResponse {
uomCode: string | null;
uomDesc: string | null;
status: string | null;
handler: string | null;
lots: LotDetailResponse[];
}

@@ -868,9 +869,9 @@ export const fetchCompletedJobOrderPickOrders = cache(async (userId: number) =>
);
});
// 获取已完成的 Job Order pick orders
export const fetchCompletedJobOrderPickOrdersrecords = cache(async (userId: number) => {
export const fetchCompletedJobOrderPickOrdersrecords = cache(async () => {
return serverFetchJson<any>(
`${BASE_API_URL}/jo/completed-job-order-pick-orders-only/${userId}`,
`${BASE_API_URL}/jo/completed-job-order-pick-orders-only`,
{
method: "GET",
next: { tags: ["jo-completed"] },


+ 15
- 10
src/components/Jodetail/JobPickExecutionForm.tsx Целия файл

@@ -196,16 +196,19 @@ useEffect(() => {
if (verifiedQty === undefined || verifiedQty < 0) {
newErrors.actualPickQty = t('Qty is required');
}
const totalQty = verifiedQty + badItemQty + missQty;
const hasAnyValue = verifiedQty > 0 || badItemQty > 0 || missQty > 0;
// ✅ 新增:必须至少有一个 > 0
if (!hasAnyValue) {
newErrors.actualPickQty = t('At least one of Verified / Missing / Bad must be greater than 0');
}
if (hasAnyValue && totalQty !== requiredQty) {
newErrors.actualPickQty = t('Total (Verified + Bad + Missing) must equal Required quantity');
}

setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
@@ -214,9 +217,10 @@ useEffect(() => {
return;
}
// Handle normal pick submission: verifiedQty > 0 with no issues, OR all zeros (verifiedQty=0, missQty=0, badItemQty=0)
const isNormalPick = (verifiedQty > 0 || (verifiedQty === 0 && formData.missQty == 0 && formData.badItemQty == 0))
&& formData.missQty == 0 && formData.badItemQty == 0;
// ✅ 只允许 Verified>0 且没有问题时,走 normal pick
const isNormalPick = verifiedQty > 0
&& formData.missQty == 0
&& formData.badItemQty == 0;
if (isNormalPick) {
if (onNormalPickSubmit) {
@@ -235,11 +239,12 @@ useEffect(() => {
}
return;
}

// ❌ 有问题(或全部为 0)才进入 Issue 提报流程
if (!validateForm() || !formData.pickOrderId) {
return;
}
setLoading(true);
try {
const submissionData = {


+ 4
- 1
src/components/Jodetail/JobPickExecutionsecondscan.tsx Целия файл

@@ -487,7 +487,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => {
matchStatus: lot.matchStatus,
routerArea: lot.routerArea,
routerRoute: lot.routerRoute,
uomShortDesc: lot.uomShortDesc
uomShortDesc: lot.uomShortDesc,
handler: lot.handler,
});
});
}
@@ -1173,6 +1174,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => {
<TableRow>
<TableCell>{t("Index")}</TableCell>
<TableCell>{t("Route")}</TableCell>
<TableCell>{t("Handler")}</TableCell>
<TableCell>{t("Item Code")}</TableCell>
<TableCell>{t("Item Name")}</TableCell>
<TableCell>{t("Lot No")}</TableCell>
@@ -1212,6 +1214,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBack }) => {
{lot.routerRoute || '-'}
</Typography>
</TableCell>
<TableCell>{lot.handler || '-'}</TableCell>
<TableCell>{lot.itemCode}</TableCell>
<TableCell>{lot.itemName+'('+lot.uomDesc+')'}</TableCell>
<TableCell>


+ 87
- 79
src/components/Jodetail/JodetailSearch.tsx Целия файл

@@ -15,7 +15,7 @@ import {
import {
arrayToDayjs,
} from "@/app/utils/formatUtil";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box } from "@mui/material";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box, TextField, Autocomplete } from "@mui/material";
import Jodetail from "./Jodetail"
import PickExecution from "./JobPickExecution";
import { fetchAllItemsInClient, ItemCombo } from "@/app/api/settings/item/actions";
@@ -63,12 +63,18 @@ const JodetailSearch: React.FC<Props> = ({ pickOrders, printerCombo }) => {
const [totalCount, setTotalCount] = useState<number>();
const [isAssigning, setIsAssigning] = useState(false);
const [unassignedOrders, setUnassignedOrders] = useState<any[]>([]);
const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
const [hasAssignedJobOrders, setHasAssignedJobOrders] = useState(false);
const [hasDataTab0, setHasDataTab0] = useState(false);
const [hasDataTab1, setHasDataTab1] = useState(false);
const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
//const [printers, setPrinters] = useState<PrinterCombo[]>([]);
const [isLoadingUnassigned, setIsLoadingUnassigned] = useState(false);
const [hasAssignedJobOrders, setHasAssignedJobOrders] = useState(false);
const [hasDataTab0, setHasDataTab0] = useState(false);
const [hasDataTab1, setHasDataTab1] = useState(false);
const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
// Add printer selection state
const [selectedPrinter, setSelectedPrinter] = useState<PrinterCombo | null>(
printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
);
const [printQty, setPrintQty] = useState<number>(1);
const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
);
@@ -98,21 +104,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
window.removeEventListener('jobOrderDataStatus', handleJobOrderDataChange as EventListener);
};
}, []);
/*
useEffect(() => {
const fetchPrinters = async () => {
try {
// 需要创建一个客户端版本的 fetchPrinterCombo
// 或者使用 API 路由
// const printersData = await fetch('/api/printers/combo').then(r => r.json());
// setPrinters(printersData);
} catch (error) {
console.error("Error fetching printers:", error);
}
};
fetchPrinters();
}, []);
*/
useEffect(() => {
const onAssigned = () => {
localStorage.removeItem('hideCompletedUntilNext');
@@ -121,7 +113,6 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
window.addEventListener('pickOrderAssigned', onAssigned);
return () => window.removeEventListener('pickOrderAssigned', onAssigned);
}, []);
// ... existing code ...

useEffect(() => {
const handleCompletionStatusChange = (event: CustomEvent) => {
@@ -139,7 +130,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
return () => {
window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener);
};
}, [tabIndex]); // 添加 tabIndex 依赖
}, [tabIndex]);

// 新增:处理标签页切换时的打印按钮状态重置
useEffect(() => {
@@ -150,7 +141,6 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
}
}, [tabIndex]);

// ... existing code ...
const handleAssignByStore = async (storeId: "2/F" | "4/F") => {
if (!currentUserId) {
console.error("Missing user id in session");
@@ -430,71 +420,89 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;

return (
<Box sx={{
height: '100vh', // Full viewport height
overflow: 'auto' // Single scrollbar for the whole page
height: '100vh',
overflow: 'auto'
}}>
{/* Header section */}
<Box sx={{ p: 2, borderBottom: '1px solid #e0e0e0' }}>
<Stack rowGap={2}>
<Grid container alignItems="center">
<Grid item xs={8}>

</Grid>
{/* Last 2 buttons aligned right

<Grid item xs={6} >
{!hasAnyAssignedData && unassignedOrders && unassignedOrders.length > 0 && (
<Box sx={{ mt: 2, p: 2, border: '1px solid #e0e0e0', borderRadius: 1 }}>
<Typography variant="h6" gutterBottom>
{t("Unassigned Job Orders")} ({unassignedOrders.length})
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap">
{unassignedOrders.map((order) => (
<Button
key={order.pickOrderId}
variant="outlined"
size="small"
onClick={() => handleAssignOrder(order.pickOrderId)}
disabled={isLoadingUnassigned}
>
{order.pickOrderCode} - {order.jobOrderName}
</Button>
))}
</Stack>
</Box>
)}
</Grid>
*/}
{/* Header section with printer selection */}
<Box sx={{
p: 1,
borderBottom: '1px solid #e0e0e0',
minHeight: 'auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 2,
flexWrap: 'wrap',
}}>
{/* Left side - Title */}

</Grid>
</Stack>
</Box>
{/* Right side - Printer selection (only show on tab 1) */}
{tabIndex === 1 && (
<Stack
direction="row"
spacing={2}
sx={{
alignItems: 'center',
flexWrap: 'wrap',
rowGap: 1,
}}
>
<Typography variant="body2" sx={{ minWidth: 'fit-content', mr: 1.5 }}>
{t("Select Printer")}:
</Typography>
<Autocomplete
options={printerCombo || []}
getOptionLabel={(option) =>
option.name || option.label || option.code || `Printer ${option.id}`
}
value={selectedPrinter}
onChange={(_, newValue) => setSelectedPrinter(newValue)}
sx={{ minWidth: 200 }}
size="small"
renderInput={(params) => (
<TextField {...params} placeholder={t("Printer")} />
)}
/>
<Typography variant="body2" sx={{ minWidth: 'fit-content', ml: 1 }}>
{t("Print Quantity")}:
</Typography>
<TextField
type="number"
label={t("Print Quantity")}
value={printQty}
onChange={(e) => {
const value = parseInt(e.target.value) || 1;
setPrintQty(Math.max(1, value));
}}
inputProps={{ min: 1, step: 1 }}
sx={{ width: 120 }}
size="small"
/>
</Stack>
)}
</Box>

{/* Tabs section - Move the click handler here */}
{/* Tabs section */}
<Box sx={{
borderBottom: '1px solid #e0e0e0'
}}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
{/* <Tab label={t("Pick Order Detail")} iconPosition="end" /> */}
<Tab label={t("Jo Pick Order Detail")} iconPosition="end" />
<Tab label={t("Complete Job Order Record")} iconPosition="end" />

{/* <Tab label={t("Job Order Match")} iconPosition="end" /> */}
{/* <Tab label={t("Finished Job Order Record")} iconPosition="end" /> */}
<Tab label={t("Jo Pick Order Detail")} iconPosition="end" />
<Tab label={t("Complete Job Order Record")} iconPosition="end" />
</Tabs>
</Box>

{/* Content section - NO overflow: 'auto' here */}
<Box sx={{
p: 2
}}>
{/* {tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />} */}
{tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />}
{/* Content section */}
<Box sx={{ p: 2 }}>
{tabIndex === 0 && <JoPickOrderList onSwitchToRecordTab={handleSwitchToRecordTab} />}
{/* {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} */}
{/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */}
{tabIndex === 1 && (
<CompleteJobOrderRecord
filterArgs={filterArgs}
printerCombo={printerCombo}
selectedPrinter={selectedPrinter}
printQty={printQty}
/>
)}
</Box>
</Box>
);


+ 29
- 66
src/components/Jodetail/completeJobOrderRecord.tsx Целия файл

@@ -49,6 +49,8 @@ import { PrinterCombo } from "@/app/api/settings/printer";
interface Props {
filterArgs: Record<string, any>;
printerCombo: PrinterCombo[];
selectedPrinter?: PrinterCombo | null;
printQty?: number;
}

// 修改:已完成的 Job Order Pick Order 接口
@@ -101,7 +103,12 @@ interface LotDetail {
uomDesc: string;
}

const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) => {
const CompleteJobOrderRecord: React.FC<Props> = ({
filterArgs,
printerCombo,
selectedPrinter: selectedPrinterProp,
printQty: printQtyProp
}) => {
const { t } = useTranslation("jo");
const router = useRouter();
const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -121,25 +128,11 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
// 修改:搜索状态
const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
//const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);
const defaultDemoPrinter: PrinterCombo = {
id: 2,
value: 2,
name: "2fi",
label: "2fi",
code: "2fi"
};
const availablePrinters = useMemo(() => {
if (printerCombo.length === 0) {
console.log("No printers available, using default demo printer");
return [defaultDemoPrinter];
}
return printerCombo;
}, [printerCombo]);
const [selectedPrinter, setSelectedPrinter] = useState<PrinterCombo | null>(
printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
);
const [printQty, setPrintQty] = useState<number>(1);
// Use props with fallback
const selectedPrinter = selectedPrinterProp ?? (printerCombo && printerCombo.length > 0 ? printerCombo[0] : null);
const printQty = printQtyProp ?? 1;
// 修改:分页状态
const [paginationController, setPaginationController] = useState({
pageNum: 0,
@@ -157,7 +150,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
try {
console.log("🔍 Fetching completed Job Order pick orders (pick completed only)...");
const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords(currentUserId);
const completedJobOrderPickOrders = await fetchCompletedJobOrderPickOrdersrecords();
// Fix: Ensure the data is always an array
const safeData = Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : [];
@@ -226,7 +219,19 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
setFilteredJobOrderPickOrders(filtered);
console.log("Filtered Job Order pick orders count:", filtered.length);
}, [completedJobOrderPickOrders]);

const formatDateTime = (value: any) => {
if (!value) return "-";
// 后端发来的是 [yyyy, MM, dd, HH, mm, ss]
if (Array.isArray(value)) {
const [year, month, day, hour = 0, minute = 0, second = 0] = value;
return new Date(year, month - 1, day, hour, minute, second).toLocaleString();
}
// 如果以后改成字符串/ISO,也兼容
const d = new Date(value);
return isNaN(d.getTime()) ? "-" : d.toLocaleString();
};
// 修改:重置搜索
const handleSearchReset = useCallback(() => {
setSearchQuery({});
@@ -433,18 +438,6 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
<strong>{t("Required Qty")}:</strong> {selectedJobOrderPickOrder.reqQty} {selectedJobOrderPickOrder.uom}
</Typography>
</Stack>
{/*
<Stack direction="row" spacing={4} useFlexGap flexWrap="wrap" sx={{ mt: 2 }}>
<Button
variant="contained"
color="primary"
onClick={() => handlePickRecord(selectedJobOrderPickOrder)}
sx={{ mt: 1 }}
>
{t("Print Pick Record")}
</Button>
</Stack>
*/}
</CardContent>
</Card>

@@ -600,37 +593,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("Total")}: {filteredJobOrderPickOrders.length} {t("completed Job Order pick orders with matching")}
</Typography>
<Box sx={{ mb: 2, p: 2, border: '1px solid #e0e0e0', borderRadius: 1, bgcolor: 'background.paper' }}>
<Stack direction="row" spacing={2} alignItems="center" flexWrap="wrap">
<Typography variant="subtitle1" sx={{ minWidth: 'fit-content' }}>
{t("Select Printer")}:
</Typography>
<Autocomplete
options={availablePrinters}
getOptionLabel={(option) => option.name || option.label || option.code || `Printer ${option.id}`}
value={selectedPrinter}
onChange={(_, newValue) => setSelectedPrinter(newValue)}
sx={{ minWidth: 250 }}
size="small"
renderInput={(params) => <TextField {...params} label={t("Printer")} />}
/>
<Typography variant="subtitle1" sx={{ minWidth: 'fit-content' }}>
{t("Print Quantity")}:
</Typography>
<TextField
type="number"
label={t("Print Quantity")}
value={printQty}
onChange={(e) => {
const value = parseInt(e.target.value) || 1;
setPrintQty(Math.max(1, value));
}}
inputProps={{ min: 1, step: 1 }}
sx={{ width: 120 }}
size="small"
/>
</Stack>
</Box>
{/* 列表 */}
{filteredJobOrderPickOrders.length === 0 ? (
<Box sx={{ p: 3, textAlign: 'center' }}>
@@ -652,7 +615,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ filterArgs ,printerCombo}) =>
{jobOrderPickOrder.jobOrderName} - {jobOrderPickOrder.pickOrderCode}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Completed")}: {new Date(jobOrderPickOrder.completedDate).toLocaleString()}
{t("Completed")}: {formatDateTime(jobOrderPickOrder.planEnd)}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Target Date")}: {jobOrderPickOrder.pickOrderTargetDate}


+ 99
- 30
src/components/Jodetail/newJobPickExecution.tsx Целия файл

@@ -42,8 +42,6 @@ import {
} from "@/app/api/pickOrder/actions";
// 修改:使用 Job Order API
import {
//fetchJobOrderLotsHierarchical,
//fetchUnassignedJobOrderPickOrders,
assignJobOrderPickOrder,
fetchJobOrderLotsHierarchicalByPickOrderId,
updateJoPickOrderHandledBy,
@@ -412,6 +410,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
pickOrderType: data.pickOrder.type,
pickOrderStatus: data.pickOrder.status,
pickOrderAssignTo: data.pickOrder.assignTo,
handler: line.handler,
});
});
}
@@ -537,6 +536,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
setCombinedDataLoading(false);
}
}, [getAllLotsFromHierarchical]);

const updateHandledBy = useCallback(async (pickOrderId: number, itemId: number) => {
if (!currentUserId || !pickOrderId || !itemId) {
return;
@@ -901,11 +901,9 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
// Use the first active suggested lot as the "expected" lot
const expectedLot = activeSuggestedLots[0];
// 2) Check if the scanned lot matches exactly
if (scanned?.lotNo === expectedLot.lotNo) {
// ✅ Case 1: 使用 updateStockOutLineStatusByQRCodeAndLotNo API(更快)
console.log(`✅ Exact lot match found for ${scanned.lotNo}, using fast API`);
if (!expectedLot.stockOutLineId) {
console.warn("No stockOutLineId on expectedLot, cannot update status by QR.");
setQrScanError(true);
@@ -922,24 +920,33 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
status: "checked",
});
if (res.code === "checked" || res.code === "SUCCESS") {
setQrScanError(false);
setQrScanSuccess(true);
const updateOk =
res?.type === "checked" ||
typeof res?.id === "number" ||
(res?.message && res.message.includes("success"));
if (updateOk) {
setQrScanError(false);
setQrScanSuccess(true);
// ✅ 刷新数据而不是直接更新 state
const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined;
await fetchJobOrderData(pickOrderId);
console.log("✅ Status updated, data refreshed");
} else if (res.code === "LOT_NUMBER_MISMATCH") {
console.warn("Backend reported LOT_NUMBER_MISMATCH:", res.message);
setQrScanError(true);
setQrScanSuccess(false);
} else if (res.code === "ITEM_MISMATCH") {
console.warn("Backend reported ITEM_MISMATCH:", res.message);

if (
expectedLot.pickOrderId &&
expectedLot.itemId &&
(expectedLot.stockOutLineStatus?.toLowerCase?.() === "pending" ||
!expectedLot.stockOutLineStatus) &&
!expectedLot.handler
) {
await updateHandledBy(expectedLot.pickOrderId, expectedLot.itemId);
}
const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined;
await fetchJobOrderData(pickOrderId);
} else if (res?.code === "LOT_NUMBER_MISMATCH" || res?.code === "ITEM_MISMATCH") {
setQrScanError(true);
setQrScanSuccess(false);
} else {
console.warn("Unexpected response code from backend:", res.code);
console.warn("Unexpected response from backend:", res);
setQrScanError(true);
setQrScanSuccess(false);
}
@@ -949,7 +956,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
setQrScanSuccess(false);
}
return; // ✅ 直接返回,不再调用 handleQrCodeSubmit
return; // ✅ 直接返回,不再调用后面的分支
}
// Case 2: Same item, different lot - show confirmation modal
@@ -977,7 +984,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
setQrScanSuccess(false);
return;
}
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotConfirmationOpen]);
}, [combinedLotData, handleQrCodeSubmit, handleLotMismatch, lotConfirmationOpen, updateHandledBy]);


const handleManualInputSubmit = useCallback(() => {
@@ -1310,6 +1317,14 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
console.error("Error submitting pick quantity:", error);
}
}, [fetchJobOrderData, checkAndAutoAssignNext, filterArgs]);
const handleSkip = useCallback(async (lot: any) => {
try {
console.log("Skip clicked, submit 0 qty for lot:", lot.lotNo);
await handleSubmitPickQtyWithQty(lot, 0);
} catch (err) {
console.error("Error in Skip:", err);
}
}, [handleSubmitPickQtyWithQty]);
const handleSubmitAllScanned = useCallback(async () => {
const scannedLots = combinedLotData.filter(lot =>
lot.stockOutLineStatus === 'checked'
@@ -1544,7 +1559,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
}, [startScan]);

const handleStopScan = useCallback(() => {
console.log("⏹️ Stopping manual QR scan...");
console.log(" Stopping manual QR scan...");
setIsManualScanning(false);
setQrScanError(false);
setQrScanSuccess(false);
@@ -1563,7 +1578,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
}, [isManualScanning, stopScan, resetScan]);
useEffect(() => {
if (isManualScanning && combinedLotData.length === 0) {
console.log("⏹️ No data available, auto-stopping QR scan...");
console.log(" No data available, auto-stopping QR scan...");
handleStopScan();
}
}, [combinedLotData.length, isManualScanning, handleStopScan]);
@@ -1677,16 +1692,59 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
</Box>
</Box>

{qrScanError && !qrScanSuccess && (
<Alert severity="error" sx={{ mb: 2 }}>
{qrScanError && !qrScanSuccess && (
<Alert
severity="error"
sx={{
mb: 2,
display: "flex",
justifyContent: "center",
alignItems: "center",
fontWeight: "bold",
fontSize: "1rem",
color: "error.main", // ✅ 整个 Alert 文字用错误红
"& .MuiAlert-message": {
width: "100%",
textAlign: "center",
// color: "error.main", // ✅ 明确指定 message 文字颜色
},
"& .MuiSvgIcon-root": {
color: "error.main", // 图标继续红色(可选)
},
backgroundColor: "error.light",
}}
>
{t("QR code does not match any item in current orders.")}
</Alert>
)}
{qrScanSuccess && (
<Alert severity="success" sx={{ mb: 2 }}>
{t("QR code verified.")}
</Alert>
)}
{qrScanSuccess && (
<Alert
severity="success"
sx={{
mb: 2,
display: "flex",
justifyContent: "center",
alignItems: "center",
fontWeight: "bold",
fontSize: "1rem",
// 背景用很浅的绿色
bgcolor: "rgba(76, 175, 80, 0.08)",
// 文字用主题 success 绿
color: "success.main",
// 去掉默认强烈的色块感
"& .MuiAlert-icon": {
color: "success.main",
},
"& .MuiAlert-message": {
width: "100%",
textAlign: "center",
color: "success.main",
},
}}
>
{t("QR code verified.")}
</Alert>
)}
<TableContainer component={Paper}>
<Table>
@@ -1694,6 +1752,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
<TableRow>
<TableCell>{t("Index")}</TableCell>
<TableCell>{t("Route")}</TableCell>
<TableCell>{t("Handler")}</TableCell>
<TableCell>{t("Item Code")}</TableCell>
<TableCell>{t("Item Name")}</TableCell>
<TableCell>{t("Lot No")}</TableCell>
@@ -1733,6 +1792,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
{lot.routerRoute || '-'}
</Typography>
</TableCell>
<TableCell>{lot.handler || '-'}</TableCell>
<TableCell>{lot.itemCode}</TableCell>
<TableCell>{lot.itemName+'('+lot.uomDesc+')'}</TableCell>
<TableCell>
@@ -1837,6 +1897,15 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onSwitchToRecordTab })
>
{t("Issue")}
</Button>
<Button
variant="outlined"
size="small"
onClick={() => handleSkip(lot)}
disabled={lot.stockOutLineStatus === 'completed'}
sx={{ fontSize: '0.7rem', py: 0.5, minHeight: '28px', minWidth: '60px' }}
>
{t("Skip")}
</Button>
</Stack>
</Box>
</TableCell>


+ 2
- 1
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx Целия файл

@@ -173,7 +173,8 @@ const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => {
const response = await deleteJobOrder(jobOrderId)
if (response) {
//setProcessData(response.entity);
await fetchData();
//await fetchData();
onBack();
}
}, [jobOrderId]);
const handleRelease = useCallback(async ( jobOrderId: number) => {


+ 1
- 0
src/i18n/zh/common.json Целия файл

@@ -221,6 +221,7 @@
"View Details": "查看詳情",
"view stockin": "品檢",
"No completed Job Order pick orders with matching found": "沒有相關記錄",
"Handler": "提料員",
"Completed Step": "完成步驟",
"Continue": "繼續",
"Executing": "執行中",


+ 2
- 0
src/i18n/zh/jo.json Целия файл

@@ -86,6 +86,8 @@
"Job Order Item Name": "工單物料名稱",
"Job Order Code": "工單編號",
"View Details": "查看詳情",
"Skip": "跳過",
"Handler": "提料員",
"Required Qty": "需求數量",
"completed Job Order pick orders with Matching": "工單已完成提料和對料",
"No completed Job Order pick orders with matching found": "沒有相關記錄",


Зареждане…
Отказ
Запис