# Conflicts: # src/app/api/pickOrder/actions.tsmaster
| @@ -4,6 +4,7 @@ import { Suspense } from "react"; | |||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import DashboardPage from "@/components/DashboardPage"; | import DashboardPage from "@/components/DashboardPage"; | ||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | import { SearchParams } from "@/app/utils/fetchUtil"; | ||||
| import { fetchEscalationLogsByUser } from "@/app/api/escalation"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Dashboard", | title: "Dashboard", | ||||
| @@ -14,6 +15,8 @@ type Props = {} & SearchParams; | |||||
| const Dashboard: React.FC<Props> = async ({ searchParams }) => { | const Dashboard: React.FC<Props> = async ({ searchParams }) => { | ||||
| const { t } = await getServerI18n("dashboard"); | const { t } = await getServerI18n("dashboard"); | ||||
| fetchEscalationLogsByUser() | |||||
| return ( | return ( | ||||
| <I18nProvider namespaces={["dashboard", "common"]}> | <I18nProvider namespaces={["dashboard", "common"]}> | ||||
| <Suspense fallback={<DashboardPage.Loading />}> | <Suspense fallback={<DashboardPage.Loading />}> | ||||
| @@ -37,7 +37,7 @@ export default async function MainLayout({ | |||||
| return ( | return ( | ||||
| <SessionProviderWrapper session={session}> | <SessionProviderWrapper session={session}> | ||||
| <UploadProvider> | <UploadProvider> | ||||
| <CameraProvider> | |||||
| {/* <CameraProvider> */} | |||||
| <AxiosProvider> | <AxiosProvider> | ||||
| <QrCodeScannerProvider> | <QrCodeScannerProvider> | ||||
| <> | <> | ||||
| @@ -62,7 +62,7 @@ export default async function MainLayout({ | |||||
| </> | </> | ||||
| </QrCodeScannerProvider> | </QrCodeScannerProvider> | ||||
| </AxiosProvider> | </AxiosProvider> | ||||
| </CameraProvider> | |||||
| {/* </CameraProvider> */} | |||||
| </UploadProvider> | </UploadProvider> | ||||
| </SessionProviderWrapper> | </SessionProviderWrapper> | ||||
| ); | ); | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { fetchEscalationCombo } from "@/app/api/user"; | |||||
| import { SearchParams } from "@/app/utils/fetchUtil"; | import { SearchParams } from "@/app/utils/fetchUtil"; | ||||
| import { TypeEnum } from "@/app/utils/typeEnum"; | import { TypeEnum } from "@/app/utils/typeEnum"; | ||||
| import CreateProductMaterial from "@/components/CreateItem"; | import CreateProductMaterial from "@/components/CreateItem"; | ||||
| @@ -22,6 +23,9 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => { | |||||
| if (!id) { | if (!id) { | ||||
| notFound(); | notFound(); | ||||
| } | } | ||||
| fetchEscalationCombo() | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | {/* <Typography variant="h4">{t("Create Material")}</Typography> */} | ||||
| @@ -30,9 +30,6 @@ export interface StockInLineEntry { | |||||
| acceptedQty: number; | acceptedQty: number; | ||||
| status?: string; | status?: string; | ||||
| expiryDate?: string; | expiryDate?: string; | ||||
| productLotNo?: string; | |||||
| receiptDate?: string; | |||||
| dnDate?: string; | |||||
| } | } | ||||
| export interface PurchaseQcResult { | export interface PurchaseQcResult { | ||||
| @@ -6,7 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "../../../config/api"; | import { BASE_API_URL } from "../../../config/api"; | ||||
| import { Uom } from "../settings/uom"; | import { Uom } from "../settings/uom"; | ||||
| import { RecordsRes } from "../utils"; | import { RecordsRes } from "../utils"; | ||||
| import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval"; | |||||
| // import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval"; | |||||
| export interface PoResult { | export interface PoResult { | ||||
| id: number; | id: number; | ||||
| @@ -84,7 +84,8 @@ export const fetchPoWithStockInLines = cache(async (id: number) => { | |||||
| }); | }); | ||||
| export const fetchIqcLogByUser = cache(async () => { | export const fetchIqcLogByUser = cache(async () => { | ||||
| return serverFetchJson<IQCItems[]>(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, { | |||||
| next: { tags: ["qcLog"] }, | |||||
| }); | |||||
| // return serverFetchJson<IQCItems[]>(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, { | |||||
| // next: { tags: ["qcLog"] }, | |||||
| // }); | |||||
| return undefined; | |||||
| }); | }); | ||||
| @@ -0,0 +1,58 @@ | |||||
| "server only" | |||||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { cache } from "react"; | |||||
| export interface EscalationResult { | |||||
| id: number; | |||||
| handler?: string; | |||||
| handlerDepartment?: string; | |||||
| handlerName?: string; | |||||
| handlerTitle?: string; | |||||
| polId?: number; | |||||
| poId?: number; | |||||
| reason?: string; | |||||
| handlerId?: number; | |||||
| itemName?: string; | |||||
| demandQty?: number; | |||||
| acceptedQty?: number; | |||||
| purchaseUomCode?: string; | |||||
| purchaseUomDesc?: string; | |||||
| stockUomCode?: string; | |||||
| stockUomDesc?: string; | |||||
| stockInLineId?: number; | |||||
| stockOutLineId?: number; | |||||
| qcFailCount?: number; | |||||
| qcTotalCount?: number; | |||||
| poCode?: string; | |||||
| itemCode?: string; | |||||
| dnDate?: number[]; | |||||
| 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() => { | |||||
| return serverFetchJson<EscalationResult[]>(`${BASE_API_URL}/escalationLog/user`, | |||||
| { | |||||
| method: "GET", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| next: { | |||||
| tags: ["escalationLogs"], | |||||
| }, | |||||
| }, | |||||
| ); | |||||
| }); | |||||
| @@ -0,0 +1,17 @@ | |||||
| "use server"; | |||||
| import { serverFetchBlob } from "@/app/utils/fetchUtil"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { cache } from "react"; | |||||
| export const getMailTemplateForStockInLine = cache(async (stockInLineId: number) => { | |||||
| console.log("stockInLineId", stockInLineId) | |||||
| return serverFetchBlob(`${BASE_API_URL}/mailTemplates/getMailTemplateForStockInLine/${stockInLineId}`, | |||||
| { | |||||
| method: "GET", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| next: { | |||||
| tags: ["mailTemplateForStockInLine"], | |||||
| }, | |||||
| }) | |||||
| }) | |||||
| @@ -144,47 +144,6 @@ export interface PickOrderLotDetailResponse { | |||||
| } | } | ||||
| export interface AssignPickOrderInputs { | |||||
| pickOrderIds: number[]; | |||||
| assignTo: number; | |||||
| } | |||||
| export const newassignPickOrder = async (data: AssignPickOrderInputs) => { | |||||
| const pickOrder = await serverFetchJson<any>( | |||||
| `${BASE_API_URL}/pickOrder/assign`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| revalidateTag("pickorder"); | |||||
| return pickOrder; | |||||
| }; | |||||
| export const newreleasePickOrder = async (data: AssignPickOrderInputs) => { | |||||
| const pickOrder = await serverFetchJson<any>( | |||||
| `${BASE_API_URL}/pickOrder/release`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| revalidateTag("pickorder"); | |||||
| return pickOrder; | |||||
| }; | |||||
| export const releaseAssignedPickOrders = async (data: AssignPickOrderInputs) => { | |||||
| const pickOrder = await serverFetchJson<any>( | |||||
| `${BASE_API_URL}/pickOrder/release-assigned`, | |||||
| { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| revalidateTag("pickorder"); | |||||
| return pickOrder; | |||||
| }; | |||||
| export const fetchAllPickOrderDetails = cache(async () => { | export const fetchAllPickOrderDetails = cache(async () => { | ||||
| return serverFetchJson<GetPickOrderInfoResponse>( | return serverFetchJson<GetPickOrderInfoResponse>( | ||||
| `${BASE_API_URL}/pickOrder/detail`, | `${BASE_API_URL}/pickOrder/detail`, | ||||
| @@ -31,13 +31,17 @@ export interface StockInLineEntry { | |||||
| acceptedQty: number; | acceptedQty: number; | ||||
| status?: string; | status?: string; | ||||
| expiryDate?: string; | expiryDate?: string; | ||||
| productLotNo?: string; | |||||
| receiptDate?: string; | |||||
| dnDate?: string; | |||||
| } | } | ||||
| export interface PurchaseQcResult { | |||||
| export interface PurchaseQcResult{ | |||||
| qcItemId: number; | qcItemId: number; | ||||
| isPassed: boolean, | |||||
| qcPassed: boolean; | |||||
| failQty: number; | failQty: number; | ||||
| remarks?: string | |||||
| remarks?: string; | |||||
| } | } | ||||
| export interface StockInInput { | export interface StockInInput { | ||||
| status: string; | status: string; | ||||
| @@ -110,7 +114,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||||
| export const createStockInLine = async (data: StockInLineEntry) => { | export const createStockInLine = async (data: StockInLineEntry) => { | ||||
| const stockInLine = await serverFetchJson< | const stockInLine = await serverFetchJson< | ||||
| PostStockInLineResponse<StockInLineEntry> | |||||
| PostStockInLineResponse<StockInLine> | |||||
| >(`${BASE_API_URL}/stockInLine/create`, { | >(`${BASE_API_URL}/stockInLine/create`, { | ||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| @@ -124,7 +128,7 @@ export const updateStockInLine = async ( | |||||
| data: StockInLineEntry & ModalFormInput, | data: StockInLineEntry & ModalFormInput, | ||||
| ) => { | ) => { | ||||
| const stockInLine = await serverFetchJson< | const stockInLine = await serverFetchJson< | ||||
| PostStockInLineResponse<StockInLineEntry & ModalFormInput> | |||||
| PostStockInLineResponse<StockInLine & ModalFormInput> | |||||
| >(`${BASE_API_URL}/stockInLine/update`, { | >(`${BASE_API_URL}/stockInLine/update`, { | ||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| @@ -15,6 +15,11 @@ export interface PoResult { | |||||
| estimatedArrivalDate: string; | estimatedArrivalDate: string; | ||||
| completedDate: string; | completedDate: string; | ||||
| itemDetail?: string; | itemDetail?: string; | ||||
| itemCode?: string; | |||||
| itemName?: string; | |||||
| itemQty?: string; | |||||
| itemSumAcceptedQty?: string; | |||||
| itemUom?: string; | |||||
| escalated: boolean; | escalated: boolean; | ||||
| status: string; | status: string; | ||||
| pol?: PurchaseOrderLine[]; | pol?: PurchaseOrderLine[]; | ||||
| @@ -49,7 +54,7 @@ export interface StockUomForPoLine { | |||||
| export interface StockInLine { | export interface StockInLine { | ||||
| id: number; | id: number; | ||||
| stockInId: number; | |||||
| stockInId?: number; | |||||
| purchaseOrderId?: number; | purchaseOrderId?: number; | ||||
| purchaseOrderLineId: number; | purchaseOrderLineId: number; | ||||
| itemId: number; | itemId: number; | ||||
| @@ -58,22 +63,24 @@ export interface StockInLine { | |||||
| itemType: string; | itemType: string; | ||||
| demandQty: number; | demandQty: number; | ||||
| acceptedQty: number; | acceptedQty: number; | ||||
| qty: number; | |||||
| processed: number; | |||||
| price: number; | |||||
| priceUnit: string; | |||||
| qty?: number; | |||||
| processed?: number; | |||||
| price?: number; | |||||
| priceUnit?: string; | |||||
| shelfLife?: number; | shelfLife?: number; | ||||
| receiptDate?: string; | receiptDate?: string; | ||||
| productionDate?: string; | productionDate?: string; | ||||
| productLotNo?: string; | |||||
| expiryDate?: string; | expiryDate?: string; | ||||
| status: string; | status: string; | ||||
| supplier: string; | |||||
| lotNo: string; | |||||
| poCode: string; | |||||
| uom: Uom; | |||||
| supplier?: string; | |||||
| lotNo?: string; | |||||
| poCode?: string; | |||||
| uom?: Uom; | |||||
| defaultWarehouseId: number; // id for now | defaultWarehouseId: number; // id for now | ||||
| dnNo: string; | |||||
| dnDate: number[]; | |||||
| dnNo?: string; | |||||
| dnDate?: number[]; | |||||
| stockQty?: number; | |||||
| } | } | ||||
| export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | ||||
| @@ -15,6 +15,16 @@ export interface QcItemWithChecks { | |||||
| description: string | undefined; | description: string | undefined; | ||||
| } | } | ||||
| export interface QcData { | |||||
| id: number, | |||||
| code: string, | |||||
| name: string, | |||||
| qcDescription: string, | |||||
| qcPassed: boolean | undefined | |||||
| failQty: number | undefined | |||||
| remarks: string | undefined | |||||
| } | |||||
| export const fetchQcItemCheckList = cache(async () => { | export const fetchQcItemCheckList = cache(async () => { | ||||
| return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { | return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { | ||||
| next: { tags: ["qc"] }, | next: { tags: ["qc"] }, | ||||
| @@ -31,6 +31,15 @@ export type passwordRule = { | |||||
| specialChar: boolean; | specialChar: boolean; | ||||
| }; | }; | ||||
| export interface EscalationCombo { | |||||
| id: number; | |||||
| value: number; | |||||
| label: string; | |||||
| name: string; | |||||
| title: string; | |||||
| department: string; | |||||
| } | |||||
| export const preloadUser = () => { | export const preloadUser = () => { | ||||
| fetchUser(); | fetchUser(); | ||||
| }; | }; | ||||
| @@ -56,3 +65,9 @@ export const fetchPwRules = cache(async () => { | |||||
| next: { tags: ["pwRule"] }, | next: { tags: ["pwRule"] }, | ||||
| }); | }); | ||||
| }); | }); | ||||
| export const fetchEscalationCombo = cache(async () => { | |||||
| return serverFetchJson<EscalationCombo>(`${BASE_API_URL}/user/escalation-combo`, { | |||||
| next: { tags: ["escalationCombo"]} | |||||
| }) | |||||
| }) | |||||
| @@ -3,6 +3,11 @@ import { getServerSession } from "next-auth"; | |||||
| import { headers } from "next/headers"; | import { headers } from "next/headers"; | ||||
| import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
| export interface BlobResponse { | |||||
| filename: string; | |||||
| blobValue: Uint8Array; | |||||
| } | |||||
| export interface Pageable { | export interface Pageable { | ||||
| pageSize?: number; | pageSize?: number; | ||||
| pageNum?: number; | pageNum?: number; | ||||
| @@ -86,7 +91,7 @@ export async function serverFetchJson<T>(...args: FetchParams) { | |||||
| } | } | ||||
| } | } | ||||
| export async function serverFetchBlob<T>(...args: FetchParams) { | |||||
| export async function serverFetchBlob<T extends BlobResponse>(...args: FetchParams) { | |||||
| const response = await serverFetch(...args); | const response = await serverFetch(...args); | ||||
| if (response.ok) { | if (response.ok) { | ||||
| @@ -14,13 +14,17 @@ import ApplicationCompletionChart from "./chart/ApplicationCompletionChart"; | |||||
| import OrderCompletionChart from "./chart/OrderCompletionChart"; | import OrderCompletionChart from "./chart/OrderCompletionChart"; | ||||
| import DashboardBox from "./Dashboardbox"; | import DashboardBox from "./Dashboardbox"; | ||||
| import CollapsibleCard from "./CollapsibleCard"; | import CollapsibleCard from "./CollapsibleCard"; | ||||
| import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; | |||||
| // import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; | |||||
| import { EscalationResult } from "@/app/api/escalation"; | |||||
| import EscalationLogTable from "./escalation/EscalationLogTable"; | |||||
| type Props = { | type Props = { | ||||
| iqc: IQCItems[] | |||||
| // iqc: IQCItems[] | undefined | |||||
| escalationLogs: EscalationResult[] | |||||
| }; | }; | ||||
| const DashboardPage: React.FC<Props> = ({ | const DashboardPage: React.FC<Props> = ({ | ||||
| iqc | |||||
| // iqc, | |||||
| escalationLogs | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| @@ -29,9 +33,9 @@ const DashboardPage: React.FC<Props> = ({ | |||||
| <ThemeProvider theme={theme}> | <ThemeProvider theme={theme}> | ||||
| <Grid container spacing={2}> | <Grid container spacing={2}> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <CollapsibleCard title={t("stock in escalation list")}> | |||||
| <CollapsibleCard title={t("Escalation List")}> | |||||
| <CardContent> | <CardContent> | ||||
| <SupervisorQcApproval items={iqc || []}/> | |||||
| <EscalationLogTable items={escalationLogs || []}/> | |||||
| </CardContent> | </CardContent> | ||||
| </CollapsibleCard> | </CollapsibleCard> | ||||
| </Grid> | </Grid> | ||||
| @@ -4,7 +4,8 @@ import DashboardPage from "./DashboardPage"; | |||||
| import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| import DashboardLoading from "./DashboardLoading"; | import DashboardLoading from "./DashboardLoading"; | ||||
| import { fetchIqcLogByUser } from "@/app/api/dashboard"; | |||||
| import { fetchEscalationLogsByUser } from "@/app/api/escalation"; | |||||
| // import { fetchIqcLogByUser } from "@/app/api/dashboard"; | |||||
| // export type SessionWithAbilities = { | // export type SessionWithAbilities = { | ||||
| // abilities: string[] | // abilities: string[] | ||||
| @@ -23,16 +24,20 @@ const DashboardWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| }) => { | }) => { | ||||
| const { t } = await getServerI18n("dashboard"); | const { t } = await getServerI18n("dashboard"); | ||||
| // const session: SessionWithAbilities = await getServerSession(authOptions) | // const session: SessionWithAbilities = await getServerSession(authOptions) | ||||
| const [iqcLog] = await Promise.all([ | |||||
| fetchIqcLogByUser() | |||||
| // const [iqcLog] = await Promise.all([ | |||||
| // fetchIqcLogByUser() | |||||
| // ]) | |||||
| const [escalationLogs] = await Promise.all([ | |||||
| fetchEscalationLogsByUser() | |||||
| ]) | ]) | ||||
| console.log(iqcLog) | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Typography variant="h4">{t("Dashboard")}</Typography> | <Typography variant="h4">{t("Dashboard")}</Typography> | ||||
| <DashboardPage | <DashboardPage | ||||
| iqc={iqcLog} | |||||
| escalationLogs={escalationLogs} | |||||
| // iqc={iqcLog} | |||||
| // abilities={session ? session?.abilities : []} | // abilities={session ? session?.abilities : []} | ||||
| /> | /> | ||||
| </> | </> | ||||
| @@ -2,9 +2,13 @@ | |||||
| import { Box, Card, CardActionArea, CardContent, CardHeader, Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; | import { Box, Card, CardActionArea, CardContent, CardHeader, Grid, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; | ||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
| import { useCallback, useState } from "react"; | |||||
| import { useCallback, useMemo, useState } from "react"; | |||||
| import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { EscalationResult } from "@/app/api/escalation"; | |||||
| import { Column } from "@/components/SearchResults"; | |||||
| import SearchResults from "@/components/SearchResults/SearchResults"; | |||||
| import { arrayToDateString } from "@/app/utils/formatUtil"; | |||||
| export type IQCItems = { | export type IQCItems = { | ||||
| id: number; | id: number; | ||||
| @@ -18,11 +22,11 @@ export type IQCItems = { | |||||
| }; | }; | ||||
| type Props = { | type Props = { | ||||
| items: IQCItems[]; | |||||
| items: EscalationResult[]; | |||||
| }; | }; | ||||
| const SupervisorQcApproval: React.FC<Props> = ({ | |||||
| items | |||||
| const EscalationLogTable: React.FC<Props> = ({ | |||||
| items | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("dashboard"); | const { t } = useTranslation("dashboard"); | ||||
| const CARD_HEADER = t("stock in escalation list") | const CARD_HEADER = t("stock in escalation list") | ||||
| @@ -31,8 +35,8 @@ const SupervisorQcApproval: React.FC<Props> = ({ | |||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [selectedId, setSelectedId] = useState<number | null>(null); | const [selectedId, setSelectedId] = useState<number | null>(null); | ||||
| const navigateTo = useCallback( | |||||
| (item: IQCItems) => { | |||||
| const navigateTo = useCallback( | |||||
| (item: EscalationResult) => { | |||||
| setSelectedId(item.id); | setSelectedId(item.id); | ||||
| console.log(pathname) | console.log(pathname) | ||||
| router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); | router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); | ||||
| @@ -40,17 +44,62 @@ const navigateTo = useCallback( | |||||
| [router, pathname] | [router, pathname] | ||||
| ); | ); | ||||
| const handleKeyDown = useCallback( | |||||
| (e: React.KeyboardEvent, item: IQCItems) => { | |||||
| if (e.key === 'Enter' || e.key === ' ') { | |||||
| e.preventDefault(); | |||||
| navigateTo(item); | |||||
| } | |||||
| }, | |||||
| [navigateTo] | |||||
| ); | |||||
| const onRowClick = useCallback((item: EscalationResult) => { | |||||
| router.push(`/po/edit?id=${item.poId}&selectedIds=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); | |||||
| }, [router]) | |||||
| // const handleKeyDown = useCallback( | |||||
| // (e: React.KeyboardEvent, item: EscalationResult) => { | |||||
| // if (e.key === 'Enter' || e.key === ' ') { | |||||
| // e.preventDefault(); | |||||
| // navigateTo(item); | |||||
| // } | |||||
| // }, | |||||
| // [navigateTo] | |||||
| // ); | |||||
| return ( | |||||
| const columns = useMemo<Column<EscalationResult>[]>( | |||||
| () => [ | |||||
| { | |||||
| name: "handler", | |||||
| label: t("Responsible for handling colleagues") | |||||
| }, | |||||
| { | |||||
| name: "acceptedQty", | |||||
| label: t("Received Qty"), | |||||
| align: "right", | |||||
| headerAlign: "right" | |||||
| }, | |||||
| { | |||||
| name: "purchaseUomDesc", | |||||
| label: t("Purchase UoM") | |||||
| }, | |||||
| { | |||||
| name: "dnDate", | |||||
| label: t("DN Date"), | |||||
| renderCell: (params) => { | |||||
| return params.dnDate ? arrayToDateString(params.dnDate) : "N/A" | |||||
| } | |||||
| }, | |||||
| { | |||||
| name: "qcTotalCount", | |||||
| label: t("QC Completed Count"), | |||||
| align: "right", | |||||
| headerAlign: "right" | |||||
| }, | |||||
| { | |||||
| name: "qcFailCount", | |||||
| label: t("QC Fail Count"), | |||||
| align: "right", | |||||
| headerAlign: "right" | |||||
| }, | |||||
| { | |||||
| name: "reason", | |||||
| label: t("Reason"), | |||||
| }, | |||||
| ], []) | |||||
| {/* return ( | |||||
| <TableContainer component={Paper}> | <TableContainer component={Paper}> | ||||
| <Table aria-label="Two column navigable table" size="small"> | <Table aria-label="Two column navigable table" size="small"> | ||||
| <TableHead> | <TableHead> | ||||
| @@ -87,7 +136,15 @@ const navigateTo = useCallback( | |||||
| </TableBody> | </TableBody> | ||||
| </Table> | </Table> | ||||
| </TableContainer> | </TableContainer> | ||||
| ); | |||||
| }; | |||||
| );*/} | |||||
| return ( | |||||
| <SearchResults | |||||
| onRowClick={onRowClick} | |||||
| items={items} | |||||
| columns={columns} | |||||
| isAutoPaging={false} | |||||
| /> | |||||
| ) | |||||
| }; | |||||
| export default SupervisorQcApproval; | |||||
| export default EscalationLogTable; | |||||
| @@ -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; | |||||
| @@ -84,6 +84,7 @@ export interface SelectionInputDataGridProps<T, V, E> { | |||||
| columns: GridColDef[]; | columns: GridColDef[]; | ||||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | ||||
| needAdd?: boolean; | needAdd?: boolean; | ||||
| showRemoveBtn?: boolean; | |||||
| } | } | ||||
| export type Props<T, V, E> = | export type Props<T, V, E> = | ||||
| @@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent"; | |||||
| import QcDataGrid from "./QCDatagrid"; | import QcDataGrid from "./QCDatagrid"; | ||||
| import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
| import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | ||||
| import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||||
| import { ModalFormInput } from "@/app/api/po/actions"; | |||||
| import { escape } from "lodash"; | import { escape } from "lodash"; | ||||
| interface Props { | interface Props { | ||||
| @@ -83,10 +83,18 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
| return ( | return ( | ||||
| // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> | // <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> | ||||
| <> | <> | ||||
| <Paper> | |||||
| <Paper sx={{padding: 2}}> | |||||
| {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | {/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | ||||
| <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> | <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={ | control={ | ||||
| <Checkbox | <Checkbox | ||||
| checked={isCollapsed} | checked={isCollapsed} | ||||
| @@ -104,23 +112,10 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| } | } | ||||
| /> | |||||
| /> */} | |||||
| </Box> | </Box> | ||||
| <Collapse in={isCollapsed}> | <Collapse in={isCollapsed}> | ||||
| <Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | <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> | <FormControl fullWidth> | ||||
| <select | <select | ||||
| id="name" | id="name" | ||||
| @@ -136,7 +131,31 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
| ))} | ))} | ||||
| </select> | </select> | ||||
| </FormControl> | </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 | fullWidth | ||||
| id="quantity" | id="quantity" | ||||
| name="quantity" | name="quantity" | ||||
| @@ -146,21 +165,21 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
| onChange={handleInputChange} | onChange={handleInputChange} | ||||
| InputProps={{ inputProps: { min: 1 } }} | InputProps={{ inputProps: { min: 1 } }} | ||||
| placeholder="請輸入數量" | placeholder="請輸入數量" | ||||
| /> | |||||
| /> */} | |||||
| <TextField | <TextField | ||||
| fullWidth | fullWidth | ||||
| id="message" | id="message" | ||||
| name="message" | name="message" | ||||
| label="備註" | |||||
| label="上報原因" | |||||
| multiline | multiline | ||||
| rows={4} | rows={4} | ||||
| value={formData.message} | value={formData.message} | ||||
| onChange={handleInputChange} | onChange={handleInputChange} | ||||
| placeholder="請輸入您的備註" | |||||
| placeholder="請輸入上報原因" | |||||
| /> | /> | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button | <Button | ||||
| type="submit" | type="submit" | ||||
| variant="contained" | variant="contained" | ||||
| @@ -168,7 +187,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||||
| > | > | ||||
| {t("update qc info")} | {t("update qc info")} | ||||
| </Button> | </Button> | ||||
| </Stack> | |||||
| </Stack> */} | |||||
| </Box> | </Box> | ||||
| </Collapse> | </Collapse> | ||||
| </Paper> | </Paper> | ||||
| @@ -43,9 +43,11 @@ import { | |||||
| import { | import { | ||||
| checkPolAndCompletePo, | checkPolAndCompletePo, | ||||
| fetchPoInClient, | fetchPoInClient, | ||||
| fetchPoListClient, | |||||
| fetchStockInLineInfo, | fetchStockInLineInfo, | ||||
| PurchaseQcResult, | PurchaseQcResult, | ||||
| startPo, | startPo, | ||||
| createStockInLine | |||||
| } from "@/app/api/po/actions"; | } from "@/app/api/po/actions"; | ||||
| import { | import { | ||||
| useCallback, | useCallback, | ||||
| @@ -66,21 +68,20 @@ import PoQcStockInModal from "./PoQcStockInModal"; | |||||
| import QrModal from "./QrModal"; | import QrModal from "./QrModal"; | ||||
| import { PlayArrow } from "@mui/icons-material"; | import { PlayArrow } from "@mui/icons-material"; | ||||
| import DoneIcon from "@mui/icons-material/Done"; | import DoneIcon from "@mui/icons-material/Done"; | ||||
| import { getCustomWidth } from "@/app/utils/commonUtil"; | |||||
| import { downloadFile, getCustomWidth } from "@/app/utils/commonUtil"; | |||||
| import PoInfoCard from "./PoInfoCard"; | import PoInfoCard from "./PoInfoCard"; | ||||
| import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | ||||
| import { fetchPoListClient } from "@/app/api/po/actions"; | |||||
| import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material"; | 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 { Controller, FormProvider, useForm } from "react-hook-form"; | ||||
| import dayjs, { Dayjs } from "dayjs"; | import dayjs, { Dayjs } from "dayjs"; | ||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||||
| import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; | import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; | ||||
| import { debounce } from "lodash"; | import { debounce } from "lodash"; | ||||
| import LoadingComponent from "../General/LoadingComponent"; | |||||
| import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions"; | |||||
| //import { useRouter } from "next/navigation"; | //import { useRouter } from "next/navigation"; | ||||
| type Props = { | type Props = { | ||||
| po: PoResult; | po: PoResult; | ||||
| qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
| @@ -115,7 +116,7 @@ const PoSearchList: React.FC<{ | |||||
| return ( | return ( | ||||
| <Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}> | <Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}> | ||||
| <Typography variant="h6" gutterBottom> | <Typography variant="h6" gutterBottom> | ||||
| {t("Purchase Orders")} | |||||
| {t("Purchase Order")} | |||||
| </Typography> | </Typography> | ||||
| <TextField | <TextField | ||||
| label={t("Search")} | 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", | 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 && ( | {searchTerm && ( | ||||
| <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}> | <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> | </Typography> | ||||
| )} | )} | ||||
| </Paper> | </Paper> | ||||
| @@ -184,7 +190,7 @@ interface PolInputResult { | |||||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | ||||
| const cameras = useContext(CameraContext); | const cameras = useContext(CameraContext); | ||||
| console.log(cameras); | |||||
| // console.log(cameras); | |||||
| const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
| const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
| const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | ||||
| @@ -205,6 +211,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| const searchParams = useSearchParams(); | const searchParams = useSearchParams(); | ||||
| const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null); | const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null); | ||||
| const defaultPolId = searchParams.get("polId") | |||||
| useEffect(() => { | |||||
| if (defaultPolId) { | |||||
| setSelectedRow(rows.find((r) => r.id.toString() === defaultPolId) ?? null) | |||||
| } | |||||
| }, []) | |||||
| const [stockInLine, setStockInLine] = useState<StockInLine[]>([]); | const [stockInLine, setStockInLine] = useState<StockInLine[]>([]); | ||||
| const [processedQty, setProcessedQty] = useState(0); | const [processedQty, setProcessedQty] = useState(0); | ||||
| @@ -212,6 +224,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const [poList, setPoList] = useState<PoResult[]>([]); | const [poList, setPoList] = useState<PoResult[]>([]); | ||||
| const [selectedPoId, setSelectedPoId] = useState(po.id); | const [selectedPoId, setSelectedPoId] = useState(po.id); | ||||
| const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||||
| const currentPoId = searchParams.get('id'); | const currentPoId = searchParams.get('id'); | ||||
| const selectedIdsParam = searchParams.get('selectedIds'); | const selectedIdsParam = searchParams.get('selectedIds'); | ||||
| // const [selectedRowId, setSelectedRowId] = useState<number | null>(null); | // const [selectedRowId, setSelectedRowId] = useState<number | null>(null); | ||||
| @@ -261,6 +275,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| setProcessedQty(result.pol[0].processed); | setProcessedQty(result.pol[0].processed); | ||||
| } | } | ||||
| } | } | ||||
| // if (focusField) {console.log(focusField);focusField.focus();} | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Failed to fetch PO detail:", error); | console.error("Failed to fetch PO detail:", error); | ||||
| @@ -319,6 +334,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| setPurchaseOrder(newPo); | setPurchaseOrder(newPo); | ||||
| }, [purchaseOrder.id]); | }, [purchaseOrder.id]); | ||||
| const handleMailTemplateForStockInLine = useCallback(async (stockInLineId: number) => { | |||||
| const response = await getMailTemplateForStockInLine(stockInLineId) | |||||
| if (response) { | |||||
| downloadFile(new Uint8Array(response.blobValue), response.filename); | |||||
| } | |||||
| }, []) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| setRows(purchaseOrder.pol || []); | setRows(purchaseOrder.pol || []); | ||||
| }, [purchaseOrder]); | }, [purchaseOrder]); | ||||
| @@ -388,6 +410,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| setTimeout(async () => { | setTimeout(async () => { | ||||
| // post stock in line | // post stock in line | ||||
| const oldId = row.id; | const oldId = row.id; | ||||
| const acceptedQty = Number(polInputList[rowIndex].dnQty); | |||||
| if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update | |||||
| const postData = { | const postData = { | ||||
| dnNo: dnFormProps.watch("dnNo"), | dnNo: dnFormProps.watch("dnNo"), | ||||
| dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), | dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), | ||||
| @@ -396,7 +421,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| itemName: row.itemName, | itemName: row.itemName, | ||||
| purchaseOrderId: row.purchaseOrderId, | purchaseOrderId: row.purchaseOrderId, | ||||
| purchaseOrderLineId: row.id, | purchaseOrderLineId: row.id, | ||||
| acceptedQty: polInputList[rowIndex].dnQty || 0, | |||||
| acceptedQty: acceptedQty, | |||||
| productLotNo: polInputList[rowIndex].lotNo || '', | productLotNo: polInputList[rowIndex].lotNo || '', | ||||
| // acceptedQty: secondReceiveQty || 0, | // acceptedQty: secondReceiveQty || 0, | ||||
| // acceptedQty: row.acceptedQty, | // acceptedQty: row.acceptedQty, | ||||
| @@ -447,6 +472,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| } | } | ||||
| // setPolInputList(() => temp) | // setPolInputList(() => temp) | ||||
| }, 300), [rowIndex]); | }, 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 ( | return ( | ||||
| <> | <> | ||||
| <TableRow | <TableRow | ||||
| @@ -477,7 +506,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell> | <TableCell align="right">{integerFormatter.format(row.qty)}</TableCell> | ||||
| <TableCell align="right">{integerFormatter.format(processedQty)}</TableCell> | <TableCell align="right">{integerFormatter.format(processedQty)}</TableCell> | ||||
| <TableCell align="left">{row.uom?.code}</TableCell> | <TableCell align="left">{row.uom?.code}</TableCell> | ||||
| <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> | |||||
| {/* <TableCell align="right">{decimalFormatter.format(row.stockUom.stockQty)}</TableCell> */} | |||||
| <TableCell align="right">{decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio)}</TableCell> | |||||
| <TableCell align="left">{row.stockUom.stockUomCode}</TableCell> | <TableCell align="left">{row.stockUom.stockUomCode}</TableCell> | ||||
| {/* <TableCell align="right"> | {/* <TableCell align="right"> | ||||
| {decimalFormatter.format(totalWeight)} {weightUnit} | {decimalFormatter.format(totalWeight)} {weightUnit} | ||||
| @@ -495,6 +525,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| variant="outlined" | variant="outlined" | ||||
| defaultValue={polInputList[rowIndex]?.lotNo ?? ''} | defaultValue={polInputList[rowIndex]?.lotNo ?? ''} | ||||
| onChange={handleChange} | onChange={handleChange} | ||||
| // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} | |||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | <TableCell align="center"> | ||||
| @@ -520,7 +551,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| handleStart() | handleStart() | ||||
| } | } | ||||
| > | > | ||||
| 提交 | |||||
| {t("submit")} | |||||
| </Button> | </Button> | ||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| @@ -554,6 +585,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| </> | </> | ||||
| ); | ); | ||||
| } | } | ||||
| // ROW END | |||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | ||||
| (_e, newValue) => { | (_e, newValue) => { | ||||
| @@ -824,6 +857,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| itemDetail={selectedRow} | itemDetail={selectedRow} | ||||
| warehouse={warehouse} | warehouse={warehouse} | ||||
| fetchPoDetail={fetchPoDetail} | fetchPoDetail={fetchPoDetail} | ||||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| @@ -10,6 +10,7 @@ import PoDetail from "./PoDetail"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { fetchWarehouseList } from "@/app/api/warehouse"; | import { fetchWarehouseList } from "@/app/api/warehouse"; | ||||
| import { fetchQcItemCheck } from "@/app/api/qc/actions"; | import { fetchQcItemCheck } from "@/app/api/qc/actions"; | ||||
| import { fetchEscalationCombo } from "@/app/api/user"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof PoDetailLoading; | Loading: typeof PoDetailLoading; | ||||
| @@ -20,10 +21,16 @@ type Props = { | |||||
| }; | }; | ||||
| const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | ||||
| const [poWithStockInLine, warehouse, qc] = await Promise.all([ | |||||
| const [ | |||||
| poWithStockInLine, | |||||
| warehouse, | |||||
| qc, | |||||
| escalationCombo | |||||
| ] = await Promise.all([ | |||||
| fetchPoWithStockInLines(id), | fetchPoWithStockInLines(id), | ||||
| fetchWarehouseList(), | fetchWarehouseList(), | ||||
| fetchQcItemCheck(), | fetchQcItemCheck(), | ||||
| fetchEscalationCombo(), | |||||
| ]); | ]); | ||||
| // const poWithStockInLine = await fetchPoWithStockInLines(id) | // const poWithStockInLine = await fetchPoWithStockInLines(id) | ||||
| @@ -19,7 +19,7 @@ type Props = { | |||||
| po: PoResult; | po: PoResult; | ||||
| }; | }; | ||||
| const PoInfoCard: React.FC<Props> = async ( | |||||
| const PoInfoCard: React.FC<Props> = ( | |||||
| { | { | ||||
| // id | // id | ||||
| po | po | ||||
| @@ -74,6 +74,7 @@ interface Props { | |||||
| stockInLine: StockInLine[]; | stockInLine: StockInLine[]; | ||||
| warehouse: WarehouseResult[]; | warehouse: WarehouseResult[]; | ||||
| fetchPoDetail: (poId: string) => void; | fetchPoDetail: (poId: string) => void; | ||||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||||
| } | } | ||||
| export type StockInLineEntryError = { | export type StockInLineEntryError = { | ||||
| @@ -112,7 +113,8 @@ function PoInputGrid({ | |||||
| itemDetail, | itemDetail, | ||||
| stockInLine, | stockInLine, | ||||
| warehouse, | warehouse, | ||||
| fetchPoDetail | |||||
| fetchPoDetail, | |||||
| handleMailTemplateForStockInLine | |||||
| }: Props) { | }: Props) { | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| const { t } = useTranslation("purchaseOrder"); | const { t } = useTranslation("purchaseOrder"); | ||||
| @@ -270,6 +272,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 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 | ||||
| @@ -282,46 +285,51 @@ const closeNewModal = useCallback(() => { | |||||
| // Open modal | // Open modal | ||||
| const openNewModal = useCallback(() => { | const openNewModal = useCallback(() => { | ||||
| setNewOpen(true); | |||||
| setNewOpen(() => true); | |||||
| }, []); | }, []); | ||||
| // Button handler to update the URL and open the modal | // Button handler to update the URL and open the modal | ||||
| const handleNewQC = useCallback( | const handleNewQC = useCallback( | ||||
| (id: GridRowId, params: any) => async () => { | |||||
| (id: GridRowId, params: any) => async() => { | |||||
| // console.log(id) | // console.log(id) | ||||
| // console.log(params) | // console.log(params) | ||||
| setBtnIsLoading(true); | |||||
| // setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| })); | })); | ||||
| const qcResult = await fetchQcDefaultValue(id); | const qcResult = await fetchQcDefaultValue(id); | ||||
| setModalInfo({ | |||||
| setModalInfo(() => ({ | |||||
| ...params.row, | ...params.row, | ||||
| qcResult: qcResult, | qcResult: qcResult, | ||||
| receivedQty: itemDetail.receivedQty, | receivedQty: itemDetail.receivedQty, | ||||
| }); | |||||
| })); | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| const newParams = new URLSearchParams(searchParams.toString()); | const newParams = new URLSearchParams(searchParams.toString()); | ||||
| newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates | newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates | ||||
| router.replace(`${pathname}?${newParams.toString()}`); | router.replace(`${pathname}?${newParams.toString()}`); | ||||
| console.log("hello") | |||||
| // console.log("hello") | |||||
| openNewModal() | openNewModal() | ||||
| setBtnIsLoading(false); | |||||
| // setBtnIsLoading(false); | |||||
| }, 200); | }, 200); | ||||
| }, | }, | ||||
| [fetchQcDefaultValue, openNewModal, pathname, router, searchParams] | [fetchQcDefaultValue, openNewModal, pathname, router, searchParams] | ||||
| ); | ); | ||||
| // Open modal if `stockInLineId` exists in the URL | // Open modal if `stockInLineId` exists in the URL | ||||
| const [firstCheckForSil, setFirstCheckForSil] = useState(false) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (stockInLineId) { | |||||
| console.log("heeloo") | |||||
| console.log(stockInLineId) | |||||
| handleNewQC(stockInLineId, apiRef.current.getRow(stockInLineId)); | |||||
| if (stockInLineId && itemDetail && !firstCheckForSil) { | |||||
| // console.log("heeloo") | |||||
| // console.log(stockInLineId) | |||||
| // console.log(apiRef.current.getRow(stockInLineId)) | |||||
| setFirstCheckForSil(true) | |||||
| const fn = handleNewQC(stockInLineId, {row: apiRef.current.getRow(stockInLineId)}); | |||||
| fn(); | |||||
| } | } | ||||
| }, [stockInLineId, newOpen, handleNewQC, apiRef]); | |||||
| }, [stockInLineId, poLineId, itemDetail]); | |||||
| const handleEscalation = useCallback( | const handleEscalation = useCallback( | ||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| // setBtnIsLoading(true); | // setBtnIsLoading(true); | ||||
| @@ -580,7 +588,7 @@ const closeNewModal = useCallback(() => { | |||||
| }} | }} | ||||
| onClick={handleNewQC(params.row.id, params)} | onClick={handleNewQC(params.row.id, params)} | ||||
| color="inherit" | color="inherit" | ||||
| key="edit" | |||||
| key={`edit`} | |||||
| />, | />, | ||||
| <GridActionsCellItem | <GridActionsCellItem | ||||
| icon={<Button | icon={<Button | ||||
| @@ -589,7 +597,7 @@ const closeNewModal = useCallback(() => { | |||||
| variant="contained" | variant="contained" | ||||
| color="primary" | color="primary" | ||||
| sx={{ width: '150px' }} | sx={{ width: '150px' }} | ||||
| // onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | |||||
| onClick={() => handleMailTemplateForStockInLine(params.row.id as number)} | |||||
| > | > | ||||
| {t("email supplier")} | {t("email supplier")} | ||||
| </Button>} | </Button>} | ||||
| @@ -598,7 +606,7 @@ const closeNewModal = useCallback(() => { | |||||
| // color: "primary.main", | // color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| onClick={handleNewQC(params.row.id, params)} | |||||
| // onClick={handleNewQC(params.row.id, params)} | |||||
| color="inherit" | color="inherit" | ||||
| key="edit" | key="edit" | ||||
| />, | />, | ||||
| @@ -908,6 +916,7 @@ const closeNewModal = useCallback(() => { | |||||
| open={newOpen} | open={newOpen} | ||||
| onClose={closeNewModal} | onClose={closeNewModal} | ||||
| itemDetail={modalInfo} | itemDetail={modalInfo} | ||||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||||
| /> | /> | ||||
| </> | </> | ||||
| ) | ) | ||||
| @@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||||
| import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | 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 axios from "@/app/(main)/axios/axiosInstance"; | ||||
| import { NEXT_PUBLIC_API_URL } from "@/config/api"; | import { NEXT_PUBLIC_API_URL } from "@/config/api"; | ||||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | import axiosInstance from "@/app/(main)/axios/axiosInstance"; | ||||
| import EscalationComponent from "./EscalationComponent"; | import EscalationComponent from "./EscalationComponent"; | ||||
| import QcDataGrid from "./QCDatagrid"; | import QcDataGrid from "./QCDatagrid"; | ||||
| import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
| import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||||
| import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||||
| import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | |||||
| 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"; | ||||
| interface Props { | interface Props { | ||||
| itemDetail: StockInLine; | |||||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||||
| qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
| disabled: boolean; | disabled: boolean; | ||||
| qcItems: QcData[] | qcItems: QcData[] | ||||
| @@ -88,8 +88,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
| const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); | const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); | ||||
| 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 qcResult = watch("qcResult"); | |||||
| console.log(qcResult); | |||||
| // const [qcAccept, setQcAccept] = useState(true); | // const [qcAccept, setQcAccept] = useState(true); | ||||
| // const [qcItems, setQcItems] = useState(dummyQCData) | // const [qcItems, setQcItems] = useState(dummyQCData) | ||||
| @@ -184,43 +186,45 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| const qcColumns: GridColDef[] = [ | const qcColumns: GridColDef[] = [ | ||||
| { | { | ||||
| field: "qcItem", | |||||
| field: "code", | |||||
| headerName: t("qcItem"), | headerName: t("qcItem"), | ||||
| flex: 2, | flex: 2, | ||||
| renderCell: (params) => ( | renderCell: (params) => ( | ||||
| <Box> | <Box> | ||||
| <b>{params.value}</b><br/> | <b>{params.value}</b><br/> | ||||
| {params.row.qcDescription}<br/> | |||||
| {params.row.name}<br/> | |||||
| </Box> | </Box> | ||||
| ), | ), | ||||
| }, | }, | ||||
| { | { | ||||
| field: 'isPassed', | |||||
| field: 'qcPassed', | |||||
| headerName: t("qcResult"), | headerName: t("qcResult"), | ||||
| flex: 1.5, | flex: 1.5, | ||||
| renderCell: (params) => { | renderCell: (params) => { | ||||
| const currentValue = params.value; | |||||
| const currentValue = params.row; | |||||
| console.log(currentValue.row); | |||||
| return ( | return ( | ||||
| <FormControl> | <FormControl> | ||||
| <RadioGroup | <RadioGroup | ||||
| row | row | ||||
| aria-labelledby="demo-radio-buttons-group-label" | aria-labelledby="demo-radio-buttons-group-label" | ||||
| value={currentValue === undefined ? "" : (currentValue ? "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) => { | onChange={(e) => { | ||||
| const value = e.target.value; | const value = e.target.value; | ||||
| setQcItems((prev) => | 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 | <FormControlLabel | ||||
| value="true" | value="true" | ||||
| control={<Radio />} | control={<Radio />} | ||||
| label="合格" | label="合格" | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| sx={{ | sx={{ | ||||
| color: currentValue === true ? "green" : "inherit", | |||||
| color: currentValue.qcPassed === true ? "green" : "inherit", | |||||
| "& .Mui-checked": {color: "green"} | "& .Mui-checked": {color: "green"} | ||||
| }} | }} | ||||
| /> | /> | ||||
| @@ -228,9 +232,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| value="false" | value="false" | ||||
| control={<Radio />} | control={<Radio />} | ||||
| label="不合格" | label="不合格" | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| sx={{ | sx={{ | ||||
| color: currentValue === false ? "red" : "inherit", | |||||
| color: currentValue.qcPassed === false ? "red" : "inherit", | |||||
| "& .Mui-checked": {color: "red"} | "& .Mui-checked": {color: "red"} | ||||
| }} | }} | ||||
| /> | /> | ||||
| @@ -248,8 +252,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| <TextField | <TextField | ||||
| type="number" | type="number" | ||||
| size="small" | size="small" | ||||
| value={!params.row.isPassed? (params.value ?? '') : '0'} | |||||
| disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"} | |||||
| value={!params.row.qcPassed? (params.value ?? '') : '0'} | |||||
| disabled={params.row.qcPassed || disabled} | |||||
| 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); | ||||
| @@ -257,6 +261,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| setQcItems((prev) => | setQcItems((prev) => | ||||
| prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) | prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) | ||||
| ); | ); | ||||
| // setValue(`failQty`,failQty); | |||||
| }} | }} | ||||
| onClick={(e) => e.stopPropagation()} | onClick={(e) => e.stopPropagation()} | ||||
| onMouseDown={(e) => e.stopPropagation()} | onMouseDown={(e) => e.stopPropagation()} | ||||
| @@ -274,7 +279,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| <TextField | <TextField | ||||
| size="small" | size="small" | ||||
| value={params.value ?? ''} | value={params.value ?? ''} | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| 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); | ||||
| @@ -283,6 +288,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) | prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) | ||||
| ); | ); | ||||
| }} | }} | ||||
| // {...register(`qcResult.${params.row.rowIndex}.remarks`, { | |||||
| // required: "remarks required!", | |||||
| // })} | |||||
| onClick={(e) => e.stopPropagation()} | onClick={(e) => e.stopPropagation()} | ||||
| onMouseDown={(e) => e.stopPropagation()} | onMouseDown={(e) => e.stopPropagation()} | ||||
| onKeyDown={(e) => e.stopPropagation()} | onKeyDown={(e) => e.stopPropagation()} | ||||
| @@ -293,11 +301,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| }, | }, | ||||
| ] | ] | ||||
| useEffect(() => { | |||||
| console.log(itemDetail); | |||||
| }, [itemDetail]); | |||||
| // Set initial value for acceptQty | // Set initial value for acceptQty | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (itemDetail?.demandQty > 0) { //!== undefined) { | if (itemDetail?.demandQty > 0) { //!== undefined) { | ||||
| @@ -308,10 +311,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | ||||
| // const [openCollapse, setOpenCollapse] = useState(false) | // const [openCollapse, setOpenCollapse] = useState(false) | ||||
| const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | |||||
| const [isCollapsed, setIsCollapsed] = useState<boolean>(true); | |||||
| const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { | |||||
| const isFailed = qcItems.some((qc) => !qc.isPassed) | |||||
| const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => { | |||||
| const isFailed = qcItems.some((qc) => !qc.qcPassed) | |||||
| console.log(isFailed) | console.log(isFailed) | ||||
| if (isFailed) { | if (isFailed) { | ||||
| setIsCollapsed(true) | setIsCollapsed(true) | ||||
| @@ -327,10 +330,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log(itemDetail); | |||||
| console.log("ItemDetail in QC:", itemDetail); | |||||
| }, [itemDetail]); | }, [itemDetail]); | ||||
| 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]); // Removed onFailedOpenCollapse from dependency array | ||||
| @@ -366,19 +370,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| /> */} | /> */} | ||||
| <StyledDataGrid | <StyledDataGrid | ||||
| columns={qcColumns} | columns={qcColumns} | ||||
| rows={qcItems} | |||||
| rows={disabled? qcResult:qcItems} | |||||
| autoHeight | autoHeight | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| {!qcAccept && ( | |||||
| <Grid item xs={12}> | |||||
| <EscalationComponent | |||||
| forSupervisor={false} | |||||
| isCollapsed={isCollapsed} | |||||
| setIsCollapsed={setIsCollapsed} | |||||
| /> | |||||
| </Grid>)} | |||||
| </> | </> | ||||
| )} | )} | ||||
| {tabIndex == 1 && ( | {tabIndex == 1 && ( | ||||
| @@ -425,7 +420,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| field.onChange(value); | field.onChange(value); | ||||
| }} | }} | ||||
| > | > | ||||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| <FormControlLabel disabled={disabled} | |||||
| value="true" control={<Radio />} label="接受" /> | value="true" control={<Radio />} label="接受" /> | ||||
| <Box sx={{mr:2}}> | <Box sx={{mr:2}}> | ||||
| <TextField | <TextField | ||||
| @@ -434,7 +429,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| sx={{ width: '150px' }} | sx={{ width: '150px' }} | ||||
| value={qcAccept? accQty : 0 } | value={qcAccept? accQty : 0 } | ||||
| defaultValue={accQty} | defaultValue={accQty} | ||||
| disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={!qcAccept || disabled} | |||||
| {...register("acceptQty", { | {...register("acceptQty", { | ||||
| required: "acceptQty required!", | required: "acceptQty required!", | ||||
| })} | })} | ||||
| @@ -442,15 +437,27 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||||
| helperText={errors.acceptQty?.message} | helperText={errors.acceptQty?.message} | ||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| <FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| <FormControlLabel disabled={disabled} | |||||
| value="false" control={<Radio />} | value="false" control={<Radio />} | ||||
| sx={{"& .Mui-checked": {color: "red"}}} | sx={{"& .Mui-checked": {color: "red"}}} | ||||
| label="不接受及上報" /> | |||||
| label="不接受" /> | |||||
| <FormControlLabel disabled={disabled} | |||||
| value="false" control={<Radio />} | |||||
| sx={{"& .Mui-checked": {color: "blue"}}} | |||||
| label="上報品檢結果" /> | |||||
| </RadioGroup> | </RadioGroup> | ||||
| )} | )} | ||||
| /> | /> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| {!qcAccept && ( | |||||
| <Grid item xs={12}> | |||||
| <EscalationComponent | |||||
| forSupervisor={false} | |||||
| isCollapsed={isCollapsed} | |||||
| setIsCollapsed={setIsCollapsed} | |||||
| /> | |||||
| </Grid>)} | |||||
| {/* {qcAccept && <Grid item xs={12}> | {/* {qcAccept && <Grid item xs={12}> | ||||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | <Typography variant="h6" display="block" marginBlockEnd={1}> | ||||
| {t("Escalation Result")} | {t("Escalation Result")} | ||||
| @@ -1,7 +1,7 @@ | |||||
| "use client"; | "use client"; | ||||
| import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
| import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||||
| import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions"; | |||||
| import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Button, | Button, | ||||
| @@ -19,10 +19,9 @@ import StockInForm from "./StockInForm"; | |||||
| import StockInFormVer2 from "./StockInFormVer2"; | import StockInFormVer2 from "./StockInFormVer2"; | ||||
| import QcFormVer2 from "./QcFormVer2"; | import QcFormVer2 from "./QcFormVer2"; | ||||
| import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
| import { dummyPutawayLine, dummyQCData, QcData } 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 { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions"; | |||||
| import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; | import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| @@ -36,7 +35,7 @@ const style = { | |||||
| px: 5, | px: 5, | ||||
| pb: 10, | pb: 10, | ||||
| display: "block", | display: "block", | ||||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||||
| width: { xs: "90%", sm: "90%", md: "90%" }, | |||||
| // height: { xs: "60%", sm: "60%", md: "60%" }, | // height: { xs: "60%", sm: "60%", md: "60%" }, | ||||
| }; | }; | ||||
| interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
| @@ -55,6 +54,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||||
| qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
| warehouse?: any[]; | warehouse?: any[]; | ||||
| // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | ||||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||||
| } | } | ||||
| interface Props extends CommonProps { | interface Props extends CommonProps { | ||||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | ||||
| @@ -70,8 +70,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||||
| setItemDetail, | setItemDetail, | ||||
| qc, | qc, | ||||
| warehouse, | warehouse, | ||||
| handleMailTemplateForStockInLine, | |||||
| }) => { | }) => { | ||||
| console.log(warehouse); | |||||
| const { | const { | ||||
| t, | t, | ||||
| i18n: { language }, | i18n: { language }, | ||||
| @@ -113,6 +113,15 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| setOpenPutaway(isPutaway); | setOpenPutaway(isPutaway); | ||||
| }, [open]) | }, [open]) | ||||
| const [viewOnly, setViewOnly] = useState(false); | |||||
| useEffect(() => { | |||||
| if (itemDetail && itemDetail.status) { | |||||
| const isViewOnly = itemDetail.status.toLowerCase() == "completed" || itemDetail.status.toLowerCase() == "rejected" | |||||
| setViewOnly(isViewOnly) | |||||
| } | |||||
| }, [itemDetail]); | |||||
| const [openPutaway, setOpenPutaway] = useState(false); | const [openPutaway, setOpenPutaway] = useState(false); | ||||
| const onOpenPutaway = useCallback(() => { | const onOpenPutaway = useCallback(() => { | ||||
| @@ -155,21 +164,25 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| // Get QC data from the shared form context | // Get QC data from the shared form context | ||||
| const qcAccept = data.qcAccept; | const qcAccept = data.qcAccept; | ||||
| const acceptQty = data.acceptQty as number; | const acceptQty = data.acceptQty as number; | ||||
| const qcResults = 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 | // Check if all QC items have results | ||||
| const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined); | |||||
| const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | |||||
| if (itemsWithoutResult.length > 0) { | 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 | // Check if failed items have failed quantity | ||||
| const failedItemsWithoutQty = qcItems.filter(item => | |||||
| item.isPassed === false && (!item.failQty || item.failQty <= 0) | |||||
| const failedItemsWithoutQty = qcResults.filter(item => | |||||
| item.qcPassed === false && (!item.failQty || item.failQty <= 0) | |||||
| ); | ); | ||||
| if (failedItemsWithoutQty.length > 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 | // Check if QC accept decision is made | ||||
| @@ -184,10 +197,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| // Check if dates are input | // Check if dates are input | ||||
| if (data.productionDate === undefined || data.productionDate == null) { | if (data.productionDate === undefined || data.productionDate == null) { | ||||
| validationErrors.push("Production Date cannot be null!"); | |||||
| validationErrors.push("請輸入生產日期!"); | |||||
| } | } | ||||
| if (data.expiryDate === undefined || data.expiryDate == null) { | 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) { | if (validationErrors.length > 0) { | ||||
| @@ -205,12 +224,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| qcAccept: qcAccept? qcAccept : false, | qcAccept: qcAccept? qcAccept : false, | ||||
| acceptQty: acceptQty? acceptQty : 0, | acceptQty: acceptQty? acceptQty : 0, | ||||
| qcResult: qcItems.map(item => ({ | |||||
| qcResult: qcResults.map(item => ({ | |||||
| qcItemId: item.id, | qcItemId: item.id, | ||||
| // qcItem: item.qcItem, | |||||
| // code: item.code, | |||||
| // qcDescription: item.qcDescription, | // 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, | // failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, | ||||
| remarks: item.remarks || '' | remarks: item.remarks || '' | ||||
| })) | })) | ||||
| @@ -218,42 +237,21 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| // const qcData = data; | // const qcData = data; | ||||
| console.log("QC Data for submission:", qcData); | 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], | [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 postStockInLine = useCallback(async (args: ModalFormInput) => { | ||||
| const submitData = { | const submitData = { | ||||
| @@ -329,8 +327,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| const acceptQty = formProps.watch("acceptedQty") | const acceptQty = formProps.watch("acceptedQty") | ||||
| const checkQcIsPassed = useCallback((qcItems: QcData[]) => { | |||||
| const isPassed = qcItems.every((qc) => qc.isPassed); | |||||
| const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { | |||||
| const isPassed = qcItems.every((qc) => qc.qcPassed); | |||||
| console.log(isPassed) | console.log(isPassed) | ||||
| if (isPassed) { | if (isPassed) { | ||||
| formProps.setValue("passingQty", acceptQty) | formProps.setValue("passingQty", acceptQty) | ||||
| @@ -343,7 +341,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // maybe check if submitted before | // maybe check if submitted before | ||||
| console.log(qcItems) | console.log(qcItems) | ||||
| checkQcIsPassed(qcItems) | |||||
| // checkQcIsPassed(qcItems) | |||||
| }, [qcItems, checkQcIsPassed]) | }, [qcItems, checkQcIsPassed]) | ||||
| return ( | return ( | ||||
| @@ -368,7 +366,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| <PutawayForm | <PutawayForm | ||||
| itemDetail={itemDetail} | itemDetail={itemDetail} | ||||
| warehouse={warehouse!} | warehouse={warehouse!} | ||||
| disabled={false} | |||||
| disabled={viewOnly} | |||||
| /> | /> | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | <Button | ||||
| @@ -406,7 +404,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <StockInFormVer2 itemDetail={itemDetail} disabled={false} /> | |||||
| <StockInFormVer2 itemDetail={itemDetail} disabled={viewOnly} /> | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | {/* <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| @@ -428,13 +426,13 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||||
| <QcFormVer2 | <QcFormVer2 | ||||
| qc={qc!} | qc={qc!} | ||||
| itemDetail={itemDetail} | itemDetail={itemDetail} | ||||
| disabled={false} | |||||
| disabled={viewOnly} | |||||
| qcItems={qcItems} | qcItems={qcItems} | ||||
| setQcItems={setQcItems} | setQcItems={setQcItems} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| {itemDetail.status.toLowerCase() != "completed" && (<Button | |||||
| {!viewOnly && (<Button | |||||
| id="qcSubmit" | id="qcSubmit" | ||||
| type="button" | type="button" | ||||
| variant="contained" | variant="contained" | ||||
| @@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| {...register("dnNo", { | {...register("dnNo", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| // error={Boolean(errors.productLotNo)} | // error={Boolean(errors.productLotNo)} | ||||
| // helperText={errors.productLotNo?.message} | // helperText={errors.productLotNo?.message} | ||||
| /> | /> | ||||
| @@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| {...register("productLotNo", { | {...register("productLotNo", { | ||||
| // required: "productLotNo required!", | // required: "productLotNo required!", | ||||
| })} | })} | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| error={Boolean(errors.productLotNo)} | error={Boolean(errors.productLotNo)} | ||||
| helperText={errors.productLotNo?.message} | helperText={errors.productLotNo?.message} | ||||
| /> | /> | ||||
| @@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| sx={{ width: "100%" }} | sx={{ width: "100%" }} | ||||
| label={t("productionDate")} | label={t("productionDate")} | ||||
| value={productionDate ? dayjs(productionDate) : undefined} | value={productionDate ? dayjs(productionDate) : undefined} | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | onChange={(date) => { | ||||
| if (!date) return; | if (!date) return; | ||||
| setValue( | setValue( | ||||
| @@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| sx={{ width: "100%" }} | sx={{ width: "100%" }} | ||||
| label={t("expiryDate")} | label={t("expiryDate")} | ||||
| value={expiryDate ? dayjs(expiryDate) : undefined} | value={expiryDate ? dayjs(expiryDate) : undefined} | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| onChange={(date) => { | onChange={(date) => { | ||||
| console.log(date); | console.log(date); | ||||
| if (!date) return; | if (!date) return; | ||||
| @@ -311,10 +311,10 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| <TextField | <TextField | ||||
| label={t("uom")} | label={t("uom")} | ||||
| fullWidth | fullWidth | ||||
| {...register("uom", { | |||||
| {...register("uom.code", { | |||||
| required: "uom required!", | required: "uom required!", | ||||
| })} | })} | ||||
| value={uom.code} | |||||
| // value={uom?.code} | |||||
| disabled={true} | disabled={true} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||||
| <TextField | <TextField | ||||
| label={t("acceptedQty")} | label={t("acceptedQty")} | ||||
| fullWidth | fullWidth | ||||
| disabled={itemDetail.status.toLowerCase() == "completed"} | |||||
| disabled={disabled} | |||||
| {...register("acceptedQty", { | {...register("acceptedQty", { | ||||
| required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
| })} | })} | ||||
| @@ -1,52 +1,58 @@ | |||||
| import { PutawayLine } from "@/app/api/po/actions" | 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[] = [ | export const dummyQCData: QcData[] = [ | ||||
| { | { | ||||
| id: 1, | |||||
| qcItem: "包裝", | |||||
| id: 4, | |||||
| code: "包裝", | |||||
| qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | ||||
| isPassed: undefined, | |||||
| name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||||
| qcPassed: undefined, | |||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 2, | |||||
| qcItem: "肉質", | |||||
| id: 5, | |||||
| code: "肉質", | |||||
| qcDescription: "肉質鬆散,則不合格", | qcDescription: "肉質鬆散,則不合格", | ||||
| isPassed: undefined, | |||||
| name: "肉質鬆散,則不合格", | |||||
| qcPassed: undefined, | |||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 3, | |||||
| qcItem: "顔色", | |||||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,", | |||||
| isPassed: undefined, | |||||
| id: 6, | |||||
| code: "顔色", | |||||
| qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||||
| name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||||
| qcPassed: undefined, | |||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 4, | |||||
| qcItem: "狀態", | |||||
| id: 7, | |||||
| code: "狀態", | |||||
| qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | ||||
| isPassed: undefined, | |||||
| name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||||
| qcPassed: undefined, | |||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| }, | }, | ||||
| { | { | ||||
| id: 5, | |||||
| qcItem: "異物", | |||||
| id: 8, | |||||
| code: "異物", | |||||
| qcDescription: "有不屬於本食材的雜質,則不合格", | qcDescription: "有不屬於本食材的雜質,則不合格", | ||||
| isPassed: undefined, | |||||
| name: "有不屬於本食材的雜質,則不合格", | |||||
| qcPassed: undefined, | |||||
| failQty: undefined, | failQty: undefined, | ||||
| remarks: undefined, | remarks: undefined, | ||||
| }, | }, | ||||
| @@ -40,7 +40,7 @@ | |||||
| "non-consumables": "非消耗品", | "non-consumables": "非消耗品", | ||||
| "fg": "成品", | "fg": "成品", | ||||
| "sfg": "半成品", | "sfg": "半成品", | ||||
| "item": "物品", | |||||
| "item": "貨品", | |||||
| "FG":"成品", | "FG":"成品", | ||||
| "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | ||||
| "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", | ||||
| @@ -1,39 +1,46 @@ | |||||
| { | { | ||||
| "Dashboard": "資訊展示面板", | |||||
| "Order status": "訂單狀態", | |||||
| "pending": "未收貨", | |||||
| "receiving": "收貨中", | |||||
| "total": "未完成總數", | |||||
| "Warehouse temperature record": "倉庫溫度記錄", | |||||
| "Warehouse type": "倉庫類型", | |||||
| "Last 6 hours": "過去6小時", | |||||
| "Last 24 hours": "過去24小時", | |||||
| "Cold storage": "冷藏倉", | |||||
| "Normal temperature storage": "常溫倉", | |||||
| "Temperature status": "溫度狀態", | |||||
| "Humidity status": "濕度狀態", | |||||
| "Warehouse status": "倉庫狀態", | |||||
| "Progress chart": "進度圖表", | |||||
| "Purchase Order Code": "採購單號", | |||||
| "Item Name": "貨品名稱", | |||||
| "Escalation Level": "上報等級", | |||||
| "Reason": "原因", | |||||
| "Order completion": "訂單完成度", | |||||
| "Raw material": "原料", | |||||
| "Consumable": "消耗品", | |||||
| "Shipment": "出貨", | |||||
| "Extracted order": "已提取提料單", | |||||
| "Pending order": "待提取提料單", | |||||
| "Temperature": "溫度", | |||||
| "Humidity": "濕度", | |||||
| "Pending storage": "待入倉物料", | |||||
| "Total storage": "已入倉物料", | |||||
| "Application completion": "提料申請完成度", | |||||
| "Processed application": "已處理提料申請", | |||||
| "Pending application": "待處理提料申請", | |||||
| "pending inspection material": "待品檢物料", | |||||
| "inspected material": "已品檢物料", | |||||
| "total material": "物料總數", | |||||
| "stock in escalation list": "收貨已上報列表" | |||||
| } | |||||
| "Dashboard": "資訊展示面板", | |||||
| "Order status": "訂單狀態", | |||||
| "pending": "未收貨", | |||||
| "receiving": "收貨中", | |||||
| "total": "未完成總數", | |||||
| "Warehouse temperature record": "倉庫溫度記錄", | |||||
| "Warehouse type": "倉庫類型", | |||||
| "Last 6 hours": "過去6小時", | |||||
| "Last 24 hours": "過去24小時", | |||||
| "Cold storage": "冷藏倉", | |||||
| "Normal temperature storage": "常溫倉", | |||||
| "Temperature status": "溫度狀態", | |||||
| "Humidity status": "濕度狀態", | |||||
| "Warehouse status": "倉庫狀態", | |||||
| "Progress chart": "進度圖表", | |||||
| "Purchase Order Code": "採購單號", | |||||
| "Item Name": "貨品名稱", | |||||
| "Escalation Level": "上報等級", | |||||
| "Reason": "原因", | |||||
| "Order completion": "訂單完成度", | |||||
| "Raw material": "原料", | |||||
| "Consumable": "消耗品", | |||||
| "Shipment": "出貨", | |||||
| "Extracted order": "已提取提料單", | |||||
| "Pending order": "待提取提料單", | |||||
| "Temperature": "溫度", | |||||
| "Humidity": "濕度", | |||||
| "Pending storage": "待入倉物料", | |||||
| "Total storage": "已入倉物料", | |||||
| "Application completion": "提料申請完成度", | |||||
| "Processed application": "已處理提料申請", | |||||
| "Pending application": "待處理提料申請", | |||||
| "pending inspection material": "待品檢物料", | |||||
| "inspected material": "已品檢物料", | |||||
| "total material": "物料總數", | |||||
| "stock in escalation list": "收貨已上報列表", | |||||
| "Responsible for handling colleagues": "負責處理同事", | |||||
| "Completed QC Total": "品檢完成數量", | |||||
| "QC Fail Count": "品檢不合格數量", | |||||
| "DN Date": "送貨日期", | |||||
| "Received Qty": "收貨數量", | |||||
| "Escalation List": "上報列表", | |||||
| "Purchase UoM": "計量單位", | |||||
| "QC Completed Count": "品檢完成數量" | |||||
| } | |||||
| @@ -15,6 +15,6 @@ | |||||
| "Base UoM": "基本單位", | "Base UoM": "基本單位", | ||||
| "Lot No": "批號", | "Lot No": "批號", | ||||
| "Expiry Date": "到期日", | "Expiry Date": "到期日", | ||||
| "No items are selected yet.": "未選擇項目", | |||||
| "Item selected": "已選擇項目" | |||||
| "No items are selected yet.": "未選擇貨品", | |||||
| "Item selected": "已選擇貨品" | |||||
| } | } | ||||
| @@ -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": "已找到" | |||||
| } | |||||
| @@ -314,7 +314,7 @@ const components: ThemeOptions["components"] = { | |||||
| styleOverrides: { | styleOverrides: { | ||||
| root: { | root: { | ||||
| borderBottomColor: palette.divider, | borderBottomColor: palette.divider, | ||||
| padding: "1px 6px", | |||||
| padding: "10px 6px", | |||||
| fontSize: defaultFontSize - 2, | fontSize: defaultFontSize - 2, | ||||
| // padding: "15px 16px", | // padding: "15px 16px", | ||||
| // lineHeight: 1.5, | // lineHeight: 1.5, | ||||
| @@ -342,6 +342,13 @@ const components: ThemeOptions["components"] = { | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||
| // MuiTableFooter: { | |||||
| // styleOverrides: { | |||||
| // root: { | |||||
| // padding: "1px 6px", | |||||
| // }, | |||||
| // }, | |||||
| // }, | |||||
| MuiTextField: { | MuiTextField: { | ||||
| defaultProps: { | defaultProps: { | ||||
| variant: "filled", | variant: "filled", | ||||