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