ソースを参照

update qc modal

update po
master
kelvinsuen 5日前
コミット
0cfaa09f86
10個のファイルの変更83行の追加90行の削除
  1. +2
    -2
      src/app/(main)/layout.tsx
  2. +0
    -3
      src/app/api/dashboard/actions.ts
  3. +6
    -3
      src/app/api/po/actions.ts
  4. +13
    -12
      src/app/api/po/index.ts
  5. +1
    -1
      src/app/api/qc/index.ts
  6. +1
    -1
      src/components/PickOrderSearch/QcFormVer2.tsx
  7. +7
    -5
      src/components/PoDetail/PoDetail.tsx
  8. +16
    -11
      src/components/PoDetail/QcFormVer2.tsx
  9. +32
    -47
      src/components/PoDetail/QcStockInModalVer2.tsx
  10. +5
    -5
      src/components/PoDetail/dummyQcTemplate.tsx

+ 2
- 2
src/app/(main)/layout.tsx ファイルの表示

@@ -37,7 +37,7 @@ export default async function MainLayout({
return (
<SessionProviderWrapper session={session}>
<UploadProvider>
<CameraProvider>
{/* <CameraProvider> */}
<AxiosProvider>
<QrCodeScannerProvider>
<>
@@ -62,7 +62,7 @@ export default async function MainLayout({
</>
</QrCodeScannerProvider>
</AxiosProvider>
</CameraProvider>
{/* </CameraProvider> */}
</UploadProvider>
</SessionProviderWrapper>
);


+ 0
- 3
src/app/api/dashboard/actions.ts ファイルの表示

@@ -30,9 +30,6 @@ export interface StockInLineEntry {
acceptedQty: number;
status?: string;
expiryDate?: string;
productLotNo?: string;
receiptDate?: string;
dnDate?: string;
}

export interface PurchaseQcResult {


+ 6
- 3
src/app/api/po/actions.ts ファイルの表示

@@ -31,11 +31,14 @@ export interface StockInLineEntry {
acceptedQty: number;
status?: string;
expiryDate?: string;
productLotNo?: string;
receiptDate?: string;
dnDate?: string;
}

export interface PurchaseQcResult{
qcItemId: number;
isPassed: boolean;
qcPassed: boolean;
failQty: number;
remarks?: string;
@@ -111,7 +114,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => {

export const createStockInLine = async (data: StockInLineEntry) => {
const stockInLine = await serverFetchJson<
PostStockInLineResponse<StockInLineEntry>
PostStockInLineResponse<StockInLine>
>(`${BASE_API_URL}/stockInLine/create`, {
method: "POST",
body: JSON.stringify(data),
@@ -125,7 +128,7 @@ export const updateStockInLine = async (
data: StockInLineEntry & ModalFormInput,
) => {
const stockInLine = await serverFetchJson<
PostStockInLineResponse<StockInLineEntry & ModalFormInput>
PostStockInLineResponse<StockInLine & ModalFormInput>
>(`${BASE_API_URL}/stockInLine/update`, {
method: "POST",
body: JSON.stringify(data),


+ 13
- 12
src/app/api/po/index.ts ファイルの表示

@@ -54,7 +54,7 @@ export interface StockUomForPoLine {

export interface StockInLine {
id: number;
stockInId: number;
stockInId?: number;
purchaseOrderId?: number;
purchaseOrderLineId: number;
itemId: number;
@@ -63,23 +63,24 @@ export interface StockInLine {
itemType: string;
demandQty: number;
acceptedQty: number;
qty: number;
processed: number;
price: number;
priceUnit: string;
qty?: number;
processed?: number;
price?: number;
priceUnit?: string;
shelfLife?: number;
receiptDate?: string;
productionDate?: string;
productLotNo?: string;
expiryDate?: string;
status: string;
supplier: string;
lotNo: string;
poCode: string;
uom: Uom;
supplier?: string;
lotNo?: string;
poCode?: string;
uom?: Uom;
defaultWarehouseId: number; // id for now
dnNo: string;
dnDate: number[];
stockQty: number;
dnNo?: string;
dnDate?: number[];
stockQty?: number;
}

export const fetchPoList = cache(async (queryParams?: Record<string, any>) => {


+ 1
- 1
src/app/api/qc/index.ts ファイルの表示

@@ -20,7 +20,7 @@ export interface QcData {
code: string,
name: string,
qcDescription: string,
isPassed: boolean | undefined
qcPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
}


+ 1
- 1
src/components/PickOrderSearch/QcFormVer2.tsx ファイルの表示

@@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/dashboard/actions";
import { ModalFormInput } from "@/app/api/po/actions";
import { escape } from "lodash";

interface Props {


+ 7
- 5
src/components/PoDetail/PoDetail.tsx ファイルの表示

@@ -43,9 +43,11 @@ import {
import {
checkPolAndCompletePo,
fetchPoInClient,
fetchPoListClient,
fetchStockInLineInfo,
PurchaseQcResult,
startPo,
createStockInLine
} from "@/app/api/po/actions";
import {
useCallback,
@@ -69,9 +71,7 @@ import DoneIcon from "@mui/icons-material/Done";
import { getCustomWidth } from "@/app/utils/commonUtil";
import PoInfoCard from "./PoInfoCard";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import { fetchPoListClient } from "@/app/api/po/actions";
import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material";
import { createStockInLine } from "@/app/api/dashboard/actions";
import { Controller, FormProvider, useForm } from "react-hook-form";
import dayjs, { Dayjs } from "dayjs";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
@@ -81,7 +81,6 @@ import LoadingComponent from "../General/LoadingComponent";
//import { useRouter } from "next/navigation";



type Props = {
po: PoResult;
qc: QcItemWithChecks[];
@@ -397,6 +396,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
setTimeout(async () => {
// post stock in line
const oldId = row.id;
const acceptedQty = Number(polInputList[rowIndex].dnQty);
if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update

const postData = {
dnNo: dnFormProps.watch("dnNo"),
dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")),
@@ -405,7 +407,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
itemName: row.itemName,
purchaseOrderId: row.purchaseOrderId,
purchaseOrderLineId: row.id,
acceptedQty: polInputList[rowIndex].dnQty || 0,
acceptedQty: acceptedQty,
productLotNo: polInputList[rowIndex].lotNo || '',
// acceptedQty: secondReceiveQty || 0,
// acceptedQty: row.acceptedQty,
@@ -535,7 +537,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
handleStart()
}
>
提交
{t("submit")}
</Button>
</TableCell>
</TableRow>


+ 16
- 11
src/components/PoDetail/QcFormVer2.tsx ファイルの表示

@@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import StockInFormVer2 from "./StockInFormVer2";
import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate";
import { ModalFormInput } from "@/app/api/dashboard/actions";
import { ModalFormInput } from "@/app/api/po/actions";
import { escape } from "lodash";
import { PanoramaSharp } from "@mui/icons-material";

@@ -197,7 +197,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
),
},
{
field: 'qcResult',
field: 'qcPassed',
headerName: t("qcResult"),
flex: 1.5,
renderCell: (params) => {
@@ -208,14 +208,15 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
value={currentValue.isPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.isPassed ? "true" : "false")}
value={currentValue.qcPassed === undefined ? "" : (currentValue.qcPassed ? "true" : "false")}
// value={currentValue.qcPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.qcPassed ? "true" : "false")}
onChange={(e) => {
const value = e.target.value;
setQcItems((prev) =>
prev.map((r): QcData => (r.id === params.id ? { ...r, isPassed: value === "true" } : r))
prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
);
}}
name={`isPassed-${params.id}`}
name={`qcPassed-${params.id}`}
>
<FormControlLabel
value="true"
@@ -223,7 +224,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
label="合格"
disabled={disabled}
sx={{
color: currentValue.isPassed === true ? "green" : "inherit",
color: currentValue.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
@@ -233,7 +234,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
label="不合格"
disabled={disabled}
sx={{
color: currentValue.isPassed === false ? "red" : "inherit",
color: currentValue.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
}}
/>
@@ -251,8 +252,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<TextField
type="number"
size="small"
value={!params.row.isPassed? (params.value ?? '') : '0'}
disabled={params.row.isPassed || disabled}
value={!params.row.qcPassed? (params.value ?? '') : '0'}
disabled={params.row.qcPassed || disabled}
onChange={(e) => {
const v = e.target.value;
const next = v === '' ? undefined : Number(v);
@@ -313,7 +314,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);

const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => {
const isFailed = qcItems.some((qc) => !qc.isPassed)
const isFailed = qcItems.some((qc) => !qc.qcPassed)
console.log(isFailed)
if (isFailed) {
setIsCollapsed(true)
@@ -439,7 +440,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI
<FormControlLabel disabled={disabled}
value="false" control={<Radio />}
sx={{"& .Mui-checked": {color: "red"}}}
label="不接受及上報" />
label="不接受" />
<FormControlLabel disabled={disabled}
value="false" control={<Radio />}
sx={{"& .Mui-checked": {color: "blue"}}}
label="上報品檢結果" />
</RadioGroup>
)}
/>


+ 32
- 47
src/components/PoDetail/QcStockInModalVer2.tsx ファイルの表示

@@ -1,6 +1,6 @@
"use client";
import { StockInLine } from "@/app/api/po";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import {
Box,
@@ -22,7 +22,6 @@ import PutawayForm from "./PutawayForm";
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";
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil";
import dayjs from "dayjs";

@@ -114,10 +113,11 @@ const [qcItems, setQcItems] = useState(dummyQCData)
}, [open])
const [isCompleted, setIsCompleted] = useState(false);
const [viewOnly, setViewOnly] = useState(false);
useEffect(() => {
setIsCompleted(itemDetail.status.toLowerCase() == "completed")
const isViewOnly = itemDetail.status.toLowerCase() == "completed" || itemDetail.status.toLowerCase() == "rejected"
setViewOnly(isViewOnly)
}, [itemDetail]);

const [openPutaway, setOpenPutaway] = useState(false);
@@ -162,12 +162,12 @@ const [qcItems, setQcItems] = useState(dummyQCData)
const qcAccept = data.qcAccept;
const acceptQty = data.acceptQty as number;
const qcResults = qcItems;
// const qcResults = isCompleted? data.qcResult as PurchaseQcResult[] : qcItems;
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
// Validate QC data
const validationErrors : string[] = [];
// Check if all QC items have results
const itemsWithoutResult = qcResults.filter(item => item.isPassed === undefined);
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined);
if (itemsWithoutResult.length > 0) {
validationErrors.push(`${t("QC items without result")}`);
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
@@ -175,7 +175,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)

// Check if failed items have failed quantity
const failedItemsWithoutQty = qcResults.filter(item =>
item.isPassed === false && (!item.failQty || item.failQty <= 0)
item.qcPassed === false && (!item.failQty || item.failQty <= 0)
);
if (failedItemsWithoutQty.length > 0) {
validationErrors.push(`${t("Failed items must have failed quantity")}`);
@@ -194,10 +194,16 @@ const [qcItems, setQcItems] = useState(dummyQCData)

// Check if dates are input
if (data.productionDate === undefined || data.productionDate == null) {
validationErrors.push("Production Date cannot be null!");
validationErrors.push("請輸入生產日期!");
}
if (data.expiryDate === undefined || data.expiryDate == null) {
validationErrors.push("Expiry Date cannot be null!");
validationErrors.push("請輸入到期日!");
}
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept) {
validationErrors.push("有不合格檢查項目,無法收貨!");
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
// confirmButtonText: t("confirm putaway"), html: ""});
// return;
}

if (validationErrors.length > 0) {
@@ -219,8 +225,8 @@ const [qcItems, setQcItems] = useState(dummyQCData)
qcItemId: item.id,
// code: item.code,
// qcDescription: item.qcDescription,
isPassed: item.isPassed? item.isPassed : false,
failQty: (item.failQty && !item.isPassed) ? item.failQty : 0,
qcPassed: item.qcPassed? item.qcPassed : false,
failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0,
// failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0,
remarks: item.remarks || ''
}))
@@ -228,42 +234,21 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// const qcData = data;

console.log("QC Data for submission:", qcData);

await postStockInLine(qcData);
if (!qcData.qcResult.every((qc) => qc.isPassed) && qcData.qcAccept) {
submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
confirmButtonText: t("confirm putaway"), html: ""});
return;
if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""});
onOpenPutaway();
} else {
closeHandler({}, "backdropClick");
}
await postStockInLineWithQc(qcData);
// return;
return ;
},
[onOpenPutaway, qcItems],
);
const postStockInLineWithQc = useCallback(async (qcData: PurchaseQCInput) => {
const args = {
...qcData
// id: itemDetail.id,
// purchaseOrderId: itemDetail.purchaseOrderId,
// purchaseOrderLineId: itemDetail.purchaseOrderLineId,
// itemId: itemDetail.itemId,
// ...data,
// productionDate: productionDate,
// expiryDate: expiryDate,
// receiptDate: receiptDate,
} as ModalFormInput;

await postStockInLine(args);

if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""});
onOpenPutaway();
} else {
closeHandler({}, "backdropClick");
}
return ;
},[onOpenPutaway,closeHandler]);

const postStockInLine = useCallback(async (args: ModalFormInput) => {
const submitData = {
@@ -340,7 +325,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
const acceptQty = formProps.watch("acceptedQty")

const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => {
const isPassed = qcItems.every((qc) => qc.isPassed);
const isPassed = qcItems.every((qc) => qc.qcPassed);
console.log(isPassed)
if (isPassed) {
formProps.setValue("passingQty", acceptQty)
@@ -378,7 +363,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<PutawayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={isCompleted}
disabled={viewOnly}
/>
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
@@ -416,7 +401,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
</Typography>
</Grid>
<Grid item xs={12}>
<StockInFormVer2 itemDetail={itemDetail} disabled={isCompleted} />
<StockInFormVer2 itemDetail={itemDetail} disabled={viewOnly} />
</Grid>
</Grid>
{/* <Stack direction="row" justifyContent="flex-end" gap={1}>
@@ -438,13 +423,13 @@ const [qcItems, setQcItems] = useState(dummyQCData)
<QcFormVer2
qc={qc!}
itemDetail={itemDetail}
disabled={isCompleted}
disabled={viewOnly}
qcItems={qcItems}
setQcItems={setQcItems}
/>
</Grid>
<Stack direction="row" justifyContent="flex-end" gap={1}>
{!isCompleted && (<Button
{!viewOnly && (<Button
id="qcSubmit"
type="button"
variant="contained"


+ 5
- 5
src/components/PoDetail/dummyQcTemplate.tsx ファイルの表示

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


読み込み中…
キャンセル
保存