@@ -31,6 +31,7 @@ export interface PurchaseQcCheck { | |||
qty: number; | |||
} | |||
export interface StockInInput { | |||
status: string | |||
productLotNo?: string, | |||
receiptDate: string | |||
acceptedQty: number | |||
@@ -39,18 +40,23 @@ export interface StockInInput { | |||
expiryDate: string | |||
} | |||
export interface PurchaseQCInput { | |||
status: string | |||
sampleRate: number; | |||
sampleWeight: number; | |||
totalWeight: number; | |||
qcCheck: PurchaseQcCheck[]; | |||
} | |||
export interface EscalationInput { | |||
status: string | |||
handler: string | |||
stockInLine: StockInLineEntry[] | |||
} | |||
export interface PutawayInput { | |||
handler: string | |||
stockInLine: StockInLineEntry[] | |||
status: string | |||
acceptedQty: number | |||
warehouseId: number | |||
// handler: string | |||
// stockInLine: StockInLineEntry[] | |||
} | |||
export type ModalFormInput = Partial<PurchaseQCInput & StockInInput & EscalationInput & PutawayInput> | |||
@@ -20,6 +20,7 @@ export interface PurchaseOrderLine { | |||
itemNo: string | |||
itemName: string | |||
qty: number | |||
processed: number | |||
uom?: string | |||
price: number | |||
status: string | |||
@@ -0,0 +1,15 @@ | |||
"use server"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { revalidateTag } from "next/cache"; | |||
import { cache } from "react"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { QcItemWithChecks } from "."; | |||
export const fetchQcItemCheck = cache(async (itemId?: number) => { | |||
var url = `${BASE_API_URL}/qcCheck` | |||
if (itemId) url +=`/${itemId}` | |||
return serverFetchJson<QcItemWithChecks[]>(url, { | |||
next: { tags: ["qc"] }, | |||
}); | |||
}); | |||
@@ -1,12 +1,30 @@ | |||
import { cache } from "react"; | |||
import "server-only"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
export interface QcItemWithChecks { | |||
id: number; | |||
code: string; | |||
name: string; | |||
itemId: number; | |||
lowerLimit: number; | |||
upperLimit: number; | |||
description: string; | |||
lowerLimit: number | undefined; | |||
upperLimit: number | undefined; | |||
description: string | undefined; | |||
} | |||
export const fetchQcItemCheckList = cache(async () => { | |||
return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { | |||
next: { tags: ["qc"] }, | |||
}); | |||
}); | |||
export const fetchQcItemCheck = cache(async (itemId?: number) => { | |||
var url = `${BASE_API_URL}/qcCheck` | |||
if (itemId) url +=`/${itemId}` | |||
return serverFetchJson<QcItemWithChecks[]>(url, { | |||
next: { tags: ["qc"] }, | |||
}); | |||
}); |
@@ -1,7 +1,7 @@ | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { cache } from "react"; | |||
import "server-only"; | |||
import { serverFetchJson } from "src/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "src/config/api"; | |||
export interface TaskGroup { | |||
id: number; | |||
@@ -0,0 +1,17 @@ | |||
import { cache } from "react"; | |||
import "server-only"; | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
export interface WarehouseResult { | |||
id: number | |||
code: string | |||
name: string | |||
description: string | |||
} | |||
export const fetchWarehouseList = cache(async () => { | |||
return serverFetchJson<WarehouseResult[]>(`${BASE_API_URL}/warehouse`, { | |||
next: { tags: ["warehouse"] }, | |||
}); | |||
}); |
@@ -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, useEffect, useMemo } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
@@ -31,6 +31,7 @@ import QcSelect from "./QcSelect"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
@@ -144,6 +145,12 @@ const EscalationForm: React.FC<Props> = ({ | |||
}, | |||
[] | |||
); | |||
useEffect(() => { | |||
console.log("triggered") | |||
// setValue("status", stockInLineStatusMap.determine1.key) | |||
}, []) | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
@@ -1,6 +1,11 @@ | |||
"use client"; | |||
import { fetchPoWithStockInLines, PoResult, PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||
import { | |||
fetchPoWithStockInLines, | |||
PoResult, | |||
PurchaseOrderLine, | |||
StockInLine, | |||
} from "@/app/api/po"; | |||
import { | |||
Box, | |||
Button, | |||
@@ -35,10 +40,12 @@ import InputDataGrid, { | |||
import PoInputGrid from "./PoInputGrid"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { useSearchParams } from "next/navigation"; | |||
import { WarehouseResult } from "@/app/api/warehouse"; | |||
type Props = { | |||
po: PoResult; | |||
qc: QcItemWithChecks[] | |||
qc: QcItemWithChecks[]; | |||
warehouse: WarehouseResult[]; | |||
}; | |||
type EntryError = | |||
@@ -48,20 +55,17 @@ type EntryError = | |||
| undefined; | |||
// type PolRow = TableRow<Partial<StockInLine>, EntryError>; | |||
const PoDetail: React.FC<Props> = ({ | |||
po, | |||
// poLine, | |||
qc | |||
}) => { | |||
const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
const { t } = useTranslation(); | |||
const apiRef = useGridApiRef(); | |||
const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | |||
const params = useSearchParams() | |||
const params = useSearchParams(); | |||
function Row(props: { row: PurchaseOrderLine }) { | |||
const { row } = props; | |||
const [open, setOpen] = useState(false); | |||
const [processedQty, setProcessedQty] = useState(row.processed); | |||
return ( | |||
<> | |||
<TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | |||
@@ -77,30 +81,30 @@ 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">{processedQty}</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> | |||
</TableRow> | |||
<TableRow> | |||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> | |||
{/* <TableCell /> */} | |||
<TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={12}> | |||
<Collapse in={open} timeout="auto" unmountOnExit> | |||
<Table> | |||
<TableBody> | |||
<TableRow> | |||
{/* <Button | |||
onClick={()=> { | |||
console.log(row) | |||
console.log(row.stockInLine) | |||
}} | |||
>console log</Button> */} | |||
<TableCell> | |||
<PoInputGrid | |||
qc={qc} | |||
setRows={setRows} | |||
itemDetail={row} | |||
stockInLine={row.stockInLine} | |||
/> | |||
<TableCell align="right"> | |||
<Box> | |||
<PoInputGrid | |||
qc={qc} | |||
setRows={setRows} | |||
setProcessedQty={setProcessedQty} | |||
itemDetail={row} | |||
stockInLine={row.stockInLine} | |||
warehouse={warehouse} | |||
/> | |||
</Box> | |||
</TableCell> | |||
</TableRow> | |||
</TableBody> | |||
@@ -133,6 +137,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">processed</TableCell> | |||
<TableCell align="left">{t("uom")}</TableCell> | |||
<TableCell align="left">{t("price")}</TableCell> | |||
{/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | |||
@@ -8,6 +8,8 @@ import { fetchPoWithStockInLines, PoResult } from "@/app/api/po"; | |||
import PoDetailLoading from "./PoDetailLoading"; | |||
import PoDetail from "./PoDetail"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { fetchWarehouseList } from "@/app/api/warehouse"; | |||
import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||
interface SubComponents { | |||
Loading: typeof PoDetailLoading; | |||
@@ -19,35 +21,22 @@ type Props = { | |||
const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||
const [ | |||
poWithStockInLine | |||
poWithStockInLine, | |||
warehouse, | |||
qc, | |||
] = await Promise.all([ | |||
fetchPoWithStockInLines(id) | |||
fetchPoWithStockInLines(id), | |||
fetchWarehouseList(), | |||
fetchQcItemCheck() | |||
]) | |||
// const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
console.log(poWithStockInLine) | |||
const qc: QcItemWithChecks[] = [ // just qc | |||
{ | |||
id: 1, | |||
code: "code1", | |||
name: "name1", | |||
itemId: 1, | |||
lowerLimit: 1, | |||
upperLimit: 3, | |||
description: 'desc', | |||
}, | |||
{ | |||
id: 2, | |||
code: "code2", | |||
name: "name2", | |||
itemId: 1, | |||
lowerLimit: 1, | |||
upperLimit: 3, | |||
description: 'desc', | |||
}, | |||
] | |||
console.log(warehouse) | |||
console.log(qc) | |||
return <PoDetail po={poWithStockInLine} qc={qc} />; | |||
return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse}/>; | |||
}; | |||
PoDetailWrapper.Loading = PoDetailLoading; | |||
@@ -37,7 +37,8 @@ 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'; | |||
import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; | |||
import { WarehouseResult } from "@/app/api/warehouse"; | |||
interface ResultWithId { | |||
id: number; | |||
} | |||
@@ -45,8 +46,10 @@ interface ResultWithId { | |||
interface Props { | |||
qc: QcItemWithChecks[]; | |||
setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||
setProcessedQty: Dispatch<SetStateAction<number>>; | |||
itemDetail: PurchaseOrderLine; | |||
stockInLine: StockInLine[]; | |||
warehouse: WarehouseResult[]; | |||
} | |||
export type StockInLineEntryError = { | |||
@@ -64,7 +67,11 @@ export type StockInLineRow = Partial< | |||
class ProcessRowUpdateError extends Error { | |||
public readonly row: StockInLineRow; | |||
public readonly errors: StockInLineEntryError | undefined; | |||
constructor(row: StockInLineRow, message?: string, errors?: StockInLineEntryError) { | |||
constructor( | |||
row: StockInLineRow, | |||
message?: string, | |||
errors?: StockInLineEntryError | |||
) { | |||
super(message); | |||
this.row = row; | |||
this.errors = errors; | |||
@@ -73,7 +80,14 @@ class ProcessRowUpdateError extends Error { | |||
} | |||
} | |||
function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
function PoInputGrid({ | |||
qc, | |||
setRows, | |||
setProcessedQty, | |||
itemDetail, | |||
stockInLine, | |||
warehouse, | |||
}: Props) { | |||
const { t } = useTranslation("home"); | |||
const apiRef = useGridApiRef(); | |||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||
@@ -83,25 +97,32 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
); | |||
console.log(stockInLine); | |||
const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||
const [modalInfo, setModalInfo] = useState<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 [currQty, setCurrQty] = useState(() => { | |||
const total = entries.reduce( | |||
(acc, curr) => acc + (curr.acceptedQty || 0), | |||
0 | |||
); | |||
return 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]) | |||
useEffect(() => { | |||
}, []) | |||
useEffect(() => { | |||
const completedList = entries.filter( | |||
(e) => e.status === stockInLineStatusMap.completed.key | |||
); | |||
const processedQty = completedList.reduce( | |||
(acc, curr) => acc + (curr.acceptedQty || 0), | |||
0 | |||
); | |||
setProcessedQty(processedQty); | |||
}, [entries]); | |||
const handleDelete = useCallback( | |||
(id: GridRowId) => () => { | |||
@@ -119,8 +140,8 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
// post stock in line | |||
console.log("delayed"); | |||
console.log(params); | |||
const oldId = params.row.id | |||
console.log(oldId) | |||
const oldId = params.row.id; | |||
console.log(oldId); | |||
const postData = { | |||
itemId: params.row.itemId, | |||
itemNo: params.row.itemNo, | |||
@@ -128,10 +149,12 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
purchaseOrderId: params.row.purchaseOrderId, | |||
purchaseOrderLineId: params.row.purchaseOrderLineId, | |||
acceptedQty: params.row.acceptedQty, | |||
} | |||
const res = await createStockInLine(postData) | |||
console.log(res) | |||
setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity as StockInLine : p)) | |||
}; | |||
const res = await createStockInLine(postData); | |||
console.log(res); | |||
setEntries((prev) => | |||
prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)) | |||
); | |||
// do post directly to test | |||
// openStartModal(); | |||
}, 200); | |||
@@ -144,7 +167,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
setModalInfo(params.row) | |||
setModalInfo(params.row); | |||
setTimeout(() => { | |||
// open qc modal | |||
console.log("delayed"); | |||
@@ -153,31 +176,31 @@ 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 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, params: any) => () => { | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
setModalInfo(params.row) | |||
setModalInfo(params.row); | |||
setTimeout(() => { | |||
// open stock in modal | |||
openStockInModal() | |||
openStockInModal(); | |||
// return the record with its status as pending | |||
// update layout | |||
console.log("delayed"); | |||
@@ -192,10 +215,10 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
setModalInfo(params.row) | |||
setModalInfo(params.row); | |||
setTimeout(() => { | |||
// open stock in modal | |||
openPutAwayModal() | |||
openPutAwayModal(); | |||
// return the record with its status as pending | |||
// update layout | |||
console.log("delayed"); | |||
@@ -236,7 +259,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
() => [ | |||
{ | |||
field: "itemNo", | |||
flex: .8, | |||
flex: 0.8, | |||
}, | |||
{ | |||
field: "itemName", | |||
@@ -253,24 +276,24 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
{ | |||
field: "status", | |||
flex: 0.5, | |||
editable: true | |||
// editable: true, | |||
}, | |||
{ | |||
field: "actions", | |||
type: "actions", | |||
headerName: "start | qc | escalate | stock in | putaway | delete", | |||
headerName: "start | qc | stock in | putaway | delete", | |||
flex: 1.5, | |||
cellClassName: "actions", | |||
getActions: (params) => { | |||
console.log(params.row.status); | |||
const status = params.row.status.toLowerCase() | |||
const status = params.row.status.toLowerCase(); | |||
return [ | |||
<GridActionsCellItem | |||
icon={<PlayArrowIcon />} | |||
label="start" | |||
sx={{ | |||
color: "primary.main", | |||
marginRight: 2 | |||
marginRight: 2, | |||
}} | |||
disabled={!(stockInLineStatusMap[status].value === 0)} | |||
// set _isNew to false after posting | |||
@@ -284,35 +307,41 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
label="qc" | |||
sx={{ | |||
color: "primary.main", | |||
marginRight: 2 | |||
marginRight: 2, | |||
}} | |||
disabled={stockInLineStatusMap[status].value <= 0 || stockInLineStatusMap[status].value >= 5} | |||
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={<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 | |||
marginRight: 2, | |||
}} | |||
disabled={stockInLineStatusMap[status].value !== 6} | |||
// set _isNew to false after posting | |||
@@ -326,7 +355,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
label="putaway" | |||
sx={{ | |||
color: "primary.main", | |||
marginRight: 2 | |||
marginRight: 2, | |||
}} | |||
disabled={stockInLineStatusMap[status].value !== 7} | |||
// set _isNew to false after posting | |||
@@ -363,7 +392,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
purchaseOrderLineId: itemDetail.id, | |||
itemNo: itemDetail.itemNo, | |||
itemName: itemDetail.itemName, | |||
acceptedQty: defaultQty, | |||
acceptedQty: itemDetail.qty - currQty, // this bug | |||
status: "draft", | |||
}; | |||
setEntries((e) => [...e, newEntry]); | |||
@@ -374,7 +403,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
// fieldToFocus: "projectId", | |||
}, | |||
})); | |||
}, [getRowId]); | |||
}, [currQty, getRowId]); | |||
const validation = useCallback( | |||
( | |||
newRow: GridRowModel<StockInLineRow> | |||
@@ -382,16 +411,19 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
): StockInLineEntryError | undefined => { | |||
const error: StockInLineEntryError = {}; | |||
console.log(newRow); | |||
console.log(defaultQty); | |||
if (newRow.acceptedQty && newRow.acceptedQty > defaultQty) { | |||
console.log(currQty); | |||
if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) { | |||
error["acceptedQty"] = "qty cannot be greater than remaining qty"; | |||
} | |||
return Object.keys(error).length > 0 ? error : undefined; | |||
}, | |||
[defaultQty] | |||
[currQty] | |||
); | |||
const processRowUpdate = useCallback( | |||
(newRow: GridRowModel<StockInLineRow>, originalRow: GridRowModel<StockInLineRow>) => { | |||
( | |||
newRow: GridRowModel<StockInLineRow>, | |||
originalRow: GridRowModel<StockInLineRow> | |||
) => { | |||
const errors = validation(newRow); // change to validation | |||
if (errors) { | |||
throw new ProcessRowUpdateError( | |||
@@ -409,8 +441,11 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
); | |||
setEntries(newEntries); | |||
//update remaining qty | |||
const total = newEntries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||
setDefaultQty(itemDetail.qty - total); | |||
const total = newEntries.reduce( | |||
(acc, curr) => acc + (curr.acceptedQty || 0), | |||
0 | |||
); | |||
setCurrQty(total); | |||
return rowToSave; | |||
}, | |||
[getRowId, entries] | |||
@@ -426,17 +461,20 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
[apiRef] | |||
); | |||
useEffect(() => { | |||
const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||
setDefaultQty(itemDetail.qty - total); | |||
}, [entries]); | |||
// useEffect(() => { | |||
// const total = entries.reduce( | |||
// (acc, curr) => acc + (curr.acceptedQty || 0), | |||
// 0 | |||
// ); | |||
// setDefaultQty(itemDetail.qty - total); | |||
// }, [entries]); | |||
const footer = ( | |||
<Box display="flex" gap={2} alignItems="center"> | |||
<Button | |||
disableRipple | |||
variant="outlined" | |||
startIcon={<Add />} | |||
disabled={defaultQty <= 0} | |||
disabled={itemDetail.qty - currQty <= 0} | |||
onClick={addRow} | |||
size="small" | |||
> | |||
@@ -469,6 +507,13 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
processRowUpdate={processRowUpdate} | |||
onProcessRowUpdateError={onProcessRowUpdateError} | |||
columns={columns} | |||
isCellEditable={(params) => { | |||
const status = params.row.status.toLowerCase(); | |||
return ( | |||
stockInLineStatusMap[status].value >= 0 || | |||
stockInLineStatusMap[status].value <= 1 | |||
); | |||
}} | |||
getCellClassName={(params: GridCellParams<StockInLineRow>) => { | |||
let classname = ""; | |||
if (params.row._error) { | |||
@@ -512,14 +557,14 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
open={stockInOpen} | |||
onClose={closeStockInModal} | |||
itemDetail={modalInfo!!} | |||
/> | |||
/> | |||
</> | |||
<> | |||
<PoQcStockInModal | |||
type={"putaway"} | |||
setEntries={setEntries} | |||
open={putAwayOpen} | |||
warehouse={[]} | |||
warehouse={warehouse} | |||
onClose={closePutAwayModal} | |||
itemDetail={modalInfo!!} | |||
/> | |||
@@ -14,6 +14,7 @@ import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { StockInLineRow } from "./PoInputGrid"; | |||
import EscalationForm from "./EscalationForm"; | |||
import StockInForm from "./StockInForm"; | |||
import PutawayForm from "./PutawayForm"; | |||
interface CommonProps extends Omit<ModalProps, "children"> { | |||
setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | |||
@@ -86,20 +87,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
}, []); | |||
// 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 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) => { | |||
@@ -108,10 +109,6 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
console.log(data); | |||
console.log(itemDetail); | |||
try { | |||
console.log(type) | |||
var status = getPostingStatus(type) | |||
// if escalation, take data.status as status | |||
console.log(status) | |||
// add checking | |||
// const qty = data.sampleRate | |||
@@ -134,11 +131,10 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
...data, | |||
acceptedQty: acceptedQty, | |||
productionDate: productionDate, | |||
status: status, | |||
} as StockInLineEntry & ModalFormInput; | |||
////////////////////////////////////////////////////////////////////// | |||
console.log(args) | |||
// return | |||
return | |||
if (hasErrors) { | |||
setServerError(t("An error has occurred. Please try again later.")); | |||
return false; | |||
@@ -188,6 +184,7 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
{type === "qc" && <QcForm qc={qc} itemDetail={itemDetail} />} | |||
{type === "stockIn" && <StockInForm itemDetail={itemDetail} />} | |||
{type === "escalation" && <EscalationForm itemDetail={itemDetail} />} | |||
{type === "putaway" && <PutawayForm itemDetail={itemDetail} warehouse={warehouse} />} | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
name="submit" | |||
@@ -1,10 +1,12 @@ | |||
"use client"; | |||
import { PurchaseQcCheck, PurchaseQCInput } from "@/app/api/po/actions"; | |||
import { PurchaseQcCheck, PutawayInput } from "@/app/api/po/actions"; | |||
import { | |||
Autocomplete, | |||
Box, | |||
Card, | |||
CardContent, | |||
FormControl, | |||
Grid, | |||
Stack, | |||
TextField, | |||
@@ -14,7 +16,7 @@ import { | |||
import { useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
@@ -31,10 +33,13 @@ import QcSelect from "./QcSelect"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { WarehouseResult } from "@/app/api/warehouse"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
qc: QcItemWithChecks[]; | |||
warehouse: WarehouseResult[]; | |||
// qc: QcItemWithChecks[]; | |||
} | |||
type EntryError = | |||
| { | |||
@@ -42,12 +47,9 @@ type EntryError = | |||
} | |||
| undefined; | |||
type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||
// type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||
const PutawayForm: React.FC<Props> = ({ | |||
qc, | |||
itemDetail, | |||
}) => { | |||
const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
const { t } = useTranslation(); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
@@ -61,97 +63,52 @@ const PutawayForm: React.FC<Props> = ({ | |||
resetField, | |||
setError, | |||
clearErrors, | |||
} = useFormContext<PurchaseQCInput>(); | |||
console.log(itemDetail) | |||
const [recordQty, setRecordQty] = useState(0) | |||
const columns = useMemo<GridColDef[]>( | |||
() => [ | |||
} = useFormContext<PutawayInput>(); | |||
console.log(itemDetail); | |||
const [recordQty, setRecordQty] = useState(0); | |||
const [warehouseId, setWarehouseId] = useState(0); | |||
const filteredWarehouse = useMemo(() => { | |||
// do filtering here if any | |||
return warehouse; | |||
}, []); | |||
const options = useMemo(() => { | |||
return [ | |||
{ | |||
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 | |||
); | |||
}, | |||
value: -1, // think think sin | |||
label: t("Select warehouse"), | |||
group: "default", | |||
}, | |||
], | |||
[] | |||
); | |||
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; | |||
...filteredWarehouse.map((w) => ({ | |||
value: w.id, | |||
label: `${w.code} - ${w.name}`, | |||
group: "existing", | |||
})), | |||
]; | |||
}, [filteredWarehouse]); | |||
const currentValue = | |||
warehouseId > 0 | |||
? options.find((o) => o.value === warehouseId) | |||
: options.find((o) => o.value === getValues("warehouseId")) || options[0]; | |||
const onChange = useCallback( | |||
( | |||
event: React.SyntheticEvent, | |||
newValue: { value: number; group: string } | { value: number }[] | |||
) => { | |||
const singleNewVal = newValue as { | |||
value: number; | |||
group: string; | |||
}; | |||
console.log(singleNewVal); | |||
setValue("warehouseId", singleNewVal.value); | |||
setWarehouseId(singleNewVal.value); | |||
}, | |||
[] | |||
); | |||
useEffect(() => { | |||
setValue("status", stockInLineStatusMap.completed.key) | |||
}, []) | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
@@ -172,58 +129,40 @@ const PutawayForm: React.FC<Props> = ({ | |||
fullWidth | |||
value={itemDetail.acceptedQty} | |||
disabled | |||
// {...register("sampleRate", { | |||
// required: "sampleRate required!", | |||
// })} | |||
// error={Boolean(errors.sampleRate)} | |||
// helperText={errors.sampleRate?.message} | |||
/> | |||
</Grid> | |||
</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")} | |||
label={t("acceptedQty")} | |||
fullWidth | |||
{...register("sampleWeight", { | |||
required: "sampleWeight required!", | |||
{...register("acceptedQty", { | |||
required: "acceptedQty required!", | |||
})} | |||
error={Boolean(errors.sampleWeight)} | |||
helperText={errors.sampleWeight?.message} | |||
error={Boolean(errors.acceptedQty)} | |||
helperText={errors.acceptedQty?.message} | |||
/> | |||
</Grid> | |||
<Grid item xs={4}> | |||
<TextField | |||
label={t("totalWeight")} | |||
fullWidth | |||
{...register("totalWeight", { | |||
required: "totalWeight required!", | |||
})} | |||
error={Boolean(errors.totalWeight)} | |||
helperText={errors.totalWeight?.message} | |||
/> | |||
<FormControl fullWidth> | |||
<Autocomplete | |||
noOptionsText={t("No Warehouse")} | |||
disableClearable | |||
fullWidth | |||
value={currentValue} | |||
onChange={onChange} | |||
getOptionLabel={(option) => option.label} | |||
options={options} | |||
renderInput={(params) => <TextField {...params} />} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
</Grid> | |||
<Grid | |||
@@ -233,15 +172,15 @@ const PutawayForm: React.FC<Props> = ({ | |||
spacing={2} | |||
sx={{ mt: 0.5 }} | |||
> | |||
<Grid item xs={12}> | |||
<InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||
{/* <Grid item xs={12}> | |||
<InputDataGrid<PutawayInput, PurchaseQcCheck, EntryError> | |||
apiRef={apiRef} | |||
checkboxSelection={false} | |||
_formKey={"qcCheck"} | |||
columns={columns} | |||
validateRow={validation} | |||
/> | |||
</Grid> | |||
</Grid> */} | |||
</Grid> | |||
</Grid> | |||
); | |||
@@ -14,7 +14,7 @@ import { | |||
import { useFormContext } from "react-hook-form"; | |||
import { useTranslation } from "react-i18next"; | |||
import StyledDataGrid from "../StyledDataGrid"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
@@ -28,9 +28,13 @@ 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"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import axios from "@/app/(main)/axios/axiosInstance"; | |||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
@@ -43,11 +47,8 @@ type EntryError = | |||
| undefined; | |||
type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||
const QcForm: React.FC<Props> = ({ | |||
qc, | |||
itemDetail, | |||
}) => { | |||
// fetchQcItemCheck | |||
const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
const { t } = useTranslation(); | |||
const apiRef = useGridApiRef(); | |||
const { | |||
@@ -62,8 +63,21 @@ const QcForm: React.FC<Props> = ({ | |||
setError, | |||
clearErrors, | |||
} = useFormContext<PurchaseQCInput>(); | |||
console.log(itemDetail) | |||
const [recordQty, setRecordQty] = useState(0) | |||
console.log(itemDetail); | |||
// const [qc, setQc] = useState<QcItemWithChecks[]>([]) | |||
// const fetchQcCheck = useCallback(async () => { | |||
// const params = { | |||
// itemId: itemDetail.itemId | |||
// } | |||
// console.log(params) | |||
// const res = await axios.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcCheck`, { params }) | |||
// console.log(res) | |||
// }, [axios]) | |||
// useEffect(() => { | |||
// fetchQcCheck() | |||
// }, [fetchQcCheck]) | |||
const [recordQty, setRecordQty] = useState(0); | |||
const columns = useMemo<GridColDef[]>( | |||
() => [ | |||
{ | |||
@@ -133,7 +147,7 @@ const QcForm: React.FC<Props> = ({ | |||
}, | |||
}, | |||
], | |||
[] | |||
[qc] | |||
); | |||
const validation = useCallback( | |||
(newRow: GridRowModel<PoQcRow>): EntryError => { | |||
@@ -152,6 +166,18 @@ const QcForm: React.FC<Props> = ({ | |||
}, | |||
[] | |||
); | |||
useEffect(() => { | |||
console.log(itemDetail) | |||
var status = stockInLineStatusMap.receiving.key | |||
// switch (itemDetail.status) { | |||
// case 'pending': | |||
// status = "receiving" | |||
// break; | |||
// } | |||
setValue("status", status) | |||
}, [itemDetail]) | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||
@@ -178,7 +204,7 @@ const QcForm: React.FC<Props> = ({ | |||
// error={Boolean(errors.sampleRate)} | |||
// helperText={errors.sampleRate?.message} | |||
/> | |||
</Grid> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Total record qty")} | |||
@@ -191,7 +217,7 @@ const QcForm: React.FC<Props> = ({ | |||
// error={Boolean(errors.sampleRate)} | |||
// helperText={errors.sampleRate?.message} | |||
/> | |||
</Grid> | |||
</Grid> | |||
<Grid item xs={4}> | |||
<TextField | |||
label={t("sampleRate")} | |||
@@ -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, useEffect, useMemo } from "react"; | |||
import { | |||
GridColDef, | |||
GridRowIdGetter, | |||
@@ -31,6 +31,7 @@ import QcSelect from "./QcSelect"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { GridEditInputCell } from "@mui/x-data-grid"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
// change PurchaseQcCheck to stock in entry props | |||
interface Props { | |||
itemDetail: StockInLine; | |||
@@ -64,6 +65,11 @@ const StockInForm: React.FC<Props> = ({ | |||
} = useFormContext<StockInInput>(); | |||
console.log(itemDetail) | |||
useEffect(() => { | |||
console.log("triggered") | |||
setValue("status", stockInLineStatusMap.received.key) | |||
}, []) | |||
return ( | |||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
<Grid item xs={12}> | |||