# Conflicts: # src/components/PoDetail/PoDetail.tsxmaster
@@ -9,6 +9,7 @@ import { PoResult, StockInLine } from "."; | |||||
import { serverFetchJson } from "../../utils/fetchUtil"; | import { serverFetchJson } from "../../utils/fetchUtil"; | ||||
import { QcItemResult } from "../settings/qcItem"; | import { QcItemResult } from "../settings/qcItem"; | ||||
import { RecordsRes } from "../utils"; | import { RecordsRes } from "../utils"; | ||||
import { Uom } from "../settings/uom"; | |||||
// import { BASE_API_URL } from "@/config/api"; | // import { BASE_API_URL } from "@/config/api"; | ||||
export interface PostStockInLiineResponse<T> { | export interface PostStockInLiineResponse<T> { | ||||
@@ -44,18 +45,23 @@ export interface StockInInput { | |||||
itemName: string; | itemName: string; | ||||
invoiceNo?: string; | invoiceNo?: string; | ||||
receiptDate: string; | receiptDate: string; | ||||
supplier: string; | |||||
acceptedQty: number; | acceptedQty: number; | ||||
qty: number; | |||||
receivedQty: number; | |||||
acceptedWeight?: number; | acceptedWeight?: number; | ||||
productionDate?: string; | productionDate?: string; | ||||
expiryDate: string; | expiryDate: string; | ||||
uom: Uom; | |||||
} | } | ||||
export interface PurchaseQCInput { | export interface PurchaseQCInput { | ||||
status: string; | status: string; | ||||
acceptedQty: number; | |||||
acceptQty: number; | |||||
passingQty: number; | passingQty: number; | ||||
sampleRate: number; | sampleRate: number; | ||||
sampleWeight: number; | sampleWeight: number; | ||||
totalWeight: number; | totalWeight: number; | ||||
qcAccept: boolean; | |||||
qcResult: PurchaseQcResult[]; | qcResult: PurchaseQcResult[]; | ||||
} | } | ||||
export interface EscalationInput { | export interface EscalationInput { | ||||
@@ -28,6 +28,7 @@ export interface PurchaseOrderLine { | |||||
itemName: string; | itemName: string; | ||||
qty: number; | qty: number; | ||||
processed: number; | processed: number; | ||||
receivedQty: number; | |||||
uom: Uom; | uom: Uom; | ||||
price: number; | price: number; | ||||
status: string; | status: string; | ||||
@@ -45,6 +46,8 @@ export interface StockInLine { | |||||
itemType: string; | itemType: string; | ||||
demandQty: number; | demandQty: number; | ||||
acceptedQty: number; | acceptedQty: number; | ||||
qty: number; | |||||
processed: number; | |||||
price: number; | price: number; | ||||
priceUnit: string; | priceUnit: string; | ||||
shelfLife?: number; | shelfLife?: number; | ||||
@@ -73,6 +73,7 @@ export interface InputDataGridProps<T, V, E> { | |||||
columns: GridColDef[]; | columns: GridColDef[]; | ||||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | ||||
needAdd?: boolean; | needAdd?: boolean; | ||||
showRemoveBtn?: boolean; | |||||
} | } | ||||
export interface SelectionInputDataGridProps<T, V, E> { | export interface SelectionInputDataGridProps<T, V, E> { | ||||
@@ -109,6 +110,7 @@ function InputDataGrid<T, V, E>({ | |||||
columns, | columns, | ||||
validateRow, | validateRow, | ||||
needAdd, | needAdd, | ||||
showRemoveBtn = true, | |||||
}: Props<T, V, E>) { | }: Props<T, V, E>) { | ||||
const { | const { | ||||
t, | t, | ||||
@@ -303,7 +305,7 @@ function InputDataGrid<T, V, E>({ | |||||
新增 | 新增 | ||||
{/* {t("Add Record")} */} | {/* {t("Add Record")} */} | ||||
</Button> | </Button> | ||||
<Button | |||||
{showRemoveBtn && <Button | |||||
disableRipple | disableRipple | ||||
variant="outlined" | variant="outlined" | ||||
startIcon={<Add />} | startIcon={<Add />} | ||||
@@ -312,7 +314,7 @@ function InputDataGrid<T, V, E>({ | |||||
> | > | ||||
{/* {t("Clean Record")} */} | {/* {t("Clean Record")} */} | ||||
清除 | 清除 | ||||
</Button> | |||||
</Button>} | |||||
</Box> | </Box> | ||||
); | ); | ||||
// const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | // const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { | ||||
@@ -182,8 +182,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
const searchParams = useSearchParams(); | const searchParams = useSearchParams(); | ||||
const [row, setRow] = useState(rows[0]); | const [row, setRow] = useState(rows[0]); | ||||
const [stockInLine, setStockInLine] = useState<StockInLine[]>(rows[0].stockInLine); | |||||
const [processedQty, setProcessedQty] = useState(rows[0].processed); | |||||
const [stockInLine, setStockInLine] = useState<StockInLine[]>([]); | |||||
const [processedQty, setProcessedQty] = useState(0); | |||||
const router = useRouter(); | const router = useRouter(); | ||||
const [poList, setPoList] = useState<PoResult[]>([]); | const [poList, setPoList] = useState<PoResult[]>([]); | ||||
@@ -427,9 +428,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
<TableCell align="right">{decimalFormatter.format(row.price)}</TableCell> | <TableCell align="right">{decimalFormatter.format(row.price)}</TableCell> | ||||
{/* <TableCell align="left">{row.expiryDate}</TableCell> */} | {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | ||||
<TableCell align="left">{t(`${currStatus.toLowerCase()}`)}</TableCell> | <TableCell align="left">{t(`${currStatus.toLowerCase()}`)}</TableCell> | ||||
<TableCell align="right"> | |||||
0 | |||||
</TableCell> | |||||
<TableCell align="right">{integerFormatter.format(row.receivedQty)}</TableCell> | |||||
<TableCell align="center"> | <TableCell align="center"> | ||||
<TextField | <TextField | ||||
label="輸入來貨數量" | label="輸入來貨數量" | ||||
@@ -284,8 +284,8 @@ const closeNewModal = useCallback(() => { | |||||
// Button handler to update the URL and open the modal | // Button handler to update the URL and open the modal | ||||
const handleNewQC = useCallback( | const handleNewQC = useCallback( | ||||
(id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
console.log(id) | |||||
console.log(params) | |||||
// console.log(id) | |||||
// console.log(params) | |||||
setBtnIsLoading(true); | setBtnIsLoading(true); | ||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
...prev, | ...prev, | ||||
@@ -296,6 +296,7 @@ const closeNewModal = useCallback(() => { | |||||
setModalInfo({ | setModalInfo({ | ||||
...params.row, | ...params.row, | ||||
qcResult: qcResult, | qcResult: qcResult, | ||||
receivedQty: itemDetail.receivedQty, | |||||
}); | }); | ||||
setTimeout(() => { | setTimeout(() => { | ||||
@@ -499,7 +500,7 @@ const closeNewModal = useCallback(() => { | |||||
// }, | // }, | ||||
{ | { | ||||
field: "status", | field: "status", | ||||
headerName: t("status"), | |||||
headerName: t("Status"), | |||||
width: 70, | width: 70, | ||||
// flex: 0.5, | // flex: 0.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
@@ -499,6 +499,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||||
columns={columns} | columns={columns} | ||||
validateRow={validation} | validateRow={validation} | ||||
needAdd={true} | needAdd={true} | ||||
showRemoveBtn={false} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
@@ -6,7 +6,11 @@ import { | |||||
Card, | Card, | ||||
CardContent, | CardContent, | ||||
Checkbox, | Checkbox, | ||||
FormControl, | |||||
FormControlLabel, | |||||
Grid, | Grid, | ||||
Radio, | |||||
RadioGroup, | |||||
Stack, | Stack, | ||||
Tab, | Tab, | ||||
Tabs, | Tabs, | ||||
@@ -15,7 +19,7 @@ import { | |||||
Tooltip, | Tooltip, | ||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { useFormContext } from "react-hook-form"; | |||||
import { useFormContext, Controller } from "react-hook-form"; | |||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | ||||
@@ -46,6 +50,7 @@ import QcDataGrid from "./QCDatagrid"; | |||||
import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | ||||
import { ModalFormInput } from "@/app/api/dashboard/actions"; | import { ModalFormInput } from "@/app/api/dashboard/actions"; | ||||
import { escape } from "lodash"; | |||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
@@ -54,6 +59,7 @@ interface Props { | |||||
qcItems: QcData[] | qcItems: QcData[] | ||||
setQcItems: Dispatch<SetStateAction<QcData[]>> | setQcItems: Dispatch<SetStateAction<QcData[]>> | ||||
} | } | ||||
type EntryError = | type EntryError = | ||||
| { | | { | ||||
[field in keyof QcData]?: string; | [field in keyof QcData]?: string; | ||||
@@ -79,8 +85,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
} = useFormContext<PurchaseQCInput>(); | } = useFormContext<PurchaseQCInput>(); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>() | |||||
const [qcResult, setQcResult] = useState(dummyEscalationHistory) | |||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); | |||||
const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); | |||||
const [qcResult, setQcResult] = useState(); | |||||
const qcAccept = watch("qcAccept"); | |||||
// const [qcAccept, setQcAccept] = useState(true); | |||||
// const [qcItems, setQcItems] = useState(dummyQCData) | // const [qcItems, setQcItems] = useState(dummyQCData) | ||||
const column = useMemo<GridColDef[]>( | const column = useMemo<GridColDef[]>( | ||||
@@ -105,25 +114,25 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
); | ); | ||||
//// validate form | //// validate form | ||||
const accQty = watch("acceptedQty"); | |||||
const accQty = watch("acceptQty"); | |||||
const validateForm = useCallback(() => { | const validateForm = useCallback(() => { | ||||
console.log(accQty); | console.log(accQty); | ||||
if (accQty > itemDetail.acceptedQty) { | if (accQty > itemDetail.acceptedQty) { | ||||
setError("acceptedQty", { | |||||
message: `${t("acceptedQty must not greater than")} ${ | |||||
setError("acceptQty", { | |||||
message: `${t("acceptQty must not greater than")} ${ | |||||
itemDetail.acceptedQty | itemDetail.acceptedQty | ||||
}`, | }`, | ||||
type: "required", | type: "required", | ||||
}); | }); | ||||
} | } | ||||
if (accQty < 1) { | if (accQty < 1) { | ||||
setError("acceptedQty", { | |||||
setError("acceptQty", { | |||||
message: t("minimal value is 1"), | message: t("minimal value is 1"), | ||||
type: "required", | type: "required", | ||||
}); | }); | ||||
} | } | ||||
if (isNaN(accQty)) { | if (isNaN(accQty)) { | ||||
setError("acceptedQty", { | |||||
setError("acceptQty", { | |||||
message: t("value must be a number"), | message: t("value must be a number"), | ||||
type: "required", | type: "required", | ||||
}); | }); | ||||
@@ -137,77 +146,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
() => [ | () => [ | ||||
// { | |||||
// field: "qcItemId", | |||||
// headerName: t("qc Check"), | |||||
// flex: 1, | |||||
// editable: !disabled, | |||||
// valueFormatter(params) { | |||||
// const row = params.id ? params.api.getRow<QcRow>(params.id) : null; | |||||
// if (!row) { | |||||
// return null; | |||||
// } | |||||
// const Qc = qc.find((q) => q.id === row.qcItemId); | |||||
// return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||||
// }, | |||||
// renderCell(params: GridRenderCellParams<QcRow, number>) { | |||||
// console.log(params.value); | |||||
// return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||||
// }, | |||||
// renderEditCell(params: GridRenderEditCellParams<QcRow, number>) { | |||||
// const errorMessage = | |||||
// params.row._error?.[params.field as keyof PurchaseQcResult]; | |||||
// console.log(errorMessage); | |||||
// const content = ( | |||||
// <QcSelect | |||||
// allQcs={qc} | |||||
// value={params.row.qcItemId} | |||||
// onQcSelect={async (qcItemId) => { | |||||
// await params.api.setEditCellValue({ | |||||
// id: params.id, | |||||
// field: "qcItemId", | |||||
// value: qcItemId, | |||||
// }); | |||||
// // await params.api.setEditCellValue({ | |||||
// // id: params.id, | |||||
// // field: "type", | |||||
// // value: "determine1", | |||||
// // }); | |||||
// }} | |||||
// /> | |||||
// ); | |||||
// return errorMessage ? ( | |||||
// <Tooltip title={errorMessage}> | |||||
// <Box width="100%">{content}</Box> | |||||
// </Tooltip> | |||||
// ) : ( | |||||
// content | |||||
// ); | |||||
// }, | |||||
// }, | |||||
// { | |||||
// field: "failQty", | |||||
// headerName: t("failQty"), | |||||
// flex: 1, | |||||
// editable: !disabled, | |||||
// type: "number", | |||||
// renderEditCell(params: GridRenderEditCellParams<QcRow>) { | |||||
// // const recordQty = params.row.qty | |||||
// // if (recordQty !== undefined) { | |||||
// // setUnrecordQty((prev) => prev - recordQty) | |||||
// // } | |||||
// const errorMessage = | |||||
// params.row._error?.[params.field as keyof PurchaseQcResult]; | |||||
// const content = <GridEditInputCell {...params} />; | |||||
// return errorMessage ? ( | |||||
// <Tooltip title={t(errorMessage)}> | |||||
// <Box width="100%">{content}</Box> | |||||
// </Tooltip> | |||||
// ) : ( | |||||
// content | |||||
// ); | |||||
// }, | |||||
// }, | |||||
{ | { | ||||
field: "escalation", | field: "escalation", | ||||
headerName: t("escalation"), | headerName: t("escalation"), | ||||
@@ -247,56 +185,57 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
{ | { | ||||
field: "qcItem", | field: "qcItem", | ||||
headerName: t("qcItem"), | headerName: t("qcItem"), | ||||
flex: 1, | |||||
flex: 2, | |||||
renderCell: (params) => ( | |||||
<Box> | |||||
<b>{params.value}</b><br/> | |||||
{params.row.qcDescription}<br/> | |||||
</Box> | |||||
), | |||||
}, | }, | ||||
{ | { | ||||
field: 'isPassed', | field: 'isPassed', | ||||
headerName: t("passed"), | |||||
flex: 1, | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={!!params.value} | |||||
onClick={(e) => e.stopPropagation()} // avoid row selection | |||||
onMouseDown={(e) => e.stopPropagation()} // extra guard | |||||
onChange={(e) => { | |||||
const checked = e.target.checked; | |||||
setQcItems((prev) => | |||||
prev.map((r) => | |||||
r.id === params.id | |||||
? { ...r, isPassed: checked, isFailed: !checked ? r.isFailed : false } | |||||
: r | |||||
) | |||||
); | |||||
}} | |||||
size="small" | |||||
/> | |||||
), | |||||
headerName: t("qcResult"), | |||||
flex: 1.5, | |||||
renderCell: (params) => { | |||||
const currentValue = params.value; | |||||
return ( | |||||
<FormControl> | |||||
<RadioGroup | |||||
row | |||||
aria-labelledby="demo-radio-buttons-group-label" | |||||
value={currentValue === undefined ? "" : (currentValue ? "true" : "false")} | |||||
onChange={(e) => { | |||||
const value = e.target.value; | |||||
setQcItems((prev) => | |||||
prev.map((r): QcData => (r.id === params.id ? { ...r, isPassed: value === "true" } : r)) | |||||
); | |||||
}} | |||||
name={`isPassed-${params.id}`} | |||||
> | |||||
<FormControlLabel | |||||
value="true" | |||||
control={<Radio />} | |||||
label="合格" | |||||
sx={{ | |||||
color: currentValue === true ? "green" : "inherit", | |||||
"& .Mui-checked": {color: "green"} | |||||
}} | |||||
/> | |||||
<FormControlLabel | |||||
value="false" | |||||
control={<Radio />} | |||||
label="不合格" | |||||
sx={{ | |||||
color: currentValue === false ? "red" : "inherit", | |||||
"& .Mui-checked": {color: "red"} | |||||
}} | |||||
/> | |||||
</RadioGroup> | |||||
</FormControl> | |||||
); | |||||
}, | |||||
}, | }, | ||||
{ | |||||
field: "isFailed", | |||||
headerName: t("failed"), | |||||
flex: 1, | |||||
editable: true, | |||||
type: "boolean", | |||||
renderCell: (params) => ( | |||||
<Checkbox | |||||
checked={!!params.value} | |||||
onClick={(e) => e.stopPropagation()} // avoid row selection | |||||
onMouseDown={(e) => e.stopPropagation()} // extra guard | |||||
onChange={(e) => { | |||||
const checked = e.target.checked; | |||||
setQcItems((prev) => | |||||
prev.map((r) => | |||||
r.id === params.id | |||||
? { ...r, isFailed: checked, isPassed: !checked ? r.isPassed : false } | |||||
: r | |||||
) | |||||
); | |||||
}} | |||||
size="small" | |||||
/> | |||||
), | |||||
}, | |||||
{ | { | ||||
field: "failedQty", | field: "failedQty", | ||||
headerName: t("failedQty"), | headerName: t("failedQty"), | ||||
@@ -306,7 +245,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
<TextField | <TextField | ||||
type="number" | type="number" | ||||
size="small" | size="small" | ||||
value={params.value ?? ''} | |||||
value={!params.row.isPassed? (params.value ?? '') : '0'} | |||||
disabled={params.row.isPassed} | |||||
onChange={(e) => { | onChange={(e) => { | ||||
const v = e.target.value; | const v = e.target.value; | ||||
const next = v === '' ? undefined : Number(v); | const next = v === '' ? undefined : Number(v); | ||||
@@ -326,7 +266,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
{ | { | ||||
field: "remarks", | field: "remarks", | ||||
headerName: t("remarks"), | headerName: t("remarks"), | ||||
flex: 1, | |||||
flex: 2, | |||||
renderCell: (params) => ( | renderCell: (params) => ( | ||||
<TextField | <TextField | ||||
size="small" | size="small" | ||||
@@ -354,11 +294,18 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
}, [itemDetail]); | }, [itemDetail]); | ||||
// Set initial value for acceptQty | |||||
useEffect(() => { | |||||
if (itemDetail?.acceptedQty !== undefined) { | |||||
setValue("acceptQty", itemDetail.acceptedQty); | |||||
} | |||||
}, [itemDetail?.acceptedQty, setValue]); | |||||
// const [openCollapse, setOpenCollapse] = useState(false) | // const [openCollapse, setOpenCollapse] = useState(false) | ||||
const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | ||||
const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { | const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { | ||||
const isFailed = qcItems.some((qc) => qc.isFailed) | |||||
const isFailed = qcItems.some((qc) => !qc.isPassed) | |||||
console.log(isFailed) | console.log(isFailed) | ||||
if (isFailed) { | if (isFailed) { | ||||
setIsCollapsed(true) | setIsCollapsed(true) | ||||
@@ -367,14 +314,20 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
} | } | ||||
}, []) | }, []) | ||||
// const handleRadioChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { | |||||
// const value = event.target.value === 'true'; | |||||
// setValue("qcAccept", value); | |||||
// }, [setValue]); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log(itemDetail); | console.log(itemDetail); | ||||
}, [itemDetail]); | }, [itemDetail]); | ||||
useEffect(() => { | useEffect(() => { | ||||
onFailedOpenCollapse(qcItems) | |||||
}, [qcItems, onFailedOpenCollapse]); | |||||
// onFailedOpenCollapse(qcItems) // This function is no longer needed | |||||
}, [qcItems]); // Removed onFailedOpenCollapse from dependency array | |||||
return ( | return ( | ||||
<> | <> | ||||
@@ -411,24 +364,15 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
autoHeight | autoHeight | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={4}> | |||||
<TextField | |||||
type="number" | |||||
label={t("acceptedQty")} | |||||
fullWidth | |||||
defaultValue={watch("passingQty")} | |||||
{...register("passingQty", { | |||||
required: "passingQty required!", | |||||
})} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={12}> | |||||
{/* <Grid item xs={12}> | |||||
<EscalationComponent | <EscalationComponent | ||||
forSupervisor={false} | forSupervisor={false} | ||||
isCollapsed={isCollapsed} | isCollapsed={isCollapsed} | ||||
setIsCollapsed={setIsCollapsed} | setIsCollapsed={setIsCollapsed} | ||||
/> | /> | ||||
</Grid> | |||||
</Grid> */} | |||||
</> | </> | ||||
)} | )} | ||||
{tabIndex == 1 && ( | {tabIndex == 1 && ( | ||||
@@ -446,27 +390,68 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
</Grid> | </Grid> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<StyledDataGrid | <StyledDataGrid | ||||
rows={qcResult} | |||||
rows={escalationHistory} | |||||
columns={columns} | columns={columns} | ||||
onRowSelectionModelChange={(newRowSelectionModel) => { | onRowSelectionModelChange={(newRowSelectionModel) => { | ||||
setRowSelectionModel(newRowSelectionModel); | setRowSelectionModel(newRowSelectionModel); | ||||
}} | }} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={12}> | |||||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||||
{t("Escalation Result")} | |||||
</Typography> | |||||
</Grid> | |||||
<Grid item xs={12}> | |||||
<EscalationComponent | |||||
forSupervisor={true} | |||||
isCollapsed={isCollapsed} | |||||
setIsCollapsed={setIsCollapsed} | |||||
/> | |||||
</Grid> | |||||
</> | </> | ||||
)} | )} | ||||
<Grid item xs={12}> | |||||
<FormControl> | |||||
<Controller | |||||
name="qcAccept" | |||||
control={control} | |||||
defaultValue={true} | |||||
render={({ field }) => ( | |||||
<RadioGroup | |||||
row | |||||
aria-labelledby="demo-radio-buttons-group-label" | |||||
{...field} | |||||
value={field.value?.toString() || "true"} | |||||
onChange={(e) => { | |||||
const value = e.target.value === 'true'; | |||||
if (!value && Boolean(errors.acceptQty)) { | |||||
setValue("acceptQty", itemDetail.acceptedQty); | |||||
} | |||||
field.onChange(value); | |||||
}} | |||||
> | |||||
<FormControlLabel value="true" control={<Radio />} label="接受" /> | |||||
<Box sx={{mr:2}}> | |||||
<TextField | |||||
type="number" | |||||
label={t("acceptQty")} | |||||
sx={{ width: '150px' }} | |||||
defaultValue={accQty} | |||||
disabled={!qcAccept} | |||||
{...register("acceptQty", { | |||||
required: "acceptQty required!", | |||||
})} | |||||
error={Boolean(errors.acceptQty)} | |||||
helperText={errors.acceptQty?.message} | |||||
/> | |||||
</Box> | |||||
<FormControlLabel value="false" control={<Radio />} label="不接受及上報" /> | |||||
</RadioGroup> | |||||
)} | |||||
/> | |||||
</FormControl> | |||||
</Grid> | |||||
{/* <Grid item xs={12}> | |||||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||||
{t("Escalation Result")} | |||||
</Typography> | |||||
</Grid> | |||||
<Grid item xs={12}> | |||||
<EscalationComponent | |||||
forSupervisor={true} | |||||
isCollapsed={isCollapsed} | |||||
setIsCollapsed={setIsCollapsed} | |||||
/> | |||||
</Grid> */} | |||||
</Grid> | </Grid> | ||||
</Grid> | </Grid> | ||||
</> | </> | ||||
@@ -21,6 +21,8 @@ import QcFormVer2 from "./QcFormVer2"; | |||||
import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; | import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; | ||||
import { useGridApiRef } from "@mui/x-data-grid"; | import { useGridApiRef } from "@mui/x-data-grid"; | ||||
import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | |||||
const style = { | const style = { | ||||
position: "absolute", | position: "absolute", | ||||
top: "50%", | top: "50%", | ||||
@@ -120,22 +122,70 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | ||||
async (data, event) => { | async (data, event) => { | ||||
console.log("QC Submission:", event!.nativeEvent); | console.log("QC Submission:", event!.nativeEvent); | ||||
// Extract only QC related fields | |||||
// Get QC data from the shared form context | |||||
const qcAccept = data.qcAccept; | |||||
const acceptQty = data.acceptQty; | |||||
// Validate QC data | |||||
const validationErrors : string[] = []; | |||||
// Check if all QC items have results | |||||
const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined); | |||||
if (itemsWithoutResult.length > 0) { | |||||
validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`); | |||||
} | |||||
// Check if failed items have failed quantity | |||||
const failedItemsWithoutQty = qcItems.filter(item => | |||||
item.isPassed === false && (!item.failedQty || item.failedQty <= 0) | |||||
); | |||||
if (failedItemsWithoutQty.length > 0) { | |||||
validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`); | |||||
} | |||||
// Check if QC accept decision is made | |||||
// if (qcAccept === undefined) { | |||||
// validationErrors.push("QC accept/reject decision is required"); | |||||
// } | |||||
// Check if accept quantity is valid | |||||
if (acceptQty === undefined || acceptQty <= 0) { | |||||
validationErrors.push("Accept quantity must be greater than 0"); | |||||
} | |||||
if (validationErrors.length > 0) { | |||||
console.error("QC Validation failed:", validationErrors); | |||||
alert(`未完成品檢: ${validationErrors}`); | |||||
return; | |||||
} | |||||
const qcData = { | const qcData = { | ||||
// qcStatus: data.qcStatus, | |||||
// qcComments: data.qcComments, | |||||
// qcResult: data.qcResult, | |||||
// approvedBy: data.approvedBy, | |||||
// qualityGrade: data.qualityGrade, | |||||
// defectNotes: data.defectNotes, | |||||
data: data, | |||||
// Add other QC specific fields from your form | |||||
qcAccept: qcAccept, | |||||
acceptQty: acceptQty, | |||||
qcItems: qcItems.map(item => ({ | |||||
id: item.id, | |||||
qcItem: item.qcItem, | |||||
qcDescription: item.qcDescription, | |||||
isPassed: item.isPassed, | |||||
failedQty: (item.failedQty && !item.isPassed) || 0, | |||||
remarks: item.remarks || '' | |||||
})) | |||||
}; | }; | ||||
console.log(qcItems) | |||||
console.log("QC Data:", qcData); | |||||
// Handle QC submission logic here | |||||
// After QC approval, open putaway form | |||||
// onOpenPutaway(); | |||||
// const qcData = data; | |||||
console.log("QC Data for submission:", qcData); | |||||
// await submitQcData(qcData); | |||||
if (!qcData.qcItems.every((qc) => qc.isPassed) && qcData.qcAccept) { | |||||
submitDialogWithWarning(onOpenPutaway, t, {title:"有不合格檢查項目,確認接受收貨?", confirmButtonText: "Confirm", html: ""}); | |||||
return; | |||||
} | |||||
if (qcData.qcAccept) { | |||||
onOpenPutaway(); | |||||
} else { | |||||
onClose(); | |||||
} | |||||
}, | }, | ||||
[onOpenPutaway, qcItems], | [onOpenPutaway, qcItems], | ||||
); | ); | ||||
@@ -237,7 +287,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
id="printButton" | id="printButton" | ||||
type="button" | type="button" | ||||
variant="contained" | variant="contained" | ||||
color="secondary" | |||||
color="primary" | |||||
sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
onClick={onPrint} | onClick={onPrint} | ||||
> | > | ||||
@@ -247,7 +297,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
id="putawaySubmit" | id="putawaySubmit" | ||||
type="submit" | type="submit" | ||||
variant="contained" | variant="contained" | ||||
color="secondary" | |||||
color="primary" | |||||
sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
> | > | ||||
{t("confirm putaway")} | {t("confirm putaway")} | ||||
@@ -299,7 +349,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
id="emailSupplier" | id="emailSupplier" | ||||
type="button" | type="button" | ||||
variant="contained" | variant="contained" | ||||
color="secondary" | |||||
color="primary" | |||||
sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | ||||
> | > | ||||
@@ -309,7 +359,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
id="qcSubmit" | id="qcSubmit" | ||||
type="button" | type="button" | ||||
variant="contained" | variant="contained" | ||||
color="secondary" | |||||
color="primary" | |||||
sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
onClick={formProps.handleSubmit(onSubmitQc)} | onClick={formProps.handleSubmit(onSubmitQc)} | ||||
> | > | ||||
@@ -75,7 +75,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
setError, | setError, | ||||
clearErrors, | clearErrors, | ||||
} = useFormContext<StockInInput>(); | } = useFormContext<StockInInput>(); | ||||
console.log(itemDetail); | |||||
// console.log(itemDetail); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log("triggered"); | console.log("triggered"); | ||||
@@ -90,15 +90,17 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
const productionDate = watch("productionDate"); | const productionDate = watch("productionDate"); | ||||
const expiryDate = watch("expiryDate"); | const expiryDate = watch("expiryDate"); | ||||
const uom = watch("uom"); | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log(uom); | |||||
console.log(productionDate); | console.log(productionDate); | ||||
console.log(expiryDate); | console.log(expiryDate); | ||||
if (expiryDate) clearErrors(); | if (expiryDate) clearErrors(); | ||||
if (productionDate) clearErrors(); | if (productionDate) clearErrors(); | ||||
}, [productionDate, expiryDate, clearErrors]); | }, [productionDate, expiryDate, clearErrors]); | ||||
console.log(itemDetail) | |||||
// console.log(itemDetail) | |||||
return ( | return ( | ||||
<Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
@@ -186,6 +188,28 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
}} | }} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("Supplier")} | |||||
fullWidth | |||||
{...register("supplier", { | |||||
// required: "productLotNo required!", | |||||
})} | |||||
disabled={true} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("productLotNo")} | |||||
fullWidth | |||||
{...register("productLotNo", { | |||||
// required: "productLotNo required!", | |||||
})} | |||||
disabled={disabled} | |||||
error={Boolean(errors.productLotNo)} | |||||
helperText={errors.productLotNo?.message} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<Controller | <Controller | ||||
control={control} | control={control} | ||||
@@ -227,14 +251,12 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("productLotNo")} | |||||
label={t("qty")} | |||||
fullWidth | fullWidth | ||||
{...register("productLotNo", { | |||||
// required: "productLotNo required!", | |||||
{...register("qty", { | |||||
required: "qty required!", | |||||
})} | })} | ||||
disabled={disabled} | |||||
error={Boolean(errors.productLotNo)} | |||||
helperText={errors.productLotNo?.message} | |||||
disabled={true} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
@@ -275,6 +297,27 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
}} | }} | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("receivedQty")} | |||||
fullWidth | |||||
{...register("receivedQty", { | |||||
required: "receivedQty required!", | |||||
})} | |||||
disabled={true} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("uom")} | |||||
fullWidth | |||||
{...register("uom", { | |||||
required: "uom required!", | |||||
})} | |||||
value={uom.code} | |||||
disabled={true} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
@@ -282,9 +325,10 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
{...register("acceptedQty", { | {...register("acceptedQty", { | ||||
required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
})} | })} | ||||
disabled={disabled} | |||||
error={Boolean(errors.acceptedQty)} | |||||
helperText={errors.acceptedQty?.message} | |||||
disabled={true} | |||||
// disabled={disabled} | |||||
// error={Boolean(errors.acceptedQty)} | |||||
// helperText={errors.acceptedQty?.message} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
{/* <Grid item xs={4}> | {/* <Grid item xs={4}> | ||||
@@ -3,8 +3,8 @@ import { PutawayLine } from "@/app/api/po/actions" | |||||
export interface QcData { | export interface QcData { | ||||
id: number, | id: number, | ||||
qcItem: string, | qcItem: string, | ||||
qcDescription: string, | |||||
isPassed: boolean | undefined | isPassed: boolean | undefined | ||||
isFailed: boolean | undefined | |||||
failedQty: number | undefined | failedQty: number | undefined | ||||
remarks: string | undefined | remarks: string | undefined | ||||
} | } | ||||
@@ -12,25 +12,41 @@ export interface QcData { | |||||
export const dummyQCData: QcData[] = [ | export const dummyQCData: QcData[] = [ | ||||
{ | { | ||||
id: 1, | id: 1, | ||||
qcItem: "目測", | |||||
qcItem: "包裝", | |||||
qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||||
isPassed: undefined, | isPassed: undefined, | ||||
isFailed: undefined, | |||||
failedQty: undefined, | failedQty: undefined, | ||||
remarks: undefined, | remarks: undefined, | ||||
}, | }, | ||||
{ | { | ||||
id: 2, | id: 2, | ||||
qcItem: "目測2", | |||||
qcItem: "肉質", | |||||
qcDescription: "肉質鬆散,則不合格", | |||||
isPassed: undefined, | isPassed: undefined, | ||||
isFailed: undefined, | |||||
failedQty: undefined, | failedQty: undefined, | ||||
remarks: undefined, | remarks: undefined, | ||||
}, | }, | ||||
{ | { | ||||
id: 3, | id: 3, | ||||
qcItem: "目測3", | |||||
qcItem: "顔色", | |||||
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,", | |||||
isPassed: undefined, | |||||
failedQty: undefined, | |||||
remarks: undefined, | |||||
}, | |||||
{ | |||||
id: 4, | |||||
qcItem: "狀態", | |||||
qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||||
isPassed: undefined, | |||||
failedQty: undefined, | |||||
remarks: undefined, | |||||
}, | |||||
{ | |||||
id: 5, | |||||
qcItem: "異物", | |||||
qcDescription: "有不屬於本食材的雜質,則不合格", | |||||
isPassed: undefined, | isPassed: undefined, | ||||
isFailed: undefined, | |||||
failedQty: undefined, | failedQty: undefined, | ||||
remarks: undefined, | remarks: undefined, | ||||
}, | }, | ||||
@@ -38,6 +38,10 @@ | |||||
"processed": "已處理數量", | "processed": "已處理數量", | ||||
"expiryDate": "到期日", | "expiryDate": "到期日", | ||||
"acceptedQty": "是次來貨數量", | "acceptedQty": "是次來貨數量", | ||||
"putawayQty": "上架數量", | |||||
"acceptQty": "揀收數量", | |||||
"printQty": "列印數量", | |||||
"qcResult": "品檢結果", | |||||
"weight": "重量", | "weight": "重量", | ||||
"start": "開始", | "start": "開始", | ||||
"qc": "質量控制", | "qc": "質量控制", | ||||
@@ -45,7 +49,11 @@ | |||||
"stock in": "入庫", | "stock in": "入庫", | ||||
"putaway": "上架", | "putaway": "上架", | ||||
"delete": "刪除", | "delete": "刪除", | ||||
"Accept quantity must be greater than 0": "揀收數量不能少於1", | |||||
"QC items without result": "請完成品檢結果", | |||||
"Failed items must have failed quantity": "請輸入不合格數量", | |||||
"qty cannot be greater than remaining qty": "數量不能大於剩餘數量", | "qty cannot be greater than remaining qty": "數量不能大於剩餘數量", | ||||
"acceptQty must not greater than": "揀收數量不能大於", | |||||
"Record pol": "記錄採購訂單", | "Record pol": "記錄採購訂單", | ||||
"Add some entries!": "添加條目!", | "Add some entries!": "添加條目!", | ||||
"draft": "草稿", | "draft": "草稿", | ||||
@@ -74,9 +82,10 @@ | |||||
"Escalation": "上報", | "Escalation": "上報", | ||||
"to be processed": "待處理", | "to be processed": "待處理", | ||||
"supervisor": "管理層", | |||||
"Stock In Detail": "入庫詳情", | "Stock In Detail": "入庫詳情", | ||||
"productLotNo": "產品批號", | |||||
"productLotNo": "貨品批號", | |||||
"receiptDate": "收貨日期", | "receiptDate": "收貨日期", | ||||
"acceptedWeight": "接受重量", | "acceptedWeight": "接受重量", | ||||
"productionDate": "生產日期", | "productionDate": "生產日期", | ||||
@@ -94,7 +103,7 @@ | |||||
"receivedQty": "已來貨數量", | "receivedQty": "已來貨數量", | ||||
"dnQty": "送貨單數量", | "dnQty": "送貨單數量", | ||||
"Accept submit": "處理來貨", | |||||
"Accept submit": "接受來貨", | |||||
"qc processing": "處理來貨及品檢", | "qc processing": "處理來貨及品檢", | ||||
"putawayBtn": "上架", | "putawayBtn": "上架", | ||||
"dnNo": "送貨單編號", | "dnNo": "送貨單編號", | ||||
@@ -177,7 +177,7 @@ const components: ThemeOptions["components"] = { | |||||
styleOverrides: { | styleOverrides: { | ||||
root: { | root: { | ||||
"& .MuiFilledInput-root": { | "& .MuiFilledInput-root": { | ||||
paddingTop: 8, | |||||
paddingTop: 16, | |||||
paddingBottom: 8, | paddingBottom: 8, | ||||
}, | }, | ||||
}, | }, | ||||
@@ -315,6 +315,7 @@ const components: ThemeOptions["components"] = { | |||||
root: { | root: { | ||||
borderBottomColor: palette.divider, | borderBottomColor: palette.divider, | ||||
padding: "1px 6px", | padding: "1px 6px", | ||||
fontSize: defaultFontSize - 2, | |||||
// padding: "15px 16px", | // padding: "15px 16px", | ||||
// lineHeight: 1.5, | // lineHeight: 1.5, | ||||
}, | }, | ||||