| @@ -42,6 +42,7 @@ import FgStockInForm from "../StockIn/FgStockInForm"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| import { printFGStockInLabel, PrintFGStockInLabelRequest, fetchFGStockInLabel } from "@/app/api/jo/actions"; | |||
| import { fetchItemForPutAway } from "@/app/api/stockIn/actions"; | |||
| import { fetchWarehouseListClient } from "@/app/api/warehouse/client"; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| @@ -97,6 +98,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| const [isSubmitting, setIsSubmitting] = useState<boolean>(false); | |||
| // const [skipQc, setSkipQc] = useState<Boolean>(false); | |||
| // const [viewOnly, setViewOnly] = useState(false); | |||
| const [itemLocationCode, setItemLocationCode] = useState<string | null>(null); | |||
| const printerStorageKey = useMemo( | |||
| () => `qcStockInModal_selectedPrinterId_${session?.id ?? "guest"}`, | |||
| @@ -340,10 +342,11 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // } | |||
| // Get QC data from the shared form context | |||
| const qcAccept = data.qcDecision == 1; | |||
| const qcAcceptLocal = data.qcDecision == 1; | |||
| // const qcAccept = data.qcAccept; | |||
| let acceptQty = Number(data.acceptQty); | |||
| const qcResults = data.qcResult?.filter((qc) => qc.escalationLogId === undefined) || []; // Remove old QC data | |||
| let acceptQtyLocal = Number(data.acceptQty); | |||
| const qcResultsLocal = data.qcResult?.filter((qc) => qc.escalationLogId === undefined) || []; // Remove old QC data | |||
| // const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems; | |||
| // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; | |||
| @@ -352,7 +355,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // Check if failed items have failed quantity | |||
| const failedItemsWithoutQty = qcResults.filter(item => | |||
| const failedItemsWithoutQty = qcResultsLocal.filter(item => | |||
| item.qcPassed === false && (!item.failQty || item.failQty <= 0) | |||
| ); | |||
| if (failedItemsWithoutQty.length > 0) { | |||
| @@ -368,9 +371,9 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // Check if accept quantity is valid | |||
| if (data.qcDecision == 2) { | |||
| acceptQty = 0; | |||
| acceptQtyLocal = 0; | |||
| } else { | |||
| if (acceptQty === undefined || acceptQty <= 0) { | |||
| if (acceptQtyLocal === undefined || acceptQtyLocal <= 0) { | |||
| validationErrors.push("Accept quantity must be greater than 0"); | |||
| } | |||
| } | |||
| @@ -383,7 +386,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| alert("請輸入到期日!"); | |||
| return; | |||
| } | |||
| if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please! | |||
| if (!qcResultsLocal.every((qc) => qc.qcPassed) && qcAcceptLocal && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please! | |||
| validationErrors.push("有不合格檢查項目,無法收貨!"); | |||
| // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| @@ -391,7 +394,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| } | |||
| // Check if all QC items have results | |||
| const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | |||
| const itemsWithoutResult = qcResultsLocal.filter(item => item.qcPassed === undefined); | |||
| if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != StockInStatus.ESCALATED) { //TODO: fix it please! | |||
| validationErrors.push(`${t("QC items without result")}`); | |||
| @@ -412,13 +415,13 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| expiryDate : data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined, | |||
| receiptDate : data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined, | |||
| qcAccept: qcAccept? qcAccept : false, | |||
| acceptQty: acceptQty? acceptQty : 0, | |||
| qcAccept: qcAcceptLocal ? qcAcceptLocal : false, | |||
| acceptQty: acceptQtyLocal ? acceptQtyLocal : 0, | |||
| // For Job Order QC, allow updating received qty beyond demand/accepted. | |||
| // Backend uses request.acceptedQty in QC flow, so we must send it explicitly. | |||
| acceptedQty: (qcAccept && isJobOrderSource) ? (acceptQty ? acceptQty : 0) : stockInLineInfo?.acceptedQty, | |||
| acceptedQty: (qcAcceptLocal && isJobOrderSource) ? (acceptQtyLocal ? acceptQtyLocal : 0) : stockInLineInfo?.acceptedQty, | |||
| // qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({ | |||
| qcResult: qcResults.map(item => ({ | |||
| qcResult: qcResultsLocal.map(item => ({ | |||
| // id: item.id, | |||
| qcItemId: item.qcItemId, | |||
| // code: item.code, | |||
| @@ -462,22 +465,43 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| // onOpenPutaway(); | |||
| const isJobOrderBom = (stockInLineInfo?.jobOrderId != null || printSource === "productionProcess") | |||
| && stockInLineInfo?.bomDescription === "WIP"; | |||
| const isJobOrderSource = (stockInLineInfo?.jobOrderId != null || printSource === "productionProcess"); | |||
| const isWipBom = isJobOrderSource && stockInLineInfo?.bomDescription === "WIP"; | |||
| const isFgBom = isJobOrderSource && stockInLineInfo?.bomDescription === "FG"; | |||
| const isFaItem = (stockInLineInfo?.itemNo ?? "").toUpperCase().includes("FA"); | |||
| const shouldAutoPutaway = isJobOrderBom || isFaItem; | |||
| const shouldAutoPutaway = isWipBom || isFgBom || isFaItem; | |||
| if (shouldAutoPutaway) { | |||
| // Auto putaway to default warehouse | |||
| const defaultWarehouseId = stockInLineInfo?.defaultWarehouseId ?? 1141; | |||
| const loc = (itemLocationCode ?? "").trim().toUpperCase(); | |||
| const warehouseListForLookup = | |||
| isFgBom && ((warehouse?.length ?? 0) === 0) | |||
| ? await fetchWarehouseListClient() | |||
| : (warehouse ?? []); | |||
| const matchedWarehouse = | |||
| isFgBom && loc.length > 0 | |||
| ? warehouseListForLookup.find((w) => (w.code ?? "").trim().toUpperCase().endsWith(loc)) | |||
| : undefined; | |||
| const resolvedWarehouseId = | |||
| (isFgBom ? matchedWarehouse?.id : undefined) | |||
| ?? stockInLineInfo?.defaultWarehouseId | |||
| ?? 1141; | |||
| console.log("[AUTO_PUTAWAY_DEBUG]", { | |||
| silId: stockInLineInfo?.id, | |||
| bomDescription: stockInLineInfo?.bomDescription, | |||
| isJobOrderSource, | |||
| isWipBom, | |||
| isFgBom, | |||
| isFaItem, | |||
| itemLocationCode, | |||
| loc, | |||
| warehouseCount: warehouse?.length, | |||
| matchedWarehouse: matchedWarehouse ? { id: matchedWarehouse.id, code: matchedWarehouse.code } : null, | |||
| defaultWarehouseId: stockInLineInfo?.defaultWarehouseId, | |||
| resolvedWarehouseId, | |||
| }); | |||
| // Get warehouse name from warehouse prop or use default | |||
| let defaultWarehouseName = "2F-W200-#A-00"; // Default warehouse name | |||
| if (warehouse && warehouse.length > 0) { | |||
| const defaultWarehouse = warehouse.find(w => w.id === defaultWarehouseId); | |||
| if (defaultWarehouse) { | |||
| defaultWarehouseName = `${defaultWarehouse.code} - ${defaultWarehouse.name}`; | |||
| } | |||
| } | |||
| // Create putaway data | |||
| const putawayData = { | |||
| @@ -485,19 +509,26 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| itemId: stockInLineInfo?.itemId, // Include Item ID | |||
| purchaseOrderId: stockInLineInfo?.purchaseOrderId, // Include PO ID if exists | |||
| purchaseOrderLineId: stockInLineInfo?.purchaseOrderLineId, // Include POL ID if exists | |||
| acceptedQty: acceptQty, // Keep in sync with QC acceptQty | |||
| acceptQty: acceptQty, // Putaway quantity | |||
| warehouseId: defaultWarehouseId, | |||
| acceptedQty: acceptQtyLocal, // Keep in sync with QC acceptQty | |||
| acceptQty: acceptQtyLocal, // Putaway quantity | |||
| warehouseId: resolvedWarehouseId, | |||
| status: "received", // Use string like PutAwayModal | |||
| productionDate: data.productionDate ? (Array.isArray(data.productionDate) ? arrayToDateString(data.productionDate, "input") : data.productionDate) : undefined, | |||
| expiryDate: data.expiryDate ? (Array.isArray(data.expiryDate) ? arrayToDateString(data.expiryDate, "input") : data.expiryDate) : undefined, | |||
| receiptDate: data.receiptDate ? (Array.isArray(data.receiptDate) ? arrayToDateString(data.receiptDate, "input") : data.receiptDate) : undefined, | |||
| inventoryLotLines: [{ | |||
| warehouseId: defaultWarehouseId, | |||
| qty: acceptQty, // Putaway qty | |||
| warehouseId: resolvedWarehouseId, | |||
| qty: acceptQtyLocal, // Putaway qty | |||
| }], | |||
| } as StockInLineEntry & ModalFormInput; | |||
| console.log("[AUTO_PUTAWAY]", { | |||
| isFgBom, | |||
| itemLocationCode, | |||
| loc, | |||
| warehouseCount: warehouse?.length, | |||
| matchedWarehouse: isFgBom ? warehouse?.find(w => (w.code ?? "").trim().toUpperCase().endsWith(loc)) : null, | |||
| resolvedWarehouseId, | |||
| }); | |||
| try { | |||
| // Use updateStockInLine directly like PutAwayModal does | |||
| const res = await updateStockInLine(putawayData); | |||
| @@ -510,6 +541,7 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| } | |||
| } | |||
| closeWithResult(qcRes); | |||
| // setTabIndex(1); // Need to go Putaway tab? | |||
| } else { | |||
| @@ -520,7 +552,15 @@ const QcStockInModal: React.FC<Props> = ({ | |||
| return ; | |||
| }, | |||
| [onOpenPutaway, formProps.formState.errors], | |||
| [ | |||
| stockInLineInfo, | |||
| printSource, | |||
| skipQc, | |||
| warehouse, | |||
| itemLocationCode, | |||
| closeWithResult, | |||
| t, | |||
| ], | |||
| ); | |||
| const postStockInLine = useCallback(async (args: ModalFormInput) => { | |||