| @@ -903,8 +903,24 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO | |||||
| // 将层级数据转换为平铺格式(用于表格显示) | // 将层级数据转换为平铺格式(用于表格显示) | ||||
| const flatLotData: any[] = []; | const flatLotData: any[] = []; | ||||
| mergedPickOrder.pickOrderLines.forEach((line: any) => { | |||||
| // 2/F 與後端 store_id 一致時需按 itemOrder;避免 API 未走 2F 分支時畫面仍亂序 | |||||
| const doFloorKey = String(hierarchicalData.fgInfo.storeId ?? '') | |||||
| .trim() | |||||
| .toUpperCase() | |||||
| .replace(/\//g, '') | |||||
| .replace(/\s/g, ''); | |||||
| const pickOrderLinesForDisplay = | |||||
| doFloorKey === '2F' | |||||
| ? [...(mergedPickOrder.pickOrderLines || [])].sort((a: any, b: any) => { | |||||
| const ao = a.itemOrder != null ? Number(a.itemOrder) : 999999; | |||||
| const bo = b.itemOrder != null ? Number(b.itemOrder) : 999999; | |||||
| if (ao !== bo) return ao - bo; | |||||
| return (Number(a.id) || 0) - (Number(b.id) || 0); | |||||
| }) | |||||
| : mergedPickOrder.pickOrderLines || []; | |||||
| pickOrderLinesForDisplay.forEach((line: any) => { | |||||
| // 用来记录这一行已经通过 lots 出现过的 lotId | // 用来记录这一行已经通过 lots 出现过的 lotId | ||||
| const lotIdSet = new Set<number>(); | const lotIdSet = new Set<number>(); | ||||
| @@ -2769,7 +2785,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| if (cumulativeQty >= lot.requiredQty) { | if (cumulativeQty >= lot.requiredQty) { | ||||
| newStatus = 'completed'; | newStatus = 'completed'; | ||||
| } else if (cumulativeQty > 0) { | } else if (cumulativeQty > 0) { | ||||
| newStatus = 'completed'; | |||||
| newStatus = 'partially_completed'; | |||||
| } else { | } else { | ||||
| newStatus = 'checked'; // QR scanned but no quantity submitted yet | newStatus = 'checked'; // QR scanned but no quantity submitted yet | ||||
| } | } | ||||
| @@ -2786,18 +2802,12 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe | |||||
| await updateStockOutLineStatus({ | await updateStockOutLineStatus({ | ||||
| id: lot.stockOutLineId, | id: lot.stockOutLineId, | ||||
| status: newStatus, | status: newStatus, | ||||
| qty: cumulativeQty // Use cumulative quantity | |||||
| // 后端 updateStatus 的 qty 是“增量 delta”,不能传 cumulativeQty(否则会重复累加导致 out/hold 大幅偏移) | |||||
| qty: submitQty | |||||
| }); | }); | ||||
| applyLocalStockOutLineUpdate(Number(lot.stockOutLineId), newStatus, cumulativeQty); | applyLocalStockOutLineUpdate(Number(lot.stockOutLineId), newStatus, cumulativeQty); | ||||
| if (submitQty > 0) { | |||||
| await updateInventoryLotLineQuantities({ | |||||
| inventoryLotLineId: lot.lotId, | |||||
| qty: submitQty, | |||||
| status: 'available', | |||||
| operation: 'pick' | |||||
| }); | |||||
| } | |||||
| // 注意:库存过账(hold->out)与 ledger 由后端 updateStatus 内部统一处理; | |||||
| // 前端不再额外调用 updateInventoryLotLineQuantities(operation='pick'),避免 double posting。 | |||||
| // Check if pick order is completed when lot status becomes 'completed' | // Check if pick order is completed when lot status becomes 'completed' | ||||
| if (newStatus === 'completed' && lot.pickOrderConsoCode) { | if (newStatus === 'completed' && lot.pickOrderConsoCode) { | ||||
| @@ -42,7 +42,16 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| return ( | return ( | ||||
| <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> | |||||
| <Dialog | |||||
| open={open} | |||||
| onClose={onClose} | |||||
| maxWidth="md" | |||||
| fullWidth | |||||
| disableScrollLock | |||||
| disableAutoFocus | |||||
| disableEnforceFocus | |||||
| disableRestoreFocus | |||||
| > | |||||
| <DialogTitle> | <DialogTitle> | ||||
| <Typography variant="h6" component="div" color="warning.main"> | <Typography variant="h6" component="div" color="warning.main"> | ||||
| {t("Lot Number Mismatch")} | {t("Lot Number Mismatch")} | ||||
| @@ -252,20 +252,16 @@ useEffect(() => { | |||||
| && (formData.badItemQty == null || formData.badItemQty === 0) | && (formData.badItemQty == null || formData.badItemQty === 0) | ||||
| && (badPackageQty === 0); | && (badPackageQty === 0); | ||||
| if (isNormalPick) { | |||||
| if (onNormalPickSubmit) { | |||||
| setLoading(true); | |||||
| try { | |||||
| console.log('Calling onNormalPickSubmit with:', { lot: selectedLot, submitQty: verifiedQty }); | |||||
| await onNormalPickSubmit(selectedLot, verifiedQty); | |||||
| onClose(); | |||||
| } catch (error) { | |||||
| console.error('Error submitting normal pick:', error); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | |||||
| } else { | |||||
| console.warn('onNormalPickSubmit callback not provided'); | |||||
| if (isNormalPick && onNormalPickSubmit) { | |||||
| setLoading(true); | |||||
| try { | |||||
| console.log('Calling onNormalPickSubmit with:', { lot: selectedLot, submitQty: verifiedQty }); | |||||
| await onNormalPickSubmit(selectedLot, verifiedQty); | |||||
| onClose(); | |||||
| } catch (error) { | |||||
| console.error('Error submitting normal pick:', error); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -42,7 +42,16 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| return ( | return ( | ||||
| <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> | |||||
| <Dialog | |||||
| open={open} | |||||
| onClose={onClose} | |||||
| maxWidth="md" | |||||
| fullWidth | |||||
| disableScrollLock | |||||
| disableAutoFocus | |||||
| disableEnforceFocus | |||||
| disableRestoreFocus | |||||
| > | |||||
| <DialogTitle> | <DialogTitle> | ||||
| <Typography variant="h6" component="div" color="warning.main"> | <Typography variant="h6" component="div" color="warning.main"> | ||||
| {t("Lot Number Mismatch")} | {t("Lot Number Mismatch")} | ||||
| @@ -861,8 +861,6 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| try { | try { | ||||
| if (!pickOrderId) { | if (!pickOrderId) { | ||||
| console.warn("⚠️ No pickOrderId provided, skipping API call"); | console.warn("⚠️ No pickOrderId provided, skipping API call"); | ||||
| setJobOrderData(null); | |||||
| setIssuePickedQtyBySolId({}); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -1937,7 +1935,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| if (cumulativeQty >= lot.requiredQty) { | if (cumulativeQty >= lot.requiredQty) { | ||||
| newStatus = "completed"; | newStatus = "completed"; | ||||
| } else if (cumulativeQty > 0) { | } else if (cumulativeQty > 0) { | ||||
| newStatus = "completed"; | |||||
| newStatus = "partially_completed"; | |||||
| } else { | } else { | ||||
| newStatus = "checked"; | newStatus = "checked"; | ||||
| } | } | ||||
| @@ -1954,7 +1952,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| await updateStockOutLineStatus({ | await updateStockOutLineStatus({ | ||||
| id: lot.stockOutLineId, | id: lot.stockOutLineId, | ||||
| status: newStatus, | status: newStatus, | ||||
| qty: cumulativeQty | |||||
| // 后端 updateStatus 的 qty 是“增量 delta”,不能传 cumulativeQty(否则会重复累加导致 out/hold 大幅偏移) | |||||
| qty: submitQty | |||||
| }); | }); | ||||
| if (solId > 0) { | if (solId > 0) { | ||||
| setIssuePickedQtyBySolId((prev) => { | setIssuePickedQtyBySolId((prev) => { | ||||
| @@ -1965,15 +1964,8 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| }); | }); | ||||
| setLocalSolStatusById(prev => ({ ...prev, [solId]: newStatus })); | setLocalSolStatusById(prev => ({ ...prev, [solId]: newStatus })); | ||||
| } | } | ||||
| if (submitQty > 0) { | |||||
| await updateInventoryLotLineQuantities({ | |||||
| inventoryLotLineId: lot.lotId, | |||||
| qty: submitQty, | |||||
| status: 'available', | |||||
| operation: 'pick' | |||||
| }); | |||||
| } | |||||
| // 注意:库存过账(hold->out)与 ledger 由后端 updateStatus 内部统一处理; | |||||
| // 前端不再额外调用 updateInventoryLotLineQuantities(operation='pick'),避免 double posting。 | |||||
| // Check if pick order is completed when lot status becomes 'completed' | // Check if pick order is completed when lot status becomes 'completed' | ||||
| if (newStatus === 'completed' && lot.pickOrderConsoCode) { | if (newStatus === 'completed' && lot.pickOrderConsoCode) { | ||||
| @@ -2317,7 +2309,10 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| setPickExecutionFormOpen(false); | setPickExecutionFormOpen(false); | ||||
| setSelectedLotForExecutionForm(null); | setSelectedLotForExecutionForm(null); | ||||
| const pickOrderId = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; | |||||
| const pickOrderId = | |||||
| filterArgs?.pickOrderId | |||||
| ? Number(filterArgs.pickOrderId) | |||||
| : Number(selectedLotForExecutionForm?.pickOrderId || 0) || undefined; | |||||
| await fetchJobOrderData(pickOrderId); | await fetchJobOrderData(pickOrderId); | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error submitting pick execution form:", error); | console.error("Error submitting pick execution form:", error); | ||||
| @@ -3039,16 +3034,6 @@ const sortedData = [...sourceData].sort((a, b) => { | |||||
| }} | }} | ||||
| pickOrderId={selectedLotForExecutionForm.pickOrderId} | pickOrderId={selectedLotForExecutionForm.pickOrderId} | ||||
| pickOrderCreateDate={new Date()} | pickOrderCreateDate={new Date()} | ||||
| onNormalPickSubmit={async (lot, submitQty) => { | |||||
| console.log('onNormalPickSubmit called in newJobPickExecution:', { lot, submitQty }); | |||||
| if (!lot) { | |||||
| console.error('Lot is null or undefined'); | |||||
| return; | |||||
| } | |||||
| const lotKey = `${lot.pickOrderLineId}-${lot.lotId}`; | |||||
| handlePickQtyChange(lotKey, submitQty); | |||||
| await handleSubmitPickQtyWithQty(lot, submitQty); | |||||
| }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| </FormProvider> | </FormProvider> | ||||