@@ -29,7 +29,7 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => { | |||||
return ( | return ( | ||||
<> | <> | ||||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | ||||
<I18nProvider namespaces={[type]}> | |||||
<I18nProvider namespaces={[type, "dashboard"]}> | |||||
<Suspense fallback={<PoDetail.Loading />}> | <Suspense fallback={<PoDetail.Loading />}> | ||||
<PoDetail id={id} /> | <PoDetail id={id} /> | ||||
</Suspense> | </Suspense> | ||||
@@ -0,0 +1,21 @@ | |||||
"use server" | |||||
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | |||||
import { cache } from "react"; | |||||
import { EscalationResult } from "."; | |||||
export const fetchEscalationLogsByStockInLines = cache(async(stockInLineIds: number[]) => { | |||||
const searchParams = convertObjToURLSearchParams({stockInLineIds: stockInLineIds}) | |||||
return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/stockInLines?${searchParams}`, | |||||
{ | |||||
method: "GET", | |||||
headers: { "Content-Type": "application/json" }, | |||||
next: { | |||||
tags: ["escalationLogs"], | |||||
}, | |||||
}, | |||||
); | |||||
}); |
@@ -32,19 +32,6 @@ export interface EscalationResult { | |||||
dnNo?: string; | dnNo?: string; | ||||
} | } | ||||
export const fetchEscalationLogsByStockInLines = cache(async(stockInLineIds: number[]) => { | |||||
const searchParams = convertObjToURLSearchParams({stockInLineIds: stockInLineIds}) | |||||
return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/stockInLines?${searchParams}`, | |||||
{ | |||||
method: "GET", | |||||
headers: { "Content-Type": "application/json" }, | |||||
next: { | |||||
tags: ["escalationLogs"], | |||||
}, | |||||
}, | |||||
); | |||||
}); | |||||
export const fetchEscalationLogsByUser = cache(async() => { | export const fetchEscalationLogsByUser = cache(async() => { | ||||
return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/user`, | return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/user`, | ||||
{ | { | ||||
@@ -38,8 +38,8 @@ export interface StockInLineEntry { | |||||
export interface PurchaseQcResult{ | export interface PurchaseQcResult{ | ||||
qcItemId: number; | qcItemId: number; | ||||
qcPassed: boolean; | |||||
failQty: number; | |||||
qcPassed?: boolean; | |||||
failQty?: number; | |||||
remarks?: string; | remarks?: string; | ||||
} | } | ||||
@@ -69,14 +69,16 @@ export interface PurchaseQCInput { | |||||
sampleWeight: number; | sampleWeight: number; | ||||
totalWeight: number; | totalWeight: number; | ||||
qcAccept: boolean; | qcAccept: boolean; | ||||
qcDecision?: number; | |||||
qcResult: PurchaseQcResult[]; | qcResult: PurchaseQcResult[]; | ||||
} | } | ||||
export interface EscalationInput { | export interface EscalationInput { | ||||
status: string; | status: string; | ||||
remarks?: string; | remarks?: string; | ||||
handler: string; | |||||
productLotNo: string; | |||||
acceptedQty: number; // this is the qty to be escalated | |||||
reason?: string; | |||||
handlerId: number; | |||||
productLotNo?: string; | |||||
acceptedQty?: number; // this is the qty to be escalated | |||||
// escalationQty: number | // escalationQty: number | ||||
} | } | ||||
export interface PutawayLine { | export interface PutawayLine { | ||||
@@ -94,8 +96,10 @@ export interface PutawayInput { | |||||
} | } | ||||
export type ModalFormInput = Partial< | export type ModalFormInput = Partial< | ||||
PurchaseQCInput & StockInInput & EscalationInput & PutawayInput | |||||
>; | |||||
PurchaseQCInput & StockInInput & PutawayInput | |||||
> & { | |||||
escalationLog? : Partial<EscalationInput> | |||||
}; | |||||
export const testFetch = cache(async (id: number) => { | export const testFetch = cache(async (id: number) => { | ||||
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | ||||
@@ -81,6 +81,7 @@ export interface StockInLine { | |||||
dnNo?: string; | dnNo?: string; | ||||
dnDate?: number[]; | dnDate?: number[]; | ||||
stockQty?: number; | stockQty?: number; | ||||
handlerId?: number; | |||||
} | } | ||||
export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | ||||
@@ -32,6 +32,8 @@ export const INPUT_DATE_FORMAT = "YYYY-MM-DD"; | |||||
export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD"; | export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD"; | ||||
export const INPUT_TIME_FORMAT = "HH:mm:ss"; | |||||
export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | ||||
export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => { | export const arrayToDayjs = (arr: ConfigType | (number | undefined)[]) => { | ||||
@@ -73,6 +75,10 @@ export const dayjsToDateString = (date: Dayjs) => { | |||||
}; | }; | ||||
export const dayjsToInputDateString = (date: Dayjs) => { | export const dayjsToInputDateString = (date: Dayjs) => { | ||||
return date.format(INPUT_DATE_FORMAT + "T" + INPUT_TIME_FORMAT); | |||||
}; | |||||
export const dayjsToInputDatetimeString = (date: Dayjs) => { | |||||
return date.format(INPUT_DATE_FORMAT); | return date.format(INPUT_DATE_FORMAT); | ||||
}; | }; | ||||
@@ -62,40 +62,57 @@ const EscalationLogTable: React.FC<Props> = ({ | |||||
() => [ | () => [ | ||||
{ | { | ||||
name: "handler", | name: "handler", | ||||
label: t("Responsible for handling colleagues") | |||||
label: t("Responsible for handling colleagues"), | |||||
sx: { width: "20%", minWidth: 200, maxWidth: 500 }, | |||||
}, | }, | ||||
{ | { | ||||
name: "acceptedQty", | name: "acceptedQty", | ||||
label: t("Received Qty"), | label: t("Received Qty"), | ||||
align: "right", | align: "right", | ||||
headerAlign: "right" | |||||
headerAlign: "right", | |||||
sx: { width: "10%", minWidth: 100 }, | |||||
}, | }, | ||||
{ | { | ||||
name: "purchaseUomDesc", | name: "purchaseUomDesc", | ||||
label: t("Purchase UoM") | |||||
label: t("Purchase UoM"), | |||||
sx: { width: "15%", minWidth: 120 }, | |||||
}, | }, | ||||
{ | { | ||||
name: "dnDate", | name: "dnDate", | ||||
label: t("DN Date"), | label: t("DN Date"), | ||||
sx: { width: "10%", minWidth: 120 }, | |||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return params.dnDate ? arrayToDateString(params.dnDate) : "N/A" | return params.dnDate ? arrayToDateString(params.dnDate) : "N/A" | ||||
} | } | ||||
}, | }, | ||||
{ | |||||
name: "qcTotalCount", | |||||
label: t("QC Completed Count"), | |||||
align: "right", | |||||
headerAlign: "right" | |||||
}, | |||||
{ | { | ||||
name: "qcFailCount", | name: "qcFailCount", | ||||
label: t("QC Fail Count"), | label: t("QC Fail Count"), | ||||
align: "right", | align: "right", | ||||
headerAlign: "right" | |||||
headerAlign: "right", | |||||
sx: { width: "15%", minWidth: 120 }, | |||||
renderCell: (params) => { | |||||
return `${params.qcFailCount} / ${params.qcTotalCount}` | |||||
} | |||||
}, | }, | ||||
// { | |||||
// name: "qcTotalCount", | |||||
// label: t("QC Completed Count"), | |||||
// align: "right", | |||||
// headerAlign: "right" | |||||
// flex: 1, | |||||
// }, | |||||
// { | |||||
// name: "qcFailCount", | |||||
// label: t("QC Fail Count"), | |||||
// align: "right", | |||||
// headerAlign: "right" | |||||
// flex: 1, | |||||
// }, | |||||
{ | { | ||||
name: "reason", | name: "reason", | ||||
label: t("Reason"), | label: t("Reason"), | ||||
sx: { width: "30%", minWidth: 150 }, | |||||
}, | }, | ||||
], []) | ], []) | ||||
@@ -21,6 +21,8 @@ import { SelectChangeEvent } from '@mui/material/Select'; | |||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | ||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | import ExpandLessIcon from '@mui/icons-material/ExpandLess'; | ||||
import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
import { useFormContext } from 'react-hook-form'; | |||||
import { EscalationInput, ModalFormInput } from '@/app/api/po/actions'; | |||||
interface NameOption { | interface NameOption { | ||||
value: string; | value: string; | ||||
@@ -38,6 +40,7 @@ interface Props { | |||||
isCollapsed: boolean | isCollapsed: boolean | ||||
setIsCollapsed: Dispatch<React.SetStateAction<boolean>> | setIsCollapsed: Dispatch<React.SetStateAction<boolean>> | ||||
} | } | ||||
const EscalationComponent: React.FC<Props> = ({ | const EscalationComponent: React.FC<Props> = ({ | ||||
forSupervisor, | forSupervisor, | ||||
isCollapsed, | isCollapsed, | ||||
@@ -60,6 +63,19 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
{ value: 'david', label: '林建國' }, | { value: 'david', label: '林建國' }, | ||||
]; | ]; | ||||
const { | |||||
register, | |||||
formState: { errors, defaultValues, touchedFields }, | |||||
watch, | |||||
control, | |||||
setValue, | |||||
getValues, | |||||
reset, | |||||
resetField, | |||||
setError, | |||||
clearErrors, | |||||
} = useFormContext<ModalFormInput>(); | |||||
const handleInputChange = ( | const handleInputChange = ( | ||||
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string> | event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SelectChangeEvent<string> | ||||
): void => { | ): void => { | ||||
@@ -70,7 +86,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
})); | })); | ||||
}; | }; | ||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => { | |||||
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {console.log("called this?"); | |||||
e.preventDefault(); | e.preventDefault(); | ||||
console.log('表單已提交:', formData); | console.log('表單已提交:', formData); | ||||
// 處理表單提交 | // 處理表單提交 | ||||
@@ -118,9 +134,12 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | ||||
<FormControl fullWidth> | <FormControl fullWidth> | ||||
<select | <select | ||||
id="name" | |||||
name="name" | |||||
value={formData.name} | |||||
id="handlerId" | |||||
// name="name" | |||||
// value={formData.name} | |||||
{...register("escalationLog.handlerId", { | |||||
required: "handler required!", | |||||
})} | |||||
onChange={handleInputChange} | onChange={handleInputChange} | ||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white" | className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white" | ||||
> | > | ||||
@@ -169,12 +188,15 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
<TextField | <TextField | ||||
fullWidth | fullWidth | ||||
id="message" | |||||
name="message" | |||||
id="reason" | |||||
// name="reason" | |||||
{...register("escalationLog.reason", { | |||||
required: "reason required!", | |||||
})} | |||||
label="上報原因" | label="上報原因" | ||||
multiline | multiline | ||||
rows={4} | rows={4} | ||||
value={formData.message} | |||||
// value={formData.reason} | |||||
onChange={handleInputChange} | onChange={handleInputChange} | ||||
placeholder="請輸入上報原因" | placeholder="請輸入上報原因" | ||||
/> | /> | ||||
@@ -9,6 +9,7 @@ import { | |||||
GridRowModes, | GridRowModes, | ||||
GridRowModesModel, | GridRowModesModel, | ||||
GridToolbarContainer, | GridToolbarContainer, | ||||
GridValidRowModel, | |||||
useGridApiRef, | useGridApiRef, | ||||
} from "@mui/x-data-grid"; | } from "@mui/x-data-grid"; | ||||
import { | import { | ||||
@@ -58,8 +59,12 @@ import { fetchQcResult } from "@/app/api/qc/actions"; | |||||
import PoQcStockInModal from "./PoQcStockInModal"; | import PoQcStockInModal from "./PoQcStockInModal"; | ||||
import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | import DoDisturbIcon from "@mui/icons-material/DoDisturb"; | ||||
import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
// import { SessionWithTokens } from "src/config/authConfig"; | |||||
import PoQcStockInModalVer2 from "./QcStockInModalVer2"; | import PoQcStockInModalVer2 from "./QcStockInModalVer2"; | ||||
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | ||||
import { EscalationResult } from "@/app/api/escalation"; | |||||
import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | |||||
import { SessionWithTokens } from "@/config/authConfig"; | |||||
interface ResultWithId { | interface ResultWithId { | ||||
id: number; | id: number; | ||||
@@ -130,7 +135,7 @@ function PoInputGrid({ | |||||
setEntries(stockInLine) | setEntries(stockInLine) | ||||
}, [stockInLine]) | }, [stockInLine]) | ||||
const [modalInfo, setModalInfo] = useState< | const [modalInfo, setModalInfo] = useState< | ||||
StockInLine & { qcResult?: PurchaseQcResult[] } | |||||
StockInLine & { qcResult?: PurchaseQcResult[] } & { escalationResult?: EscalationResult[] } | |||||
>(); | >(); | ||||
const pathname = usePathname() | const pathname = usePathname() | ||||
const router = useRouter(); | const router = useRouter(); | ||||
@@ -151,6 +156,7 @@ function PoInputGrid({ | |||||
}); | }); | ||||
const { data: session } = useSession(); | const { data: session } = useSession(); | ||||
const sessionToken = session as SessionWithTokens | null; | |||||
useEffect(() => { | useEffect(() => { | ||||
const completedList = entries.filter( | const completedList = entries.filter( | ||||
@@ -245,7 +251,7 @@ function PoInputGrid({ | |||||
return await fetchQcResult(stockInLineId as number); | return await fetchQcResult(stockInLineId as number); | ||||
}, []); | }, []); | ||||
const handleQC = useCallback( | |||||
const handleQC = useCallback( // UNUSED NOW! | |||||
(id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
setBtnIsLoading(true); | setBtnIsLoading(true); | ||||
setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
@@ -253,8 +259,10 @@ function PoInputGrid({ | |||||
[id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
})); | })); | ||||
const qcResult = await fetchQcDefaultValue(id); | const qcResult = await fetchQcDefaultValue(id); | ||||
const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||||
console.log(params.row); | console.log(params.row); | ||||
console.log(qcResult); | console.log(qcResult); | ||||
setModalInfo({ | setModalInfo({ | ||||
...params.row, | ...params.row, | ||||
qcResult: qcResult, | qcResult: qcResult, | ||||
@@ -273,6 +281,7 @@ function PoInputGrid({ | |||||
const [newOpen, setNewOpen] = useState(false); | const [newOpen, setNewOpen] = useState(false); | ||||
const stockInLineId = searchParams.get("stockInLineId"); | const stockInLineId = searchParams.get("stockInLineId"); | ||||
const poLineId = searchParams.get("poLineId"); | const poLineId = searchParams.get("poLineId"); | ||||
const closeNewModal = useCallback(() => { | const closeNewModal = useCallback(() => { | ||||
const newParams = new URLSearchParams(searchParams.toString()); | const newParams = new URLSearchParams(searchParams.toString()); | ||||
newParams.delete("stockInLineId"); // Remove the parameter | newParams.delete("stockInLineId"); // Remove the parameter | ||||
@@ -300,9 +309,12 @@ const closeNewModal = useCallback(() => { | |||||
})); | })); | ||||
const qcResult = await fetchQcDefaultValue(id); | const qcResult = await fetchQcDefaultValue(id); | ||||
const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); | |||||
setModalInfo(() => ({ | setModalInfo(() => ({ | ||||
...params.row, | ...params.row, | ||||
qcResult: qcResult, | qcResult: qcResult, | ||||
escResult: escResult, | |||||
receivedQty: itemDetail.receivedQty, | receivedQty: itemDetail.receivedQty, | ||||
})); | })); | ||||
@@ -424,10 +436,14 @@ const closeNewModal = useCallback(() => { | |||||
[], | [], | ||||
); | ); | ||||
const getButtonSx = (status : string) => { | |||||
const getButtonSx = (sil : StockInLine) => { | |||||
const status = sil?.status?.toLowerCase(); | |||||
let btnSx = {label:"", color:""}; | let btnSx = {label:"", color:""}; | ||||
switch (status) { | switch (status) { | ||||
case "received": btnSx = {label: t("putaway processing"), color:"secondary.main"}; break; | case "received": btnSx = {label: t("putaway processing"), color:"secondary.main"}; break; | ||||
case "escalated": if (sessionToken?.id == sil?.handlerId) { | |||||
btnSx = {label: t("escalation processing"), color:"warning.main"}; | |||||
break;} | |||||
case "rejected": | case "rejected": | ||||
case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break; | case "completed": btnSx = {label: t("view stockin"), color:"info.main"}; break; | ||||
default: btnSx = {label: t("qc processing"), color:"success.main"}; | default: btnSx = {label: t("qc processing"), color:"success.main"}; | ||||
@@ -476,7 +492,7 @@ const closeNewModal = useCallback(() => { | |||||
headerName: t("dnDate"), | headerName: t("dnDate"), | ||||
width: 125, | width: 125, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
console.log(params.row) | |||||
// console.log(params.row) | |||||
// return <>07/08/2025</> | // return <>07/08/2025</> | ||||
return arrayToDateString(params.value) | return arrayToDateString(params.value) | ||||
} | } | ||||
@@ -511,7 +527,7 @@ const closeNewModal = useCallback(() => { | |||||
width: 120, | width: 120, | ||||
// flex: 0.5, | // flex: 0.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return params.row.uom.code; | |||||
return params.row.uom?.code; | |||||
}, | }, | ||||
}, | }, | ||||
{ | { | ||||
@@ -557,7 +573,12 @@ const closeNewModal = useCallback(() => { | |||||
width: 140, | width: 140, | ||||
// flex: 0.5, | // flex: 0.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
return t(`${params.row.status}`); | |||||
const handlerId = params.row.handlerId | |||||
const status = params.row.status | |||||
return (<span style={{ | |||||
color: (status == "escalated")? "red":"inherit"}}> | |||||
{t(`${params.row.status}`)} | |||||
</span>); | |||||
}, | }, | ||||
}, | }, | ||||
{ | { | ||||
@@ -572,9 +593,9 @@ const closeNewModal = useCallback(() => { | |||||
// flex: 2, | // flex: 2, | ||||
cellClassName: "actions", | cellClassName: "actions", | ||||
getActions: (params) => { | getActions: (params) => { | ||||
const data = params.row; | |||||
// console.log(params.row.status); | // console.log(params.row.status); | ||||
const status = params.row.status.toLowerCase(); | |||||
const btnSx = getButtonSx(status); | |||||
const btnSx = getButtonSx(data); | |||||
// console.log(stockInLineStatusMap[status]); | // console.log(stockInLineStatusMap[status]); | ||||
// console.log(session?.user?.abilities?.includes("APPROVAL")); | // console.log(session?.user?.abilities?.includes("APPROVAL")); | ||||
return [ | return [ | ||||
@@ -754,7 +775,7 @@ const closeNewModal = useCallback(() => { | |||||
}, | }, | ||||
}, | }, | ||||
], | ], | ||||
[t, handleStart, handleQC, handleEscalation, session?.user?.abilities, handleStockIn, handlePutAway, handleDelete, handleReject, itemDetail], | |||||
[t, handleStart, handleQC, handleEscalation, handleStockIn, handlePutAway, handleDelete, handleReject, itemDetail], | |||||
); | ); | ||||
const addRow = useCallback(() => { | const addRow = useCallback(() => { | ||||
@@ -911,6 +932,7 @@ const closeNewModal = useCallback(() => { | |||||
setEntries={setEntries} | setEntries={setEntries} | ||||
setStockInLine={setStockInLine} | setStockInLine={setStockInLine} | ||||
setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
session={sessionToken} | |||||
qc={qc} | qc={qc} | ||||
warehouse={warehouse} | warehouse={warehouse} | ||||
open={newOpen} | open={newOpen} | ||||
@@ -921,7 +943,7 @@ const closeNewModal = useCallback(() => { | |||||
</> | </> | ||||
) | ) | ||||
} | } | ||||
{modalInfo !== undefined && ( | |||||
{/* {modalInfo !== undefined && ( | |||||
<> | <> | ||||
<PoQcStockInModal | <PoQcStockInModal | ||||
type={"qc"} | type={"qc"} | ||||
@@ -995,7 +1017,7 @@ const closeNewModal = useCallback(() => { | |||||
itemDetail={modalInfo} | itemDetail={modalInfo} | ||||
/> | /> | ||||
</> | </> | ||||
)} | |||||
)} */} | |||||
</> | </> | ||||
); | ); | ||||
} | } | ||||
@@ -52,9 +52,11 @@ import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | |||||
import { ModalFormInput } from "@/app/api/po/actions"; | import { ModalFormInput } from "@/app/api/po/actions"; | ||||
import { escape } from "lodash"; | import { escape } from "lodash"; | ||||
import { PanoramaSharp } from "@mui/icons-material"; | import { PanoramaSharp } from "@mui/icons-material"; | ||||
import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable"; | |||||
import { EscalationResult } from "@/app/api/escalation"; | |||||
interface Props { | interface Props { | ||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
disabled: boolean; | disabled: boolean; | ||||
qcItems: QcData[] | qcItems: QcData[] | ||||
@@ -90,6 +92,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); | const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); | ||||
// const [qcResult, setQcResult] = useState(); | // const [qcResult, setQcResult] = useState(); | ||||
const qcAccept = watch("qcAccept"); | const qcAccept = watch("qcAccept"); | ||||
const qcDecision = watch("qcDecision"); //WIP | |||||
const qcResult = watch("qcResult"); | const qcResult = watch("qcResult"); | ||||
console.log(qcResult); | console.log(qcResult); | ||||
// const [qcAccept, setQcAccept] = useState(true); | // const [qcAccept, setQcAccept] = useState(true); | ||||
@@ -119,28 +122,29 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
//// validate form | //// validate form | ||||
const accQty = watch("acceptQty"); | const accQty = watch("acceptQty"); | ||||
const validateForm = useCallback(() => { | const validateForm = useCallback(() => { | ||||
console.log(accQty); | |||||
if (accQty > itemDetail.acceptedQty) { | |||||
setError("acceptQty", { | |||||
message: `${t("acceptQty must not greater than")} ${ | |||||
itemDetail.acceptedQty | |||||
}`, | |||||
type: "required", | |||||
}); | |||||
if (qcDecision == 1) { | |||||
if (accQty > itemDetail.acceptedQty) { | |||||
setError("acceptQty", { | |||||
message: `${t("acceptQty must not greater than")} ${ | |||||
itemDetail.acceptedQty | |||||
}`, | |||||
type: "required", | |||||
}); | |||||
} | |||||
if (accQty < 1) { | |||||
setError("acceptQty", { | |||||
message: t("minimal value is 1"), | |||||
type: "required", | |||||
}); | |||||
} | |||||
if (isNaN(accQty)) { | |||||
setError("acceptQty", { | |||||
message: t("value must be a number"), | |||||
type: "required", | |||||
}); | |||||
} | |||||
} | } | ||||
if (accQty < 1) { | |||||
setError("acceptQty", { | |||||
message: t("minimal value is 1"), | |||||
type: "required", | |||||
}); | |||||
} | |||||
if (isNaN(accQty)) { | |||||
setError("acceptQty", { | |||||
message: t("value must be a number"), | |||||
type: "required", | |||||
}); | |||||
} | |||||
}, [accQty]); | |||||
}, [accQty, qcDecision]); | |||||
useEffect(() => { | useEffect(() => { | ||||
clearErrors(); | clearErrors(); | ||||
@@ -191,7 +195,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
flex: 2, | flex: 2, | ||||
renderCell: (params) => ( | renderCell: (params) => ( | ||||
<Box> | <Box> | ||||
<b>{params.value}</b><br/> | |||||
<b>{`${params.api.getRowIndexRelativeToVisibleRows(params.id) + 1}. ${params.value}`}</b><br/> | |||||
{params.row.name}<br/> | {params.row.name}<br/> | ||||
</Box> | </Box> | ||||
), | ), | ||||
@@ -202,7 +206,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
flex: 1.5, | flex: 1.5, | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
const currentValue = params.row; | const currentValue = params.row; | ||||
console.log(currentValue.row); | |||||
const index = params.api.getRowIndexRelativeToVisibleRows(params.id); | |||||
// console.log(currentValue.row); | |||||
return ( | return ( | ||||
<FormControl> | <FormControl> | ||||
<RadioGroup | <RadioGroup | ||||
@@ -215,6 +220,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
setQcItems((prev) => | setQcItems((prev) => | ||||
prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r)) | prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r)) | ||||
); | ); | ||||
// setValue(`qcResult.${index}.qcPassed`, value == "true"); | |||||
}} | }} | ||||
name={`qcPassed-${params.id}`} | name={`qcPassed-${params.id}`} | ||||
> | > | ||||
@@ -222,7 +228,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
value="true" | value="true" | ||||
control={<Radio />} | control={<Radio />} | ||||
label="合格" | label="合格" | ||||
disabled={disabled} | |||||
disabled={disabled || itemDetail.status == "escalated"} | |||||
sx={{ | sx={{ | ||||
color: currentValue.qcPassed === true ? "green" : "inherit", | color: currentValue.qcPassed === true ? "green" : "inherit", | ||||
"& .Mui-checked": {color: "green"} | "& .Mui-checked": {color: "green"} | ||||
@@ -232,7 +238,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
value="false" | value="false" | ||||
control={<Radio />} | control={<Radio />} | ||||
label="不合格" | label="不合格" | ||||
disabled={disabled} | |||||
disabled={disabled || itemDetail.status == "escalated"} | |||||
sx={{ | sx={{ | ||||
color: currentValue.qcPassed === false ? "red" : "inherit", | color: currentValue.qcPassed === false ? "red" : "inherit", | ||||
"& .Mui-checked": {color: "red"} | "& .Mui-checked": {color: "red"} | ||||
@@ -253,7 +259,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
type="number" | type="number" | ||||
size="small" | size="small" | ||||
value={!params.row.qcPassed? (params.value ?? '') : '0'} | value={!params.row.qcPassed? (params.value ?? '') : '0'} | ||||
disabled={params.row.qcPassed || disabled} | |||||
disabled={params.row.qcPassed || disabled || itemDetail.status == "escalated"} | |||||
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); | ||||
@@ -279,7 +285,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
<TextField | <TextField | ||||
size="small" | size="small" | ||||
value={params.value ?? ''} | value={params.value ?? ''} | ||||
disabled={disabled} | |||||
disabled={disabled || itemDetail.status == "escalated"} | |||||
onChange={(e) => { | onChange={(e) => { | ||||
const remarks = e.target.value; | const remarks = e.target.value; | ||||
// const next = v === '' ? undefined : Number(v); | // const next = v === '' ? undefined : Number(v); | ||||
@@ -331,13 +337,29 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
useEffect(() => { | useEffect(() => { | ||||
console.log("ItemDetail in QC:", itemDetail); | console.log("ItemDetail in QC:", itemDetail); | ||||
console.log("Qc Result in QC:", qcResult); | |||||
}, [itemDetail]); | }, [itemDetail]); | ||||
const setQcDecision = (status : string | undefined) => { | |||||
const param = status?.toLowerCase(); | |||||
if (param !== undefined && param !== null) { | |||||
if (param == "completed") { | |||||
return 1; | |||||
} else if (param == "rejected") { | |||||
return 2; | |||||
} else if (param == "escalated") { | |||||
return 3; | |||||
} else { return undefined; } | |||||
} else { | |||||
return undefined; | |||||
} | |||||
} | |||||
useEffect(() => { | useEffect(() => { | ||||
// onFailedOpenCollapse(qcItems) // This function is no longer needed | // onFailedOpenCollapse(qcItems) // This function is no longer needed | ||||
}, [qcItems]); // Removed onFailedOpenCollapse from dependency array | |||||
}, [qcItems]); | |||||
return ( | return ( | ||||
<> | <> | ||||
@@ -362,6 +384,19 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
{tabIndex == 0 && ( | {tabIndex == 0 && ( | ||||
<> | <> | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}> | |||||
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}> | |||||
Group A - 急凍貨類 (QCA1-MEAT01) | |||||
</Typography> | |||||
<Typography variant="subtitle1" sx={{ color: '#666' }}> | |||||
<b>品檢類型</b>:IQC | |||||
</Typography> | |||||
<Typography variant="subtitle2" sx={{ color: '#666' }}> | |||||
記錄探測溫度的時間,請在1小時内完成卸貨盤點入庫,以保障食品安全<br/> | |||||
監察方法:目視檢查、嗅覺檢查和使用適當的食物溫度計,檢查食物溫度是否符合指標 | |||||
</Typography> | |||||
</Box> | |||||
{/* <QcDataGrid<ModalFormInput, QcData, EntryError> | {/* <QcDataGrid<ModalFormInput, QcData, EntryError> | ||||
apiRef={apiRef} | apiRef={apiRef} | ||||
columns={qcColumns} | columns={qcColumns} | ||||
@@ -370,7 +405,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
/> */} | /> */} | ||||
<StyledDataGrid | <StyledDataGrid | ||||
columns={qcColumns} | columns={qcColumns} | ||||
rows={disabled? qcResult:qcItems} | |||||
rows={qcResult && qcResult.length > 0 ? qcResult : qcItems} | |||||
// rows={disabled? qcResult:qcItems} | |||||
autoHeight | autoHeight | ||||
/> | /> | ||||
</Grid> | </Grid> | ||||
@@ -384,52 +420,58 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
disabled={false} | disabled={false} | ||||
/> | /> | ||||
</Grid> */} | </Grid> */} | ||||
<Grid item xs={12}> | |||||
{/* <Grid item xs={12}> | |||||
<Typography variant="h6" display="block" marginBlockEnd={1}> | <Typography variant="h6" display="block" marginBlockEnd={1}> | ||||
{t("Escalation Info")} | {t("Escalation Info")} | ||||
</Typography> | </Typography> | ||||
</Grid> | |||||
</Grid> */} | |||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<StyledDataGrid | |||||
<EscalationLogTable items={itemDetail.escResult || []}/> | |||||
{/* <StyledDataGrid | |||||
rows={escalationHistory} | rows={escalationHistory} | ||||
columns={columns} | columns={columns} | ||||
onRowSelectionModelChange={(newRowSelectionModel) => { | onRowSelectionModelChange={(newRowSelectionModel) => { | ||||
setRowSelectionModel(newRowSelectionModel); | setRowSelectionModel(newRowSelectionModel); | ||||
}} | }} | ||||
/> | |||||
/> */} | |||||
</Grid> | </Grid> | ||||
</> | </> | ||||
)} | )} | ||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<FormControl> | <FormControl> | ||||
<Controller | <Controller | ||||
name="qcAccept" | |||||
name="qcDecision" | |||||
// name="qcAccept" | |||||
control={control} | control={control} | ||||
defaultValue={true} | |||||
defaultValue={setQcDecision(itemDetail?.status)} | |||||
// defaultValue={true} | |||||
render={({ field }) => ( | render={({ field }) => ( | ||||
<RadioGroup | <RadioGroup | ||||
row | row | ||||
aria-labelledby="demo-radio-buttons-group-label" | aria-labelledby="demo-radio-buttons-group-label" | ||||
{...field} | {...field} | ||||
value={field.value?.toString() || "true"} | |||||
value={field.value} | |||||
// value={field.value?.toString() || "true"} | |||||
onChange={(e) => { | onChange={(e) => { | ||||
const value = e.target.value === 'true'; | |||||
if (!value && Boolean(errors.acceptQty)) { | |||||
setValue("acceptQty", itemDetail.acceptedQty); | |||||
const value = e.target.value.toString();// === 'true'; | |||||
if (value != "1" && Boolean(errors.acceptQty)) { | |||||
// if (!value && Boolean(errors.acceptQty)) { | |||||
setValue("acceptQty", itemDetail.acceptedQty ?? 0); | |||||
} | } | ||||
field.onChange(value); | field.onChange(value); | ||||
}} | }} | ||||
> | > | ||||
<FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
value="true" control={<Radio />} label="接受" /> | |||||
value="1" control={<Radio />} label="接受" /> | |||||
<Box sx={{mr:2}}> | <Box sx={{mr:2}}> | ||||
<TextField | <TextField | ||||
type="number" | type="number" | ||||
label={t("acceptQty")} | label={t("acceptQty")} | ||||
sx={{ width: '150px' }} | sx={{ width: '150px' }} | ||||
value={qcAccept? accQty : 0 } | |||||
defaultValue={accQty} | |||||
disabled={!qcAccept || disabled} | |||||
value={(qcDecision == 1)? accQty : 0 } | |||||
// value={qcAccept? accQty : 0 } | |||||
disabled={qcDecision != 1 || disabled} | |||||
// disabled={!qcAccept || disabled} | |||||
{...register("acceptQty", { | {...register("acceptQty", { | ||||
required: "acceptQty required!", | required: "acceptQty required!", | ||||
})} | })} | ||||
@@ -438,11 +480,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
/> | /> | ||||
</Box> | </Box> | ||||
<FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
value="false" control={<Radio />} | |||||
value="2" control={<Radio />} | |||||
sx={{"& .Mui-checked": {color: "red"}}} | sx={{"& .Mui-checked": {color: "red"}}} | ||||
label="不接受" /> | label="不接受" /> | ||||
<FormControlLabel disabled={disabled} | <FormControlLabel disabled={disabled} | ||||
value="false" control={<Radio />} | |||||
value="3" control={<Radio />} | |||||
sx={{"& .Mui-checked": {color: "blue"}}} | sx={{"& .Mui-checked": {color: "blue"}}} | ||||
label="上報品檢結果" /> | label="上報品檢結果" /> | ||||
</RadioGroup> | </RadioGroup> | ||||
@@ -450,7 +492,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
</Grid> | </Grid> | ||||
{!qcAccept && ( | |||||
{qcDecision == 3 && ( | |||||
// {!qcAccept && ( | |||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<EscalationComponent | <EscalationComponent | ||||
forSupervisor={false} | forSupervisor={false} | ||||
@@ -11,8 +11,8 @@ import { | |||||
Stack, | Stack, | ||||
Typography, | Typography, | ||||
} from "@mui/material"; | } from "@mui/material"; | ||||
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; | |||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||||
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; | |||||
import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import StockInForm from "./StockInForm"; | import StockInForm from "./StockInForm"; | ||||
@@ -22,8 +22,11 @@ import PutawayForm from "./PutawayForm"; | |||||
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | ||||
import { useGridApiRef } from "@mui/x-data-grid"; | import { useGridApiRef } from "@mui/x-data-grid"; | ||||
import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | ||||
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; | |||||
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import { watch } from "fs"; | |||||
import { EscalationResult } from "@/app/api/escalation"; | |||||
import { SessionWithTokens } from "@/config/authConfig"; | |||||
const style = { | const style = { | ||||
position: "absolute", | position: "absolute", | ||||
@@ -42,7 +45,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||||
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | ||||
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | ||||
setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | ||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
setItemDetail: Dispatch< | setItemDetail: Dispatch< | ||||
SetStateAction< | SetStateAction< | ||||
| (StockInLine & { | | (StockInLine & { | ||||
@@ -51,13 +54,15 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||||
| undefined | | undefined | ||||
> | > | ||||
>; | >; | ||||
session: SessionWithTokens | null; | |||||
qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
warehouse?: any[]; | warehouse?: any[]; | ||||
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | ||||
handleMailTemplateForStockInLine: (stockInLineId: number) => void; | handleMailTemplateForStockInLine: (stockInLineId: number) => void; | ||||
onClose: () => void; | |||||
} | } | ||||
interface Props extends CommonProps { | interface Props extends CommonProps { | ||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; | |||||
} | } | ||||
const PoQcStockInModalVer2: React.FC<Props> = ({ | const PoQcStockInModalVer2: React.FC<Props> = ({ | ||||
// type, | // type, | ||||
@@ -68,6 +73,7 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
onClose, | onClose, | ||||
itemDetail, | itemDetail, | ||||
setItemDetail, | setItemDetail, | ||||
session, | |||||
qc, | qc, | ||||
warehouse, | warehouse, | ||||
handleMailTemplateForStockInLine, | handleMailTemplateForStockInLine, | ||||
@@ -77,20 +83,32 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
i18n: { language }, | i18n: { language }, | ||||
} = useTranslation("purchaseOrder"); | } = useTranslation("purchaseOrder"); | ||||
const defaultNewValue = useMemo(() => { | |||||
return ( | |||||
{ | |||||
...itemDetail, | |||||
status: itemDetail.status ?? "pending", | |||||
dnDate: arrayToInputDateString(itemDetail.dnDate)?? dayjsToInputDateString(dayjs()), | |||||
putawayLine: dummyPutawayLine, | |||||
qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData], | |||||
escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], | |||||
receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||||
acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | |||||
warehouseId: itemDetail.defaultWarehouseId ?? 1 | |||||
} | |||||
) | |||||
},[itemDetail]) | |||||
const [qcItems, setQcItems] = useState(dummyQCData) | const [qcItems, setQcItems] = useState(dummyQCData) | ||||
const formProps = useForm<ModalFormInput>({ | const formProps = useForm<ModalFormInput>({ | ||||
defaultValues: { | defaultValues: { | ||||
...itemDetail, | |||||
dnDate: dayjsToInputDateString(dayjs()), | |||||
putawayLine: dummyPutawayLine, | |||||
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||||
// warehouseId: itemDetail.defaultWarehouseId || 0 | |||||
...defaultNewValue, | |||||
}, | }, | ||||
}); | }); | ||||
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
(...args) => { | |||||
onClose?.(...args); | |||||
() => { | |||||
onClose?.(); | |||||
// reset(); | // reset(); | ||||
}, | }, | ||||
[onClose], | [onClose], | ||||
@@ -104,25 +122,31 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
} else return false; | } else return false; | ||||
}; | }; | ||||
useEffect(() => { | |||||
formProps.reset({ | |||||
...itemDetail, | |||||
dnDate: dayjsToInputDateString(dayjs()), | |||||
putawayLine: dummyPutawayLine, | |||||
}) | |||||
setOpenPutaway(isPutaway); | |||||
}, [open]) | |||||
const [viewOnly, setViewOnly] = useState(false); | const [viewOnly, setViewOnly] = useState(false); | ||||
useEffect(() => { | useEffect(() => { | ||||
if (itemDetail && itemDetail.status) { | if (itemDetail && itemDetail.status) { | ||||
const isViewOnly = itemDetail.status.toLowerCase() == "completed" || itemDetail.status.toLowerCase() == "rejected" | |||||
const isViewOnly = itemDetail.status.toLowerCase() == "completed" | |||||
|| itemDetail.status.toLowerCase() == "rejected" | |||||
|| (itemDetail.status.toLowerCase() == "escalated" && session?.id != itemDetail.handlerId) | |||||
setViewOnly(isViewOnly) | setViewOnly(isViewOnly) | ||||
} | } | ||||
console.log("ITEM", itemDetail); | |||||
}, [itemDetail]); | }, [itemDetail]); | ||||
useEffect(() => { | |||||
const qcRes = itemDetail?.qcResult; | |||||
// if (!qcRes || qcRes?.length <= 0) { | |||||
// itemDetail.qcResult = dummyQCData; | |||||
// } | |||||
formProps.reset({ | |||||
...defaultNewValue | |||||
}) | |||||
setQcItems(dummyQCData); | |||||
setOpenPutaway(isPutaway); | |||||
}, [open, defaultNewValue]) | |||||
const [openPutaway, setOpenPutaway] = useState(false); | const [openPutaway, setOpenPutaway] = useState(false); | ||||
const onOpenPutaway = useCallback(() => { | const onOpenPutaway = useCallback(() => { | ||||
@@ -155,6 +179,13 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
[], | [], | ||||
); | ); | ||||
// QC submission handler | |||||
const onSubmitErrorQc = useCallback<SubmitErrorHandler<ModalFormInput>>( | |||||
async (data, event) => { | |||||
console.log("Error", data); | |||||
}, [] | |||||
); | |||||
// QC submission handler | // QC submission handler | ||||
const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>( | ||||
async (data, event) => { | async (data, event) => { | ||||
@@ -162,19 +193,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
// TODO: Move validation into QC page | // TODO: Move validation into QC page | ||||
// Get QC data from the shared form context | // Get QC data from the shared form context | ||||
const qcAccept = data.qcAccept; | |||||
const qcAccept = data.qcDecision == 1; | |||||
// const qcAccept = data.qcAccept; | |||||
const acceptQty = data.acceptQty as number; | const acceptQty = data.acceptQty as number; | ||||
const qcResults = qcItems; | |||||
const qcResults = qcItems; // qcItems; | |||||
// const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems; | |||||
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; | // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; | ||||
// Validate QC data | // Validate QC data | ||||
const validationErrors : string[] = []; | const validationErrors : string[] = []; | ||||
// Check if all QC items have results | |||||
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(', ')}`); | |||||
} | |||||
// Check if failed items have failed quantity | // Check if failed items have failed quantity | ||||
const failedItemsWithoutQty = qcResults.filter(item => | const failedItemsWithoutQty = qcResults.filter(item => | ||||
@@ -202,13 +230,21 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
if (data.expiryDate === undefined || data.expiryDate == null) { | if (data.expiryDate === undefined || data.expiryDate == null) { | ||||
validationErrors.push("請輸入到期日!"); | validationErrors.push("請輸入到期日!"); | ||||
} | } | ||||
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept) { | |||||
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && itemDetail.status != "escalated") { //TODO: fix it please! | |||||
validationErrors.push("有不合格檢查項目,無法收貨!"); | validationErrors.push("有不合格檢查項目,無法收貨!"); | ||||
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | ||||
// confirmButtonText: t("confirm putaway"), html: ""}); | // confirmButtonText: t("confirm putaway"), html: ""}); | ||||
// return; | // return; | ||||
} | } | ||||
// Check if all QC items have results | |||||
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | |||||
// if (itemsWithoutResult.length > 0 && itemDetail.status != "escalated") { //TODO: fix it please! | |||||
// validationErrors.push(`${t("QC items without result")}`); | |||||
// // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | |||||
// } | |||||
if (validationErrors.length > 0) { | if (validationErrors.length > 0) { | ||||
console.error("QC Validation failed:", validationErrors); | console.error("QC Validation failed:", validationErrors); | ||||
alert(`未完成品檢: ${validationErrors}`); | alert(`未完成品檢: ${validationErrors}`); | ||||
@@ -224,7 +260,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
qcAccept: qcAccept? qcAccept : false, | qcAccept: qcAccept? qcAccept : false, | ||||
acceptQty: acceptQty? acceptQty : 0, | acceptQty: acceptQty? acceptQty : 0, | ||||
qcResult: qcResults.map(item => ({ | |||||
qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({ | |||||
// id: item.id, | |||||
qcItemId: item.id, | qcItemId: item.id, | ||||
// code: item.code, | // code: item.code, | ||||
// qcDescription: item.qcDescription, | // qcDescription: item.qcDescription, | ||||
@@ -232,13 +269,27 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0, | failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0, | ||||
// failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, | // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, | ||||
remarks: item.remarks || '' | remarks: item.remarks || '' | ||||
})) | |||||
})) : [], | |||||
}; | }; | ||||
// const qcData = data; | // const qcData = data; | ||||
console.log("QC Data for submission:", qcData); | console.log("QC Data for submission:", qcData); | ||||
await postStockInLine(qcData); | |||||
if (data.qcDecision == 3) { // Escalate | |||||
const escalationLog = { | |||||
type : "qc", | |||||
status : "pending", // TODO: update with supervisor decision | |||||
reason : data.escalationLog?.reason, | |||||
recordDate : dayjsToInputDateString(dayjs()), | |||||
handlerId : Number(session?.id), | |||||
} | |||||
console.log("ESCALATION RESULT", escalationLog); | |||||
await postStockInLine({...qcData, escalationLog}); | |||||
} else { | |||||
await postStockInLine(qcData); | |||||
} | |||||
if (qcData.qcAccept) { | if (qcData.qcAccept) { | ||||
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | ||||
@@ -257,12 +308,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
const submitData = { | const submitData = { | ||||
...itemDetail, ...args | ...itemDetail, ...args | ||||
} as StockInLineEntry & ModalFormInput; | } as StockInLineEntry & ModalFormInput; | ||||
console.log(submitData); | |||||
console.log("Submitting", submitData); | |||||
const res = await updateStockInLine(submitData); | const res = await updateStockInLine(submitData); | ||||
console.log("result ", res); | |||||
console.log("Result ", res); | |||||
return res; | return res; | ||||
},[]) | |||||
},[itemDetail]) | |||||
// Email supplier handler | // Email supplier handler | ||||
const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>( | const onSubmitEmailSupplier = useCallback<SubmitHandler<ModalFormInput>>( | ||||
@@ -297,7 +348,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
// binLocation: data.binLocation, | // binLocation: data.binLocation, | ||||
// putawayQuantity: data.putawayQuantity, | // putawayQuantity: data.putawayQuantity, | ||||
// putawayNotes: data.putawayNotes, | // putawayNotes: data.putawayNotes, | ||||
acceptQty: itemDetail.demandQty? itemDetail.demandQty : itemDetail.acceptedQty, | |||||
acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty, | |||||
...data, | ...data, | ||||
dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()), | dnDate : data.dnDate? arrayToInputDateString(data.dnDate) : dayjsToInputDateString(dayjs()), | ||||
@@ -311,7 +362,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
// Handle putaway submission logic here | // Handle putaway submission logic here | ||||
const res = await postStockInLine(putawayData); | const res = await postStockInLine(putawayData); | ||||
console.log("result ", res); | |||||
console.log("Result ", res); | |||||
// Close modal after successful putaway | // Close modal after successful putaway | ||||
closeHandler({}, "backdropClick"); | closeHandler({}, "backdropClick"); | ||||
@@ -438,7 +489,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
variant="contained" | variant="contained" | ||||
color="primary" | color="primary" | ||||
sx={{ mt: 1 }} | sx={{ mt: 1 }} | ||||
onClick={formProps.handleSubmit(onSubmitQc)} | |||||
onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)} | |||||
> | > | ||||
{t("confirm qc result")} | {t("confirm qc result")} | ||||
</Button>)} | </Button>)} | ||||
@@ -76,18 +76,18 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
clearErrors, | clearErrors, | ||||
} = useFormContext<StockInInput>(); | } = useFormContext<StockInInput>(); | ||||
// console.log(itemDetail); | // console.log(itemDetail); | ||||
useEffect(() => { | useEffect(() => { | ||||
console.log("triggered"); | |||||
// receiptDate default tdy | |||||
setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||||
setValue("status", "received"); | |||||
// console.log("triggered"); | |||||
// // receiptDate default tdy | |||||
// setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||||
// setValue("status", "received"); | |||||
}, [setValue]); | }, [setValue]); | ||||
useEffect(() => { | useEffect(() => { | ||||
console.log(errors); | console.log(errors); | ||||
}, [errors]); | }, [errors]); | ||||
const productionDate = watch("productionDate"); | const productionDate = watch("productionDate"); | ||||
const expiryDate = watch("expiryDate"); | const expiryDate = watch("expiryDate"); | ||||
const uom = watch("uom"); | const uom = watch("uom"); | ||||
@@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
<TextField | <TextField | ||||
label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
fullWidth | fullWidth | ||||
disabled={disabled} | |||||
disabled={true} | |||||
{...register("acceptedQty", { | {...register("acceptedQty", { | ||||
required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
})} | })} | ||||
@@ -215,7 +215,7 @@ const PoSearch: React.FC<Props> = ({ | |||||
name: "escalated", | name: "escalated", | ||||
label: t("Escalated"), | label: t("Escalated"), | ||||
renderCell: (params) => { | renderCell: (params) => { | ||||
console.log(params.escalated); | |||||
// console.log(params.escalated); | |||||
return params.escalated ? ( | return params.escalated ? ( | ||||
<NotificationIcon color="warning" /> | <NotificationIcon color="warning" /> | ||||
) : undefined; | ) : undefined; | ||||
@@ -42,5 +42,6 @@ | |||||
"Received Qty": "收貨數量", | "Received Qty": "收貨數量", | ||||
"Escalation List": "上報列表", | "Escalation List": "上報列表", | ||||
"Purchase UoM": "計量單位", | "Purchase UoM": "計量單位", | ||||
"QC Completed Count": "品檢完成數量" | |||||
"QC Completed Count": "品檢完成數量", | |||||
"QC Fail-Total Count": "品檢不合格/總數" | |||||
} | } |
@@ -72,7 +72,8 @@ | |||||
"receiving": "收貨中", | "receiving": "收貨中", | ||||
"received": "已檢收", | "received": "已檢收", | ||||
"completed": "已上架", | "completed": "已上架", | ||||
"rejected": "已拒絕及上報", | |||||
"rejected": "已拒絕", | |||||
"escalated": "已上報", | |||||
"status": "狀態", | "status": "狀態", | ||||
"acceptedQty must not greater than": "接受數量不得大於", | "acceptedQty must not greater than": "接受數量不得大於", | ||||
"minimal value is 1": "最小值為1", | "minimal value is 1": "最小值為1", | ||||
@@ -131,5 +132,6 @@ | |||||
"print": "列印", | "print": "列印", | ||||
"bind": "綁定", | "bind": "綁定", | ||||
"Search": "搜尋", | "Search": "搜尋", | ||||
"Found": "已找到" | |||||
"Found": "已找到", | |||||
"escalation processing": "處理上報記錄" | |||||
} | } |