|
|
|
@@ -71,6 +71,40 @@ interface Props { |
|
|
|
onBackToList?: () => void; |
|
|
|
} |
|
|
|
|
|
|
|
/** 過期批號:與 noLot 類似——單筆/批量預設 0,除非 Issue 改數(對齊 GoodPickExecutiondetail) */ |
|
|
|
function isLotAvailabilityExpired(lot: any): boolean { |
|
|
|
return String(lot?.lotAvailability || "").toLowerCase() === "expired"; |
|
|
|
} |
|
|
|
|
|
|
|
const JO_ISSUE_PICKED_KEY = (pickOrderId: number) => |
|
|
|
`fpsms-jo-issuePickedQty:${pickOrderId}`; |
|
|
|
|
|
|
|
function loadIssuePickedMapJo(pickOrderId: number): Record<number, number> { |
|
|
|
if (typeof window === "undefined" || !pickOrderId) return {}; |
|
|
|
try { |
|
|
|
const raw = sessionStorage.getItem(JO_ISSUE_PICKED_KEY(pickOrderId)); |
|
|
|
if (!raw) return {}; |
|
|
|
const parsed = JSON.parse(raw) as Record<string, number>; |
|
|
|
const out: Record<number, number> = {}; |
|
|
|
Object.entries(parsed).forEach(([k, v]) => { |
|
|
|
const n = Number(v); |
|
|
|
if (!Number.isNaN(n)) out[Number(k)] = n; |
|
|
|
}); |
|
|
|
return out; |
|
|
|
} catch { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function saveIssuePickedMapJo(pickOrderId: number, map: Record<number, number>) { |
|
|
|
if (typeof window === "undefined" || !pickOrderId) return; |
|
|
|
try { |
|
|
|
sessionStorage.setItem(JO_ISSUE_PICKED_KEY(pickOrderId), JSON.stringify(map)); |
|
|
|
} catch { |
|
|
|
// ignore quota / private mode |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Manual Lot Confirmation Modal (align with GoodPickExecutiondetail, opened by {2fic}) |
|
|
|
const ManualLotConfirmationModal: React.FC<{ |
|
|
|
open: boolean; |
|
|
|
@@ -481,6 +515,9 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
const formProps = useForm(); |
|
|
|
const errors = formProps.formState.errors; |
|
|
|
const [isSubmittingAll, setIsSubmittingAll] = useState<boolean>(false); |
|
|
|
const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle'); |
|
|
|
const [completionStatus, setCompletionStatus] = useState<PickOrderCompletionResponse | null>(null); |
|
|
|
const [autoAssignMessage, setAutoAssignMessage] = useState<string>(''); |
|
|
|
|
|
|
|
// Add QR modal states |
|
|
|
const [qrModalOpen, setQrModalOpen] = useState(false); |
|
|
|
@@ -825,6 +862,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
if (!pickOrderId) { |
|
|
|
console.warn("⚠️ No pickOrderId provided, skipping API call"); |
|
|
|
setJobOrderData(null); |
|
|
|
setIssuePickedQtyBySolId({}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
@@ -833,14 +871,16 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
console.log("✅ Job Order data (hierarchical):", jobOrderData); |
|
|
|
|
|
|
|
setJobOrderData(jobOrderData); |
|
|
|
setIssuePickedQtyBySolId(loadIssuePickedMapJo(pickOrderId)); |
|
|
|
|
|
|
|
// 使用辅助函数获取所有 lots(不再扁平化) |
|
|
|
const allLots = getAllLotsFromHierarchical(jobOrderData); |
|
|
|
getAllLotsFromHierarchical(jobOrderData); |
|
|
|
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
console.error("❌ Error fetching job order data:", error); |
|
|
|
setJobOrderData(null); |
|
|
|
setIssuePickedQtyBySolId({}); |
|
|
|
} finally { |
|
|
|
setCombinedDataLoading(false); |
|
|
|
} |
|
|
|
@@ -1703,9 +1743,30 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
})); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const [autoAssignStatus, setAutoAssignStatus] = useState<'idle' | 'checking' | 'assigned' | 'no_orders'>('idle'); |
|
|
|
const [autoAssignMessage, setAutoAssignMessage] = useState<string>(''); |
|
|
|
const [completionStatus, setCompletionStatus] = useState<PickOrderCompletionResponse | null>(null); |
|
|
|
/** 单笔「提交」数量:Issue 改数 → pickQtyData → noLot/過期 → 0 → 否则 required(對齊 GoodPickExecutiondetail) */ |
|
|
|
const resolveSingleSubmitQty = useCallback( |
|
|
|
(lot: any) => { |
|
|
|
const required = Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); |
|
|
|
const solId = Number(lot.stockOutLineId) || 0; |
|
|
|
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; |
|
|
|
const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined; |
|
|
|
if (issuePicked !== undefined && !Number.isNaN(Number(issuePicked))) { |
|
|
|
return Number(issuePicked); |
|
|
|
} |
|
|
|
const fromPick = pickQtyData[lotKey]; |
|
|
|
if (fromPick !== undefined && fromPick !== null && !Number.isNaN(Number(fromPick))) { |
|
|
|
return Number(fromPick); |
|
|
|
} |
|
|
|
if (lot.noLot === true || !lot.lotId) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
if (isLotAvailabilityExpired(lot)) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
return required; |
|
|
|
}, |
|
|
|
[issuePickedQtyBySolId, pickQtyData] |
|
|
|
); |
|
|
|
|
|
|
|
const checkAndAutoAssignNext = useCallback(async () => { |
|
|
|
if (!currentUserId) return; |
|
|
|
@@ -1847,7 +1908,12 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
|
|
|
|
// 记录该 SOL 的“目标实际拣货量=0”,让 batch submit 走 onlyComplete(不补拣到 required) |
|
|
|
if (solId > 0) { |
|
|
|
setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: 0 })); |
|
|
|
setIssuePickedQtyBySolId((prev) => { |
|
|
|
const next = { ...prev, [solId]: 0 }; |
|
|
|
const pid = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; |
|
|
|
if (pid) saveIssuePickedMapJo(pid, next); |
|
|
|
return next; |
|
|
|
}); |
|
|
|
setLocalSolStatusById(prev => ({ ...prev, [solId]: 'checked' })); |
|
|
|
} |
|
|
|
|
|
|
|
@@ -1866,15 +1932,14 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
const currentActualPickQty = lot.actualPickQty || 0; |
|
|
|
const cumulativeQty = currentActualPickQty + submitQty; |
|
|
|
|
|
|
|
// Determine status based on cumulative quantity vs required quantity |
|
|
|
let newStatus = 'partially_completed'; |
|
|
|
|
|
|
|
// 短拣一次 completed(對齊 GoodPickExecutiondetail) |
|
|
|
let newStatus = "partially_completed"; |
|
|
|
if (cumulativeQty >= lot.requiredQty) { |
|
|
|
newStatus = 'completed'; |
|
|
|
newStatus = "completed"; |
|
|
|
} else if (cumulativeQty > 0) { |
|
|
|
newStatus = 'partially_completed'; |
|
|
|
newStatus = "completed"; |
|
|
|
} else { |
|
|
|
newStatus = 'checked'; // QR scanned but no quantity submitted yet |
|
|
|
newStatus = "checked"; |
|
|
|
} |
|
|
|
|
|
|
|
console.log(`=== PICK QUANTITY SUBMISSION DEBUG ===`); |
|
|
|
@@ -1892,7 +1957,12 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
qty: cumulativeQty |
|
|
|
}); |
|
|
|
if (solId > 0) { |
|
|
|
setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: cumulativeQty })); |
|
|
|
setIssuePickedQtyBySolId((prev) => { |
|
|
|
const next = { ...prev, [solId]: cumulativeQty }; |
|
|
|
const pid = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; |
|
|
|
if (pid) saveIssuePickedMapJo(pid, next); |
|
|
|
return next; |
|
|
|
}); |
|
|
|
setLocalSolStatusById(prev => ({ ...prev, [solId]: newStatus })); |
|
|
|
} |
|
|
|
|
|
|
|
@@ -1966,29 +2036,25 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
return () => window.removeEventListener("beforeunload", handler); |
|
|
|
}, [hasPendingBatchSubmit]); |
|
|
|
const handleSubmitAllScanned = useCallback(async () => { |
|
|
|
const scannedLots = combinedLotData.filter(lot => { |
|
|
|
const scannedLots = combinedLotData.filter((lot) => { |
|
|
|
const status = lot.stockOutLineStatus; |
|
|
|
const statusLower = String(status || "").toLowerCase(); |
|
|
|
if (statusLower === "completed" || statusLower === "complete") { |
|
|
|
return false; |
|
|
|
} |
|
|
|
console.log("lot.noLot:", lot.noLot); |
|
|
|
console.log("lot.status:", lot.stockOutLineStatus); |
|
|
|
// ✅ no-lot:允許 pending / checked / partially_completed / PARTIALLY_COMPLETE |
|
|
|
if (lot.noLot === true || !lot.lotId) { |
|
|
|
// noLot / 過期批號:允許 pending(對齊 GoodPickExecutiondetail) |
|
|
|
if (lot.noLot === true || !lot.lotId || isLotAvailabilityExpired(lot)) { |
|
|
|
return ( |
|
|
|
status === 'checked' || |
|
|
|
status === 'pending' || |
|
|
|
status === 'partially_completed' || |
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
status === "checked" || |
|
|
|
status === "pending" || |
|
|
|
status === "partially_completed" || |
|
|
|
status === "PARTIALLY_COMPLETE" |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ 有 lot:維持原本規則 |
|
|
|
return ( |
|
|
|
status === 'checked' || |
|
|
|
status === 'partially_completed' || |
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
status === "checked" || |
|
|
|
status === "partially_completed" || |
|
|
|
status === "PARTIALLY_COMPLETE" |
|
|
|
); |
|
|
|
}); |
|
|
|
if (scannedLots.length === 0) { |
|
|
|
@@ -2016,44 +2082,43 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { |
|
|
|
console.log(`✅ Updated handlers for ${uniqueItemIds.size} unique items`); |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ 转换为 batchSubmitList 所需的格式 |
|
|
|
const lines: batchSubmitListLineRequest[] = scannedLots.map((lot) => { |
|
|
|
const requiredQty = Number(lot.requiredQty || lot.pickOrderLineRequiredQty || 0); |
|
|
|
const solId = Number(lot.stockOutLineId) || 0; |
|
|
|
const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined; |
|
|
|
const currentActualPickQty = Number(issuePicked ?? lot.actualPickQty ?? 0); |
|
|
|
const isNoLot = lot.noLot === true || !lot.lotId; |
|
|
|
|
|
|
|
// ✅ 只改狀態模式:有 issuePicked 或 noLot |
|
|
|
const onlyComplete = |
|
|
|
lot.stockOutLineStatus === 'partially_completed' || |
|
|
|
issuePicked !== undefined || |
|
|
|
isNoLot; |
|
|
|
|
|
|
|
let targetActual: number; |
|
|
|
let newStatus: string; |
|
|
|
|
|
|
|
if (onlyComplete) { |
|
|
|
targetActual = currentActualPickQty; // no‑lot = 0,一律只改狀態 |
|
|
|
newStatus = 'completed'; |
|
|
|
} else { |
|
|
|
const remainingQty = Math.max(0, requiredQty - currentActualPickQty); |
|
|
|
targetActual = currentActualPickQty + remainingQty; |
|
|
|
newStatus = |
|
|
|
requiredQty > 0 && targetActual >= requiredQty |
|
|
|
? 'completed' |
|
|
|
: 'partially_completed'; |
|
|
|
} |
|
|
|
|
|
|
|
const solId = Number(lot.stockOutLineId) || 0; |
|
|
|
const issuePicked = solId > 0 ? issuePickedQtyBySolId[solId] : undefined; |
|
|
|
const currentActualPickQty = Number(issuePicked ?? lot.actualPickQty ?? 0); |
|
|
|
const onlyComplete = |
|
|
|
lot.stockOutLineStatus === "partially_completed" || issuePicked !== undefined; |
|
|
|
const expired = isLotAvailabilityExpired(lot); |
|
|
|
|
|
|
|
let targetActual: number; |
|
|
|
let newStatus: string; |
|
|
|
|
|
|
|
if (expired && issuePicked === undefined) { |
|
|
|
targetActual = 0; |
|
|
|
newStatus = "completed"; |
|
|
|
} else if (onlyComplete) { |
|
|
|
targetActual = currentActualPickQty; |
|
|
|
newStatus = "completed"; |
|
|
|
} else { |
|
|
|
const remainingQty = Math.max(0, requiredQty - currentActualPickQty); |
|
|
|
const cumulativeQty = currentActualPickQty + remainingQty; |
|
|
|
targetActual = cumulativeQty; |
|
|
|
newStatus = "partially_completed"; |
|
|
|
if (requiredQty > 0 && cumulativeQty >= requiredQty) { |
|
|
|
newStatus = "completed"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
stockOutLineId: Number(lot.stockOutLineId) || 0, |
|
|
|
pickOrderLineId: Number(lot.pickOrderLineId), |
|
|
|
inventoryLotLineId: lot.lotId ? Number(lot.lotId) : null, |
|
|
|
requiredQty, |
|
|
|
actualPickQty: Number(targetActual), |
|
|
|
actualPickQty: targetActual, |
|
|
|
stockOutLineStatus: newStatus, |
|
|
|
pickOrderConsoCode: String(lot.pickOrderConsoCode || ''), |
|
|
|
noLot: Boolean(lot.noLot === true) |
|
|
|
pickOrderConsoCode: String(lot.pickOrderConsoCode || ""), |
|
|
|
noLot: Boolean(lot.noLot === true), |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
@@ -2118,29 +2183,24 @@ if (onlyComplete) { |
|
|
|
} |
|
|
|
}, [combinedLotData, fetchJobOrderData, checkAndAutoAssignNext, currentUserId, filterArgs?.pickOrderId, onBackToList, updateHandledBy, issuePickedQtyBySolId]) |
|
|
|
const scannedItemsCount = useMemo(() => { |
|
|
|
return combinedLotData.filter(lot => { |
|
|
|
return combinedLotData.filter((lot) => { |
|
|
|
const status = lot.stockOutLineStatus; |
|
|
|
const statusLower = String(status || "").toLowerCase(); |
|
|
|
if (statusLower === "completed" || statusLower === "complete") { |
|
|
|
return false; |
|
|
|
} |
|
|
|
const isNoLot = lot.noLot === true || !lot.lotId; |
|
|
|
|
|
|
|
if (isNoLot) { |
|
|
|
// no-lot:pending / checked / partially_completed 都算「已掃描」 |
|
|
|
if (lot.noLot === true || !lot.lotId || isLotAvailabilityExpired(lot)) { |
|
|
|
return ( |
|
|
|
status === 'pending' || |
|
|
|
status === 'checked' || |
|
|
|
status === 'partially_completed' || |
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
status === "checked" || |
|
|
|
status === "pending" || |
|
|
|
status === "partially_completed" || |
|
|
|
status === "PARTIALLY_COMPLETE" |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 有 lot:維持原規則 |
|
|
|
return ( |
|
|
|
status === 'checked' || |
|
|
|
status === 'partially_completed' || |
|
|
|
status === 'PARTIALLY_COMPLETE' |
|
|
|
status === "checked" || |
|
|
|
status === "partially_completed" || |
|
|
|
status === "PARTIALLY_COMPLETE" |
|
|
|
); |
|
|
|
}).length; |
|
|
|
}, [combinedLotData]); |
|
|
|
@@ -2160,13 +2220,16 @@ if (onlyComplete) { |
|
|
|
return combinedLotData.filter(lot => extractFloor(lot) === selectedFloor); |
|
|
|
}, [combinedLotData, selectedFloor]); |
|
|
|
|
|
|
|
// Progress bar data - 现在可以正确引用 filteredByFloor |
|
|
|
// 與批量篩選一致:noLot / 過期 的 pending 也算已處理(對齊 GoodPickExecutiondetail) |
|
|
|
const progress = useMemo(() => { |
|
|
|
const data = selectedFloor ? filteredByFloor : combinedLotData; |
|
|
|
if (data.length === 0) return { completed: 0, total: 0 }; |
|
|
|
const nonPendingCount = data.filter(lot => |
|
|
|
lot.stockOutLineStatus?.toLowerCase() !== 'pending' |
|
|
|
).length; |
|
|
|
const nonPendingCount = data.filter((lot) => { |
|
|
|
const status = lot.stockOutLineStatus?.toLowerCase(); |
|
|
|
if (status !== "pending") return true; |
|
|
|
if (lot.noLot === true || isLotAvailabilityExpired(lot)) return true; |
|
|
|
return false; |
|
|
|
}).length; |
|
|
|
return { completed: nonPendingCount, total: data.length }; |
|
|
|
}, [selectedFloor, filteredByFloor, combinedLotData]); |
|
|
|
// Handle reject lot |
|
|
|
@@ -2240,7 +2303,12 @@ if (onlyComplete) { |
|
|
|
const solId = Number(issueData.stockOutLineId || data?.stockOutLineId); |
|
|
|
if (solId > 0) { |
|
|
|
const picked = Number(issueData.actualPickQty || 0); |
|
|
|
setIssuePickedQtyBySolId(prev => ({ ...prev, [solId]: picked })); |
|
|
|
setIssuePickedQtyBySolId((prev) => { |
|
|
|
const next = { ...prev, [solId]: picked }; |
|
|
|
const pid = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; |
|
|
|
if (pid) saveIssuePickedMapJo(pid, next); |
|
|
|
return next; |
|
|
|
}); |
|
|
|
} |
|
|
|
} else { |
|
|
|
console.error("❌ Failed to record pick execution issue:", result); |
|
|
|
@@ -2254,7 +2322,7 @@ if (onlyComplete) { |
|
|
|
} catch (error) { |
|
|
|
console.error("Error submitting pick execution form:", error); |
|
|
|
} |
|
|
|
}, [fetchJobOrderData, currentUserId, selectedLotForExecutionForm, updateHandledBy, filterArgs?.pickOrderId]); |
|
|
|
}, [fetchJobOrderData, currentUserId, selectedLotForExecutionForm, updateHandledBy, filterArgs?.pickOrderId, filterArgs]); |
|
|
|
|
|
|
|
// Calculate remaining required quantity |
|
|
|
const calculateRemainingRequiredQty = useCallback((lot: any) => { |
|
|
|
@@ -2542,13 +2610,14 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell> |
|
|
|
<TableCell align="right">{t("Available Qty")}</TableCell> |
|
|
|
<TableCell align="center">{t("Scan Result")}</TableCell> |
|
|
|
<TableCell align="center">{t("Qty will submit")}</TableCell> |
|
|
|
<TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> |
|
|
|
</TableRow> |
|
|
|
</TableHead> |
|
|
|
<TableBody> |
|
|
|
{paginatedData.length === 0 ? ( |
|
|
|
<TableRow> |
|
|
|
<TableCell colSpan={9} align="center"> |
|
|
|
<TableCell colSpan={11} align="center"> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
{t("No data available")} |
|
|
|
</Typography> |
|
|
|
@@ -2580,10 +2649,34 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
<TableCell>{lot.itemCode}</TableCell> |
|
|
|
<TableCell>{lot.itemName+'('+lot.uomDesc+')'}</TableCell> |
|
|
|
<TableCell> |
|
|
|
{lot.noLot === true || !lot.lotId |
|
|
|
? t("Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.") // i18n key,下一步加文案 |
|
|
|
: (lot.lotNo || '-')} |
|
|
|
</TableCell> |
|
|
|
<Box> |
|
|
|
<Typography |
|
|
|
sx={{ |
|
|
|
color: |
|
|
|
lot.lotAvailability === "expired" |
|
|
|
? "warning.main" |
|
|
|
: "inherit", |
|
|
|
}} |
|
|
|
> |
|
|
|
{lot.lotNo ? ( |
|
|
|
lot.lotAvailability === "expired" ? ( |
|
|
|
<> |
|
|
|
{lot.lotNo}{" "} |
|
|
|
{t( |
|
|
|
"is expired. Please check around have available QR code or not.", |
|
|
|
)} |
|
|
|
</> |
|
|
|
) : ( |
|
|
|
lot.lotNo |
|
|
|
) |
|
|
|
) : ( |
|
|
|
t( |
|
|
|
"Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.", |
|
|
|
) |
|
|
|
)} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
</TableCell> |
|
|
|
<TableCell align="right"> |
|
|
|
{(() => { |
|
|
|
const requiredQty = lot.requiredQty || 0; |
|
|
|
@@ -2607,8 +2700,7 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
const status = lot.stockOutLineStatus?.toLowerCase(); |
|
|
|
const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; |
|
|
|
const isNoLot = !lot.lotNo; |
|
|
|
|
|
|
|
// ✅ rejected lot:显示红色勾选(已扫描但被拒绝) |
|
|
|
|
|
|
|
if (isRejected && !isNoLot) { |
|
|
|
return ( |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> |
|
|
|
@@ -2626,8 +2718,25 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
</Box> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ 正常 lot:已扫描(checked/partially_completed/completed) |
|
|
|
|
|
|
|
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 (!isNoLot && status !== 'pending' && status !== 'rejected') { |
|
|
|
return ( |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> |
|
|
|
@@ -2645,11 +2754,31 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
</Box> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (isNoLot && (status === 'partially_completed' || status === 'completed')) { |
|
|
|
return ( |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}> |
|
|
|
<Checkbox |
|
|
|
checked={true} |
|
|
|
disabled={true} |
|
|
|
readOnly={true} |
|
|
|
size="large" |
|
|
|
sx={{ |
|
|
|
color: 'error.main', |
|
|
|
'&.Mui-checked': { color: 'error.main' }, |
|
|
|
transform: 'scale(1.3)', |
|
|
|
}} |
|
|
|
/> |
|
|
|
</Box> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
})()} |
|
|
|
</TableCell> |
|
|
|
|
|
|
|
<TableCell align="center">{resolveSingleSubmitQty(lot)}</TableCell> |
|
|
|
|
|
|
|
<TableCell align="center"> |
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'center' }}> |
|
|
|
{(() => { |
|
|
|
@@ -2689,7 +2818,7 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
} |
|
|
|
try { |
|
|
|
const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; |
|
|
|
const submitQty = lot.requiredQty || lot.pickOrderLineRequiredQty; |
|
|
|
const submitQty = resolveSingleSubmitQty(lot); |
|
|
|
handlePickQtyChange(lotKey, submitQty); |
|
|
|
|
|
|
|
await handleSubmitPickQtyWithQty(lot, submitQty); |
|
|
|
@@ -2722,7 +2851,12 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
size="small" |
|
|
|
onClick={() => handlePickExecutionForm(lot)} |
|
|
|
disabled={ |
|
|
|
lot.stockOutLineStatus === 'completed' || lot.noLot === true || !lot.lotId |
|
|
|
lot.lotAvailability === "expired" || |
|
|
|
lot.stockOutLineStatus === "completed" || |
|
|
|
lot.noLot === true || |
|
|
|
!lot.lotId || |
|
|
|
(Number(lot.stockOutLineId) > 0 && |
|
|
|
actionBusyBySolId[Number(lot.stockOutLineId)] === true) |
|
|
|
} |
|
|
|
sx={{ |
|
|
|
fontSize: '0.7rem', |
|
|
|
@@ -2764,13 +2898,12 @@ const sortedData = [...sourceData].sort((a, b) => { |
|
|
|
(Number(lot.stockOutLineId) > 0 && actionBusyBySolId[Number(lot.stockOutLineId)] === true) || |
|
|
|
lot.stockOutLineStatus === 'completed' || |
|
|
|
lot.stockOutLineStatus === 'checked' || |
|
|
|
lot.stockOutLineStatus === 'partially_completed' || |
|
|
|
lot.lotAvailability === 'expired' || |
|
|
|
lot.noLot === true || |
|
|
|
!lot.lotId || |
|
|
|
(Number(lot.stockOutLineId) > 0 && |
|
|
|
Object.prototype.hasOwnProperty.call( |
|
|
|
issuePickedQtyBySolId, |
|
|
|
Number(lot.stockOutLineId) |
|
|
|
)) |
|
|
|
issuePickedQtyBySolId[Number(lot.stockOutLineId)] !== undefined) |
|
|
|
} |
|
|
|
sx={{ fontSize: '0.7rem', py: 0.5, minHeight: '28px', minWidth: '90px' }} |
|
|
|
> |
|
|
|
|