Sfoglia il codice sorgente

update new escalation flow

master
kelvinsuen 5 giorni fa
parent
commit
28a23973c0
8 ha cambiato i file con 293 aggiunte e 195 eliminazioni
  1. +4
    -5
      src/app/api/po/actions.ts
  2. +7
    -0
      src/app/api/po/index.ts
  3. +3
    -2
      src/components/PoDetail/PoInputGrid.tsx
  4. +2
    -1
      src/components/PoDetail/PutAwayForm.tsx
  5. +209
    -145
      src/components/PoDetail/QcComponent.tsx
  6. +50
    -36
      src/components/PoDetail/QcStockInModalVer2.tsx
  7. +12
    -3
      src/components/PoDetail/StockInFormVer2.tsx
  8. +6
    -3
      src/i18n/zh/purchaseOrder.json

+ 4
- 5
src/app/api/po/actions.ts Vedi File

@@ -38,11 +38,10 @@ export interface StockInLineEntry {
}

export interface PurchaseQcResult{
qcItemId: number;
id: number;
qcPassed?: boolean;
failQty?: number;
remarks?: string;
}
export interface StockInInput {
status: string;
@@ -66,9 +65,9 @@ export interface PurchaseQCInput {
status: string;
acceptQty: number;
passingQty: number;
sampleRate: number;
sampleWeight: number;
totalWeight: number;
sampleRate?: number;
sampleWeight?: number;
totalWeight?: number;
qcAccept: boolean;
qcDecision?: number;
qcResult: PurchaseQcResult[];


+ 7
- 0
src/app/api/po/index.ts Vedi File

@@ -8,6 +8,13 @@ import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils";
import { PutAwayLine } from "./actions";

export enum StockInStatus {
PENDING = "pending",
RECEIVED = "received",
APPROVED = "escalated",
REJECTED = "rejected",
COMPLETED = "completed",
}
export interface PoResult {
id: number;
code: string;


+ 3
- 2
src/components/PoDetail/PoInputGrid.tsx Vedi File

@@ -124,7 +124,7 @@ function PoInputGrid({
handleMailTemplateForStockInLine,
printerCombo
}: Props) {
console.log(itemDetail);
const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef();
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
@@ -132,7 +132,7 @@ function PoInputGrid({
(row) => row.id as number,
[],
);
console.log(stockInLine);
const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
useEffect(() => {
setEntries(stockInLine)
@@ -448,6 +448,7 @@ const closeNewModal = useCallback(() => {
btnSx = {label: t("escalation processing"), color:"warning.main"};
break;}
case "rejected":
case "partially_completed":
case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break;
default: btnSx = {label: t("qc processing"), color:"success.main"};
}


+ 2
- 1
src/components/PoDetail/PutAwayForm.tsx Vedi File

@@ -338,7 +338,8 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, printer
);

const addRowDefaultValue = useMemo(() => {
const defaultMaxQty = watch("acceptedQty") - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0)
const defaultMaxQty = Number(itemDetail.demandQty?? itemDetail.acceptedQty)//watch("acceptedQty")
- watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0)
const defaultWarehouseId = itemDetail.defaultWarehouseId ?? 1
const defaultWarehouse = options.find((o) => o.value === defaultWarehouseId)?.label
return {qty: defaultMaxQty, warehouseId: defaultWarehouseId, warehouse: defaultWarehouse, printQty: 1, _isNew: true } as Partial<PutAwayLine>


+ 209
- 145
src/components/PoDetail/QcComponent.tsx Vedi File

@@ -19,7 +19,7 @@ import {
Tooltip,
Typography,
} from "@mui/material";
import { useFormContext, Controller } from "react-hook-form";
import { useFormContext, Controller, FieldPath, useFieldArray } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
@@ -59,8 +59,8 @@ interface Props {
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
qc: QcItemWithChecks[];
disabled: boolean;
qcItems: QcData[]
setQcItems: Dispatch<SetStateAction<QcData[]>>
// qcItems: QcData[]
// setQcItems: Dispatch<SetStateAction<QcData[]>>
}

type EntryError =
@@ -71,7 +71,7 @@ type EntryError =

type QcRow = TableRow<Partial<QcData>, EntryError>;
// fetchQcItemCheck
const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcItems }) => {
const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled = false }) => {
const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef();
const {
@@ -93,8 +93,9 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
// const [qcResult, setQcResult] = useState();
const qcAccept = watch("qcAccept");
const qcDecision = watch("qcDecision"); //WIP
const qcResult = watch("qcResult");
console.log(qcResult);
// const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]);
const qcResult = [...watch("qcResult")];

// const [qcAccept, setQcAccept] = useState(true);
// const [qcItems, setQcItems] = useState(dummyQCData)

@@ -119,32 +120,59 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
[],
);

// W I P //
const validateFieldFail = (field : FieldPath<PurchaseQCInput>, condition: boolean, message: string) : boolean => {
// console.log("Checking if " + message)
if (condition) { setError(field, { message: message}); return false; }
else { clearErrors(field); return true; }
}

//// validate form
const accQty = watch("acceptQty");
const validateForm = useCallback(() => {
if (qcDecision == 1) {
if (accQty > itemDetail.acceptedQty) {
setError("acceptQty", {
message: `${t("acceptQty must not greater than")} ${
itemDetail.acceptedQty
}`,
type: "required",
});
if (accQty > itemDetail.acceptedQty){
setError("acceptQty", { message: `${t("acceptQty must not greater than")} ${
itemDetail.acceptedQty}` });
}
if (accQty < 1) {
setError("acceptQty", {
message: t("minimal value is 1"),
type: "required",
});
if (accQty < 1){
setError("acceptQty", { message: t("minimal value is 1") });
}
if (isNaN(accQty)) {
setError("acceptQty", {
message: t("value must be a number"),
type: "required",
});
if (isNaN(accQty)){
setError("acceptQty", { message: t("value must be a number") });
}
}
}, [accQty, qcDecision]);
},[setError, qcDecision, accQty, itemDetail])

useEffect(() => { // W I P // -----
if (qcDecision == 1) {
if (validateFieldFail("acceptQty", accQty > itemDetail.acceptedQty, `${t("acceptQty must not greater than")} ${
itemDetail.acceptedQty}`)) return;

if (validateFieldFail("acceptQty", accQty < 1, t("minimal value is 1"))) return;
if (validateFieldFail("acceptQty", isNaN(accQty), t("value must be a number"))) return;
}

const qcResultItems = qcResult; console.log("Validating:", qcResultItems);
// Check if failed items have failed quantity
const failedItemsWithoutQty = qcResultItems.filter(item =>
item.qcPassed === false && (!item.failQty || item.failQty <= 0)
);
if (validateFieldFail("qcResult", failedItemsWithoutQty.length > 0, `${t("Failed items must have failed quantity")}`)) return;

// Check if all QC items have results
const itemsWithoutResult = qcResultItems.filter(item => item.qcPassed === undefined);
if (validateFieldFail("qcDecision", (itemsWithoutResult.length > 0 && itemDetail.status != "escalated"),
`${t("QC items without result")}`)) return;

if (validateFieldFail("qcDecision", (!qcResultItems.every((qc) => qc.qcPassed) && qcDecision == 1 && itemDetail.status != "escalated"),
"有不合格檢查項目,無法收貨!")) return; // TODO: Fix it please
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
// confirmButtonText: t("confirm putaway"), html: ""});
// return;

// console.log("Validated without errors");
}, [accQty, qcDecision, watch("qcResult")]);

useEffect(() => {
clearErrors();
@@ -188,39 +216,41 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />;
}

const qcColumns: GridColDef[] = [
const qcColumns: GridColDef[] = useMemo(() => [
{
field: "code",
headerName: t("qcItem"),
flex: 2,
renderCell: (params) => (
renderCell: (params) => {
const index = params.api.getRowIndexRelativeToVisibleRows(params.id) + 1;
return (
<Box>
<b>{`${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}. ${params.value}`}</b><br/>
<b>{`${index}. ${params.value}`}</b><br/>
{params.row.name}<br/>
</Box>
),
)},
},
{
field: 'qcPassed',
field: 'qcResult',
headerName: t("qcResult"),
flex: 1.5,
renderCell: (params) => {
const currentValue = params.row;
const rowValue = params.row;
const index = params.api.getRowIndexRelativeToVisibleRows(params.id);
// console.log(currentValue.row);
// console.log(rowValue.row);
return (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
value={currentValue.qcPassed === undefined ? "" : (currentValue.qcPassed ? "true" : "false")}
// value={currentValue.qcPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.qcPassed ? "true" : "false")}
// defaultValue={""}
value={rowValue.qcPassed === undefined ? "" : (rowValue.qcPassed ? "true" : "false")}
onChange={(e) => {
const value = e.target.value;
setQcItems((prev) =>
prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
);
// setValue(`qcResult.${index}.qcPassed`, value == "true");
const value = (e.target.value === "true");
// setQcItems((prev) =>
// prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
// );
setValue(`qcResult.${index}.qcPassed`, value);
}}
name={`qcPassed-${params.id}`}
>
@@ -230,7 +260,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
label="合格"
disabled={disabled || itemDetail.status == "escalated"}
sx={{
color: currentValue.qcPassed === true ? "green" : "inherit",
color: rowValue.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
@@ -240,7 +270,7 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
label="不合格"
disabled={disabled || itemDetail.status == "escalated"}
sx={{
color: currentValue.qcPassed === false ? "red" : "inherit",
color: rowValue.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
}}
/>
@@ -254,58 +284,68 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
headerName: t("failedQty"),
flex: 1,
// editable: true,
renderCell: (params) => (
<TextField
type="number"
size="small"
value={!params.row.qcPassed? (params.value ?? '') : '0'}
disabled={params.row.qcPassed || disabled || itemDetail.status == "escalated"}
onChange={(e) => {
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))
);
// setValue(`failQty`,failQty);
}}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%' }}
/>
),
renderCell: (params) => {
const index = params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<TextField
type="number"
size="small"
value={!params.row.qcPassed? params.value : '0'}
disabled={params.row.qcPassed || disabled || itemDetail.status == "escalated"}
onBlur={(e) => {
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))
// );
setValue(`qcResult.${index}.failQty`, next);
}}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%' }}
/>
);
},
},
{
field: "remarks",
headerName: t("remarks"),
flex: 2,
renderCell: (params) => (
<TextField
size="small"
value={params.value ?? ''}
disabled={disabled || itemDetail.status == "escalated"}
onChange={(e) => {
const remarks = e.target.value;
// const next = v === '' ? undefined : Number(v);
// if (Number.isNaN(next)) return;
setQcItems((prev) =>
prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
);
}}
// {...register(`qcResult.${params.row.rowIndex}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%' }}
/>
),
renderCell: (params) => {
const index = params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<TextField
size="small"
defaultValue={params.value}
disabled={disabled || itemDetail.status == "escalated"}
onBlur={(e) => {
const value = e.target.value;
setValue(`qcResult.${index}.remarks`, value);
}}
// onChange={(e) => {
// const remarks = e.target.value;
// // const next = v === '' ? undefined : Number(v);
// // if (Number.isNaN(next)) return;
// // setQcItems((prev) =>
// // prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
// // );
// }}
// {...register(`qcResult.${index}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%' }}
/>
);
},
},
]
], [])

// Set initial value for acceptQty
useEffect(() => {
@@ -316,12 +356,23 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
}
}, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]);

useEffect(() => {
// console.log("Qc Result updated:", qcResult);
if (qcResult.length < 1) { // New QC
const mutableQcData = dummyQCData;
// const mutableQcData = JSON.parse(JSON.stringify(dummyQCData));
// replace([mutableQcData]);
setValue("qcResult", mutableQcData);
// setValue("qcResult.0.qcPassed", false);
}
}, [qcResult, setValue])

// const [openCollapse, setOpenCollapse] = useState(false)
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);

const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => {
const isFailed = qcItems.some((qc) => !qc.qcPassed)
console.log(isFailed)
// console.log(isFailed)
if (isFailed) {
setIsCollapsed(true)
} else {
@@ -336,30 +387,29 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc


useEffect(() => {
console.log("ItemDetail in QC:", itemDetail);
console.log("Qc Result in QC:", qcResult);
console.log("%c QC ItemDetail updated:", "color: gold", itemDetail);
}, [itemDetail]);

const setQcDecision = (status : string | undefined) => {
const setDefaultQcDecision = (status : string | undefined) => {
const param = status?.toLowerCase();
if (param !== undefined && param !== null) {
if (param == "completed") {
if (param == "completed" || param == "partially_completed") {
return 1;
} else if (param == "rejected") {
return 2;
} else if (param == "escalated") {
return 3;
return 1; // For new flow
// return 3;
} else { return undefined; }
} else {
return undefined;
}
}


useEffect(() => {
// onFailedOpenCollapse(qcItems) // This function is no longer needed
}, [qcItems]);
// useEffect(() => {
// // onFailedOpenCollapse(qcItems)
// }, [qcItems]);

return (
<>
@@ -405,9 +455,11 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
/> */}
<StyledDataGrid
columns={qcColumns}
rows={qcResult && qcResult.length > 0 ? qcResult : qcItems}
rows={qcResult}
// rows={qcResult && qcResult.length > 0 ? qcResult : qcItems}
// rows={disabled? qcResult:qcItems}
autoHeight
sortModel={[]}
/>
</Grid>
</>
@@ -443,57 +495,69 @@ const QcComponent: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQc
name="qcDecision"
// name="qcAccept"
control={control}
defaultValue={setQcDecision(itemDetail?.status)}
defaultValue={setDefaultQcDecision(itemDetail?.status)}
// defaultValue={true}
render={({ field }) => (
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
{...field}
value={field.value}
// value={field.value?.toString() || "true"}
onChange={(e) => {
const value = e.target.value.toString();// === 'true';
if (value != "1" && Boolean(errors.acceptQty)) {
// if (!value && Boolean(errors.acceptQty)) {
setValue("acceptQty", itemDetail.acceptedQty ?? 0);
}
field.onChange(value);
}}
>
<FormControlLabel disabled={disabled}
value="1" control={<Radio />} label="接受來貨" />
{itemDetail.status == "escalated" && (<Box sx={{mr:2}}>
<TextField
type="number"
label={t("acceptQty")}
sx={{ width: '150px' }}
value={(qcDecision == 1)? accQty : 0 }
// value={qcAccept? accQty : 0 }
disabled={qcDecision != 1 || disabled}
// disabled={!qcAccept || disabled}
{...register("acceptQty", {
required: "acceptQty required!",
})}
error={Boolean(errors.acceptQty)}
helperText={errors.acceptQty?.message}
/>
</Box>)}

{itemDetail.status == "pending" && (<>
<FormControlLabel disabled={disabled}
value="2" control={<Radio />}
sx={{"& .Mui-checked": {color: "red"}}}
label="不接受並退貨" />

<>
{/* <Typography sx={{color:"red"}}>
{errors.qcDecision?.message}
</Typography> */}
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
{...field}
value={field.value}
// value={field.value?.toString() || "true"}
onChange={(e) => {
const value = e.target.value.toString();// === 'true';
if (value != "1" && Boolean(errors.acceptQty)) {
// if (!value && Boolean(errors.acceptQty)) {
setValue("acceptQty", itemDetail.acceptedQty ?? 0);
}
field.onChange(value);
}}
>
<FormControlLabel disabled={disabled}
value="1" control={<Radio />} label="接受來貨" />
<FormControlLabel disabled={disabled}
value="3" control={<Radio />}
sx={{"& .Mui-checked": {color: "blue"}}}
label="上報品檢結果" />
</>)}
</RadioGroup>
{(itemDetail.status == "escalated" || (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve
<Box sx={{mr:2}}>
<TextField
type="number"
label={t("acceptQty")}
sx={{ width: '150px' }}
value={(qcDecision == 1)? Number(accQty) : 0 }
// value={qcAccept? accQty : 0 }
disabled={qcDecision != 1 || disabled}
// disabled={!qcAccept || disabled}
{...register("acceptQty", {
required: "acceptQty required!",
})}
error={Boolean(errors.acceptQty)}
helperText={errors.acceptQty?.message}
/>
<TextField
type="number"
label={t("rejectQty")}
sx={{ width: '150px' }}
value={itemDetail.acceptedQty - accQty}
disabled={true}
/>
</Box>)}

<FormControlLabel disabled={disabled}
value="2" control={<Radio />}
sx={{"& .Mui-checked": {color: "red"}}}
label= {itemDetail.status == "escalated" ? "全部拒絕並退貨" : "不接受並退貨"} />
{(itemDetail.status == "pending" || disabled) && (<>
<FormControlLabel disabled={disabled}
value="3" control={<Radio />}
sx={{"& .Mui-checked": {color: "blue"}}}
label="上報品檢結果" />
</>)}
</RadioGroup>
</>
)}
/>
</FormControl>


+ 50
- 36
src/components/PoDetail/QcStockInModalVer2.tsx Vedi File

@@ -30,7 +30,6 @@ import dayjs from "dayjs";
import { fetchPoQrcode } from "@/app/api/pdf/actions";
import { downloadFile } from "@/app/utils/commonUtil";
import { PrinterCombo } from "@/app/api/settings/printer";
import { watch } from "fs";
import { EscalationResult } from "@/app/api/escalation";
import { SessionWithTokens } from "@/config/authConfig";

@@ -93,7 +92,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
} = useTranslation("purchaseOrder");

// Select Printer
const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0])
const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);

const defaultNewValue = useMemo(() => {
return (
@@ -104,7 +103,7 @@ const defaultNewValue = useMemo(() => {
// putAwayLines: dummyPutAwayLine,
// putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [],
putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [],
qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData],
// qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData],
escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [],
receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT),
acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty,
@@ -135,27 +134,17 @@ const [qcItems, setQcItems] = useState(dummyQCData)

} else return false;
};

useEffect(() => {
formProps.reset({
...itemDetail,
dnDate: dayjsToInputDateString(dayjs()),
// putAwayLines: dummyPutAwayLine,
putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [],
})
setOpenPutaway(isPutaway);
}, [open, itemDetail])
const [viewOnly, setViewOnly] = useState(false);
useEffect(() => {
if (itemDetail && itemDetail.status) {
const isViewOnly = itemDetail.status.toLowerCase() == "completed"
|| itemDetail.status.toLowerCase() == "partially_completed" // TODO update DB
|| itemDetail.status.toLowerCase() == "rejected"
|| (itemDetail.status.toLowerCase() == "escalated" && session?.id != itemDetail.handlerId)
setViewOnly(isViewOnly)
}
console.log("ITEM", itemDetail);
console.log("Modal ItemDetail updated:", itemDetail);
}, [itemDetail]);

useEffect(() => {
@@ -170,7 +159,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
setQcItems(dummyQCData);
setOpenPutaway(isPutaway);
}, [open, defaultNewValue])
}, [open])

const [openPutaway, setOpenPutaway] = useState(false);
const onOpenPutaway = useCallback(() => {
@@ -216,12 +205,17 @@ const [qcItems, setQcItems] = useState(dummyQCData)
async (data, event) => {
console.log("QC Submission:", event!.nativeEvent);
// TODO: Move validation into QC page

// if (errors.length > 0) {
// alert(`未完成品檢: ${errors.map((err) => err[1].message)}`);
// return;
// }

// Get QC data from the shared form context
const qcAccept = data.qcDecision == 1;
// const qcAccept = data.qcAccept;
const acceptQty = data.acceptQty as number;
const qcResults = qcItems; // qcItems;
const acceptQty = Number(data.acceptQty);
const qcResults = data.qcResult || dummyQCData; // qcItems;
// const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems;
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
@@ -239,9 +233,10 @@ const [qcItems, setQcItems] = useState(dummyQCData)
}

// Check if QC accept decision is made
if (data.qcDecision === undefined) {
// if (qcAccept === undefined) {
// validationErrors.push("QC accept/reject decision is required");
// }
validationErrors.push(t("QC decision is required"));
}

// Check if accept quantity is valid
if (acceptQty === undefined || acceptQty <= 0) {
@@ -250,10 +245,12 @@ const [qcItems, setQcItems] = useState(dummyQCData)

// Check if dates are input
if (data.productionDate === undefined || data.productionDate == null) {
validationErrors.push("請輸入生產日期!");
alert("請輸入生產日期!");
return;
}
if (data.expiryDate === undefined || data.expiryDate == null) {
validationErrors.push("請輸入到期日!");
alert("請輸入到期日!");
return;
}
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && itemDetail.status != "escalated") { //TODO: fix it please!
validationErrors.push("有不合格檢查項目,無法收貨!");
@@ -265,10 +262,10 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// Check if all QC items have results
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined);
// if (itemsWithoutResult.length > 0 && itemDetail.status != "escalated") { //TODO: fix it please!
// validationErrors.push(`${t("QC items without result")}`);
// // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
// }
if (itemsWithoutResult.length > 0 && itemDetail.status != "escalated") { //TODO: fix it please!
validationErrors.push(`${t("QC items without result")}`);
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
}

if (validationErrors.length > 0) {
console.error("QC Validation failed:", validationErrors);
@@ -286,7 +283,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
qcAccept: qcAccept? qcAccept : false,
acceptQty: acceptQty? acceptQty : 0,
qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({
// id: item.id,
id: item.id,
qcItemId: item.id,
// code: item.code,
// qcDescription: item.qcDescription,
@@ -307,7 +304,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
recordDate : dayjsToInputDateString(dayjs()),
handlerId : Number(session?.id),
}
console.log("ESCALATION RESULT", escalationLog);
console.log("Escalation Data for submission", escalationLog);
await postStockInLine({...qcData, escalationLog});
} else {
await postStockInLine(qcData);
@@ -323,7 +320,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
return ;

},
[onOpenPutaway, qcItems],
[onOpenPutaway, qcItems, formProps.formState.errors],
);

const postStockInLine = useCallback(async (args: ModalFormInput) => {
@@ -371,8 +368,10 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// binLocation: data.binLocation,
// putawayQuantity: data.putawayQuantity,
// putawayNotes: data.putawayNotes,
acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty,
...data,
acceptQty: Number(data.acceptQty?? (itemDetail.demandQty?? (itemDetail.acceptedQty))), //TODO improve
warehouseId: data.warehouseId,
status: data.status, //TODO Fix it!
// ...data,
dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()),
productionDate : arrayToInputDateString(data.productionDate),
@@ -386,6 +385,21 @@ const [qcItems, setQcItems] = useState(dummyQCData)
} as ModalFormInput;
console.log("Putaway Data:", putawayData);

console.log("DEBUG",data.putAwayLines);
if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) {
alert("請新增上架資料!");
return;
}
console.log(typeof data.putAwayLines!![0].qty + " = 'number'");
console.log(typeof data.putAwayLines!![0].qty !== "number");
if (data.putAwayLines!!.filter((line) => /[^0-9]/.test(String(line.qty))).length > 0) { //TODO Improve
alert("上架數量不正確!");
return;
}
if (data.putAwayLines!!.reduce((acc, cur) => acc + Number(cur.qty), 0) > putawayData.acceptQty!!) {
alert(`上架數量不能大於 ${putawayData.acceptQty}!`);
return;
}
// Handle putaway submission logic here
const res = await postStockInLine(putawayData);
console.log("result ", res);
@@ -438,7 +452,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)

useEffect(() => {
// maybe check if submitted before
console.log(qcItems)
console.log("Modal QC Items updated:", qcItems);
// checkQcIsPassed(qcItems)
}, [qcItems, checkQcIsPassed])
@@ -491,7 +505,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
color="primary"
sx={{ mt: 1 }}
onClick={handlePrint}
disabled={isPrinting}
disabled={isPrinting || printerCombo.length <= 0}
>
{isPrinting ? t("Printing") : t("print")}
</Button>
@@ -543,8 +557,8 @@ const [qcItems, setQcItems] = useState(dummyQCData)
qc={qc!}
itemDetail={itemDetail}
disabled={viewOnly}
qcItems={qcItems}
setQcItems={setQcItems}
// qcItems={qcItems}
// setQcItems={setQcItems}
/>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>


+ 12
- 3
src/components/PoDetail/StockInFormVer2.tsx Vedi File

@@ -92,10 +92,19 @@ const StockInFormVer2: React.FC<Props> = ({
const expiryDate = watch("expiryDate");
const uom = watch("uom");

//// TODO : Add Checking ////
// Check if dates are input
// if (data.productionDate === undefined || data.productionDate == null) {
// validationErrors.push("請輸入生產日期!");
// }
// if (data.expiryDate === undefined || data.expiryDate == null) {
// validationErrors.push("請輸入到期日!");
// }

useEffect(() => {
console.log(uom);
console.log(productionDate);
console.log(expiryDate);
// console.log(uom);
// console.log(productionDate);
// console.log(expiryDate);
if (expiryDate) clearErrors();
if (productionDate) clearErrors();
}, [productionDate, expiryDate, clearErrors]);


+ 6
- 3
src/i18n/zh/purchaseOrder.json Vedi File

@@ -58,7 +58,7 @@
"putaway": "上架",
"delete": "刪除",
"Accept quantity must be greater than 0": "揀收數量不能少於1",
"QC items without result": "請完成品檢結果",
"QC items without result": "有未完成品檢項目",
"Failed items must have failed quantity": "請輸入不合格數量",
"qty cannot be greater than remaining qty": "數量不能大於剩餘數量",
"acceptQty must not greater than": "揀收數量不能大於",
@@ -70,8 +70,9 @@
"determine2": "上報2",
"determine3": "上報3",
"receiving": "收貨中",
"received": "已檢收",
"received": "待上架",
"completed": "已上架",
"partially_completed": "已部分上架",
"rejected": "已拒絕",
"escalated": "已上報",
"status": "狀態",
@@ -135,5 +136,7 @@
"Found": "已找到",
"escalation processing": "處理上報記錄",
"Printer": "列印機",
"Printing": "列印中"
"Printing": "列印中",
"rejectQty": "拒絕數量",
"QC decision is required": "請決定品檢結果"
}

Caricamento…
Annulla
Salva