|
|
|
@@ -1671,9 +1671,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO |
|
|
|
processedQrCombinations, |
|
|
|
) |
|
|
|
) { |
|
|
|
const itemRows = indexes.byItemId.get(scannedItemId) ?? []; |
|
|
|
const errMsg = |
|
|
|
itemRows.length === 0 |
|
|
|
? t("QR code does not match any item in current orders.") |
|
|
|
: t( |
|
|
|
"No pending pick line left for this item. It may already be completed or fully processed.", |
|
|
|
); |
|
|
|
console.log( |
|
|
|
` [SKIP] No pending stock-out line left for itemId=${scannedItemId}, stockInLineId=${scannedStockInLineId}`, |
|
|
|
); |
|
|
|
startTransition(() => { |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg(errMsg); |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -2275,9 +2287,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO |
|
|
|
) |
|
|
|
) { |
|
|
|
const mismatchCheckTime = performance.now() - mismatchCheckStartTime; |
|
|
|
const itemRows = indexes.byItemId.get(scannedItemId) ?? []; |
|
|
|
const errMsg = |
|
|
|
itemRows.length === 0 |
|
|
|
? t("QR code does not match any item in current orders.") |
|
|
|
: t( |
|
|
|
"No pending pick line left for this item. It may already be completed or fully processed.", |
|
|
|
); |
|
|
|
console.log( |
|
|
|
` [SKIP] No pending stock-out line left for mismatch path (check time: ${mismatchCheckTime.toFixed(2)}ms)`, |
|
|
|
); |
|
|
|
startTransition(() => { |
|
|
|
setQrScanError(true); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg(errMsg); |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
const mismatchCheckTime = performance.now() - mismatchCheckStartTime; |
|
|
|
@@ -3395,6 +3419,7 @@ const handleStartScan = useCallback(() => { |
|
|
|
lastConsumedQrValuesLengthRef.current = qrValues.length; |
|
|
|
setQrScanError(false); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg(''); |
|
|
|
|
|
|
|
const beforeStartScanTime = performance.now(); |
|
|
|
startScan(); |
|
|
|
@@ -3427,6 +3452,7 @@ const handleStartScan = useCallback(() => { |
|
|
|
setIsManualScanning(false); |
|
|
|
setQrScanError(false); |
|
|
|
setQrScanSuccess(false); |
|
|
|
setQrScanErrorMsg(''); |
|
|
|
stopScan(); |
|
|
|
resetScan(); |
|
|
|
lastConsumedQrValuesLengthRef.current = 0; |
|
|
|
@@ -3850,7 +3876,10 @@ const handleSubmitAllScanned = useCallback(async () => { |
|
|
|
<ScanStatusAlert |
|
|
|
error={qrScanError} |
|
|
|
success={qrScanSuccess} |
|
|
|
errorMessage={t("QR code does not match any item in current orders.")} |
|
|
|
errorMessage={ |
|
|
|
qrScanErrorMsg || |
|
|
|
t("QR code does not match any item in current orders.") |
|
|
|
} |
|
|
|
successMessage={t("QR code verified.")} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
@@ -4116,76 +4145,85 @@ paginatedData.map((row, index) => { |
|
|
|
{lot.routerRoute || '-'} |
|
|
|
</Typography> |
|
|
|
</TableCell> |
|
|
|
<TableCell> |
|
|
|
<Stack direction="row" spacing={1} alignItems="flex-start"> |
|
|
|
<Box sx={{ flex: 1, minWidth: 0 }}> |
|
|
|
<Typography |
|
|
|
sx={{ |
|
|
|
color: |
|
|
|
rejectDisplay || isSolRejected |
|
|
|
? 'error.main' |
|
|
|
: isInventoryLotLineUnavailable(lot) |
|
|
|
? 'error.main' |
|
|
|
: lot.lotAvailability === 'expired' |
|
|
|
? 'warning.main' |
|
|
|
: 'inherit', |
|
|
|
}} |
|
|
|
> |
|
|
|
{lot.lotNo ? ( |
|
|
|
rejectDisplay ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo} |
|
|
|
<Box |
|
|
|
component="span" |
|
|
|
sx={{ display: 'block', mt: 0.25, typography: 'body2', fontWeight: 400 }} |
|
|
|
> |
|
|
|
{rejectDisplay} |
|
|
|
</Box> |
|
|
|
</> |
|
|
|
) : |
|
|
|
|
|
|
|
lot.lotAvailability === 'expired' ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo}{' '} |
|
|
|
{t('is expired. Please check around have available QR code or not.')} |
|
|
|
</> |
|
|
|
) : isInventoryLotLineUnavailable(lot) ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo}{' '} |
|
|
|
{t('is unavable. Please check around have available QR code or not.')} |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
lot.lotNo |
|
|
|
) |
|
|
|
) : ( |
|
|
|
<Box component="span" sx={{ fontSize: "0.85rem", lineHeight: 1.4 }}> |
|
|
|
{rejectDisplay || |
|
|
|
<TableCell> |
|
|
|
<Stack direction="row" spacing={1} alignItems="flex-start"> |
|
|
|
<Box sx={{ flex: 1, minWidth: 0 }}> |
|
|
|
{(() => { |
|
|
|
const hasLotNo = Boolean(lot.lotNo); |
|
|
|
const isExpired = lot.lotAvailability === 'expired'; |
|
|
|
const isUnavailable = isInventoryLotLineUnavailable(lot); |
|
|
|
const isNoLotHint = !hasLotNo && !rejectDisplay; // 顯示「請檢查周圍…」那種 |
|
|
|
const isIssueText = |
|
|
|
Boolean(rejectDisplay) || !hasLotNo || isExpired || isUnavailable; |
|
|
|
const textColor = |
|
|
|
isNoLotHint |
|
|
|
? 'error.main' // 或 'text.primary':固定黑,不受 handled / unavailable 影響 |
|
|
|
: rejectDisplay || isSolRejected || isUnavailable |
|
|
|
? 'error.main' |
|
|
|
: isExpired |
|
|
|
? 'warning.main' |
|
|
|
: 'inherit'; |
|
|
|
return ( |
|
|
|
<Typography |
|
|
|
variant={isIssueText ? "body2" : "body1"} |
|
|
|
sx={{ |
|
|
|
color: textColor, |
|
|
|
whiteSpace: 'pre-wrap', |
|
|
|
wordBreak: 'break-word', |
|
|
|
}} |
|
|
|
> |
|
|
|
{hasLotNo ? ( |
|
|
|
rejectDisplay ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo} |
|
|
|
<Box component="span" sx={{ display: 'block', mt: 0.5, color: 'error.main' }}> |
|
|
|
{rejectDisplay} |
|
|
|
</Box> |
|
|
|
</> |
|
|
|
) : isExpired ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo}{' '} |
|
|
|
{t('is expired. Please check around have available QR code or not.')} |
|
|
|
</> |
|
|
|
) : isUnavailable ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo}{' '} |
|
|
|
{t('is unavailable. Please check around have available QR code or not.')} |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
lot.lotNo |
|
|
|
) |
|
|
|
) : ( |
|
|
|
rejectDisplay || |
|
|
|
t( |
|
|
|
"Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.", |
|
|
|
)} |
|
|
|
</Box> |
|
|
|
)} |
|
|
|
</Typography> |
|
|
|
) |
|
|
|
)} |
|
|
|
</Typography> |
|
|
|
); |
|
|
|
})()} |
|
|
|
</Box> |
|
|
|
{Number(lot.stockOutLineId) > 0 && Number(lot.itemId) > 0 ? ( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
size="small" |
|
|
|
onClick={() => openWorkbenchLotLabelModalForLot(lot)} |
|
|
|
sx={{ |
|
|
|
flexShrink: 0, |
|
|
|
fontSize: "0.7rem", |
|
|
|
py: 0.25, |
|
|
|
minWidth: "auto", |
|
|
|
px: 1, |
|
|
|
whiteSpace: "nowrap", |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("lot QR code")} |
|
|
|
</Button> |
|
|
|
) : null} |
|
|
|
</Stack> |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
{/* Lot QR Code Button */} |
|
|
|
{Number(lot.stockOutLineId) > 0 && Number(lot.itemId) > 0 && ( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
size="small" |
|
|
|
onClick={() => openWorkbenchLotLabelModalForLot(lot)} |
|
|
|
sx={{ |
|
|
|
flexShrink: 0, |
|
|
|
fontSize: "0.75rem", |
|
|
|
py: 0.25, |
|
|
|
minWidth: "auto", |
|
|
|
px: 1, |
|
|
|
whiteSpace: "nowrap", |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("lot QR code")} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
</Stack> |
|
|
|
</TableCell> |
|
|
|
<TableCell align="right"> |
|
|
|
{(() => { |
|
|
|
const requiredQty = lot.requiredQty || 0; |
|
|
|
@@ -4219,22 +4257,14 @@ paginatedData.map((row, index) => { |
|
|
|
} |
|
|
|
|
|
|
|
// 過期批號:與 noLot 同類——視為已掃到/可處理(含 pending),顯示警示色勾選 |
|
|
|
if (isLotAvailabilityExpired(lot) && status !== "rejected") { |
|
|
|
return ( |
|
|
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}> |
|
|
|
<Checkbox |
|
|
|
checked={true} |
|
|
|
disabled={true} |
|
|
|
readOnly={true} |
|
|
|
size="large" |
|
|
|
sx={{ |
|
|
|
color: "warning.main", |
|
|
|
"&.Mui-checked": { color: "warning.main" }, |
|
|
|
transform: "scale(1.3)", |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
); |
|
|
|
if ( |
|
|
|
isLotAvailabilityExpired(lot) && |
|
|
|
status !== "rejected" && |
|
|
|
status !== "completed" && |
|
|
|
status !== "partially_completed" && |
|
|
|
status !== "partially_complete" |
|
|
|
) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 正常 lot:已扫描(checked/partially_completed/completed) |
|
|
|
|