Browse Source

update po

master
kelvinsuen 6 days ago
parent
commit
5e4336e1eb
12 changed files with 353 additions and 283 deletions
  1. +4
    -3
      src/app/api/po/actions.ts
  2. +10
    -0
      src/app/api/qc/index.ts
  3. +19
    -0
      src/components/General/LoadingComponent.tsx
  4. +1
    -0
      src/components/InputDataGrid/InputDataGrid.tsx
  5. +41
    -22
      src/components/PoDetail/EscalationComponent.tsx
  6. +50
    -36
      src/components/PoDetail/PoDetail.tsx
  7. +1
    -1
      src/components/PoDetail/PoInfoCard.tsx
  8. +38
    -36
      src/components/PoDetail/QcFormVer2.tsx
  9. +25
    -15
      src/components/PoDetail/QcStockInModalVer2.tsx
  10. +5
    -5
      src/components/PoDetail/StockInFormVer2.tsx
  11. +25
    -19
      src/components/PoDetail/dummyQcTemplate.tsx
  12. +134
    -146
      src/i18n/zh/purchaseOrder.json

+ 4
- 3
src/app/api/po/actions.ts View File

@@ -33,11 +33,12 @@ export interface StockInLineEntry {
expiryDate?: string;
}

export interface PurchaseQcResult {
export interface PurchaseQcResult{
qcItemId: number;
isPassed: boolean,
isPassed: boolean;
failQty: number;
remarks?: string
remarks?: string;
}
export interface StockInInput {
status: string;


+ 10
- 0
src/app/api/qc/index.ts View File

@@ -15,6 +15,16 @@ export interface QcItemWithChecks {
description: string | undefined;
}

export interface QcData {
id: number,
code: string,
name: string,
qcDescription: string,
isPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
}

export const fetchQcItemCheckList = cache(async () => {
return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, {
next: { tags: ["qc"] },


+ 19
- 0
src/components/General/LoadingComponent.tsx View File

@@ -0,0 +1,19 @@
import {Box, CircularProgress, Grid} from "@mui/material";

export const LoadingComponent: React.FC = () => {
return (
<>
<Grid item xs={12} md={12} lg={12} justifyContent="space-between" alignItems="center" marginTop={10}>
<Box
display="flex"
justifyContent="center"
alignItems="center"
// autoheight="true"
>
<CircularProgress />
</Box>
</Grid>
</>
)
}
export default LoadingComponent;

+ 1
- 0
src/components/InputDataGrid/InputDataGrid.tsx View File

@@ -84,6 +84,7 @@ export interface SelectionInputDataGridProps<T, V, E> {
columns: GridColDef[];
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
needAdd?: boolean;
showRemoveBtn?: boolean;
}

export type Props<T, V, E> =


+ 41
- 22
src/components/PoDetail/EscalationComponent.tsx View File

@@ -83,10 +83,18 @@ const EscalationComponent: React.FC<Props> = ({
return (
// <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}>
<>
<Paper>
<Paper sx={{padding: 2}}>
{/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<FormControlLabel
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="body1">上報結果</Typography>
{/* {isCollapsed ? (
<ExpandLessIcon sx={{ ml: 1 }} />
) : (
<ExpandMoreIcon sx={{ ml: 1 }} />
)} */}
</Box>
{/* <FormControlLabel
control={
<Checkbox
checked={isCollapsed}
@@ -104,23 +112,10 @@ const EscalationComponent: React.FC<Props> = ({
)}
</Box>
}
/>
/> */}
</Box>
<Collapse in={isCollapsed}>
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{forSupervisor ? (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
): undefined}
<FormControl fullWidth>
<select
id="name"
@@ -136,7 +131,31 @@ const EscalationComponent: React.FC<Props> = ({
))}
</select>
</FormControl>
<TextField
{forSupervisor ? (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="pass"
name="radio-buttons-group"
>
<FormControlLabel value="pass" control={<Radio />} label="合格" />
<FormControlLabel value="fail" control={<Radio />} label="不合格" />
</RadioGroup>
</FormControl>
): undefined}
{forSupervisor && (<TextField
fullWidth
id="decision"
name="decision"
label="上報結果"
type="radio"
// value={formData.decision}
onChange={handleInputChange}
InputProps={{ inputProps: { min: 1 } }}
placeholder="請決定上報結果"
/>)}
{/* <TextField
fullWidth
id="quantity"
name="quantity"
@@ -146,21 +165,21 @@ const EscalationComponent: React.FC<Props> = ({
onChange={handleInputChange}
InputProps={{ inputProps: { min: 1 } }}
placeholder="請輸入數量"
/>
/> */}

<TextField
fullWidth
id="message"
name="message"
label="備註"
label="上報原因"
multiline
rows={4}
value={formData.message}
onChange={handleInputChange}
placeholder="請輸入您的備註"
placeholder="請輸入上報原因"
/>

<Stack direction="row" justifyContent="flex-end" gap={1}>
{/* <Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
type="submit"
variant="contained"
@@ -168,7 +187,7 @@ const EscalationComponent: React.FC<Props> = ({
>
{t("update qc info")}
</Button>
</Stack>
</Stack> */}
</Box>
</Collapse>
</Paper>


+ 50
- 36
src/components/PoDetail/PoDetail.tsx View File

@@ -77,6 +77,7 @@ import dayjs, { Dayjs } from "dayjs";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers";
import { debounce } from "lodash";
import LoadingComponent from "../General/LoadingComponent";
//import { useRouter } from "next/navigation";


@@ -115,7 +116,7 @@ const PoSearchList: React.FC<{
return (
<Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}>
<Typography variant="h6" gutterBottom>
{t("Purchase Orders")}
{t("Purchase Order")}
</Typography>
<TextField
label={t("Search")}
@@ -133,44 +134,49 @@ const PoSearchList: React.FC<{
),
}}
/>
<List dense sx={{ width: '100%' }}>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding sx={{ width: '100%' }}>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
width: '100%',
"&.Mui-selected": {
backgroundColor: "primary.light",
"&:hover": {
{(filteredPoList.length > 0)? (
<List dense sx={{ width: '100%' }}>
{filteredPoList.map((poItem, index) => (
<div key={poItem.id}>
<ListItem disablePadding sx={{ width: '100%' }}>
<ListItemButton
selected={selectedPoId === poItem.id}
onClick={() => onSelect(poItem)}
sx={{
width: '100%',
"&.Mui-selected": {
backgroundColor: "primary.light",
"&:hover": {
backgroundColor: "primary.light",
},
},
},
}}
>
<ListItemText
primary={
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{poItem.code}
</Typography>
}
secondary={
<Typography variant="caption" color="text.secondary">
{t(`${poItem.status.toLowerCase()}`)}
</Typography>
}
/>
</ListItemButton>
</ListItem>
{index < filteredPoList.length - 1 && <Divider />}
</div>
))}
</List>
}}
>
<ListItemText
primary={
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}>
{poItem.code}
</Typography>
}
secondary={
<Typography variant="caption" color="text.secondary">
{t(`${poItem.status.toLowerCase()}`)}
</Typography>
}
/>
</ListItemButton>
</ListItem>
{index < filteredPoList.length - 1 && <Divider />}
</div>
))}
</List>) : (
<LoadingComponent/>
)
}
{searchTerm && (
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}>
{t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")}
{`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`}
{/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */}
</Typography>
)}
</Paper>
@@ -184,7 +190,7 @@ interface PolInputResult {

const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const cameras = useContext(CameraContext);
console.log(cameras);
// console.log(cameras);
const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef();
const [purchaseOrder, setPurchaseOrder] = useState({ ...po });
@@ -212,6 +218,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const router = useRouter();
const [poList, setPoList] = useState<PoResult[]>([]);
const [selectedPoId, setSelectedPoId] = useState(po.id);
const [focusField, setFocusField] = useState<HTMLInputElement>();

const currentPoId = searchParams.get('id');
const selectedIdsParam = searchParams.get('selectedIds');
// const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
@@ -261,6 +269,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setProcessedQty(result.pol[0].processed);
}
}
// if (focusField) {console.log(focusField);focusField.focus();}
}
} catch (error) {
console.error("Failed to fetch PO detail:", error);
@@ -448,6 +457,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
// setPolInputList(() => temp)
}, 300), [rowIndex]);

// const [focusField, setFocusField] = useState<HTMLInputElement>();

const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1)
return (
<>
@@ -498,6 +509,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
variant="outlined"
defaultValue={polInputList[rowIndex]?.lotNo ?? ''}
onChange={handleChange}
// onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}}
/>
</TableCell>
<TableCell align="center">
@@ -557,6 +569,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
</>
);
}
// ROW END

const [tabIndex, setTabIndex] = useState(0);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {


+ 1
- 1
src/components/PoDetail/PoInfoCard.tsx View File

@@ -19,7 +19,7 @@ type Props = {
po: PoResult;
};

const PoInfoCard: React.FC<Props> = async (
const PoInfoCard: React.FC<Props> = (
{
// id
po


+ 38
- 36
src/components/PoDetail/QcFormVer2.tsx View File

@@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid";
import { StockInLine } from "@/app/api/po";
import { stockInLineStatusMap } from "@/app/utils/formatUtil";
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import axios from "@/app/(main)/axios/axiosInstance";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance";
import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate";
import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/dashboard/actions";
import { escape } from "lodash";
import { PanoramaSharp } from "@mui/icons-material";

interface Props {
itemDetail: StockInLine;
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] };
qc: QcItemWithChecks[];
disabled: boolean;
qcItems: QcData[]
@@ -88,8 +88,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
const [tabIndex, setTabIndex] = useState(0);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>();
const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory);
const [qcResult, setQcResult] = useState();
// const [qcResult, setQcResult] = useState();
const qcAccept = watch("qcAccept");
const qcResult = watch("qcResult");
console.log(qcResult);
// const [qcAccept, setQcAccept] = useState(true);
// const [qcItems, setQcItems] = useState(dummyQCData)

@@ -184,28 +186,29 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI

const qcColumns: GridColDef[] = [
{
field: "qcItem",
field: "code",
headerName: t("qcItem"),
flex: 2,
renderCell: (params) => (
<Box>
<b>{params.value}</b><br/>
{params.row.qcDescription}<br/>
{params.row.name}<br/>
</Box>
),
},
{
field: 'isPassed',
field: 'qcResult',
headerName: t("qcResult"),
flex: 1.5,
renderCell: (params) => {
const currentValue = params.value;
const currentValue = params.row;
console.log(currentValue.row);
return (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
value={currentValue === undefined ? "" : (currentValue ? "true" : "false")}
value={currentValue.isPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.isPassed ? "true" : "false")}
onChange={(e) => {
const value = e.target.value;
setQcItems((prev) =>
@@ -218,9 +221,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
value="true"
control={<Radio />}
label="合格"
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
sx={{
color: currentValue === true ? "green" : "inherit",
color: currentValue.isPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
@@ -228,9 +231,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
value="false"
control={<Radio />}
label="不合格"
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
sx={{
color: currentValue === false ? "red" : "inherit",
color: currentValue.isPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
}}
/>
@@ -249,7 +252,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
type="number"
size="small"
value={!params.row.isPassed? (params.value ?? '') : '0'}
disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"}
disabled={params.row.isPassed || disabled}
onChange={(e) => {
const v = e.target.value;
const next = v === '' ? undefined : Number(v);
@@ -257,6 +260,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
setQcItems((prev) =>
prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r))
);
// setValue(`failQty`,failQty);
}}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
@@ -274,7 +278,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<TextField
size="small"
value={params.value ?? ''}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(e) => {
const remarks = e.target.value;
// const next = v === '' ? undefined : Number(v);
@@ -283,6 +287,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
);
}}
// {...register(`qcResult.${params.row.rowIndex}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
@@ -293,11 +300,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
},
]

useEffect(() => {
console.log(itemDetail);
}, [itemDetail]);

// Set initial value for acceptQty
useEffect(() => {
if (itemDetail?.demandQty > 0) { //!== undefined) {
@@ -308,9 +310,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
}, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]);

// const [openCollapse, setOpenCollapse] = useState(false)
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);

const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => {
const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => {
const isFailed = qcItems.some((qc) => !qc.isPassed)
console.log(isFailed)
if (isFailed) {
@@ -327,10 +329,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI


useEffect(() => {
console.log(itemDetail);
console.log("ItemDetail in QC:", itemDetail);
}, [itemDetail]);


useEffect(() => {
// onFailedOpenCollapse(qcItems) // This function is no longer needed
}, [qcItems]); // Removed onFailedOpenCollapse from dependency array
@@ -366,19 +369,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
/> */}
<StyledDataGrid
columns={qcColumns}
rows={qcItems}
rows={disabled? qcResult:qcItems}
autoHeight
/>
</Grid>
{!qcAccept && (
<Grid item xs={12}>
<EscalationComponent
forSupervisor={false}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Grid>)}
</>
)}
{tabIndex == 1 && (
@@ -425,7 +419,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
field.onChange(value);
}}
>
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"}
<FormControlLabel disabled={disabled}
value="true" control={<Radio />} label="接受" />
<Box sx={{mr:2}}>
<TextField
@@ -434,7 +428,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
sx={{ width: '150px' }}
value={qcAccept? accQty : 0 }
defaultValue={accQty}
disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"}
disabled={!qcAccept || disabled}
{...register("acceptQty", {
required: "acceptQty required!",
})}
@@ -442,7 +436,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
helperText={errors.acceptQty?.message}
/>
</Box>
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"}
<FormControlLabel disabled={disabled}
value="false" control={<Radio />}
sx={{"& .Mui-checked": {color: "red"}}}
label="不接受及上報" />
@@ -451,6 +445,14 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
/>
</FormControl>
</Grid>
{!qcAccept && (
<Grid item xs={12}>
<EscalationComponent
forSupervisor={false}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Grid>)}
{/* {qcAccept && <Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Escalation Result")}


+ 25
- 15
src/components/PoDetail/QcStockInModalVer2.tsx View File

@@ -1,7 +1,7 @@
"use client";
import { StockInLine } from "@/app/api/po";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions";
import { QcItemWithChecks } from "@/app/api/qc";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import {
Box,
Button,
@@ -19,7 +19,7 @@ import StockInForm from "./StockInForm";
import StockInFormVer2 from "./StockInFormVer2";
import QcFormVer2 from "./QcFormVer2";
import PutawayForm from "./PutawayForm";
import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate";
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate";
import { useGridApiRef } from "@mui/x-data-grid";
import {submitDialogWithWarning} from "../Swal/CustomAlerts";
import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions";
@@ -36,7 +36,7 @@ const style = {
px: 5,
pb: 10,
display: "block",
width: { xs: "60%", sm: "60%", md: "60%" },
width: { xs: "90%", sm: "90%", md: "90%" },
// height: { xs: "60%", sm: "60%", md: "60%" },
};
interface CommonProps extends Omit<ModalProps, "children"> {
@@ -113,6 +113,12 @@ const [qcItems, setQcItems] = useState(dummyQCData)
setOpenPutaway(isPutaway);
}, [open])
const [isCompleted, setIsCompleted] = useState(false);
useEffect(() => {
setIsCompleted(itemDetail.status.toLowerCase() == "completed")
}, [itemDetail]);

const [openPutaway, setOpenPutaway] = useState(false);
const onOpenPutaway = useCallback(() => {
@@ -155,21 +161,25 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// Get QC data from the shared form context
const qcAccept = data.qcAccept;
const acceptQty = data.acceptQty as number;
const qcResults = qcItems;
// const qcResults = isCompleted? data.qcResult as PurchaseQcResult[] : qcItems;
// Validate QC data
const validationErrors : string[] = [];
// Check if all QC items have results
const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined);
const itemsWithoutResult = qcResults.filter(item => item.isPassed === undefined);
if (itemsWithoutResult.length > 0) {
validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`);
validationErrors.push(`${t("QC items without result")}`);
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
}

// Check if failed items have failed quantity
const failedItemsWithoutQty = qcItems.filter(item =>
const failedItemsWithoutQty = qcResults.filter(item =>
item.isPassed === false && (!item.failQty || item.failQty <= 0)
);
if (failedItemsWithoutQty.length > 0) {
validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`);
validationErrors.push(`${t("Failed items must have failed quantity")}`);
// validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`);
}

// Check if QC accept decision is made
@@ -205,9 +215,9 @@ const [qcItems, setQcItems] = useState(dummyQCData)
qcAccept: qcAccept? qcAccept : false,
acceptQty: acceptQty? acceptQty : 0,
qcResult: qcItems.map(item => ({
qcResult: qcResults.map(item => ({
qcItemId: item.id,
// qcItem: item.qcItem,
// code: item.code,
// qcDescription: item.qcDescription,
isPassed: item.isPassed? item.isPassed : false,
failQty: (item.failQty && !item.isPassed) ? item.failQty : 0,
@@ -329,7 +339,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
const acceptQty = formProps.watch("acceptedQty")

const checkQcIsPassed = useCallback((qcItems: QcData[]) => {
const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => {
const isPassed = qcItems.every((qc) => qc.isPassed);
console.log(isPassed)
if (isPassed) {
@@ -343,7 +353,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
useEffect(() => {
// maybe check if submitted before
console.log(qcItems)
checkQcIsPassed(qcItems)
// checkQcIsPassed(qcItems)
}, [qcItems, checkQcIsPassed])

return (
@@ -368,7 +378,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<PutawayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={false}
disabled={isCompleted}
/>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
@@ -406,7 +416,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
</Typography>
</Grid>
<Grid item xs={12}>
<StockInFormVer2 itemDetail={itemDetail} disabled={false} />
<StockInFormVer2 itemDetail={itemDetail} disabled={isCompleted} />
</Grid>
</Grid>
{/* <Stack direction="row" justifyContent="flex-end" gap={1}>
@@ -428,13 +438,13 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
disabled={false}
disabled={isCompleted}
qcItems={qcItems}
setQcItems={setQcItems}
/>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>
{itemDetail.status.toLowerCase() != "completed" && (<Button
{!isCompleted && (<Button
id="qcSubmit"
type="button"
variant="contained"


+ 5
- 5
src/components/PoDetail/StockInFormVer2.tsx View File

@@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({
{...register("dnNo", {
// required: "productLotNo required!",
})}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
// error={Boolean(errors.productLotNo)}
// helperText={errors.productLotNo?.message}
/>
@@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({
{...register("productLotNo", {
// required: "productLotNo required!",
})}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
error={Boolean(errors.productLotNo)}
helperText={errors.productLotNo?.message}
/>
@@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({
sx={{ width: "100%" }}
label={t("productionDate")}
value={productionDate ? dayjs(productionDate) : undefined}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(date) => {
if (!date) return;
setValue(
@@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({
sx={{ width: "100%" }}
label={t("expiryDate")}
value={expiryDate ? dayjs(expiryDate) : undefined}
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
onChange={(date) => {
console.log(date);
if (!date) return;
@@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({
<TextField
label={t("acceptedQty")}
fullWidth
disabled={itemDetail.status.toLowerCase() == "completed"}
disabled={disabled}
{...register("acceptedQty", {
required: "acceptedQty required!",
})}


+ 25
- 19
src/components/PoDetail/dummyQcTemplate.tsx View File

@@ -1,51 +1,57 @@
import { PutawayLine } from "@/app/api/po/actions"
import { QcData } from "@/app/api/qc"

export interface QcData {
id: number,
qcItem: string,
qcDescription: string,
isPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
}
// export interface QcData {
// qcItemId: number,
// qcItem: string,
// qcDescription: string,
// isPassed: boolean | undefined
// failQty: number | undefined
// remarks: string | undefined
// }

export const dummyQCData: QcData[] = [
{
id: 1,
qcItem: "包裝",
id: 4,
code: "包裝",
qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格",
name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格",
isPassed: undefined,
failQty: undefined,
remarks: undefined,
},
{
id: 2,
qcItem: "肉質",
id: 5,
code: "肉質",
qcDescription: "肉質鬆散,則不合格",
name: "肉質鬆散,則不合格",
isPassed: undefined,
failQty: undefined,
remarks: undefined,
},
{
id: 3,
qcItem: "顔色",
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,",
id: 6,
code: "顔色",
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格",
name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格",
isPassed: undefined,
failQty: undefined,
remarks: undefined,
},
{
id: 4,
qcItem: "狀態",
id: 7,
code: "狀態",
qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格",
name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格",
isPassed: undefined,
failQty: undefined,
remarks: undefined,
},
{
id: 5,
qcItem: "異物",
id: 8,
code: "異物",
qcDescription: "有不屬於本食材的雜質,則不合格",
name: "有不屬於本食材的雜質,則不合格",
isPassed: undefined,
failQty: undefined,
remarks: undefined,


+ 134
- 146
src/i18n/zh/purchaseOrder.json View File

@@ -1,147 +1,135 @@
{
"Purchase Order": "採購訂單",
"Purchase Receipt": "處理採購來貨",
"Code": "編號",
"OrderDate": "下單日期",
"Order Date": "下單日期",
"Order Date To": "下單日期至",
"ETA": "預計到貨日期",
"ETA To": "預計到貨日期至",
"Details": "詳情",
"Supplier": "供應商",
"Status": "來貨狀態",
"Escalated": "上報狀態",
"NotEscalated": "無上報",

"Do you want to start?": "確定開始嗎?",
"Start": "開始",
"Start Success": "開始成功",
"Start Fail": "開始失敗",
"Start PO": "開始採購訂單",
"Do you want to complete?": "確定完成嗎?",
"Cancel": "取消",
"Complete": "完成",
"Complete Success": "完成成功",
"Complete Fail": "完成失敗",
"Complete PO": "完成採購訂單",
"General": "一般",
"Bind Storage": "綁定倉位",
"Po No.": "採購訂單編號",
"PO No.": "採購訂單編號",
"itemNo": "貨品編號",
"itemName": "貨品名稱",
"Item Detail": "貨品詳情",
"Item Code": "貨品編號",
"Item Name": "貨品名稱",
"Item Qty": "貨品數量",
"Item Accepted Qty": "貨品已收貨數量",
"Item Purchase UoM": "貨品計量單位",
"qty": "訂單數量",
"uom": "計量單位",
"Stock UoM": "庫存單位",
"Stock In Qty": "收貨數量",
"total weight": "總重量",
"weight unit": "重量單位",
"price": "訂單貨值",
"processed": "已上架數量",
"expiryDate": "到期日",
"acceptedQty": "是次來貨數量",
"putawayQty": "上架數量",
"acceptQty": "揀收數量",
"printQty": "列印數量",
"qcResult": "品檢結果",
"weight": "重量",
"start": "開始",
"qc": "質量控制",
"escalation": "上報",
"stock in": "入庫",
"putaway": "上架",
"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": "數量不能大於剩餘數量",
"acceptQty must not greater than": "揀收數量不能大於",
"Record pol": "記錄採購訂單",
"Add some entries!": "添加條目!",
"draft": "草稿",
"pending": "待處理",
"determine1": "上報1",
"determine2": "上報2",
"determine3": "上報3",
"receiving": "收貨中",
"received": "已檢收",
"completed": "已上架",
"rejected": "已拒絕及上報",
"status": "狀態",

"acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
"qc Check": "質量控制檢查",
"Please select QC": "請選擇質量控制",
"failQty": "失敗數量",
"select qc": "選擇質量控制",
"enter a failQty": "請輸入失敗數量",
"qty too big": "數量過大",
"sampleRate": "抽樣率",
"sampleWeight": "樣本重量",
"totalWeight": "總重量",

"Escalation": "上報",
"to be processed": "待處理",
"supervisor": "管理層",

"Stock In Detail": "入庫詳情",
"productLotNo": "貨品批號",
"receiptDate": "收貨日期",
"acceptedWeight": "接受重量",
"productionDate": "生產日期",

"reportQty": "上報數量",

"Default Warehouse": "預設倉庫",
"Select warehouse": "選擇倉庫",
"Putaway Detail": "上架詳情",
"LotNo": "批號",
"Po Code": "採購訂單編號",
"No Warehouse": "沒有倉庫",
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。",
"receivedQty": "已來貨數量",
"dnQty": "送貨單數量",

"Accept submit": "接受來貨",
"qc processing": "處理來貨及品檢",
"putaway processing": "處理來貨及上架",
"view stockin": "查看收貨及品檢",
"putaway processing": "處理來貨及上架",
"putawayBtn": "上架",
"dnNo": "送貨單編號",
"dnDate": "送貨單日期",

"submitStockIn": "更新來貨資料",
"QC Info": "品檢資料",
"Escalation History": "上報記錄",
"Escalation Info": "上報資料",
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"confirm qc result": "確定品檢結果",
"warehouse": "倉庫",
"qcItem": "品檢項目",
"passed": "接受",
"failed": "不接受",
"failedQty": "不合格數",
"remarks": "備註",

"Reject": "拒絕",
"submit": "提交",
"print": "列印",
"bind": "綁定"


}
"Purchase Order": "採購訂單",
"Purchase Receipt": "處理採購來貨",
"Code": "編號",
"OrderDate": "下單日期",
"Order Date": "下單日期",
"Order Date To": "下單日期至",
"ETA": "預計到貨日期",
"ETA To": "預計到貨日期至",
"Details": "詳情",
"Supplier": "供應商",
"Status": "來貨狀態",
"Escalated": "上報狀態",
"NotEscalated": "無上報",
"Do you want to start?": "確定開始嗎?",
"Start": "開始",
"Start Success": "開始成功",
"Start Fail": "開始失敗",
"Start PO": "開始採購訂單",
"Do you want to complete?": "確定完成嗎?",
"Cancel": "取消",
"Complete": "完成",
"Complete Success": "完成成功",
"Complete Fail": "完成失敗",
"Complete PO": "完成採購訂單",
"General": "一般",
"Bind Storage": "綁定倉位",
"Po No.": "採購訂單編號",
"PO No.": "採購訂單編號",
"itemNo": "貨品編號",
"itemName": "貨品名稱",
"Item Detail": "貨品詳情",
"Item Code": "貨品編號",
"Item Name": "貨品名稱",
"Item Qty": "貨品數量",
"Item": "貨品",
"Item Accepted Qty": "貨品已收貨數量",
"Item Purchase UoM": "貨品計量單位",
"qty": "訂單數量",
"uom": "計量單位",
"Stock UoM": "庫存單位",
"Stock In Qty": "收貨數量",
"total weight": "總重量",
"weight unit": "重量單位",
"price": "訂單貨值",
"processed": "已上架數量",
"expiryDate": "到期日",
"acceptedQty": "是次來貨數量",
"putawayQty": "上架數量",
"acceptQty": "揀收數量",
"printQty": "列印數量",
"qcResult": "品檢結果",
"weight": "重量",
"start": "開始",
"qc": "質量控制",
"escalation": "上報",
"stock in": "入庫",
"putaway": "上架",
"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": "數量不能大於剩餘數量",
"acceptQty must not greater than": "揀收數量不能大於",
"Record pol": "記錄採購訂單",
"Add some entries!": "添加條目!",
"draft": "草稿",
"pending": "待處理",
"determine1": "上報1",
"determine2": "上報2",
"determine3": "上報3",
"receiving": "收貨中",
"received": "已檢收",
"completed": "已上架",
"rejected": "已拒絕及上報",
"status": "狀態",
"acceptedQty must not greater than": "接受數量不得大於",
"minimal value is 1": "最小值為1",
"value must be a number": "值必須是數字",
"qc Check": "質量控制檢查",
"Please select QC": "請選擇質量控制",
"failQty": "失敗數量",
"select qc": "選擇質量控制",
"enter a failQty": "請輸入失敗數量",
"qty too big": "數量過大",
"sampleRate": "抽樣率",
"sampleWeight": "樣本重量",
"totalWeight": "總重量",
"Escalation": "上報",
"to be processed": "待處理",
"supervisor": "管理層",
"Stock In Detail": "入庫詳情",
"productLotNo": "貨品批號",
"receiptDate": "收貨日期",
"acceptedWeight": "接受重量",
"productionDate": "生產日期",
"reportQty": "上報數量",
"Default Warehouse": "預設倉庫",
"Select warehouse": "選擇倉庫",
"Putaway Detail": "上架詳情",
"LotNo": "批號",
"Po Code": "採購訂單編號",
"No Warehouse": "沒有倉庫",
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。",
"receivedQty": "已來貨數量",
"dnQty": "送貨單數量",
"Accept submit": "接受來貨",
"qc processing": "處理來貨及品檢",
"putaway processing": "處理來貨及上架",
"view stockin": "查看收貨及品檢",
"putawayBtn": "上架",
"dnNo": "送貨單編號",
"dnDate": "送貨單日期",
"submitStockIn": "更新來貨資料",
"QC Info": "品檢資料",
"Escalation History": "上報記錄",
"Escalation Info": "上報資料",
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"confirm qc result": "確定品檢結果",
"warehouse": "倉庫",
"qcItem": "品檢項目",
"passed": "接受",
"failed": "不接受",
"failedQty": "不合格數",
"remarks": "備註",
"Reject": "拒絕",
"submit": "提交",
"print": "列印",
"bind": "綁定",
"Search": "搜尋",
"Found": "已找到"
}

Loading…
Cancel
Save