"use client"; import { fetchPoWithStockInLines, PoResult, PurchaseOrderLine, } from "@/app/api/po"; import { Box, Button, ButtonProps, Collapse, Grid, IconButton, Paper, Stack, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tabs, TabsProps, TextField, Typography, Checkbox, FormControlLabel, Card, CardContent, Radio, alpha, Autocomplete, Dialog, DialogActions, DialogContent, DialogTitle, } from "@mui/material"; import { useTranslation } from "react-i18next"; import { submitDialogWithWarning } from "../Swal/CustomAlerts"; // import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid"; import { GridColDef, GridRowId, GridRowModel, useGridApiRef, } from "@mui/x-data-grid"; import { checkPolAndCompletePo, fetchPoInClient, fetchPoSummariesClient, startPo, } from "@/app/api/po/actions"; import { createStockInLine } from "@/app/api/stockIn/actions"; import { useCallback, useContext, useEffect, useMemo, useState, } from "react"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import PoInputGrid from "./PoInputGrid"; // import { QcItemWithChecks } from "@/app/api/qc"; import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { WarehouseResult } from "@/app/api/warehouse"; import { calculateWeight, dateStringToDayjs, dayjsToDateString, OUTPUT_DATE_FORMAT, outputDateStringToInputDateString, returnWeightUnit } from "@/app/utils/formatUtil"; import { CameraContext } from "../Cameras/CameraProvider"; import QrModal from "./QrModal"; import { PlayArrow } from "@mui/icons-material"; import DoneIcon from "@mui/icons-material/Done"; import { downloadFile, getCustomWidth } from "@/app/utils/commonUtil"; import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; import { arrayToDateString } from "@/app/utils/formatUtil"; import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material"; import { Controller, FormProvider, useForm } from "react-hook-form"; import dayjs, { Dayjs } from "dayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; import LoadingComponent from "../General/LoadingComponent"; import { getMailTemplatePdfForStockInLine } from "@/app/api/mailTemplate/actions"; import { PrinterCombo } from "@/app/api/settings/printer"; import { EscalationCombo } from "@/app/api/user"; import { StockInLine } from "@/app/api/stockIn"; import { printQrCodeForSil } from "@/app/api/stockIn/actions"; import { useSession } from "next-auth/react"; import { AUTH } from "@/authorities"; //import { useRouter } from "next/navigation"; type Props = { po: PoResult; // qc: QcItemWithChecks[]; warehouse: WarehouseResult[]; printerCombo: PrinterCombo[]; }; /** PO stock-in lines still in pre-complete workflow (align with nav alert: pending / receiving). */ const PURCHASE_STOCK_IN_ALERT_STATUSES = new Set(["pending", "receiving"]); /** Sum of put-away in stock units (matches StockInForm「已上架數量」stockQty). */ function totalPutAwayStockQtyForPol(row: PurchaseOrderLine): number { return row.stockInLine .filter((sil) => sil.purchaseOrderLineId === row.id) .reduce((acc, sil) => { const lineSum = sil.putAwayLines?.reduce( (s, p) => s + Number(p.stockQty ?? p.qty ?? 0), 0, ) ?? 0; return acc + lineSum; }, 0); } /** POL order demand in stock units (same basis as PoDetail processed / backend PO detail). */ function polOrderStockQty(row: PurchaseOrderLine): number { return Number(row.stockUom?.stockQty ?? row.qty ?? 0); } function purchaseOrderLineHasIncompleteStockIn(row: PurchaseOrderLine): boolean { const orderStock = polOrderStockQty(row); const putAway = totalPutAwayStockQtyForPol(row); if (orderStock > 0 && putAway >= orderStock) { return false; } return row.stockInLine .filter((sil) => sil.purchaseOrderLineId === row.id) .some((sil) => PURCHASE_STOCK_IN_ALERT_STATUSES.has((sil.status ?? "").toLowerCase().trim()), ); } type EntryError = | { [field in keyof StockInLine]?: string; } | undefined; // type PolRow = TableRow, EntryError>; const PoSearchList: React.FC<{ poList: PoResult[]; selectedPoId: number; onSelect: (po: PoResult) => void; loading?: boolean; }> = ({ poList, selectedPoId, onSelect, loading = false }) => { const { t } = useTranslation(["purchaseOrder", "dashboard"]); const [searchTerm, setSearchTerm] = useState(''); const filteredPoList = useMemo(() => { if (searchTerm.trim() === '') { return poList; } return poList.filter(poItem => poItem.code.toLowerCase().includes(searchTerm.toLowerCase()) || poItem.supplier?.toLowerCase().includes(searchTerm.toLowerCase()) || t(`${poItem.status.toLowerCase()}`).toLowerCase().includes(searchTerm.toLowerCase()) ); }, [poList, searchTerm, t]); return ( {t("Purchase Order")} setSearchTerm(e.target.value)} sx={{ mb: 2 }} InputProps={{ startAdornment: ( ), }} /> {loading ? ( ) : filteredPoList.length > 0 ? ( {filteredPoList.map((poItem, index) => (
onSelect(poItem)} sx={{ width: "100%", "&.Mui-selected": { backgroundColor: "primary.light", "&:hover": { backgroundColor: "primary.light", }, }, }} > {poItem.code} } secondary={ {t(`${poItem.status.toLowerCase()}`)} } /> {index < filteredPoList.length - 1 && }
))}
) : ( {searchTerm.trim() ? t("No purchase orders match your search", { defaultValue: "沒有符合搜尋的採購單" }) : t("No purchase orders to show", { defaultValue: "沒有可顯示的採購單" })} )}
{searchTerm && ( {`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`} {/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */} )}
); }; interface PolInputResult { lotNo: string, dnQty: string, } const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const cameras = useContext(CameraContext); const { data: session } = useSession(); const canSeeStockInReminders = useMemo(() => { const set = new Set((session?.user?.abilities ?? []).map((a) => String(a).trim())); return set.has(AUTH.TESTING) || set.has(AUTH.ADMIN) || set.has(AUTH.STOCK); }, [session?.user?.abilities]); // console.log(cameras); const { t } = useTranslation("purchaseOrder"); const apiRef = useGridApiRef(); const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); const [rows, setRows] = useState( purchaseOrder.pol || [], ); const [polInputList, setPolInputList] = useState>({}) const PO_DETAIL_SELECTION_KEY = "po-detail-selection"; useEffect(() => { setPolInputList((prev) => { const next: Record = {}; (purchaseOrder.pol ?? []).forEach((pol) => { next[pol.id] = prev[pol.id] ?? { lotNo: "", dnQty: "", }; }); return next; }); }, [purchaseOrder.pol]); useEffect(() => { try { const raw = sessionStorage.getItem("po-detail-selection"); if (raw) { const parsed = JSON.parse(raw) as { id: number; code: string; status: string; supplier: string | null }[]; if (Array.isArray(parsed) && parsed.length > 0) { setPoList(parsed as PoResult[]); sessionStorage.removeItem("po-detail-selection"); // 可选:用一次就删,避免下次从别处进还看到旧数据 } } } catch (e) { console.warn("sessionStorage getItem/parse failed", e); } }, []); const pathname = usePathname() const searchParams = useSearchParams(); const [selectedRow, setSelectedRow] = useState(null); const [stockInLine, setStockInLine] = useState([]); const [processedQty, setProcessedQty] = useState(0); useEffect(() => { const polIdParam = searchParams.get("polId"); if (!polIdParam || rows.length === 0) return; const match = rows.find((r) => r.id.toString() === polIdParam); if (match) { setSelectedRow(match); setStockInLine(match.stockInLine); setProcessedQty(match.processed); } }, [rows, searchParams]); const router = useRouter(); const [poList, setPoList] = useState(() => [po]); const [isPoListLoading, setIsPoListLoading] = useState(false); const [selectedPoId, setSelectedPoId] = useState(po.id); const [focusField, setFocusField] = useState(); const currentPoId = searchParams.get('id'); const selectedIdsParam = searchParams.get('selectedIds'); // const [selectedRowId, setSelectedRowId] = useState(null); const dnFormProps = useForm({ defaultValues: { dnNo: '', receiptDate: dayjsToDateString(dayjs()) } }) const [selectedPrinter, setSelectedPrinter] = useState( printerCombo?.[0], ); const [printQty, setPrintQty] = useState(1); const [printDialogOpen, setPrintDialogOpen] = useState(false); const [isBulkPrinting, setIsBulkPrinting] = useState(false); const [printStatusFilter, setPrintStatusFilter] = useState({ received: true, completed: false, }); const [selectedPrintSilIds, setSelectedPrintSilIds] = useState>( () => new Set(), ); const eligiblePrintSils = useMemo(() => { const statusSet = new Set(); if (printStatusFilter.received) statusSet.add("received"); if (printStatusFilter.completed) statusSet.add("completed"); const pols = purchaseOrder.pol ?? []; return pols .flatMap((pol) => pol.stockInLine ?? []) .filter((sil) => statusSet.has((sil.status ?? "").toLowerCase().trim())); }, [purchaseOrder.pol, printStatusFilter.completed, printStatusFilter.received]); const openPrintDialog = useCallback(() => { setSelectedPrintSilIds(new Set()); setPrintDialogOpen(true); }, []); const closePrintDialog = useCallback(() => { if (isBulkPrinting) return; setPrintDialogOpen(false); }, [isBulkPrinting]); const togglePrintSilSelection = useCallback((id: number, checked: boolean) => { setSelectedPrintSilIds((prev) => { const next = new Set(prev); if (checked) next.add(id); else next.delete(id); return next; }); }, []); const setAllVisiblePrintSilsSelected = useCallback((checked: boolean) => { setSelectedPrintSilIds(() => { if (!checked) return new Set(); return new Set(eligiblePrintSils.map((s) => s.id)); }); }, [eligiblePrintSils]); const handleBulkPrint = useCallback(async () => { if (!selectedPrinter) { alert("請先選擇印表機"); return; } if (!Number.isFinite(printQty) || printQty <= 0) { alert("列印數量必須大於 0"); return; } const ids = Array.from(selectedPrintSilIds.values()); if (ids.length <= 0) { alert("請先選擇要列印的項目"); return; } setIsBulkPrinting(true); try { for (const id of ids) { await printQrCodeForSil({ stockInLineId: id, printerId: selectedPrinter.id, printQty, }); } setPrintDialogOpen(false); } finally { setIsBulkPrinting(false); } }, [printQty, selectedPrinter, selectedPrintSilIds]); /** Only loads sidebar list when `selectedIds` is in the URL; otherwise show current PO only (no /po/list fetch). */ const fetchPoList = useCallback(async () => { if (!selectedIdsParam) return; setIsPoListLoading(true); try { const MAX_IDS = 20; // 一次最多加载 20 个,防止卡死 const allIds = selectedIdsParam .split(',') .map((id) => parseInt(id)) .filter((id) => !Number.isNaN(id)); const limitedIds = allIds.slice(0, MAX_IDS); if (allIds.length > MAX_IDS) { console.warn(`selectedIds too many (${allIds.length}), only loading first ${MAX_IDS}.`); } const result = await fetchPoSummariesClient(limitedIds); setPoList(result as any); } catch (error) { console.error("Failed to fetch PO list:", error); } finally { setIsPoListLoading(false); } }, [selectedIdsParam]); const fetchPoDetail = useCallback(async (poId: string, preserveDnNo: boolean = false, preferredPolId?: number) => { try { const result = await fetchPoInClient(parseInt(poId)); if (result) { console.log("%c Fetched PO:", "color:orange", result); setPurchaseOrder(result); const currentDnNo = preserveDnNo ? dnFormProps.getValues("dnNo") : ""; dnFormProps.reset({ dnNo: currentDnNo, receiptDate: dayjsToDateString(dayjs()), }); setRows(result.pol || []); if (result.pol && result.pol.length > 0) { const targetPolId = preferredPolId ?? selectedRow?.id; const targetPol = result.pol.find((p) => p.id === targetPolId) ?? result.pol[0]; setSelectedRow(targetPol); setStockInLine(targetPol.stockInLine); setProcessedQty(targetPol.processed); } // if (focusField) {console.log(focusField);focusField.focus();} } } catch (error) { console.error("Failed to fetch PO detail:", error); } }, [selectedRow, selectedPoId]); const handlePoSelect = useCallback( async (selectedPo: PoResult) => { if (selectedPo.id === selectedPoId) return; setSelectedPoId(selectedPo.id); await fetchPoDetail(selectedPo.id.toString()); const newSelectedIds = selectedIdsParam || selectedPo.id.toString(); const newUrl = `/po/edit?id=${selectedPo.id}&start=true&selectedIds=${newSelectedIds}`; if (pathname + searchParams.toString() !== newUrl) { router.replace(newUrl, { scroll: false }); } }, [selectedPoId, fetchPoDetail, selectedIdsParam, pathname, searchParams, router] ); useEffect(() => { if (currentPoId && currentPoId !== selectedPoId.toString()) { setSelectedPoId(parseInt(currentPoId)); fetchPoDetail(currentPoId); } }, [currentPoId, fetchPoDetail]); useEffect(() => { if (selectedIdsParam) { void fetchPoList(); } }, [selectedIdsParam, fetchPoList]); useEffect(() => { if (selectedIdsParam) return; setPoList([purchaseOrder]); }, [selectedIdsParam, purchaseOrder]); useEffect(() => { if (currentPoId) { setSelectedPoId(parseInt(currentPoId)); } }, [currentPoId]); const removeParam = (paramToRemove: string) => { const newParams = new URLSearchParams(searchParams.toString()); newParams.delete(paramToRemove); window.history.replaceState({}, '', `${window.location.pathname}?${newParams}`); }; const handleCompletePo = useCallback(async () => { const checkRes = await checkPolAndCompletePo(purchaseOrder.id); console.log(checkRes); const newPo = await fetchPoInClient(purchaseOrder.id); setPurchaseOrder(newPo); }, [purchaseOrder.id]); const handleStartPo = useCallback(async () => { const startRes = await startPo(purchaseOrder.id); console.log(startRes); const newPo = await fetchPoInClient(purchaseOrder.id); setPurchaseOrder(newPo); }, [purchaseOrder.id]); const handleMailTemplateForStockInLine = useCallback(async (stockInLineId: number) => { const response = await getMailTemplatePdfForStockInLine(stockInLineId) if (response) { downloadFile(new Uint8Array(response.blobValue), response.filename); } }, []) useEffect(() => { setRows(purchaseOrder.pol || []); }, [purchaseOrder]); // useEffect(() => { // setStockInLine([]) // }, []); function Row(props: { row: PurchaseOrderLine }) { const { row } = props; // const [firstReceiveQty, setFirstReceiveQty] = useState() // const [secondReceiveQty, setSecondReceiveQty] = useState() // const [open, setOpen] = useState(false); const [processedQty, setProcessedQty] = useState(row.processed); const [currStatus, setCurrStatus] = useState(row.status); const [lotNoInput, setLotNoInput] = useState(polInputList[row.id]?.lotNo ?? ""); const [dnQtyInput, setDnQtyInput] = useState(polInputList[row.id]?.dnQty ?? ""); // const [stockInLine, setStockInLine] = useState(row.stockInLine); const totalWeight = useMemo( () => calculateWeight(row.qty, row.uom), [row.qty, row.uom], ); const weightUnit = useMemo( () => returnWeightUnit(row.uom), [row.uom], ); useEffect(() => { const polId = searchParams.get("polId") != null ? parseInt(searchParams.get("polId")!) : null if (polId) { setStockInLine(rows.find((r) => r.id == polId)!.stockInLine) } }, []); useEffect(() => { // `processedQty` comes from putAwayLines (stock unit). // After the fix, `row.qty` is qtyM18 (M18 unit), so compare using stockUom demand. const targetStockQty = Number(row.stockUom?.stockQty ?? row.qty ?? 0); if (targetStockQty > 0 && processedQty >= targetStockQty) { setCurrStatus("completed".toUpperCase()); } else if (processedQty > 0) { setCurrStatus("receiving".toUpperCase()); } else { setCurrStatus("pending".toUpperCase()); } }, [processedQty, row.qty, row.stockUom?.stockQty]); useEffect(() => { setLotNoInput(polInputList[row.id]?.lotNo ?? ""); setDnQtyInput(polInputList[row.id]?.dnQty ?? ""); }, [polInputList, row.id]); const handleRowSelect = () => { // setSelectedRowId(row.id); setSelectedRow(row); setStockInLine(row.stockInLine); setProcessedQty(row.processed); }; const changeStockInLines = useCallback( (id: number) => { //rows = purchaseOrderLine const target = rows.find((r) => r.id === id) const stockInLine = target!.stockInLine setStockInLine(stockInLine) setSelectedRow(target!) // console.log(pathname) // router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); }, [rows] ); const handleStart = useCallback( () => { const orderQty = Number(row?.qty) ?? 0; const acceptedQty = Number(dnQtyInput.trim()); if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } const doSubmit = () => { setTimeout(async () => { const currentDnNo = dnFormProps.watch("dnNo"); const postData = { dnNo: dnFormProps.watch("dnNo"), receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), itemId: row.itemId, itemNo: row.itemNo, itemName: row.itemName, purchaseOrderLineId: row.id, acceptedQty: acceptedQty, productLotNo: lotNoInput || "", }; const res = await createStockInLine(postData); if (res) { setLotNoInput(""); setDnQtyInput(""); setPolInputList((prev) => ({ ...prev, [row.id]: { lotNo: "", dnQty: "" }, })); setSelectedRow(row); fetchPoDetail(selectedPoId.toString(), true, row.id); } console.log(res); }, 200); }; const exceedOrderBy10Percent = orderQty > 0 && acceptedQty > orderQty * 1.1; if (exceedOrderBy10Percent) { submitDialogWithWarning(doSubmit, t, { title: t("Confirm submit"), html: t("This batch quantity exceeds order quantity. Do you still want to submit?"), confirmButtonText: t("Submit"), }); } else { doSubmit(); } }, [dnQtyInput, row, dnFormProps, selectedPoId, fetchPoDetail, t, lotNoInput], ); const syncRowInputToParent = useCallback((lotNo: string, dnQty: string) => { setPolInputList((prev) => { const current = prev[row.id] ?? { lotNo: "", dnQty: "" }; if (current.lotNo === lotNo && current.dnQty === dnQty) return prev; return { ...prev, [row.id]: { lotNo, dnQty }, }; }); }, [row.id]); // const [focusField, setFocusField] = useState(); // 本批收貨數量(訂單單位): 使用者在該行輸入的 dnQty const batchPurchaseQty = Number(dnQtyInput.trim()) || 0; // 已來貨總數(庫存單位): 同一 POL 底下所有 stock_in_line.acceptedQty 的合計 const totalStockReceived = row.stockInLine .filter((sil) => sil.purchaseOrderLineId === row.id) .reduce((acc, cur) => acc + (cur.acceptedQty ?? 0), 0); const receivedTotalText = decimalFormatter.format(totalStockReceived); const highlightColor = Number(receivedTotalText.replace(/,/g, "")) <= 0 ? "red" : "inherit"; const needsStockInAttention = canSeeStockInReminders && purchaseOrderLineHasIncompleteStockIn(row); return ( <> *": { borderBottom: "unset" }, color: "black", ...(needsStockInAttention ? (theme) => ({ boxShadow: `inset 4px 0 0 ${theme.palette.error.main}`, backgroundColor: alpha(theme.palette.error.main, 0.07), }) : {}), }} onClick={() => changeStockInLines(row.id)} > {/* setOpen(!open)} > {open ? : } */} {needsStockInAttention && ( `0 0 0 1px ${alpha(theme.palette.error.main, 0.45)}`, zIndex: 1, }} /> )} e.stopPropagation()} /> {row.itemNo} {row.itemName} {integerFormatter.format(row.qty)} {integerFormatter.format(row.processed)} {row.uom?.udfudesc} {/* {decimalFormatter.format(row.stockUom.stockQty)} */} {/* {receivedTotal} */} {decimalFormatter.format(totalStockReceived)} {row.stockUom.stockUomDesc} {/* {decimalFormatter.format(totalWeight)} {weightUnit} */} {/* {weightUnit} */} {/* {decimalFormatter.format(row.price)} */} {/* {row.expiryDate} */} {t(`${row.status.toLowerCase()}`)} {/* {t(`${currStatus.toLowerCase()}`)} */} {/* {integerFormatter.format(row.receivedQty)} */} setLotNoInput(e.target.value)} onBlur={() => syncRowInputToParent(lotNoInput, dnQtyInput)} onClick={(e) => e.stopPropagation()} // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} /> setDnQtyInput(e.target.value)} onBlur={() => syncRowInputToParent(lotNoInput, dnQtyInput)} onClick={(e) => e.stopPropagation()} InputProps={{ inputProps: { min: 0, // Optional: set a minimum value step: "any", inputMode: "decimal", } }} /> {/* */} {/* */} {/* */} {/* */} {/* */} {/*
*/} {/*
*/} {/*
*/} {/*
*/} ); } // ROW END const [tabIndex, setTabIndex] = useState(0); const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); const [isOpenScanner, setOpenScanner] = useState(false); // const testing = useCallback(() => { // // setOpenScanner(true); // const newParams = new URLSearchParams(searchParams.toString()); // console.log(pathname) // }, [pathname, router, searchParams]); const onOpenScanner = useCallback(() => { setOpenScanner(true); }, []); const onCloseScanner = useCallback(() => { setOpenScanner(false); }, []); const [itemInfo, setItemInfo] = useState< StockInLine & { warehouseId?: number } >(); const [putAwayOpen, setPutAwayOpen] = useState(false); // const [scannedInfo, setScannedInfo] = useState({} as QrCodeInfo); const closePutAwayModal = useCallback(() => { setPutAwayOpen(false); setItemInfo(undefined); }, []); const openPutAwayModal = useCallback(() => { setPutAwayOpen(true); }, []); const buttonData = useMemo(() => { switch (purchaseOrder.status.toLowerCase()) { case "pending": return { buttonName: "start", title: t("Do you want to start?"), confirmButtonText: t("Start"), successTitle: t("Start Success"), errorTitle: t("Start Fail"), buttonText: t("Start PO"), buttonIcon: , buttonColor: "success", disabled: false, onClick: handleStartPo, }; case "receiving": return { buttonName: "complete", title: t("Do you want to complete?"), confirmButtonText: t("Complete"), successTitle: t("Complete Success"), errorTitle: t("Complete Fail"), buttonText: t("Complete PO"), buttonIcon: , buttonColor: "info", disabled: false, onClick: handleCompletePo, }; default: return { buttonName: "complete", title: t("Do you want to complete?"), confirmButtonText: t("Complete"), successTitle: t("Complete Success"), errorTitle: t("Complete Fail"), buttonText: t("Complete PO"), buttonIcon: , buttonColor: "info", disabled: true, }; // break; } }, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); const FIRST_IN_FIELD = "firstInQty" const SECOND_IN_FIELD = "secondInQty" const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => { switch (field) { case FIRST_IN_FIELD: return true; case SECOND_IN_FIELD: return true; default: return false; // Default case } }, []); const handleDatePickerChange = useCallback((value: Dayjs | null, onChange: (...event: any[]) => void) => { if (value != null) { const updatedValue = dayjsToDateString(value) onChange(updatedValue) } else { onChange(value) } }, []) const fillTodayLotNo = useCallback(() => { const today = dayjs().format("YYYYMMDD"); setPolInputList((prev) => { const next: Record = { ...prev }; (rows ?? []).forEach((r) => { const current = next[r.id] ?? { lotNo: "", dnQty: "" }; const lotNo = (current.lotNo ?? "").trim(); if (!lotNo) { next[r.id] = { ...current, lotNo: today }; } }); return next; }); }, [rows]); return ( <> {/* Area1: title */} {purchaseOrder.code} -{" "} {t(`${purchaseOrder.status.toLowerCase()}`)} {/* area2: dn info */} {/* left side select po */} {/* right side po info */} ( { handleDatePickerChange(newValue, field.onChange); }} slotProps={{ textField: { fullWidth: true } }} /> )} /> 列印 setSelectedPrinter(value)} renderInput={(params) => ( )} /> { const cleaned = String(event.target.value).replace(/[^0-9]/g, ""); setPrintQty(Number(cleaned || 0)); }} fullWidth /> 只會顯示「待上架 / 已上架」的來貨記錄 列印 {/* Area4: Main Table */} {t("itemNo")} {t("itemName")} {t("qty")} {t("processedQty")} {t("uom")} {t("receivedTotal")} {t("Stock UoM")} {/* {t("total weight")} */} {/* {`${t("price")} (HKD)`} */} {t("status")} {/* {renderFieldCondition(FIRST_IN_FIELD) ? {t("receivedQty")} : undefined} */} {t("productLotNo")} {renderFieldCondition(SECOND_IN_FIELD) ? {t("dnQty")}
(以訂單單位計算)
: undefined}
{rows.map((row) => ( ))}
{/* area5: selected item info */} {selectedRow ? `已選擇貨品: ${selectedRow?.itemNo ? selectedRow.itemNo : 'N/A'} - ${selectedRow?.itemName ? selectedRow?.itemName : 'N/A'}` : "未選擇貨品"} {selectedRow && (
)}
{/* tab 2 */} {/* */}
列印標籤 setPrintStatusFilter((p) => ({ ...p, received: e.target.checked })) } /> } label="待上架" /> setPrintStatusFilter((p) => ({ ...p, completed: e.target.checked })) } /> } label="已上架" /> 0 && selectedPrintSilIds.size === eligiblePrintSils.length } indeterminate={ selectedPrintSilIds.size > 0 && selectedPrintSilIds.size < eligiblePrintSils.length } onChange={(e) => setAllVisiblePrintSilsSelected(e.target.checked)} /> } label="全選(目前篩選結果)" /> 已選擇 {selectedPrintSilIds.size} / {eligiblePrintSils.length} 貨品編號 貨品名稱 換算庫存數量 庫存單位 收貨日期 來貨批號 來貨狀態 {eligiblePrintSils.map((sil) => { const status = (sil.status ?? "").toLowerCase().trim(); const statusText = status === "received" ? "待上架" : status === "completed" ? "已上架" : sil.status; const receiptText = sil.receiptDate ? Array.isArray(sil.receiptDate) ? arrayToDateString(sil.receiptDate) : String(sil.receiptDate) : "-"; const stockQty = Number(sil.acceptedQty ?? 0); const stockQtyText = Number.isFinite(stockQty) && stockQty > 0 ? decimalFormatter.format(stockQty) : decimalFormatter.format(0); return ( togglePrintSilSelection(sil.id, e.target.checked)} /> {sil.itemNo} {sil.itemName} {stockQtyText} {sil.stockUomDesc || "-"} {receiptText} {sil.productLotNo || "-"} {statusText} ); })} {eligiblePrintSils.length === 0 && ( 沒有符合條件的項目 )}
{/* {itemInfo !== undefined && ( <> )} */} ); }; export default PoDetail;