diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index f1cb0f3..23e0e01 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -271,7 +271,30 @@ export interface ProductProcessLineDetailResponse { byproductQty: number, byproductUom: string | undefined, } -export const fetchProductProcessLineDetailByJoid = cache(async (joid: number) => { +export interface ProductProcessLineDetailResponse { + id: number, + productProcessId: number, + bomProcessId: number, + operatorId: number, + equipmentType: string, + operatorName: string, + handlerId: number, + seqNo: number, + name: string, + description: string, + equipment: string, + startTime: string, + endTime: string, + defectQty: number, + defectUom: string, + scrapQty: number, + scrapUom: string, + byproductId: number, + byproductName: string, + byproductQty: number, + byproductUom: string | undefined, +} +export const fetchProductProcessLinesByJoid = cache(async (joid: number) => { return serverFetchJson( `${BASE_API_URL}/product-process/demo/joid/${joid}`, { diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 8a9a76b..1ee2fa8 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -175,29 +175,66 @@ const PickExecutionForm: React.FC = ({ } }, [errors]); - // Update form validation to require either missQty > 0 OR badItemQty > 0 - const validateForm = (): boolean => { - const newErrors: FormErrors = {}; - const req = selectedLot?.requiredQty || 0; - const ap = formData.actualPickQty || 0; - const miss = formData.missQty || 0; - const bad = formData.badItemQty || 0; + // Update form validation to require either missQty > 0 OR badItemQty > 0 +const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + const req = selectedLot?.requiredQty || 0; + const ap = Number(formData.actualPickQty) || 0; + const miss = Number(formData.missQty) || 0; + const bad = Number(formData.badItemQty) || 0; + const total = ap + miss + bad; - 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'); - } - 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; - }; + // 1. 检查 actualPickQty 不能为负数 + if (ap < 0) { + newErrors.actualPickQty = t('Qty cannot be negative'); + } + + // 2. 检查 actualPickQty 不能超过可用数量或需求数量 + if (ap > Math.min(remainingAvailableQty, req)) { + newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); + } + + // 3. 检查 missQty 和 badItemQty 不能为负数 + if (miss < 0) { + newErrors.missQty = t('Invalid qty'); + } + if (bad < 0) { + newErrors.badItemQty = t('Invalid qty'); + } + + // 4. 🔥 关键验证:总和必须等于 Required Qty(不能多也不能少) + if (total !== req) { + const diff = req - total; + const errorMsg = diff > 0 + ? t('Total must equal Required Qty. Missing: {{diff}}', { diff }) + : t('Total must equal Required Qty. Exceeds by: {{diff}}', { diff: Math.abs(diff) }); + newErrors.actualPickQty = errorMsg; + newErrors.missQty = errorMsg; + newErrors.badItemQty = errorMsg; + } + + // 5. 🔥 关键验证:如果只有 actualPickQty 有值,而 missQty 和 badItemQty 都为 0,不允许提交 + // 这意味着如果 actualPickQty < requiredQty,必须报告问题(missQty 或 badItemQty > 0) + if (ap > 0 && miss === 0 && bad === 0 && ap < req) { + newErrors.missQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty'); + newErrors.badItemQty = t('If Actual Pick Qty is less than Required Qty, you must report Missing Qty or Bad Item Qty'); + } + + // 6. 如果所有值都为 0,不允许提交 + 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'); + } + + // 7. 如果 actualPickQty = requiredQty,missQty 和 badItemQty 必须都为 0 + if (ap === req && (miss > 0 || bad > 0)) { + newErrors.missQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0'); + newErrors.badItemQty = t('If Actual Pick Qty equals Required Qty, Missing Qty and Bad Item Qty must be 0'); + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; +}; const handleSubmit = async () => { // First validate the form diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index 66d82ca..c4b5e3a 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -1984,23 +1984,15 @@ paginatedData.map((lot, index) => { - {/* Issue lot 不显示扫描状态 */} - {!isIssueLot && lot.stockOutLineStatus?.toLowerCase() !== 'pending' ? ( - - - - ) : isIssueLot&&lot.stockOutLineStatus?.toLowerCase() == 'partially_completed'||lot.stockOutLineStatus?.toLowerCase() == 'completed' ? ( - + {(() => { + const status = lot.stockOutLineStatus?.toLowerCase(); + const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; + const isNoLot = !lot.lotNo; + + // rejected lot:显示红色勾选(已扫描但被拒绝) + if (isRejected && !isNoLot) { + return ( + { }} /> - ) : null} - - - - - {isIssueLot ? ( - // Issue lot 只显示 Issue 按钮 - - ) : ( - // Normal lot 显示两个按钮 - - - - - - )} + ); + } + + // 正常 lot:已扫描(checked/partially_completed/completed) + if (!isNoLot && status !== 'pending' && status !== 'rejected') { + return ( + + - + ); + } + + // noLot 且已完成/部分完成:显示红色勾选 + if (isNoLot && (status === 'partially_completed' || status === 'completed')) { + return ( + + + + ); + } + + return null; + })()} + + + + {(() => { + const status = lot.stockOutLineStatus?.toLowerCase(); + const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; + const isNoLot = !lot.lotNo; + + // rejected lot:不显示任何按钮 + if (isRejected && !isNoLot) { + return null; + } + + // noLot 情况:只显示 Issue 按钮 + if (isNoLot) { + return ( + + ); + } + + // 正常 lot:显示 Submit 和 Issue 按钮 + return ( + + + + + + ); + })()} + + ); }) diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 3678ca2..6c80296 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -108,7 +108,8 @@ "submit": "確認提交", "print": "列印", "bind": "綁定", - + "Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}", + "Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}", "Pick Order": "提料單", diff --git a/src/i18n/zh/purchaseOrder.json b/src/i18n/zh/purchaseOrder.json index 5320af3..e350450 100644 --- a/src/i18n/zh/purchaseOrder.json +++ b/src/i18n/zh/purchaseOrder.json @@ -166,5 +166,7 @@ "confirm expiry date": "確認到期日", "Invalid Date": "無效日期", "Missing QC Template, please contact administrator": "找不到品檢模板,請聯絡管理員", - "submitting": "提交中..." + "submitting": "提交中...", + "Total must equal Required Qty. Missing": "總數量必須等於所需數量。缺少:{{diff}}", + "Total must equal Required Qty. Exceeds by": "總數量必須等於所需數量。超出:{{diff}}" }