| @@ -109,6 +109,8 @@ export interface StockInLine { | |||||
| itemType: string; | itemType: string; | ||||
| demandQty: number; | demandQty: number; | ||||
| acceptedQty: number; | acceptedQty: number; | ||||
| purchaseDemandQty?: number; | |||||
| purchaseAcceptedQty?: number; | |||||
| qty?: number; | qty?: number; | ||||
| receivedQty?: number; | receivedQty?: number; | ||||
| processed?: number; | processed?: number; | ||||
| @@ -124,6 +126,8 @@ export interface StockInLine { | |||||
| lotNo?: string; | lotNo?: string; | ||||
| poCode?: string; | poCode?: string; | ||||
| uom?: Uom; | uom?: Uom; | ||||
| purchaseUomDesc?: string; | |||||
| stockUomDesc?: string; | |||||
| joCode?: string; | joCode?: string; | ||||
| warehouseCode?: string; | warehouseCode?: string; | ||||
| defaultWarehouseId: number; // id for now | defaultWarehouseId: number; // id for now | ||||
| @@ -150,6 +154,7 @@ export interface EscalationInput { | |||||
| export interface PutAwayLine { | export interface PutAwayLine { | ||||
| id?: number | id?: number | ||||
| qty: number | qty: number | ||||
| stockQty?: number | |||||
| warehouseId: number; | warehouseId: number; | ||||
| warehouse: string; | warehouse: string; | ||||
| printQty: number; | printQty: number; | ||||
| @@ -451,7 +451,10 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||||
| const oldId = row.id; | const oldId = row.id; | ||||
| const acceptedQty = Number(polInputList[rowIndex].dnQty); | const acceptedQty = Number(polInputList[rowIndex].dnQty); | ||||
| if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update | if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update | ||||
| if (!Number.isInteger(acceptedQty)) { | |||||
| alert("來貨數量必須是整數(不能有小數)!"); | |||||
| return; | |||||
| } | |||||
| const postData = { | const postData = { | ||||
| dnNo: dnFormProps.watch("dnNo"), | dnNo: dnFormProps.watch("dnNo"), | ||||
| receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), | receiptDate: outputDateStringToInputDateString(dnFormProps.watch("receiptDate")), | ||||
| @@ -491,22 +494,30 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||||
| break; | break; | ||||
| case "dnQty": | case "dnQty": | ||||
| // Allow empty input | // 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 | // Keep digits only | ||||
| const cleaned = raw.replace(/[^\d]/g, ''); | const cleaned = raw.replace(/[^\d]/g, ''); | ||||
| if (cleaned === '') { | if (cleaned === '') { | ||||
| // If the user typed only non-digits, keep previous value | // If the user typed only non-digits, keep previous value | ||||
| break; | 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; | break; | ||||
| } | |||||
| polInputList[rowIndex].dnQty = parsed; // 這裡允許小數,先存起來 | |||||
| break; | |||||
| default: | default: | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -515,9 +526,16 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||||
| // const [focusField, setFocusField] = useState<HTMLInputElement>(); | // 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 ( | return ( | ||||
| <> | <> | ||||
| @@ -552,7 +570,10 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => { | |||||
| <TableCell align="right">{integerFormatter.format(row.processed)}</TableCell> | <TableCell align="right">{integerFormatter.format(row.processed)}</TableCell> | ||||
| <TableCell align="left">{row.uom?.udfudesc}</TableCell> | <TableCell align="left">{row.uom?.udfudesc}</TableCell> | ||||
| {/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</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 sx={{ color: highlightColor}} align="left">{row.stockUom.stockUomDesc}</TableCell> | ||||
| {/* <TableCell align="right"> | {/* <TableCell align="right"> | ||||
| {decimalFormatter.format(totalWeight)} {weightUnit} | {decimalFormatter.format(totalWeight)} {weightUnit} | ||||
| @@ -524,7 +524,8 @@ function PoInputGrid({ | |||||
| // editable: true, | // editable: true, | ||||
| // replace with tooltip + content | // replace with tooltip + content | ||||
| renderCell: (params) => { | 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, | // editable: true, | ||||
| // replace with tooltip + content | // replace with tooltip + content | ||||
| renderCell: (params) => { | 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, | expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined, | ||||
| receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") | receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input") | ||||
| : dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | : 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 : [], | // escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [], | ||||
| // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], | // qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData], | ||||
| warehouseId: d.defaultWarehouseId ?? 1, | warehouseId: d.defaultWarehouseId ?? 1, | ||||
| @@ -451,7 +451,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| async (data, event) => { | async (data, event) => { | ||||
| // Extract only putaway related fields | // Extract only putaway related fields | ||||
| const putawayData = { | 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, | warehouseId: data.warehouseId, | ||||
| status: data.status, //TODO Fix it! | status: data.status, //TODO Fix it! | ||||
| // ...data, | // ...data, | ||||
| @@ -251,7 +251,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| formProps.reset({ | formProps.reset({ | ||||
| ...defaultNewValue | ...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); | setPutQty(itemDetail?.acceptedQty - total); | ||||
| // ✅ Get first warehouse from existing put away lines | // ✅ 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); | const res = await fetchStockInLineInfo(stockInLineId); | ||||
| console.log("%c Fetched Stock In Line Info:", "color:gold", res); | 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); | setTotalPutAwayQty(total); | ||||
| setItemDetail(res); | setItemDetail(res); | ||||
| } catch (e) { | } catch (e) { | ||||
| @@ -346,6 +346,10 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| // qty: acceptQty; | // qty: acceptQty; | ||||
| // } | // } | ||||
| try { | try { | ||||
| if (!itemDetail?.id || itemDetail.acceptedQty === undefined || itemDetail.acceptedQty === null) { | |||||
| alert("找不到收貨數量(acceptedQty),請重新掃碼再試。"); | |||||
| return; | |||||
| } | |||||
| // 确定最终使用的 warehouseId | // 确定最终使用的 warehouseId | ||||
| const effectiveWarehouseId = warehouseId > 0 | const effectiveWarehouseId = warehouseId > 0 | ||||
| ? warehouseId | ? warehouseId | ||||
| @@ -361,8 +365,8 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| purchaseOrderId: itemDetail?.purchaseOrderId, | purchaseOrderId: itemDetail?.purchaseOrderId, | ||||
| purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | ||||
| itemId: itemDetail?.itemId, | itemId: itemDetail?.itemId, | ||||
| acceptedQty: itemDetail?.acceptedQty, | |||||
| acceptQty: itemDetail?.acceptedQty, | |||||
| acceptedQty: Number(itemDetail.acceptedQty), | |||||
| acceptQty: Number(itemDetail.acceptedQty), | |||||
| status: "received", | status: "received", | ||||
| // purchaseOrderId: parseInt(params.get("id")!), | // purchaseOrderId: parseInt(params.get("id")!), | ||||
| // purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | // purchaseOrderLineId: itemDetail?.purchaseOrderLineId, | ||||
| @@ -370,47 +370,38 @@ const StockInForm: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| </Grid></> | </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}> | <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 | <TextField | ||||
| label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
| fullWidth | fullWidth | ||||
| @@ -419,13 +410,9 @@ const StockInForm: React.FC<Props> = ({ | |||||
| {...register("acceptedQty", { | {...register("acceptedQty", { | ||||
| required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
| })} | })} | ||||
| // disabled={true} | |||||
| // disabled={disabled} | |||||
| // error={Boolean(errors.acceptedQty)} | |||||
| // helperText={errors.acceptedQty?.message} | |||||
| /> | /> | ||||
| )} | |||||
| </Grid> | |||||
| </Grid> | |||||
| )} | |||||
| {/* <Grid item xs={4}> | {/* <Grid item xs={4}> | ||||
| <TextField | <TextField | ||||
| label={t("acceptedWeight")} | label={t("acceptedWeight")} | ||||