| @@ -11,7 +11,7 @@ import { QcItemResult } from "../settings/qcItem"; | |||
| import { RecordsRes } from "../utils"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockInLiineResponse<T> { | |||
| export interface PostStockInLineResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| @@ -38,6 +38,7 @@ export interface StockInLineEntry { | |||
| export interface PurchaseQcResult { | |||
| qcItemId: number; | |||
| failQty: number; | |||
| isPassed: boolean; | |||
| } | |||
| export interface StockInInput { | |||
| status: string; | |||
| @@ -49,11 +50,12 @@ export interface StockInInput { | |||
| expiryDate: string; | |||
| } | |||
| export interface PurchaseQCInput { | |||
| status: string; | |||
| acceptedQty: number; | |||
| sampleRate: number; | |||
| sampleWeight: number; | |||
| totalWeight: number; | |||
| status?: string; | |||
| qcAccept: boolean; | |||
| acceptQty: number; | |||
| sampleRate?: number; | |||
| sampleWeight?: number; | |||
| totalWeight?: number; | |||
| qcResult: PurchaseQcResult[]; | |||
| } | |||
| export interface EscalationInput { | |||
| @@ -92,7 +94,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||
| console.log(data) | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLiineResponse<StockInLineEntry> | |||
| PostStockInLineResponse<StockInLineEntry> | |||
| >(`${BASE_API_URL}/stockInLine/create`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -106,7 +108,7 @@ export const updateStockInLine = async ( | |||
| data: StockInLineEntry & ModalFormInput, | |||
| ) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLiineResponse<StockInLineEntry & ModalFormInput> | |||
| PostStockInLineResponse<StockInLineEntry & ModalFormInput> | |||
| >(`${BASE_API_URL}/stockInLine/update`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -117,7 +119,7 @@ export const updateStockInLine = async ( | |||
| }; | |||
| export const startPo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>( | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/start/${poId}`, | |||
| { | |||
| method: "POST", | |||
| @@ -130,7 +132,7 @@ export const startPo = async (poId: number) => { | |||
| }; | |||
| export const checkPolAndCompletePo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>( | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/check/${poId}`, | |||
| { | |||
| method: "POST", | |||
| @@ -12,7 +12,7 @@ import { RecordsRes } from "../utils"; | |||
| import { Uom } from "../settings/uom"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockInLiineResponse<T> { | |||
| export interface PostStockInLineResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| @@ -35,13 +35,16 @@ export interface StockInLineEntry { | |||
| export interface PurchaseQcResult { | |||
| qcItemId: number; | |||
| isPassed: boolean, | |||
| failQty: number; | |||
| remarks?: string | |||
| } | |||
| export interface StockInInput { | |||
| status: string; | |||
| poCode: string; | |||
| productLotNo?: string; | |||
| dnNo?: string; | |||
| dnDate?: string; | |||
| itemName: string; | |||
| invoiceNo?: string; | |||
| receiptDate: string; | |||
| @@ -107,7 +110,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
| export const createStockInLine = async (data: StockInLineEntry) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLiineResponse<StockInLineEntry> | |||
| PostStockInLineResponse<StockInLineEntry> | |||
| >(`${BASE_API_URL}/stockInLine/create`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -121,7 +124,7 @@ export const updateStockInLine = async ( | |||
| data: StockInLineEntry & ModalFormInput, | |||
| ) => { | |||
| const stockInLine = await serverFetchJson< | |||
| PostStockInLiineResponse<StockInLineEntry & ModalFormInput> | |||
| PostStockInLineResponse<StockInLineEntry & ModalFormInput> | |||
| >(`${BASE_API_URL}/stockInLine/update`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| @@ -132,7 +135,7 @@ export const updateStockInLine = async ( | |||
| }; | |||
| export const startPo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>( | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/start/${poId}`, | |||
| { | |||
| method: "POST", | |||
| @@ -145,7 +148,7 @@ export const startPo = async (poId: number) => { | |||
| }; | |||
| export const checkPolAndCompletePo = async (poId: number) => { | |||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>( | |||
| const po = await serverFetchJson<PostStockInLineResponse<PoResult>>( | |||
| `${BASE_API_URL}/po/check/${poId}`, | |||
| { | |||
| method: "POST", | |||
| @@ -54,6 +54,10 @@ export const arrayToDateString = (arr: ConfigType | (number | undefined)[]) => { | |||
| return arrayToDayjs(arr).format(OUTPUT_DATE_FORMAT); | |||
| }; | |||
| export const arrayToInputDateString = (arr: ConfigType | (number | undefined)[]) => { | |||
| return arrayToDayjs(arr).format(INPUT_DATE_FORMAT); | |||
| }; | |||
| export const dateStringToDayjs = (date: string) => { | |||
| // Format: YYYY/MM/DD | |||
| return dayjs(date, OUTPUT_DATE_FORMAT); | |||
| @@ -55,10 +55,10 @@ const navigateTo = useCallback( | |||
| <Table aria-label="Two column navigable table" size="small"> | |||
| <TableHead> | |||
| <TableRow> | |||
| <TableCell>{t("purchase order code")}</TableCell> | |||
| <TableCell>{t("item name")}</TableCell> | |||
| <TableCell>{t("escalation level")}</TableCell> | |||
| <TableCell>{t("reason")}</TableCell> | |||
| <TableCell>{t("Purchase Order Code")}</TableCell> | |||
| <TableCell>{t("Item Name")}</TableCell> | |||
| <TableCell>{t("Escalation Level")}</TableCell> | |||
| <TableCell>{t("Reason")}</TableCell> | |||
| </TableRow> | |||
| </TableHead> | |||
| <TableBody> | |||
| @@ -823,6 +823,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| setProcessedQty={setProcessedQty} | |||
| itemDetail={selectedRow} | |||
| warehouse={warehouse} | |||
| fetchPoDetail={fetchPoDetail} | |||
| /> | |||
| </Box> | |||
| </TableCell> | |||
| @@ -73,6 +73,7 @@ interface Props { | |||
| itemDetail: PurchaseOrderLine; | |||
| stockInLine: StockInLine[]; | |||
| warehouse: WarehouseResult[]; | |||
| fetchPoDetail: (poId: string) => void; | |||
| } | |||
| export type StockInLineEntryError = { | |||
| @@ -111,6 +112,7 @@ function PoInputGrid({ | |||
| itemDetail, | |||
| stockInLine, | |||
| warehouse, | |||
| fetchPoDetail | |||
| }: Props) { | |||
| console.log(itemDetail); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -272,6 +274,7 @@ const closeNewModal = useCallback(() => { | |||
| const newParams = new URLSearchParams(searchParams.toString()); | |||
| newParams.delete("stockInLineId"); // Remove the parameter | |||
| router.replace(`${pathname}?${newParams.toString()}`); | |||
| fetchPoDetail(itemDetail.purchaseOrderId.toString()); | |||
| setTimeout(() => { | |||
| setNewOpen(false); // Close the modal first | |||
| }, 300); // Add a delay to avoid immediate re-trigger of useEffect | |||
| @@ -413,6 +416,17 @@ const closeNewModal = useCallback(() => { | |||
| [], | |||
| ); | |||
| const getButtonSx = (status : string) => { | |||
| let btnSx = {label:"", color:""}; | |||
| switch (status) { | |||
| case "received": btnSx = {label: t("putaway processing"), color:"secondary.main"}; break; | |||
| case "rejected": | |||
| case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break; | |||
| default: btnSx = {label: t("qc processing"), color:"success.main"}; | |||
| } | |||
| return btnSx | |||
| }; | |||
| // const handleQrCode = useCallback( | |||
| // (id: GridRowId, params: any) => () => { | |||
| // setRowModesModel((prev) => ({ | |||
| @@ -532,7 +546,7 @@ const closeNewModal = useCallback(() => { | |||
| { | |||
| field: "status", | |||
| headerName: t("Status"), | |||
| width: 70, | |||
| width: 140, | |||
| // flex: 0.5, | |||
| renderCell: (params) => { | |||
| return t(`${params.row.status}`); | |||
| @@ -546,20 +560,22 @@ const closeNewModal = useCallback(() => { | |||
| // )} | ${t("putaway")} | ${t("delete")}`, | |||
| headerName: "動作", | |||
| // headerName: "start | qc | escalation | stock in | putaway | delete", | |||
| width: 300, | |||
| width: 200, | |||
| // flex: 2, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| // console.log(params.row.status); | |||
| const status = params.row.status.toLowerCase(); | |||
| const btnSx = getButtonSx(status); | |||
| // console.log(stockInLineStatusMap[status]); | |||
| // console.log(session?.user?.abilities?.includes("APPROVAL")); | |||
| return [ | |||
| <GridActionsCellItem | |||
| icon={<Button variant="contained">{t("qc processing")}</Button>} | |||
| icon={<Button variant="contained" sx={{ width: '150px', backgroundColor: btnSx.color }}> | |||
| {btnSx.label}</Button>} | |||
| label="start" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| // disabled={!(stockInLineStatusMap[status] === 0)} | |||
| @@ -569,20 +585,21 @@ const closeNewModal = useCallback(() => { | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<Button variant="contained">{t("putawayBtn")}</Button>} | |||
| label="start" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| // disabled={!(stockInLineStatusMap[status] === 0)} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStart(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| // <GridActionsCellItem | |||
| // icon={<Button variant="contained">{t("putawayBtn")}</Button>} | |||
| // label="start" | |||
| // sx={{ | |||
| // color: "primary.main", | |||
| // // marginRight: 1, | |||
| // }} | |||
| // // disabled={!(stockInLineStatusMap[status] === 0)} | |||
| // // set _isNew to false after posting | |||
| // // or check status | |||
| // onClick={handleStart(params.row.id, params)} | |||
| // color="inherit" | |||
| // key="edit" | |||
| // />, | |||
| // <GridActionsCellItem | |||
| // icon={<Button variant="contained">{t("qc processing")}</Button>} | |||
| // label="start" | |||
| @@ -121,9 +121,9 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| const params = useSearchParams(); | |||
| const [btnIsLoading, setBtnIsLoading] = useState(false); | |||
| console.log(params.get("id")); | |||
| console.log(itemDetail); | |||
| console.log(itemDetail.qcResult); | |||
| // console.log(params.get("id")); | |||
| // console.log(itemDetail); | |||
| // console.log(itemDetail.qcResult); | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: { | |||
| ...itemDetail, | |||
| @@ -245,8 +245,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| }, [scanner.values]); | |||
| useEffect(() => { | |||
| setValue("status", "completed"); | |||
| setValue("warehouseId", options[0].value); | |||
| setValue("status", "received"); | |||
| // setValue("status", "completed"); | |||
| setValue("warehouseId", options[0].value); //TODO: save all warehouse entry? | |||
| }, []); | |||
| useEffect(() => { | |||
| @@ -51,6 +51,7 @@ import StockInFormVer2 from "./StockInFormVer2"; | |||
| import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
| import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||
| import { escape } from "lodash"; | |||
| import { PanoramaSharp } from "@mui/icons-material"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| @@ -216,7 +217,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| <FormControlLabel | |||
| value="true" | |||
| control={<Radio />} | |||
| label="合格" | |||
| label="合格" | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| sx={{ | |||
| color: currentValue === true ? "green" : "inherit", | |||
| "& .Mui-checked": {color: "green"} | |||
| @@ -226,6 +228,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| value="false" | |||
| control={<Radio />} | |||
| label="不合格" | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| sx={{ | |||
| color: currentValue === false ? "red" : "inherit", | |||
| "& .Mui-checked": {color: "red"} | |||
| @@ -237,7 +240,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| }, | |||
| }, | |||
| { | |||
| field: "failedQty", | |||
| field: "failQty", | |||
| headerName: t("failedQty"), | |||
| flex: 1, | |||
| // editable: true, | |||
| @@ -246,13 +249,13 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| type="number" | |||
| size="small" | |||
| value={!params.row.isPassed? (params.value ?? '') : '0'} | |||
| disabled={params.row.isPassed} | |||
| disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"} | |||
| 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, failedQty: next } : r)) | |||
| prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) | |||
| ); | |||
| }} | |||
| onClick={(e) => e.stopPropagation()} | |||
| @@ -271,6 +274,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| <TextField | |||
| size="small" | |||
| value={params.value ?? ''} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| onChange={(e) => { | |||
| const remarks = e.target.value; | |||
| // const next = v === '' ? undefined : Number(v); | |||
| @@ -296,10 +300,12 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| // Set initial value for acceptQty | |||
| useEffect(() => { | |||
| if (itemDetail?.acceptedQty !== undefined) { | |||
| setValue("acceptQty", itemDetail.acceptedQty); | |||
| if (itemDetail?.demandQty > 0) { //!== undefined) { | |||
| setValue("acceptQty", itemDetail.demandQty); // THIS NEED TO UPDATE TO NOT USE DEMAND QTY | |||
| } else { | |||
| setValue("acceptQty", itemDetail?.acceptedQty); | |||
| } | |||
| }, [itemDetail?.acceptedQty, setValue]); | |||
| }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | |||
| // const [openCollapse, setOpenCollapse] = useState(false) | |||
| const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | |||
| @@ -365,14 +371,14 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| /> | |||
| </Grid> | |||
| {/* <Grid item xs={12}> | |||
| {!qcAccept && ( | |||
| <Grid item xs={12}> | |||
| <EscalationComponent | |||
| forSupervisor={false} | |||
| isCollapsed={isCollapsed} | |||
| setIsCollapsed={setIsCollapsed} | |||
| /> | |||
| </Grid> */} | |||
| </Grid>)} | |||
| </> | |||
| )} | |||
| {tabIndex == 1 && ( | |||
| @@ -419,14 +425,16 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| field.onChange(value); | |||
| }} | |||
| > | |||
| <FormControlLabel value="true" control={<Radio />} label="接受" /> | |||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| value="true" control={<Radio />} label="接受" /> | |||
| <Box sx={{mr:2}}> | |||
| <TextField | |||
| type="number" | |||
| label={t("acceptQty")} | |||
| sx={{ width: '150px' }} | |||
| value={qcAccept? accQty : 0 } | |||
| defaultValue={accQty} | |||
| disabled={!qcAccept} | |||
| disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"} | |||
| {...register("acceptQty", { | |||
| required: "acceptQty required!", | |||
| })} | |||
| @@ -434,13 +442,16 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| helperText={errors.acceptQty?.message} | |||
| /> | |||
| </Box> | |||
| <FormControlLabel value="false" control={<Radio />} label="不接受及上報" /> | |||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| value="false" control={<Radio />} | |||
| sx={{"& .Mui-checked": {color: "red"}}} | |||
| label="不接受及上報" /> | |||
| </RadioGroup> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| {/* <Grid item xs={12}> | |||
| {/* {qcAccept && <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Escalation Result")} | |||
| </Typography> | |||
| @@ -451,7 +462,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
| isCollapsed={isCollapsed} | |||
| setIsCollapsed={setIsCollapsed} | |||
| /> | |||
| </Grid> */} | |||
| </Grid>} */} | |||
| </Grid> | |||
| </Grid> | |||
| </> | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { ModalFormInput, PurchaseQcResult } from "@/app/api/po/actions"; | |||
| import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { | |||
| Box, | |||
| @@ -22,6 +22,9 @@ import PutawayForm from "./PutawayForm"; | |||
| import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
| import { useGridApiRef } from "@mui/x-data-grid"; | |||
| import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | |||
| import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions"; | |||
| import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; | |||
| import dayjs from "dayjs"; | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -73,15 +76,18 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation("purchaseOrder"); | |||
| const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: { | |||
| ...itemDetail, | |||
| dnDate: dayjsToInputDateString(dayjs()), | |||
| putawayLine: dummyPutawayLine, | |||
| // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||
| // warehouseId: itemDetail.defaultWarehouseId || 0 | |||
| }, | |||
| }); | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| onClose?.(...args); | |||
| @@ -89,13 +95,34 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| }, | |||
| [onClose], | |||
| ); | |||
| const isPutaway = () => { | |||
| if (itemDetail) { | |||
| const status = itemDetail.status; | |||
| return status == "received"; | |||
| } else return false; | |||
| }; | |||
| useEffect(() => { | |||
| formProps.reset({ | |||
| ...itemDetail, | |||
| dnDate: dayjsToInputDateString(dayjs()), | |||
| putawayLine: dummyPutawayLine, | |||
| }) | |||
| setOpenPutaway(isPutaway); | |||
| }, [open]) | |||
| const [openPutaway, setOpenPutaway] = useState(false); | |||
| const onOpenPutaway = useCallback(() => { | |||
| setOpenPutaway(true); | |||
| }, []); | |||
| const onClosePutaway = useCallback(() => { | |||
| setOpenPutaway(false); | |||
| }, []); | |||
| // Stock In submission handler | |||
| const onSubmitStockIn = useCallback<SubmitHandler<ModalFormInput>>( | |||
| async (data, event) => { | |||
| @@ -118,14 +145,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| }, | |||
| [], | |||
| ); | |||
| // QC submission handler | |||
| const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | |||
| async (data, event) => { | |||
| console.log("QC Submission:", event!.nativeEvent); | |||
| // TODO: Move validation into QC page | |||
| // Get QC data from the shared form context | |||
| const qcAccept = data.qcAccept; | |||
| const acceptQty = data.acceptQty; | |||
| const acceptQty = data.acceptQty as number; | |||
| // Validate QC data | |||
| const validationErrors : string[] = []; | |||
| @@ -137,7 +166,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // Check if failed items have failed quantity | |||
| const failedItemsWithoutQty = qcItems.filter(item => | |||
| item.isPassed === false && (!item.failedQty || item.failedQty <= 0) | |||
| item.isPassed === false && (!item.failQty || item.failQty <= 0) | |||
| ); | |||
| if (failedItemsWithoutQty.length > 0) { | |||
| validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`); | |||
| @@ -153,6 +182,14 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| validationErrors.push("Accept quantity must be greater than 0"); | |||
| } | |||
| // Check if dates are input | |||
| if (data.productionDate === undefined || data.productionDate == null) { | |||
| validationErrors.push("Production Date cannot be null!"); | |||
| } | |||
| if (data.expiryDate === undefined || data.expiryDate == null) { | |||
| validationErrors.push("Expiry Date cannot be null!"); | |||
| } | |||
| if (validationErrors.length > 0) { | |||
| console.error("QC Validation failed:", validationErrors); | |||
| alert(`未完成品檢: ${validationErrors}`); | |||
| @@ -160,35 +197,75 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| } | |||
| const qcData = { | |||
| qcAccept: qcAccept, | |||
| acceptQty: acceptQty, | |||
| qcItems: qcItems.map(item => ({ | |||
| id: item.id, | |||
| qcItem: item.qcItem, | |||
| qcDescription: item.qcDescription, | |||
| isPassed: item.isPassed, | |||
| failedQty: (item.failedQty && !item.isPassed) || 0, | |||
| dnNo : data.dnNo? data.dnNo : "DN00000", | |||
| dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()), | |||
| productionDate : arrayToInputDateString(data.productionDate), | |||
| expiryDate : arrayToInputDateString(data.expiryDate), | |||
| receiptDate : arrayToInputDateString(data.receiptDate), | |||
| qcAccept: qcAccept? qcAccept : false, | |||
| acceptQty: acceptQty? acceptQty : 0, | |||
| qcResult: qcItems.map(item => ({ | |||
| qcItemId: item.id, | |||
| // qcItem: item.qcItem, | |||
| // qcDescription: item.qcDescription, | |||
| isPassed: item.isPassed? item.isPassed : false, | |||
| failQty: (item.failQty && !item.isPassed) ? item.failQty : 0, | |||
| // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, | |||
| remarks: item.remarks || '' | |||
| })) | |||
| }; | |||
| // const qcData = data; | |||
| console.log("QC Data for submission:", qcData); | |||
| // await submitQcData(qcData); | |||
| if (!qcData.qcItems.every((qc) => qc.isPassed) && qcData.qcAccept) { | |||
| submitDialogWithWarning(onOpenPutaway, t, {title:"有不合格檢查項目,確認接受收貨?", confirmButtonText: "Confirm", html: ""}); | |||
| if (!qcData.qcResult.every((qc) => qc.isPassed) && qcData.qcAccept) { | |||
| submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | |||
| confirmButtonText: t("confirm putaway"), html: ""}); | |||
| return; | |||
| } | |||
| if (qcData.qcAccept) { | |||
| onOpenPutaway(); | |||
| } else { | |||
| onClose(); | |||
| } | |||
| await postStockInLineWithQc(qcData); | |||
| // return; | |||
| }, | |||
| [onOpenPutaway, qcItems], | |||
| ); | |||
| const postStockInLineWithQc = useCallback(async (qcData: PurchaseQCInput) => { | |||
| const args = { | |||
| ...qcData | |||
| // id: itemDetail.id, | |||
| // purchaseOrderId: itemDetail.purchaseOrderId, | |||
| // purchaseOrderLineId: itemDetail.purchaseOrderLineId, | |||
| // itemId: itemDetail.itemId, | |||
| // ...data, | |||
| // productionDate: productionDate, | |||
| // expiryDate: expiryDate, | |||
| // receiptDate: receiptDate, | |||
| } as ModalFormInput; | |||
| await postStockInLine(args); | |||
| if (qcData.qcAccept) { | |||
| // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| onOpenPutaway(); | |||
| } else { | |||
| closeHandler({}, "backdropClick"); | |||
| } | |||
| return ; | |||
| },[onOpenPutaway,closeHandler]); | |||
| const postStockInLine = useCallback(async (args: ModalFormInput) => { | |||
| const submitData = { | |||
| ...itemDetail, ...args | |||
| } as StockInLineEntry & ModalFormInput; | |||
| console.log(submitData); | |||
| const res = await updateStockInLine(submitData); | |||
| console.log("result ", res); | |||
| return res; | |||
| },[]) | |||
| // Email supplier handler | |||
| const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>( | |||
| async (data, event) => { | |||
| @@ -222,12 +299,22 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // binLocation: data.binLocation, | |||
| // putawayQuantity: data.putawayQuantity, | |||
| // putawayNotes: data.putawayNotes, | |||
| data: data, | |||
| acceptQty: itemDetail.demandQty? itemDetail.demandQty : itemDetail.acceptedQty, | |||
| ...data, | |||
| dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()), | |||
| productionDate : arrayToInputDateString(data.productionDate), | |||
| expiryDate : arrayToInputDateString(data.expiryDate), | |||
| receiptDate : arrayToInputDateString(data.receiptDate), | |||
| // Add other putaway specific fields | |||
| }; | |||
| } as ModalFormInput; | |||
| console.log("Putaway Data:", putawayData); | |||
| // Handle putaway submission logic here | |||
| const res = await postStockInLine(putawayData); | |||
| console.log("result ", res); | |||
| // Close modal after successful putaway | |||
| closeHandler({}, "backdropClick"); | |||
| }, | |||
| @@ -239,6 +326,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // Handle print logic here | |||
| window.print(); | |||
| }, []); | |||
| const acceptQty = formProps.watch("acceptedQty") | |||
| const checkQcIsPassed = useCallback((qcItems: QcData[]) => { | |||
| @@ -272,7 +360,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| marginRight: 3, | |||
| }} | |||
| > | |||
| {openPutaway ? ( | |||
| {openPutaway ? ( | |||
| <Box | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmitPutaway)} | |||
| @@ -299,6 +387,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={formProps.handleSubmit(onSubmitPutaway)} | |||
| > | |||
| {t("confirm putaway")} | |||
| </Button> | |||
| @@ -320,7 +409,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| <StockInFormVer2 itemDetail={itemDetail} disabled={false} /> | |||
| </Grid> | |||
| </Grid> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| id="stockInSubmit" | |||
| type="button" | |||
| @@ -330,7 +419,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| > | |||
| {t("submitStockIn")} | |||
| </Button> | |||
| </Stack> | |||
| </Stack> */} | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| @@ -345,7 +434,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| /> | |||
| </Grid> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| {itemDetail.status.toLowerCase() == "rejected" && (<Button | |||
| id="emailSupplier" | |||
| type="button" | |||
| variant="contained" | |||
| @@ -354,8 +443,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | |||
| > | |||
| {t("email supplier")} | |||
| </Button> | |||
| <Button | |||
| </Button>)} | |||
| {itemDetail.status.toLowerCase() != "completed" && (<Button | |||
| id="qcSubmit" | |||
| type="button" | |||
| variant="contained" | |||
| @@ -363,8 +452,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| sx={{ mt: 1 }} | |||
| onClick={formProps.handleSubmit(onSubmitQc)} | |||
| > | |||
| {t("confirm putaway")} | |||
| </Button> | |||
| {t("confirm qc result")} | |||
| </Button>)} | |||
| </Stack> | |||
| </> | |||
| )} | |||
| @@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| {...register("dnNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| disabled={true} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| // error={Boolean(errors.productLotNo)} | |||
| // helperText={errors.productLotNo?.message} | |||
| /> | |||
| @@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| {...register("productLotNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| disabled={disabled} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| error={Boolean(errors.productLotNo)} | |||
| helperText={errors.productLotNo?.message} | |||
| /> | |||
| @@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| sx={{ width: "100%" }} | |||
| label={t("productionDate")} | |||
| value={productionDate ? dayjs(productionDate) : undefined} | |||
| disabled={disabled} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| setValue( | |||
| @@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| sx={{ width: "100%" }} | |||
| label={t("expiryDate")} | |||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||
| disabled={disabled} | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| onChange={(date) => { | |||
| console.log(date); | |||
| if (!date) return; | |||
| @@ -322,10 +322,11 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||
| {...register("acceptedQty", { | |||
| required: "acceptedQty required!", | |||
| })} | |||
| disabled={true} | |||
| // disabled={true} | |||
| // disabled={disabled} | |||
| // error={Boolean(errors.acceptedQty)} | |||
| // helperText={errors.acceptedQty?.message} | |||
| @@ -5,7 +5,7 @@ export interface QcData { | |||
| qcItem: string, | |||
| qcDescription: string, | |||
| isPassed: boolean | undefined | |||
| failedQty: number | undefined | |||
| failQty: number | undefined | |||
| remarks: string | undefined | |||
| } | |||
| @@ -15,7 +15,7 @@ export const dummyQCData: QcData[] = [ | |||
| qcItem: "包裝", | |||
| qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||
| isPassed: undefined, | |||
| failedQty: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| @@ -23,7 +23,7 @@ export const dummyQCData: QcData[] = [ | |||
| qcItem: "肉質", | |||
| qcDescription: "肉質鬆散,則不合格", | |||
| isPassed: undefined, | |||
| failedQty: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| @@ -31,7 +31,7 @@ export const dummyQCData: QcData[] = [ | |||
| qcItem: "顔色", | |||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,", | |||
| isPassed: undefined, | |||
| failedQty: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| @@ -39,7 +39,7 @@ export const dummyQCData: QcData[] = [ | |||
| qcItem: "狀態", | |||
| qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||
| isPassed: undefined, | |||
| failedQty: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| { | |||
| @@ -47,7 +47,7 @@ export const dummyQCData: QcData[] = [ | |||
| qcItem: "異物", | |||
| qcDescription: "有不屬於本食材的雜質,則不合格", | |||
| isPassed: undefined, | |||
| failedQty: undefined, | |||
| failQty: undefined, | |||
| remarks: undefined, | |||
| }, | |||
| ] | |||
| @@ -126,6 +126,7 @@ export const submitDialogWithWarning = async ( | |||
| title: t("Do you want to submit?") as SweetAlertTitle, | |||
| html: t("Warning!") as SweetAlertHtml, | |||
| confirmButtonText: t("Submit") as SweetAlertConfirmButtonText, | |||
| // cancelButtonText: t("Cancel") as SweetAlertConfirmButtonText, | |||
| }, | |||
| ) => { | |||
| // console.log(props) | |||
| @@ -6,8 +6,8 @@ import { authOptions } from "@/config/authConfig"; | |||
| import I18nClientProvider from "./I18nClientProvider"; | |||
| import universalLanguageDetect from "@unly/universal-language-detector"; | |||
| const FALLBACK_LANG = "en"; | |||
| const SUPPORTED_LANGUAGES = ["en", "zh"]; | |||
| const FALLBACK_LANG = "zh"; | |||
| const SUPPORTED_LANGUAGES = ["zh"]; | |||
| export const detectLanguage = async (): Promise<string> => { | |||
| // Logic to get language preference from cookies/headers/session | |||
| @@ -14,6 +14,10 @@ | |||
| "Humidity status": "濕度狀態", | |||
| "Warehouse status": "倉庫狀態", | |||
| "Progress chart": "進度圖表", | |||
| "Purchase Order Code": "採購單號", | |||
| "Item Name": "貨品名稱", | |||
| "Escalation Level": "上報等級", | |||
| "Reason": "原因", | |||
| "Order completion": "訂單完成度", | |||
| "Raw material": "原料", | |||
| "Consumable": "消耗品", | |||
| @@ -19,6 +19,7 @@ | |||
| "Start Fail": "開始失敗", | |||
| "Start PO": "開始採購訂單", | |||
| "Do you want to complete?": "確定完成嗎?", | |||
| "Cancel": "取消", | |||
| "Complete": "完成", | |||
| "Complete Success": "完成成功", | |||
| "Complete Fail": "完成失敗", | |||
| @@ -64,9 +65,9 @@ | |||
| "determine2": "上報2", | |||
| "determine3": "上報3", | |||
| "receiving": "收貨中", | |||
| "received": "已收貨", | |||
| "completed": "已完成", | |||
| "rejected": "已拒絕", | |||
| "received": "已檢收", | |||
| "completed": "已上架", | |||
| "rejected": "已拒絕及上報", | |||
| "status": "狀態", | |||
| "acceptedQty must not greater than": "接受數量不得大於", | |||
| @@ -107,6 +108,9 @@ | |||
| "Accept submit": "接受來貨", | |||
| "qc processing": "處理來貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| "view stockin": "查看收貨及品檢", | |||
| "putaway processing": "處理來貨及上架", | |||
| "putawayBtn": "上架", | |||
| "dnNo": "送貨單編號", | |||
| "dnDate": "送貨單日期", | |||
| @@ -120,9 +124,10 @@ | |||
| "update qc info": "更新品檢資料", | |||
| "email supplier": "電郵供應商", | |||
| "confirm putaway": "確定及上架", | |||
| "confirm qc result": "確定品檢結果", | |||
| "warehouse": "倉庫", | |||
| "qcItem": "檢查項目", | |||
| "qcItem": "品檢項目", | |||
| "passed": "接受", | |||
| "failed": "不接受", | |||
| "failedQty": "不合格數", | |||