浏览代码

update po stock in flow related

create_edit_user
MSI\derek 3 个月前
父节点
当前提交
1781fe8ab8
共有 12 个文件被更改,包括 928 次插入71 次删除
  1. +21
    -4
      src/app/api/po/actions.ts
  2. +1
    -0
      src/app/api/po/index.ts
  3. +2
    -1
      src/app/utils/fetchUtil.ts
  4. +10
    -9
      src/app/utils/formatUtil.ts
  5. +206
    -0
      src/components/PoDetail/EscalationForm.tsx
  6. +2
    -1
      src/components/PoDetail/PoDetail.tsx
  7. +142
    -30
      src/components/PoDetail/PoInputGrid.tsx
  8. +90
    -23
      src/components/PoDetail/PoQcStockInModal.tsx
  9. +0
    -0
      src/components/PoDetail/PoStockInModal.tsx
  10. +249
    -0
      src/components/PoDetail/PutawayForm.tsx
  11. +37
    -3
      src/components/PoDetail/QcForm.tsx
  12. +168
    -0
      src/components/PoDetail/StockInForm.tsx

+ 21
- 4
src/app/api/po/actions.ts 查看文件

@@ -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" },


+ 1
- 0
src/app/api/po/index.ts 查看文件

@@ -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[]


+ 2
- 1
src/app/utils/fetchUtil.ts 查看文件

@@ -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",
} }
: {}), : {}),
}, },


+ 10
- 9
src/app/utils/formatUtil.ts 查看文件

@@ -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 },
}; };

+ 206
- 0
src/components/PoDetail/EscalationForm.tsx 查看文件

@@ -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;

+ 2
- 1
src/components/PoDetail/PoDetail.tsx 查看文件

@@ -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>


+ 142
- 30
src/components/PoDetail/PoInputGrid.tsx 查看文件

@@ -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!!}
/>
</>
</> </>
); );
} }


src/components/PoDetail/PoQcModal.tsx → src/components/PoDetail/PoQcStockInModal.tsx 查看文件

@@ -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
src/components/PoDetail/PoStockInModal.tsx 查看文件


+ 249
- 0
src/components/PoDetail/PutawayForm.tsx 查看文件

@@ -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;

+ 37
- 3
src/components/PoDetail/QcForm.tsx 查看文件

@@ -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>


+ 168
- 0
src/components/PoDetail/StockInForm.tsx 查看文件

@@ -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;

正在加载...
取消
保存