Ver código fonte

update po stock in flow related

create_edit_user
MSI\derek 3 meses atrás
pai
commit
1781fe8ab8
12 arquivos alterados com 928 adições e 71 exclusões
  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 Ver arquivo

@@ -16,7 +16,6 @@ export interface PostStockInLiineResponse<T> {
entity: StockInLine | StockInLine[]
}


export interface StockInLineEntry {
id?: number
itemId: number
@@ -24,19 +23,37 @@ export interface StockInLineEntry {
purchaseOrderLineId: number
acceptedQty: number
status?: string
expiryDate?: string
}

export interface PurchaseQcCheck {
qcCheckId: number;
qty: number;
}

export interface StockInInput {
productLotNo?: string,
receiptDate: string
acceptedQty: number
acceptedWeight?: number
productionDate?: string
expiryDate: string
}
export interface PurchaseQCInput {
sampleRate: number;
sampleWeight: number;
totalWeight: number;
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) => {
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, {
@@ -53,8 +70,8 @@ export const createStockInLine = async (data: StockInLineEntry) => {
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",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },


+ 1
- 0
src/app/api/po/index.ts Ver arquivo

@@ -20,6 +20,7 @@ export interface PurchaseOrderLine {
itemNo: string
itemName: string
qty: number
uom?: string
price: number
status: string
stockInLine: StockInLine[]


+ 2
- 1
src/app/utils/fetchUtil.ts Ver arquivo

@@ -45,7 +45,8 @@ export const serverFetch: typeof fetch = async (input, init) => {
...(accessToken
? {
Authorization: `Bearer ${accessToken}`,
Accept:
"application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, multipart/form-data",
}
: {}),
},


+ 10
- 9
src/app/utils/formatUtil.ts Ver arquivo

@@ -8,13 +8,14 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", {
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 Ver arquivo

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

@@ -77,7 +77,7 @@ const PoDetail: React.FC<Props> = ({
<TableCell align="left">{row.itemNo}</TableCell>
<TableCell align="left">{row.itemName}</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.expiryDate}</TableCell> */}
<TableCell align="left">{row.status}</TableCell>
@@ -133,6 +133,7 @@ const PoDetail: React.FC<Props> = ({
<TableCell>{t("itemNo")}</TableCell>
<TableCell align="left">{t("itemName")}</TableCell>
<TableCell align="left">{t("qty")}</TableCell>
<TableCell align="left">{t("uom")}</TableCell>
<TableCell align="left">{t("price")}</TableCell>
{/* <TableCell align="left">{t("expiryDate")}</TableCell> */}
<TableCell align="left">{t("status")}</TableCell>


+ 142
- 30
src/components/PoDetail/PoInputGrid.tsx Ver arquivo

@@ -30,14 +30,14 @@ import DeleteIcon from "@mui/icons-material/Delete";
import CancelIcon from "@mui/icons-material/Cancel";
import FactCheckIcon from "@mui/icons-material/FactCheck";
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
import PoQcModal from "./PoQcModal";
import { QcItemWithChecks } from "src/app/api/qc";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import { PurchaseOrderLine, StockInLine } from "@/app/api/po";
import { createStockInLine, testFetch } from "@/app/api/po/actions";
import { useSearchParams } from "next/navigation";
import { stockInLineStatusMap } from "@/app/utils/formatUtil";

import PoQcStockInModal from "./PoQcStockInModal";
import NotificationImportantIcon from '@mui/icons-material/NotificationImportant';
interface ResultWithId {
id: number;
}
@@ -85,19 +85,23 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
const [modalInfo, setModalInfo] = useState<StockInLine>()
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 total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0);
return itemDetail.qty - total;
});

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(
(id: GridRowId) => () => {
@@ -127,7 +131,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
}
const res = await createStockInLine(postData)
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
// openStartModal();
}, 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(
(id: GridRowId) => () => {
(id: GridRowId, params: any) => () => {
setRowModesModel((prev) => ({
...prev,
[id]: { mode: GridRowModes.View },
}));
setModalInfo(params.row)
setTimeout(() => {
// 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
// update layout
console.log("delayed");
@@ -172,11 +211,32 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
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[]>(
() => [
{
field: "itemNo",
flex: 1,
flex: .8,
},
{
field: "itemName",
@@ -198,20 +258,10 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
{
field: "actions",
type: "actions",
headerName: "start | qc | stock in | delete",
flex: 1,
headerName: "start | qc | escalate | stock in | putaway | delete",
flex: 1.5,
cellClassName: "actions",
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);
const status = params.row.status.toLowerCase()
return [
@@ -220,8 +270,9 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
label="start"
sx={{
color: "primary.main",
marginRight: 2
}}
disabled={!(stockInLineStatusMap[status] === 0)}
disabled={!(stockInLineStatusMap[status].value === 0)}
// set _isNew to false after posting
// or check status
onClick={handleStart(params.row.id, params)}
@@ -233,24 +284,54 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
label="qc"
sx={{
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
// or check status
onClick={handleQC(params.row.id, params)}
color="inherit"
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
icon={<ShoppingCartIcon />}
label="stockin"
sx={{
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
// or check status
onClick={handleStockIn(params.row.id)}
onClick={handlePutAway(params.row.id, params)}
color="inherit"
key="edit"
/>,
@@ -260,7 +341,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
sx={{
color: "error.main",
}}
disabled={stockInLineStatusMap[status] !== 0}
disabled={stockInLineStatusMap[status].value !== 0}
// disabled={Boolean(params.row.status)}
onClick={handleDelete(params.row.id)}
color="inherit"
@@ -404,7 +485,8 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
}}
/>
<>
<PoQcModal
<PoQcStockInModal
type={"qc"}
setEntries={setEntries}
qc={qc}
open={qcOpen}
@@ -412,6 +494,36 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) {
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 Ver arquivo

@@ -1,8 +1,8 @@
"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 { 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 { useTranslation } from "react-i18next";
import QcForm from "./QcForm";
@@ -12,24 +12,36 @@ import { StockInLine } from "@/app/api/po";
import { useSearchParams } from "next/navigation";
import { stockInLineStatusMap } from "@/app/utils/formatUtil";
import { StockInLineRow } from "./PoInputGrid";
// type:
import EscalationForm from "./EscalationForm";
import StockInForm from "./StockInForm";

interface CommonProps extends Omit<ModalProps, "children"> {
setEntries: Dispatch<SetStateAction<StockInLineRow[]>>
itemDetail: StockInLine;
qc?: QcItemWithChecks[];
warehouse?: any[];
type: "qc" | "stockIn" | "escalation" | "putaway"
}
interface QcProps extends CommonProps {
qc: QcItemWithChecks[];
type: "qc"
}
interface StockInProps extends CommonProps {
// naming
type: "stockIn"
}
interface PutawayProps extends CommonProps {
// naming
// 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 = {
position: "absolute",
@@ -42,7 +54,8 @@ const style = {
pb: 10,
width: { xs: "80%", sm: "80%", md: "80%" },
};
const PoQcModal: React.FC<Props> = ({
const PoQcStockInModal: React.FC<Props> = ({
type,
setEntries,
open,
onClose,
@@ -56,7 +69,7 @@ const PoQcModal: React.FC<Props> = ({
const params = useSearchParams()
console.log(params.get("id"))
const [defaultValues, setDefaultValues] = useState({});
const formProps = useForm<PurchaseQCInput>({
const formProps = useForm<ModalFormInput>({
defaultValues: defaultValues ? defaultValues : {},
});
const errors = formProps.formState.errors;
@@ -72,32 +85,84 @@ const PoQcModal: React.FC<Props> = ({
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) => {
let hasErrors = false;
console.log(errors);
console.log(data);
console.log(itemDetail);
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,
purchaseOrderId: parseInt(params.get("id")!!),
purchaseOrderLineId: itemDetail.purchaseOrderLineId,
itemId: itemDetail.itemId,
acceptedQty: itemDetail.acceptedQty,
status: "receiving",
}
...data,
acceptedQty: acceptedQty,
productionDate: productionDate,
status: status,
} as StockInLineEntry & ModalFormInput;
//////////////////////////////////////////////////////////////////////
console.log(args)
// return
if (hasErrors) {
setServerError(t("An error has occurred. Please try again later."));
return false;
}
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)
@@ -110,7 +175,7 @@ const PoQcModal: React.FC<Props> = ({
},
[t, itemDetail]
);
return (
<>
<Modal open={open} onClose={closeHandler}>
@@ -120,7 +185,9 @@ const PoQcModal: React.FC<Props> = ({
component="form"
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}>
<Button
name="submit"
@@ -138,4 +205,4 @@ const PoQcModal: React.FC<Props> = ({
</>
);
};
export default PoQcModal;
export default PoQcStockInModal;

+ 0
- 0
src/components/PoDetail/PoStockInModal.tsx Ver arquivo


+ 249
- 0
src/components/PoDetail/PutawayForm.tsx Ver arquivo

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

@@ -14,7 +14,7 @@ import {
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import { useCallback, useMemo } from "react";
import { useCallback, useMemo, useState } from "react";
import {
GridColDef,
GridRowIdGetter,
@@ -63,6 +63,7 @@ const QcForm: React.FC<Props> = ({
clearErrors,
} = useFormContext<PurchaseQCInput>();
console.log(itemDetail)
const [recordQty, setRecordQty] = useState(0)
const columns = useMemo<GridColDef[]>(
() => [
{
@@ -115,6 +116,10 @@ const QcForm: React.FC<Props> = ({
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} />;
@@ -130,7 +135,7 @@ const QcForm: React.FC<Props> = ({
],
[]
);
const validationTest = useCallback(
const validation = useCallback(
(newRow: GridRowModel<PoQcRow>): EntryError => {
const error: EntryError = {};
const { qcCheckId, qty } = newRow;
@@ -140,6 +145,9 @@ const QcForm: React.FC<Props> = ({
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;
},
[]
@@ -158,6 +166,32 @@ const QcForm: React.FC<Props> = ({
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")}
@@ -205,7 +239,7 @@ const QcForm: React.FC<Props> = ({
checkboxSelection={false}
_formKey={"qcCheck"}
columns={columns}
validateRow={validationTest}
validateRow={validation}
/>
</Grid>
</Grid>


+ 168
- 0
src/components/PoDetail/StockInForm.tsx Ver arquivo

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

Carregando…
Cancelar
Salvar