"use client"; import { GetPickOrderLineInfo, updateStockOutLineStatus } from "@/app/api/pickOrder/actions"; import { QcItemWithChecks } from "@/app/api/qc"; import { PurchaseQcResult } from "@/app/api/po/actions"; import { Box, Button, Grid, Modal, ModalProps, Stack, Typography, TextField, Radio, RadioGroup, FormControlLabel, FormControl, Tab, Tabs, TabsProps, Paper, } from "@mui/material"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { dummyQCData } from "../PoDetail/dummyQcTemplate"; import StyledDataGrid from "../StyledDataGrid"; import { GridColDef } from "@mui/x-data-grid"; import { submitDialogWithWarning } from "../Swal/CustomAlerts"; import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; import EscalationComponent from "../PoDetail/EscalationComponent"; import { fetchPickOrderQcResult, savePickOrderQcResult } from "@/app/api/qc/actions"; import { updateInventoryLotLineStatus } from "@/app/api/inventory/actions"; // 导入新的 API import { dayjsToDateTimeString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; // Define QcData interface locally interface ExtendedQcItem extends QcItemWithChecks { qcPassed?: boolean; failQty?: number; remarks?: string; order?: number; // Add order property stableId?: string; // Also add stableId for better row identification } interface Props extends CommonProps { itemDetail: GetPickOrderLineInfo & { pickOrderCode: string; qcResult?: PurchaseQcResult[] }; qcItems: ExtendedQcItem[]; setQcItems: Dispatch>; selectedLotId?: number; onStockOutLineUpdate?: () => void; lotData: LotPickData[]; // Add missing props pickQtyData?: PickQtyData; selectedRowId?: number; } const style = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", pt: 5, px: 5, pb: 10, display: "block", width: { xs: "80%", sm: "80%", md: "80%" }, maxHeight: "90vh", overflowY: "auto", }; interface PickQtyData { [lineId: number]: { [lotId: number]: number; }; } interface CommonProps extends Omit { itemDetail: GetPickOrderLineInfo & { pickOrderCode: string; qcResult?: PurchaseQcResult[] }; setItemDetail: Dispatch< SetStateAction< | (GetPickOrderLineInfo & { pickOrderCode: string; warehouseId?: number; }) | undefined > >; qc?: QcItemWithChecks[]; warehouse?: any[]; } interface Props extends CommonProps { itemDetail: GetPickOrderLineInfo & { pickOrderCode: string; qcResult?: PurchaseQcResult[] }; qcItems: ExtendedQcItem[]; // Change to ExtendedQcItem setQcItems: Dispatch>; // Change to ExtendedQcItem // Add props for stock out line update selectedLotId?: number; onStockOutLineUpdate?: () => void; lotData: LotPickData[]; } interface LotPickData { id: number; lotId: number; lotNo: string; expiryDate: string; location: string; stockUnit: string; availableQty: number; requiredQty: number; actualPickQty: number; lotStatus: string; lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected'; stockOutLineId?: number; stockOutLineStatus?: string; stockOutLineQty?: number; } const PickQcStockInModalVer3: React.FC = ({ open, onClose, itemDetail, setItemDetail, qc, warehouse, qcItems, setQcItems, selectedLotId, onStockOutLineUpdate, lotData, pickQtyData, selectedRowId, }) => { const { t, i18n: { language }, } = useTranslation("pickOrder"); const [tabIndex, setTabIndex] = useState(0); //const [qcItems, setQcItems] = useState(dummyQCData); const [isCollapsed, setIsCollapsed] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [feedbackMessage, setFeedbackMessage] = useState(""); // Add state to store submitted data const [submittedData, setSubmittedData] = useState([]); const formProps = useForm({ defaultValues: { qcAccept: true, acceptQty: null, qcDecision: "1", // Default to accept ...itemDetail, }, }); const { control, register, formState: { errors }, watch, setValue } = formProps; const qcDecision = watch("qcDecision"); const accQty = watch("acceptQty"); const closeHandler = useCallback>( (...args) => { onClose?.(...args); }, [onClose], ); const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); // Save failed QC results only const saveQcResults = async (qcData: any) => { try { const qcResults = qcData.qcItems .map((item: any) => ({ qcItemId: item.id, itemId: itemDetail.itemId, stockInLineId: null, stockOutLineId: 1, // Fixed to 1 as requested failQty: item.isPassed ? 0 : (item.failQty || 0), // 0 for passed, actual qty for failed type: "pick_order_qc", remarks: item.remarks || "", qcPassed: item.isPassed, // This will now be included })); // Store the submitted data for debug display setSubmittedData(qcResults); console.log("Saving QC results:", qcResults); // Use the corrected API function instead of manual fetch for (const qcResult of qcResults) { const response = await savePickOrderQcResult(qcResult); console.log("QC Result save success:", response); // Check if the response indicates success if (!response.id) { throw new Error(`Failed to save QC result: ${response.message || 'Unknown error'}`); } } return true; } catch (error) { console.error("Error saving QC results:", error); return false; } }; // 修改:在组件开始时自动设置失败数量 useEffect(() => { if (itemDetail && qcItems.length > 0 && selectedLotId) { // 获取选中的批次数据 const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId); if (selectedLot) { // 自动将 Lot Required Pick Qty 设置为所有失败项目的 failQty const updatedQcItems = qcItems.map((item, index) => ({ ...item, failQty: selectedLot.requiredQty || 0, // 使用 Lot Required Pick Qty // Add stable order and ID fields order: index, stableId: `qc-${item.id}-${index}` })); setQcItems(updatedQcItems); } } }, [itemDetail, qcItems.length, selectedLotId, lotData]); // Add this helper function at the top of the component const safeClose = useCallback(() => { if (onClose) { // Create a mock event object that satisfies the Modal onClose signature const mockEvent = { target: null, currentTarget: null, type: 'close', preventDefault: () => {}, stopPropagation: () => {}, bubbles: false, cancelable: false, defaultPrevented: false, isTrusted: false, timeStamp: Date.now(), nativeEvent: null, isDefaultPrevented: () => false, isPropagationStopped: () => false, persist: () => {}, eventPhase: 0, isPersistent: () => false } as any; // Fixed: Pass both event and reason parameters onClose(mockEvent, 'escapeKeyDown'); // 'escapeKeyDown' is a valid reason } }, [onClose]); // 修改:移除 alert 弹窗,改为控制台日志 const onSubmitQc = useCallback>( async (data, event) => { setIsSubmitting(true); try { const qcAccept = qcDecision === "1"; const acceptQty = Number(accQty) || null; const validationErrors : string[] = []; const selectedLot = lotData.find(lot => lot.stockOutLineId === selectedLotId); // Add safety check for selectedLot if (!selectedLot) { console.error("Selected lot not found"); return; } const itemsWithoutResult = qcItems.filter(item => item.qcPassed === undefined); if (itemsWithoutResult.length > 0) { validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(", ")}`); } if (validationErrors.length > 0) { console.error(`QC validation failed: ${validationErrors.join(", ")}`); return; } const qcData = { qcAccept, acceptQty, qcItems: qcItems.map(item => ({ id: item.id, qcItem: item.code, qcDescription: item.description || "", isPassed: item.qcPassed, failQty: item.qcPassed ? 0 : (selectedLot.requiredQty || 0), remarks: item.remarks || "", })), }; console.log("Submitting QC data:", qcData); const saveSuccess = await saveQcResults(qcData); if (!saveSuccess) { console.error("Failed to save QC results"); return; } // Handle different QC decisions if (selectedLotId) { try { const allPassed = qcData.qcItems.every(item => item.isPassed); if (qcDecision === "2") { // QC Decision 2: Report and Re-pick console.log("QC Decision 2 - Report and Re-pick: Rejecting lot and marking as unavailable"); // Inventory lot line status: unavailable if (selectedLot) { try { console.log("=== DEBUG: Updating inventory lot line status ==="); console.log("Selected lot:", selectedLot); console.log("Selected lot ID:", selectedLotId); // FIX: Only send the fields that the backend expects const updateData = { inventoryLotLineId: selectedLot.lotId, status: 'unavailable' // ❌ Remove qty and operation - backend doesn't expect these }; console.log("Update data:", updateData); const result = await updateInventoryLotLineStatus(updateData); console.log(" Inventory lot line status updated successfully:", result); } catch (error) { console.error("❌ Error updating inventory lot line status:", error); console.error("Error details:", { selectedLot, selectedLotId, acceptQty }); // Show user-friendly error message return; // Stop execution if this fails } } else { console.error("❌ Selected lot not found for inventory update"); alert("Selected lot not found. Cannot update inventory status."); return; } // Close modal and refresh data safeClose(); // Fixed: Use safe close function with both parameters if (onStockOutLineUpdate) { onStockOutLineUpdate(); } } else if (qcDecision === "1") { // QC Decision 1: Accept console.log("QC Decision 1 - Accept: QC passed"); // Stock out line status: checked (QC completed) await updateStockOutLineStatus({ id: selectedLotId, status: 'checked', qty: acceptQty || 0 }); // Inventory lot line status: NO CHANGE needed // Keep the existing status from handleSubmitPickQty // Close modal and refresh data safeClose(); // Fixed: Use safe close function with both parameters if (onStockOutLineUpdate) { onStockOutLineUpdate(); } } console.log("Stock out line status updated successfully after QC"); // Call callback to refresh data if (onStockOutLineUpdate) { onStockOutLineUpdate(); } } catch (error) { console.error("Error updating stock out line status after QC:", error); } } console.log("QC results saved successfully!"); // Show warning dialog for failed QC items when accepting if (qcDecision === "1" && !qcData.qcItems.every((q) => q.isPassed)) { submitDialogWithWarning(() => { closeHandler?.({}, 'escapeKeyDown'); }, t, {title:"有不合格檢查項目,確認接受出庫?", confirmButtonText: "Confirm", html: ""}); return; } closeHandler?.({}, 'escapeKeyDown'); } catch (error) { console.error("Error in QC submission:", error); if (error instanceof Error) { console.error("Error details:", error.message); console.error("Error stack:", error.stack); } } finally { setIsSubmitting(false); } }, [qcItems, closeHandler, t, itemDetail, qcDecision, accQty, selectedLotId, onStockOutLineUpdate, lotData, pickQtyData, selectedRowId], ); // DataGrid columns (QcComponent style) const qcColumns: GridColDef[] = useMemo( () => [ { field: "code", headerName: t("qcItem"), flex: 2, renderCell: (params) => ( {`${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}. ${params.value}`}
{params.row.name}
), }, { field: "qcPassed", headerName: t("qcResult"), flex: 1.5, renderCell: (params) => { const current = params.row; return ( { const value = e.target.value === "true"; // Simple state update setQcItems(prev => prev.map(item => item.id === params.id ? { ...item, qcPassed: value } : item ) ); }} name={`qcPassed-${params.id}`} > } label="合格" sx={{ color: current.qcPassed === true ? "green" : "inherit", "& .Mui-checked": {color: "green"} }} /> } label="不合格" sx={{ color: current.qcPassed === false ? "red" : "inherit", "& .Mui-checked": {color: "red"} }} /> ); }, }, { field: "failQty", headerName: t("failedQty"), flex: 1, renderCell: (params) => ( { // const v = e.target.value; // const next = v === "" ? undefined : Number(v); // if (Number.isNaN(next)) return; // setQcItems((prev) => // prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) // ); // }} onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} inputProps={{ min: 0, max: itemDetail?.requiredQty || 0 }} sx={{ width: "100%" }} /> ), }, { field: "remarks", headerName: t("remarks"), flex: 2, renderCell: (params) => ( { const remarks = e.target.value; setQcItems((prev) => prev.map((r) => (r.id === params.id ? { ...r, remarks } : r)) ); }} onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} sx={{ width: "100%" }} /> ), }, ], [t], ); // Add stable update function const handleQcResultChange = useCallback((itemId: number, qcPassed: boolean) => { setQcItems(prevItems => prevItems.map(item => item.id === itemId ? { ...item, qcPassed } : item ) ); }, []); // Remove duplicate functions const getRowId = useCallback((row: any) => { return row.id; // Just use the original ID }, []); // Remove complex sorting logic // const stableQcItems = useMemo(() => { ... }); // Remove // const sortedQcItems = useMemo(() => { ... }); // Remove // Use qcItems directly in DataGrid return ( <> {tabIndex == 0 && ( <> Group A - 急凍貨類 (QCA1-MEAT01) 品檢類型:OQC 記錄探測溫度的時間,請在1小時内完成出庫盤點,以保障食品安全
監察方法:目視檢查、嗅覺檢查和使用適當的食物溫度計,檢查食物溫度是否符合指標
)} {tabIndex == 1 && ( <> )} ( { const value = e.target.value.toString(); if (value != "1" && Boolean(errors.acceptQty)) { setValue("acceptQty", itemDetail.requiredQty ?? 0); } field.onChange(value); }} > } label={t("Accept Stock Out")} /> {/* Combirne options 2 & 3 into one */} } sx={{"& .Mui-checked": {color: "blue"}}} label={t("Report and Pick another lot")} /> )} /> {/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}
); }; export default PickQcStockInModalVer3;