diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index e54376a..83403a5 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -40,6 +40,7 @@ export interface InventoryLotDetailResponse { approverQty: number | null; approverBadQty: number | null; finalQty: number | null; + bookQty: number | null; } export const getInventoryLotDetailsBySection = async ( @@ -207,6 +208,7 @@ export interface BatchSaveApproverStockTakeRecordRequest { stockTakeId: number; stockTakeSection: string; approverId: number; + variancePercentTolerance?: number | null; } export interface BatchSaveApproverStockTakeRecordResponse { @@ -312,7 +314,10 @@ export const getInventoryLotDetailsBySectionNotMatch = async ( ); return response; } - +export interface SearchStockTransactionResult { + records: StockTransactionResponse[]; + total: number; +} export interface SearchStockTransactionRequest { startDate: string | null; endDate: string | null; @@ -345,7 +350,6 @@ export interface StockTransactionListResponse { } export const searchStockTransactions = cache(async (request: SearchStockTransactionRequest) => { - // 构建查询字符串 const params = new URLSearchParams(); if (request.itemCode) params.append("itemCode", request.itemCode); @@ -366,7 +370,10 @@ export const searchStockTransactions = cache(async (request: SearchStockTransact next: { tags: ["Stock Transaction List"] }, } ); - // 确保返回正确的格式 - return response?.records || []; + // 回傳 records 與 total,供分頁正確顯示 + return { + records: response?.records || [], + total: response?.total ?? 0, + }; }); diff --git a/src/components/Qc/QcStockInModal.tsx b/src/components/Qc/QcStockInModal.tsx index 3743a6f..ce55928 100644 --- a/src/components/Qc/QcStockInModal.tsx +++ b/src/components/Qc/QcStockInModal.tsx @@ -68,6 +68,7 @@ interface CommonProps extends Omit { interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } + const QcStockInModal: React.FC = ({ open, onClose, @@ -94,6 +95,10 @@ const QcStockInModal: React.FC = ({ () => `qcStockInModal_selectedPrinterId_${session?.id ?? "guest"}`, [session?.id], ); + const labelPrinterCombo = useMemo( + () => (printerCombo || []).filter((p) => p.type === "Label"), + [printerCombo], + ); const getDefaultPrinter = useMemo(() => { if (!printerCombo.length) return undefined; if (typeof window === "undefined") return printerCombo[0]; @@ -102,7 +107,7 @@ const QcStockInModal: React.FC = ({ const matched = savedId ? printerCombo.find(p => p.id === Number(savedId)) : undefined; return matched ?? printerCombo[0]; }, [printerCombo, printerStorageKey]); - const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]); + const [selectedPrinter, setSelectedPrinter] = useState(labelPrinterCombo[0]); const [printQty, setPrintQty] = useState(1); const [tabIndex, setTabIndex] = useState(0); @@ -504,6 +509,7 @@ const QcStockInModal: React.FC = ({ // Put away model const [pafRowModesModel, setPafRowModesModel] = useState({}) const [pafRowSelectionModel, setPafRowSelectionModel] = useState([]) + const pafSubmitDisable = useMemo(() => { return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit) }, [pafRowModesModel]) @@ -749,21 +755,25 @@ const printQrcode = useCallback( {tabIndex == 1 && ( { - setSelectedPrinter(value) - }} - renderInput={(params) => ( - - )} - /> + disableClearable + options={labelPrinterCombo} + getOptionLabel={(option) => + option.name || option.label || option.code || `Printer ${option.id}` + } + value={selectedPrinter} + onChange={(_, newValue) => { + if (newValue) setSelectedPrinter(newValue); + }} + renderInput={(params) => ( + + )} + /> = ({ dataList: initialDataList }) => { // 当 processedData 变化时更新 filteredList(不更新 pagingController,避免循环) useEffect(() => { setFilteredList(processedData); - setTotalCount(processedData.length); + // 只在初始加载时设置 pageSize if (isInitialMount.current && processedData.length > 0) { setPageSize("all"); @@ -146,55 +146,53 @@ const SearchPage: React.FC = ({ dataList: initialDataList }) => { // API 调用函数(参考 PoSearch 的实现) // API 调用函数(参考 PoSearch 的实现) -const newPageFetch = useCallback( - async ( - pagingController: Record, - filterArgs: Record, - ) => { - setLoading(true); - try { - // 处理空字符串,转换为 null - const itemCode = filterArgs.itemCode?.trim() || null; - const itemName = filterArgs.itemName?.trim() || null; - - // 验证:至少需要 itemCode 或 itemName - if (!itemCode && !itemName) { - console.warn("Search requires at least itemCode or itemName"); + const newPageFetch = useCallback( + async ( + pagingController: Record, + filterArgs: Record, + ) => { + setLoading(true); + try { + const itemCode = filterArgs.itemCode?.trim() || null; + const itemName = filterArgs.itemName?.trim() || null; + + if (!itemCode && !itemName) { + console.warn("Search requires at least itemCode or itemName"); + setDataList([]); + setTotalCount(0); + return; + } + + const params: SearchStockTransactionRequest = { + itemCode: itemCode, + itemName: itemName, + type: filterArgs.type?.trim() || null, + startDate: filterArgs.startDate || null, + endDate: filterArgs.endDate || null, + pageNum: pagingController.pageNum - 1 || 0, + pageSize: pagingController.pageSize || 100, + }; + + const res = await searchStockTransactions(params); + + if (res && typeof res === 'object' && Array.isArray(res.records)) { + setDataList(res.records); + setTotalCount(res.total ?? res.records.length); + } else { + console.error("Invalid response format:", res); + setDataList([]); + setTotalCount(0); + } + } catch (error) { + console.error("Fetch error:", error); setDataList([]); setTotalCount(0); - return; + } finally { + setLoading(false); } - - const params: SearchStockTransactionRequest = { - itemCode: itemCode, - itemName: itemName, - type: filterArgs.type?.trim() || null, - startDate: filterArgs.startDate || null, - endDate: filterArgs.endDate || null, - pageNum: pagingController.pageNum - 1 || 0, - pageSize: pagingController.pageSize || 100, - }; - - console.log("Search params:", params); // 添加调试日志 - - const res = await searchStockTransactions(params); - console.log("Search response:", res); // 添加调试日志 - - if (res && Array.isArray(res)) { - setDataList(res); - } else { - console.error("Invalid response format:", res); - setDataList([]); - } - } catch (error) { - console.error("Fetch error:", error); - setDataList([]); - } finally { - setLoading(false); - } - }, - [], -); + }, + [], + ); // 使用 useRef 来存储上一次的值,避免不必要的 API 调用 const prevPagingControllerRef = useRef(pagingController); @@ -240,13 +238,13 @@ const newPageFetch = useCallback( const newSize = parseInt(event.target.value, 10); if (newSize === -1) { setPageSize("all"); - setPagingController(prev => ({ ...prev, pageSize: filteredList.length, pageNum: 1 })); + setPagingController(prev => ({ ...prev, pageSize: 100, pageNum: 1 })); // 用 100 觸發後端回傳全部 } else if (!isNaN(newSize)) { setPageSize(newSize); setPagingController(prev => ({ ...prev, pageSize: newSize, pageNum: 1 })); } setPage(0); - }, [filteredList.length]); + }, []); const searchCriteria: Criterion[] = useMemo( () => [ @@ -390,29 +388,25 @@ const newPageFetch = useCallback( setPagingController(prev => ({ ...prev, pageNum: 1 })); }, []); - // 计算实际显示的 items(分页) const paginatedItems = useMemo(() => { if (pageSize === "all") { return filteredList; } - const actualPageSize = typeof pageSize === 'number' ? pageSize : 10; - const startIndex = page * actualPageSize; - const endIndex = startIndex + actualPageSize; - return filteredList.slice(startIndex, endIndex); - }, [filteredList, page, pageSize]); + + return filteredList; + }, [filteredList, pageSize]); // 计算传递给 SearchResults 的 pageSize(确保在选项中) const actualPageSizeForTable = useMemo(() => { if (pageSize === "all") { - return filteredList.length; + return totalCount > 0 ? totalCount : filteredList.length; } const size = typeof pageSize === 'number' ? pageSize : 10; - // 如果 size 不在标准选项中,使用 "all" 模式 if (![10, 25, 100].includes(size)) { - return filteredList.length; + return size; } return size; - }, [pageSize, filteredList.length]); + }, [pageSize, filteredList.length, totalCount]); return ( <> diff --git a/src/components/StockTakeManagement/ApproverStockTake.tsx b/src/components/StockTakeManagement/ApproverStockTake.tsx index d2661fb..49d806e 100644 --- a/src/components/StockTakeManagement/ApproverStockTake.tsx +++ b/src/components/StockTakeManagement/ApproverStockTake.tsx @@ -56,8 +56,8 @@ const ApproverStockTake: React.FC = ({ const [inventoryLotDetails, setInventoryLotDetails] = useState([]); const [loadingDetails, setLoadingDetails] = useState(false); - const [showOnlyWithDifference, setShowOnlyWithDifference] = useState(false); - + const [showOnlyWithDifference, setShowOnlyWithDifference] = useState(true); + const [variancePercentTolerance, setVariancePercentTolerance] = useState("5"); // 每个记录的选择状态,key 为 detail.id const [qtySelection, setQtySelection] = useState>({}); const [approverQty, setApproverQty] = useState>({}); @@ -71,7 +71,17 @@ const ApproverStockTake: React.FC = ({ const currentUserId = session?.id ? parseInt(session.id) : undefined; const handleBatchSubmitAllRef = useRef<() => Promise>(); - + const isWithinVarianceTolerance = useCallback(( + difference: number, + bookQty: number, + percentStr: string + ): boolean => { + const percent = parseFloat(percentStr || "0"); + if (isNaN(percent) || percent < 0) return true; // 无效输入时视为全部通过 + if (bookQty === 0) return difference === 0; + const threshold = Math.abs(bookQty) * (percent / 100); + return Math.abs(difference) <= threshold; + }, []); const handleChangePage = useCallback((event: unknown, newPage: number) => { setPage(newPage); }, []); @@ -133,7 +143,7 @@ const ApproverStockTake: React.FC = ({ selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0")) || 0; } - const bookQty = detail.availableQty || 0; + const bookQty = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0); return selectedQty - bookQty; }, [approverQty, approverBadQty]); @@ -159,16 +169,29 @@ const ApproverStockTake: React.FC = ({ // 4. 添加过滤逻辑(在渲染表格之前) const filteredDetails = useMemo(() => { - if (!showOnlyWithDifference) { - return inventoryLotDetails; + let result = inventoryLotDetails; + + if (showOnlyWithDifference) { + const percent = parseFloat(variancePercentTolerance || "0"); + const thresholdPercent = isNaN(percent) || percent < 0 ? 0 : percent; + + result = result.filter(detail => { + // 已完成項目一律顯示 + if (detail.finalQty != null || detail.stockTakeRecordStatus === "completed") { + return true; + } + const selection = qtySelection[detail.id] ?? + (detail.secondStockTakeQty != null && detail.secondStockTakeQty > 0 ? "second" : "first"); + const difference = calculateDifference(detail, selection); + const bookQty = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0); + if (bookQty === 0) return difference !== 0; + const threshold = Math.abs(bookQty) * (thresholdPercent / 100); + return Math.abs(difference) > threshold; + }); } - return inventoryLotDetails.filter(detail => { - const selection = qtySelection[detail.id] || (detail.secondStockTakeQty != null && detail.secondStockTakeQty > 0 ? "second" : "first"); - const difference = calculateDifference(detail, selection); - return difference !== 0; - }); - }, [inventoryLotDetails, showOnlyWithDifference, qtySelection, calculateDifference]); + return result; + }, [inventoryLotDetails, showOnlyWithDifference, variancePercentTolerance, qtySelection, calculateDifference]); const handleSaveApproverStockTake = useCallback(async (detail: InventoryLotDetailResponse) => { if (!selectedSession || !currentUserId) { @@ -231,7 +254,22 @@ const ApproverStockTake: React.FC = ({ onSnackbar(t("Approver stock take record saved successfully"), "success"); - await loadDetails(page, pageSize); + // 計算最終數量(合格數) + const goodQty = finalQty - finalBadQty; + + setInventoryLotDetails((prev) => + prev.map((d) => + d.id === detail.id + ? { + ...d, + finalQty: goodQty, + approverQty: selection === "approver" ? finalQty : d.approverQty, + approverBadQty: selection === "approver" ? finalBadQty : d.approverBadQty, + stockTakeRecordStatus: "completed", + } + : d + ) + ); } catch (e: any) { console.error("Save approver stock take record error:", e); let errorMessage = t("Failed to save approver stock take record"); @@ -264,6 +302,11 @@ const ApproverStockTake: React.FC = ({ await updateStockTakeRecordStatusToNotMatch(detail.stockTakeRecordId); onSnackbar(t("Stock take record status updated to not match"), "success"); + setInventoryLotDetails((prev) => + prev.map((d) => + d.id === detail.id ? { ...d, stockTakeRecordStatus: "notMatch" } : d + ) + ); } catch (e: any) { console.error("Update stock take record status error:", e); let errorMessage = t("Failed to update stock take record status"); @@ -284,17 +327,9 @@ const ApproverStockTake: React.FC = ({ setUpdatingStatus(false); // Reload after status update - the useEffect will handle it with current page/pageSize // Or explicitly reload: - setPage((currentPage) => { - setPageSize((currentPageSize) => { - setTimeout(() => { - loadDetails(currentPage, currentPageSize); - }, 0); - return currentPageSize; - }); - return currentPage; - }); + } - }, [selectedSession, t, onSnackbar, loadDetails]); + }, [selectedSession, t, onSnackbar, ]); const handleBatchSubmitAll = useCallback(async () => { if (!selectedSession || !currentUserId) { @@ -309,6 +344,7 @@ const ApproverStockTake: React.FC = ({ stockTakeId: selectedSession.stockTakeId, stockTakeSection: selectedSession.stockTakeSession, approverId: currentUserId, + variancePercentTolerance: parseFloat(variancePercentTolerance || "0") || undefined, }; const result = await batchSaveApproverStockTakeRecords(request); @@ -349,10 +385,10 @@ const ApproverStockTake: React.FC = ({ }, [handleBatchSubmitAll]); const formatNumber = (num: number | null | undefined): string => { - if (num == null) return "0.00"; + if (num == null) return "0"; return num.toLocaleString('en-US', { - minimumFractionDigits: 2, - maximumFractionDigits: 2 + minimumFractionDigits: 0, + maximumFractionDigits: 0 }); }; @@ -411,25 +447,30 @@ const ApproverStockTake: React.FC = ({ - - - + {loadingDetails ? ( @@ -454,9 +495,10 @@ const ApproverStockTake: React.FC = ({ {t("Warehouse Location")} {t("Item-lotNo-ExpiryDate")} + {t("UOM")} {t("Stock Take Qty(include Bad Qty)= Available Qty")} {t("Remark")} - {t("UOM")} + {t("Record Status")} {t("Action")} @@ -492,25 +534,27 @@ const ApproverStockTake: React.FC = ({ {detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"} - + {detail.uom || "-"} - {detail.finalQty != null ? ( - - {(() => { - const finalDifference = (detail.finalQty || 0) - (detail.availableQty || 0); - const differenceColor = finalDifference > 0 - ? 'error.main' - : finalDifference < 0 - ? 'error.main' - : 'success.main'; - - return ( - - {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(detail.availableQty)} = {formatNumber(finalDifference)} - - ); - })()} - + {detail.finalQty != null ? ( + + {(() => { + // 若有 bookQty(盤點當時帳面),用它來算差異;否則用 availableQty + const bookQtyToUse = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0); + const finalDifference = (detail.finalQty || 0) - bookQtyToUse; + const differenceColor = detail.stockTakeRecordStatus === "completed" + ? 'text.secondary' + : finalDifference !== 0 + ? 'error.main' + : 'success.main'; + + return ( + + {t("Difference")}: {formatNumber(detail.finalQty)} - {formatNumber(bookQtyToUse)} = {formatNumber(finalDifference)} + + ); + })()} + ) : ( {hasFirst && ( @@ -581,7 +625,7 @@ const ApproverStockTake: React.FC = ({ disabled={selection !== "approver"} /> - ={(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))} + = {formatNumber(parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))} )} @@ -597,12 +641,12 @@ const ApproverStockTake: React.FC = ({ selectedQty = (parseFloat(approverQty[detail.id] || "0") - parseFloat(approverBadQty[detail.id] || "0"))|| 0; } - const bookQty = detail.availableQty || 0; + const bookQty = detail.bookQty != null ? detail.bookQty : (detail.availableQty || 0); const difference = selectedQty - bookQty; - const differenceColor = difference > 0 - ? 'error.main' - : difference < 0 - ? 'error.main' + const differenceColor = detail.stockTakeRecordStatus === "completed" + ? 'text.secondary' + : difference !== 0 + ? 'error.main' : 'success.main'; return ( @@ -621,11 +665,13 @@ const ApproverStockTake: React.FC = ({ - {detail.uom || "-"} + - {detail.stockTakeRecordStatus === "pass" ? ( + {detail.stockTakeRecordStatus === "completed" ? ( + ) : detail.stockTakeRecordStatus === "pass" ? ( + ) : detail.stockTakeRecordStatus === "notMatch" ? ( ) : ( diff --git a/src/components/StockTakeManagement/PickerReStockTake.tsx b/src/components/StockTakeManagement/PickerReStockTake.tsx index 9233ca8..1ff1001 100644 --- a/src/components/StockTakeManagement/PickerReStockTake.tsx +++ b/src/components/StockTakeManagement/PickerReStockTake.tsx @@ -21,7 +21,6 @@ import { useState, useCallback, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { AllPickedStockTakeListReponse, - getInventoryLotDetailsBySection, InventoryLotDetailResponse, saveStockTakeRecord, SaveStockTakeRecordRequest, @@ -51,13 +50,13 @@ const PickerReStockTake: React.FC = ({ const [inventoryLotDetails, setInventoryLotDetails] = useState([]); const [loadingDetails, setLoadingDetails] = useState(false); - // 编辑状态 - const [editingRecord, setEditingRecord] = useState(null); - const [firstQty, setFirstQty] = useState(""); - const [secondQty, setSecondQty] = useState(""); - const [firstBadQty, setFirstBadQty] = useState(""); - const [secondBadQty, setSecondBadQty] = useState(""); - const [remark, setRemark] = useState(""); + const [recordInputs, setRecordInputs] = useState>({}); const [saving, setSaving] = useState(false); const [batchSaving, setBatchSaving] = useState(false); const [shortcutInput, setShortcutInput] = useState(""); @@ -115,28 +114,36 @@ const PickerReStockTake: React.FC = ({ } }, [selectedSession, total]); + useEffect(() => { + const inputs: Record = {}; + inventoryLotDetails.forEach((detail) => { + const firstTotal = detail.firstStockTakeQty != null + ? (detail.firstStockTakeQty + (detail.firstBadQty ?? 0)).toString() + : ""; + const secondTotal = detail.secondStockTakeQty != null + ? (detail.secondStockTakeQty + (detail.secondBadQty ?? 0)).toString() + : ""; + inputs[detail.id] = { + firstQty: firstTotal, + secondQty: secondTotal, + firstBadQty: detail.firstBadQty?.toString() || "", + secondBadQty: detail.secondBadQty?.toString() || "", + remark: detail.remarks || "", + }; + }); + setRecordInputs(inputs); + }, [inventoryLotDetails]); + useEffect(() => { loadDetails(page, pageSize); }, [page, pageSize, loadDetails]); - - const handleStartEdit = useCallback((detail: InventoryLotDetailResponse) => { - setEditingRecord(detail); - setFirstQty(detail.firstStockTakeQty?.toString() || ""); - setSecondQty(detail.secondStockTakeQty?.toString() || ""); - setFirstBadQty(detail.firstBadQty?.toString() || ""); - setSecondBadQty(detail.secondBadQty?.toString() || ""); - setRemark(detail.remarks || ""); - }, []); - - const handleCancelEdit = useCallback(() => { - setEditingRecord(null); - setFirstQty(""); - setSecondQty(""); - setFirstBadQty(""); - setSecondBadQty(""); - setRemark(""); - }, []); - + const formatNumber = (num: number | null | undefined): string => { + if (num == null || Number.isNaN(num)) return "0"; + return num.toLocaleString("en-US", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }); + }; const handleSaveStockTake = useCallback(async (detail: InventoryLotDetailResponse) => { if (!selectedSession || !currentUserId) { return; @@ -145,41 +152,69 @@ const PickerReStockTake: React.FC = ({ const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; - const qty = isFirstSubmit ? firstQty : secondQty; - const badQty = isFirstSubmit ? firstBadQty : secondBadQty; - - if (!qty || !badQty) { + // 用戶輸入為 total 和 bad,需計算 available = total - bad(與 PickerStockTake 一致) + const totalQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstQty : recordInputs[detail.id]?.secondQty; + const badQtyStr = isFirstSubmit ? recordInputs[detail.id]?.firstBadQty : recordInputs[detail.id]?.secondBadQty; + + if (!totalQtyStr) { onSnackbar( isFirstSubmit - ? t("Please enter QTY and Bad QTY") - : t("Please enter Second QTY and Bad QTY"), + ? t("Please enter QTY") + : t("Please enter Second QTY"), "error" ); return; } - + + const totalQty = parseFloat(totalQtyStr); + const badQty = parseFloat(badQtyStr || "0") || 0; + + if (Number.isNaN(totalQty)) { + onSnackbar(t("Invalid QTY"), "error"); + return; + } + + const availableQty = totalQty - badQty; + + if (availableQty < 0) { + onSnackbar(t("Available QTY cannot be negative"), "error"); + return; + } + setSaving(true); try { const request: SaveStockTakeRecordRequest = { stockTakeRecordId: detail.stockTakeRecordId || null, inventoryLotLineId: detail.id, - qty: parseFloat(qty), - badQty: parseFloat(badQty), - remark: isSecondSubmit ? (remark || null) : null, + qty: availableQty, + badQty: badQty, + remark: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : null, }; - console.log('handleSaveStockTake: request:', request); - console.log('handleSaveStockTake: selectedSession.stockTakeId:', selectedSession.stockTakeId); - console.log('handleSaveStockTake: currentUserId:', currentUserId); - await saveStockTakeRecord( + const result = await saveStockTakeRecord( request, selectedSession.stockTakeId, currentUserId ); onSnackbar(t("Stock take record saved successfully"), "success"); - handleCancelEdit(); - - await loadDetails(page, pageSize); + + const savedId = result?.id ?? detail.stockTakeRecordId; + setInventoryLotDetails((prev) => + prev.map((d) => + d.id === detail.id + ? { + ...d, + stockTakeRecordId: savedId ?? d.stockTakeRecordId, + firstStockTakeQty: isFirstSubmit ? availableQty : d.firstStockTakeQty, + firstBadQty: isFirstSubmit ? badQty : d.firstBadQty ?? null, + secondStockTakeQty: isSecondSubmit ? availableQty : d.secondStockTakeQty, + secondBadQty: isSecondSubmit ? badQty : d.secondBadQty ?? null, + remarks: isSecondSubmit ? (recordInputs[detail.id]?.remark || null) : d.remarks, + stockTakeRecordStatus: "pass", + } + : d + ) + ); } catch (e: any) { console.error("Save stock take record error:", e); let errorMessage = t("Failed to save stock take record"); @@ -199,15 +234,13 @@ const PickerReStockTake: React.FC = ({ } finally { setSaving(false); } - }, [selectedSession, firstQty, secondQty, firstBadQty, secondBadQty, remark, handleCancelEdit, t, currentUserId, onSnackbar, page, pageSize, loadDetails]); + }, [selectedSession, recordInputs, t, currentUserId, onSnackbar, page, pageSize, loadDetails]); const handleBatchSubmitAll = useCallback(async () => { if (!selectedSession || !currentUserId) { - console.log('handleBatchSubmitAll: Missing selectedSession or currentUserId'); return; } - console.log('handleBatchSubmitAll: Starting batch save...'); setBatchSaving(true); try { const request: BatchSaveStockTakeRecordRequest = { @@ -217,7 +250,6 @@ const PickerReStockTake: React.FC = ({ }; const result = await batchSaveStockTakeRecords(request); - console.log('handleBatchSubmitAll: Result:', result); onSnackbar( t("Batch save completed: {{success}} success, {{errors}} errors", { @@ -273,31 +305,19 @@ const PickerReStockTake: React.FC = ({ const newInput = prev + e.key; if (newInput === '{2fitestall}') { - console.log('✅ Shortcut {2fitestall} detected!'); setTimeout(() => { if (handleBatchSubmitAllRef.current) { - console.log('Calling handleBatchSubmitAll...'); handleBatchSubmitAllRef.current().catch(err => { console.error('Error in handleBatchSubmitAll:', err); }); - } else { - console.error('handleBatchSubmitAllRef.current is null'); } }, 0); return ""; } - if (newInput.length > 15) { - return ""; - } - - if (newInput.length > 0 && !newInput.startsWith('{')) { - return ""; - } - - if (newInput.length > 5 && !newInput.startsWith('{2fi')) { - return ""; - } + if (newInput.length > 15) return ""; + if (newInput.length > 0 && !newInput.startsWith('{')) return ""; + if (newInput.length > 5 && !newInput.startsWith('{2fi')) return ""; return newInput; }); @@ -315,11 +335,15 @@ const PickerReStockTake: React.FC = ({ }, []); const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => { - if (detail.stockTakeRecordStatus === "pass") { + if (selectedSession?.status?.toLowerCase() === "completed") { + return true; + } + const recordStatus = detail.stockTakeRecordStatus?.toLowerCase(); + if (recordStatus === "pass" || recordStatus === "completed") { return true; } return false; - }, []); + }, [selectedSession?.status]); const uniqueWarehouses = Array.from( new Set( @@ -328,6 +352,9 @@ const PickerReStockTake: React.FC = ({ .filter(warehouse => warehouse && warehouse.trim() !== "") ) ).join(", "); + + const defaultInputs = { firstQty: "", secondQty: "", firstBadQty: "", secondBadQty: "", remark: "" }; + return (