| @@ -16,7 +16,6 @@ export interface PostStockInLiineResponse<T> { | |||||
| entity: StockInLine | StockInLine[] | entity: StockInLine | StockInLine[] | ||||
| } | } | ||||
| export interface StockInLineEntry { | export interface StockInLineEntry { | ||||
| id?: number | id?: number | ||||
| itemId: number | itemId: number | ||||
| @@ -24,19 +23,37 @@ export interface StockInLineEntry { | |||||
| purchaseOrderLineId: number | purchaseOrderLineId: number | ||||
| acceptedQty: number | acceptedQty: number | ||||
| status?: string | status?: string | ||||
| expiryDate?: string | |||||
| } | } | ||||
| export interface PurchaseQcCheck { | export interface PurchaseQcCheck { | ||||
| qcCheckId: number; | qcCheckId: number; | ||||
| qty: number; | qty: number; | ||||
| } | } | ||||
| export interface StockInInput { | |||||
| productLotNo?: string, | |||||
| receiptDate: string | |||||
| acceptedQty: number | |||||
| acceptedWeight?: number | |||||
| productionDate?: string | |||||
| expiryDate: string | |||||
| } | |||||
| export interface PurchaseQCInput { | export interface PurchaseQCInput { | ||||
| sampleRate: number; | sampleRate: number; | ||||
| sampleWeight: number; | sampleWeight: number; | ||||
| totalWeight: number; | totalWeight: number; | ||||
| qcCheck: PurchaseQcCheck[]; | qcCheck: PurchaseQcCheck[]; | ||||
| } | } | ||||
| export interface EscalationInput { | |||||
| handler: string | |||||
| stockInLine: StockInLineEntry[] | |||||
| } | |||||
| export interface PutawayInput { | |||||
| handler: string | |||||
| stockInLine: StockInLineEntry[] | |||||
| } | |||||
| export type ModalFormInput = Partial<PurchaseQCInput & StockInInput & EscalationInput & PutawayInput> | |||||
| export const testFetch = cache(async (id: number) => { | export const testFetch = cache(async (id: number) => { | ||||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | ||||
| @@ -53,8 +70,8 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||||
| return stockInLine | return stockInLine | ||||
| } | } | ||||
| export const updateStockInLine = async (data: StockInLineEntry) => { | |||||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/update`, { | |||||
| export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) => { | |||||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry & ModalFormInput>>(`${BASE_API_URL}/stockInLine/update`, { | |||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| @@ -20,6 +20,7 @@ export interface PurchaseOrderLine { | |||||
| itemNo: string | itemNo: string | ||||
| itemName: string | itemName: string | ||||
| qty: number | qty: number | ||||
| uom?: string | |||||
| price: number | price: number | ||||
| status: string | status: string | ||||
| stockInLine: StockInLine[] | stockInLine: StockInLine[] | ||||
| @@ -45,7 +45,8 @@ export const serverFetch: typeof fetch = async (input, init) => { | |||||
| ...(accessToken | ...(accessToken | ||||
| ? { | ? { | ||||
| Authorization: `Bearer ${accessToken}`, | Authorization: `Bearer ${accessToken}`, | ||||
| Accept: | |||||
| "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, multipart/form-data", | |||||
| } | } | ||||
| : {}), | : {}), | ||||
| }, | }, | ||||
| @@ -8,13 +8,14 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { | |||||
| currency: "HKD", | currency: "HKD", | ||||
| }); | }); | ||||
| export const stockInLineStatusMap: { [status: string]: number } = { | |||||
| draft: 0, | |||||
| pending: 1, | |||||
| qc: 2, | |||||
| determine1: 3, | |||||
| determine2: 4, | |||||
| determine3: 5, | |||||
| receiving: 6, | |||||
| completed: 7, | |||||
| export const stockInLineStatusMap: { [status: string]: {key: string, value: number} } = { | |||||
| draft: { key: "draft", value: 0 }, | |||||
| pending: { key: "pending", value: 1 }, | |||||
| qc: { key: "qc", value: 2 }, | |||||
| determine1: { key: "determine1", value: 3 }, | |||||
| determine2: { key: "determine2", value: 4 }, | |||||
| determine3: { key: "determine3", value: 5 }, | |||||
| receiving: { key: "receiving", value: 6 }, | |||||
| received: { key: "received", value: 7 }, | |||||
| completed: { key: "completed", value: 8 }, | |||||
| }; | }; | ||||
| @@ -0,0 +1,206 @@ | |||||
| "use client"; | |||||
| import { StockInLineEntry, EscalationInput } 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, 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 TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/po"; | |||||
| interface Props { | |||||
| itemDetail: StockInLine; | |||||
| // qc: QcItemWithChecks[]; | |||||
| } | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof StockInLineEntry]?: string; | |||||
| } | |||||
| | undefined; | |||||
| type PoEscalationRow = TableRow<Partial<StockInLineEntry>, EntryError>; | |||||
| const EscalationForm: React.FC<Props> = ({ | |||||
| // qc, | |||||
| itemDetail, | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<EscalationInput>(); | |||||
| console.log(itemDetail) | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| // { | |||||
| // field: "qcCheckId", | |||||
| // headerName: "qc Check", | |||||
| // flex: 1, | |||||
| // editable: true, | |||||
| // valueFormatter(params) { | |||||
| // const row = params.id ? params.api.getRow<PoEscalationRow>(params.id) : null; | |||||
| // if (!row) { | |||||
| // return null; | |||||
| // } | |||||
| // const Qc = qc.find((q) => q.id === row.qcCheckId); | |||||
| // return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||||
| // }, | |||||
| // renderCell(params: GridRenderCellParams<PoEscalationRow, number>) { | |||||
| // console.log(params.value); | |||||
| // return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||||
| // }, | |||||
| // renderEditCell(params: GridRenderEditCellParams<PoEscalationRow, number>) { | |||||
| // const errorMessage = | |||||
| // params.row._error?.[params.field as keyof StockInLineEntry]; | |||||
| // console.log(errorMessage); | |||||
| // const content = ( | |||||
| // <QcSelect | |||||
| // allQcs={qc} | |||||
| // value={params.row.qcCheckId} | |||||
| // onQcSelect={async (qcCheckId) => { | |||||
| // await params.api.setEditCellValue({ | |||||
| // id: params.id, | |||||
| // field: "qcCheckId", | |||||
| // value: qcCheckId, | |||||
| // }); | |||||
| // }} | |||||
| // /> | |||||
| // ); | |||||
| // return errorMessage ? ( | |||||
| // <Tooltip title={t(errorMessage)}> | |||||
| // <Box width="100%">{content}</Box> | |||||
| // </Tooltip> | |||||
| // ) : ( | |||||
| // content | |||||
| // ); | |||||
| // }, | |||||
| // }, | |||||
| { | |||||
| field: "qty", | |||||
| headerName: "qty", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| type: "number", | |||||
| renderEditCell(params: GridRenderEditCellParams<PoEscalationRow>) { | |||||
| const errorMessage = | |||||
| params.row._error?.[params.field as keyof StockInLineEntry]; | |||||
| const content = <GridEditInputCell {...params} />; | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validationTest = useCallback( | |||||
| (newRow: GridRowModel<PoEscalationRow>): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| // const { qcCheckId, qty } = newRow; | |||||
| // if (!qcCheckId || qcCheckId <= 0) { | |||||
| // error["qcCheckId"] = "select qc"; | |||||
| // } | |||||
| // if (!qty || qty <= 0) { | |||||
| // error["qty"] = "enter a qty"; | |||||
| // } | |||||
| 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={4}> | |||||
| <TextField | |||||
| label={t("handler")} | |||||
| fullWidth | |||||
| {...register("handler", { | |||||
| required: "handler required!", | |||||
| })} | |||||
| error={Boolean(errors.handler)} | |||||
| helperText={errors.handler?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("total")} | |||||
| fullWidth | |||||
| // {...register("handler", { | |||||
| // required: "handler required!", | |||||
| // })} | |||||
| value={itemDetail.acceptedQty} | |||||
| disabled | |||||
| // error={Boolean(errors.handler)} | |||||
| // helperText={errors.handler?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| <InputDataGrid<EscalationInput, StockInLineEntry, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={false} | |||||
| _formKey={"stockInLine"} | |||||
| columns={columns} | |||||
| validateRow={validationTest} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default EscalationForm; | |||||
| @@ -77,7 +77,7 @@ const PoDetail: React.FC<Props> = ({ | |||||
| <TableCell align="left">{row.itemNo}</TableCell> | <TableCell align="left">{row.itemNo}</TableCell> | ||||
| <TableCell align="left">{row.itemName}</TableCell> | <TableCell align="left">{row.itemName}</TableCell> | ||||
| <TableCell align="left">{row.qty}</TableCell> | <TableCell align="left">{row.qty}</TableCell> | ||||
| {/* <TableCell align="left">{row.uom}</TableCell> */} | |||||
| <TableCell align="left">{row.uom}</TableCell> | |||||
| <TableCell align="left">{row.price}</TableCell> | <TableCell align="left">{row.price}</TableCell> | ||||
| {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | ||||
| <TableCell align="left">{row.status}</TableCell> | <TableCell align="left">{row.status}</TableCell> | ||||
| @@ -133,6 +133,7 @@ const PoDetail: React.FC<Props> = ({ | |||||
| <TableCell>{t("itemNo")}</TableCell> | <TableCell>{t("itemNo")}</TableCell> | ||||
| <TableCell align="left">{t("itemName")}</TableCell> | <TableCell align="left">{t("itemName")}</TableCell> | ||||
| <TableCell align="left">{t("qty")}</TableCell> | <TableCell align="left">{t("qty")}</TableCell> | ||||
| <TableCell align="left">{t("uom")}</TableCell> | |||||
| <TableCell align="left">{t("price")}</TableCell> | <TableCell align="left">{t("price")}</TableCell> | ||||
| {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | ||||
| <TableCell align="left">{t("status")}</TableCell> | <TableCell align="left">{t("status")}</TableCell> | ||||
| @@ -30,14 +30,14 @@ import DeleteIcon from "@mui/icons-material/Delete"; | |||||
| import CancelIcon from "@mui/icons-material/Cancel"; | import CancelIcon from "@mui/icons-material/Cancel"; | ||||
| import FactCheckIcon from "@mui/icons-material/FactCheck"; | import FactCheckIcon from "@mui/icons-material/FactCheck"; | ||||
| import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; | ||||
| import PoQcModal from "./PoQcModal"; | |||||
| import { QcItemWithChecks } from "src/app/api/qc"; | import { QcItemWithChecks } from "src/app/api/qc"; | ||||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | ||||
| import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | ||||
| import { createStockInLine, testFetch } from "@/app/api/po/actions"; | import { createStockInLine, testFetch } from "@/app/api/po/actions"; | ||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||||
| import NotificationImportantIcon from '@mui/icons-material/NotificationImportant'; | |||||
| interface ResultWithId { | interface ResultWithId { | ||||
| id: number; | id: number; | ||||
| } | } | ||||
| @@ -85,19 +85,23 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | ||||
| const [modalInfo, setModalInfo] = useState<StockInLine>() | const [modalInfo, setModalInfo] = useState<StockInLine>() | ||||
| const [qcOpen, setQcOpen] = useState(false); | const [qcOpen, setQcOpen] = useState(false); | ||||
| const [escalOpen, setEscalOpen] = useState(false); | |||||
| const [stockInOpen, setStockInOpen] = useState(false); | |||||
| const [putAwayOpen, setPutAwayOpen] = useState(false); | |||||
| const [type, setType] = useState<"qc" | "stockIn">("qc"); | |||||
| const [defaultQty, setDefaultQty] = useState(() => { | const [defaultQty, setDefaultQty] = useState(() => { | ||||
| const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | ||||
| return itemDetail.qty - total; | return itemDetail.qty - total; | ||||
| }); | }); | ||||
| const params = useSearchParams() | const params = useSearchParams() | ||||
| const refetchData = useCallback(async () => { | |||||
| const id = parseInt(params.get("id")!!) | |||||
| const res = await testFetch(id) | |||||
| const pol = res.pol!! | |||||
| console.log(pol) | |||||
| setRows(pol); | |||||
| }, [params]) | |||||
| // const refetchData = useCallback(async () => { | |||||
| // const id = parseInt(params.get("id")!!) | |||||
| // const res = await testFetch(id) | |||||
| // const pol = res.pol!! | |||||
| // console.log(pol) | |||||
| // setRows(pol); | |||||
| // }, [params]) | |||||
| const handleDelete = useCallback( | const handleDelete = useCallback( | ||||
| (id: GridRowId) => () => { | (id: GridRowId) => () => { | ||||
| @@ -127,7 +131,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| } | } | ||||
| const res = await createStockInLine(postData) | const res = await createStockInLine(postData) | ||||
| console.log(res) | console.log(res) | ||||
| // setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity : p)) | |||||
| setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity as StockInLine : p)) | |||||
| // do post directly to test | // do post directly to test | ||||
| // openStartModal(); | // openStartModal(); | ||||
| }, 200); | }, 200); | ||||
| @@ -149,14 +153,49 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| }, | }, | ||||
| [] | [] | ||||
| ); | ); | ||||
| const handleEscalation = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setModalInfo(params.row) | |||||
| setTimeout(() => { | |||||
| // open qc modal | |||||
| console.log("delayed"); | |||||
| openEscalationModal() | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handleStockIn = useCallback( | const handleStockIn = useCallback( | ||||
| (id: GridRowId) => () => { | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| })); | })); | ||||
| setModalInfo(params.row) | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| // open stock in modal | // open stock in modal | ||||
| openStockInModal() | |||||
| // return the record with its status as pending | |||||
| // update layout | |||||
| console.log("delayed"); | |||||
| }, 200); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| const handlePutAway = useCallback( | |||||
| (id: GridRowId, params: any) => () => { | |||||
| setRowModesModel((prev) => ({ | |||||
| ...prev, | |||||
| [id]: { mode: GridRowModes.View }, | |||||
| })); | |||||
| setModalInfo(params.row) | |||||
| setTimeout(() => { | |||||
| // open stock in modal | |||||
| openPutAwayModal() | |||||
| // return the record with its status as pending | // return the record with its status as pending | ||||
| // update layout | // update layout | ||||
| console.log("delayed"); | console.log("delayed"); | ||||
| @@ -172,11 +211,32 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| setQcOpen(true); | setQcOpen(true); | ||||
| }, []); | }, []); | ||||
| const closeStockInModal = useCallback(() => { | |||||
| setStockInOpen(false); | |||||
| }, []); | |||||
| const openStockInModal = useCallback(() => { | |||||
| setStockInOpen(true); | |||||
| }, []); | |||||
| const closePutAwayModal = useCallback(() => { | |||||
| setPutAwayOpen(false); | |||||
| }, []); | |||||
| const openPutAwayModal = useCallback(() => { | |||||
| setPutAwayOpen(true); | |||||
| }, []); | |||||
| const closeEscalationModal = useCallback(() => { | |||||
| setEscalOpen(false); | |||||
| }, []); | |||||
| const openEscalationModal = useCallback(() => { | |||||
| setEscalOpen(true); | |||||
| }, []); | |||||
| const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
| () => [ | () => [ | ||||
| { | { | ||||
| field: "itemNo", | field: "itemNo", | ||||
| flex: 1, | |||||
| flex: .8, | |||||
| }, | }, | ||||
| { | { | ||||
| field: "itemName", | field: "itemName", | ||||
| @@ -198,20 +258,10 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| { | { | ||||
| field: "actions", | field: "actions", | ||||
| type: "actions", | type: "actions", | ||||
| headerName: "start | qc | stock in | delete", | |||||
| flex: 1, | |||||
| headerName: "start | qc | escalate | stock in | putaway | delete", | |||||
| flex: 1.5, | |||||
| cellClassName: "actions", | cellClassName: "actions", | ||||
| getActions: (params) => { | getActions: (params) => { | ||||
| // const stockInLineStatusMap: { [status: string]: number } = { | |||||
| // draft: 0, | |||||
| // pending: 1, | |||||
| // qc: 2, | |||||
| // determine1: 3, | |||||
| // determine2: 4, | |||||
| // determine3: 5, | |||||
| // receiving: 6, | |||||
| // completed: 7, | |||||
| // }; | |||||
| console.log(params.row.status); | console.log(params.row.status); | ||||
| const status = params.row.status.toLowerCase() | const status = params.row.status.toLowerCase() | ||||
| return [ | return [ | ||||
| @@ -220,8 +270,9 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| label="start" | label="start" | ||||
| sx={{ | sx={{ | ||||
| color: "primary.main", | color: "primary.main", | ||||
| marginRight: 2 | |||||
| }} | }} | ||||
| disabled={!(stockInLineStatusMap[status] === 0)} | |||||
| disabled={!(stockInLineStatusMap[status].value === 0)} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleStart(params.row.id, params)} | onClick={handleStart(params.row.id, params)} | ||||
| @@ -233,24 +284,54 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| label="qc" | label="qc" | ||||
| sx={{ | sx={{ | ||||
| color: "primary.main", | color: "primary.main", | ||||
| marginRight: 2 | |||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] <= 0 || stockInLineStatusMap[status] >= 6} | |||||
| disabled={stockInLineStatusMap[status].value <= 0 || stockInLineStatusMap[status].value >= 5} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleQC(params.row.id, params)} | onClick={handleQC(params.row.id, params)} | ||||
| color="inherit" | color="inherit" | ||||
| key="edit" | key="edit" | ||||
| />, | />, | ||||
| <GridActionsCellItem | |||||
| icon={<NotificationImportantIcon />} | |||||
| label="escalation" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| marginRight: 2 | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status].value <= 0 || stockInLineStatusMap[status].value >= 5} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleEscalation(params.row.id, params)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| <GridActionsCellItem | <GridActionsCellItem | ||||
| icon={<ShoppingCartIcon />} | icon={<ShoppingCartIcon />} | ||||
| label="stockin" | label="stockin" | ||||
| sx={{ | sx={{ | ||||
| color: "primary.main", | color: "primary.main", | ||||
| marginRight: 2 | |||||
| }} | |||||
| disabled={stockInLineStatusMap[status].value !== 6} | |||||
| // set _isNew to false after posting | |||||
| // or check status | |||||
| onClick={handleStockIn(params.row.id, params)} | |||||
| color="inherit" | |||||
| key="edit" | |||||
| />, | |||||
| <GridActionsCellItem | |||||
| icon={<ShoppingCartIcon />} | |||||
| label="putaway" | |||||
| sx={{ | |||||
| color: "primary.main", | |||||
| marginRight: 2 | |||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] !== 6} | |||||
| disabled={stockInLineStatusMap[status].value !== 7} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleStockIn(params.row.id)} | |||||
| onClick={handlePutAway(params.row.id, params)} | |||||
| color="inherit" | color="inherit" | ||||
| key="edit" | key="edit" | ||||
| />, | />, | ||||
| @@ -260,7 +341,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| sx={{ | sx={{ | ||||
| color: "error.main", | color: "error.main", | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] !== 0} | |||||
| disabled={stockInLineStatusMap[status].value !== 0} | |||||
| // disabled={Boolean(params.row.status)} | // disabled={Boolean(params.row.status)} | ||||
| onClick={handleDelete(params.row.id)} | onClick={handleDelete(params.row.id)} | ||||
| color="inherit" | color="inherit" | ||||
| @@ -404,7 +485,8 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| }} | }} | ||||
| /> | /> | ||||
| <> | <> | ||||
| <PoQcModal | |||||
| <PoQcStockInModal | |||||
| type={"qc"} | |||||
| setEntries={setEntries} | setEntries={setEntries} | ||||
| qc={qc} | qc={qc} | ||||
| open={qcOpen} | open={qcOpen} | ||||
| @@ -412,6 +494,36 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||||
| itemDetail={modalInfo!!} | itemDetail={modalInfo!!} | ||||
| /> | /> | ||||
| </> | </> | ||||
| <> | |||||
| <PoQcStockInModal | |||||
| type={"escalation"} | |||||
| setEntries={setEntries} | |||||
| // qc={qc} | |||||
| open={escalOpen} | |||||
| onClose={closeEscalationModal} | |||||
| itemDetail={modalInfo!!} | |||||
| /> | |||||
| </> | |||||
| <> | |||||
| <PoQcStockInModal | |||||
| type={"stockIn"} | |||||
| setEntries={setEntries} | |||||
| // qc={qc} | |||||
| open={stockInOpen} | |||||
| onClose={closeStockInModal} | |||||
| itemDetail={modalInfo!!} | |||||
| /> | |||||
| </> | |||||
| <> | |||||
| <PoQcStockInModal | |||||
| type={"putaway"} | |||||
| setEntries={setEntries} | |||||
| open={putAwayOpen} | |||||
| warehouse={[]} | |||||
| onClose={closePutAwayModal} | |||||
| itemDetail={modalInfo!!} | |||||
| /> | |||||
| </> | |||||
| </> | </> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,8 +1,8 @@ | |||||
| "use client"; | "use client"; | ||||
| import { PurchaseQCInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
| import { ModalFormInput, PurchaseQCInput, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
| import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | ||||
| import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; | |||||
| import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import QcForm from "./QcForm"; | import QcForm from "./QcForm"; | ||||
| @@ -12,24 +12,36 @@ import { StockInLine } from "@/app/api/po"; | |||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
| // type: | |||||
| import EscalationForm from "./EscalationForm"; | |||||
| import StockInForm from "./StockInForm"; | |||||
| interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
| setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | ||||
| itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
| qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
| warehouse?: any[]; | warehouse?: any[]; | ||||
| type: "qc" | "stockIn" | "escalation" | "putaway" | |||||
| } | } | ||||
| interface QcProps extends CommonProps { | interface QcProps extends CommonProps { | ||||
| qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
| type: "qc" | |||||
| } | } | ||||
| interface StockInProps extends CommonProps { | interface StockInProps extends CommonProps { | ||||
| // naming | // naming | ||||
| type: "stockIn" | |||||
| } | |||||
| interface PutawayProps extends CommonProps { | |||||
| // naming | |||||
| // warehouse: any[]; | |||||
| warehouse: any[]; | warehouse: any[]; | ||||
| type: "putaway" | |||||
| } | |||||
| interface EscalationProps extends CommonProps { | |||||
| // naming | |||||
| type: "escalation" | |||||
| } | } | ||||
| type Props = QcProps | StockInProps; | |||||
| type Props = QcProps | StockInProps | EscalationProps | PutawayProps; | |||||
| const style = { | const style = { | ||||
| position: "absolute", | position: "absolute", | ||||
| @@ -42,7 +54,8 @@ const style = { | |||||
| pb: 10, | pb: 10, | ||||
| width: { xs: "80%", sm: "80%", md: "80%" }, | width: { xs: "80%", sm: "80%", md: "80%" }, | ||||
| }; | }; | ||||
| const PoQcModal: React.FC<Props> = ({ | |||||
| const PoQcStockInModal: React.FC<Props> = ({ | |||||
| type, | |||||
| setEntries, | setEntries, | ||||
| open, | open, | ||||
| onClose, | onClose, | ||||
| @@ -56,7 +69,7 @@ const PoQcModal: React.FC<Props> = ({ | |||||
| const params = useSearchParams() | const params = useSearchParams() | ||||
| console.log(params.get("id")) | console.log(params.get("id")) | ||||
| const [defaultValues, setDefaultValues] = useState({}); | const [defaultValues, setDefaultValues] = useState({}); | ||||
| const formProps = useForm<PurchaseQCInput>({ | |||||
| const formProps = useForm<ModalFormInput>({ | |||||
| defaultValues: defaultValues ? defaultValues : {}, | defaultValues: defaultValues ? defaultValues : {}, | ||||
| }); | }); | ||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| @@ -72,32 +85,84 @@ const PoQcModal: React.FC<Props> = ({ | |||||
| setDefaultValues({}); | setDefaultValues({}); | ||||
| }, []); | }, []); | ||||
| const onSubmit = useCallback<SubmitHandler<PurchaseQCInput & {}>>( | |||||
| // status to be posted | |||||
| const getPostingStatus = useCallback( | |||||
| (type: string) => { | |||||
| switch (type) { | |||||
| case "qc": | |||||
| return stockInLineStatusMap.receiving.key; | |||||
| case "stockIn": | |||||
| return stockInLineStatusMap.received.key; | |||||
| case "putaway": | |||||
| return stockInLineStatusMap.completed.key; | |||||
| default: | |||||
| return stockInLineStatusMap.pending.key; | |||||
| } | |||||
| }, [] | |||||
| ) | |||||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | |||||
| async (data, event) => { | async (data, event) => { | ||||
| let hasErrors = false; | let hasErrors = false; | ||||
| console.log(errors); | console.log(errors); | ||||
| console.log(data); | console.log(data); | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| try { | try { | ||||
| if (hasErrors) { | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| return false; | |||||
| console.log(type) | |||||
| var status = getPostingStatus(type) | |||||
| // if escalation, take data.status as status | |||||
| console.log(status) | |||||
| // add checking | |||||
| // const qty = data.sampleRate | |||||
| //////////////////////// modify this mess later ////////////////////// | |||||
| var productionDate = null | |||||
| var acceptedQty = null | |||||
| if (data.productionDate && data.productionDate.length > 0) { | |||||
| productionDate = data.productionDate | |||||
| } | |||||
| if (data.acceptedQty) { | |||||
| acceptedQty = parseInt(data.acceptedQty.toString()) | |||||
| } else { | |||||
| acceptedQty = data.sampleRate | |||||
| } | } | ||||
| // do post update stock in line | |||||
| // const reqStatus = stockInLineStatusMap | |||||
| const args: StockInLineEntry = { | |||||
| const args = { | |||||
| id: itemDetail.id, | id: itemDetail.id, | ||||
| purchaseOrderId: parseInt(params.get("id")!!), | purchaseOrderId: parseInt(params.get("id")!!), | ||||
| purchaseOrderLineId: itemDetail.purchaseOrderLineId, | purchaseOrderLineId: itemDetail.purchaseOrderLineId, | ||||
| itemId: itemDetail.itemId, | itemId: itemDetail.itemId, | ||||
| acceptedQty: itemDetail.acceptedQty, | |||||
| status: "receiving", | |||||
| } | |||||
| ...data, | |||||
| acceptedQty: acceptedQty, | |||||
| productionDate: productionDate, | |||||
| status: status, | |||||
| } as StockInLineEntry & ModalFormInput; | |||||
| ////////////////////////////////////////////////////////////////////// | |||||
| console.log(args) | console.log(args) | ||||
| // return | |||||
| if (hasErrors) { | |||||
| setServerError(t("An error has occurred. Please try again later.")); | |||||
| return false; | |||||
| } | |||||
| const res = await updateStockInLine(args) | const res = await updateStockInLine(args) | ||||
| // this.res.entity = list of entity | |||||
| for (const inLine in res.entity as StockInLine[]) { | |||||
| if (Boolean(res.id)) { | |||||
| // set entries | |||||
| const newEntries = res.entity as StockInLine[] | |||||
| setEntries((prev) => { | |||||
| const updatedEntries = [...prev]; // Create a new array | |||||
| newEntries.forEach((item) => { | |||||
| const index = updatedEntries.findIndex(p => p.id === item.id); | |||||
| if (index !== -1) { | |||||
| // Update existing item | |||||
| updatedEntries[index] = item; | |||||
| } else { | |||||
| // Add new item | |||||
| updatedEntries.push(item); | |||||
| } | |||||
| }); | |||||
| return updatedEntries; // Return the new array | |||||
| }) | |||||
| // add loading | |||||
| closeHandler({}, "backdropClick") | |||||
| } | } | ||||
| console.log(res) | console.log(res) | ||||
| @@ -110,7 +175,7 @@ const PoQcModal: React.FC<Props> = ({ | |||||
| }, | }, | ||||
| [t, itemDetail] | [t, itemDetail] | ||||
| ); | ); | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Modal open={open} onClose={closeHandler}> | <Modal open={open} onClose={closeHandler}> | ||||
| @@ -120,7 +185,9 @@ const PoQcModal: React.FC<Props> = ({ | |||||
| component="form" | component="form" | ||||
| onSubmit={formProps.handleSubmit(onSubmit)} | onSubmit={formProps.handleSubmit(onSubmit)} | ||||
| > | > | ||||
| {qc && <QcForm qc={qc} itemDetail={itemDetail} />} | |||||
| {type === "qc" && <QcForm qc={qc} itemDetail={itemDetail} />} | |||||
| {type === "stockIn" && <StockInForm itemDetail={itemDetail} />} | |||||
| {type === "escalation" && <EscalationForm itemDetail={itemDetail} />} | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | <Button | ||||
| name="submit" | name="submit" | ||||
| @@ -138,4 +205,4 @@ const PoQcModal: React.FC<Props> = ({ | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default PoQcModal; | |||||
| export default PoQcStockInModal; | |||||
| @@ -0,0 +1,249 @@ | |||||
| "use client"; | |||||
| import { PurchaseQcCheck, 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, 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 TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/po"; | |||||
| interface Props { | |||||
| itemDetail: StockInLine; | |||||
| qc: QcItemWithChecks[]; | |||||
| } | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof PurchaseQcCheck]?: string; | |||||
| } | |||||
| | undefined; | |||||
| type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||||
| const PutawayForm: React.FC<Props> = ({ | |||||
| qc, | |||||
| itemDetail, | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<PurchaseQCInput>(); | |||||
| console.log(itemDetail) | |||||
| const [recordQty, setRecordQty] = useState(0) | |||||
| const columns = useMemo<GridColDef[]>( | |||||
| () => [ | |||||
| { | |||||
| field: "qcCheckId", | |||||
| headerName: "qc Check", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| 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.qcCheckId); | |||||
| 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 PurchaseQcCheck]; | |||||
| console.log(errorMessage); | |||||
| const content = ( | |||||
| <QcSelect | |||||
| allQcs={qc} | |||||
| value={params.row.qcCheckId} | |||||
| onQcSelect={async (qcCheckId) => { | |||||
| await params.api.setEditCellValue({ | |||||
| id: params.id, | |||||
| field: "qcCheckId", | |||||
| value: qcCheckId, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| ); | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| { | |||||
| field: "qty", | |||||
| headerName: "qty", | |||||
| flex: 1, | |||||
| editable: true, | |||||
| 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 PurchaseQcCheck]; | |||||
| const content = <GridEditInputCell {...params} />; | |||||
| return errorMessage ? ( | |||||
| <Tooltip title={t(errorMessage)}> | |||||
| <Box width="100%">{content}</Box> | |||||
| </Tooltip> | |||||
| ) : ( | |||||
| content | |||||
| ); | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| [] | |||||
| ); | |||||
| const validation = useCallback( | |||||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | |||||
| const error: EntryError = {}; | |||||
| const { qcCheckId, qty } = newRow; | |||||
| if (!qcCheckId || qcCheckId <= 0) { | |||||
| error["qcCheckId"] = "select qc"; | |||||
| } | |||||
| if (!qty || qty <= 0) { | |||||
| error["qty"] = "enter a qty"; | |||||
| } | |||||
| if (qty && qty > itemDetail.acceptedQty) { | |||||
| error["qty"] = "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={6}> | |||||
| <TextField | |||||
| label={t("Total qty")} | |||||
| fullWidth | |||||
| value={itemDetail.acceptedQty} | |||||
| disabled | |||||
| // {...register("sampleRate", { | |||||
| // required: "sampleRate required!", | |||||
| // })} | |||||
| // error={Boolean(errors.sampleRate)} | |||||
| // helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Total record qty")} | |||||
| fullWidth | |||||
| value={recordQty} | |||||
| disabled | |||||
| // {...register("sampleRate", { | |||||
| // required: "sampleRate required!", | |||||
| // })} | |||||
| // error={Boolean(errors.sampleRate)} | |||||
| // helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("sampleRate")} | |||||
| fullWidth | |||||
| {...register("sampleRate", { | |||||
| required: "sampleRate required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleRate)} | |||||
| helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("sampleWeight")} | |||||
| fullWidth | |||||
| {...register("sampleWeight", { | |||||
| required: "sampleWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.sampleWeight)} | |||||
| helperText={errors.sampleWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("totalWeight")} | |||||
| fullWidth | |||||
| {...register("totalWeight", { | |||||
| required: "totalWeight required!", | |||||
| })} | |||||
| error={Boolean(errors.totalWeight)} | |||||
| helperText={errors.totalWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| <Grid item xs={12}> | |||||
| <InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={false} | |||||
| _formKey={"qcCheck"} | |||||
| columns={columns} | |||||
| validateRow={validation} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default PutawayForm; | |||||
| @@ -14,7 +14,7 @@ import { | |||||
| import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
| import { useCallback, useMemo } from "react"; | |||||
| import { useCallback, useMemo, useState } from "react"; | |||||
| import { | import { | ||||
| GridColDef, | GridColDef, | ||||
| GridRowIdGetter, | GridRowIdGetter, | ||||
| @@ -63,6 +63,7 @@ const QcForm: React.FC<Props> = ({ | |||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<PurchaseQCInput>(); | } = useFormContext<PurchaseQCInput>(); | ||||
| console.log(itemDetail) | console.log(itemDetail) | ||||
| const [recordQty, setRecordQty] = useState(0) | |||||
| const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
| () => [ | () => [ | ||||
| { | { | ||||
| @@ -115,6 +116,10 @@ const QcForm: React.FC<Props> = ({ | |||||
| editable: true, | editable: true, | ||||
| type: "number", | type: "number", | ||||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | ||||
| // const recordQty = params.row.qty | |||||
| // if (recordQty !== undefined) { | |||||
| // setUnrecordQty((prev) => prev - recordQty) | |||||
| // } | |||||
| const errorMessage = | const errorMessage = | ||||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | params.row._error?.[params.field as keyof PurchaseQcCheck]; | ||||
| const content = <GridEditInputCell {...params} />; | const content = <GridEditInputCell {...params} />; | ||||
| @@ -130,7 +135,7 @@ const QcForm: React.FC<Props> = ({ | |||||
| ], | ], | ||||
| [] | [] | ||||
| ); | ); | ||||
| const validationTest = useCallback( | |||||
| const validation = useCallback( | |||||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | (newRow: GridRowModel<PoQcRow>): EntryError => { | ||||
| const error: EntryError = {}; | const error: EntryError = {}; | ||||
| const { qcCheckId, qty } = newRow; | const { qcCheckId, qty } = newRow; | ||||
| @@ -140,6 +145,9 @@ const QcForm: React.FC<Props> = ({ | |||||
| if (!qty || qty <= 0) { | if (!qty || qty <= 0) { | ||||
| error["qty"] = "enter a qty"; | error["qty"] = "enter a qty"; | ||||
| } | } | ||||
| if (qty && qty > itemDetail.acceptedQty) { | |||||
| error["qty"] = "qty too big"; | |||||
| } | |||||
| return Object.keys(error).length > 0 ? error : undefined; | return Object.keys(error).length > 0 ? error : undefined; | ||||
| }, | }, | ||||
| [] | [] | ||||
| @@ -158,6 +166,32 @@ const QcForm: React.FC<Props> = ({ | |||||
| spacing={2} | spacing={2} | ||||
| sx={{ mt: 0.5 }} | sx={{ mt: 0.5 }} | ||||
| > | > | ||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Total qty")} | |||||
| fullWidth | |||||
| value={itemDetail.acceptedQty} | |||||
| disabled | |||||
| // {...register("sampleRate", { | |||||
| // required: "sampleRate required!", | |||||
| // })} | |||||
| // error={Boolean(errors.sampleRate)} | |||||
| // helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Total record qty")} | |||||
| fullWidth | |||||
| value={recordQty} | |||||
| disabled | |||||
| // {...register("sampleRate", { | |||||
| // required: "sampleRate required!", | |||||
| // })} | |||||
| // error={Boolean(errors.sampleRate)} | |||||
| // helperText={errors.sampleRate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <TextField | <TextField | ||||
| label={t("sampleRate")} | label={t("sampleRate")} | ||||
| @@ -205,7 +239,7 @@ const QcForm: React.FC<Props> = ({ | |||||
| checkboxSelection={false} | checkboxSelection={false} | ||||
| _formKey={"qcCheck"} | _formKey={"qcCheck"} | ||||
| columns={columns} | columns={columns} | ||||
| validateRow={validationTest} | |||||
| validateRow={validation} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -0,0 +1,168 @@ | |||||
| "use client"; | |||||
| import { PurchaseQcCheck, PurchaseQCInput, StockInInput } 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, 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 TwoLineCell from "./TwoLineCell"; | |||||
| import QcSelect from "./QcSelect"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/po"; | |||||
| // change PurchaseQcCheck to stock in entry props | |||||
| interface Props { | |||||
| itemDetail: StockInLine; | |||||
| // qc: QcItemWithChecks[]; | |||||
| } | |||||
| type EntryError = | |||||
| | { | |||||
| [field in keyof StockInInput]?: string; | |||||
| } | |||||
| | undefined; | |||||
| // type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||||
| const StockInForm: React.FC<Props> = ({ | |||||
| // qc, | |||||
| itemDetail, | |||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | |||||
| const { | |||||
| register, | |||||
| formState: { errors, defaultValues, touchedFields }, | |||||
| watch, | |||||
| control, | |||||
| setValue, | |||||
| getValues, | |||||
| reset, | |||||
| resetField, | |||||
| setError, | |||||
| clearErrors, | |||||
| } = useFormContext<StockInInput>(); | |||||
| console.log(itemDetail) | |||||
| 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={4}> | |||||
| <TextField | |||||
| label={t("productLotNo")} | |||||
| fullWidth | |||||
| {...register("productLotNo", { | |||||
| // required: "productLotNo required!", | |||||
| })} | |||||
| // error={Boolean(errors.productLotNo)} | |||||
| // helperText={errors.productLotNo?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("receiptDate")} | |||||
| fullWidth | |||||
| {...register("receiptDate", { | |||||
| required: "receiptDate required!", | |||||
| })} | |||||
| error={Boolean(errors.receiptDate)} | |||||
| helperText={errors.receiptDate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("acceptedQty")} | |||||
| fullWidth | |||||
| {...register("acceptedQty", { | |||||
| required: "acceptedQty required!", | |||||
| })} | |||||
| error={Boolean(errors.acceptedQty)} | |||||
| helperText={errors.acceptedQty?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("acceptedWeight")} | |||||
| fullWidth | |||||
| // {...register("acceptedWeight", { | |||||
| // required: "acceptedWeight required!", | |||||
| // })} | |||||
| error={Boolean(errors.acceptedWeight)} | |||||
| helperText={errors.acceptedWeight?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("productionDate")} | |||||
| fullWidth | |||||
| {...register("productionDate", { | |||||
| // required: "productionDate required!", | |||||
| })} | |||||
| // error={Boolean(errors.productionDate)} | |||||
| // helperText={errors.productionDate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={4}> | |||||
| <TextField | |||||
| label={t("expiryDate")} | |||||
| fullWidth | |||||
| {...register("expiryDate", { | |||||
| required: "expiryDate required!", | |||||
| })} | |||||
| error={Boolean(errors.expiryDate)} | |||||
| helperText={errors.expiryDate?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <Grid | |||||
| container | |||||
| justifyContent="flex-start" | |||||
| alignItems="flex-start" | |||||
| spacing={2} | |||||
| sx={{ mt: 0.5 }} | |||||
| > | |||||
| {/* <Grid item xs={12}> | |||||
| <InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||||
| apiRef={apiRef} | |||||
| checkboxSelection={false} | |||||
| _formKey={"qcCheck"} | |||||
| columns={columns} | |||||
| validateRow={validationTest} | |||||
| /> | |||||
| </Grid> */} | |||||
| </Grid> | |||||
| </Grid> | |||||
| ); | |||||
| }; | |||||
| export default StockInForm; | |||||