| @@ -210,8 +210,8 @@ export default function ProductionSchedulePage() { | |||
| } | |||
| }; | |||
| const fromDateDefault = dayjs().subtract(29, "day").format("YYYY-MM-DD"); | |||
| const toDateDefault = dayjs().format("YYYY-MM-DD"); | |||
| const fromDateDefault = dayjs().subtract(6, "day").format("YYYY-MM-DD"); | |||
| const toDateDefault = dayjs().add(1, "day").format("YYYY-MM-DD"); | |||
| const fetchItemDailyOut = async (force: boolean = false) => { | |||
| // Avoid starting a new fetch while an import is in progress, | |||
| @@ -416,14 +416,17 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| }, []); | |||
| useEffect(() => { | |||
| if (processedQty === row.qty) { | |||
| // `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]); | |||
| }, [processedQty, row.qty, row.stockUom?.stockQty]); | |||
| const handleRowSelect = () => { | |||
| // setSelectedRowId(row.id); | |||
| @@ -153,7 +153,8 @@ function PoInputGrid({ | |||
| const [btnIsLoading, setBtnIsLoading] = useState(false); | |||
| const [currQty, setCurrQty] = useState(() => { | |||
| const total = entries.reduce( | |||
| (acc, curr) => acc + (curr.acceptedQty || 0), | |||
| // remaining qty (M18 unit) | |||
| (acc, curr) => acc + (curr.purchaseAcceptedQty || 0), | |||
| 0, | |||
| ); | |||
| return total; | |||
| @@ -231,7 +232,8 @@ function PoInputGrid({ | |||
| itemName: params.row.itemName, | |||
| // purchaseOrderId: params.row.purchaseOrderId, | |||
| purchaseOrderLineId: params.row.purchaseOrderLineId, | |||
| acceptedQty: params.row.acceptedQty, | |||
| // For PO-origin, backend expects M18 qty and converts it to stock qty. | |||
| acceptedQty: params.row.purchaseAcceptedQty ?? params.row.acceptedQty, | |||
| }; | |||
| const res = await createStockInLine(postData); | |||
| console.log(res); | |||
| @@ -516,7 +518,7 @@ function PoInputGrid({ | |||
| // // flex: 0.6, | |||
| // }, | |||
| { | |||
| field: "acceptedQty", | |||
| field: "purchaseAcceptedQty", | |||
| headerName: t("acceptedQty"), | |||
| // flex: 0.5, | |||
| width: 125, | |||
| @@ -524,7 +526,7 @@ function PoInputGrid({ | |||
| // editable: true, | |||
| // replace with tooltip + content | |||
| renderCell: (params) => { | |||
| const qty = params.row.purchaseAcceptedQty ?? params.row.acceptedQty ?? 0; | |||
| const qty = params.row.purchaseAcceptedQty ?? 0; | |||
| return integerFormatter.format(qty); | |||
| } | |||
| }, | |||
| @@ -818,7 +820,8 @@ function PoInputGrid({ | |||
| purchaseOrderLineId: itemDetail.id, | |||
| itemNo: itemDetail.itemNo, | |||
| itemName: itemDetail.itemName, | |||
| acceptedQty: itemDetail.qty - currQty, // this bug | |||
| // User inputs qty in M18 unit; backend will convert to stock unit on create. | |||
| purchaseAcceptedQty: itemDetail.qty - currQty, | |||
| uom: itemDetail.uom, | |||
| status: "draft", | |||
| }; | |||
| @@ -840,8 +843,13 @@ function PoInputGrid({ | |||
| const error: StockInLineEntryError = {}; | |||
| console.log(newRow); | |||
| console.log(currQty); | |||
| if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) { | |||
| error["acceptedQty"] = t("qty cannot be greater than remaining qty"); | |||
| if ( | |||
| newRow.purchaseAcceptedQty && | |||
| newRow.purchaseAcceptedQty > itemDetail.qty | |||
| ) { | |||
| error["purchaseAcceptedQty"] = t( | |||
| "qty cannot be greater than remaining qty", | |||
| ); | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| @@ -872,7 +880,7 @@ function PoInputGrid({ | |||
| setEntries(newEntries); | |||
| //update remaining qty | |||
| const total = newEntries.reduce( | |||
| (acc, curr) => acc + (curr.acceptedQty || 0), | |||
| (acc, curr) => acc + (curr.purchaseAcceptedQty || 0), | |||
| 0, | |||
| ); | |||
| setCurrQty(total); | |||
| @@ -395,7 +395,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg | |||
| <TextField | |||
| label={t("acceptedPutawayQty")} // TODO: fix it back to acceptedQty after db is fixed | |||
| fullWidth | |||
| value={itemDetail.acceptedQty ?? itemDetail.demandQty} | |||
| value={itemDetail.qty ?? itemDetail.purchaseAcceptedQty ?? itemDetail.acceptedQty ?? itemDetail.demandQty} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| @@ -403,7 +403,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg | |||
| <TextField | |||
| label={t("uom")} | |||
| fullWidth | |||
| value={itemDetail.uom?.udfudesc} | |||
| value={itemDetail.purchaseUomDesc ?? itemDetail.uom?.udfudesc} | |||
| disabled | |||
| /> | |||
| </Grid> | |||
| @@ -13,7 +13,7 @@ import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import NotificationIcon from "@mui/icons-material/NotificationImportant"; | |||
| import { useSession } from "next-auth/react"; | |||
| import { defaultPagingController } from "../SearchResults/SearchResults"; | |||
| import { fetchPoListClient, testing } from "@/app/api/po/actions"; | |||
| import { testing } from "@/app/api/po/actions"; | |||
| import dayjs from "dayjs"; | |||
| import { arrayToDateString, dayjsToDateString } from "@/app/utils/formatUtil"; | |||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||
| @@ -289,7 +289,20 @@ const PoSearch: React.FC<Props> = ({ | |||
| }; | |||
| setAutoSyncStatus(null); | |||
| const res = await fetchPoListClient(params); | |||
| const cleanedQuery: Record<string, string> = {}; | |||
| Object.entries(params).forEach(([k, v]) => { | |||
| if (v === undefined || v === null) return; | |||
| if (typeof v === "string" && (v as string).trim() === "") return; | |||
| cleanedQuery[k] = String(v); | |||
| }); | |||
| const baseListResp = await clientAuthFetch( | |||
| `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(cleanedQuery).toString()}`, | |||
| { method: "GET" }, | |||
| ); | |||
| if (!baseListResp.ok) { | |||
| throw new Error(`PO list fetch failed: ${baseListResp.status}`); | |||
| } | |||
| const res = await baseListResp.json(); | |||
| if (!res) return; | |||
| if (res.records && res.records.length > 0) { | |||
| @@ -340,14 +353,6 @@ const PoSearch: React.FC<Props> = ({ | |||
| if (syncOk) { | |||
| setAutoSyncStatus("成功找到PO"); | |||
| // Re-fetch /po/list directly from client to avoid cached server action results. | |||
| const cleanedQuery: Record<string, string> = {}; | |||
| Object.entries(params).forEach(([k, v]) => { | |||
| if (v === undefined || v === null) return; | |||
| if (typeof v === "string" && v.trim() === "") return; | |||
| cleanedQuery[k] = String(v); | |||
| }); | |||
| const listResp = await clientAuthFetch( | |||
| `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams( | |||
| cleanedQuery, | |||