From d5701925f32fb76d43b1e799ad9e28ebfae8e037 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Tue, 8 Sep 2026 22:24:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BA=AB=E5=AD=98=E8=AA=BF=E6=95=B4=20Remarks?= =?UTF-8?q?=20=E5=AF=AB=E5=85=A5=20DB=20=E4=B8=A6=E5=9B=9E=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/stockAdjustment/actions.ts | 14 ++++ .../InventorySearch/InventoryLotLineTable.tsx | 75 +++++++++++++++---- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/app/api/stockAdjustment/actions.ts b/src/app/api/stockAdjustment/actions.ts index cbcb04d5..c42e0c09 100644 --- a/src/app/api/stockAdjustment/actions.ts +++ b/src/app/api/stockAdjustment/actions.ts @@ -16,6 +16,7 @@ export interface StockAdjustmentLineRequest { expiryDate: string; warehouseId: number; uom?: string | null; + remarks?: string | null; } export interface StockAdjustmentRequest { @@ -33,6 +34,19 @@ export interface MessageResponse { errorPosition: string | null; } +export interface StockAdjustmentRemarksResponse { + lotNo: string | null; + remarks: string; +} + +/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.7 | 2026-09-08 */ +export const fetchLatestAdjustmentRemarks = async (itemId: number) => { + return serverFetchJson( + `${BASE_API_URL}/stockAdjustment/latestRemarks?itemId=${itemId}`, + { method: "GET" }, + ); +}; + export const submitStockAdjustment = async (data: StockAdjustmentRequest) => { const result = await serverFetchJson( `${BASE_API_URL}/stockAdjustment/submit`, diff --git a/src/components/InventorySearch/InventoryLotLineTable.tsx b/src/components/InventorySearch/InventoryLotLineTable.tsx index fd6165d1..9e3a041f 100644 --- a/src/components/InventorySearch/InventoryLotLineTable.tsx +++ b/src/components/InventorySearch/InventoryLotLineTable.tsx @@ -32,7 +32,7 @@ import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import dayjs from "dayjs"; import CheckIcon from "@mui/icons-material/Check"; -import { submitStockAdjustment, StockAdjustmentLineRequest } from "@/app/api/stockAdjustment/actions"; +import { submitStockAdjustment, StockAdjustmentLineRequest, fetchLatestAdjustmentRemarks } from "@/app/api/stockAdjustment/actions"; import { useSession } from "next-auth/react"; import { AUTH, hasAbility } from "@/authorities"; @@ -59,7 +59,7 @@ interface Props { onStockAdjustmentSuccess?: () => void | Promise; } -/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.6 | 2026-09-07 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 3 | v1.0.7 | 2026-09-08 */ const InventoryLotLineTable: React.FC = ({ inventoryLotLines, pagingController, setPagingController, totalCount, inventory, filterLotNo, @@ -99,7 +99,11 @@ const InventoryLotLineTable: React.FC = ({ remarks: '', }); const originalAdjustmentLinesRef = useRef([]); + const adjustSaveInFlightRef = useRef(false); + const loadedRemarksByLotRef = useRef>(new Map()); + const remarksFetchGenRef = useRef(0); const [adjustmentEntries, setAdjustmentEntries] = useState([]); + const [isAdjustSaving, setIsAdjustSaving] = useState(false); useEffect(() => { if (stockTransferModalOpen) { fetchWarehouseListClient() @@ -153,9 +157,34 @@ const prevAdjustmentModalOpenRef = useRef(false); })); setAdjustmentEntries(initial); originalAdjustmentLinesRef.current = initial; + loadedRemarksByLotRef.current = new Map(); + const fetchGen = ++remarksFetchGenRef.current; + const itemId = inventory.itemId; + fetchLatestAdjustmentRemarks(itemId) + .then((rows) => { + if (fetchGen !== remarksFetchGenRef.current) return; + const byLot = new Map(); + for (const row of rows ?? []) { + const lot = row.lotNo?.trim(); + const remarks = row.remarks?.trim(); + if (!lot || !remarks || byLot.has(lot)) continue; + byLot.set(lot, remarks); + } + loadedRemarksByLotRef.current = byLot; + const apply = (line: AdjustmentEntry): AdjustmentEntry => { + const lot = line.lotNo?.trim(); + const remarks = (lot && byLot.get(lot)) || line.remarks || ''; + return { ...line, remarks }; + }; + setAdjustmentEntries((prev) => prev.map(apply)); + originalAdjustmentLinesRef.current = originalAdjustmentLinesRef.current.map(apply); + }) + .catch(console.error); } setPendingRemovalLineId(null); setRemovalReasons({}); + } else if (!stockAdjustmentModalOpen) { + remarksFetchGenRef.current += 1; } }, [stockAdjustmentModalOpen, inventory, availableLotLines]); @@ -164,12 +193,15 @@ const prevAdjustmentModalOpenRef = useRef(false); setPendingRemovalLineId(null); setRemovalReasons({}); setAdjustmentEntries( - (availableLotLines ?? []).map((line) => ({ - ...line, - adjustedQty: line.availableQty ?? 0, - originalQty: line.availableQty ?? 0, - remarks: '', - })) + (availableLotLines ?? []).map((line) => { + const lot = line.lotNo?.trim(); + return { + ...line, + adjustedQty: line.availableQty ?? 0, + originalQty: line.availableQty ?? 0, + remarks: (lot && loadedRemarksByLotRef.current.get(lot)) || '', + }; + }) ); }, [availableLotLines]); @@ -241,15 +273,26 @@ const prevAdjustmentModalOpenRef = useRef(false); expiryDate, warehouseId: line.warehouse?.id ?? 0, uom: line.uom ?? null, + remarks: line.remarks?.trim() || null, }; }, []); const handleAdjustmentSave = useCallback(async () => { if (!inventory) return; - const itemCode = inventory.itemCode; - const originalLines = originalAdjustmentLinesRef.current.map((line) => toApiLine(line, itemCode)); - const currentLines = adjustmentEntries.map((line) => toApiLine(line, itemCode)); + if (adjustSaveInFlightRef.current) return; + adjustSaveInFlightRef.current = true; + setIsAdjustSaving(true); try { + const itemCode = inventory.itemCode; + const currentIds = new Set(adjustmentEntries.map((line) => line.id)); + const originalLines = originalAdjustmentLinesRef.current.map((line) => { + const api = toApiLine(line, itemCode); + if (!currentIds.has(line.id)) { + api.remarks = removalReasons[line.id]?.trim() || null; + } + return api; + }); + const currentLines = adjustmentEntries.map((line) => toApiLine(line, itemCode)); setIsUploading(true); await submitStockAdjustment({ itemId: inventory.itemId, @@ -264,8 +307,10 @@ const prevAdjustmentModalOpenRef = useRef(false); msgError(message || t("Save failed")); } finally { setIsUploading(false); + setIsAdjustSaving(false); + adjustSaveInFlightRef.current = false; } - }, [adjustmentEntries, inventory, t, toApiLine, onStockAdjustmentSuccess]); + }, [adjustmentEntries, inventory, t, toApiLine, onStockAdjustmentSuccess, removalReasons]); const handleOpenAddEntry = useCallback(() => { setAddEntryForm({ @@ -857,7 +902,7 @@ const prevAdjustmentModalOpenRef = useRef(false); color="primary" startIcon={} onClick={handleAdjustmentSave} - disabled={!hasAdjustmentChange} + disabled={!hasAdjustmentChange || isAdjustSaving} > {t("Save")} @@ -1004,7 +1049,9 @@ const prevAdjustmentModalOpenRef = useRef(false); }, }} /> - ) : null} + ) : ( + line.remarks || null + )} {pendingRemovalLineId === line.id ? (