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