Browse Source

update

master
CANCERYS\kw093 1 week ago
parent
commit
71494a4089
8 changed files with 237 additions and 104 deletions
  1. +151
    -86
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  2. +4
    -3
      src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx
  3. +40
    -5
      src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx
  4. +2
    -2
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  5. +34
    -5
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  6. +2
    -2
      src/i18n/zh/common.json
  7. +2
    -1
      src/i18n/zh/jo.json
  8. +2
    -0
      src/i18n/zh/pickOrder.json

+ 151
- 86
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx View File

@@ -15,7 +15,7 @@ import {
import {
arrayToDayjs,
} from "@/app/utils/formatUtil";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box } from "@mui/material";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box, TextField } from "@mui/material";
import PickOrders from "./FinishedGood";
import ConsolidatedPickOrders from "./ConsolidatedPickOrders";
import PickExecution from "./GoodPickExecution";
@@ -38,10 +38,13 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import dayjs, { Dayjs } from 'dayjs';
import { PrinterCombo } from "@/app/api/settings/printer";
import { Autocomplete } from "@mui/material";
import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable";

interface Props {
pickOrders: PickOrderResult[];
printerCombo: PrinterCombo[];
}

type SearchQuery = Partial<
@@ -50,7 +53,7 @@ type SearchQuery = Partial<

type SearchParamNames = keyof SearchQuery;

const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
const PickOrderSearch: React.FC<Props> = ({ pickOrders, printerCombo }) => {
const { t } = useTranslation("pickOrder");
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;
@@ -67,6 +70,18 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
// const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null);
// const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null);
const [isLoadingSummary, setIsLoadingSummary] = useState(false);

const [selectedPrinterForAllDraft, setSelectedPrinterForAllDraft] = useState<PrinterCombo | null>(
printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
);
const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCombo | null>(
printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
);
const [selectedPrinterForRecord, setSelectedPrinterForRecord] = useState<PrinterCombo | null>(
printerCombo && printerCombo.length > 0 ? printerCombo[0] : null
);


const [hideCompletedUntilNext, setHideCompletedUntilNext] = useState<boolean>(
typeof window !== 'undefined' && localStorage.getItem('hideCompletedUntilNext') === 'true'
);
@@ -118,11 +133,20 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
})
return;
}

if (!selectedPrinterForDraft) {
Swal.fire({
position: "bottom-end",
icon: "warning",
text: t("Please select a printer first"),
showConfirmButton: false,
timer: 1500
});
return;
}
const currentFgOrder = fgPickOrdersData[0];

const printRequest = {
printerId: 1,
printerId: selectedPrinterForDraft.id,
printQty: 1,
isDraft: true,
numOfCarton: 0,
@@ -154,13 +178,22 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
} catch(error){
console.error("error: ", error)
}
},[t, fgPickOrdersData]);
},[t, fgPickOrdersData, selectedPrinterForDraft]);

const handleAllDraft = useCallback(async () =>{
try {
const releasedOrders = await fetchReleasedDoPickOrders();
console.log('fgPickOrdersData length:' + releasedOrders.length)

if (!selectedPrinterForAllDraft) {
Swal.fire({
position: "bottom-end",
icon: "warning",
text: t("Please select a printer first"),
showConfirmButton: false,
timer: 1500
});
return;
}
if(releasedOrders.length === 0) {
console.log("No released do_pick_order records found");
Swal.fire({
@@ -203,7 +236,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
console.log(`Processing order - DoPickOrder ID: ${doPickOrderId}, Ticket No: ${order.ticketNo}`);

const printRequest = {
printerId: 1,
printerId: selectedPrinterForAllDraft.id,
printQty: 1,
isDraft: true,
numOfCarton: 0,
@@ -229,7 +262,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
console.error("Error in handleAllDraft:",error);
}

},[t, fgPickOrdersData]);
},[t, fgPickOrdersData, selectedPrinterForAllDraft]);



@@ -540,86 +573,118 @@ const handleAssignByLane = useCallback(async (
{/* Header section */}
<Box sx={{
p: 1,
borderBottom: '1px solid #e0e0e0',
minHeight: 'auto' // 确保最小高度自适应
}}>
<Grid container alignItems="center" spacing={1}>
<Grid item xs={8}>
<Typography
variant="h5"
sx={{
lineHeight: 1.4, // 调整行高
m: 0,
fontWeight: 500
}}
>
{t("Finished Good Order")}
</Typography>
</Grid>

<Grid item xs={4}>
<Box sx={{
display: 'flex',
justifyContent: 'flex-end',
<Box
sx={{
p: 1,
borderBottom: '1px solid #e0e0e0',
minHeight: 'auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between', // 左标题,右控件
gap: 2,
flexWrap: 'wrap', // 如果屏幕窄就自动换行
}}
>
{/* 左侧标题 */}
<Typography
variant="h5"
sx={{
lineHeight: 1.4,
m: 0,
fontWeight: 500,
}}
>
{t("Finished Good Order")}
</Typography>

{/* 右侧:打印机 + 按钮 */}
<Stack
direction="row"
spacing={2}
sx={{
alignItems: 'center',
flexWrap: 'wrap', // 控件太多时换行,不会撑出横向滚动
rowGap: 1,
}}
>
<Typography variant="body2" sx={{ minWidth: 'fit-content', mr: 1.5 }}>
{t("A4 Printer")}:
</Typography>
<Autocomplete
options={printerCombo || []}
getOptionLabel={(option) =>
option.name || option.label || option.code || `Printer ${option.id}`
}
value={selectedPrinterForAllDraft}
onChange={(_, newValue) => setSelectedPrinterForAllDraft(newValue)}
sx={{ minWidth: 200 }}
size="small"
renderInput={(params) => (
<TextField {...params} placeholder={t("A4 Printer")} />
)}
/>

<Typography variant="body2" sx={{ minWidth: 'fit-content', ml: 1 }}>
{t("Label Printer")}:
</Typography>
<Autocomplete
options={printerCombo || []}
getOptionLabel={(option) =>
option.name || option.label || option.code || `Printer ${option.id}`
}
value={selectedPrinterForDraft}
onChange={(_, newValue) => setSelectedPrinterForDraft(newValue)}
sx={{ minWidth: 200 }}
size="small"
renderInput={(params) => (
<TextField {...params} placeholder={t("Label Printer")} />
)}
/>

<Button
variant="contained"
sx={{
py: 0.5,
px: 1.25,
height: '40px',
fontSize: '0.75rem',
lineHeight: 1.2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&.Mui-disabled': {
height: '40px',
},
}}
onClick={handleAllDraft}
>
{t("Print All Draft")} ({releasedOrderCount})
</Button>

<Button
variant="contained"
sx={{
py: 0.5,
px: 1.25,
height: '40px',
fontSize: '0.75rem',
lineHeight: 1.2,
display: 'flex',
alignItems: 'center',
height: '100%'
}}>
<Stack
direction="row"
spacing={0.5}
sx={{
alignItems: 'center',
height: '100%'
}}
>
<Button
variant="contained"
sx={{
py: 0.5, // 增加垂直padding
px: 1.25, // 增加水平padding
height: '40px', // 增加按钮高度
fontSize: '0.75rem',
lineHeight: 1.2, // 添加行高控制
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&.Mui-disabled': {
height: '40px'
}
}}
onClick={handleAllDraft}
>
{t("Print All Draft")} ({releasedOrderCount})
</Button>
<Button
variant="contained"
sx={{
py: 0.5,
px: 1.25,
height: '40px',
fontSize: '0.75rem',
lineHeight: 1.2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&.Mui-disabled': {
height: '40px'
}
}}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
onClick={handleDraft}
>
{t("Print Draft")}
</Button>
</Stack>
</Box>
</Grid>
</Grid>
justifyContent: 'center',
'&.Mui-disabled': {
height: '40px',
},
}}
title={!printButtonsEnabled ? t("All lots must be completed before printing") : ""}
onClick={handleDraft}
>
{t("Print Draft")}
</Button>
</Stack>
</Box>


{/* Tabs section - ✅ Move the click handler here */}
<Box sx={{
borderBottom: '1px solid #e0e0e0'
@@ -650,7 +715,7 @@ const handleAssignByLane = useCallback(async (
onRefreshReleasedOrderCount={fetchReleasedOrderCount}
/>
) }
{tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} />}
{tabIndex === 2 && <GoodPickExecutionRecord filterArgs={filterArgs} printerCombo={printerCombo} a4Printer={selectedPrinterForAllDraft} labelPrinter={selectedPrinterForDraft} />}
{tabIndex === 3 && <FGPickOrderTicketReleaseTable/>}
</Box>
</Box>


+ 4
- 3
src/components/FinishedGoodSearch/FinishedGoodSearchWrapper.tsx View File

@@ -1,13 +1,13 @@
import { fetchPickOrders } from "@/app/api/pickOrder";
import GeneralLoading from "../General/GeneralLoading";
import PickOrderSearch from "./FinishedGoodSearch";
import{fetchPrinterCombo} from "@/app/api/settings/printer";
interface SubComponents {
Loading: typeof GeneralLoading;
}

const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => {
const [pickOrders] = await Promise.all([
const [pickOrders, printerCombo] = await Promise.all([
fetchPickOrders({
code: undefined,
targetDateFrom: undefined,
@@ -16,9 +16,10 @@ const FinishedGoodSearchWrapper: React.FC & SubComponents = async () => {
status: undefined,
itemName: undefined,
}),
fetchPrinterCombo(),
]);

return <PickOrderSearch pickOrders={pickOrders} />;
return <PickOrderSearch pickOrders={pickOrders} printerCombo={printerCombo} />;
};

FinishedGoodSearchWrapper.Loading = GeneralLoading;


+ 40
- 5
src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx View File

@@ -25,6 +25,8 @@ import {
AccordionSummary,
AccordionDetails,
} from "@mui/material";
import { PrinterCombo } from "@/app/api/settings/printer";
import { Autocomplete } from "@mui/material";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useCallback, useEffect, useState, useRef, useMemo } from "react";
import { useTranslation } from "react-i18next";
@@ -69,6 +71,9 @@ import Swal from "sweetalert2";

interface Props {
filterArgs: Record<string, any>;
printerCombo: PrinterCombo[];
a4Printer: PrinterCombo | null; // A4 打印机(DN 用)
labelPrinter: PrinterCombo | null;
}


@@ -82,7 +87,7 @@ interface PickOrderData {
lots: any[];
}

const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs, printerCombo, a4Printer, labelPrinter }) => {
const { t } = useTranslation("pickOrder");
const router = useRouter();
const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -112,6 +117,16 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const errors = formProps.formState.errors;

const handleDN = useCallback(async (recordId: number) => {
if (!a4Printer) {
Swal.fire({
position: "bottom-end",
icon: "warning",
text: t("Please select a printer first"),
showConfirmButton: false,
timer: 1500
});
return;
}
const askNumofCarton = await Swal.fire({
title: t("Enter the number of cartons: "),
icon: "info",
@@ -143,7 +158,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const numOfCartons = askNumofCarton.value;
try{
const printRequest = {
printerId: 1,
printerId: a4Printer.id,
printQty: 1,
isDraft: false,
numOfCarton: numOfCartons,
@@ -172,6 +187,26 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
}, [t]);

const handleDNandLabel = useCallback(async (recordId: number) => {
if (!a4Printer || !labelPrinter) {
Swal.fire({
position: "bottom-end",
icon: "warning",
text: t("Please select a printer first"),
showConfirmButton: false,
timer: 1500
});
return;
}
if (!labelPrinter) {
Swal.fire({
position: "bottom-end",
icon: "warning",
text: t("Please select a label printer first"),
showConfirmButton: false,
timer: 1500
});
return;
}
const askNumofCarton = await Swal.fire({
title: t("Enter the number of cartons: "),
icon: "info",
@@ -203,7 +238,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const numOfCartons = askNumofCarton.value;
try{
const printDNRequest = {
printerId: 1,
printerId: a4Printer.id,
printQty: 1,
isDraft: false,
numOfCarton: numOfCartons,
@@ -211,7 +246,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
};

const printDNLabelsRequest = {
printerId: 1,
printerId: labelPrinter.id,
printQty: 1,
numOfCarton: numOfCartons,
doPickOrderId: recordId
@@ -280,7 +315,7 @@ const GoodPickExecutionRecord: React.FC<Props> = ({ filterArgs }) => {
const numOfCartons = askNumofCarton.value;
try{
const printRequest = {
printerId: 1,
printerId: labelPrinter?.id ?? 0,
printQty: 1,
numOfCarton: numOfCartons,
doPickOrderId: recordId


+ 2
- 2
src/components/ProductionProcess/ProductionProcessDetail.tsx View File

@@ -568,7 +568,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
<Typography variant="h6" gutterBottom fontWeight="bold">
{t("Production Process Steps")}
</Typography>
<ProcessSummaryHeader t={t} processData={processData} />
<ProcessSummaryHeader processData={processData} />
{!isExecutingLine ? (
/* ========== 步骤列表视图 ========== */
<TableContainer>
@@ -578,7 +578,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
<TableCell>{t("Seq")}</TableCell>
<TableCell>{t("Step Name")}</TableCell>
<TableCell>{t("Description")}</TableCell>
<TableCell>{t("Equipment Type/Code")}</TableCell>
<TableCell>{t("EquipmentType-EquipmentName-Code")}</TableCell>
<TableCell>{t("Operator")}</TableCell>
{/*}
<TableCell>{t("Processing Time (mins)")}</TableCell>


+ 34
- 5
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx View File

@@ -43,6 +43,7 @@ interface JobOrderLine {
uom: string;
shortUom: string;
availableStatus: string;
type: string;
}

interface ProductProcessJobOrderDetailProps {
@@ -105,6 +106,9 @@ const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProp
}, [fetchData]);
// PickTable 组件内容
const getStockAvailable = (line: JobOrderLine) => {
if (line.type?.toLowerCase() === "consumables") {
return null;
}
const inventory = inventoryData.find(inv =>
inv.itemCode === line.itemCode || inv.itemName === line.itemName
);
@@ -115,12 +119,22 @@ const getStockAvailable = (line: JobOrderLine) => {
};

const isStockSufficient = (line: JobOrderLine) => {
if (line.type?.toLowerCase() === "consumables") {
return false;
}
const stockAvailable = getStockAvailable(line);
if (stockAvailable === null) {
return false;
}
return stockAvailable >= line.reqQty;
};
const stockCounts = useMemo(() => {
const total = jobOrderLines.length;
const sufficient = jobOrderLines.filter(isStockSufficient).length;
// 过滤掉 consumables 类型的 lines
const nonConsumablesLines = jobOrderLines.filter(
line => line.type?.toLowerCase() !== "consumables"
);
const total = nonConsumablesLines.length;
const sufficient = nonConsumablesLines.filter(isStockSufficient).length;
return {
total,
sufficient,
@@ -131,7 +145,8 @@ const status = processData?.status?.toLowerCase?.() ?? "";
const handleDeleteJobOrder = useCallback(async ( jobOrderId: number) => {
const response = await deleteJobOrder(jobOrderId)
if (response) {
setProcessData(response.entity);
//setProcessData(response.entity);
await fetchData();
}
}, [jobOrderId]);
const handleRelease = useCallback(async ( jobOrderId: number) => {
@@ -139,7 +154,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
console.log("Release clicked for jobOrderId:", jobOrderId);
const response = await releaseJo({ id: jobOrderId })
if (response) {
setProcessData(response.entity);
//setProcessData(response.entity);
await fetchData();
}
}, [jobOrderId]);
const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
@@ -318,6 +334,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
if (params.row.type?.toLowerCase() === "consumables") {
return t("N/A");
}
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`;
},
},
@@ -328,9 +348,15 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "right",
headerAlign: "right",
type: "number",

renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
// 如果是 consumables,显示 N/A
if (params.row.type?.toLowerCase() === "consumables") {
return t("N/A");
}
const stockAvailable = getStockAvailable(params.row);
if (stockAvailable === null) {
return t("N/A");
}
return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`;
},
},
@@ -360,6 +386,9 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
headerAlign: "center",
type: "boolean",
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
if (params.row.type?.toLowerCase() === "consumables") {
return <Typography>{t("N/A")}</Typography>;
}
return isStockSufficient(params.row)
? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />
: <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />;


+ 2
- 2
src/i18n/zh/common.json View File

@@ -18,7 +18,7 @@
"R&D": "研發",
"STF": "樣品",
"Other": "其他",
"Add some entries!": "添加條目",
"Add Record": "新增",
"Clean Record": "重置",
@@ -34,7 +34,7 @@
"Items": "物料",
"Release": "放單",
"Demand Forecast Setting": "需求預測設定",
"Equipment Type/Code": "使用設備-編號",
"EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號",
"Equipment": "設備",
"Time Information(mins)": "時間信息(分鐘)",
"Processing Time": "生產時間",


+ 2
- 1
src/i18n/zh/jo.json View File

@@ -390,7 +390,8 @@
"Step Information": "步驟信息",
"Stop": "停止",
"Demand Forecast Setting": "需求預測設定",
"Equipment Type/Code": "使用設備-編號",
"EquipmentType-EquipmentName-Code": "設備類型-設備名稱-編號",

"Equipment": "設備",
"Time Information(mins)": "時間信息(分鐘)",
"Processing Time": "生產時間",


+ 2
- 0
src/i18n/zh/pickOrder.json View File

@@ -205,6 +205,8 @@
"Accept Stock Out": "接受出庫",
"Pick Another Lot": "欠數,並重新選擇批號",
"Delivery Note Code": "送貨單編號",
"A4 Printer": "A4 打印機",
"Label Printer": "標籤打印機",
"Lot No": "批號",
"Expiry Date": "到期日",
"Location": "位置",


Loading…
Cancel
Save