# Conflicts: # src/app/api/pickOrder/actions.tsmaster
@@ -4,6 +4,7 @@ import { Suspense } from "react"; | |||
import { getServerI18n } from "@/i18n"; | |||
import DashboardPage from "@/components/DashboardPage"; | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { fetchEscalationLogsByUser } from "@/app/api/escalation"; | |||
export const metadata: Metadata = { | |||
title: "Dashboard", | |||
@@ -14,6 +15,8 @@ type Props = {} & SearchParams; | |||
const Dashboard: React.FC<Props> = async ({ searchParams }) => { | |||
const { t } = await getServerI18n("dashboard"); | |||
fetchEscalationLogsByUser() | |||
return ( | |||
<I18nProvider namespaces={["dashboard", "common"]}> | |||
<Suspense fallback={<DashboardPage.Loading />}> | |||
@@ -37,7 +37,7 @@ export default async function MainLayout({ | |||
return ( | |||
<SessionProviderWrapper session={session}> | |||
<UploadProvider> | |||
<CameraProvider> | |||
{/* <CameraProvider> */} | |||
<AxiosProvider> | |||
<QrCodeScannerProvider> | |||
<> | |||
@@ -62,7 +62,7 @@ export default async function MainLayout({ | |||
</> | |||
</QrCodeScannerProvider> | |||
</AxiosProvider> | |||
</CameraProvider> | |||
{/* </CameraProvider> */} | |||
</UploadProvider> | |||
</SessionProviderWrapper> | |||
); | |||
@@ -1,3 +1,4 @@ | |||
import { fetchEscalationCombo } from "@/app/api/user"; | |||
import { SearchParams } from "@/app/utils/fetchUtil"; | |||
import { TypeEnum } from "@/app/utils/typeEnum"; | |||
import CreateProductMaterial from "@/components/CreateItem"; | |||
@@ -22,6 +23,9 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => { | |||
if (!id) { | |||
notFound(); | |||
} | |||
fetchEscalationCombo() | |||
return ( | |||
<> | |||
{/* <Typography variant="h4">{t("Create Material")}</Typography> */} | |||
@@ -30,9 +30,6 @@ export interface StockInLineEntry { | |||
acceptedQty: number; | |||
status?: string; | |||
expiryDate?: string; | |||
productLotNo?: string; | |||
receiptDate?: string; | |||
dnDate?: string; | |||
} | |||
export interface PurchaseQcResult { | |||
@@ -6,7 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||
import { BASE_API_URL } from "../../../config/api"; | |||
import { Uom } from "../settings/uom"; | |||
import { RecordsRes } from "../utils"; | |||
import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval"; | |||
// import { IQCItems } from "@/components/DashboardPage/QC/SupervisorQcApproval"; | |||
export interface PoResult { | |||
id: number; | |||
@@ -84,7 +84,8 @@ export const fetchPoWithStockInLines = cache(async (id: number) => { | |||
}); | |||
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 () => { | |||
return serverFetchJson<GetPickOrderInfoResponse>( | |||
`${BASE_API_URL}/pickOrder/detail`, | |||
@@ -31,13 +31,17 @@ export interface StockInLineEntry { | |||
acceptedQty: number; | |||
status?: string; | |||
expiryDate?: string; | |||
productLotNo?: string; | |||
receiptDate?: string; | |||
dnDate?: string; | |||
} | |||
export interface PurchaseQcResult { | |||
export interface PurchaseQcResult{ | |||
qcItemId: number; | |||
isPassed: boolean, | |||
qcPassed: boolean; | |||
failQty: number; | |||
remarks?: string | |||
remarks?: string; | |||
} | |||
export interface StockInInput { | |||
status: string; | |||
@@ -110,7 +114,7 @@ export const fetchStockInLineInfo = cache(async (stockInLineId: number) => { | |||
export const createStockInLine = async (data: StockInLineEntry) => { | |||
const stockInLine = await serverFetchJson< | |||
PostStockInLineResponse<StockInLineEntry> | |||
PostStockInLineResponse<StockInLine> | |||
>(`${BASE_API_URL}/stockInLine/create`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
@@ -124,7 +128,7 @@ export const updateStockInLine = async ( | |||
data: StockInLineEntry & ModalFormInput, | |||
) => { | |||
const stockInLine = await serverFetchJson< | |||
PostStockInLineResponse<StockInLineEntry & ModalFormInput> | |||
PostStockInLineResponse<StockInLine & ModalFormInput> | |||
>(`${BASE_API_URL}/stockInLine/update`, { | |||
method: "POST", | |||
body: JSON.stringify(data), | |||
@@ -15,6 +15,11 @@ export interface PoResult { | |||
estimatedArrivalDate: string; | |||
completedDate: string; | |||
itemDetail?: string; | |||
itemCode?: string; | |||
itemName?: string; | |||
itemQty?: string; | |||
itemSumAcceptedQty?: string; | |||
itemUom?: string; | |||
escalated: boolean; | |||
status: string; | |||
pol?: PurchaseOrderLine[]; | |||
@@ -49,7 +54,7 @@ export interface StockUomForPoLine { | |||
export interface StockInLine { | |||
id: number; | |||
stockInId: number; | |||
stockInId?: number; | |||
purchaseOrderId?: number; | |||
purchaseOrderLineId: number; | |||
itemId: number; | |||
@@ -58,22 +63,24 @@ export interface StockInLine { | |||
itemType: string; | |||
demandQty: number; | |||
acceptedQty: number; | |||
qty: number; | |||
processed: number; | |||
price: number; | |||
priceUnit: string; | |||
qty?: number; | |||
processed?: number; | |||
price?: number; | |||
priceUnit?: string; | |||
shelfLife?: number; | |||
receiptDate?: string; | |||
productionDate?: string; | |||
productLotNo?: string; | |||
expiryDate?: string; | |||
status: string; | |||
supplier: string; | |||
lotNo: string; | |||
poCode: string; | |||
uom: Uom; | |||
supplier?: string; | |||
lotNo?: string; | |||
poCode?: string; | |||
uom?: Uom; | |||
defaultWarehouseId: number; // id for now | |||
dnNo: string; | |||
dnDate: number[]; | |||
dnNo?: string; | |||
dnDate?: number[]; | |||
stockQty?: number; | |||
} | |||
export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | |||
@@ -15,6 +15,16 @@ export interface QcItemWithChecks { | |||
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 () => { | |||
return serverFetchJson<QcItemWithChecks[]>(`${BASE_API_URL}/qc/list`, { | |||
next: { tags: ["qc"] }, | |||
@@ -31,6 +31,15 @@ export type passwordRule = { | |||
specialChar: boolean; | |||
}; | |||
export interface EscalationCombo { | |||
id: number; | |||
value: number; | |||
label: string; | |||
name: string; | |||
title: string; | |||
department: string; | |||
} | |||
export const preloadUser = () => { | |||
fetchUser(); | |||
}; | |||
@@ -56,3 +65,9 @@ export const fetchPwRules = cache(async () => { | |||
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 { redirect } from "next/navigation"; | |||
export interface BlobResponse { | |||
filename: string; | |||
blobValue: Uint8Array; | |||
} | |||
export interface Pageable { | |||
pageSize?: 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); | |||
if (response.ok) { | |||
@@ -14,13 +14,17 @@ import ApplicationCompletionChart from "./chart/ApplicationCompletionChart"; | |||
import OrderCompletionChart from "./chart/OrderCompletionChart"; | |||
import DashboardBox from "./Dashboardbox"; | |||
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 = { | |||
iqc: IQCItems[] | |||
// iqc: IQCItems[] | undefined | |||
escalationLogs: EscalationResult[] | |||
}; | |||
const DashboardPage: React.FC<Props> = ({ | |||
iqc | |||
// iqc, | |||
escalationLogs | |||
}) => { | |||
const { t } = useTranslation("dashboard"); | |||
const router = useRouter(); | |||
@@ -29,9 +33,9 @@ const DashboardPage: React.FC<Props> = ({ | |||
<ThemeProvider theme={theme}> | |||
<Grid container spacing={2}> | |||
<Grid item xs={12}> | |||
<CollapsibleCard title={t("stock in escalation list")}> | |||
<CollapsibleCard title={t("Escalation List")}> | |||
<CardContent> | |||
<SupervisorQcApproval items={iqc || []}/> | |||
<EscalationLogTable items={escalationLogs || []}/> | |||
</CardContent> | |||
</CollapsibleCard> | |||
</Grid> | |||
@@ -4,7 +4,8 @@ import DashboardPage from "./DashboardPage"; | |||
import { Typography } from "@mui/material"; | |||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||
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 = { | |||
// abilities: string[] | |||
@@ -23,16 +24,20 @@ const DashboardWrapper: React.FC<Props> & SubComponents = async ({ | |||
}) => { | |||
const { t } = await getServerI18n("dashboard"); | |||
// 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 ( | |||
<> | |||
<Typography variant="h4">{t("Dashboard")}</Typography> | |||
<DashboardPage | |||
iqc={iqcLog} | |||
escalationLogs={escalationLogs} | |||
// iqc={iqcLog} | |||
// 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 { useRouter } from "next/navigation"; | |||
import { useCallback, useState } from "react"; | |||
import { useCallback, useMemo, useState } from "react"; | |||
import { usePathname } from "next/navigation"; | |||
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 = { | |||
id: number; | |||
@@ -18,11 +22,11 @@ export type IQCItems = { | |||
}; | |||
type Props = { | |||
items: IQCItems[]; | |||
items: EscalationResult[]; | |||
}; | |||
const SupervisorQcApproval: React.FC<Props> = ({ | |||
items | |||
const EscalationLogTable: React.FC<Props> = ({ | |||
items | |||
}) => { | |||
const { t } = useTranslation("dashboard"); | |||
const CARD_HEADER = t("stock in escalation list") | |||
@@ -31,8 +35,8 @@ const SupervisorQcApproval: React.FC<Props> = ({ | |||
const router = useRouter(); | |||
const [selectedId, setSelectedId] = useState<number | null>(null); | |||
const navigateTo = useCallback( | |||
(item: IQCItems) => { | |||
const navigateTo = useCallback( | |||
(item: EscalationResult) => { | |||
setSelectedId(item.id); | |||
console.log(pathname) | |||
router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); | |||
@@ -40,17 +44,62 @@ const navigateTo = useCallback( | |||
[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}> | |||
<Table aria-label="Two column navigable table" size="small"> | |||
<TableHead> | |||
@@ -87,7 +136,15 @@ const navigateTo = useCallback( | |||
</TableBody> | |||
</Table> | |||
</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[]; | |||
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
needAdd?: boolean; | |||
showRemoveBtn?: boolean; | |||
} | |||
export type Props<T, V, E> = | |||
@@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent"; | |||
import QcDataGrid from "./QCDatagrid"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||
import { ModalFormInput } from "@/app/api/po/actions"; | |||
import { escape } from "lodash"; | |||
interface Props { | |||
@@ -83,10 +83,18 @@ const EscalationComponent: React.FC<Props> = ({ | |||
return ( | |||
// <Paper elevation={3} sx={{ maxWidth: 400, mx: 'auto', p: 3 }}> | |||
<> | |||
<Paper> | |||
<Paper sx={{padding: 2}}> | |||
{/* <Paper elevation={3} sx={{ mx: 'auto', p: 3 }}> */} | |||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> | |||
<FormControlLabel | |||
<Box sx={{ display: 'flex', alignItems: 'center' }}> | |||
<Typography variant="body1">上報結果</Typography> | |||
{/* {isCollapsed ? ( | |||
<ExpandLessIcon sx={{ ml: 1 }} /> | |||
) : ( | |||
<ExpandMoreIcon sx={{ ml: 1 }} /> | |||
)} */} | |||
</Box> | |||
{/* <FormControlLabel | |||
control={ | |||
<Checkbox | |||
checked={isCollapsed} | |||
@@ -104,23 +112,10 @@ const EscalationComponent: React.FC<Props> = ({ | |||
)} | |||
</Box> | |||
} | |||
/> | |||
/> */} | |||
</Box> | |||
<Collapse in={isCollapsed}> | |||
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> | |||
{forSupervisor ? ( | |||
<FormControl> | |||
<RadioGroup | |||
row | |||
aria-labelledby="demo-radio-buttons-group-label" | |||
defaultValue="pass" | |||
name="radio-buttons-group" | |||
> | |||
<FormControlLabel value="pass" control={<Radio />} label="合格" /> | |||
<FormControlLabel value="fail" control={<Radio />} label="不合格" /> | |||
</RadioGroup> | |||
</FormControl> | |||
): undefined} | |||
<FormControl fullWidth> | |||
<select | |||
id="name" | |||
@@ -136,7 +131,31 @@ const EscalationComponent: React.FC<Props> = ({ | |||
))} | |||
</select> | |||
</FormControl> | |||
<TextField | |||
{forSupervisor ? ( | |||
<FormControl> | |||
<RadioGroup | |||
row | |||
aria-labelledby="demo-radio-buttons-group-label" | |||
defaultValue="pass" | |||
name="radio-buttons-group" | |||
> | |||
<FormControlLabel value="pass" control={<Radio />} label="合格" /> | |||
<FormControlLabel value="fail" control={<Radio />} label="不合格" /> | |||
</RadioGroup> | |||
</FormControl> | |||
): undefined} | |||
{forSupervisor && (<TextField | |||
fullWidth | |||
id="decision" | |||
name="decision" | |||
label="上報結果" | |||
type="radio" | |||
// value={formData.decision} | |||
onChange={handleInputChange} | |||
InputProps={{ inputProps: { min: 1 } }} | |||
placeholder="請決定上報結果" | |||
/>)} | |||
{/* <TextField | |||
fullWidth | |||
id="quantity" | |||
name="quantity" | |||
@@ -146,21 +165,21 @@ const EscalationComponent: React.FC<Props> = ({ | |||
onChange={handleInputChange} | |||
InputProps={{ inputProps: { min: 1 } }} | |||
placeholder="請輸入數量" | |||
/> | |||
/> */} | |||
<TextField | |||
fullWidth | |||
id="message" | |||
name="message" | |||
label="備註" | |||
label="上報原因" | |||
multiline | |||
rows={4} | |||
value={formData.message} | |||
onChange={handleInputChange} | |||
placeholder="請輸入您的備註" | |||
placeholder="請輸入上報原因" | |||
/> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
{/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
type="submit" | |||
variant="contained" | |||
@@ -168,7 +187,7 @@ const EscalationComponent: React.FC<Props> = ({ | |||
> | |||
{t("update qc info")} | |||
</Button> | |||
</Stack> | |||
</Stack> */} | |||
</Box> | |||
</Collapse> | |||
</Paper> | |||
@@ -43,9 +43,11 @@ import { | |||
import { | |||
checkPolAndCompletePo, | |||
fetchPoInClient, | |||
fetchPoListClient, | |||
fetchStockInLineInfo, | |||
PurchaseQcResult, | |||
startPo, | |||
createStockInLine | |||
} from "@/app/api/po/actions"; | |||
import { | |||
useCallback, | |||
@@ -66,21 +68,20 @@ import PoQcStockInModal from "./PoQcStockInModal"; | |||
import QrModal from "./QrModal"; | |||
import { PlayArrow } from "@mui/icons-material"; | |||
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 { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||
import { fetchPoListClient } from "@/app/api/po/actions"; | |||
import { List, ListItem, ListItemButton, ListItemText, Divider } from "@mui/material"; | |||
import { createStockInLine } from "@/app/api/dashboard/actions"; | |||
import { Controller, FormProvider, useForm } from "react-hook-form"; | |||
import dayjs, { Dayjs } from "dayjs"; | |||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; | |||
import { debounce } from "lodash"; | |||
import LoadingComponent from "../General/LoadingComponent"; | |||
import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions"; | |||
//import { useRouter } from "next/navigation"; | |||
type Props = { | |||
po: PoResult; | |||
qc: QcItemWithChecks[]; | |||
@@ -115,7 +116,7 @@ const PoSearchList: React.FC<{ | |||
return ( | |||
<Paper sx={{ p: 2, maxHeight: "480px", overflow: "auto", minWidth: "300px", height: "480px" }}> | |||
<Typography variant="h6" gutterBottom> | |||
{t("Purchase Orders")} | |||
{t("Purchase Order")} | |||
</Typography> | |||
<TextField | |||
label={t("Search")} | |||
@@ -133,44 +134,49 @@ const PoSearchList: React.FC<{ | |||
), | |||
}} | |||
/> | |||
<List dense sx={{ width: '100%' }}> | |||
{filteredPoList.map((poItem, index) => ( | |||
<div key={poItem.id}> | |||
<ListItem disablePadding sx={{ width: '100%' }}> | |||
<ListItemButton | |||
selected={selectedPoId === poItem.id} | |||
onClick={() => onSelect(poItem)} | |||
sx={{ | |||
width: '100%', | |||
"&.Mui-selected": { | |||
backgroundColor: "primary.light", | |||
"&:hover": { | |||
{(filteredPoList.length > 0)? ( | |||
<List dense sx={{ width: '100%' }}> | |||
{filteredPoList.map((poItem, index) => ( | |||
<div key={poItem.id}> | |||
<ListItem disablePadding sx={{ width: '100%' }}> | |||
<ListItemButton | |||
selected={selectedPoId === poItem.id} | |||
onClick={() => onSelect(poItem)} | |||
sx={{ | |||
width: '100%', | |||
"&.Mui-selected": { | |||
backgroundColor: "primary.light", | |||
"&:hover": { | |||
backgroundColor: "primary.light", | |||
}, | |||
}, | |||
}, | |||
}} | |||
> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}> | |||
{poItem.code} | |||
</Typography> | |||
} | |||
secondary={ | |||
<Typography variant="caption" color="text.secondary"> | |||
{t(`${poItem.status.toLowerCase()}`)} | |||
</Typography> | |||
} | |||
/> | |||
</ListItemButton> | |||
</ListItem> | |||
{index < filteredPoList.length - 1 && <Divider />} | |||
</div> | |||
))} | |||
</List> | |||
}} | |||
> | |||
<ListItemText | |||
primary={ | |||
<Typography variant="body2" sx={{ wordBreak: 'break-all' }}> | |||
{poItem.code} | |||
</Typography> | |||
} | |||
secondary={ | |||
<Typography variant="caption" color="text.secondary"> | |||
{t(`${poItem.status.toLowerCase()}`)} | |||
</Typography> | |||
} | |||
/> | |||
</ListItemButton> | |||
</ListItem> | |||
{index < filteredPoList.length - 1 && <Divider />} | |||
</div> | |||
))} | |||
</List>) : ( | |||
<LoadingComponent/> | |||
) | |||
} | |||
{searchTerm && ( | |||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: "block" }}> | |||
{t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")} | |||
{`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`} | |||
{/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */} | |||
</Typography> | |||
)} | |||
</Paper> | |||
@@ -184,7 +190,7 @@ interface PolInputResult { | |||
const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
const cameras = useContext(CameraContext); | |||
console.log(cameras); | |||
// console.log(cameras); | |||
const { t } = useTranslation("purchaseOrder"); | |||
const apiRef = useGridApiRef(); | |||
const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | |||
@@ -205,6 +211,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
const searchParams = useSearchParams(); | |||
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 [processedQty, setProcessedQty] = useState(0); | |||
@@ -212,6 +224,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
const router = useRouter(); | |||
const [poList, setPoList] = useState<PoResult[]>([]); | |||
const [selectedPoId, setSelectedPoId] = useState(po.id); | |||
const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||
const currentPoId = searchParams.get('id'); | |||
const selectedIdsParam = searchParams.get('selectedIds'); | |||
// const [selectedRowId, setSelectedRowId] = useState<number | null>(null); | |||
@@ -261,6 +275,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
setProcessedQty(result.pol[0].processed); | |||
} | |||
} | |||
// if (focusField) {console.log(focusField);focusField.focus();} | |||
} | |||
} catch (error) { | |||
console.error("Failed to fetch PO detail:", error); | |||
@@ -319,6 +334,13 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
setPurchaseOrder(newPo); | |||
}, [purchaseOrder.id]); | |||
const handleMailTemplateForStockInLine = useCallback(async (stockInLineId: number) => { | |||
const response = await getMailTemplateForStockInLine(stockInLineId) | |||
if (response) { | |||
downloadFile(new Uint8Array(response.blobValue), response.filename); | |||
} | |||
}, []) | |||
useEffect(() => { | |||
setRows(purchaseOrder.pol || []); | |||
}, [purchaseOrder]); | |||
@@ -388,6 +410,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
setTimeout(async () => { | |||
// post stock in line | |||
const oldId = row.id; | |||
const acceptedQty = Number(polInputList[rowIndex].dnQty); | |||
if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); return; } // Temp check, need update | |||
const postData = { | |||
dnNo: dnFormProps.watch("dnNo"), | |||
dnDate: outputDateStringToInputDateString(dnFormProps.watch("dnDate")), | |||
@@ -396,7 +421,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
itemName: row.itemName, | |||
purchaseOrderId: row.purchaseOrderId, | |||
purchaseOrderLineId: row.id, | |||
acceptedQty: polInputList[rowIndex].dnQty || 0, | |||
acceptedQty: acceptedQty, | |||
productLotNo: polInputList[rowIndex].lotNo || '', | |||
// acceptedQty: secondReceiveQty || 0, | |||
// acceptedQty: row.acceptedQty, | |||
@@ -447,6 +472,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
} | |||
// setPolInputList(() => temp) | |||
}, 300), [rowIndex]); | |||
// const [focusField, setFocusField] = useState<HTMLInputElement>(); | |||
const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) | |||
return ( | |||
<> | |||
<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(processedQty)}</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="right"> | |||
{decimalFormatter.format(totalWeight)} {weightUnit} | |||
@@ -495,6 +525,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
variant="outlined" | |||
defaultValue={polInputList[rowIndex]?.lotNo ?? ''} | |||
onChange={handleChange} | |||
// onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} | |||
/> | |||
</TableCell> | |||
<TableCell align="center"> | |||
@@ -520,7 +551,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
handleStart() | |||
} | |||
> | |||
提交 | |||
{t("submit")} | |||
</Button> | |||
</TableCell> | |||
</TableRow> | |||
@@ -554,6 +585,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
</> | |||
); | |||
} | |||
// ROW END | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||
(_e, newValue) => { | |||
@@ -824,6 +857,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
itemDetail={selectedRow} | |||
warehouse={warehouse} | |||
fetchPoDetail={fetchPoDetail} | |||
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
/> | |||
</Box> | |||
</TableCell> | |||
@@ -10,6 +10,7 @@ import PoDetail from "./PoDetail"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { fetchWarehouseList } from "@/app/api/warehouse"; | |||
import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||
import { fetchEscalationCombo } from "@/app/api/user"; | |||
interface SubComponents { | |||
Loading: typeof PoDetailLoading; | |||
@@ -20,10 +21,16 @@ type Props = { | |||
}; | |||
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), | |||
fetchWarehouseList(), | |||
fetchQcItemCheck(), | |||
fetchEscalationCombo(), | |||
]); | |||
// const poWithStockInLine = await fetchPoWithStockInLines(id) | |||
@@ -19,7 +19,7 @@ type Props = { | |||
po: PoResult; | |||
}; | |||
const PoInfoCard: React.FC<Props> = async ( | |||
const PoInfoCard: React.FC<Props> = ( | |||
{ | |||
// id | |||
po | |||
@@ -74,6 +74,7 @@ interface Props { | |||
stockInLine: StockInLine[]; | |||
warehouse: WarehouseResult[]; | |||
fetchPoDetail: (poId: string) => void; | |||
handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
} | |||
export type StockInLineEntryError = { | |||
@@ -112,7 +113,8 @@ function PoInputGrid({ | |||
itemDetail, | |||
stockInLine, | |||
warehouse, | |||
fetchPoDetail | |||
fetchPoDetail, | |||
handleMailTemplateForStockInLine | |||
}: Props) { | |||
console.log(itemDetail); | |||
const { t } = useTranslation("purchaseOrder"); | |||
@@ -270,6 +272,7 @@ function PoInputGrid({ | |||
const [newOpen, setNewOpen] = useState(false); | |||
const stockInLineId = searchParams.get("stockInLineId"); | |||
const poLineId = searchParams.get("poLineId"); | |||
const closeNewModal = useCallback(() => { | |||
const newParams = new URLSearchParams(searchParams.toString()); | |||
newParams.delete("stockInLineId"); // Remove the parameter | |||
@@ -282,46 +285,51 @@ const closeNewModal = useCallback(() => { | |||
// Open modal | |||
const openNewModal = useCallback(() => { | |||
setNewOpen(true); | |||
setNewOpen(() => true); | |||
}, []); | |||
// Button handler to update the URL and open the modal | |||
const handleNewQC = useCallback( | |||
(id: GridRowId, params: any) => async () => { | |||
(id: GridRowId, params: any) => async() => { | |||
// console.log(id) | |||
// console.log(params) | |||
setBtnIsLoading(true); | |||
// setBtnIsLoading(true); | |||
setRowModesModel((prev) => ({ | |||
...prev, | |||
[id]: { mode: GridRowModes.View }, | |||
})); | |||
const qcResult = await fetchQcDefaultValue(id); | |||
setModalInfo({ | |||
setModalInfo(() => ({ | |||
...params.row, | |||
qcResult: qcResult, | |||
receivedQty: itemDetail.receivedQty, | |||
}); | |||
})); | |||
setTimeout(() => { | |||
const newParams = new URLSearchParams(searchParams.toString()); | |||
newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates | |||
router.replace(`${pathname}?${newParams.toString()}`); | |||
console.log("hello") | |||
// console.log("hello") | |||
openNewModal() | |||
setBtnIsLoading(false); | |||
// setBtnIsLoading(false); | |||
}, 200); | |||
}, | |||
[fetchQcDefaultValue, openNewModal, pathname, router, searchParams] | |||
); | |||
// Open modal if `stockInLineId` exists in the URL | |||
const [firstCheckForSil, setFirstCheckForSil] = useState(false) | |||
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( | |||
(id: GridRowId, params: any) => () => { | |||
// setBtnIsLoading(true); | |||
@@ -580,7 +588,7 @@ const closeNewModal = useCallback(() => { | |||
}} | |||
onClick={handleNewQC(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
key={`edit`} | |||
/>, | |||
<GridActionsCellItem | |||
icon={<Button | |||
@@ -589,7 +597,7 @@ const closeNewModal = useCallback(() => { | |||
variant="contained" | |||
color="primary" | |||
sx={{ width: '150px' }} | |||
// onClick={formProps.handleSubmit(onSubmitEmailSupplier)} | |||
onClick={() => handleMailTemplateForStockInLine(params.row.id as number)} | |||
> | |||
{t("email supplier")} | |||
</Button>} | |||
@@ -598,7 +606,7 @@ const closeNewModal = useCallback(() => { | |||
// color: "primary.main", | |||
// marginRight: 1, | |||
}} | |||
onClick={handleNewQC(params.row.id, params)} | |||
// onClick={handleNewQC(params.row.id, params)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
@@ -908,6 +916,7 @@ const closeNewModal = useCallback(() => { | |||
open={newOpen} | |||
onClose={closeNewModal} | |||
itemDetail={modalInfo} | |||
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
/> | |||
</> | |||
) | |||
@@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||
import axios from "@/app/(main)/axios/axiosInstance"; | |||
import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||
import axiosInstance from "@/app/(main)/axios/axiosInstance"; | |||
import EscalationComponent from "./EscalationComponent"; | |||
import QcDataGrid from "./QCDatagrid"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
import { ModalFormInput } from "@/app/api/dashboard/actions"; | |||
import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; | |||
import { ModalFormInput } from "@/app/api/po/actions"; | |||
import { escape } from "lodash"; | |||
import { PanoramaSharp } from "@mui/icons-material"; | |||
interface Props { | |||
itemDetail: StockInLine; | |||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
qc: QcItemWithChecks[]; | |||
disabled: boolean; | |||
qcItems: QcData[] | |||
@@ -88,8 +88,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
const [tabIndex, setTabIndex] = useState(0); | |||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>(); | |||
const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); | |||
const [qcResult, setQcResult] = useState(); | |||
// const [qcResult, setQcResult] = useState(); | |||
const qcAccept = watch("qcAccept"); | |||
const qcResult = watch("qcResult"); | |||
console.log(qcResult); | |||
// const [qcAccept, setQcAccept] = useState(true); | |||
// const [qcItems, setQcItems] = useState(dummyQCData) | |||
@@ -184,43 +186,45 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
const qcColumns: GridColDef[] = [ | |||
{ | |||
field: "qcItem", | |||
field: "code", | |||
headerName: t("qcItem"), | |||
flex: 2, | |||
renderCell: (params) => ( | |||
<Box> | |||
<b>{params.value}</b><br/> | |||
{params.row.qcDescription}<br/> | |||
{params.row.name}<br/> | |||
</Box> | |||
), | |||
}, | |||
{ | |||
field: 'isPassed', | |||
field: 'qcPassed', | |||
headerName: t("qcResult"), | |||
flex: 1.5, | |||
renderCell: (params) => { | |||
const currentValue = params.value; | |||
const currentValue = params.row; | |||
console.log(currentValue.row); | |||
return ( | |||
<FormControl> | |||
<RadioGroup | |||
row | |||
aria-labelledby="demo-radio-buttons-group-label" | |||
value={currentValue === undefined ? "" : (currentValue ? "true" : "false")} | |||
value={currentValue.qcPassed === undefined ? "" : (currentValue.qcPassed ? "true" : "false")} | |||
// value={currentValue.qcPassed === undefined ? (currentValue.failQty!==undefined?(currentValue.failQty==0?"true":"false"):"") : (currentValue.qcPassed ? "true" : "false")} | |||
onChange={(e) => { | |||
const value = e.target.value; | |||
setQcItems((prev) => | |||
prev.map((r): QcData => (r.id === params.id ? { ...r, isPassed: value === "true" } : r)) | |||
prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r)) | |||
); | |||
}} | |||
name={`isPassed-${params.id}`} | |||
name={`qcPassed-${params.id}`} | |||
> | |||
<FormControlLabel | |||
value="true" | |||
control={<Radio />} | |||
label="合格" | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
sx={{ | |||
color: currentValue === true ? "green" : "inherit", | |||
color: currentValue.qcPassed === true ? "green" : "inherit", | |||
"& .Mui-checked": {color: "green"} | |||
}} | |||
/> | |||
@@ -228,9 +232,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
value="false" | |||
control={<Radio />} | |||
label="不合格" | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
sx={{ | |||
color: currentValue === false ? "red" : "inherit", | |||
color: currentValue.qcPassed === false ? "red" : "inherit", | |||
"& .Mui-checked": {color: "red"} | |||
}} | |||
/> | |||
@@ -248,8 +252,8 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
<TextField | |||
type="number" | |||
size="small" | |||
value={!params.row.isPassed? (params.value ?? '') : '0'} | |||
disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"} | |||
value={!params.row.qcPassed? (params.value ?? '') : '0'} | |||
disabled={params.row.qcPassed || disabled} | |||
onChange={(e) => { | |||
const v = e.target.value; | |||
const next = v === '' ? undefined : Number(v); | |||
@@ -257,6 +261,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
setQcItems((prev) => | |||
prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) | |||
); | |||
// setValue(`failQty`,failQty); | |||
}} | |||
onClick={(e) => e.stopPropagation()} | |||
onMouseDown={(e) => e.stopPropagation()} | |||
@@ -274,7 +279,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
<TextField | |||
size="small" | |||
value={params.value ?? ''} | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
onChange={(e) => { | |||
const remarks = e.target.value; | |||
// const next = v === '' ? undefined : Number(v); | |||
@@ -283,6 +288,9 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) | |||
); | |||
}} | |||
// {...register(`qcResult.${params.row.rowIndex}.remarks`, { | |||
// required: "remarks required!", | |||
// })} | |||
onClick={(e) => e.stopPropagation()} | |||
onMouseDown={(e) => e.stopPropagation()} | |||
onKeyDown={(e) => e.stopPropagation()} | |||
@@ -293,11 +301,6 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
}, | |||
] | |||
useEffect(() => { | |||
console.log(itemDetail); | |||
}, [itemDetail]); | |||
// Set initial value for acceptQty | |||
useEffect(() => { | |||
if (itemDetail?.demandQty > 0) { //!== undefined) { | |||
@@ -308,10 +311,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
}, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); | |||
// const [openCollapse, setOpenCollapse] = useState(false) | |||
const [isCollapsed, setIsCollapsed] = useState<boolean>(false); | |||
const [isCollapsed, setIsCollapsed] = useState<boolean>(true); | |||
const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { | |||
const isFailed = qcItems.some((qc) => !qc.isPassed) | |||
const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => { | |||
const isFailed = qcItems.some((qc) => !qc.qcPassed) | |||
console.log(isFailed) | |||
if (isFailed) { | |||
setIsCollapsed(true) | |||
@@ -327,10 +330,11 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
useEffect(() => { | |||
console.log(itemDetail); | |||
console.log("ItemDetail in QC:", itemDetail); | |||
}, [itemDetail]); | |||
useEffect(() => { | |||
// onFailedOpenCollapse(qcItems) // This function is no longer needed | |||
}, [qcItems]); // Removed onFailedOpenCollapse from dependency array | |||
@@ -366,19 +370,10 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
/> */} | |||
<StyledDataGrid | |||
columns={qcColumns} | |||
rows={qcItems} | |||
rows={disabled? qcResult:qcItems} | |||
autoHeight | |||
/> | |||
</Grid> | |||
{!qcAccept && ( | |||
<Grid item xs={12}> | |||
<EscalationComponent | |||
forSupervisor={false} | |||
isCollapsed={isCollapsed} | |||
setIsCollapsed={setIsCollapsed} | |||
/> | |||
</Grid>)} | |||
</> | |||
)} | |||
{tabIndex == 1 && ( | |||
@@ -425,7 +420,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
field.onChange(value); | |||
}} | |||
> | |||
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
<FormControlLabel disabled={disabled} | |||
value="true" control={<Radio />} label="接受" /> | |||
<Box sx={{mr:2}}> | |||
<TextField | |||
@@ -434,7 +429,7 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
sx={{ width: '150px' }} | |||
value={qcAccept? accQty : 0 } | |||
defaultValue={accQty} | |||
disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"} | |||
disabled={!qcAccept || disabled} | |||
{...register("acceptQty", { | |||
required: "acceptQty required!", | |||
})} | |||
@@ -442,15 +437,27 @@ const QcFormVer2: React.FC<Props> = ({ qc, itemDetail, disabled, qcItems, setQcI | |||
helperText={errors.acceptQty?.message} | |||
/> | |||
</Box> | |||
<FormControlLabel disabled={itemDetail.status.toLowerCase() == "completed"} | |||
<FormControlLabel disabled={disabled} | |||
value="false" control={<Radio />} | |||
sx={{"& .Mui-checked": {color: "red"}}} | |||
label="不接受及上報" /> | |||
label="不接受" /> | |||
<FormControlLabel disabled={disabled} | |||
value="false" control={<Radio />} | |||
sx={{"& .Mui-checked": {color: "blue"}}} | |||
label="上報品檢結果" /> | |||
</RadioGroup> | |||
)} | |||
/> | |||
</FormControl> | |||
</Grid> | |||
{!qcAccept && ( | |||
<Grid item xs={12}> | |||
<EscalationComponent | |||
forSupervisor={false} | |||
isCollapsed={isCollapsed} | |||
setIsCollapsed={setIsCollapsed} | |||
/> | |||
</Grid>)} | |||
{/* {qcAccept && <Grid item xs={12}> | |||
<Typography variant="h6" display="block" marginBlockEnd={1}> | |||
{t("Escalation Result")} | |||
@@ -1,7 +1,7 @@ | |||
"use client"; | |||
import { StockInLine } from "@/app/api/po"; | |||
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||
import { QcItemWithChecks } from "@/app/api/qc"; | |||
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions"; | |||
import { QcItemWithChecks, QcData } from "@/app/api/qc"; | |||
import { | |||
Box, | |||
Button, | |||
@@ -19,10 +19,9 @@ import StockInForm from "./StockInForm"; | |||
import StockInFormVer2 from "./StockInFormVer2"; | |||
import QcFormVer2 from "./QcFormVer2"; | |||
import PutawayForm from "./PutawayForm"; | |||
import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; | |||
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; | |||
import { useGridApiRef } from "@mui/x-data-grid"; | |||
import {submitDialogWithWarning} from "../Swal/CustomAlerts"; | |||
import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions"; | |||
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; | |||
import dayjs from "dayjs"; | |||
@@ -36,7 +35,7 @@ const style = { | |||
px: 5, | |||
pb: 10, | |||
display: "block", | |||
width: { xs: "60%", sm: "60%", md: "60%" }, | |||
width: { xs: "90%", sm: "90%", md: "90%" }, | |||
// height: { xs: "60%", sm: "60%", md: "60%" }, | |||
}; | |||
interface CommonProps extends Omit<ModalProps, "children"> { | |||
@@ -55,6 +54,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||
qc?: QcItemWithChecks[]; | |||
warehouse?: any[]; | |||
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||
handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
} | |||
interface Props extends CommonProps { | |||
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | |||
@@ -70,8 +70,8 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
setItemDetail, | |||
qc, | |||
warehouse, | |||
handleMailTemplateForStockInLine, | |||
}) => { | |||
console.log(warehouse); | |||
const { | |||
t, | |||
i18n: { language }, | |||
@@ -113,6 +113,15 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
setOpenPutaway(isPutaway); | |||
}, [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 onOpenPutaway = useCallback(() => { | |||
@@ -155,21 +164,25 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
// Get QC data from the shared form context | |||
const qcAccept = data.qcAccept; | |||
const acceptQty = data.acceptQty as number; | |||
const qcResults = qcItems; | |||
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; | |||
// Validate QC data | |||
const validationErrors : string[] = []; | |||
// Check if all QC items have results | |||
const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined); | |||
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); | |||
if (itemsWithoutResult.length > 0) { | |||
validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`); | |||
validationErrors.push(`${t("QC items without result")}`); | |||
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); | |||
} | |||
// Check if failed items have failed quantity | |||
const failedItemsWithoutQty = qcItems.filter(item => | |||
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) { | |||
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 | |||
@@ -184,10 +197,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
// Check if dates are input | |||
if (data.productionDate === undefined || data.productionDate == null) { | |||
validationErrors.push("Production Date cannot be null!"); | |||
validationErrors.push("請輸入生產日期!"); | |||
} | |||
if (data.expiryDate === undefined || data.expiryDate == null) { | |||
validationErrors.push("Expiry Date cannot be null!"); | |||
validationErrors.push("請輸入到期日!"); | |||
} | |||
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept) { | |||
validationErrors.push("有不合格檢查項目,無法收貨!"); | |||
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | |||
// confirmButtonText: t("confirm putaway"), html: ""}); | |||
// return; | |||
} | |||
if (validationErrors.length > 0) { | |||
@@ -205,12 +224,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
qcAccept: qcAccept? qcAccept : false, | |||
acceptQty: acceptQty? acceptQty : 0, | |||
qcResult: qcItems.map(item => ({ | |||
qcResult: qcResults.map(item => ({ | |||
qcItemId: item.id, | |||
// qcItem: item.qcItem, | |||
// code: item.code, | |||
// qcDescription: item.qcDescription, | |||
isPassed: item.isPassed? item.isPassed : false, | |||
failQty: (item.failQty && !item.isPassed) ? item.failQty : 0, | |||
qcPassed: item.qcPassed? item.qcPassed : false, | |||
failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0, | |||
// failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0, | |||
remarks: item.remarks || '' | |||
})) | |||
@@ -218,42 +237,21 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
// const qcData = data; | |||
console.log("QC Data for submission:", qcData); | |||
await postStockInLine(qcData); | |||
if (!qcData.qcResult.every((qc) => qc.isPassed) && qcData.qcAccept) { | |||
submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", | |||
confirmButtonText: t("confirm putaway"), html: ""}); | |||
return; | |||
if (qcData.qcAccept) { | |||
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | |||
// confirmButtonText: t("confirm putaway"), html: ""}); | |||
onOpenPutaway(); | |||
} else { | |||
closeHandler({}, "backdropClick"); | |||
} | |||
await postStockInLineWithQc(qcData); | |||
// return; | |||
return ; | |||
}, | |||
[onOpenPutaway, qcItems], | |||
); | |||
const postStockInLineWithQc = useCallback(async (qcData: PurchaseQCInput) => { | |||
const args = { | |||
...qcData | |||
// id: itemDetail.id, | |||
// purchaseOrderId: itemDetail.purchaseOrderId, | |||
// purchaseOrderLineId: itemDetail.purchaseOrderLineId, | |||
// itemId: itemDetail.itemId, | |||
// ...data, | |||
// productionDate: productionDate, | |||
// expiryDate: expiryDate, | |||
// receiptDate: receiptDate, | |||
} as ModalFormInput; | |||
await postStockInLine(args); | |||
if (qcData.qcAccept) { | |||
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | |||
// confirmButtonText: t("confirm putaway"), html: ""}); | |||
onOpenPutaway(); | |||
} else { | |||
closeHandler({}, "backdropClick"); | |||
} | |||
return ; | |||
},[onOpenPutaway,closeHandler]); | |||
const postStockInLine = useCallback(async (args: ModalFormInput) => { | |||
const submitData = { | |||
@@ -329,8 +327,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
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) | |||
if (isPassed) { | |||
formProps.setValue("passingQty", acceptQty) | |||
@@ -343,7 +341,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
useEffect(() => { | |||
// maybe check if submitted before | |||
console.log(qcItems) | |||
checkQcIsPassed(qcItems) | |||
// checkQcIsPassed(qcItems) | |||
}, [qcItems, checkQcIsPassed]) | |||
return ( | |||
@@ -368,7 +366,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
<PutawayForm | |||
itemDetail={itemDetail} | |||
warehouse={warehouse!} | |||
disabled={false} | |||
disabled={viewOnly} | |||
/> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
<Button | |||
@@ -406,7 +404,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
</Typography> | |||
</Grid> | |||
<Grid item xs={12}> | |||
<StockInFormVer2 itemDetail={itemDetail} disabled={false} /> | |||
<StockInFormVer2 itemDetail={itemDetail} disabled={viewOnly} /> | |||
</Grid> | |||
</Grid> | |||
{/* <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
@@ -428,13 +426,13 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
<QcFormVer2 | |||
qc={qc!} | |||
itemDetail={itemDetail} | |||
disabled={false} | |||
disabled={viewOnly} | |||
qcItems={qcItems} | |||
setQcItems={setQcItems} | |||
/> | |||
</Grid> | |||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||
{itemDetail.status.toLowerCase() != "completed" && (<Button | |||
{!viewOnly && (<Button | |||
id="qcSubmit" | |||
type="button" | |||
variant="contained" | |||
@@ -123,7 +123,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
{...register("dnNo", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
// error={Boolean(errors.productLotNo)} | |||
// helperText={errors.productLotNo?.message} | |||
/> | |||
@@ -205,7 +205,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
{...register("productLotNo", { | |||
// required: "productLotNo required!", | |||
})} | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
error={Boolean(errors.productLotNo)} | |||
helperText={errors.productLotNo?.message} | |||
/> | |||
@@ -226,7 +226,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
sx={{ width: "100%" }} | |||
label={t("productionDate")} | |||
value={productionDate ? dayjs(productionDate) : undefined} | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
onChange={(date) => { | |||
if (!date) return; | |||
setValue( | |||
@@ -275,7 +275,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
sx={{ width: "100%" }} | |||
label={t("expiryDate")} | |||
value={expiryDate ? dayjs(expiryDate) : undefined} | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
onChange={(date) => { | |||
console.log(date); | |||
if (!date) return; | |||
@@ -311,10 +311,10 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
<TextField | |||
label={t("uom")} | |||
fullWidth | |||
{...register("uom", { | |||
{...register("uom.code", { | |||
required: "uom required!", | |||
})} | |||
value={uom.code} | |||
// value={uom?.code} | |||
disabled={true} | |||
/> | |||
</Grid> | |||
@@ -322,7 +322,7 @@ const StockInFormVer2: React.FC<Props> = ({ | |||
<TextField | |||
label={t("acceptedQty")} | |||
fullWidth | |||
disabled={itemDetail.status.toLowerCase() == "completed"} | |||
disabled={disabled} | |||
{...register("acceptedQty", { | |||
required: "acceptedQty required!", | |||
})} | |||
@@ -1,52 +1,58 @@ | |||
import { PutawayLine } from "@/app/api/po/actions" | |||
import { QcData } from "@/app/api/qc" | |||
export interface QcData { | |||
id: number, | |||
qcItem: string, | |||
qcDescription: string, | |||
isPassed: boolean | undefined | |||
failQty: number | undefined | |||
remarks: string | undefined | |||
} | |||
// export interface QcData { | |||
// qcItemId: number, | |||
// qcItem: string, | |||
// qcDescription: string, | |||
// isPassed: boolean | undefined | |||
// failQty: number | undefined | |||
// remarks: string | undefined | |||
// } | |||
export const dummyQCData: QcData[] = [ | |||
{ | |||
id: 1, | |||
qcItem: "包裝", | |||
id: 4, | |||
code: "包裝", | |||
qcDescription: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||
isPassed: undefined, | |||
name: "有破爛、污糟、脹袋、積水、與實物不符等任何一種情況,則不合格", | |||
qcPassed: undefined, | |||
failQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 2, | |||
qcItem: "肉質", | |||
id: 5, | |||
code: "肉質", | |||
qcDescription: "肉質鬆散,則不合格", | |||
isPassed: undefined, | |||
name: "肉質鬆散,則不合格", | |||
qcPassed: undefined, | |||
failQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 3, | |||
qcItem: "顔色", | |||
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,", | |||
isPassed: undefined, | |||
id: 6, | |||
code: "顔色", | |||
qcDescription: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||
name: "不是食材應有的顔色、顔色不均匀、出現其他顔色、腌料/醬顔色不均匀,油脂部分變綠色、黃色,則不合格", | |||
qcPassed: undefined, | |||
failQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 4, | |||
qcItem: "狀態", | |||
id: 7, | |||
code: "狀態", | |||
qcDescription: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||
isPassed: undefined, | |||
name: "有結晶、結霜、解凍跡象、發霉、散發異味等任何一種情況,則不合格", | |||
qcPassed: undefined, | |||
failQty: undefined, | |||
remarks: undefined, | |||
}, | |||
{ | |||
id: 5, | |||
qcItem: "異物", | |||
id: 8, | |||
code: "異物", | |||
qcDescription: "有不屬於本食材的雜質,則不合格", | |||
isPassed: undefined, | |||
name: "有不屬於本食材的雜質,則不合格", | |||
qcPassed: undefined, | |||
failQty: undefined, | |||
remarks: undefined, | |||
}, | |||
@@ -40,7 +40,7 @@ | |||
"non-consumables": "非消耗品", | |||
"fg": "成品", | |||
"sfg": "半成品", | |||
"item": "物品", | |||
"item": "貨品", | |||
"FG":"成品", | |||
"FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", | |||
"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": "基本單位", | |||
"Lot No": "批號", | |||
"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: { | |||
root: { | |||
borderBottomColor: palette.divider, | |||
padding: "1px 6px", | |||
padding: "10px 6px", | |||
fontSize: defaultFontSize - 2, | |||
// padding: "15px 16px", | |||
// lineHeight: 1.5, | |||
@@ -342,6 +342,13 @@ const components: ThemeOptions["components"] = { | |||
}, | |||
}, | |||
}, | |||
// MuiTableFooter: { | |||
// styleOverrides: { | |||
// root: { | |||
// padding: "1px 6px", | |||
// }, | |||
// }, | |||
// }, | |||
MuiTextField: { | |||
defaultProps: { | |||
variant: "filled", | |||