| @@ -109,6 +109,8 @@ export interface StockInLine { | |||
| itemType: string; | |||
| demandQty: number; | |||
| acceptedQty: number; | |||
| purchaseDemandQty?: number; | |||
| purchaseAcceptedQty?: number; | |||
| qty?: number; | |||
| receivedQty?: number; | |||
| processed?: number; | |||
| @@ -124,6 +126,8 @@ export interface StockInLine { | |||
| lotNo?: string; | |||
| poCode?: string; | |||
| uom?: Uom; | |||
| purchaseUomDesc?: string; | |||
| stockUomDesc?: string; | |||
| joCode?: string; | |||
| warehouseCode?: string; | |||
| defaultWarehouseId: number; // id for now | |||
| @@ -150,6 +154,7 @@ export interface EscalationInput { | |||
| export interface PutAwayLine { | |||
| id?: number | |||
| qty: number | |||
| stockQty?: number | |||
| warehouseId: number; | |||
| warehouse: string; | |||
| printQty: number; | |||
| @@ -451,7 +451,10 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| const oldId = row.id; | |||
| const acceptedQty = Number(polInputList[rowIndex].dnQty); | |||
| if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update | |||
| if (!Number.isInteger(acceptedQty)) { | |||
| alert("來貨數量必須是整數(不能有小數)!"); | |||
| return; | |||
| } | |||
| const postData = { | |||
| dnNo: dnFormProps.watch("dnNo"), | |||
| receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), | |||
| @@ -491,22 +494,30 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| break; | |||
| case "dnQty": | |||
| // Allow empty input | |||
| if (raw.trim() === '') { | |||
| polInputList[rowIndex].dnQty = 0; | |||
| break; | |||
| } | |||
| const trimmed = raw.trim(); | |||
| if (trimmed === "") { | |||
| polInputList[rowIndex].dnQty = 0; | |||
| break; | |||
| } | |||
| /* | |||
| // Keep digits only | |||
| const cleaned = raw.replace(/[^\d]/g, ''); | |||
| if (cleaned === '') { | |||
| // If the user typed only non-digits, keep previous value | |||
| break; | |||
| } | |||
| */ | |||
| const parsed = Number(trimmed); | |||
| // Parse and clamp to non-negative integer | |||
| const next = Math.max(0, Math.floor(Number(cleaned))); | |||
| polInputList[rowIndex].dnQty = next; | |||
| // 不是合法數字(例如 "abc") | |||
| if (!Number.isFinite(parsed)) { | |||
| polInputList[rowIndex].dnQty = 0; // 或保留舊值,看你需求 | |||
| break; | |||
| } | |||
| polInputList[rowIndex].dnQty = parsed; // 這裡允許小數,先存起來 | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -515,9 +526,16 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| // const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||
| const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) | |||
| const receivedTotal = decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio); | |||
| const highlightColor = (Number(receivedTotal.replace(/,/g, '')) <= 0) ? "red" : "inherit"; | |||
| // 本批收貨數量(訂單單位): 使用者在該行輸入的 dnQty | |||
| const batchPurchaseQty = polInputList[rowIndex]?.dnQty ?? 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"; | |||
| return ( | |||
| <> | |||
| @@ -552,7 +570,10 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||
| <TableCell align="right">{integerFormatter.format(row.processed)}</TableCell> | |||
| <TableCell align="left">{row.uom?.udfudesc}</TableCell> | |||
| {/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> */} | |||
| <TableCell sx={{ color: highlightColor}} align="right">{receivedTotal}</TableCell> | |||
| {/* <TableCell sx={{ color: highlightColor}} align="right">{receivedTotal}</TableCell> */} | |||
| <TableCell sx={{ color: highlightColor }} align="right"> | |||
| {decimalFormatter.format(totalStockReceived)} | |||
| </TableCell> | |||
| <TableCell sx={{ color: highlightColor}} align="left">{row.stockUom.stockUomDesc}</TableCell> | |||
| {/* <TableCell align="right"> | |||
| {decimalFormatter.format(totalWeight)} {weightUnit} | |||
| @@ -524,7 +524,8 @@ function PoInputGrid({ | |||
| // editable: true, | |||
| // replace with tooltip + content | |||
| renderCell: (params) => { | |||
| return integerFormatter.format(params.value) | |||
| const qty = params.row.purchaseAcceptedQty ?? params.row.acceptedQty ?? 0; | |||
| return integerFormatter.format(qty); | |||
| } | |||
| }, | |||
| { | |||
| @@ -545,9 +546,9 @@ function PoInputGrid({ | |||
| // editable: true, | |||
| // replace with tooltip + content | |||
| renderCell: (params) => { | |||
| const baseQty = (params.row.acceptedQty ?? 0) * (itemDetail.stockUom.purchaseRatioN ?? 1) / (itemDetail.stockUom.purchaseRatioD ?? 1) | |||
| const stockQty = baseQty * (itemDetail.stockUom.stockRatioD ?? 1) / (itemDetail.stockUom.stockRatioN ?? 1) | |||
| return decimalFormatter.format(stockQty) | |||
| // acceptedQty 現在就是庫存單位數量 | |||
| const stockQty = params.row.acceptedQty ?? 0; | |||
| return decimalFormatter.format(stockQty); | |||
| } | |||
| }, | |||
| { | |||
| @@ -198,7 +198,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, | |||
| receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") | |||
| : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||
| acceptQty: d.demandQty?? d.acceptedQty, | |||
| acceptQty: d.acceptedQty ?? d.demandQty, | |||
| // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], | |||
| // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], | |||
| warehouseId: d.defaultWarehouseId ?? 1, | |||
| @@ -451,7 +451,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| async (data, event) => { | |||
| // Extract only putaway related fields | |||
| const putawayData = { | |||
| acceptQty: Number(data.acceptQty?? (stockInLineInfo?.demandQty?? (stockInLineInfo?.acceptedQty))), //TODO improve | |||
| acceptQty: Number(data.acceptQty ?? (stockInLineInfo?.acceptedQty ?? stockInLineInfo?.demandQty)), //TODO improve | |||
| warehouseId: data.warehouseId, | |||
| status: data.status, //TODO Fix it! | |||
| // ...data, | |||
| @@ -251,7 +251,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| formProps.reset({ | |||
| ...defaultNewValue | |||
| }) | |||
| const total = itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; | |||
| const total = itemDetail.putAwayLines?.reduce((sum, p) => sum + (p.stockQty ?? p.qty ?? 0), 0) ?? 0; | |||
| setPutQty(itemDetail?.acceptedQty - total); | |||
| // ✅ Get first warehouse from existing put away lines | |||
| @@ -294,7 +294,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| const res = await fetchStockInLineInfo(stockInLineId); | |||
| console.log("%c Fetched Stock In Line Info:", "color:gold", res); | |||
| const total = res.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; | |||
| const total = res.putAwayLines?.reduce((sum, p) => sum + (p.stockQty ?? p.qty ?? 0), 0) ?? 0; | |||
| setTotalPutAwayQty(total); | |||
| setItemDetail(res); | |||
| } catch (e) { | |||
| @@ -346,6 +346,10 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| // qty: acceptQty; | |||
| // } | |||
| try { | |||
| if (!itemDetail?.id || itemDetail.acceptedQty === undefined || itemDetail.acceptedQty === null) { | |||
| alert("找不到收貨數量(acceptedQty),請重新掃碼再試。"); | |||
| return; | |||
| } | |||
| // 确定最终使用的 warehouseId | |||
| const effectiveWarehouseId = warehouseId > 0 | |||
| ? warehouseId | |||
| @@ -361,8 +365,8 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||
| purchaseOrderId: itemDetail?.purchaseOrderId, | |||
| purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | |||
| itemId: itemDetail?.itemId, | |||
| acceptedQty: itemDetail?.acceptedQty, | |||
| acceptQty: itemDetail?.acceptedQty, | |||
| acceptedQty: Number(itemDetail.acceptedQty), | |||
| acceptQty: Number(itemDetail.acceptedQty), | |||
| status: "received", | |||
| // purchaseOrderId: parseInt(params.get("id")!), | |||
| // purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | |||
| @@ -370,47 +370,38 @@ const StockInForm: React.FC<Props> = ({ | |||
| /> | |||
| </Grid></> | |||
| )} | |||
| {putawayMode && ( | |||
| {putawayMode ? ( | |||
| <> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={"本批次庫存收貨數量"} | |||
| fullWidth | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| value={`${itemDetail.acceptedQty ?? 0} (${itemDetail.stockUomDesc ?? ""})`} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={"本批次採購收貨數量"} | |||
| fullWidth | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| value={`${itemDetail.purchaseAcceptedQty ?? 0} (${itemDetail.purchaseUomDesc ?? ""})`} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={"已上架數量"} | |||
| fullWidth | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| value={`${itemDetail.putAwayLines?.reduce((sum, p) => sum + (p.stockQty ?? 0), 0) ?? 0} (${itemDetail.stockUomDesc ?? ""})`} | |||
| /> | |||
| </Grid> | |||
| </> | |||
| ) : ( | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| value={itemDetail.acceptedQty} | |||
| // disabled={true} | |||
| // disabled={disabled} | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| )} | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("uom")} | |||
| fullWidth | |||
| {...register("uom.udfudesc", { | |||
| required: "uom required!", | |||
| })} | |||
| // value={uom?.code} | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| {putawayMode ? ( | |||
| <TextField | |||
| label={t("processedQty")} | |||
| fullWidth | |||
| sx={compactFields ? undefined : textfieldSx} | |||
| disabled={true} | |||
| value={itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0} | |||
| // disabled={true} | |||
| // disabled={disabled} | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| ) : ( | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| @@ -419,13 +410,9 @@ const StockInForm: React.FC<Props> = ({ | |||
| {...register("acceptedQty", { | |||
| required: "acceptedQty required!", | |||
| })} | |||
| // disabled={true} | |||
| // disabled={disabled} | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| /> | |||
| )} | |||
| </Grid> | |||
| </Grid> | |||
| )} | |||
| {/* <Grid item xs={4}> | |||
| <TextField | |||
| label={t("acceptedWeight")} | |||