@@ -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> | |||
); | |||
@@ -30,9 +30,6 @@ export interface StockInLineEntry { | |||
acceptedQty: number; | |||
status?: string; | |||
expiryDate?: string; | |||
productLotNo?: string; | |||
receiptDate?: string; | |||
dnDate?: string; | |||
} | |||
export interface PurchaseQcResult { | |||
@@ -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), | |||
@@ -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>) => { | |||
@@ -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 | |||
} | |||
@@ -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 { | |||
@@ -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> | |||
@@ -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> | |||
)} | |||
/> | |||
@@ -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" | |||
@@ -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, | |||
}, | |||