瀏覽代碼

Merge remote-tracking branch 'origin/MergeProblem1' into MergeProblem1

stable1
CANCERYS\kw093 2 週之前
父節點
當前提交
86ff846ad7
共有 6 個文件被更改,包括 301 次插入12 次删除
  1. +3
    -3
      src/app/(main)/testing/page.tsx
  2. +4
    -0
      src/app/api/jo/actions.ts
  3. +1
    -1
      src/app/api/laserPrint/actions.ts
  4. +255
    -0
      src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx
  5. +7
    -2
      src/components/FinishedGoodSearch/FinishedGoodSearch.tsx
  6. +31
    -6
      src/components/Jodetail/completeJobOrderRecord.tsx

+ 3
- 3
src/app/(main)/testing/page.tsx 查看文件

@@ -222,7 +222,7 @@ export default function TestingPage() {
const lim = parseInt(laserAutoLimit.trim(), 10);
const report = await runLaserBag2AutoSend({
planStart: laserAutoPlanDate,
limitPerRun: Number.isFinite(lim) ? lim : 0,
limitPerRun: Number.isFinite(lim) ? lim : 1,
});
setLaserAutoReport(report);
try {
@@ -531,11 +531,11 @@ export default function TestingPage() {
/>
<TextField
size="small"
label="limitPerRun(0=全部符合)"
label="limitPerRun(目前固定只送第一筆)"
value={laserAutoLimit}
onChange={(e) => setLaserAutoLimit(e.target.value)}
sx={{ width: 200 }}
helperText="手動測試建議 1;排程預設每分鐘最多 1 筆"
helperText="目前後端會限制為第一筆;此欄位保留給未來調整"
/>
<Button
variant="contained"


+ 4
- 0
src/app/api/jo/actions.ts 查看文件

@@ -133,6 +133,7 @@ export interface PrintPickRecordRequest{
pickOrderId: number;
printerId: number;
printQty: number;
floor?: "2F" | "3F" | "4F" | "ALL";
}

export interface PrintPickRecordResponse{
@@ -1318,6 +1319,9 @@ export async function PrintPickRecord(request: PrintPickRecordRequest){
if (request.printQty !== null && request.printQty !== undefined) {
params.append('printQty', request.printQty.toString());
}
if (request.floor) {
params.append('floor', request.floor);
}

//const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{
const response = await serverFetchWithNoContent(`${BASE_API_URL}/jo/print-PickRecord?${params.toString()}`,{


+ 1
- 1
src/app/api/laserPrint/actions.ts 查看文件

@@ -167,7 +167,7 @@ export interface LaserBag2AutoSendReport {

/**
* Same workflow as /laserPrint row click: list job orders (LASER_PRINT.itemCodes) for planStart,
* then TCP send(s) using DB LASER_PRINT.host / port. limitPerRun 0 = all matches.
* then TCP send using DB LASER_PRINT.host / port (server currently sends first matching job only).
*/
export async function runLaserBag2AutoSend(params?: {
planStart?: string;


+ 255
- 0
src/components/FinishedGoodSearch/FinishedGoodCartonDashboardTab.tsx 查看文件

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

import { useCallback, useEffect, useMemo, useState } from "react";
import {
Alert,
Box,
CircularProgress,
Grid,
MenuItem,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Typography,
} from "@mui/material";
import type { ApexOptions } from "apexcharts";
import dayjs from "dayjs";
import {
CompletedDoPickOrderResponse,
fetchCompletedDoPickOrdersAll,
} from "@/app/api/pickOrder/actions";
import SafeApexCharts from "@/components/charts/SafeApexCharts";

type FloorFilter = "all" | "2/F" | "4/F";

type DailySummaryRow = {
date: string;
floor2F: number;
floor4F: number;
truckX: number;
total: number;
};

const FinishedGoodCartonDashboardTab: React.FC = () => {
const [floor, setFloor] = useState<FloorFilter>("all");
const [date, setDate] = useState<string>(dayjs().format("YYYY-MM-DD"));
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>("");
const [records, setRecords] = useState<CompletedDoPickOrderResponse[]>([]);

const loadData = useCallback(async () => {
setLoading(true);
setError("");
try {
const data = await fetchCompletedDoPickOrdersAll(
date ? { targetDate: date } : undefined,
);
setRecords(data);
} catch (err) {
console.error("Failed to load finished good carton dashboard data", err);
setError("載入成品出倉出箱數量失敗,請稍後再試。");
setRecords([]);
} finally {
setLoading(false);
}
}, [date]);

useEffect(() => {
loadData();
}, [loadData]);

const rows = useMemo<DailySummaryRow[]>(() => {
const filtered =
floor === "all" ? records : records.filter((record) => record.storeId === floor);

const summary = new Map<string, DailySummaryRow>();

filtered.forEach((record) => {
const day = dayjs(record.deliveryDate).isValid()
? dayjs(record.deliveryDate).format("YYYY-MM-DD")
: "-";
const cartonQty = Number(record.numberOfCartons ?? 0);

const current = summary.get(day) ?? {
date: day,
floor2F: 0,
floor4F: 0,
truckX: 0,
total: 0,
};

if (record.storeId === "2/F") {
current.floor2F += cartonQty;
}
if (record.storeId === "4/F") {
current.floor4F += cartonQty;
}
if (String(record.truckLanceCode ?? "").trim() === "車線-X") {
current.truckX += cartonQty;
}

current.total += cartonQty;
summary.set(day, current);
});

return Array.from(summary.values()).sort((a, b) => b.date.localeCompare(a.date));
}, [records, floor]);

const chartOptions = useMemo<ApexOptions>(
() => ({
chart: {
type: "bar",
toolbar: { show: false },
},
colors: ["#1976d2", "#9c27b0", "#ff9800", "#2e7d32"],
dataLabels: { enabled: false },
stroke: { show: true, width: 1, colors: ["transparent"] },
plotOptions: {
bar: {
horizontal: false,
borderRadius: 3,
columnWidth: "55%",
},
},
xaxis: {
categories: rows.map((row) => row.date),
title: { text: "日期" },
},
yaxis: {
title: { text: "箱數" },
labels: {
formatter: (val) => Number(val || 0).toLocaleString("zh-HK"),
},
},
tooltip: {
y: {
formatter: (val) => `${Number(val || 0).toLocaleString("zh-HK")} 箱`,
},
},
legend: {
position: "top",
},
noData: {
text: "沒有圖表資料",
},
}),
[rows],
);

const chartSeries = useMemo(
() => [
{ name: "2/F", data: rows.map((row) => row.floor2F) },
{ name: "4/F", data: rows.map((row) => row.floor4F) },
{ name: "車線-X", data: rows.map((row) => row.truckX) },
{ name: "總數", data: rows.map((row) => row.total) },
],
[rows],
);

const summary = useMemo(() => {
return rows.reduce(
(acc, row) => {
acc.floor2F += row.floor2F;
acc.floor4F += row.floor4F;
acc.truckX += row.truckX;
acc.total += row.total;
return acc;
},
{ floor2F: 0, floor4F: 0, truckX: 0, total: 0 },
);
}, [rows]);

return (
<Box sx={{ width: "100%" }}>
<Typography variant="h6" sx={{ mb: 2 }}>
成品出倉出箱數量
</Typography>

{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}

{loading ? (
<Box sx={{ py: 6, display: "flex", justifyContent: "center" }}>
<CircularProgress />
</Box>
) : (
<Stack spacing={2}>
<Grid container spacing={1.5}>
<Grid item xs={12} md={6}>
<TextField
select
fullWidth
label="樓層"
value={floor}
onChange={(event) => setFloor(event.target.value as FloorFilter)}
>
<MenuItem value="all">全部</MenuItem>
<MenuItem value="2/F">2/F</MenuItem>
<MenuItem value="4/F">4/F</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label="日期"
type="date"
value={date}
InputLabelProps={{ shrink: true }}
onChange={(event) => setDate(event.target.value)}
/>
</Grid>
</Grid>

<Grid container spacing={1.5} alignItems="stretch">
<Grid item xs={12} md={4}>
<TableContainer component={Paper} sx={{ height: "100%" }}>
<Table size="small">
<TableBody>
<TableRow>
<TableCell>2/F 出箱數</TableCell>
<TableCell align="right">{summary.floor2F.toLocaleString("zh-HK")}</TableCell>
</TableRow>
<TableRow>
<TableCell>4/F 出箱數</TableCell>
<TableCell align="right">{summary.floor4F.toLocaleString("zh-HK")}</TableCell>
</TableRow>
<TableRow>
<TableCell>車線-X 出箱數</TableCell>
<TableCell align="right">{summary.truckX.toLocaleString("zh-HK")}</TableCell>
</TableRow>
<TableRow>
<TableCell>總出箱數</TableCell>
<TableCell align="right">{summary.total.toLocaleString("zh-HK")}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item xs={12} md={8}>
<Paper sx={{ p: 1.5, height: "100%" }}>
<SafeApexCharts
type="bar"
height={240}
options={chartOptions}
series={chartSeries}
chartRevision={`${floor}-${date}-${rows.length}`}
/>
</Paper>
</Grid>
</Grid>
</Stack>
)}
</Box>
);
};

export default FinishedGoodCartonDashboardTab;

+ 7
- 2
src/components/FinishedGoodSearch/FinishedGoodSearch.tsx 查看文件

@@ -42,6 +42,7 @@ import { PrinterCombo } from "@/app/api/settings/printer";
import { Autocomplete } from "@mui/material";
import FGPickOrderTicketReleaseTable from "./FGPickOrderTicketReleaseTable";
import TruckRoutingSummaryTab, { TruckRoutingSummaryFilters } from "./TruckRoutingSummaryTab";
import FinishedGoodCartonDashboardTab from "./FinishedGoodCartonDashboardTab";
import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import { fetchTruckRoutingSummaryPrecheck } from "@/app/(main)/report/truckRoutingSummaryApi";
@@ -378,7 +379,7 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCo
}
}, [truckRoutingFilters, selectedPrinterForAllDraft, t]);

const isTruckRoutingTab = tabIndex === 5;
const isTruckRoutingTab = tabIndex === 6;
const canPrintTruckRoutingSummary = Boolean(
truckRoutingFilters.storeId &&
truckRoutingFilters.truckLanceCode &&
@@ -393,7 +394,7 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCo
}, [fetchReleasedOrderCount]);

useEffect(() => {
if (tabIndex === 5) {
if (tabIndex === 6) {
logFeatureUsage(FEATURE_USAGE.TRUCK_ROUTING_SUMMARY, FEATURE_USAGE_ACTION.PAGE_VIEW);
}
}, [tabIndex]);
@@ -831,6 +832,7 @@ const handleAssignByLane = useCallback(async (
<Tab label={t("Finished Good Record")} iconPosition="end" />
<Tab label={t("Ticket Release Table")} iconPosition="end" />
<Tab label={t("Finished Good Record (All)")} iconPosition="end" />
<Tab label="成品出倉出箱數量" iconPosition="end" />
<Tab label="送貨路線摘要" iconPosition="end" />
</Tabs>
@@ -887,6 +889,9 @@ const handleAssignByLane = useCallback(async (
/>
)}
{tabIndex === 5 && (
<FinishedGoodCartonDashboardTab />
)}
{tabIndex === 6 && (
<TruckRoutingSummaryTab onFiltersChange={setTruckRoutingFilters} />
)}
</Box>


+ 31
- 6
src/components/Jodetail/completeJobOrderRecord.tsx 查看文件

@@ -378,7 +378,10 @@ const CompleteJobOrderRecord: React.FC<Props> = ({
}));
}, []);

const handlePickRecord = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
const handlePickRecord = useCallback(async (
jobOrderPickOrder: CompletedJobOrderPickOrder,
floor: "2F" | "3F" | "4F" | "ALL"
) => {
try {
if (!jobOrderPickOrder) {
console.error("No selected job order pick order available");
@@ -418,7 +421,8 @@ const CompleteJobOrderRecord: React.FC<Props> = ({
const printRequest = {
pickOrderId: pickOrderId,
printerId: printerId,
printQty: printQty
printQty: printQty,
floor,
};

console.log("Printing Pick Record with request: ", printRequest);
@@ -703,12 +707,33 @@ const CompleteJobOrderRecord: React.FC<Props> = ({
>
{t("View Details")}
</Button>
<Button
variant="contained"
<Button
variant="contained"
color="primary"
onClick={() => handlePickRecord(jobOrderPickOrder, "ALL")}
>
打印全部樓層板頭紙
</Button>
<Button
variant="contained"
color="primary"
onClick={() => handlePickRecord(jobOrderPickOrder, "2F")}
>
{t("Print Pick Record")} 2F
</Button>
<Button
variant="contained"
color="primary"
onClick={() => handlePickRecord(jobOrderPickOrder, "3F")}
>
{t("Print Pick Record")} 3F
</Button>
<Button
variant="contained"
color="primary"
onClick={() => handlePickRecord(jobOrderPickOrder)}
onClick={() => handlePickRecord(jobOrderPickOrder, "4F")}
>
{t("Print Pick Record")}
{t("Print Pick Record")} 4F
</Button>
</CardActions>
</Card>


Loading…
取消
儲存