From 511f669a0b7685420b2ab350edf9d7d4ac73544c Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 29 Sep 2025 20:45:02 +0800 Subject: [PATCH] update --- .../GoodPickExecutionForm.tsx | 201 +++++++++--------- .../GoodPickExecutiondetail.tsx | 22 +- src/i18n/zh/jo.json | 2 +- src/i18n/zh/pickOrder.json | 5 +- 4 files changed, 118 insertions(+), 112 deletions(-) diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index b7fe86d..89d50b6 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -20,7 +20,7 @@ import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { GetPickOrderLineInfo, PickExecutionIssueData } from "@/app/api/pickOrder/actions"; import { fetchEscalationCombo } from "@/app/api/user/actions"; - +import { useRef } from "react"; interface LotPickData { id: number; lotId: number; @@ -81,7 +81,6 @@ const PickExecutionForm: React.FC = ({ const [errors, setErrors] = useState({}); const [loading, setLoading] = useState(false); const [handlers, setHandlers] = useState>([]); - // 计算剩余可用数量 const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { const remainingQty = lot.inQty - lot.outQty; @@ -92,7 +91,18 @@ const PickExecutionForm: React.FC = ({ // The actualPickQty in the form should be independent of the database value return lot.requiredQty || 0; }, []); - + const remaining = selectedLot ? calculateRemainingAvailableQty(selectedLot) : 0; + const req = selectedLot ? calculateRequiredQty(selectedLot) : 0; + + const ap = Number(formData.actualPickQty) || 0; + const miss = Number(formData.missQty) || 0; + const bad = Number(formData.badItemQty) || 0; + + // Max the user can type + const maxPick = Math.min(remaining, req); + const maxIssueTotal = Math.max(0, req - ap); // remaining room for miss+bad + + const clamp0 = (v: any) => Math.max(0, Number(v) || 0); // 获取处理人员列表 useEffect(() => { const fetchHandlers = async () => { @@ -107,55 +117,49 @@ const PickExecutionForm: React.FC = ({ fetchHandlers(); }, []); - // 初始化表单数据 - 每次打开时都重新初始化 + const initKeyRef = useRef(null); + useEffect(() => { - if (open && selectedLot && selectedPickOrderLine && pickOrderId) { - const getSafeDate = (dateValue: any): string => { - if (!dateValue) return new Date().toISOString().split('T')[0]; - try { - const date = new Date(dateValue); - if (isNaN(date.getTime())) { - return new Date().toISOString().split('T')[0]; - } - return date.toISOString().split('T')[0]; - } catch { - return new Date().toISOString().split('T')[0]; - } - }; + if (!open || !selectedLot || !selectedPickOrderLine || !pickOrderId) return; - // 计算剩余可用数量 - const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); - const requiredQty = calculateRequiredQty(selectedLot); - console.log("=== PickExecutionForm Debug ==="); - console.log("selectedLot:", selectedLot); - console.log("inQty:", selectedLot.inQty); - console.log("outQty:", selectedLot.outQty); - console.log("holdQty:", selectedLot.holdQty); - console.log("availableQty:", selectedLot.availableQty); - console.log("calculated remainingAvailableQty:", remainingAvailableQty); - console.log("=== End Debug ==="); - setFormData({ - pickOrderId: pickOrderId, - pickOrderCode: selectedPickOrderLine.pickOrderCode, - pickOrderCreateDate: getSafeDate(pickOrderCreateDate), - pickExecutionDate: new Date().toISOString().split('T')[0], - pickOrderLineId: selectedPickOrderLine.id, - itemId: selectedPickOrderLine.itemId, - itemCode: selectedPickOrderLine.itemCode, - itemDescription: selectedPickOrderLine.itemName, - lotId: selectedLot.lotId, - lotNo: selectedLot.lotNo, - storeLocation: selectedLot.location, - requiredQty: selectedLot.requiredQty, - actualPickQty: selectedLot.actualPickQty || 0, - missQty: 0, - badItemQty: 0, // 初始化为 0,用户需要手动输入 - issueRemark: '', - pickerName: '', - handledBy: undefined, - }); - } - }, [open, selectedLot, selectedPickOrderLine, pickOrderId, pickOrderCreateDate, calculateRemainingAvailableQty]); + // Only initialize once per (pickOrderLineId + lotId) while dialog open + const key = `${selectedPickOrderLine.id}-${selectedLot.lotId}`; + if (initKeyRef.current === key) return; + + const getSafeDate = (dateValue: any): string => { + if (!dateValue) return new Date().toISOString().split('T')[0]; + try { + const d = new Date(dateValue); + return isNaN(d.getTime()) ? new Date().toISOString().split('T')[0] : d.toISOString().split('T')[0]; + } catch { + return new Date().toISOString().split('T')[0]; + } + }; + + setFormData({ + pickOrderId: pickOrderId, + pickOrderCode: selectedPickOrderLine.pickOrderCode, + pickOrderCreateDate: getSafeDate(pickOrderCreateDate), + pickExecutionDate: new Date().toISOString().split('T')[0], + pickOrderLineId: selectedPickOrderLine.id, + itemId: selectedPickOrderLine.itemId, + itemCode: selectedPickOrderLine.itemCode, + itemDescription: selectedPickOrderLine.itemName, + lotId: selectedLot.lotId, + lotNo: selectedLot.lotNo, + storeLocation: selectedLot.location, + requiredQty: selectedLot.requiredQty, + actualPickQty: selectedLot.actualPickQty || 0, + missQty: 0, + badItemQty: 0, + issueRemark: '', + pickerName: '', + handledBy: undefined, + }); + + initKeyRef.current = key; + }, [open, selectedPickOrderLine?.id, selectedLot?.lotId, pickOrderId, pickOrderCreateDate]); + // Mutually exclusive inputs: picking vs reporting issues const handleInputChange = useCallback((field: keyof PickExecutionIssueData, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); @@ -168,30 +172,23 @@ const PickExecutionForm: React.FC = ({ // ✅ Update form validation to require either missQty > 0 OR badItemQty > 0 const validateForm = (): boolean => { const newErrors: FormErrors = {}; - - if (formData.actualPickQty === undefined || formData.actualPickQty < 0) { - newErrors.actualPickQty = t('Qty is required'); - } - - // ✅ FIXED: Check if actual pick qty exceeds remaining available qty - if (formData.actualPickQty && formData.actualPickQty > remainingAvailableQty) { - newErrors.actualPickQty = t('Qty is not allowed to be greater than remaining available qty'); - } - - // ✅ FIXED: Check if actual pick qty exceeds required qty (use original required qty) - if (formData.actualPickQty && formData.actualPickQty > (selectedLot?.requiredQty || 0)) { - newErrors.actualPickQty = t('Qty is not allowed to be greater than required qty'); + const req = selectedLot?.requiredQty || 0; + const ap = formData.actualPickQty || 0; + const miss = formData.missQty || 0; + const bad = formData.badItemQty || 0; + + if (ap < 0) newErrors.actualPickQty = t('Qty is required'); + if (ap > Math.min(remainingAvailableQty, req)) newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); + if (miss < 0) newErrors.missQty = t('Invalid qty'); + if (bad < 0) newErrors.badItemQty = t('Invalid qty'); + if (ap + miss + bad > req) { + newErrors.actualPickQty = t('Total exceeds required qty'); + newErrors.missQty = t('Total exceeds required qty'); } - - // ✅ NEW: Require either missQty > 0 OR badItemQty > 0 (at least one issue must be reported) - const hasMissQty = formData.missQty && formData.missQty > 0; - const hasBadItemQty = formData.badItemQty && formData.badItemQty > 0; - - if (!hasMissQty && !hasBadItemQty) { - newErrors.missQty = t('At least one issue must be reported'); - newErrors.badItemQty = t('At least one issue must be reported'); + if (ap === 0 && miss === 0 && bad === 0) { + newErrors.actualPickQty = t('Enter pick qty or issue qty'); + newErrors.missQty = t('Enter pick qty or issue qty'); } - setErrors(newErrors); return Object.keys(newErrors).length === 0; }; @@ -266,42 +263,42 @@ const PickExecutionForm: React.FC = ({ - handleInputChange('actualPickQty', parseFloat(e.target.value) || 0)} - error={!!errors.actualPickQty} - helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`} - variant="outlined" - /> + handleInputChange('actualPickQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + error={!!errors.actualPickQty} + helperText={errors.actualPickQty || `${t('Max')}: ${Math.min(remainingAvailableQty, selectedLot?.requiredQty || 0)}`} + variant="outlined" + /> - handleInputChange('missQty', parseFloat(e.target.value) || 0)} - error={!!errors.missQty} - // helperText={errors.missQty || t('Enter missing quantity (required if no bad items)')} - variant="outlined" - /> + handleInputChange('missQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + error={!!errors.missQty} + variant="outlined" + //disabled={(formData.actualPickQty || 0) > 0} + /> - handleInputChange('badItemQty', parseFloat(e.target.value) || 0)} - error={!!errors.badItemQty} - // helperText={errors.badItemQty || t('Enter bad item quantity (required if no missing items)')} - variant="outlined" - /> + handleInputChange('badItemQty', e.target.value === '' ? undefined : Math.max(0, Number(e.target.value) || 0))} + error={!!errors.badItemQty} + variant="outlined" + //disabled={(formData.actualPickQty || 0) > 0} + /> {/* ✅ Show issue description and handler fields when bad items > 0 */} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 1af86fc..b3a8912 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -586,12 +586,20 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); setQrScanError(false); setQrScanSuccess(false); setQrScanInput(''); - setIsManualScanning(false); - stopScan(); - resetScan(); + //setIsManualScanning(false); + //stopScan(); + //resetScan(); setProcessedQrCodes(new Set()); setLastProcessedQr(''); - + setQrModalOpen(false); + setPickExecutionFormOpen(false); + if(selectedLotForQr?.stockOutLineId){ + const stockOutLineUpdate = await updateStockOutLineStatus({ + id: selectedLotForQr.stockOutLineId, + status: 'checked', + qty: 0 + }); + } setLotConfirmationOpen(false); setExpectedLotData(null); setScannedLotData(null); @@ -709,9 +717,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false); setQrScanSuccess(true); setQrScanError(false); setQrScanInput(''); // Clear input after successful processing - setIsManualScanning(false); - stopScan(); - resetScan(); + //setIsManualScanning(false); + // stopScan(); + // resetScan(); // ✅ Clear success state after a delay //setTimeout(() => { diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 176ef94..bcd383b 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -33,7 +33,7 @@ "Pick Order Code": "提料單編號", "Target Date": "需求日期", "Lot Required Pick Qty": "批號需求數量", - "Job Order Match": "工單匹配", + "Job Order Match": "工單對料", "Lot No": "批號", "Submit Required Pick Qty": "提交需求數量", "All Pick Order Lots": "所有提料單批號", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 70c8909..4dc9389 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -278,7 +278,7 @@ "QR code verified.":"QR 碼驗證成功。", "Order Finished":"訂單完成", "Submitted Status":"提交狀態", - "Finished Good Record":"成單記錄", + "Finished Good Record":"已完成出倉記錄", "Delivery No.":"送貨單編號", "Total":"總數", "completed DO pick orders":"已完成送貨單提料單", @@ -290,7 +290,8 @@ "Back to List":"返回列表", "No completed DO pick orders found":"沒有已完成送貨單提料單", "Enter the number of cartons: ": "請輸入總箱數", - "Number of cartons": "箱數" + "Number of cartons": "箱數", + "Total exceeds required qty":"總數超出所需數量"