From 0ed8b42c8e7551f70c1375dac72e94b5e4e5ea03 Mon Sep 17 00:00:00 2001 From: "PC-20260115JRSN\\Administrator" Date: Fri, 18 Sep 2026 16:16:17 +0800 Subject: [PATCH 1/2] added a sync for stock in PO, when entered poNum and status = pending, and also the database settings M18.po.stockIn.refreshIfNewer.enabled = true (liquidbase default is false), then ask m18 to sync with lastModified after MTMS PO modified datetime --- src/components/PoSearch/PoSearch.tsx | 114 ++++++--------------------- 1 file changed, 22 insertions(+), 92 deletions(-) diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index 5d28527e..0d82819c 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -294,117 +294,47 @@ const PoSearch: React.FC = ({ if (typeof v === "string" && (v as string).trim() === "") return; cleanedQuery[k] = String(v); }); - try { - const baseListResp = await clientAuthFetch( - `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(cleanedQuery).toString()}`, - { method: "GET" }, - ); - if (!baseListResp.ok) { - throw new Error(`PO list fetch failed: ${baseListResp.status}`); - } - const res = await baseListResp.json(); - if (!res) return; - - const records: PoResult[] = res.records ?? []; const searchedCodeRaw = (filterArgs as any)?.code; const searchedCode = typeof searchedCodeRaw === "string" ? searchedCodeRaw.trim() : ""; const isM18PoCode = searchedCode.length > 14 && (searchedCode.startsWith("PP") || searchedCode.startsWith("PF")); - const hasLocalPending = records.some( - (row) => String(row.status ?? "").toLowerCase() === "pending", - ); - const shouldLookupMissing = records.length === 0 && isM18PoCode; - const shouldRefreshPending = hasLocalPending && isM18PoCode; - - if ( - (!shouldLookupMissing && !shouldRefreshPending) || - autoSyncInProgressRef.current - ) { - setFilteredPo(records); - setTotalCount(res.total); - return; - } - - if (shouldRefreshPending) { - setFilteredPo(records); - setTotalCount(res.total); + if (isM18PoCode) { + cleanedQuery.refreshFromM18 = "true"; + setIsM18LookupLoading(true); + setAutoSyncStatus("載入中..."); } try { - autoSyncInProgressRef.current = true; - setIsM18LookupLoading(shouldLookupMissing); - setAutoSyncStatus( - shouldLookupMissing - ? "正在從M18找尋PO..." - : "正在檢查M18是否有更新...", - ); - const syncResp = await clientAuthFetch( - `${NEXT_PUBLIC_API_URL}/m18/test/po-by-code?code=${encodeURIComponent( - searchedCode, - )}&ifNewer=true`, + const baseListResp = await clientAuthFetch( + `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(cleanedQuery).toString()}`, { method: "GET" }, ); - - if (!syncResp.ok) { - throw new Error(`M18 sync failed: ${syncResp.status}`); - } - - let syncJson: any = null; - try { - syncJson = await syncResp.json(); - } catch { - // Some endpoints may respond with plain text - const txt = await syncResp.text(); - syncJson = { raw: txt }; - } - - const syncOk = Boolean(syncJson?.totalSuccess && syncJson.totalSuccess > 0); - const skippedIfNewer = String(syncJson?.query ?? "").includes( - "skipped (ifNewer)", - ); - if (syncOk) { - setAutoSyncStatus( - shouldLookupMissing ? "成功找到PO" : "已從M18更新PO", - ); - - const listResp = await clientAuthFetch( - `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams( - cleanedQuery, - ).toString()}`, - { method: "GET" }, - ); - if (listResp.ok) { - const listJson = await listResp.json(); - setFilteredPo(listJson.records ?? []); - setTotalCount(listJson.total ?? 0); - setAutoSyncStatus( - shouldLookupMissing ? "成功找到PO" : "已從M18更新PO", - ); - return; - } - setAutoSyncStatus(shouldLookupMissing ? "找不到PO" : null); - } else if (skippedIfNewer || shouldRefreshPending) { - setAutoSyncStatus(null); - } else { - setAutoSyncStatus("找不到PO"); + if (!baseListResp.ok) { + throw new Error(`PO list fetch failed: ${baseListResp.status}`); } + const res = await baseListResp.json(); + if (!res) return; + const records: PoResult[] = res.records ?? []; setFilteredPo(records); setTotalCount(res.total ?? 0); + if (isM18PoCode) { + const refreshAction = baseListResp.headers.get("X-M18-Po-Refresh"); + if (refreshAction === "synced") { + setAutoSyncStatus(records.length > 0 ? "已從M18同步PO" : "找不到PO"); + } else { + setAutoSyncStatus(null); + } + } } catch (e) { - console.error("Auto sync error:", e); - setAutoSyncStatus(shouldLookupMissing ? "找不到PO" : null); - setFilteredPo(records); - setTotalCount(res.total ?? 0); + console.error("PO list fetch error:", e); + if (isM18PoCode) setAutoSyncStatus("找不到PO"); } finally { setIsM18LookupLoading(false); autoSyncInProgressRef.current = false; } - } catch (e) { - console.error("PO list fetch error:", e); - } }, [], ); @@ -501,7 +431,7 @@ const PoSearch: React.FC = ({ > - {autoSyncStatus || "正在從M18找尋PO..."} + {autoSyncStatus || "載入中..."} From 83eb8df464e2d6b50a2010a3ebbfdd611ef4c266 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 18 Sep 2026 17:51:37 +0800 Subject: [PATCH 2/2] qc detail putawayed qty fix --- src/components/PoDetail/PoDetail.tsx | 2 +- src/components/PoDetail/PoDetailRow.tsx | 2 +- src/components/PoDetail/PoInputGrid.tsx | 2 +- src/components/PoDetail/PutAwayForm.tsx | 13 +++- src/components/PoDetail/QcStockInModal.tsx | 5 +- .../PoDetail/StockInLineRowActions.tsx | 2 +- .../PoDetail/originSilPutAwayQty.ts | 60 +++++++++++++++++++ src/components/PoDetail/stockQtyRound.ts | 5 +- src/components/Qc/QcStockInModal.tsx | 5 +- 9 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/components/PoDetail/originSilPutAwayQty.ts diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 993433dc..403cd0c6 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -207,7 +207,7 @@ interface PolInputResult { dnQty: string, } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const cameras = useContext(CameraContext); const { data: session } = useSession(); diff --git a/src/components/PoDetail/PoDetailRow.tsx b/src/components/PoDetail/PoDetailRow.tsx index d16c5259..d688df85 100644 --- a/src/components/PoDetail/PoDetailRow.tsx +++ b/src/components/PoDetail/PoDetailRow.tsx @@ -77,7 +77,7 @@ type Props = { onSubmitted: (row: PurchaseOrderLine) => void; }; -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ export const PoDetailRow = memo(function PoDetailRow({ row, selected, diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx index c8c038b8..c41dbef7 100644 --- a/src/components/PoDetail/PoInputGrid.tsx +++ b/src/components/PoDetail/PoInputGrid.tsx @@ -155,7 +155,7 @@ class ProcessRowUpdateError extends Error { } } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ function PoInputGrid({ // qc, setRows, diff --git a/src/components/PoDetail/PutAwayForm.tsx b/src/components/PoDetail/PutAwayForm.tsx index 0aa74b4b..545273b6 100644 --- a/src/components/PoDetail/PutAwayForm.tsx +++ b/src/components/PoDetail/PutAwayForm.tsx @@ -55,6 +55,7 @@ import dayjs from "dayjs"; import arraySupport from "dayjs/plugin/arraySupport"; import { dummyPutAwayLine } from "../Qc/dummyQcTemplate"; import { GridRowModesModel } from "@mui/x-data-grid"; +import { displayPutAwayLineQtyFromOriginSil } from "./originSilPutAwayQty"; dayjs.extend(arraySupport); interface Props { @@ -86,6 +87,7 @@ const style = { width: "auto", }; +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ const PutAwayForm: React.FC = ({ itemDetail, warehouse=[], disabled, suggestedLocationCode, setRowModesModel, setRowSelectionModel }) => { const { t } = useTranslation("purchaseOrder"); const apiRef = useGridApiRef(); @@ -241,8 +243,13 @@ const PutAwayForm: React.FC = ({ itemDetail, warehouse=[], disabled, sugg headerAlign: "right", align: "right", renderCell(params) { + const displayQty = displayPutAwayLineQtyFromOriginSil( + itemDetail, + Number(params.value), + params.row._isNew, + ); return - {params.value} + {displayQty} } }, @@ -309,7 +316,7 @@ const PutAwayForm: React.FC = ({ itemDetail, warehouse=[], disabled, sugg // return <>100 // }, // }, - ], []) + ], [itemDetail, t]) const validation = useCallback( (newRow: GridRowModel): EntryError => { @@ -395,7 +402,7 @@ const PutAwayForm: React.FC = ({ itemDetail, warehouse=[], disabled, sugg diff --git a/src/components/PoDetail/QcStockInModal.tsx b/src/components/PoDetail/QcStockInModal.tsx index 56d5a87a..841f3111 100644 --- a/src/components/PoDetail/QcStockInModal.tsx +++ b/src/components/PoDetail/QcStockInModal.tsx @@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next"; import StockInForm from "../StockIn/StockInForm"; import QcComponent from "../Qc/QcComponent"; import PutAwayForm from "./PutAwayForm"; +import { mapPutAwayLinesFromOriginSil } from "./originSilPutAwayQty"; import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid"; import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts"; import { INPUT_DATE_FORMAT, arrayToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; @@ -71,7 +72,7 @@ interface CommonProps extends Omit { interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ const PoQcStockInModalVer2: React.FC = ({ open, onClose, @@ -205,7 +206,7 @@ const PoQcStockInModalVer2: React.FC = ({ // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], warehouseId: d.defaultWarehouseId ?? 1, - putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [], + putAwayLines: mapPutAwayLinesFromOriginSil(d, d.putAwayLines), } as ModalFormInput ) } return undefined diff --git a/src/components/PoDetail/StockInLineRowActions.tsx b/src/components/PoDetail/StockInLineRowActions.tsx index c1431741..c34e153c 100644 --- a/src/components/PoDetail/StockInLineRowActions.tsx +++ b/src/components/PoDetail/StockInLineRowActions.tsx @@ -26,7 +26,7 @@ type Props = { onRound?: (mode: StockQtyRoundMode) => void; }; -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ export default function StockInLineRowActions({ btnSx, onPrimaryClick, diff --git a/src/components/PoDetail/originSilPutAwayQty.ts b/src/components/PoDetail/originSilPutAwayQty.ts new file mode 100644 index 00000000..2e3fa15d --- /dev/null +++ b/src/components/PoDetail/originSilPutAwayQty.ts @@ -0,0 +1,60 @@ +import { PutAwayLine, StockInLine } from "@/app/api/stockIn"; + +type OriginSil = Pick< + StockInLine, + "jobOrderId" | "purchaseAcceptedQty" | "acceptedQty" +>; + +/** + * FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 + * Path A: inventory_lot_line → inventory_lot → stock_in_line (the SIL that opened the lot). + * Never use inventory_lot_line.inQty — ADJ/TKE is added onto the same line later. + * + * PO: purchaseAcceptedQty (M18 / 採購單位) + * JO: acceptedQty (庫存/生產單位;JO 通常沒有 acceptedQtyM18) + */ +export function originSilPutAwayDisplayQty(origin: OriginSil): number { + if (origin.jobOrderId) { + const stock = Number(origin.acceptedQty); + return Number.isFinite(stock) && stock > 0 ? stock : 0; + } + const purchase = Number(origin.purchaseAcceptedQty); + if (Number.isFinite(purchase) && purchase > 0) return purchase; + const stock = Number(origin.acceptedQty); + return Number.isFinite(stock) && stock > 0 ? stock : 0; +} + +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ +export function originSilPutAwayStockQty(origin: OriginSil): number { + const stock = Number(origin.acceptedQty); + return Number.isFinite(stock) && stock > 0 ? stock : 0; +} + +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ +export function mapPutAwayLinesFromOriginSil( + origin: OriginSil, + lines: PutAwayLine[] | undefined, +): PutAwayLine[] { + const existing = lines ?? []; + const displayQty = originSilPutAwayDisplayQty(origin); + const stockQty = originSilPutAwayStockQty(origin); + return existing.map((line) => ({ + ...line, + printQty: 1, + _isNew: false, + _disableDelete: true, + ...(displayQty > 0 ? { qty: displayQty } : {}), + ...(stockQty > 0 ? { stockQty } : {}), + })); +} + +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ +export function displayPutAwayLineQtyFromOriginSil( + origin: OriginSil, + lineQty: number, + isNew: boolean | undefined, +): number { + if (isNew) return lineQty; + const originQty = originSilPutAwayDisplayQty(origin); + return originQty > 0 ? originQty : lineQty; +} diff --git a/src/components/PoDetail/stockQtyRound.ts b/src/components/PoDetail/stockQtyRound.ts index c2d066b6..630c9acc 100644 --- a/src/components/PoDetail/stockQtyRound.ts +++ b/src/components/PoDetail/stockQtyRound.ts @@ -81,6 +81,7 @@ function convertPoBatchToStockQty( } /** + * FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 * Preview converted stock qty for a 來貨數 batch. * Matches create-stock-in: ratio convert, then integer HALF_UP for 包/箱/PCS, else 2 decimals. */ @@ -104,7 +105,7 @@ export function isNotIntegerQty(qty: number): boolean { } /** - * FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 + * FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 * PO 收貨/品檢:pending 或 escalated 且庫存數量非整數時需選擇 CEILING / FLOOR */ export function needsPoQcStockQtyRound( @@ -118,7 +119,7 @@ export function needsPoQcStockQtyRound( return isNotIntegerQty(Number(acceptedQty ?? 0)); } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ export function roundStockQty(before: number, mode: StockQtyRoundMode): number { if (mode === "CEILING") return Math.ceil(before); return Math.floor(before); diff --git a/src/components/Qc/QcStockInModal.tsx b/src/components/Qc/QcStockInModal.tsx index c6b2b53d..b38d37dd 100644 --- a/src/components/Qc/QcStockInModal.tsx +++ b/src/components/Qc/QcStockInModal.tsx @@ -24,6 +24,7 @@ import PutAwayForm from "../PoDetail/PutAwayForm"; import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid"; import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts"; import { needsPoQcStockQtyRound } from "../PoDetail/stockQtyRound"; +import { mapPutAwayLinesFromOriginSil } from "../PoDetail/originSilPutAwayQty"; import { INPUT_DATE_FORMAT, arrayToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; import { fetchPoQrcode } from "@/app/api/pdf/actions"; @@ -73,7 +74,7 @@ interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.4 | 2026-09-10 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.5 | 2026-09-18 */ const QcStockInModal: React.FC = ({ open, onClose, @@ -231,7 +232,7 @@ const QcStockInModal: React.FC = ({ // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], warehouseId: d.defaultWarehouseId ?? 1141, - putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [], + putAwayLines: mapPutAwayLinesFromOriginSil(d, d.putAwayLines), } as ModalFormInput ) } return undefined