From e1a0f56b80387fbbcb0b9e1a00d55d8cfe6d1432 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Sun, 12 Apr 2026 17:43:35 +0800 Subject: [PATCH] update jo edit and jo compelte reocrd search --- src/app/api/jo/actions.ts | 52 ++++- .../Jodetail/JobPickExecutionForm.tsx | 4 +- .../Jodetail/newJobPickExecution.tsx | 181 ++++++++++++++---- 3 files changed, 193 insertions(+), 44 deletions(-) diff --git a/src/app/api/jo/actions.ts b/src/app/api/jo/actions.ts index 01f9849..33e05e7 100644 --- a/src/app/api/jo/actions.ts +++ b/src/app/api/jo/actions.ts @@ -1138,11 +1138,59 @@ export const fetchJos = cache(async (data?: SearchJoResultRequest) => { } } ) - console.log("fetchJos response:", response) + // console.log("fetchJos response:", response) return response }) - +export interface PostPickOrderResponse { + id: number | null; + name: string; + code: string; + type?: string; + message: string | null; + errorPosition: string + entity?: T | T[]; + consoCode?: string; +} +export interface PickExecutionIssueData { + type: string; + pickOrderId: number; + pickOrderCode: string; + pickOrderCreateDate: string; + pickExecutionDate: string; + pickOrderLineId: number; + itemId: number; + itemCode: string; + itemDescription: string; + lotId: number|null; + lotNo: string|null; + storeLocation: string; + requiredQty: number; + actualPickQty: number; + missQty: number; + badItemQty: number; + badPackageQty?: number; + /** Optional: frontend-only reference to stock_out_line.id for the picked lot. */ + stockOutLineId?: number; + issueRemark: string; + pickerName: string; + handledBy?: number; + badReason?: string; + reason?: string; +} +/** 无 miss/bad/bad package:仅后端 hold + SOL checked,不写 pick_execution_issue(避免 DUPLICATE)。 */ +export const applyPickExecutionHoldAndChecked = async (data: PickExecutionIssueData) => { + const result = await serverFetchJson( + `${BASE_API_URL}/pickExecution/applyHoldAndChecked`, + { + method: "POST", + body: JSON.stringify(data), + headers: { "Content-Type": "application/json" }, + }, + ); + revalidateTag("pickorder"); + return result; +}; export const updateJo = cache(async (data: UpdateJoRequest) => { return serverFetchJson(`${BASE_API_URL}/jo/update`, { diff --git a/src/components/Jodetail/JobPickExecutionForm.tsx b/src/components/Jodetail/JobPickExecutionForm.tsx index 1014d76..9503650 100644 --- a/src/components/Jodetail/JobPickExecutionForm.tsx +++ b/src/components/Jodetail/JobPickExecutionForm.tsx @@ -204,6 +204,7 @@ useEffect(() => { const total = ap + miss + totalBadQty; const availableQty = selectedLot?.availableQty || 0; const maxActual = requiredQty + availableQty; + // 1. Check actualPickQty cannot be negative if (ap < 0) { newErrors.actualPickQty = t("Qty cannot be negative"); @@ -321,9 +322,6 @@ useEffect(() => { const remainingAvailableQty = calculateRemainingAvailableQty(selectedLot); const requiredQty = calculateRequiredQty(selectedLot); - - const availableQty = selectedLot?.availableQty || 0; - const maxActual = requiredQty + availableQty; return ( diff --git a/src/components/Jodetail/newJobPickExecution.tsx b/src/components/Jodetail/newJobPickExecution.tsx index b271611..1f310d3 100644 --- a/src/components/Jodetail/newJobPickExecution.tsx +++ b/src/components/Jodetail/newJobPickExecution.tsx @@ -27,6 +27,7 @@ import { updateStockOutLineStatus, createStockOutLine, recordPickExecutionIssue, + //applyPickExecutionHoldAndChecked, fetchFGPickOrders, FGPickOrderResponse, autoAssignAndReleasePickOrder, @@ -46,6 +47,7 @@ import { fetchJobOrderLotsHierarchicalByPickOrderId, updateJoPickOrderHandledBy, JobOrderLotsHierarchicalResponse, + applyPickExecutionHoldAndChecked } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import { @@ -2403,32 +2405,55 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { console.log("Pick execution form opened for lot ID:", lot.lotId); }, []); - const handlePickExecutionFormSubmit = useCallback(async (data: any) => { - try { - if (currentUserId && selectedLotForExecutionForm?.pickOrderId && selectedLotForExecutionForm?.itemId) { - try { - await updateHandledBy(selectedLotForExecutionForm.pickOrderId, selectedLotForExecutionForm.itemId); - console.log(`✅ [ISSUE FORM] Handler updated for itemId ${selectedLotForExecutionForm.itemId}`); - } catch (error) { - console.error(`❌ [ISSUE FORM] Error updating handler (non-critical):`, error); + const handlePickExecutionFormSubmit = useCallback( + async (data: any) => { + const lotSnap = selectedLotForExecutionForm; + const pickOrderIdEarly = + filterArgs?.pickOrderId + ? Number(filterArgs.pickOrderId) + : Number(lotSnap?.pickOrderId || 0) || undefined; + + try { + if (currentUserId && lotSnap?.pickOrderId && lotSnap?.itemId) { + try { + await updateHandledBy(lotSnap.pickOrderId, lotSnap.itemId); + console.log(`✅ [ISSUE FORM] Handler updated for itemId ${lotSnap.itemId}`); + } catch (error) { + console.error(`❌ [ISSUE FORM] Error updating handler (non-critical):`, error); + } } - } - console.log("Pick execution form submitted:", data); - const issueData = { - ...data, - type: "Jo", // Delivery Order Record 类型 - pickerName: session?.user?.name || undefined, - handledBy: currentUserId || undefined, - }; - - const result = await recordPickExecutionIssue(issueData); - console.log("Pick execution issue recorded:", result); - - if (result && result.code === "SUCCESS") { - console.log(" Pick execution issue recorded successfully"); + + console.log("Pick execution form submitted:", data); + const issueData = { + ...data, + type: "Jo", + pickerName: session?.user?.name || undefined, + handledBy: currentUserId || undefined, + }; + + const missN = Number(issueData.missQty ?? 0) || 0; + const badN = Number(issueData.badItemQty ?? 0) || 0; + const badPkgN = Number(issueData.badPackageQty ?? 0) || 0; + const useHoldOnlyApi = missN === 0 && badN === 0 && badPkgN === 0; + + const result = useHoldOnlyApi + ? await applyPickExecutionHoldAndChecked(issueData) + : await recordPickExecutionIssue(issueData); + + console.log( + useHoldOnlyApi ? "Pick hold/checked applied:" : "Pick execution issue recorded:", + result, + ); + + if (!result || result.code !== "SUCCESS") { + console.error("❌ Pick execution submit failed:", result); + throw new Error(result?.message || "Submit failed"); + } + const solId = Number(issueData.stockOutLineId || data?.stockOutLineId); + const picked = Number(issueData.actualPickQty ?? 0); + if (solId > 0) { - const picked = Number(issueData.actualPickQty || 0); setIssuePickedQtyBySolId((prev) => { const next = { ...prev, [solId]: picked }; const pid = filterArgs?.pickOrderId ? Number(filterArgs.pickOrderId) : undefined; @@ -2436,23 +2461,101 @@ const JobPickExecution: React.FC = ({ filterArgs, onBackToList }) => { return next; }); } - } else { - console.error("❌ Failed to record pick execution issue:", result); + + // Hold-only:與整批相同規則,只送一筆 batch,把實揈/完成狀態寫回 DB + if (useHoldOnlyApi && pickOrderIdEarly && solId > 0) { + const freshData = await fetchJobOrderLotsHierarchicalByPickOrderId(pickOrderIdEarly); + const flatLots = getAllLotsFromHierarchical(freshData); + const lotRow = flatLots.find((l: any) => Number(l.stockOutLineId) === solId); + if (!lotRow) { + throw new Error("Could not find lot row after refresh for batch submit"); + } + + const requiredQty = Number(lotRow.requiredQty || lotRow.pickOrderLineRequiredQty || 0); + const issuePickedVal = picked; + const currentActualPickQty = Number(issuePickedVal ?? lotRow.actualPickQty ?? 0); + const onlyComplete = + lotRow.stockOutLineStatus === "partially_completed" || + lotRow.stockOutLineStatus === "PARTIALLY_COMPLETE" || + issuePickedVal !== undefined; + const expired = isLotAvailabilityExpired(lotRow); + const unavailable = isInventoryLotLineUnavailable(lotRow); + + let targetActual: number; + let newStatus: string; + + if (unavailable) { + targetActual = currentActualPickQty; + newStatus = "completed"; + } else if (expired && issuePickedVal === 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"; + } + } + + const line: batchSubmitListLineRequest = { + stockOutLineId: solId, + pickOrderLineId: Number(lotRow.pickOrderLineId), + inventoryLotLineId: lotRow.lotId ? Number(lotRow.lotId) : null, + requiredQty, + actualPickQty: targetActual, + stockOutLineStatus: newStatus, + pickOrderConsoCode: String(lotRow.pickOrderConsoCode || ""), + noLot: Boolean(lotRow.noLot === true), + }; + + const batchResult = await batchSubmitList({ + userId: currentUserId || 0, + lines: [line], + }); + + if (!batchResult || batchResult.code !== "SUCCESS") { + throw new Error(batchResult?.message || "Batch submit failed after hold adjustment"); + } + + const conso = String(lotRow.pickOrderConsoCode || "").trim(); + if (conso) { + try { + await checkAndCompletePickOrderByConsoCode(conso); + } catch (e) { + console.error("❌ completion check after single batch:", e); + } + } + } + + setPickExecutionFormOpen(false); + setSelectedLotForExecutionForm(null); + + await fetchJobOrderData(pickOrderIdEarly); + } catch (error) { + console.error("Error submitting pick execution form:", error); + throw error; } - - setPickExecutionFormOpen(false); - setSelectedLotForExecutionForm(null); - - const pickOrderId = - filterArgs?.pickOrderId - ? Number(filterArgs.pickOrderId) - : Number(selectedLotForExecutionForm?.pickOrderId || 0) || undefined; - await fetchJobOrderData(pickOrderId); - } catch (error) { - console.error("Error submitting pick execution form:", error); - } - }, [fetchJobOrderData, currentUserId, selectedLotForExecutionForm, updateHandledBy, filterArgs?.pickOrderId, filterArgs]); - + }, + [ + fetchJobOrderData, + getAllLotsFromHierarchical, + currentUserId, + selectedLotForExecutionForm, + updateHandledBy, + filterArgs, + session?.user?.name, + batchSubmitList, + checkAndCompletePickOrderByConsoCode, + isLotAvailabilityExpired, + isInventoryLotLineUnavailable, + ], + ); // Calculate remaining required quantity const calculateRemainingRequiredQty = useCallback((lot: any) => { const requiredQty = lot.requiredQty || 0;