From f1fe469ccbe5c001e21aa1d31e12021024131565 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Thu, 26 Mar 2026 15:43:37 +0800 Subject: [PATCH] update --- src/app/api/stockTake/actions.ts | 95 +++++++++++++++++-- .../FGPickOrderTicketReleaseTable.tsx | 4 +- .../Jodetail/newJobPickExecution.tsx | 1 + src/components/PickOrderSearch/LotTable.tsx | 6 +- .../PickOrderSearch/PickExecution.tsx | 19 +--- .../ProductionProcessList.tsx | 22 +++-- .../QrCodeScannerProvider.tsx | 6 +- src/i18n/zh/inventory.json | 3 + 8 files changed, 121 insertions(+), 35 deletions(-) diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index a991294..2772bc9 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -3,6 +3,7 @@ import { cache } from 'react'; import { serverFetchJson } from "@/app/utils/fetchUtil"; // 改为 serverFetchJson import { BASE_API_URL } from "@/config/api"; +import { stockTakeDebugLog } from "@/components/StockTakeManagement/stockTakeDebugLog"; export interface RecordsRes { records: T[]; @@ -51,6 +52,31 @@ export interface InventoryLotDetailResponse { approverTime?: string | string[] | null; } +/** + * `approverInventoryLotDetailsAll*`: + * - `total` = 全域 `inventory_lot_line` 中 `status = available` 筆數(與 DB COUNT 一致) + * - `filteredRecordCount` = 目前 tab/篩選後筆數(分頁用) + */ +export interface ApproverInventoryLotDetailsRecordsRes extends RecordsRes { + filteredRecordCount?: number; + totalWaitingForApprover?: number; + totalApproved?: number; +} + +function normalizeApproverInventoryLotDetailsRes( + raw: ApproverInventoryLotDetailsRecordsRes +): ApproverInventoryLotDetailsRecordsRes { + const waiting = Number(raw.totalWaitingForApprover ?? 0) || 0; + const approved = Number(raw.totalApproved ?? 0) || 0; + return { + records: Array.isArray(raw.records) ? raw.records : [], + total: Number(raw.total ?? 0) || 0, + filteredRecordCount: Number(raw.filteredRecordCount ?? 0) || 0, + totalWaitingForApprover: waiting, + totalApproved: approved, + }; +} + export const getInventoryLotDetailsBySection = async ( stockTakeSection: string, stockTakeId?: number | null, @@ -122,13 +148,13 @@ export const getApproverInventoryLotDetailsAll = async ( } const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAll?${params.toString()}`; - const response = await serverFetchJson>( + const response = await serverFetchJson( url, { method: "GET", }, ); - return response; + return normalizeApproverInventoryLotDetailsRes(response); } export const getApproverInventoryLotDetailsAllPending = async ( stockTakeId?: number | null, @@ -142,7 +168,8 @@ export const getApproverInventoryLotDetailsAllPending = async ( params.append("stockTakeId", String(stockTakeId)); } const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllPending?${params.toString()}`; - return serverFetchJson>(url, { method: "GET" }); + const response = await serverFetchJson(url, { method: "GET" }); + return normalizeApproverInventoryLotDetailsRes(response); } export const getApproverInventoryLotDetailsAllApproved = async ( stockTakeId?: number | null, @@ -156,7 +183,8 @@ export const getApproverInventoryLotDetailsAllApproved = async ( params.append("stockTakeId", String(stockTakeId)); } const url = `${BASE_API_URL}/stockTakeRecord/approverInventoryLotDetailsAllApproved?${params.toString()}`; - return serverFetchJson>(url, { method: "GET" }); + const response = await serverFetchJson(url, { method: "GET" }); + return normalizeApproverInventoryLotDetailsRes(response); } export const importStockTake = async (data: FormData) => { @@ -242,6 +270,20 @@ export const saveStockTakeRecord = async ( console.log('saveStockTakeRecord: request:', request); console.log('saveStockTakeRecord: stockTakeId:', stockTakeId); console.log('saveStockTakeRecord: stockTakerId:', stockTakerId); + // #region agent log + stockTakeDebugLog( + "actions.ts:saveStockTakeRecord", + "server action saveStockTakeRecord ok", + "H3", + { + stockTakeId, + stockTakerId, + inventoryLotLineId: request.inventoryLotLineId, + hasRecordId: request.stockTakeRecordId != null, + resultId: result?.id ?? null, + } + ); + // #endregion return result; } catch (error: any) { // 尝试从错误响应中提取消息 @@ -271,12 +313,26 @@ export interface BatchSaveStockTakeRecordResponse { errors: string[]; } export const batchSaveStockTakeRecords = cache(async (data: BatchSaveStockTakeRecordRequest) => { - return serverFetchJson(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`, + const r = await serverFetchJson(`${BASE_API_URL}/stockTakeRecord/batchSaveStockTakeRecords`, { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }) + // #region agent log + stockTakeDebugLog( + "actions.ts:batchSaveStockTakeRecords", + "server batch picker result", + "H4", + { + stockTakeId: data.stockTakeId, + stockTakeSection: data.stockTakeSection, + successCount: r.successCount, + errorCount: r.errorCount, + } + ); + // #endregion + return r }) // Add these interfaces and functions @@ -325,6 +381,19 @@ export const saveApproverStockTakeRecord = async ( body: JSON.stringify(request), }, ); + // #region agent log + stockTakeDebugLog( + "actions.ts:saveApproverStockTakeRecord", + "server action saveApproverStockTakeRecord ok", + "H3", + { + stockTakeId, + stockTakeRecordId: request.stockTakeRecordId ?? null, + lastSelect: request.lastSelect ?? null, + hasApproverQty: request.approverQty != null, + } + ); + // #endregion return result; } catch (error: any) { if (error?.response) { @@ -354,7 +423,7 @@ export const batchSaveApproverStockTakeRecords = cache(async (data: BatchSaveApp ) export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSaveApproverStockTakeAllRequest) => { - return serverFetchJson( + const r = await serverFetchJson( `${BASE_API_URL}/stockTakeRecord/batchSaveApproverStockTakeRecordsAll`, { method: "POST", @@ -362,6 +431,20 @@ export const batchSaveApproverStockTakeRecordsAll = cache(async (data: BatchSave headers: { "Content-Type": "application/json" }, } ) + // #region agent log + stockTakeDebugLog( + "actions.ts:batchSaveApproverStockTakeRecordsAll", + "server batch approver-all result", + "H4", + { + stockTakeId: data.stockTakeId, + approverId: data.approverId, + successCount: r.successCount, + errorCount: r.errorCount, + } + ); + // #endregion + return r }) export const updateStockTakeRecordStatusToNotMatch = async ( diff --git a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx index 0f0aa4e..ffd6110 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx @@ -65,7 +65,9 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { const { t } = useTranslation("ticketReleaseTable"); const { data: session } = useSession() as { data: SessionWithTokens | null }; const abilities = session?.abilities ?? session?.user?.abilities ?? []; - const canManageDoPickOps = abilities.includes(AUTH.ADMIN); + // 依照 DB `authority.authority = 'ADMIN'` 的逻辑:仅 abilities 明確包含 ADMIN 才允許操作 + // (避免 abilities 裡出現前後空白導致 includes 判斷失效) + const canManageDoPickOps = abilities.some((a) => a.trim() === AUTH.ADMIN); const [queryDate, setQueryDate] = useState(() => dayjs()); const [selectedFloor, setSelectedFloor] = useState(""); diff --git a/src/components/Jodetail/newJobPickExecution.tsx b/src/components/Jodetail/newJobPickExecution.tsx index 870adf7..4f63509 100644 --- a/src/components/Jodetail/newJobPickExecution.tsx +++ b/src/components/Jodetail/newJobPickExecution.tsx @@ -2763,6 +2763,7 @@ const sortedData = [...sourceData].sort((a, b) => { disabled={ (Number(lot.stockOutLineId) > 0 && actionBusyBySolId[Number(lot.stockOutLineId)] === true) || lot.stockOutLineStatus === 'completed' || + lot.stockOutLineStatus === 'checked' || lot.noLot === true || !lot.lotId || (Number(lot.stockOutLineId) > 0 && diff --git a/src/components/PickOrderSearch/LotTable.tsx b/src/components/PickOrderSearch/LotTable.tsx index 7430a48..cf96baf 100644 --- a/src/components/PickOrderSearch/LotTable.tsx +++ b/src/components/PickOrderSearch/LotTable.tsx @@ -397,8 +397,8 @@ const LotTable: React.FC = ({ const { t } = useTranslation("pickOrder"); const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => { const requiredQty = lot.requiredQty || 0; - const stockOutLineQty = lot.stockOutLineQty || 0; - return Math.max(0, requiredQty - stockOutLineQty); + const availableQty = lot.availableQty || 0; + return Math.max(0, requiredQty + availableQty); }, []); // Add QR scanner context const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); @@ -506,7 +506,7 @@ const LotTable: React.FC = ({ const stockOutLineUpdate = await updateStockOutLineStatus({ id: selectedLotForQr.stockOutLineId, status: 'checked', - qty: selectedLotForQr.stockOutLineQty || 0 + qty: 0 }); console.log(" Stock out line updated to 'checked':", stockOutLineUpdate); diff --git a/src/components/PickOrderSearch/PickExecution.tsx b/src/components/PickOrderSearch/PickExecution.tsx index 4eb827b..b86cbd6 100644 --- a/src/components/PickOrderSearch/PickExecution.tsx +++ b/src/components/PickOrderSearch/PickExecution.tsx @@ -361,13 +361,9 @@ const PickExecution: React.FC = ({ filterArgs }) => { try { // FIXED: 计算累计拣货数量 const totalPickedForThisLot = (selectedLot.actualPickQty || 0) + qty; - console.log(" DEBUG - Previous picked:", selectedLot.actualPickQty || 0); - console.log(" DEBUG - Current submit:", qty); - console.log(" DEBUG - Total picked:", totalPickedForThisLot); - console.log("�� DEBUG - Required qty:", selectedLot.requiredQty); - + // FIXED: 状态应该基于累计拣货数量 - let newStatus = 'partially_completed'; + let newStatus = 'completed'; if (totalPickedForThisLot >= selectedLot.requiredQty) { newStatus = 'completed'; } @@ -388,16 +384,7 @@ const PickExecution: React.FC = ({ filterArgs }) => { return; } - if (qty > 0) { - const inventoryLotLineUpdate = await updateInventoryLotLineQuantities({ - inventoryLotLineId: lotId, - qty: qty, - status: 'available', - operation: 'pick' - }); - - console.log("Inventory lot line updated:", inventoryLotLineUpdate); - } + // RE-ENABLE: Check if pick order should be completed if (newStatus === 'completed') { diff --git a/src/components/ProductionProcess/ProductionProcessList.tsx b/src/components/ProductionProcess/ProductionProcessList.tsx index ab5bf35..9522c30 100644 --- a/src/components/ProductionProcess/ProductionProcessList.tsx +++ b/src/components/ProductionProcess/ProductionProcessList.tsx @@ -32,6 +32,7 @@ import { SessionWithTokens } from "@/config/authConfig"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox"; +import { AUTH } from "@/authorities"; import { @@ -103,6 +104,9 @@ const ProductProcessList: React.FC = ({ const [openModal, setOpenModal] = useState(false); const [modalInfo, setModalInfo] = useState(); const currentUserId = session?.id ? parseInt(session.id) : undefined; + const abilities = session?.abilities ?? session?.user?.abilities ?? []; + // 依照 DB `authority.authority = 'ADMIN'` 的逻辑:僅 abilities 明確包含 ADMIN 才能操作 + const canManageUpdateJo = abilities.some((a) => a.trim() === AUTH.ADMIN); type ProcessFilter = "all" | "drink" | "other"; const [suggestedLocationCode, setSuggestedLocationCode] = useState(null); @@ -275,6 +279,7 @@ const ProductProcessList: React.FC = ({ fetchProcesses(); }, [fetchProcesses]); const handleUpdateJo = useCallback(async (process: AllJoborderProductProcessInfoResponse) => { + if (!canManageUpdateJo) return; if (!process.jobOrderId) { alert(t("Invalid Job Order Id")); return; @@ -308,7 +313,7 @@ const ProductProcessList: React.FC = ({ } finally { setLoading(false); } - }, [t, fetchProcesses]); + }, [t, fetchProcesses, canManageUpdateJo]); const openConfirm = useCallback((message: string, action: () => Promise) => { setConfirmMessage(message); @@ -590,13 +595,16 @@ const ProductProcessList: React.FC = ({