kelvin.yau 2 mesi fa
parent
commit
ade0329ab0
19 ha cambiato i file con 534 aggiunte e 93 eliminazioni
  1. +34
    -0
      src/app/(main)/settings/importExcel/page.tsx
  2. +3
    -3
      src/app/api/jo/actions.ts
  3. +12
    -3
      src/app/api/jo/index.ts
  4. +16
    -0
      src/app/api/stockTake/actions.ts
  5. +19
    -0
      src/app/utils/fetchUtil.ts
  6. +88
    -0
      src/components/ExcelFileImport/ExcelFileImport.tsx
  7. +10
    -0
      src/components/ExcelFileImport/ExcelFileImportWrapper.tsx
  8. +1
    -0
      src/components/ExcelFileImport/index.ts
  9. +42
    -30
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  10. +22
    -5
      src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx
  11. +1
    -3
      src/components/FinishedGoodSearch/LotConfirmationModal.tsx
  12. +86
    -0
      src/components/JoSave/ActionButtons.tsx
  13. +91
    -17
      src/components/JoSave/JoSave.tsx
  14. +1
    -1
      src/components/JoSave/JoSaveWrapper.tsx
  15. +64
    -11
      src/components/JoSave/PickTable.tsx
  16. +5
    -0
      src/components/NavigationContent/NavigationContent.tsx
  17. +4
    -0
      src/i18n/zh/do.json
  18. +20
    -18
      src/i18n/zh/jo.json
  19. +15
    -2
      src/i18n/zh/pickOrder.json

+ 34
- 0
src/app/(main)/settings/importExcel/page.tsx Vedi File

@@ -0,0 +1,34 @@
import { Metadata } from "next";
import { getServerI18n } from "@/i18n";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { Suspense } from "react";
import ExcelFileImport from "@/components/ExcelFileImport";

export const metadata: Metadata = {
title: "Excel File Import",
};

const ImportExcel: React.FC = async () => {
const { t } = await getServerI18n("common");

return (
<>
<Stack
direction="row"
justifyContent="space-between"
flexWrap="wrap"
rowGap={2}
>
<Typography variant="h4" marginInlineEnd={2}>
{t("Excel File Import")}
</Typography>
</Stack>
<Suspense>
<ExcelFileImport />
</Suspense>
</>
)
};

export default ImportExcel;

+ 3
- 3
src/app/api/jo/actions.ts Vedi File

@@ -2,7 +2,7 @@

import { cache } from 'react';
import { Pageable, serverFetchJson } from "@/app/utils/fetchUtil";
import { Machine, Operator } from ".";
import { JoStatus, Machine, Operator } from ".";
import { BASE_API_URL } from "@/config/api";
import { revalidateTag } from "next/cache";
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
@@ -35,7 +35,7 @@ export interface SearchJoResult {
name: string;
reqQty: number;
uom: string;
status: string;
status: JoStatus;
}

export interface ReleaseJoRequest {
@@ -44,7 +44,7 @@ export interface ReleaseJoRequest {

export interface ReleaseJoResponse {
id: number;
entity: { status: string }
entity: { status: JoStatus }
}

export interface IsOperatorExistResponse<T> {


+ 12
- 3
src/app/api/jo/index.ts Vedi File

@@ -4,6 +4,9 @@ import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export type JoStatus = "planning" | "pending" | "processing" | "packaging" | "storing" | "completed"
export type JoBomMaterialStatus = "pending" | "completed"

export interface Operator {
id: number;
name: string;
@@ -24,7 +27,7 @@ export interface JoDetail {
reqQty: number;
uom: string;
pickLines: JoDetailPickLine[];
status: string;
status: JoStatus;
planStart: number[];
planEnd: number[];
type: string;
@@ -34,10 +37,16 @@ export interface JoDetailPickLine {
id: number;
code: string;
name: string;
lotNo: string;
pickedLotNo?: JoDetailPickedLotNo[];
reqQty: number;
uom: string;
status: string;
status: JoBomMaterialStatus;
}

export interface JoDetailPickedLotNo {
lotNo: string;
qty: number;
isScanned: boolean;
}

export const fetchJoDetail = cache(async (id: number) => {


+ 16
- 0
src/app/api/stockTake/actions.ts Vedi File

@@ -0,0 +1,16 @@
"use server";

import { serverFetchString } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";

export const importStockTake = async (data: FormData) => {
const importStockTake = await serverFetchString<string>(
`${BASE_API_URL}/stockTake/import`,
{
method: "POST",
body: data,
},
);
console.log(importStockTake)
return importStockTake;
}

+ 19
- 0
src/app/utils/fetchUtil.ts Vedi File

@@ -91,6 +91,25 @@ export async function serverFetchJson<T>(...args: FetchParams) {
}
}

export async function serverFetchString<T>(...args: FetchParams) {
const response = await serverFetch(...args);

if (response.ok) {
return response.text() as T;
} else {
switch (response.status) {
case 401:
signOutUser();
default:
console.error(await response.text());
throw new ServerFetchError(
"Something went wrong fetching data in server.",
response,
);
}
}
}

export async function serverFetchBlob<T extends BlobResponse>(...args: FetchParams) {
const response = await serverFetch(...args);



+ 88
- 0
src/components/ExcelFileImport/ExcelFileImport.tsx Vedi File

@@ -0,0 +1,88 @@
"use client";

import { FileUpload } from "@mui/icons-material";
import { Button, Grid, Stack } from "@mui/material";
import React, { ChangeEvent, useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { errorDialogWithContent, submitDialog, successDialog, successDialogWithContent } from "../Swal/CustomAlerts";
import { importStockTake } from "@/app/api/stockTake/actions";

interface Props {
}

const ExcelFileImport: React.FC<Props> = async ({ }) => {

const { t } = useTranslation("common");

const buttonName: string[] = useMemo(() => {
return ["Import Stock Take"]
}, [])

const handleExcelFileImportClick = useCallback(async (event: ChangeEvent<HTMLInputElement>) => {

try {
if (event.target.files !== null) {
const file = event.target.files[0]
const formData = new FormData();
formData.append('multipartFileList', file);

if (file.name.endsWith(".xlsx") || file.name.endsWith(".csv")) {
let response: String = ""

console.log(event.target.id)
switch (event.target.id) {
case "Import Stock Take":
response = await importStockTake(formData)
break;
}

if (response.includes("Import Excel success")) {
successDialogWithContent(t("Import Success"), t(`${response}`), t)
} else {
errorDialogWithContent(t("Import Fail"), t(`${response}`), t)
}
}
}

} catch (err) {
console.log(err)
return false
}

return
}, [])

return (
<>
<Grid container rowGap={1.5}>
{
buttonName.map((name) => {
return (
<Grid container>
<Button
variant="contained"
color="info"
startIcon={<FileUpload />}
component="label"
>
<input
id={name}
type='file'
accept='.xlsx'
hidden
onChange={(event) => {
handleExcelFileImportClick(event)
}}
/>
{t(name)}
</Button>
</Grid>
)
})
}
</Grid>
</>
);
};

export default ExcelFileImport;

+ 10
- 0
src/components/ExcelFileImport/ExcelFileImportWrapper.tsx Vedi File

@@ -0,0 +1,10 @@
import React from "react";
import ExcelFileImport from "./ExcelFileImport";

const ExcelFileImportWrapper: React.FC = async () => {

return <ExcelFileImport/>;
};


export default ExcelFileImportWrapper;

+ 1
- 0
src/components/ExcelFileImport/index.ts Vedi File

@@ -0,0 +1 @@
export { default } from './ExcelFileImportWrapper'

+ 42
- 30
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx Vedi File

@@ -279,38 +279,50 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
overflow: 'auto' // Single scrollbar for the whole page
}}>
{/* Header section */}
<Box sx={{
p: 2,
borderBottom: '1px solid #e0e0e0'
}}>
<Stack rowGap={2}>
<Grid container alignItems="center">
<Grid item xs={8}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Finished Good Order")}
</Typography>
</Grid>
<Grid item xs={4} display="flex" justifyContent="end" alignItems="center">
<Stack direction="row" spacing={1}>
<Button
variant="contained"
onClick={() => handleAssignByStore("2/F")}
disabled={isAssigning}
>
{isAssigning ? t("Assigning pick order...") : t("Pick Execution 2/F")}
</Button>
<Button
variant="contained"
onClick={() => handleAssignByStore("4/F")}
disabled={isAssigning}
>
{isAssigning ? t("Assigning pick order...") : t("Pick Execution 4/F")}
</Button>
</Stack>
<Box sx={{ p: 2, borderBottom: '1px solid #e0e0e0' }}>
<Stack rowGap={2}>
<Grid container alignItems="center">
<Grid item xs={8}>
<Box mb={2}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Finished Good Order")}
</Typography>
</Box>
</Grid>
</Grid>


{/* First 4 buttons aligned left */}
<Grid item xs={6}>
<Stack direction="row" spacing={1}>
<Button variant="contained">{t("Print Draft")}</Button>
<Button variant="contained">{t("Print Pick Order and DN Label")}</Button>
<Button variant="contained">{t("Print Pick Order")}</Button>
<Button variant="contained">{t("Print DN Label")}</Button>
</Stack>
</Box>
</Grid>

{/* Last 2 buttons aligned right */}
<Grid item xs={6} display="flex" justifyContent="flex-end">
<Stack direction="row" spacing={1}>
<Button
variant="contained"
onClick={() => handleAssignByStore("2/F")}
disabled={isAssigning}
>
{isAssigning ? t("Assigning pick order...") : t("Pick Execution 2/F")}
</Button>
<Button
variant="contained"
onClick={() => handleAssignByStore("4/F")}
disabled={isAssigning}
>
{isAssigning ? t("Assigning pick order...") : t("Pick Execution 4/F")}
</Button>
</Stack>
</Grid>
</Grid>
</Stack>
</Box>

{/* Tabs section - ✅ Move the click handler here */}
<Box sx={{


+ 22
- 5
src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx Vedi File

@@ -514,8 +514,18 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
qty: 0
});
console.log(`Update stock out line result for line ${matchingLot.pickOrderLineId}:`, stockOutLineUpdate);
if (stockOutLineUpdate && stockOutLineUpdate.code === "SUCCESS") {

// Treat multiple backend shapes as success (type-safe via any)
const r: any = stockOutLineUpdate as any;
const updateOk =
r?.code === 'SUCCESS' ||
typeof r?.id === 'number' ||
r?.type === 'checked' ||
r?.status === 'checked' ||
typeof r?.entity?.id === 'number' ||
r?.entity?.status === 'checked';

if (updateOk) {
successCount++;
} else {
errorCount++;
@@ -570,8 +580,11 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
if (successCount > 0) {
console.log(`✅ QR Code processing completed: ${successCount} updated/created`);
setQrScanSuccess(true);
setQrScanError(false);
setQrScanInput(''); // Clear input after successful processing
setIsManualScanning(false);
stopScan();
resetScan();
// ✅ Clear success state after a delay
//setTimeout(() => {
@@ -778,7 +791,9 @@ const [isConfirmingLot, setIsConfirmingLot] = useState(false);
qty: selectedLotForQr.stockOutLineQty || 0
});
console.log("Stock out line updated successfully!");
setQrScanSuccess(true);
setQrScanError(false);

// Close modal
setQrModalOpen(false);
setSelectedLotForQr(null);
@@ -1276,7 +1291,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
</Box>
</Box>

{qrScanError && (
{qrScanError && !qrScanSuccess && (
<Alert severity="error" sx={{ mb: 2 }}>
{t("QR code does not match any item in current orders.")}
</Alert>
@@ -1444,6 +1459,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
</Table>
</TableContainer>
{/* ✅ Status Messages Display - Move here, outside the table */}
{/*
{paginatedData.length > 0 && (
<Box sx={{ mt: 2, p: 2, backgroundColor: 'grey.50', borderRadius: 1 }}>
{paginatedData.map((lot, index) => (
@@ -1455,6 +1471,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe
))}
</Box>
)}
*/}
<TablePagination
component="div"
count={combinedLotData.length}


+ 1
- 3
src/components/FinishedGoodSearch/LotConfirmationModal.tsx Vedi File

@@ -94,9 +94,7 @@ const LotConfirmationModal: React.FC<LotConfirmationModalProps> = ({
<Alert severity="info">
{t("If you proceed, the system will:")}
<ul style={{ margin: '8px 0 0 16px' }}>
<li>{t("Update the stock out line to use the scanned lot")}</li>
<li>{t("Put the original suggested lot on hold")}</li>
<li>{t("Update inventory lot line for the new lot")}</li>
<li>{t("Update your suggested lot to the this scanned lot")}</li>
</ul>
</Alert>
</Stack>


+ 86
- 0
src/components/JoSave/ActionButtons.tsx Vedi File

@@ -0,0 +1,86 @@
import { JoDetail } from "@/app/api/jo";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StartIcon from "@mui/icons-material/Start";
import PlayCircleFilledWhiteIcon from '@mui/icons-material/PlayCircleFilledWhite';

type Props = {
handleRelease: () => void;
handleStart: () => void;
};

interface ErrorEntry {
qtyErr: boolean;
scanErr: boolean;
}

const ActionButtons: React.FC<Props> = ({
handleRelease,
handleStart
}) => {
const { t } = useTranslation("jo");

const { watch } = useFormContext<JoDetail>();

const status = useMemo(() => {
return watch("status").toLowerCase()
}, [watch("status")])

const pickLines = useMemo(() => {
return watch("pickLines")
}, [watch("pickLines")])

// Check Error Handling (e.g. start jo)
const errors: ErrorEntry = useMemo(() => {
let qtyErr = false;
let scanErr = false;

pickLines.forEach((line) => {
if (!qtyErr) {
const pickedQty = line.pickedLotNo?.reduce((acc, cur) => acc + cur.qty, 0) ?? 0
qtyErr = pickedQty > 0 && pickedQty >= line.reqQty
}

if (!scanErr) {
scanErr = line.pickedLotNo?.some((lotNo) => Boolean(lotNo.isScanned) === false) ?? false // default false
}
})

return {
qtyErr: qtyErr,
scanErr: scanErr
}
}, [pickLines])

return (
<Stack direction="row" justifyContent="flex-start" gap={1}>
{status === "planning" && (
<Button
variant="outlined"
startIcon={<StartIcon />}
onClick={handleRelease}
>
{t("Release")}
</Button>
)}
{status === "pending" && (
<Box sx={{ flexDirection: 'column'}}>
<Button
variant="outlined"
startIcon={<PlayCircleFilledWhiteIcon />}
onClick={handleStart}
disabled={errors.qtyErr || errors.scanErr}
>
{t("Start Job Order")}
</Button>
{errors.scanErr && (<Typography variant="h3" color="error">{t("Please scan the item qr code.")}</Typography>)}
{errors.qtyErr && (<Typography variant="h3" color="error">{t("Please make sure the qty is enough.")}</Typography>)}
</Box>
)}
</Stack>
)
}

export default ActionButtons;

+ 91
- 17
src/components/JoSave/JoSave.tsx Vedi File

@@ -4,13 +4,16 @@ import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import useUploadContext from "../UploadProvider/useUploadContext";
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
import { useCallback, useState } from "react";
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
import { Button, Stack, Typography } from "@mui/material";
import StartIcon from "@mui/icons-material/Start";
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { releaseJo } from "@/app/api/jo/actions";
import InfoCard from "./InfoCard";
import PickTable from "./PickTable";
import ActionButtons from "./ActionButtons";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import { fetchStockInLineInfo } from "@/app/api/po/actions";

type Props = {
id?: number;
@@ -24,12 +27,92 @@ const JoSave: React.FC<Props> = ({
const { t } = useTranslation("jo")
const router = useRouter();
const { setIsUploading } = useUploadContext();
const scanner = useQrCodeScannerContext()
const [serverError, setServerError] = useState("");

const finalDefaultValues = useMemo(() => {
const values = {
...defaultValues,
pickLines: defaultValues?.pickLines?.map(line => ({
...line,
pickedLotNo: Array.isArray(line.pickedLotNo)
? line.pickedLotNo.map(lot => ({
...lot,
isScanned: false
}))
: line.pickedLotNo
}))
}

return values;
}, [defaultValues])

const formProps = useForm<JoDetail>({
defaultValues: defaultValues
defaultValues: finalDefaultValues
})
const pickLines = useMemo(() => {
return formProps.watch("pickLines")
}, [formProps.watch("pickLines")])

// --------------------------------------------- Qr Code Scan --------------------------------------------- //
const needScan = useMemo(() => {
return pickLines.some((line) => line.status === "pending")
}, [pickLines.some((line) => line.status === "pending")])

useLayoutEffect(() => {
if (needScan && !scanner.isScanning) {
scanner.startScan();
} else if (!needScan) {
scanner.stopScan();
}
}, [needScan])

const checkScannedId = useCallback(async (stockInLineId: number | undefined) => {
try {
setIsUploading(true)
if (stockInLineId) {
const response = await fetchStockInLineInfo(stockInLineId);
// const pickLines = formProps.watch("pickLines")
const pickedLotNoIndex = pickLines.findIndex((line) => line.pickedLotNo?.some((pln) => pln.lotNo === response?.lotNo))
if (pickedLotNoIndex >= 0) {
const updatedPickLines = [...pickLines]
const matchedLotNoIndex = updatedPickLines[pickedLotNoIndex].pickedLotNo?.findIndex((line) => line?.lotNo === response?.lotNo && Boolean(line?.isScanned) === false)
if (matchedLotNoIndex !== null && matchedLotNoIndex !== undefined && matchedLotNoIndex >= 0) {
const updatedPickedLotNo = [...(updatedPickLines[pickedLotNoIndex].pickedLotNo || [])]
updatedPickedLotNo[matchedLotNoIndex] = {
...updatedPickedLotNo[matchedLotNoIndex],
isScanned: true,
}
updatedPickLines[pickedLotNoIndex] = {
...updatedPickLines[pickedLotNoIndex],
pickedLotNo: updatedPickedLotNo,
};
formProps.setValue("pickLines", updatedPickLines, {
shouldValidate: true,
shouldDirty: true,
});
}
}
}
} finally {
scanner.resetScan()
setIsUploading(false)
}
}, [])

useEffect(() => {
const result = scanner.result
console.log(scanner.result)
if (result?.value) {
if (!isNaN(Number(result.value))) { checkScannedId(Number(result?.value)); }
} else if (result?.stockInLineId) {
checkScannedId(result?.stockInLineId)
}
}, [scanner.result])

// --------------------------------------------- Button Actions --------------------------------------------- //
const handleBack = useCallback(() => {
router.replace(`/jo`)
}, [])
@@ -38,12 +121,9 @@ const JoSave: React.FC<Props> = ({
try {
setIsUploading(true)
if (id) {
console.log(id)
const response = await releaseJo({ id: id })
console.log(response.entity.status)
if (response) {
formProps.setValue("status", response.entity.status)
console.log(formProps.watch("status"))
}
}

@@ -56,6 +136,11 @@ const JoSave: React.FC<Props> = ({
}
}, [])

const handleStart = useCallback(async () => {
console.log("first")
}, [])

// --------------------------------------------- Form Submit --------------------------------------------- //
const onSubmit = useCallback<SubmitHandler<JoDetail>>(async (data, event) => {
console.log(data)
}, [t])
@@ -76,18 +161,7 @@ const JoSave: React.FC<Props> = ({
{serverError}
</Typography>
)}
{
formProps.watch("status").toLowerCase() === "planning" && (
<Stack direction="row" justifyContent="flex-start" gap={1}>
<Button
variant="outlined"
startIcon={<StartIcon />}
onClick={handleRelease}
>
{t("Release")}
</Button>
</Stack>
)}
<ActionButtons handleRelease={handleRelease} handleStart={handleStart}/>
<InfoCard />
<PickTable />
<Stack direction="row" justifyContent="flex-end" gap={1}>


+ 1
- 1
src/components/JoSave/JoSaveWrapper.tsx Vedi File

@@ -17,7 +17,7 @@ const JoSaveWrapper: React.FC<Props> & SubComponents = async ({
id,
}) => {
const jo = id ? await fetchJoDetail(id) : undefined
return <JoSave id={id} defaultValues={jo}/>
}



+ 64
- 11
src/components/JoSave/PickTable.tsx Vedi File

@@ -1,11 +1,15 @@
import { JoDetail } from "@/app/api/jo";
import { JoDetail, JoDetailPickLine } from "@/app/api/jo";
import { decimalFormatter } from "@/app/utils/formatUtil";
import { GridColDef } from "@mui/x-data-grid";
import { GridColDef, GridRenderCellParams, GridValidRowModel } from "@mui/x-data-grid";
import { isEmpty, upperFirst } from "lodash";
import { useMemo } from "react";
import { useCallback, useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid/StyledDataGrid";
import { Box, Grid, Icon, IconButton, Stack, Typography } from "@mui/material";
import PendingOutlinedIcon from '@mui/icons-material/PendingOutlined';
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import HelpOutlineOutlinedIcon from '@mui/icons-material/HelpOutlineOutlined';

type Props = {

@@ -19,33 +23,74 @@ const PickTable: React.FC<Props> = ({
watch
} = useFormContext<JoDetail>()

const notPickedStatusColumn = useMemo(() => {
return (<HelpOutlineOutlinedIcon fontSize={"large"} color={"error"} />)
}, [])
const scanStatusColumn = useCallback((status: boolean) => {
return status ?
<CheckCircleOutlineOutlinedIcon fontSize={"large"} sx={{ ml: "5px" }} color="success" />
: <PendingOutlinedIcon fontSize={"large"} sx={{ ml: "5px" }} color="warning" />
}, [])

const columns = useMemo<GridColDef[]>(() => [
{
field: "code",
headerName: t("Code"),
flex: 1,
flex: 0.6,
},
{
field: "name",
headerName: t("Name"),
flex: 1,
},
{
field: "scanStatus",
headerName: t("Scan Status"),
flex: 0.4,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
if (params.row.pickedLotNo === null || params.row.pickedLotNo === undefined) {
return notPickedStatusColumn
}
const scanStatus = params.row.pickedLotNo.map((pln) => Boolean(pln.isScanned))
return isEmpty(scanStatus) ? notPickedStatusColumn : <Stack direction={"column"}>{scanStatus.map((status) => scanStatusColumn(status))}</Stack>
},
},
{
field: "lotNo",
headerName: t("Lot No."),
flex: 1,
renderCell: (row) => {
return isEmpty(row.value) ? "N/A" : row.value
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
if (params.row.pickedLotNo === null || params.row.pickedLotNo === undefined) {
return t("Pending for pick")
}
const lotNos = params.row.pickedLotNo.map((pln) => pln.lotNo)
return isEmpty(lotNos) ? t("Pending for pick") : lotNos.map((lotNo) => (<>{lotNo}<br /></>))
},
},
{
field: "pickedQty",
headerName: t("Picked Qty"),
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
if (params.row.pickedLotNo === null || params.row.pickedLotNo === undefined) {
return t("Pending for pick")
}
const qtys = params.row.pickedLotNo.map((pln) => pln.qty)
return isEmpty(qtys) ? t("Pending for pick") : qtys.map((qty) => <>{qty}<br /></>)
},
},
{
field: "reqQty",
headerName: t("Req. Qty"),
flex: 1,
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (row) => {
return decimalFormatter.format(row.value)
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
return decimalFormatter.format(params.value)
},
},
{
@@ -59,8 +104,15 @@ const PickTable: React.FC<Props> = ({
field: "status",
headerName: t("Status"),
flex: 1,
renderCell: (row) => {
return t(upperFirst(row.value))
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JoDetailPickLine>) => {
return (
<>
{params.row.pickedLotNo?.every((lotNo) => Boolean(lotNo.isScanned)) ? t("Scanned") : t(upperFirst(params.value))}
{scanStatusColumn(Boolean(params.row.pickedLotNo?.every((lotNo) => Boolean(lotNo.isScanned))))}
</>
)
},
},
], [])
@@ -82,6 +134,7 @@ const PickTable: React.FC<Props> = ({
disableColumnMenu
rows={watch("pickLines")}
columns={columns}
getRowHeight={() => 'auto'}
/>
</>
)


+ 5
- 0
src/components/NavigationContent/NavigationContent.tsx Vedi File

@@ -295,6 +295,11 @@ const NavigationContent: React.FC = () => {
label: "Import Testing",
path: "/settings/m18ImportTesting",
},
{
icon: <RequestQuote />,
label: "Import Excel",
path: "/settings/importExcel",
},
],
},
];


+ 4
- 0
src/i18n/zh/do.json Vedi File

@@ -41,7 +41,11 @@
"Delete": "刪除",
"Release": "放單",
"Back": "返回",
<<<<<<< HEAD
"Batch Release": "批量放單",
"Batch release completed successfully.": "已完成批量放單"

=======
"Edit Delivery Order Detail": "編輯交貨單詳情"
>>>>>>> 99400f68e2291628aae3d93b1a901a41ff80c570
}

+ 20
- 18
src/i18n/zh/jo.json Vedi File

@@ -1,19 +1,21 @@
{
"Job Order": "工單",
"Create Job Order": "創建工單",
"Edit Job Order Detail": "編輯工單",

"Details": "細節",
"Code": "編號",
"Name": "名稱",
"Req. Qty": "需求數量",
"UoM": "單位",
"Status": "來貨狀態",
"Lot No.": "批號",
"Bom": "物料清單",

"Release": "發佈",

"Pending": "待提料",
"Planning": "計劃中"
}
"Job Order": "工單",
"Create Job Order": "創建工單",
"Edit Job Order Detail": "編輯工單",
"Details": "細節",
"Code": "編號",
"Name": "名稱",
"Picked Qty": "已提料數量",
"Req. Qty": "需求數量",
"UoM": "單位",
"Status": "來貨狀態",
"Lot No.": "批號",
"Bom": "物料清單",
"Release": "發佈",
"Pending": "待掃碼",
"Pending for pick": "待提料",
"Planning": "計劃中",
"Scanned": "已掃碼",
"Scan Status": "掃碼狀態",
"Start Job Order": "開始工單"
}

+ 15
- 2
src/i18n/zh/pickOrder.json Vedi File

@@ -262,7 +262,20 @@
"Stop QR Scan":"停止QR掃描",
"Scanning...":"掃描中...",
"Print DN/Label":"列印送貨單/標籤",
"Store ID":"店鋪編號",
"QR code does not match any item in current orders.":"QR 碼不符合當前訂單中的任何貨品。"
"Store ID":"儲存編號",
"QR code does not match any item in current orders.":"QR 碼不符合當前訂單中的任何貨品。",
"Lot Number Mismatch":"批次號碼不符",
"The scanned item matches the expected item, but the lot number is different. Do you want to proceed with this different lot?":"掃描的貨品與預期的貨品相同,但批次號碼不同。您是否要繼續使用不同的批次?",
"Expected Lot:":"預期批次:",
"Scanned Lot:":"掃描批次:",
"Confirm":"確認",
"Update your suggested lot to the this scanned lot":"更新您的建議批次為此掃描的批次",
"Print Draft":"列印草稿",
"Print Pick Order and DN Label":"列印提料單和送貨單標貼",
"Print Pick Order":"列印提料單",
"Print DN Label":"列印送貨單標貼"



}

Caricamento…
Annulla
Salva