| @@ -2,6 +2,7 @@ import { cache } from "react"; | |||
| import "server-only"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| import { Uom } from "../settings/uom"; | |||
| export interface PoResult { | |||
| id: number | |||
| @@ -21,7 +22,7 @@ export interface PurchaseOrderLine { | |||
| itemName: string | |||
| qty: number | |||
| processed: number | |||
| uom?: string | |||
| uom: Uom | |||
| price: number | |||
| status: string | |||
| stockInLine: StockInLine[] | |||
| @@ -35,6 +36,7 @@ export interface StockInLine { | |||
| itemId: number | |||
| itemNo: string | |||
| itemName: string | |||
| itemType: string | |||
| demandQty: number | |||
| acceptedQty: number | |||
| price: number | |||
| @@ -45,6 +47,7 @@ export interface StockInLine { | |||
| supplier: string | |||
| lotNo: string | |||
| poCode: string | |||
| uom: Uom | |||
| } | |||
| export const fetchPoList = cache(async () => { | |||
| @@ -0,0 +1,20 @@ | |||
| import { cache } from "react"; | |||
| import "server-only"; | |||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { BASE_API_URL } from "@/config/api"; | |||
| export interface Uom { | |||
| id: number | |||
| code: string | |||
| name: string | |||
| unit1: string | |||
| unit1Qty: number | |||
| unit2?: string | |||
| unit2Qty: number | |||
| unit3?: string | |||
| unit3Qty: number | |||
| unit4?: string | |||
| unit4Qty: number | |||
| sizeInGram: number | |||
| gramPerSmallestUnit: number | |||
| } | |||
| @@ -1,3 +1,5 @@ | |||
| import { Uom } from "../api/settings/uom"; | |||
| export const manhourFormatter = new Intl.NumberFormat("en-HK", { | |||
| minimumFractionDigits: 2, | |||
| maximumFractionDigits: 2, | |||
| @@ -27,4 +29,13 @@ export const stockInLineStatusMap: { [status: string]: number } = { | |||
| "received": 7, | |||
| "completed": 8, | |||
| "rejected": 9, | |||
| }; | |||
| }; | |||
| export const calculateWeight = (qty: number, uom: Uom) => { | |||
| return qty * (uom.unit2Qty || 1) * (uom.unit3Qty || 1) * (uom.unit4Qty || 1); | |||
| } | |||
| export const returnWeightUnit = (uom: Uom) => { | |||
| return uom.unit4 || uom.unit3 || uom.unit2 || uom.unit1; | |||
| } | |||
| @@ -41,6 +41,7 @@ import PoInputGrid from "./PoInputGrid"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | |||
| type Props = { | |||
| po: PoResult; | |||
| @@ -65,7 +66,19 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| const { row } = props; | |||
| const [open, setOpen] = useState(false); | |||
| const [processedQty, setProcessedQty] = useState(row.processed); | |||
| const [currStatus, setCurrStatus] = useState(row.status); | |||
| useEffect(() => { | |||
| if (processedQty === row.qty) { | |||
| setCurrStatus("completed".toUpperCase()); | |||
| } else if (processedQty > 0) { | |||
| setCurrStatus("receiving".toUpperCase()); | |||
| } else { | |||
| setCurrStatus("pending".toUpperCase()); | |||
| } | |||
| }, [processedQty]); | |||
| const totalWeight = useMemo(() => calculateWeight(row.qty, row.uom), []); | |||
| const weightUnit = useMemo(() => returnWeightUnit(row.uom), []); | |||
| return ( | |||
| <> | |||
| <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | |||
| @@ -82,14 +95,18 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| <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.uom?.code}</TableCell> | |||
| <TableCell align="left"> | |||
| {totalWeight} {weightUnit} | |||
| </TableCell> | |||
| {/* <TableCell align="left">{weightUnit}</TableCell> */} | |||
| <TableCell align="left">{row.price}</TableCell> | |||
| {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | |||
| <TableCell align="left">{row.status}</TableCell> | |||
| <TableCell align="left">{currStatus}</TableCell> | |||
| </TableRow> | |||
| <TableRow> | |||
| {/* <TableCell /> */} | |||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0}} colSpan={12}> | |||
| {/* <TableCell /> */} | |||
| <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={12}> | |||
| <Collapse in={open} timeout="auto" unmountOnExit> | |||
| <Table> | |||
| <TableBody> | |||
| @@ -139,6 +156,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| <TableCell align="left">{t("qty")}</TableCell> | |||
| <TableCell align="left">processed</TableCell> | |||
| <TableCell align="left">{t("uom")}</TableCell> | |||
| <TableCell align="left">{t("total weight")}</TableCell> | |||
| {/* <TableCell align="left">{t("weight unit")}</TableCell> */} | |||
| <TableCell align="left">{t("price")}</TableCell> | |||
| {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | |||
| <TableCell align="left">{t("status")}</TableCell> | |||
| @@ -35,7 +35,7 @@ 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 { returnWeightUnit, calculateWeight, stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||
| import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| @@ -43,7 +43,7 @@ import LooksOneIcon from '@mui/icons-material/LooksOne'; | |||
| import LooksTwoIcon from '@mui/icons-material/LooksTwo'; | |||
| import Looks3Icon from '@mui/icons-material/Looks3'; | |||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
| import axios, { AxiosRequestConfig } from "axios"; | |||
| // import axios, { AxiosRequestConfig } from "axios"; | |||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
| import qs from 'qs'; | |||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | |||
| @@ -172,11 +172,12 @@ function PoInputGrid({ | |||
| const fetchQcDefaultValue = useCallback(async () => { | |||
| const authHeader = axiosInstance.defaults.headers['Authorization']; | |||
| if (!authHeader) { | |||
| return; // Exit the function if the token is not set | |||
| return; // Exit the function if the token is not set | |||
| } | |||
| const res = await axios.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`) | |||
| console.log(authHeader) | |||
| const res = await axiosInstance.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`) | |||
| console.log(res) | |||
| }, [axiosInstance, axios]) | |||
| }, [axiosInstance]) | |||
| const handleQC = useCallback( | |||
| (id: GridRowId, params: any) => async () => { | |||
| @@ -320,6 +321,24 @@ function PoInputGrid({ | |||
| editable: true, | |||
| // replace with tooltip + content | |||
| }, | |||
| { | |||
| field: "uom", | |||
| headerName: "uom", | |||
| flex: 0.5, | |||
| renderCell: (params) => { | |||
| return params.row.uom.code; | |||
| }, | |||
| }, | |||
| { | |||
| field: "weight", | |||
| headerName: "weight", | |||
| flex: 0.5, | |||
| renderCell: (params) => { | |||
| const weight = calculateWeight(params.row.acceptedQty, params.row.uom); | |||
| const weightUnit = returnWeightUnit(params.row.uom); | |||
| return `${weight} ${weightUnit}`; | |||
| }, | |||
| }, | |||
| { | |||
| field: "status", | |||
| flex: 0.5, | |||
| @@ -329,7 +348,7 @@ function PoInputGrid({ | |||
| field: "actions", | |||
| type: "actions", | |||
| headerName: "start | qc | escalation | stock in | putaway | delete", | |||
| flex: 1.5, | |||
| flex: 1, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| console.log(params.row.status); | |||
| @@ -357,9 +376,7 @@ function PoInputGrid({ | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={ | |||
| // stockInLineStatusMap[status] <= 0 || | |||
| // stockInLineStatusMap[status] >= 5 | |||
| stockInLineStatusMap[status] != 1 | |||
| stockInLineStatusMap[status] < 1 | |||
| } | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| @@ -405,7 +422,7 @@ function PoInputGrid({ | |||
| color: "primary.main", | |||
| // marginRight: 1, | |||
| }} | |||
| disabled={stockInLineStatusMap[status] !== 7} | |||
| disabled={stockInLineStatusMap[status] < 7} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handlePutAway(params.row.id, params)} | |||
| @@ -446,6 +463,7 @@ function PoInputGrid({ | |||
| ); | |||
| const addRow = useCallback(() => { | |||
| console.log(itemDetail) | |||
| const newEntry = { | |||
| id: Date.now(), | |||
| _isNew: true, | |||
| @@ -455,6 +473,7 @@ function PoInputGrid({ | |||
| itemNo: itemDetail.itemNo, | |||
| itemName: itemDetail.itemName, | |||
| acceptedQty: itemDetail.qty - currQty, // this bug | |||
| uom: itemDetail.uom, | |||
| status: "draft", | |||
| }; | |||
| setEntries((e) => [...e, newEntry]); | |||
| @@ -14,6 +14,7 @@ import { StockInLineRow } from "./PoInputGrid"; | |||
| import EscalationForm from "./EscalationForm"; | |||
| import StockInForm from "./StockInForm"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||
| setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | |||
| @@ -88,7 +89,6 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | |||
| async (data, event) => { | |||
| let hasErrors = false; | |||
| console.log("errors"); | |||
| console.log(errors); | |||
| console.log(data); | |||
| console.log(itemDetail); | |||
| @@ -105,14 +105,6 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| if (data.qcResult) { | |||
| acceptedQty = itemDetail.acceptedQty - data.qcResult.reduce((acc, curr) => acc + curr.failQty, 0) | |||
| } | |||
| console.log(acceptedQty) | |||
| // if (data.acceptedQty) { | |||
| // console.log("1") | |||
| // acceptedQty = parseInt(data.acceptedQty.toString()) | |||
| // } else { | |||
| // console.log("2") | |||
| // acceptedQty = data.sampleRate | |||
| // } | |||
| const args = { | |||
| id: itemDetail.id, | |||
| purchaseOrderId: parseInt(params.get("id")!!), | |||
| @@ -130,7 +122,7 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| } | |||
| const res = await updateStockInLine(args) | |||
| if (Boolean(res.id)) { | |||
| // set entries | |||
| // update entries | |||
| const newEntries = res.entity as StockInLine[] | |||
| setEntries((prev) => { | |||
| const updatedEntries = [...prev]; // Create a new array | |||
| @@ -149,7 +141,6 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| // add loading | |||
| closeHandler({}, "backdropClick") | |||
| } | |||
| console.log(res) | |||
| // if (res) | |||
| } catch (e) { | |||
| @@ -161,20 +152,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| [t, itemDetail] | |||
| ); | |||
| const canSubmit = useMemo(() => { | |||
| if (type === "qc") { | |||
| // console.log(itemDetail.status) | |||
| return formProps.formState.isValid | |||
| } | |||
| if (type === "stockIn") { | |||
| return formProps.formState.isValid | |||
| } | |||
| if (type === "putaway") { | |||
| return formProps.formState.isValid | |||
| } | |||
| return true | |||
| const renderSubmitButton = useMemo((): Boolean => { | |||
| if (itemDetail) { | |||
| const status = itemDetail.status | |||
| switch (type) { | |||
| case "qc": | |||
| return stockInLineStatusMap[status] === 1 | |||
| case "putaway": | |||
| return stockInLineStatusMap[status] === 7 | |||
| default: | |||
| return false; // Handle unexpected type | |||
| } | |||
| } else return false | |||
| }, [type, itemDetail]) | |||
| console.log(canSubmit) | |||
| renderSubmitButton | |||
| return ( | |||
| <> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| @@ -188,17 +179,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| {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" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| // disabled={submitDisabled} | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| {renderSubmitButton ? ( | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| // disabled={submitDisabled} | |||
| > | |||
| {t("submit")} | |||
| </Button> | |||
| </Stack> | |||
| ) : undefined | |||
| } | |||
| </Box> | |||
| </FormProvider> | |||
| </Modal> | |||
| @@ -35,6 +35,8 @@ 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"; | |||
| import { QRCodeSVG } from 'qrcode.react'; | |||
| import { QrCode } from "../QrCode"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| @@ -106,6 +108,15 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| [] | |||
| ); | |||
| const qrContent = useMemo(() => ({ | |||
| itemId: itemDetail.itemId, | |||
| lotNo: itemDetail.lotNo, | |||
| // expiryDate: itemDetail.expiryDate, | |||
| // productionDate: itemDetail.productionDate, | |||
| // supplier: itemDetail.supplier, | |||
| // poCode: itemDetail.poCode, | |||
| }),[itemDetail]) | |||
| useEffect(() => { | |||
| setValue("status", "completed") | |||
| }, []) | |||
| @@ -197,6 +208,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| max: itemDetail.acceptedQty, | |||
| valueAsNumber: true, | |||
| })} | |||
| defaultValue={itemDetail.acceptedQty} | |||
| error={Boolean(errors.acceptedQty)} | |||
| helperText={errors.acceptedQty?.message} | |||
| /> | |||
| @@ -215,6 +227,9 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={12} style={{ display: 'flex', justifyContent: 'center' }}> | |||
| <QrCode content={qrContent} sx={{width: 200, height: 200}}/> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| @@ -67,21 +67,21 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||
| console.log(itemDetail); | |||
| // const [qc, setQc] = useState<QcItemWithChecks[]>([]) | |||
| // const fetchQcCheck = useCallback(async () => { | |||
| // const authHeader = axiosInstance.defaults.headers['Authorization']; | |||
| // if (!authHeader) { | |||
| // return; // Exit the function if the token is not set | |||
| // } | |||
| // 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 fetchQcCheck = useCallback(async () => { | |||
| const authHeader = axiosInstance.defaults.headers['Authorization']; | |||
| if (!authHeader) { | |||
| return; // Exit the function if the token is not set | |||
| } | |||
| 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[]>( | |||
| @@ -0,0 +1,15 @@ | |||
| "use client" | |||
| import { QRCodeSVG } from 'qrcode.react'; | |||
| import { CSSProperties } from 'react'; | |||
| interface Props { | |||
| content: any; | |||
| sx?: CSSProperties; | |||
| } | |||
| export default function QrCode({ content, sx }: Props) { | |||
| console.log(content); | |||
| const jsonStr = JSON.stringify(content); | |||
| return <QRCodeSVG value={jsonStr} style={sx} />; | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| export {default as QrCode} from "./QrCode" | |||
| @@ -1,17 +1,21 @@ | |||
| import { fetchAllItems, } from "@/app/api/settings/item"; | |||
| import {RoughScheduleLoading} from "./RoughScheduleLoading"; | |||
| import { fetchAllItems } from "@/app/api/settings/item"; | |||
| import { RoughScheduleLoading } from "./RoughScheduleLoading"; | |||
| import RSOverview from "./RoughSchedileSearchView"; | |||
| interface SubComponents { | |||
| Loading: typeof RoughScheduleLoading; | |||
| Loading: typeof RoughScheduleLoading; | |||
| } | |||
| const RoughScheduleWrapper: ({}: {}) => Promise<JSX.Element> = async ({ | |||
| // type, | |||
| }) => { | |||
| // console.log(type) | |||
| var result = await fetchAllItems() | |||
| return <RSOverview records={result} />; | |||
| type Props = {}; | |||
| const RoughScheduleWrapper: React.FC<Props> & SubComponents = async ( | |||
| { | |||
| // type, | |||
| } | |||
| ) => { | |||
| // console.log(type) | |||
| var result = await fetchAllItems(); | |||
| return <RSOverview records={[]} />; | |||
| }; | |||
| RoughScheduleWrapper.Loading = RoughScheduleLoading; | |||