| @@ -6,14 +6,55 @@ import { cache } from "react"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { QcItemResult } from "../settings/qcItem"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { ConsoPickOrderResult, PickOrderLineWithSuggestedLot, PickOrderResult, PreReleasePickOrderSummary } from "."; | |||
| import { ConsoPickOrderResult, PickOrderLineWithSuggestedLot, PickOrderResult, PreReleasePickOrderSummary, StockOutLine } from "."; | |||
| import { PurchaseQcResult } from "../po/actions"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockOutLiineResponse<T> { | |||
| id: number | null; | |||
| name: string; | |||
| code: string; | |||
| type?: string | |||
| message: string | null; | |||
| errorPosition: string | keyof T; | |||
| entity: T | T[] | |||
| } | |||
| export interface ReleasePickOrderInputs { | |||
| consoCode: string | |||
| assignTo: number, | |||
| } | |||
| export interface CreateStockOutLine { | |||
| consoCode: string, | |||
| pickOrderLineId: number, | |||
| inventoryLotLineId: number, | |||
| qty: number, | |||
| } | |||
| export interface UpdateStockOutLine { | |||
| id: number, | |||
| // consoCode: String, | |||
| itemId: number, | |||
| qty: number, | |||
| pickOrderLineId: number, | |||
| inventoryLotLineId?: number, | |||
| status: string, | |||
| pickTime?: string, | |||
| // pickerId: number? | |||
| } | |||
| export interface PickOrderQcInput { | |||
| qty: number | |||
| status: string | |||
| qcResult: PurchaseQcResult[]; | |||
| } | |||
| export interface PickOrderApprovalInput { | |||
| allowQty: number | |||
| rejectQty: number | |||
| status: string | |||
| } | |||
| export const consolidatePickOrder = async (ids: number[]) => { | |||
| const pickOrder = await serverFetchJson<any>(`${BASE_API_URL}/pickOrder/conso`, { | |||
| method: "POST", | |||
| @@ -80,6 +121,13 @@ export const consolidatePickOrder_revert = async (ids: number[]) => { | |||
| } | |||
| }); | |||
| export const fetchStockOutLineClient = cache(async (pickOrderLineId: number) => { | |||
| return serverFetchJson<StockOutLine[]>(`${BASE_API_URL}/stockOutLine/getByPickOrderLineId/${pickOrderLineId}`, { | |||
| method: 'GET', | |||
| next: { tags: ["pickorder"] }, | |||
| }); | |||
| }); | |||
| export const fetchConsoDetail = cache(async (consoCode: string) => { | |||
| return serverFetchJson<PreReleasePickOrderSummary>(`${BASE_API_URL}/pickOrder/pre-release-info/${consoCode}`, { | |||
| method: 'GET', | |||
| @@ -98,4 +146,27 @@ export const consolidatePickOrder_revert = async (ids: number[]) => { | |||
| }); | |||
| revalidateTag("pickorder"); | |||
| return po | |||
| } | |||
| export const createStockOutLine = async (data: CreateStockOutLine) => { | |||
| console.log("triggering") | |||
| const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>>(`${BASE_API_URL}/stockOutLine/create`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| revalidateTag("pickorder"); | |||
| return po | |||
| } | |||
| export const updateStockOutLine = async (data: UpdateStockOutLine) => { | |||
| console.log(data) | |||
| const po = await serverFetchJson<PostStockOutLiineResponse<StockOutLine>> | |||
| (`${BASE_API_URL}/stockOutLine/update`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| }); | |||
| revalidateTag("pickorder"); | |||
| return po | |||
| } | |||
| @@ -8,7 +8,13 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||
| import { QcItemWithChecks } from "."; | |||
| export interface QcResult { | |||
| id: number | |||
| qcItemId: number | |||
| name: string | |||
| code: string | |||
| stockInLineId?: number | |||
| stockOutLineId?: number | |||
| failQty: number | |||
| } | |||
| export const fetchQcItemCheck = cache(async (itemId?: number) => { | |||
| @@ -21,7 +27,13 @@ export const fetchQcItemCheck = cache(async (itemId?: number) => { | |||
| export const fetchQcResult = cache(async (id: number) => { | |||
| return serverFetchJson<any[]>(`${BASE_API_URL}/qcResult/${id}`, { | |||
| return serverFetchJson<QcResult[]>(`${BASE_API_URL}/qcResult/${id}`, { | |||
| next: { tags: ["qc"] }, | |||
| }); | |||
| }); | |||
| export const fetchPickOrderQcResult = cache(async (id: number) => { | |||
| return serverFetchJson<QcResult[]>(`${BASE_API_URL}/qcResult/pick-order/${id}`, { | |||
| next: { tags: ["qc"] }, | |||
| }); | |||
| }); | |||
| @@ -1,6 +1,7 @@ | |||
| import dayjs, { ConfigType, Dayjs } from "dayjs"; | |||
| import { Uom } from "../api/settings/uom"; | |||
| import { ListIterateeCustom, every, isArray, isNaN, isNull, isUndefined, take } from "lodash"; | |||
| import { Box, BoxProps, } from "@mui/material"; | |||
| export const manhourFormatter = new Intl.NumberFormat("en-HK", { | |||
| minimumFractionDigits: 2, | |||
| @@ -74,8 +75,11 @@ export const stockInLineStatusMap: { [status: string]: number } = { | |||
| export const stockOutLineStatusMap: { [status: string]: number } = { | |||
| "draft": 0, | |||
| "pending": 1, // waiting for qc | |||
| "determine1": 2, // waiting for qc | |||
| "lot-change": 3, // waiting for qc | |||
| // after qc = completed | |||
| "completed": 2, | |||
| "completed": 4, | |||
| "rejected": 5, | |||
| }; | |||
| export const pickOrderStatusMap: { [status: string]: number } = { | |||
| @@ -92,4 +96,3 @@ export const calculateWeight = (qty: number, uom: Uom) => { | |||
| export const returnWeightUnit = (uom: Uom) => { | |||
| return uom.unit4 || uom.unit3 || uom.unit2 || uom.unit1; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| "use client"; | |||
| import { Box, Grid } from "@mui/material"; | |||
| export function FitAllCell(list: (string | number)[]) { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: "flex", | |||
| flexDirection: "column", | |||
| maxHeight: 100, | |||
| overflowY: "scroll", | |||
| scrollbarWidth: "none", // For Firefox | |||
| "&::-webkit-scrollbar": { | |||
| display: "none", // For Chrome, Safari, and Opera | |||
| }, | |||
| }} | |||
| > | |||
| {list.map((item, index) => ( | |||
| <Grid sx={{ mt: 1 }} key={index}> | |||
| {`${item}`} | |||
| </Grid> | |||
| ))} | |||
| </Box> | |||
| ); | |||
| } | |||
| @@ -160,7 +160,9 @@ const CreateItem: React.FC<Props> = ({ | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | |||
| > | |||
| <Grid> | |||
| <Grid | |||
| > | |||
| <Typography mb={2} variant="h4"> | |||
| {t(`${mode} ${title}`)} | |||
| </Typography> | |||
| @@ -185,7 +187,6 @@ const CreateItem: React.FC<Props> = ({ | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| // disabled={submitDisabled} | |||
| > | |||
| {isEditMode ? t("Save") : t("Confirm")} | |||
| </Button> | |||
| @@ -0,0 +1,132 @@ | |||
| "use client"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useEffect, useMemo } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| useGridApiContext, | |||
| GridRenderCellParams, | |||
| GridRenderEditCellParams, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import InputDataGrid from "../InputDataGrid"; | |||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { PickOrderApprovalInput } from "@/app/api/pickorder/actions"; | |||
| import { StockOutLine } from "@/app/api/pickorder"; | |||
| interface Props { | |||
| // approvalDefaultValues: StockInLine; | |||
| // qc: QcItemWithChecks[]; | |||
| approvalDefaultValues: StockOutLine & PickOrderApprovalInput; | |||
| disabled: boolean | |||
| } | |||
| const ApprovalContent: React.FC<Props> = ({ | |||
| // qc, | |||
| approvalDefaultValues, | |||
| disabled | |||
| }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<PickOrderApprovalInput>(); | |||
| console.log(approvalDefaultValues) | |||
| // const status = "rejected" | |||
| const totalQty = approvalDefaultValues.qty | |||
| const allowQty = watch("allowQty"); | |||
| const rejectQty = watch("rejectQty"); | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t(`Lot Change Approval`)} | |||
| </Typography> | |||
| </Grid> | |||
| {/* <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t(`to be processed`)}: {approvalDefaultValues.rejectQty - rejectQty} | |||
| </Typography> | |||
| </Grid> */} | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("allowQty")} | |||
| fullWidth | |||
| {...register("allowQty", { | |||
| required: "allowQty required!", | |||
| min: 0, | |||
| valueAsNumber: true, | |||
| max: approvalDefaultValues.allowQty | |||
| })} | |||
| disabled={disabled} | |||
| defaultValue={approvalDefaultValues.allowQty} | |||
| error={Boolean(errors.allowQty)} | |||
| helperText={errors.allowQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("rejectQty")} | |||
| fullWidth | |||
| {...register("rejectQty", { | |||
| required: "rejectQty required!", | |||
| min: 0, | |||
| valueAsNumber: true, | |||
| max: approvalDefaultValues.rejectQty | |||
| })} | |||
| disabled={disabled} | |||
| defaultValue={approvalDefaultValues.rejectQty} | |||
| error={Boolean(errors.rejectQty)} | |||
| helperText={errors.rejectQty?.message} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default ApprovalContent; | |||
| @@ -0,0 +1,137 @@ | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import useUploadContext from "../UploadProvider/useUploadContext"; | |||
| import { | |||
| PickOrderApprovalInput, | |||
| PickOrderQcInput, | |||
| updateStockOutLine, | |||
| UpdateStockOutLine, | |||
| } from "@/app/api/pickorder/actions"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import QcContent from "./QcContent"; | |||
| import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | |||
| import { useCallback } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Check } from "@mui/icons-material"; | |||
| import { StockOutLine } from "@/app/api/pickorder"; | |||
| import dayjs from "dayjs"; | |||
| import { INPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; | |||
| import ApprovalContent from "./ApprovalContent"; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| // overflow: "scroll", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| display: "block", | |||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||
| }; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| // qc: QcItemWithChecks[]; | |||
| approvalDefaultValues: StockOutLine & PickOrderApprovalInput; | |||
| disabled: boolean; | |||
| } | |||
| const ApprovalForm: React.FC<Props> = ({ | |||
| // qc, | |||
| approvalDefaultValues, | |||
| disabled, | |||
| open, | |||
| onClose, | |||
| }) => { | |||
| const { setIsUploading } = useUploadContext(); | |||
| const { t } = useTranslation("pickOrder"); | |||
| const formProps = useForm<PickOrderApprovalInput>({ | |||
| defaultValues: { | |||
| allowQty: approvalDefaultValues.qty, | |||
| rejectQty: 0, | |||
| status: approvalDefaultValues.status, | |||
| }, | |||
| }); | |||
| const errors = formProps.formState.errors; | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| onClose?.(...args); | |||
| // reset(); | |||
| }, | |||
| [onClose] | |||
| ); | |||
| const onSubmit = useCallback<SubmitHandler<PickOrderApprovalInput & {}>>( | |||
| async (data, event) => { | |||
| console.log(data); | |||
| // checking later | |||
| // post | |||
| let hasError = false; | |||
| if (data.allowQty + data.rejectQty != approvalDefaultValues.qty) { | |||
| formProps.setError("allowQty", { | |||
| message: "illegal qty", | |||
| type: "required", | |||
| }); | |||
| formProps.setError("rejectQty", { | |||
| message: "illegal qty", | |||
| type: "required", | |||
| }); | |||
| } | |||
| if (hasError) { | |||
| return | |||
| } | |||
| const postData: UpdateStockOutLine = { | |||
| id: approvalDefaultValues.id, | |||
| itemId: approvalDefaultValues.itemId, | |||
| pickOrderLineId: approvalDefaultValues.pickOrderLineId, | |||
| qty: data.allowQty, //allow qty | |||
| status: data.status, | |||
| // pickTime: dayjs().format(`${INPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`), | |||
| }; | |||
| console.log(postData); | |||
| // return; | |||
| const res = await updateStockOutLine(postData); | |||
| if (res) { | |||
| console.log(res); | |||
| closeHandler({}, "backdropClick"); | |||
| } else { | |||
| console.log(res); | |||
| console.log("bug la"); | |||
| } | |||
| }, | |||
| [t] | |||
| ); | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box | |||
| sx={style} | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| <ApprovalContent | |||
| approvalDefaultValues={approvalDefaultValues} | |||
| disabled={disabled} | |||
| /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {!disabled ? ( | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| ) : undefined} | |||
| </Stack> | |||
| </Box> | |||
| </Modal> | |||
| </FormProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| export default ApprovalForm; | |||
| @@ -1,6 +1,7 @@ | |||
| "use client"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| ButtonProps, | |||
| Card, | |||
| @@ -9,25 +10,61 @@ import { | |||
| CircularProgress, | |||
| Grid, | |||
| Stack, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { GridColDef } from "@mui/x-data-grid"; | |||
| import { | |||
| GridColDef, | |||
| GridRowId, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| GridRowModes, | |||
| useGridApiRef, | |||
| GridRenderEditCellParams, | |||
| GridEditInputCell, | |||
| GridRowParams, | |||
| } from "@mui/x-data-grid"; | |||
| import { PlayArrow } from "@mui/icons-material"; | |||
| import DoneIcon from "@mui/icons-material/Done"; | |||
| import { GridRowSelectionModel } from "@mui/x-data-grid"; | |||
| import { useQcCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import { fetchPickOrderLineClient } from "@/app/api/pickorder/actions"; | |||
| import { PickOrderLineWithSuggestedLot } from "@/app/api/pickorder"; | |||
| import { | |||
| CreateStockOutLine, | |||
| createStockOutLine, | |||
| fetchPickOrderLineClient, | |||
| fetchStockOutLineClient, | |||
| PickOrderApprovalInput, | |||
| PickOrderQcInput, | |||
| } from "@/app/api/pickorder/actions"; | |||
| import { | |||
| PickOrderLineWithSuggestedLot, | |||
| StockOutLine, | |||
| } from "@/app/api/pickorder"; | |||
| import { Pageable } from "@/app/utils/fetchUtil"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { QrCode } from "../QrCode"; | |||
| import { fetchLotDetail, LotLineInfo } from "@/app/api/inventory/actions"; | |||
| import { GridRowModesModel } from "@mui/x-data-grid"; | |||
| import { stockOutLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { GridActionsCellItem } from "@mui/x-data-grid"; | |||
| import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | |||
| import useUploadContext from "../UploadProvider/useUploadContext"; | |||
| import { FitAllCell } from "@/app/utils/gridUtil"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import QcForm from "./QcForm"; | |||
| import { fetchPickOrderQcResult, QcResult } from "@/app/api/qc/actions"; | |||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | |||
| import AutoFixNormalIcon from "@mui/icons-material/AutoFixNormal"; | |||
| import ApprovalForm from "./ApprovalForm"; | |||
| import InfoIcon from "@mui/icons-material/Info"; | |||
| import VerifiedIcon from "@mui/icons-material/Verified"; | |||
| interface Props { | |||
| qc: QcItemWithChecks[]; | |||
| consoCode: string; | |||
| } | |||
| interface IsLoadingModel { | |||
| @@ -35,9 +72,50 @@ interface IsLoadingModel { | |||
| stockOutLineTable: boolean; | |||
| } | |||
| const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| export type StockOutLineEntryError = { | |||
| [field in keyof StockOutLine]?: string; | |||
| }; | |||
| export type StockOutLineRow = Partial< | |||
| StockOutLine & { | |||
| id: number; | |||
| isActive: boolean | undefined; | |||
| _isNew: boolean; | |||
| _error: StockOutLineEntryError; | |||
| } | |||
| >; | |||
| class ProcessRowUpdateError extends Error { | |||
| public readonly row: StockOutLineRow; | |||
| public readonly errors: StockOutLineEntryError | undefined; | |||
| constructor( | |||
| row: StockOutLineRow, | |||
| message?: string, | |||
| errors?: StockOutLineEntryError | |||
| ) { | |||
| super(message); | |||
| this.row = row; | |||
| this.errors = errors; | |||
| Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||
| } | |||
| } | |||
| export type formDefaultValues = StockOutLine & | |||
| (PickOrderQcInput | PickOrderApprovalInput); | |||
| const PickOrderDetail: React.FC<Props> = ({ consoCode, qc }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const [selectedRow, setSelectRow] = useState<GridRowSelectionModel>(); | |||
| const apiRef = useGridApiRef(); | |||
| const [qcResult, setQcResult] = useState([] as QcResult[]); | |||
| const [selectedRow, setSelectedRow] = useState<GridRowSelectionModel>([]); | |||
| const [currPol, setCurrPol] = useState<PickOrderLineWithSuggestedLot>(); | |||
| const [isChangeLotSolId, setIsChangeLotSolId] = useState<number | undefined>( | |||
| undefined | |||
| ); | |||
| const [formDefaultValues, setFormDefaultValues] = | |||
| useState<formDefaultValues>(); | |||
| const [isLoadingModel, setIsLoadingModel] = useState<IsLoadingModel>({ | |||
| pickOrderLineTable: false, | |||
| stockOutLineTable: false, | |||
| @@ -53,6 +131,8 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| const [polTotalCount, setPolTotalCount] = useState(0); | |||
| const [solTotalCount, setSolTotalCount] = useState(0); | |||
| const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||
| const [btnIsLoading, setBtnIsLoading] = useState(false); | |||
| const { setIsUploading } = useUploadContext(); | |||
| const [pickOrderLine, setPickOrderLine] = useState< | |||
| PickOrderLineWithSuggestedLot[] | |||
| @@ -80,58 +160,36 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| headerName: "uom", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| field: "lotLineId", | |||
| headerName: "lotLineId", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| field: "warehouse", | |||
| headerName: "location", | |||
| flex: 1, | |||
| renderCell: (params) => { | |||
| if (!params.row.warehouse) return <></>; | |||
| const warehouseList = JSON.parse(params.row.warehouse) as string[]; | |||
| return FitAllCell(warehouseList); | |||
| }, | |||
| }, | |||
| { | |||
| field: "suggestedLotNo", | |||
| headerName: "suggestedLotNo", | |||
| flex: 1.2, | |||
| renderCell: (params) => { | |||
| if (!params.row.suggestedLotNo) return <></>; | |||
| const suggestedLotNoList = JSON.parse( | |||
| params.row.suggestedLotNo | |||
| ) as string[]; | |||
| return FitAllCell(suggestedLotNoList); | |||
| }, | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| const [stockOutLine, setStockOutLine] = useState([]); | |||
| const stockOutLineColumns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "code", | |||
| headerName: "actual lot (out line", | |||
| flex: 1, | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| const handleStartPickOrder = useCallback(async () => {}, []); | |||
| const handleCompletePickOrder = useCallback(async () => {}, []); | |||
| useEffect(() => { | |||
| console.log(selectedRow); | |||
| }, [selectedRow]); | |||
| const buttonData = useMemo( | |||
| () => ({ | |||
| buttonName: "complete", | |||
| title: t("Do you want to complete?"), | |||
| confirmButtonText: t("Complete"), | |||
| successTitle: t("Complete Success"), | |||
| errorTitle: t("Complete Fail"), | |||
| buttonText: t("Complete PO"), | |||
| buttonIcon: <DoneIcon />, | |||
| buttonColor: "info", | |||
| disabled: true, | |||
| }), | |||
| [] | |||
| ); | |||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||
| const onOpenScanner = useCallback(() => { | |||
| setOpenScanner((prev) => !prev); | |||
| }, []); | |||
| const fetchPickOrderLine = useCallback( | |||
| async (params: Record<string, any>) => { | |||
| @@ -158,27 +216,397 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| }, | |||
| [fetchPickOrderLineClient, consoCode] | |||
| ); | |||
| const buttonData = useMemo( | |||
| () => ({ | |||
| buttonName: "complete", | |||
| title: t("Do you want to complete?"), | |||
| confirmButtonText: t("Complete"), | |||
| successTitle: t("Complete Success"), | |||
| errorTitle: t("Complete Fail"), | |||
| buttonText: t("Complete PO"), | |||
| buttonIcon: <DoneIcon />, | |||
| buttonColor: "info", | |||
| disabled: true, | |||
| }), | |||
| [] | |||
| ); | |||
| const [stockOutLine, setStockOutLine] = useState<StockOutLine[]>([]); | |||
| const getRowId = useCallback<GridRowIdGetter<StockOutLineRow>>( | |||
| (row) => row.id as number, | |||
| [] | |||
| ); | |||
| const [qcOpen, setQcOpen] = useState(false); | |||
| const [approvalOpen, setApprovalOpen] = useState(false); | |||
| const closeQcModal = useCallback(() => { | |||
| setQcOpen(false); | |||
| }, []); | |||
| const openQcModal = useCallback(() => { | |||
| setQcOpen(true); | |||
| }, []); | |||
| const closeApprovalModal = useCallback(() => { | |||
| setApprovalOpen(false); | |||
| }, []); | |||
| const openApprovalModal = useCallback(() => { | |||
| setApprovalOpen(true); | |||
| }, []); | |||
| const triggerRefetch = useCallback(() => { | |||
| setSelectedRow((prev) => prev); | |||
| }, []); | |||
| const handleDelete = useCallback( | |||
| (id: GridRowId) => () => { | |||
| setStockOutLine((prev) => prev.filter((e) => getRowId(e) !== id)); | |||
| }, | |||
| [getRowId] | |||
| ); | |||
| const handleStart = useCallback( | |||
| (id: GridRowId, params: any) => () => { | |||
| setBtnIsLoading(true); | |||
| setRowModesModel((prev) => ({ | |||
| ...prev, | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| setTimeout(async () => { | |||
| // post stock in line | |||
| const oldId = params.row.id; | |||
| console.log(params.row); | |||
| // console.log(currPol); | |||
| const postData = { | |||
| consoCode: consoCode, | |||
| pickOrderLineId: params.row.pickOrderLineId, | |||
| inventoryLotLineId: params.row.inventoryLotLineId, | |||
| qty: params.row.qty, | |||
| } as CreateStockOutLine; | |||
| console.log(postData); | |||
| // return | |||
| console.log("triggering"); | |||
| const res = await createStockOutLine(postData); | |||
| if (res) { | |||
| console.log(res); | |||
| setStockOutLine((prev) => | |||
| prev.map((p) => (p.id === oldId ? (res.entity as StockOutLine) : p)) | |||
| ); | |||
| } | |||
| setBtnIsLoading(false); | |||
| // do post directly to test | |||
| // openStartModal(); | |||
| }, 500); | |||
| }, | |||
| [createStockOutLine] | |||
| ); | |||
| useEffect(() => { | |||
| console.log(stockOutLine); | |||
| }, [stockOutLine]); | |||
| const handleApproval = useCallback( | |||
| (id: GridRowId, params: any) => async () => { | |||
| setBtnIsLoading(true); | |||
| console.log(params.row.qty); | |||
| console.log(params.row); | |||
| setFormDefaultValues({ | |||
| ...(params.row as StockOutLine), | |||
| status: "lot-change", | |||
| } as StockOutLine & PickOrderApprovalInput); | |||
| setTimeout(() => { | |||
| // open qc modal | |||
| console.log("delayed"); | |||
| openApprovalModal(); | |||
| setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [] | |||
| ); | |||
| const handleLotChange = useCallback( | |||
| (id: GridRowId, params: GridRowParams<any>) => async () => { | |||
| setOpenScanner((prev) => !prev); | |||
| console.log(id); | |||
| setIsChangeLotSolId((prev) => { | |||
| if (prev != undefined) return undefined | |||
| return id as number | |||
| }); | |||
| }, | |||
| [] | |||
| ); | |||
| useEffect(() => { | |||
| console.log(isChangeLotSolId) | |||
| }, [isChangeLotSolId]) | |||
| const handleComplete = useCallback( | |||
| (id: GridRowId, params: any) => async () => { | |||
| setBtnIsLoading(true); | |||
| setRowModesModel((prev) => ({ | |||
| ...prev, | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| getQcResult(id as number).then((qcResult) => { | |||
| setQcResult(qcResult); | |||
| }); | |||
| console.log(params.row.qty); | |||
| console.log(params.row); | |||
| setFormDefaultValues({ | |||
| ...(params.row as StockOutLine), | |||
| qty: params.row.qty, | |||
| status: "completed", | |||
| } as StockOutLine & PickOrderQcInput); | |||
| setTimeout(() => { | |||
| // open qc modal | |||
| console.log("delayed"); | |||
| openQcModal(); | |||
| setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [] | |||
| ); | |||
| const stockOutLineColumns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "itemName", | |||
| headerName: "item name", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| field: "qty", | |||
| headerName: "qty", | |||
| editable: true, | |||
| flex: 1, | |||
| type: "number", | |||
| // renderEditCell(params: GridRenderEditCellParams<StockOutLineRow>) { | |||
| // const errorMessage = | |||
| // params.row._error?.[params.field as keyof StockOutLineEntryError]; | |||
| // const content = ( | |||
| // <GridEditInputCell | |||
| // {...params} | |||
| // inputProps={{ min: 0 }} | |||
| // /> | |||
| // ); | |||
| // return errorMessage ? ( | |||
| // <Tooltip title={t(errorMessage)}> | |||
| // <Box width="100%">{content}</Box> | |||
| // </Tooltip> | |||
| // ) : ( | |||
| // content | |||
| // ); | |||
| // }, | |||
| }, | |||
| { | |||
| field: "lotNo", | |||
| headerName: "lotNo", | |||
| flex: 1, | |||
| }, | |||
| { | |||
| field: "status", | |||
| headerName: t("status"), | |||
| flex: 0.5, | |||
| renderCell: (params) => { | |||
| return t(`${params.row.status}`); | |||
| }, | |||
| }, | |||
| { | |||
| field: "actions", | |||
| type: "actions", | |||
| headerName: `${t("start")} | |||
| | ${t("approval")} | |||
| | ${t("lot change")} | |||
| | ${t("checkout")} | |||
| | ${t("delete")}`, | |||
| flex: 1.5, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| const status = params.row.status.toLowerCase(); | |||
| return [ | |||
| <GridActionsCellItem | |||
| icon={<PlayArrowIcon />} | |||
| label="start" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={!(stockOutLineStatusMap[status] === 0)} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStart(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<VerifiedIcon />} | |||
| label="approval" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={stockOutLineStatusMap[status] !== 2} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleApproval(params.row.id, params)} // start scanning for that row | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<AutoFixNormalIcon />} | |||
| label="lot change" /// | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={stockOutLineStatusMap[status] !== 3} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleLotChange(params.row.id, params)} // start scanning for that row | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<ShoppingCartIcon />} | |||
| label="qcAndPick" | |||
| sx={{ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={ | |||
| !params.row.inventoryLotLineId || | |||
| stockOutLineStatusMap[status] === 2 || | |||
| stockOutLineStatusMap[status] === 5 | |||
| } | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleComplete(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<DoDisturbIcon />} | |||
| label="Delete" | |||
| sx={{ | |||
| color: "error.main", | |||
| }} | |||
| disabled={stockOutLineStatusMap[status] > 0} | |||
| onClick={handleDelete(params.row.id)} | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<InfoIcon />} | |||
| label="debug button" | |||
| sx={{ | |||
| color: "error.main", | |||
| }} | |||
| onClick={() => console.log(params.row)} | |||
| />, | |||
| ]; | |||
| }, | |||
| }, | |||
| ], | |||
| [stockOutLineStatusMap, handleStart, handleDelete] | |||
| ); | |||
| const fetchStockOutLine = useCallback( | |||
| async (params: Record<string, any>) => {}, | |||
| async (params: Record<string, any>, selectedRow: GridRowSelectionModel) => { | |||
| const _selectedRow = selectedRow as number[]; | |||
| console.log(params); | |||
| console.log(_selectedRow); | |||
| // fetch | |||
| const res = await fetchStockOutLineClient(_selectedRow[0]); | |||
| console.log(res); | |||
| // set state | |||
| setStockOutLine(res); | |||
| }, | |||
| [] | |||
| ); | |||
| const addRow = useCallback( | |||
| (qrcode: LotLineInfo) => { | |||
| const newEntry = { | |||
| id: Date.now(), | |||
| _isNew: true, | |||
| itemId: qrcode.itemId, | |||
| itemName: qrcode.itemName, | |||
| itemNo: qrcode.itemNo, | |||
| lotNo: qrcode.lotNo, | |||
| inventoryLotLineId: qrcode.inventoryLotLineId, | |||
| qty: 0, | |||
| pickOrderLineId: selectedRow[0] as number, | |||
| status: "draft", | |||
| }; | |||
| setStockOutLine((prev) => [...prev, newEntry]); | |||
| setRowModesModel((model) => ({ | |||
| ...model, | |||
| [getRowId(newEntry)]: { | |||
| mode: GridRowModes.Edit, | |||
| }, | |||
| })); | |||
| }, | |||
| [getRowId, selectedRow] | |||
| ); | |||
| // need modify this later | |||
| const changeRow = useCallback( | |||
| (id: number, qrcode: LotLineInfo) => { | |||
| console.log(stockOutLine); | |||
| console.log(stockOutLine.find((line) => line.id === id)); | |||
| const rowToSave = { | |||
| ...stockOutLine.find((line) => line.id === id), | |||
| itemId: qrcode.itemId, | |||
| itemName: qrcode.itemName, | |||
| itemNo: qrcode.itemNo, | |||
| lotNo: qrcode.lotNo, | |||
| inventoryLotLineId: qrcode.inventoryLotLineId, | |||
| }; | |||
| console.log(rowToSave); | |||
| const newEntries = stockOutLine.map((e) => | |||
| getRowId(e) === id ? rowToSave : e | |||
| ); | |||
| setStockOutLine(newEntries as StockOutLine[]); | |||
| }, | |||
| [stockOutLine, getRowId] | |||
| ); | |||
| useEffect(() => { | |||
| fetchPickOrderLine(polCriteriaArgs); | |||
| }, [polCriteriaArgs]); | |||
| useEffect(() => { | |||
| fetchStockOutLine(solCriteriaArgs); | |||
| }, [solCriteriaArgs]); | |||
| if (!qcOpen || !approvalOpen) { | |||
| console.log("triggering") | |||
| triggerRefetch(); | |||
| } | |||
| if (selectedRow.length > 0) fetchStockOutLine(solCriteriaArgs, selectedRow); | |||
| }, [qcOpen, approvalOpen, solCriteriaArgs, selectedRow, triggerRefetch]); | |||
| const getLotDetail = useCallback( | |||
| async (stockInLineId: number): Promise<LotLineInfo> => { | |||
| const res = await fetchLotDetail(stockInLineId); | |||
| console.log("res"); | |||
| console.log(res); | |||
| return res; | |||
| }, | |||
| [fetchLotDetail] | |||
| ); | |||
| const getQcResult = useCallback( | |||
| async (stockOutLineId: number): Promise<QcResult[]> => { | |||
| const res = await fetchPickOrderQcResult(stockOutLineId); | |||
| console.log("res"); | |||
| console.log(res); | |||
| return res; | |||
| }, | |||
| [fetchPickOrderQcResult] | |||
| ); | |||
| const [isOpenScanner, setOpenScanner] = useState(false); | |||
| const onOpenScanner = useCallback(() => { | |||
| setOpenScanner((prev) => !prev); | |||
| }, []); | |||
| const scanner = useQcCodeScanner(); | |||
| useEffect(() => { | |||
| if (isOpenScanner && !scanner.isScanning) { | |||
| @@ -188,24 +616,102 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| } | |||
| }, [isOpenScanner]); | |||
| const homemade_Qrcode = { | |||
| stockInLineId: 156, // eggs | |||
| // stockInLineId: 162, // chicken wings | |||
| }; | |||
| useEffect(() => { | |||
| if (scanner.values.length > 0) { | |||
| console.log(scanner.values[0]); | |||
| const data: QrCodeInfo = JSON.parse(scanner.values[0]); | |||
| console.log(data); | |||
| if (data.stockInLineId) { | |||
| console.log("still got in"); | |||
| console.log(data.stockInLineId); | |||
| setIsUploading(true); | |||
| // fetch | |||
| getLotDetail(data.stockInLineId).then((value) => {}); | |||
| getLotDetail(data.stockInLineId).then((qrcode) => { | |||
| // add row | |||
| if (isChangeLotSolId) { | |||
| changeRow(isChangeLotSolId, qrcode); | |||
| } else { | |||
| addRow(qrcode); | |||
| } | |||
| }); | |||
| setIsUploading(false); | |||
| } | |||
| scanner.resetScan(); | |||
| } | |||
| }, [scanner.values]); | |||
| }, [ | |||
| isChangeLotSolId, | |||
| scanner.values, | |||
| selectedRow, | |||
| changeRow, | |||
| addRow, | |||
| getLotDetail, | |||
| ]); | |||
| const homemade_Qrcode = { | |||
| stockInLineId: 156, | |||
| }; | |||
| const mannuallyAddRow = useCallback(() => { | |||
| getLotDetail(homemade_Qrcode.stockInLineId).then((qrcode) => { | |||
| addRow(qrcode); | |||
| // scanner.resetScan(); | |||
| }); | |||
| }, [addRow, homemade_Qrcode]); | |||
| const validation = useCallback( | |||
| ( | |||
| newRow: GridRowModel<StockOutLineRow> | |||
| // rowModel: GridRowSelectionModel | |||
| ): StockOutLineEntryError | undefined => { | |||
| const error: StockOutLineEntryError = {}; | |||
| const checkQty = currPol?.qty; | |||
| console.log(newRow); | |||
| if (!newRow.qty || newRow.qty <= 0) { | |||
| error["qty"] = t("illegal qty"); | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [currPol] | |||
| ); | |||
| const processRowUpdate = useCallback( | |||
| ( | |||
| newRow: GridRowModel<StockOutLineRow>, | |||
| originalRow: GridRowModel<StockOutLineRow> | |||
| ) => { | |||
| const errors = validation(newRow); // change to validation | |||
| console.log(newRow); | |||
| if (errors) { | |||
| throw new ProcessRowUpdateError( | |||
| originalRow, | |||
| "validation error", | |||
| errors | |||
| ); | |||
| } | |||
| const { _isNew, _error, ...updatedRow } = newRow; | |||
| const rowToSave = { | |||
| ...updatedRow, | |||
| } satisfies StockOutLineRow; | |||
| console.log(rowToSave); | |||
| const newEntries = stockOutLine.map((e) => | |||
| getRowId(e) === getRowId(originalRow) ? rowToSave : e | |||
| ); | |||
| console.log(newEntries); | |||
| setStockOutLine(newEntries as StockOutLine[]); | |||
| return rowToSave; | |||
| }, | |||
| [stockOutLine, validation] | |||
| ); | |||
| const onProcessRowUpdateError = useCallback( | |||
| (updateError: ProcessRowUpdateError) => { | |||
| const errors = updateError.errors; | |||
| const oldRow = updateError.row; | |||
| apiRef.current.updateRows([{ ...oldRow, _error: errors }]); | |||
| }, | |||
| [apiRef] | |||
| ); | |||
| return ( | |||
| <> | |||
| @@ -233,7 +739,13 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| justifyContent="end" | |||
| alignItems="end" | |||
| > | |||
| <Button onClick={onOpenScanner}> | |||
| <Button | |||
| onClick={mannuallyAddRow} | |||
| disabled={selectedRow.length === 0} | |||
| > | |||
| {isOpenScanner ? t("manual scanning") : t("manual scan")} | |||
| </Button> | |||
| <Button onClick={onOpenScanner} disabled={selectedRow.length === 0}> | |||
| {isOpenScanner ? t("binding") : t("bind")} | |||
| </Button> | |||
| </Grid> | |||
| @@ -254,7 +766,7 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| <StyledDataGrid rows={pickOrderLine} columns={columns} /> | |||
| </Grid> */} | |||
| <Grid item xs={12} sx={{ height: 400 }}> | |||
| {isLoadingModel.pickOrderLineTable ? ( | |||
| {isLoadingModel.pickOrderLineTable && pickOrderLine == undefined ? ( | |||
| <CircularProgress size={40} /> | |||
| ) : ( | |||
| <StyledDataGrid | |||
| @@ -262,7 +774,14 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| columns={pickOrderLineColumns} | |||
| rowSelectionModel={selectedRow} | |||
| onRowSelectionModelChange={(newRowSelectionModel) => { | |||
| setSelectRow(newRowSelectionModel); | |||
| setSelectedRow(newRowSelectionModel); | |||
| if (newRowSelectionModel && newRowSelectionModel.length > 0) { | |||
| const pol = pickOrderLine.find( | |||
| (item) => item.id === newRowSelectionModel[0] | |||
| ); | |||
| console.log(pol); | |||
| setCurrPol(pol); | |||
| } | |||
| }} | |||
| initialState={{ | |||
| pagination: { | |||
| @@ -282,15 +801,22 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| </Grid> | |||
| <Grid item xs={12} sx={{ height: 400 }}> | |||
| <StyledDataGrid | |||
| apiRef={apiRef} | |||
| rows={stockOutLine} | |||
| columns={stockOutLineColumns} | |||
| rowModesModel={rowModesModel} | |||
| onRowModesModelChange={setRowModesModel} | |||
| disableColumnMenu | |||
| editMode="row" | |||
| // processRowUpdate={processRowUpdate} | |||
| // onProcessRowUpdateError={onProcessRowUpdateError} | |||
| processRowUpdate={processRowUpdate} | |||
| onProcessRowUpdateError={onProcessRowUpdateError} | |||
| isCellEditable={(params) => { | |||
| const status = params.row.status.toLowerCase(); | |||
| return ( | |||
| stockOutLineStatusMap[status] === 0 || | |||
| stockOutLineStatusMap[status] === 3 | |||
| ); | |||
| }} | |||
| initialState={{ | |||
| pagination: { | |||
| paginationModel: { pageSize: 10, page: 0 }, | |||
| @@ -308,6 +834,26 @@ const PickOrderDetail: React.FC<Props> = ({ consoCode }) => { | |||
| </Grid> | |||
| </Grid> | |||
| </Stack> | |||
| {/* modals */} | |||
| {qcOpen && formDefaultValues != undefined && ( | |||
| <QcForm | |||
| qcDefaultValues={formDefaultValues as StockOutLine & PickOrderQcInput} | |||
| qc={qc} | |||
| disabled={false} | |||
| open={qcOpen} | |||
| onClose={closeQcModal} | |||
| /> | |||
| )} | |||
| {approvalOpen && formDefaultValues != undefined && ( | |||
| <ApprovalForm | |||
| approvalDefaultValues={ | |||
| formDefaultValues as StockOutLine & PickOrderApprovalInput | |||
| } | |||
| disabled={false} | |||
| open={approvalOpen} | |||
| onClose={closeApprovalModal} | |||
| /> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -20,14 +20,14 @@ type Props = { | |||
| }; | |||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ consoCode }) => { | |||
| // const [poWithStockInLine, warehouse, qc] = await Promise.all([ | |||
| // fetchPoWithStockInLines(id), | |||
| // fetchWarehouseList(), | |||
| // fetchQcItemCheck(), | |||
| // ]); | |||
| const [ | |||
| qc | |||
| ] = await Promise.all([ | |||
| fetchQcItemCheck(), | |||
| ]); | |||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
| return <PickOrderDetail consoCode={consoCode}/>; | |||
| return <PickOrderDetail consoCode={consoCode} qc={qc}/>; | |||
| }; | |||
| PoDetailWrapper.Loading = PickOrderDetailLoading; | |||
| @@ -0,0 +1,247 @@ | |||
| "use client"; | |||
| import { PurchaseQcResult, PurchaseQCInput } from "@/app/api/po/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| useGridApiContext, | |||
| GridRenderCellParams, | |||
| GridRenderEditCellParams, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import InputDataGrid from "../InputDataGrid"; | |||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import axios from "@/app/(main)/axios/axiosInstance"; | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| import TwoLineCell from "../PoDetail/TwoLineCell"; | |||
| import QcSelect from "../PoDetail/QcSelect"; | |||
| import { PickOrderQcInput } from "@/app/api/pickorder/actions"; | |||
| interface Props { | |||
| qcDefaultValues: PickOrderQcInput | |||
| qc: QcItemWithChecks[]; | |||
| disabled: boolean | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof PurchaseQcResult]?: string; | |||
| } | |||
| | undefined; | |||
| type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | |||
| // fetchQcItemCheck | |||
| const QcContent: React.FC<Props> = ({ qc, qcDefaultValues, disabled }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<PickOrderQcInput>(); | |||
| console.log(qcDefaultValues); | |||
| console.log(defaultValues); | |||
| //// validate form | |||
| const accQty = watch("qty"); | |||
| const validateForm = useCallback(() => { | |||
| console.log(accQty); | |||
| if (accQty > qcDefaultValues.qty) { | |||
| setError("qty", { | |||
| message: `${t("qty must not greater than")} ${qcDefaultValues.qty}`, | |||
| type: "required", | |||
| }); | |||
| } | |||
| if (accQty < 1) { | |||
| setError("qty", { | |||
| message: t("minimal value is 1"), | |||
| type: "required", | |||
| }); | |||
| } | |||
| if (isNaN(accQty)) { | |||
| setError("qty", { | |||
| message: t("value must be a number"), | |||
| type: "required", | |||
| }); | |||
| } | |||
| }, [accQty]); | |||
| useEffect(() => { | |||
| clearErrors(); | |||
| validateForm(); | |||
| }, [validateForm]); | |||
| // const [recordQty, setRecordQty] = useState(0); | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "qcItemId", | |||
| headerName: t("qc Check"), | |||
| flex: 1, | |||
| editable: !disabled, | |||
| valueFormatter(params) { | |||
| const row = params.id ? params.api.getRow<PoQcRow>(params.id) : null; | |||
| if (!row) { | |||
| return null; | |||
| } | |||
| const Qc = qc.find((q) => q.id === row.qcItemId); | |||
| return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||
| }, | |||
| renderCell(params: GridRenderCellParams<PoQcRow, number>) { | |||
| console.log(params.value); | |||
| return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||
| }, | |||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof PurchaseQcResult]; | |||
| console.log(errorMessage); | |||
| const content = ( | |||
| <QcSelect | |||
| allQcs={qc} | |||
| value={params.row.qcItemId} | |||
| onQcSelect={async (qcItemId) => { | |||
| await params.api.setEditCellValue({ | |||
| id: params.id, | |||
| field: "qcItemId", | |||
| value: qcItemId, | |||
| }); | |||
| // await params.api.setEditCellValue({ | |||
| // id: params.id, | |||
| // field: "type", | |||
| // value: "determine1", | |||
| // }); | |||
| }} | |||
| /> | |||
| ); | |||
| return errorMessage ? ( | |||
| <Tooltip title={errorMessage}> | |||
| <Box width="100%">{content}</Box> | |||
| </Tooltip> | |||
| ) : ( | |||
| content | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| field: "failQty", | |||
| headerName: t("failQty"), | |||
| flex: 1, | |||
| editable: !disabled, | |||
| type: "number", | |||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||
| // const recordQty = params.row.qty | |||
| // if (recordQty !== undefined) { | |||
| // setUnrecordQty((prev) => prev - recordQty) | |||
| // } | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof PurchaseQcResult]; | |||
| const content = <GridEditInputCell {...params} />; | |||
| return errorMessage ? ( | |||
| <Tooltip title={t(errorMessage)}> | |||
| <Box width="100%">{content}</Box> | |||
| </Tooltip> | |||
| ) : ( | |||
| content | |||
| ); | |||
| }, | |||
| }, | |||
| ], | |||
| [qc] | |||
| ); | |||
| /// validate datagrid | |||
| const validation = useCallback( | |||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | |||
| const error: EntryError = {}; | |||
| const { qcItemId, failQty } = newRow; | |||
| if (!qcItemId || qcItemId <= 0) { | |||
| error["qcItemId"] = t("select qc"); | |||
| } | |||
| if (!failQty || failQty <= 0) { | |||
| error["failQty"] = t("enter a failQty"); | |||
| } | |||
| if (failQty && failQty > qcDefaultValues.qty) { | |||
| error["failQty"] = t("qty too big"); | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [] | |||
| ); | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={12} lg={12}> | |||
| <TextField | |||
| label={t("accepted Qty")} | |||
| fullWidth | |||
| // value={qcDefaultValues.qty} | |||
| {...register("qty", { | |||
| required: "qty required!", | |||
| valueAsNumber: true, | |||
| max: qcDefaultValues.qty, | |||
| })} | |||
| disabled={disabled} | |||
| error={Boolean(errors.qty)} | |||
| helperText={errors.qty?.message} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={12}> | |||
| <InputDataGrid<PickOrderQcInput, PurchaseQcResult, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"qcResult"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| needAdd={!disabled} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default QcContent; | |||
| @@ -0,0 +1,119 @@ | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import useUploadContext from "../UploadProvider/useUploadContext"; | |||
| import { PickOrderQcInput, updateStockOutLine, UpdateStockOutLine } from "@/app/api/pickorder/actions"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import QcContent from "./QcContent"; | |||
| import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | |||
| import { useCallback } from "react"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Check } from "@mui/icons-material"; | |||
| import { StockOutLine } from "@/app/api/pickorder"; | |||
| import dayjs from "dayjs"; | |||
| import { INPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT } from "@/app/utils/formatUtil"; | |||
| const style = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| // overflow: "scroll", | |||
| bgcolor: "background.paper", | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| display: "block", | |||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||
| }; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| qc: QcItemWithChecks[]; | |||
| qcDefaultValues: StockOutLine & PickOrderQcInput; | |||
| disabled: boolean; | |||
| } | |||
| const QcForm: React.FC<Props> = ({ | |||
| qc, | |||
| qcDefaultValues, | |||
| disabled, | |||
| open, | |||
| onClose, | |||
| }) => { | |||
| const { setIsUploading } = useUploadContext(); | |||
| const { t } = useTranslation("pickOrder"); | |||
| const formProps = useForm<PickOrderQcInput>({ | |||
| defaultValues: { | |||
| qty: qcDefaultValues.qty, | |||
| status: qcDefaultValues.status | |||
| }, | |||
| }); | |||
| const errors = formProps.formState.errors; | |||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | |||
| (...args) => { | |||
| onClose?.(...args); | |||
| // reset(); | |||
| }, | |||
| [onClose] | |||
| ); | |||
| const onSubmit = useCallback<SubmitHandler<PickOrderQcInput & {}>>( | |||
| async (data, event) => { | |||
| console.log(data); | |||
| console.log(qcDefaultValues); | |||
| // checking later | |||
| // post | |||
| const postData: UpdateStockOutLine = { | |||
| id: qcDefaultValues.id, | |||
| itemId: qcDefaultValues.itemId, | |||
| pickOrderLineId: qcDefaultValues.pickOrderLineId, | |||
| inventoryLotLineId: qcDefaultValues.inventoryLotLineId, | |||
| qty: data.qty, | |||
| status: data.status, | |||
| // pickTime: dayjs().format(`${INPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`), | |||
| } | |||
| console.log(postData) | |||
| // return | |||
| const res = await updateStockOutLine(postData) | |||
| if (res) { | |||
| console.log(res) | |||
| closeHandler({}, "backdropClick"); | |||
| } else { | |||
| console.log(res) | |||
| console.log("bug la") | |||
| } | |||
| }, | |||
| [updateStockOutLine] | |||
| ); | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box | |||
| sx={style} | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| <QcContent | |||
| qc={qc} | |||
| qcDefaultValues={qcDefaultValues} | |||
| disabled={disabled} | |||
| /> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| {!disabled ? ( | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| ) : undefined} | |||
| </Stack> | |||
| </Box> | |||
| </Modal> | |||
| </FormProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| export default QcForm; | |||
| @@ -109,8 +109,8 @@ const ConsolidatedPickOrders: React.FC<Props> = ({ filterArgs }) => { | |||
| const onDetailClick = useCallback( | |||
| (pickOrder: any) => { | |||
| console.log(pickOrder); | |||
| const status = pickOrder.status | |||
| if (pickOrderStatusMap[status] >= 2) { | |||
| const status = pickOrder.status; | |||
| if (pickOrderStatusMap[status] >= 3) { | |||
| router.push(`/pickorder/detail?consoCode=${pickOrder.consoCode}`); | |||
| } else { | |||
| openDetailModal(pickOrder.consoCode); | |||
| @@ -210,31 +210,28 @@ const ConsolidatedPickOrders: React.FC<Props> = ({ filterArgs }) => { | |||
| [closeDetailModal] | |||
| ); | |||
| const onChange = useCallback( | |||
| ( | |||
| event: React.SyntheticEvent, | |||
| newValue: NameList | |||
| ) => { | |||
| console.log(newValue); | |||
| formProps.setValue("assignTo", newValue.id); | |||
| }, | |||
| [] | |||
| ); | |||
| const onChange = useCallback( | |||
| (event: React.SyntheticEvent, newValue: NameList) => { | |||
| console.log(newValue); | |||
| formProps.setValue("assignTo", newValue.id); | |||
| }, | |||
| [] | |||
| ); | |||
| const onSubmit = useCallback<SubmitHandler<ReleasePickOrderInputs & {}>>( | |||
| async (data, event) => { | |||
| console.log(data); | |||
| try { | |||
| const res = await releasePickOrder(data) | |||
| console.log(res) | |||
| if (res.status = 200) { | |||
| router.push(`/pickorder/detail?consoCode=${data.consoCode}`); | |||
| } else { | |||
| throw Error("hv error") | |||
| const res = await releasePickOrder(data); | |||
| console.log(res); | |||
| console.log(res.status); | |||
| if ((res.status === 200)) { | |||
| router.push(`/pickorder/detail?consoCode=${data.consoCode}`); | |||
| } else { | |||
| console.log(res); | |||
| } | |||
| } catch (error) { | |||
| console.log(error) | |||
| console.log(error); | |||
| } | |||
| }, | |||
| [releasePickOrder] | |||
| @@ -251,7 +248,7 @@ const ConsolidatedPickOrders: React.FC<Props> = ({ filterArgs }) => { | |||
| useEffect(() => { | |||
| if (consoCode) { | |||
| fetchConso(consoCode); | |||
| formProps.setValue("consoCode", consoCode) | |||
| formProps.setValue("consoCode", consoCode); | |||
| } | |||
| }, [consoCode]); | |||
| @@ -75,6 +75,7 @@ import ReactQrCodeScannerModal, { | |||
| import QrModal from "./QrModal"; | |||
| import { PlayArrow } from "@mui/icons-material"; | |||
| import DoneIcon from "@mui/icons-material/Done"; | |||
| import { QrCode } from "../QrCode"; | |||
| type Props = { | |||
| po: PoResult; | |||
| @@ -272,6 +273,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| // break; | |||
| } | |||
| }, [purchaseOrder, handleStartPo, handleCompletePo]); | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -283,7 +285,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| <Grid item> | |||
| <Typography mb={2} variant="h4"> | |||
| {/* {purchaseOrder.code} - {currPoStatus} */} | |||
| {purchaseOrder.code} - {t(`${purchaseOrder.status.toLowerCase()}`)} | |||
| {purchaseOrder.code} -{" "} | |||
| {t(`${purchaseOrder.status.toLowerCase()}`)} | |||
| </Typography> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -413,7 +413,6 @@ function PoInputGrid({ | |||
| renderCell: (params) => { | |||
| return t(`${params.row.status}`) | |||
| } | |||
| // editable: true, | |||
| }, | |||
| { | |||
| field: "actions", | |||
| @@ -423,10 +422,10 @@ function PoInputGrid({ | |||
| flex: 1.5, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| console.log(params.row.status); | |||
| // console.log(params.row.status); | |||
| const status = params.row.status.toLowerCase(); | |||
| console.log(stockInLineStatusMap[status]); | |||
| console.log(session?.user?.abilities?.includes("APPROVAL")); | |||
| // console.log(stockInLineStatusMap[status]); | |||
| // console.log(session?.user?.abilities?.includes("APPROVAL")); | |||
| return [ | |||
| <GridActionsCellItem | |||
| icon={<PlayArrowIcon />} | |||
| @@ -96,7 +96,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail, disabled }) => { | |||
| clearErrors(); | |||
| validateForm(); | |||
| }, [validateForm]); | |||
| // const [recordQty, setRecordQty] = useState(0); | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||