3 Ревизии

Автор SHA1 Съобщение Дата
  Harry Groves e400b4a9c8 Remove ability to use sensitive reports преди 1 месец
  Harry Groves 2f7419207b Remove hardcoded placeholders and codes преди 1 месец
  Harry Groves 9140cbc9b2 Remove Pings Logo преди 1 месец
променени са 30 файла, в които са добавени 332 реда и са изтрити 6992 реда
  1. Двоични данни
      public/logo/HomepageLogo.png
  2. +2
    -2
      src/app/(main)/laserPrint/page.tsx
  3. +2
    -2
      src/app/(main)/m18Syn/page.tsx
  4. +3
    -3
      src/app/(main)/ps/page.tsx
  5. +1
    -1
      src/app/(main)/report/AsyncItemCodeAutocomplete.tsx
  6. +1
    -1
      src/app/(main)/report/ItemQcReportFilters.tsx
  7. +36
    -16
      src/app/(main)/report/ReportSelectionDashboard.tsx
  8. +1
    -1
      src/app/(main)/report/bomShopSyncReportApi.ts
  9. +0
    -16
      src/app/(main)/report/grnReportApi.ts
  10. +5
    -3
      src/app/(main)/report/page.tsx
  11. +32
    -78
      src/app/(main)/testing/page.tsx
  12. +3
    -3
      src/app/api/bagPrint/actions.ts
  13. +28
    -20
      src/components/BagPrint/BagPrintSearch.tsx
  14. +2
    -2
      src/components/DeliveryOrderFloorSettings/DeliveryOrderFloorSettings.tsx
  15. +23
    -1871
      src/components/DetailScheduleDetail/ViewByFGDetails.tsx
  16. +18
    -1872
      src/components/DetailedScheduleDetail/TempRecords.tsx
  17. +21
    -15
      src/components/LaserPrint/LaserPrintSearch.tsx
  18. +2
    -2
      src/components/PoDetail/PutAwayForm.tsx
  19. +29
    -34
      src/components/PoWorkbench/detailsGrid/poWorkbenchDetailsGridMock.ts
  20. +2
    -2
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  21. +11
    -2911
      src/components/RoughScheduleDetail/TempRecords.tsx
  22. +39
    -72
      src/config/reportConfig.ts
  23. +6
    -6
      src/i18n/en/deliveryOrderFloor.json
  24. +2
    -2
      src/i18n/en/m18Sync.json
  25. +26
    -23
      src/i18n/en/report.json
  26. +6
    -6
      src/i18n/zh/deliveryOrderFloor.json
  27. +1
    -1
      src/i18n/zh/laserPrint.json
  28. +2
    -2
      src/i18n/zh/m18Sync.json
  29. +2
    -2
      src/i18n/zh/navigation.json
  30. +26
    -23
      src/i18n/zh/report.json

Двоични данни
public/logo/HomepageLogo.png Целия файл

Преди След
Ширина: 1080  |  Височина: 511  |  Големина: 211 KiB

+ 2
- 2
src/app/(main)/laserPrint/page.tsx Целия файл

@@ -4,7 +4,7 @@ import LaserPrintSearch from "@/components/LaserPrint/LaserPrintSearch";
import { Stack, Typography } from "@mui/material";

export const metadata: Metadata = {
title: "檸檬機(激光機)",
title: "激光機",
};

export default async function LaserPrintPage() {
@@ -12,7 +12,7 @@ export default async function LaserPrintPage() {
<I18nProvider namespaces={["laserPrint", "navigation", "common"]}>
<Stack direction="row" justifyContent="space-between" flexWrap="wrap" rowGap={2}>
<Typography variant="h4" marginInlineEnd={2}>
檸檬機(激光機)
激光機
</Typography>
</Stack>
<LaserPrintSearch />


+ 2
- 2
src/app/(main)/m18Syn/page.tsx Целия файл

@@ -318,7 +318,7 @@ export default function M18SynPage() {
label="PO Code"
value={m18PoCode}
onChange={(e) => setM18PoCode(e.target.value)}
placeholder="e.g. PFP002PO26030341"
placeholder="e.g. DEMO-PO-001"
sx={{ minWidth: 320 }}
/>
<Button variant="contained" color="primary" onClick={handleSyncM18PoByCode} disabled={isSyncingM18Po}>
@@ -418,7 +418,7 @@ export default function M18SynPage() {
label="Item / product code"
value={m18ProductCode}
onChange={(e) => setM18ProductCode(e.target.value)}
placeholder="e.g. PP1175 (M18 item code)"
placeholder="e.g. DEMO-FG-01"
sx={{ minWidth: 320 }}
/>
<Button


+ 3
- 3
src/app/(main)/ps/page.tsx Целия файл

@@ -1179,9 +1179,9 @@ export default function ProductionSchedulePage() {
<th className="px-4 py-2 text-left font-bold text-slate-700 dark:text-slate-200">設定排期庫存</th>
<th className="px-4 py-2 text-right font-bold text-slate-700 dark:text-slate-200">過去平均出貨量</th>
<th className="px-4 py-2 text-left font-bold text-slate-700 dark:text-slate-200">設定排期每天出貨量</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">咖啡</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">茶</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">檸檬</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">類別 A</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">類別 B</th>
<th className="px-4 py-2 text-center font-bold text-slate-700 dark:text-slate-200">類別 C</th>
</tr>
</thead>
<tbody>


+ 1
- 1
src/app/(main)/report/AsyncItemCodeAutocomplete.tsx Целия файл

@@ -239,7 +239,7 @@ const AsyncItemCodeAutocomplete: React.FC<Props> = ({
{...params}
fullWidth
label={label}
placeholder={hasSelection ? "" : (placeholder || "e.g. FA0591")}
placeholder={hasSelection ? "" : (placeholder || "e.g. DEMO-FG-01")}
helperText={t("typeToSearchItemCode", { min: minChars })}
InputLabelProps={params.InputLabelProps}
InputProps={{


+ 1
- 1
src/app/(main)/report/ItemQcReportFilters.tsx Целия файл

@@ -75,7 +75,7 @@ export default function ItemQcReportFilters({
label={field("itemCode", "貨品編號 Item Code")}
value={criteria.itemCode || ""}
onChange={(e) => onFieldChange("itemCode", e.target.value)}
placeholder="e.g. MJ0364"
placeholder="e.g. DEMO-FG-01"
/>
</Grid>
<Grid item {...gridSize}>


+ 36
- 16
src/app/(main)/report/ReportSelectionDashboard.tsx Целия файл

@@ -17,7 +17,7 @@ import OutboundOutlinedIcon from "@mui/icons-material/OutboundOutlined";
import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined";
import PieChartOutlineOutlinedIcon from "@mui/icons-material/PieChartOutlineOutlined";
import type { SvgIconComponent } from "@mui/icons-material";
import { REPORTS } from "@/config/reportConfig";
import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from "@/config/reportConfig";
import { REPORT_CATEGORIES, type ReportCategoryConfig } from "./reportCategories";
import { useReportLabels } from "./reportI18n";

@@ -52,12 +52,16 @@ function ReportCard({
title,
accent,
selected,
unavailable,
unavailableLabel,
onClick,
}: {
reportId: string;
title: string;
accent: string;
selected: boolean;
unavailable: boolean;
unavailableLabel: string;
onClick: () => void;
}) {
const Icon = REPORT_ICON_MAP[reportId] ?? Inventory2OutlinedIcon;
@@ -66,7 +70,9 @@ function ReportCard({
<Box
component="button"
type="button"
onClick={onClick}
disabled={unavailable}
onClick={unavailable ? undefined : onClick}
aria-disabled={unavailable}
sx={{
display: "flex",
alignItems: "center",
@@ -74,17 +80,22 @@ function ReportCard({
width: "100%",
p: 1.5,
textAlign: "left",
cursor: "pointer",
cursor: unavailable ? "not-allowed" : "pointer",
border: "1px solid",
borderColor: selected ? accent : "divider",
borderColor: unavailable ? "action.disabledBackground" : selected ? accent : "divider",
borderRadius: 1.5,
bgcolor: "background.paper",
boxShadow: selected ? 2 : "0 1px 3px rgba(0,0,0,0.06)",
bgcolor: unavailable ? "action.hover" : "background.paper",
color: unavailable ? "text.disabled" : "text.primary",
boxShadow: !unavailable && selected ? 2 : "0 1px 3px rgba(0,0,0,0.06)",
opacity: unavailable ? 0.72 : 1,
filter: unavailable ? "grayscale(1)" : "none",
transition: "border-color 0.15s, box-shadow 0.15s",
"&:hover": {
borderColor: accent,
boxShadow: 2,
},
"&:hover": unavailable
? {}
: {
borderColor: accent,
boxShadow: 2,
},
}}
>
<Box
@@ -95,16 +106,23 @@ function ReportCard({
width: 40,
height: 40,
borderRadius: 1,
bgcolor: `${accent}18`,
color: accent,
bgcolor: unavailable ? "action.disabledBackground" : `${accent}18`,
color: unavailable ? "text.disabled" : accent,
flexShrink: 0,
}}
>
<Icon fontSize="small" />
</Box>
<Typography variant="body2" fontWeight={selected ? 600 : 400} sx={{ lineHeight: 1.35 }}>
{title}
</Typography>
<Box sx={{ minWidth: 0 }}>
<Typography variant="body2" fontWeight={selected && !unavailable ? 600 : 400} sx={{ lineHeight: 1.35 }}>
{title}
</Typography>
{unavailable && (
<Typography variant="caption" sx={{ display: "block", lineHeight: 1.3, color: "text.secondary" }}>
{unavailableLabel}
</Typography>
)}
</Box>
</Box>
);
}
@@ -118,7 +136,7 @@ function CategoryColumn({
selectedReportId: string;
onSelectReport: (reportId: string) => void;
}) {
const { reportTitle, categoryTitle } = useReportLabels();
const { t, reportTitle, categoryTitle } = useReportLabels();
const reports = category.reportIds
.map((id) => reportById[id])
.filter(Boolean);
@@ -161,6 +179,8 @@ function CategoryColumn({
title={reportTitle(report)}
accent={category.accent}
selected={selectedReportId === report.id}
unavailable={DEMO_UNAVAILABLE_REPORT_IDS.has(report.id)}
unavailableLabel={t("unavailableInDemo")}
onClick={() => onSelectReport(report.id)}
/>
</Grid>


+ 1
- 1
src/app/(main)/report/bomShopSyncReportApi.ts Целия файл

@@ -205,7 +205,7 @@ function toMaterialExcelRow(

export async function generateBomShopSyncReportExcel(
criteria: Record<string, string>,
reportTitle: string = "M18 BOM Shop 同步記錄",
reportTitle: string = "BOM Shop 同步記錄",
t?: TFunction,
): Promise<void> {
const L = bomSyncLabels(t);


+ 0
- 16
src/app/(main)/report/grnReportApi.ts Целия файл

@@ -33,12 +33,6 @@ export interface GrnReportRow {
lineAmount?: number;
/** PO currency code (currency.code) */
currencyCode?: string;
/** M18 AN document code from m18_goods_receipt_note_log.grn_code */
grnCode?: string;
/** M18 record id (m18_record_id) */
grnId?: number | string;
/** From purchase_order.m18CreatedUId; e.g. "2569 (legato)" */
poM18CreatorDisplay?: string;
[key: string]: unknown;
}

@@ -54,7 +48,6 @@ export interface ListedPoAmounts {
poCode?: string;
currencyCode?: string;
totalAmount?: number;
grnCodes?: string;
}[];
}

@@ -148,13 +141,9 @@ function grnLabels(t?: TFunction) {
unitPrice: tx(t, "excel.grn.unitPrice", "單價"),
currency: tx(t, "excel.grn.currency", "貨幣"),
amount: tx(t, "excel.grn.amount", "金額"),
grnCode: tx(t, "excel.grn.grnCode", "M18 入倉單號"),
grnId: tx(t, "excel.grn.grnId", "M18 記錄編號"),
poCreator: tx(t, "excel.grn.poCreator", "PO建立者(M18)"),
note: tx(t, "excel.grn.note", "備註"),
category: tx(t, "excel.grn.category", "類別"),
totalAmount: tx(t, "excel.grn.totalAmount", "金額"),
grnCodes: tx(t, "excel.grn.grnCodes", "M18 入倉單號"),
noCompletedPo: tx(t, "excel.grn.noCompletedPo", "(篩選範圍內無已完成之 PO 行)"),
categoryCurrencyTotal: tx(t, "excel.grn.categoryCurrencyTotal", "貨幣小計"),
categoryPo: tx(t, "excel.grn.categoryPo", "訂單"),
@@ -187,9 +176,6 @@ function toExcelRow(
base[L.currency] = r.currencyCode ?? "";
base[L.amount] = moneyCellValue(r.lineAmount);
}
base[L.grnCode] = r.grnCode ?? "";
base[L.grnId] = r.grnId ?? "";
base[L.poCreator] = r.poM18CreatorDisplay ?? "";
return base;
}

@@ -213,7 +199,6 @@ function buildListedPoAmountSheetRows(
[L.poNo]: "",
[L.currency]: c.currencyCode ?? "",
[L.totalAmount]: moneyCellValue(c.totalAmount),
[L.grnCodes]: "",
});
}
for (const p of listed.byPurchaseOrder) {
@@ -223,7 +208,6 @@ function buildListedPoAmountSheetRows(
[L.poNo]: p.poCode ?? "",
[L.currency]: p.currencyCode ?? "",
[L.totalAmount]: moneyCellValue(p.totalAmount),
[L.grnCodes]: p.grnCodes ?? "",
});
}
return out;


+ 5
- 3
src/app/(main)/report/page.tsx Целия файл

@@ -25,7 +25,7 @@ import {
} from '@mui/material';
import DownloadIcon from '@mui/icons-material/Download';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import { REPORTS } from '@/config/reportConfig';
import { DEMO_UNAVAILABLE_REPORT_IDS, REPORTS } from '@/config/reportConfig';
import { mergePastedItemCodes, buildItemCodePasteRequestBody, parseItemCodeTokens } from './parseItemCodeTokens';
import { NEXT_PUBLIC_API_URL } from '@/config/api';
import { clientAuthFetch } from '@/app/utils/clientAuthFetch';
@@ -152,6 +152,7 @@ export default function ReportPage() {
[selectedReportId]);

const handleSelectReport = (reportId: string) => {
if (DEMO_UNAVAILABLE_REPORT_IDS.has(reportId)) return;
if (reportId === selectedReportId) return;
setSelectedReportId(reportId);
setFieldErrors({});
@@ -159,7 +160,7 @@ export default function ReportPage() {
if (reportId === 'rep-010') {
updateCriteria({ qcType: 'all', qcItemScope: 'all' });
} else if (reportId === 'rep-004') {
updateCriteria({ storeId: 'All', poPrefix: 'All' });
updateCriteria({ storeId: 'All' });
} else if (reportId === 'rep-021') {
updateCriteria({ storeId: 'All', stockTakeSectionDescription: 'All', lotOrigin: 'All' });
} else {
@@ -452,6 +453,7 @@ export default function ReportPage() {

const executeExcelReport = async () => {
if (!currentReport) return;
if (DEMO_UNAVAILABLE_REPORT_IDS.has(currentReport.id)) return;
if (excelInFlightRef.current) return;
excelInFlightRef.current = true;
setLoading(true);
@@ -727,7 +729,7 @@ export default function ReportPage() {
<Grid item {...gridSize} key={fieldKey}>
<AsyncItemCodeAutocomplete
label={translatedLabel}
placeholder={field.placeholder || "e.g. FA0591"}
placeholder={field.placeholder || "e.g. DEMO-FG-01"}
value={selectedCodes}
onInputValueChange={(text) => {
pendingItemCodeInputRef.current[field.name] = text;


+ 32
- 78
src/app/(main)/testing/page.tsx Целия файл

@@ -37,8 +37,6 @@ import {
type LaserBag2AutoSendReport,
type LaserLastReceiveSuccess,
} from "@/app/api/laserPrint/actions";
import * as XLSX from "xlsx";

interface TabPanelProps {
children?: React.ReactNode;
index: number;
@@ -175,49 +173,6 @@ export default function TestingPage() {
};
}, [tabValue]);

const handleDownloadGrnPreviewXlsx = async () => {
try {
const response = await clientAuthFetch(
`${NEXT_PUBLIC_API_URL}/report/grn-preview-m18?receiptDate=${encodeURIComponent(
grnPreviewReceiptDate,
)}`,
{ method: "GET" },
);
if (response.status === 401 || response.status === 403) return;
if (!response.ok) throw new Error(`Download failed: ${response.status}`);

const data = await response.json();
const rows = Array.isArray(data?.rows) ? data.rows : [];

const ws = XLSX.utils.json_to_sheet(rows);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "GRN Preview");

const xlsxArrayBuffer = XLSX.write(wb, {
bookType: "xlsx",
type: "array",
});
const blob = new Blob([xlsxArrayBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});

const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute(
"download",
`grn-preview-m18-${grnPreviewReceiptDate}.xlsx`,
);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (e) {
console.error("GRN Preview XLSX Download Error:", e);
alert("GRN Preview XLSX download failed. Check console/network.");
}
};

const downloadBlob = (blob: Blob, filename: string) => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
@@ -524,33 +479,35 @@ export default function TestingPage() {
</Tabs>

<TabPanel value={tabValue} index={0}>
<Section title="1. GRN Preview (M18)">
<Stack
direction="row"
spacing={2}
sx={{ mb: 2, alignItems: "center" }}
>
<TextField
size="small"
label="Receipt Date"
type="date"
value={grnPreviewReceiptDate}
onChange={(e) => setGrnPreviewReceiptDate(e.target.value)}
InputLabelProps={{ shrink: true }}
/>
<Button
variant="contained"
color="success"
size="medium"
startIcon={<FileDownload />}
onClick={handleDownloadGrnPreviewXlsx}
<Section title="1. GRN Preview">
<Box sx={{ opacity: 0.55, filter: "grayscale(1)", pointerEvents: "none" }}>
<Stack
direction="row"
spacing={2}
sx={{ mb: 2, alignItems: "center" }}
>
Download GRN Preview XLSX
</Button>
</Stack>
<Typography variant="body2" color="textSecondary">
Backend endpoint:{" "}
<code>/report/grn-preview-m18?receiptDate=YYYY-MM-DD</code>
<TextField
size="small"
label="Receipt Date"
type="date"
value={grnPreviewReceiptDate}
onChange={(e) => setGrnPreviewReceiptDate(e.target.value)}
InputLabelProps={{ shrink: true }}
disabled
/>
<Button
variant="contained"
color="success"
size="medium"
startIcon={<FileDownload />}
disabled
>
Download GRN Preview XLSX
</Button>
</Stack>
</Box>
<Typography variant="body2" color="text.secondary">
Unavailable in demo mode
</Typography>
</Section>
</TabPanel>
@@ -560,7 +517,7 @@ export default function TestingPage() {
<Alert severity="info" sx={{ mb: 2 }}>
Uses <strong>GET /py/job-orders?planStart=</strong> for the day,
then the same <code>jobOrders</code> payload as{" "}
<strong>Bag Print → 下載 OnPack2023檸檬機</strong>. The ZIP contains
<strong>Bag Print → 下載 OnPack 文字</strong>. The ZIP contains
loose <code>.job</code> / <code>.image</code> / BMPs — extract
before sending to NGE; the ZIP itself is only a transport bundle.
</Alert>
@@ -973,12 +930,9 @@ export default function TestingPage() {
<TabPanel value={tabValue} index={5}>
<Section title="6. M18 sync alert email">
<Alert severity="info" sx={{ mb: 2 }}>
Production sync errors email{" "}
<strong>[email protected]</strong> and{" "}
<strong>[email protected]</strong> (see{" "}
<code>scheduler.sync-alert.email.to-addresses</code>). WhatsApp/Twilio
is disabled. SMTP from DB <code>MAIL.smtp.*</code> (e.g. Gmail{" "}
<code>[email protected]</code> + app password).
Production sync errors email the addresses in{" "}
<code>scheduler.sync-alert.email.to-addresses</code>. WhatsApp/Twilio
is disabled. SMTP comes from DB <code>MAIL.smtp.*</code>.
</Alert>
<Stack spacing={2} sx={{ mb: 2, maxWidth: 720 }}>
<TextField


+ 3
- 3
src/app/api/bagPrint/actions.ts Целия файл

@@ -180,7 +180,7 @@ function skippedWithoutExpiryFromResponse(res: Response): string[] {
.filter(Boolean);
}

/** 汁水機 OnPack — same as QR ZIP, plus LOGO_EXP BMP from item_default_shelf_life. */
/** OnPack QR — same as QR ZIP, plus LOGO_EXP BMP from item_default_shelf_life. */
export async function downloadOnPackQrZipWithExpiry(
request: OnPackQrDownloadRequest,
): Promise<OnPackZipDownload> {
@@ -201,7 +201,7 @@ export async function downloadOnPackQrZipWithExpiry(
};
}

/** OnPack2023 檸檬機 — text QR template (`onpack2030_2`), no separate .bmp */
/** OnPack text QR template (`onpack2030_2`), no separate .bmp */
export async function downloadOnPackTextQrZip(
request: OnPackQrDownloadRequest,
): Promise<Blob> {
@@ -219,7 +219,7 @@ export async function downloadOnPackTextQrZip(
return res.blob();
}

/** OnPack2023 檸檬機 — same as text ZIP, plus TEXT_EXP from item_default_shelf_life. */
/** OnPack text QR — same as text ZIP, plus TEXT_EXP from item_default_shelf_life. */
export async function downloadOnPackTextQrZipWithExpiry(
request: OnPackQrDownloadRequest,
): Promise<OnPackZipDownload> {


+ 28
- 20
src/components/BagPrint/BagPrintSearch.tsx Целия файл

@@ -82,15 +82,23 @@ const SETTINGS_KEY = "bagPrint_settings";
const DEFAULT_SETTINGS = {
dabag_ip: "",
dabag_port: "3008",
laser_ip: "192.168.17.10",
laser_ip: "",
laser_port: "45678",
};

const LIVE_PRINTER_HOSTS = new Set(["192.168.17.10", "192.168.18.77"]);

function loadSettings(): typeof DEFAULT_SETTINGS {
if (typeof window === "undefined") return DEFAULT_SETTINGS;
try {
const s = localStorage.getItem(SETTINGS_KEY);
if (s) return { ...DEFAULT_SETTINGS, ...JSON.parse(s) };
if (s) {
const merged = { ...DEFAULT_SETTINGS, ...JSON.parse(s) };
if (LIVE_PRINTER_HOSTS.has(String(merged.laser_ip ?? "").trim())) {
merged.laser_ip = "";
}
return merged;
}
} catch {}
return DEFAULT_SETTINGS;
}
@@ -467,11 +475,11 @@ const BagPrintSearch: React.FC = () => {
link.remove();
window.URL.revokeObjectURL(url);

setSnackbar(skippedExpirySnackbar("OnPack 汁水機(含到期日)ZIP 已下載", skippedWithoutExpiry));
setSnackbar(skippedExpirySnackbar("OnPack QR(含到期日)ZIP 已下載", skippedWithoutExpiry));
} catch (e) {
setSnackbar({
open: true,
message: e instanceof Error ? e.message : "下載 OnPack 汁水機(含到期日)失敗",
message: e instanceof Error ? e.message : "下載 OnPack QR(含到期日)失敗",
severity: "error",
});
} finally {
@@ -505,11 +513,11 @@ const BagPrintSearch: React.FC = () => {
link.remove();
window.URL.revokeObjectURL(url);

setSnackbar({ open: true, message: "OnPack2023檸檬機 ZIP 已下載", severity: "success" });
setSnackbar({ open: true, message: "OnPack 文字 ZIP 已下載", severity: "success" });
} catch (e) {
setSnackbar({
open: true,
message: e instanceof Error ? e.message : "下載 OnPack2023檸檬機 失敗",
message: e instanceof Error ? e.message : "下載 OnPack 文字 失敗",
severity: "error",
});
} finally {
@@ -544,11 +552,11 @@ const BagPrintSearch: React.FC = () => {
link.remove();
window.URL.revokeObjectURL(url);

setSnackbar(skippedExpirySnackbar("OnPack2023檸檬機(含到期日)ZIP 已下載", skippedWithoutExpiry));
setSnackbar(skippedExpirySnackbar("OnPack 文字(含到期日)ZIP 已下載", skippedWithoutExpiry));
} catch (e) {
setSnackbar({
open: true,
message: e instanceof Error ? e.message : "下載 OnPack2023檸檬機(含到期日)失敗",
message: e instanceof Error ? e.message : "下載 OnPack 文字(含到期日)失敗",
severity: "error",
});
} finally {
@@ -597,7 +605,7 @@ const BagPrintSearch: React.FC = () => {
),
);
} catch {
/* 檸檬機標籤可沒有;不擋畫面 */
/* 激光機標籤可沒有;不擋畫面 */
}
})();
}, []);
@@ -793,7 +801,7 @@ const BagPrintSearch: React.FC = () => {
jobOrders.length === 0
}
>
{downloadingOnPack ? "下載中..." : "下載 OnPack 汁水機 QR code"}
{downloadingOnPack ? "下載中..." : "下載 OnPack QR code"}
</Button>
<Button
variant="contained"
@@ -808,7 +816,7 @@ const BagPrintSearch: React.FC = () => {
jobOrders.length === 0
}
>
{downloadingOnPackExp ? "下載中..." : "下載 OnPack 汁水機(含到期日)"}
{downloadingOnPackExp ? "下載中..." : "下載 OnPack QR(含到期日)"}
</Button>
<Button
variant="contained"
@@ -824,7 +832,7 @@ const BagPrintSearch: React.FC = () => {
jobOrders.length === 0
}
>
{downloadingOnPackText ? "下載中..." : "下載 OnPack2023檸檬機"}
{downloadingOnPackText ? "下載中..." : "下載 OnPack 文字"}
</Button>
{canSeeOnPackAdmin && (
<Button
@@ -841,7 +849,7 @@ const BagPrintSearch: React.FC = () => {
jobOrders.length === 0
}
>
{downloadingOnPackTextExp ? "下載中..." : "下載 OnPack2023檸檬機(含到期日)"}
{downloadingOnPackTextExp ? "下載中..." : "下載 OnPack 文字(含到期日)"}
</Button>
)}
</Stack>
@@ -903,8 +911,8 @@ const BagPrintSearch: React.FC = () => {
{jo.itemCode || "—"}
</Typography>
<Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap sx={{ mt: 0.5 }}>
{juiceExpiryOk ? <Chip size="small" label="汁水機" color="primary" /> : null}
{lemonOk ? <Chip size="small" label="檸檬機" color="secondary" /> : null}
{juiceExpiryOk ? <Chip size="small" label="OnPack QR" color="primary" /> : null}
{lemonOk ? <Chip size="small" label="激光機" color="secondary" /> : null}
</Stack>
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
@@ -1086,17 +1094,17 @@ const BagPrintSearch: React.FC = () => {
>
<Stack spacing={1.5} sx={{ flexShrink: 0, mb: 1 }}>
<Typography variant="subtitle2" color="primary">
汁水機({expiryCodes.length})
OnPack QR({expiryCodes.length})
</Typography>
<Typography variant="body2" color="text.secondary">
「下載 OnPack 汁水機(含到期日)」只用此清單。名稱印在 Product 行,過長可改短後按儲存。
「下載 OnPack QR(含到期日)」只用此清單。名稱印在 Product 行,過長可改短後按儲存。
點欄位標題可排序;冷藏/-18/列印第一次點擊會把「未設」排到最前。
</Typography>
<Stack direction="row" spacing={1} alignItems="center">
<TextField
label="新增品號"
size="small"
placeholder="例如 PP2211"
placeholder="例如 DEMO-FG-01"
value={expiryCodeInput}
onChange={(e) => setExpiryCodeInput(e.target.value)}
onKeyDown={(e) => {
@@ -1216,7 +1224,7 @@ const BagPrintSearch: React.FC = () => {
onChange={(e) =>
setNameDrafts((prev) => ({ ...prev, [row.itemCode]: e.target.value }))
}
placeholder={row.defaultPrintName || "例如 豬肉水(1包X2千克)"}
placeholder={row.defaultPrintName || "例如 鄉村麵包"}
inputProps={{ maxLength: 255 }}
fullWidth
/>
@@ -1292,7 +1300,7 @@ const BagPrintSearch: React.FC = () => {
</TableContainer>
)}
<Typography variant="body2" color="text.secondary" sx={{ flexShrink: 0, mt: 1 }}>
檸檬機到期日 ZIP 品號稍後加入。
激光機到期日 ZIP 品號稍後加入。
</Typography>
</DialogContent>
<DialogActions>


+ 2
- 2
src/components/DeliveryOrderFloorSettings/DeliveryOrderFloorSettings.tsx Целия файл

@@ -358,8 +358,8 @@ const DeliveryOrderFloorSettings: React.FC = () => {
onChange={onFloorSelectChange}
disabled={dialogSaving || comboLoading}
>
<MenuItem value="2F">2F</MenuItem>
<MenuItem value="4F">4F</MenuItem>
<MenuItem value="2F">2/F</MenuItem>
<MenuItem value="4F">4/F</MenuItem>
</Select>
</FormControl>
<Button


+ 23
- 1871
src/components/DetailScheduleDetail/ViewByFGDetails.tsx
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 18
- 1872
src/components/DetailedScheduleDetail/TempRecords.tsx
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 21
- 15
src/components/LaserPrint/LaserPrintSearch.tsx Целия файл

@@ -83,21 +83,27 @@ const LaserPrintSearch: React.FC = () => {
message: "",
});
const [successSignal, setSuccessSignal] = useState<string | null>(null);
const [laserHost, setLaserHost] = useState("192.168.18.77");
const [laserHost, setLaserHost] = useState("");
const [laserPort, setLaserPort] = useState("45678");
const [laserItemCodes, setLaserItemCodes] = useState("PP1175");
const [laserItemCodes, setLaserItemCodes] = useState("");
const [lastLaserReceive, setLastLaserReceive] = useState<LaserLastReceiveSuccess | null>(null);
const [settingsLoaded, setSettingsLoaded] = useState(false);
const [printerConnected, setPrinterConnected] = useState(false);
const [printerMessage, setPrinterMessage] = useState("檸檬機(激光機)未連接");
const [printerMessage, setPrinterMessage] = useState("激光機未連接");
const sendInFlightRef = useRef(false);

const loadSystemSettings = useCallback(async () => {
try {
const s = await fetchLaserBag2Settings();
setLaserHost(s.host);
const host = (s.host ?? "").trim();
setLaserHost(host === "192.168.18.77" || host === "192.168.17.10" ? "" : host);
setLaserPort(String(s.port));
setLaserItemCodes(s.itemCodes ?? "PP1175");
const itemCodes = (s.itemCodes ?? "")
.split(",")
.map((code) => code.trim())
.filter((code) => code && code !== "PP1175" && code !== "PP2211")
.join(",");
setLaserItemCodes(itemCodes);
setLastLaserReceive(s.lastReceiveSuccess ?? null);
setSettingsLoaded(true);
} catch (e) {
@@ -158,7 +164,7 @@ const LaserPrintSearch: React.FC = () => {
setPrinterMessage(result.message);
} catch (e) {
setPrinterConnected(false);
setPrinterMessage(e instanceof Error ? e.message : "檸檬機(激光機)狀態檢查失敗");
setPrinterMessage(e instanceof Error ? e.message : "激光機狀態檢查失敗");
}
}, []);

@@ -201,7 +207,7 @@ const LaserPrintSearch: React.FC = () => {
if (sendInFlightRef.current || sendingJobId !== null) return;

if (!laserHost.trim()) {
setErrorSnackbar({ open: true, message: "請在系統設定中填寫檸檬機(激光機) IP。" });
setErrorSnackbar({ open: true, message: "請在系統設定中填寫激光機 IP。" });
return;
}

@@ -239,8 +245,8 @@ const LaserPrintSearch: React.FC = () => {
: "";
setSuccessSignal(
laterFail
? `已送出 ${sentOk} 次至檸檬機(激光機)${ackHint}(後續重送失敗:${laterFail})`
: `已送出 ${sentOk} 次至檸檬機(激光機)${ackHint}`,
? `已送出 ${sentOk} 次至激光機${ackHint}(後續重送失敗:${laterFail})`
: `已送出 ${sentOk} 次至激光機${ackHint}`,
);
await loadSystemSettings();
} catch (e) {
@@ -287,7 +293,7 @@ const LaserPrintSearch: React.FC = () => {
{lastLaserReceive.source ? ` (${lastLaserReceive.source === "AUTO" ? "自動送出" : "手動點選"})` : ""}
</Typography>
<Typography variant="body2" sx={{ mt: 0.5 }}>
此時間只會在檸檬機回覆 receive 時更新。之後送出失敗不會改這裡。
此時間只會在激光機回覆 receive 時更新。之後送出失敗不會改這裡。
</Typography>
</Alert>
)}
@@ -332,7 +338,7 @@ const LaserPrintSearch: React.FC = () => {
}}
title={printerMessage}
>
檸檬機(激光機):
激光機:
</Box>
</Stack>
</Stack>
@@ -420,11 +426,11 @@ const LaserPrintSearch: React.FC = () => {
</Paper>

<Dialog open={settingsOpen} onClose={() => setSettingsOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>檸檬機(激光機)(系統設定)</DialogTitle>
<DialogTitle>激光機(系統設定)</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 1 }}>
<Typography variant="body2" color="text.secondary">
儲存後寫入資料庫,後端送出走此 IP/埠(預設 192.168.18.77:45678)。
儲存後寫入資料庫,後端送出走此 IP/埠。未填 IP 則不會送出。
</Typography>
<TextField
label="IP"
@@ -446,8 +452,8 @@ const LaserPrintSearch: React.FC = () => {
value={laserItemCodes}
onChange={(e) => setLaserItemCodes(e.target.value)}
fullWidth
placeholder="PP1175"
helperText="預設 PP1175;可輸入多個品號,例如 PP1175,AB999。留空則列表顯示當日全部包裝工單。"
placeholder="例如 DEMO-FG-01"
helperText="可輸入多個品號,例如 DEMO-FG-01,DEMO-FG-02。留空則列表顯示當日全部包裝工單。"
/>
</Stack>
</DialogContent>


+ 2
- 2
src/components/PoDetail/PutAwayForm.tsx Целия файл

@@ -116,7 +116,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg
group: "default",
};
const options = useMemo(() => {
const defaultLabel = suggestedLocationCode || t("W201 - 2F-A,B室");
const defaultLabel = suggestedLocationCode || t("Select warehouse");
return [
{ value: 1, label: defaultLabel, group: "default" },
...filteredWarehouse.map((w) => ({
@@ -260,7 +260,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg
editable: false,
renderCell(params) {
const value = (params.value as string) ?? "";
// 目前格式像 "2F-W201-#L-08 - 2F-W201",只要左邊 LocationCode
// Location label is "code - description"; keep the code on the left.
const locationCode = value.split(" - ")[0] || value;
return (


+ 29
- 34
src/components/PoWorkbench/detailsGrid/poWorkbenchDetailsGridMock.ts Целия файл

@@ -5,72 +5,67 @@ export const PO_WORKBENCH_DETAILS_GRID_MOCK_ROWS: readonly PoWorkbenchDetailsGri
[
{
id: "mock-line-1",
itemName:
"澳洲和牛腱(冷凍)· A5級去筋切片裝 2.5kg×4包/箱(供應商批次:AU-WG-2026-Q1)",
itemCode: "AB1234",
itemName: "普通麵粉",
itemCode: "DEMO-RM-01",
category: "material",
storageLocation: "YF-W201-#A-12",
storageLocation: "DEMO-A-#A-01",
receiveStatus: "pending",
expectedQty: 100,
purchaseUnitLabel: "1箱X100包X150克",
purchaseUnitLabel: "1袋X25公斤",
receivedQtyPurchase: 0,
receivedQtyInventory: 0,
inventoryUnitLabel: "克",
inventoryUnitLabel: "公斤",
},
{
id: "mock-line-2",
itemName:
"耐高温真空膠袋(食品級)· 300×450mm 厚度80μm 每卷500個(雙層密封條)",
itemCode: "BC5678",
itemName: "鮮奶",
itemCode: "DEMO-RM-02",
category: "consumable",
storageLocation: "YF-C305-#B-04",
storageLocation: "DEMO-B-#B-02",
receiveStatus: "receiving",
expectedQty: 24,
purchaseUnitLabel: "1箱X24卷X500個",
receivedQtyPurchase: 26,
receivedQtyInventory: 312000,
inventoryUnitLabel: "個",
purchaseUnitLabel: "1箱X12瓶X1升",
receivedQtyPurchase: 12,
receivedQtyInventory: 12,
inventoryUnitLabel: "升",
},
{
id: "mock-line-3",
itemName:
"照燒汁(商用濃縮裝)· 日式醬油基底 5L/桶(開封後需冷藏,效期見標籤)",
itemCode: "FG9012",
itemName: "鄉村麵包",
itemCode: "DEMO-FG-01",
category: "finishedGoods",
storageLocation: "YF-D102-#C-08",
storageLocation: "DEMO-A-#C-03",
receiveStatus: "completed",
expectedQty: 48,
purchaseUnitLabel: "1板X4桶X5升",
purchaseUnitLabel: "1箱X8個",
receivedQtyPurchase: 48,
receivedQtyInventory: 960,
inventoryUnitLabel: "升",
receivedQtyInventory: 48,
inventoryUnitLabel: "個",
},
{
id: "mock-line-4",
itemName:
"高筋麵粉預拌漿(半成品)· 中央廚房標準配方 20kg/袋(含酵母改良劑)",
itemCode: "DE3456",
itemName: "麵團",
itemCode: "DEMO-SF-01",
category: "semiFinished",
storageLocation: "YF-W118-#D-22",
storageLocation: "DEMO-A-#D-04",
receiveStatus: "receiving",
expectedQty: 60,
purchaseUnitLabel: "1板X3袋X20公斤",
receivedQtyPurchase: 55,
receivedQtyInventory: 3300,
purchaseUnitLabel: "1袋X10公斤",
receivedQtyPurchase: 40,
receivedQtyInventory: 40,
inventoryUnitLabel: "公斤",
},
{
id: "mock-line-5",
itemName:
"廚房多功能清潔劑(濃縮原液)· 無磷配方 1L×12瓶/箱(勿與漂白水混用)",
itemCode: "EF7890",
itemName: "奶包",
itemCode: "DEMO-FG-02",
category: "miscNonConsumable",
storageLocation: "YF-M402-#E-01",
storageLocation: "DEMO-B-#E-05",
receiveStatus: "pending",
expectedQty: 120,
purchaseUnitLabel: "1扎X3箱X12包X350克",
purchaseUnitLabel: "1箱X20個",
receivedQtyPurchase: 0,
receivedQtyInventory: 0,
inventoryUnitLabel: "瓶",
inventoryUnitLabel: "個",
},
] as const;

+ 2
- 2
src/components/ProductionProcess/ProductionProcessDetail.tsx Целия файл

@@ -315,7 +315,7 @@ const fetchProcessDetailRef = useRef<() => Promise<void>>();
const processQrCode = useCallback((qrValue: string, lineId: number) => {
// 设备快捷格式:{2fitesteXXX} - XXX 直接作为设备代码
// 格式:{2fitesteXXX} = equipmentCode: "XXX"
// 例如:{2fiteste包裝機類-真空八爪魚機-1號} = equipmentCode: "包裝機類-真空八爪魚機-1號"
// 例如:{2fitesteDEMO-EQ-01} = equipmentCode: "DEMO-EQ-01"
if (qrValue.match(/\{2fiteste(.+)\}/)) {
const match = qrValue.match(/\{2fiteste(.+)\}/);
const equipmentCode = match![1];
@@ -371,7 +371,7 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
// 可能是员工编号
setScannedStaffNo(trimmedValue);
} else if (trimmedValue.includes("-")) {
// 可能包含 "-" 的是设备代码(如 "包裝機類-真空八爪魚機-1號")
// 可能包含 "-" 的是设备代码(如 "DEMO-EQ-01")
setScannedEquipmentCode(trimmedValue);
}
}


+ 11
- 2911
src/components/RoughScheduleDetail/TempRecords.tsx
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 39
- 72
src/config/reportConfig.ts Целия файл

@@ -33,6 +33,9 @@ export interface ReportField {

export type ReportResponseType = 'pdf' | 'excel';

/** Live-system reports. The demo page shows them greyed out. */
export const DEMO_UNAVAILABLE_REPORT_IDS = new Set(["rep-015"]);

export interface ReportDefinition {
id: string;
title: string;
@@ -54,9 +57,20 @@ const asyncItemCodeField = (
multiple: true,
allowInput: true,
asyncSearch: true,
placeholder: "e.g. FA0591",
placeholder: "e.g. DEMO-FG-01",
});

const floorOptionsAll: ReportField["options"] = [
{ label: "全部", value: "All" },
{ label: "2/F", value: "2/F" },
{ label: "4/F", value: "4/F" },
];

const floorOptions: ReportField["options"] = [
{ label: "2/F", value: "2/F" },
{ label: "4/F", value: "4/F" },
];

const pasteItemCodeField = (): ReportField => ({
label: "貼上貨品編號 Paste Item Codes",
name: "itemCodePaste",
@@ -124,29 +138,12 @@ export const REPORTS: ReportDefinition[] = [
name: "storeId",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "1F", value: "1F" },
{ label: "2F", value: "2F" },
{ label: "3F", value: "3F" },
{ label: "4F", value: "4F" },
],
options: floorOptionsAll,
},
{ label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. W201" },
{ label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. DEMO-A" },
{ label: "區域 Area", name: "area", type: "text", required: false, placeholder: "e.g. #A" },
{ label: "儲位 Slot", name: "slot", type: "text", required: false, placeholder: "e.g. 01" },
{ label: "批號 Lot No", name: "lotNo", type: "text", required: false, placeholder: "e.g. LT-202608" },
{
label: "PP/PF 分類",
name: "poPrefix",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "PP", value: "PP" },
{ label: "PF", value: "PF" },
],
},
]
},
{
@@ -193,13 +190,7 @@ export const REPORTS: ReportDefinition[] = [
name: "store_id",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "1F", value: "1F" },
{ label: "2F", value: "2F" },
{ label: "3F", value: "3F" },
{ label: "4F", value: "4F" }
],
options: floorOptionsAll,
},
{
label: "狀態",
@@ -220,14 +211,11 @@ export const REPORTS: ReportDefinition[] = [
multiple: true,
options: [
{ label: "全部", value: "All" },
{ label: "PP", value: "PP" },
{ label: "PF", value: "PF" },
{ label: "TOA", value: "TOA" },
{ label: "工廠生產", value: "工廠生產" },
{ label: "倉存調整", value: "倉存調整" },
{ label: "期初存貨", value: "期初存貨" },
{ label: "採購入倉", value: "採購入倉" },
{ label: "其他入倉", value: "其他入倉" },
{ label: "採購", value: "purchase" },
{ label: "生產", value: "production" },
{ label: "倉存調整", value: "adjustment" },
{ label: "期初存貨", value: "opening" },
{ label: "其他", value: "other" },
],
},
]
@@ -241,19 +229,13 @@ export const REPORTS: ReportDefinition[] = [
fields: [
{ label: "庫存日期 Stock Date(僅今天)", name: "stockDate", type: "date", required: true, disabled: true },
{ label: "貨品編號 Item Code", name: "itemCode", type: "text", required: false },
{ label: "倉位 Warehouse Code", name: "warehouseCode", type: "text", required: false, placeholder: "e.g. W200 or 2F-W200-#A-00" },
{ label: "倉位 Warehouse Code", name: "warehouseCode", type: "text", required: false, placeholder: "e.g. DEMO-A" },
{
label: "樓層 Store ID",
name: "storeId",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "1F", value: "1F" },
{ label: "2F", value: "2F" },
{ label: "3F", value: "3F" },
{ label: "4F", value: "4F" },
],
options: floorOptionsAll,
},
{ label: "批號 Lot No", name: "lotNo", type: "text", required: false, placeholder: "e.g. LT-202608" },
],
@@ -271,15 +253,9 @@ export const REPORTS: ReportDefinition[] = [
name: "storeId",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "1F", value: "1F" },
{ label: "2F", value: "2F" },
{ label: "3F", value: "3F" },
{ label: "4F", value: "4F" },
],
options: floorOptionsAll,
},
{ label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. W201" },
{ label: "倉庫 Warehouse", name: "warehouse", type: "text", required: false, placeholder: "e.g. DEMO-A" },
{ label: "區域 Area", name: "area", type: "text", required: false, placeholder: "e.g. #A" },
{ label: "儲位 Slot", name: "slot", type: "text", required: false, placeholder: "e.g. 02" },
{
@@ -299,8 +275,8 @@ export const REPORTS: ReportDefinition[] = [
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "PP", value: "PP" },
{ label: "PF", value: "PF" },
{ label: "採購", value: "purchase" },
{ label: "生產", value: "production" },
{ label: "其他", value: "other" },
],
},
@@ -442,12 +418,10 @@ export const REPORTS: ReportDefinition[] = [
{ label: "類別 Category", name: "stockCategory", type: "select", required: false,
multiple: true,
options: [
{ label: "All", value: "All" },
{ label: "MAT", value: "MAT" },
{ label: "WIP", value: "WIP" },
{ label: "NM", value: "NM" },
{ label: "FG", value: "FG" },
{ label: "CMB", value: "CMB" }
{ label: "全部", value: "All" },
{ label: "原料", value: "mat" },
{ label: "半成品", value: "sfg" },
{ label: "成品", value: "fg" },
] },
{ label: "貨品編號 Item Code", name: "itemCode", type: "select", required: false,
multiple: true,
@@ -472,9 +446,9 @@ export const REPORTS: ReportDefinition[] = [
{ label: "類別 Category", name: "stockCategory", type: "select", required: false,
multiple: true,
options: [
{ label: "All", value: "All" },
{ label: "WIP", value: "WIP" },
{ label: "FG", value: "FG" },
{ label: "全部", value: "All" },
{ label: "半成品", value: "sfg" },
{ label: "成品", value: "fg" },
] },
{ label: "貨品編號 Item Code", name: "itemCode", type: "select", required: false,
multiple: true,
@@ -488,7 +462,7 @@ export const REPORTS: ReportDefinition[] = [
},
{
id: "rep-015",
title: "M18 BOM Shop 同步記錄",
title: "BOM Shop 同步記錄",
apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`,
responseType: "excel",
fields: [
@@ -533,10 +507,7 @@ export const REPORTS: ReportDefinition[] = [
name: "storeId",
type: "select",
required: false,
options: [
{ label: "2F", value: "2F" },
{ label: "4F", value: "4F" },
],
options: floorOptions,
},
],
},
@@ -574,11 +545,7 @@ export const REPORTS: ReportDefinition[] = [
name: "storeId",
type: "select",
required: false,
options: [
{ label: "全部", value: "All" },
{ label: "2F", value: "2F" },
{ label: "4F", value: "4F" },
],
options: floorOptionsAll,
},
],
},

+ 6
- 6
src/i18n/en/deliveryOrderFloor.json Целия файл

@@ -1,12 +1,12 @@
{
"title": "Delivery order / workbench floor (supplier codes)",
"Intro": "Manage supplier codes assigned to each floor. Use the edit button to add or remove suppliers from a floor.",
"2F supplier": "2F supplier",
"4F supplier": "4F supplier",
"Edit 2F": "Edit 2F",
"Edit 4F": "Edit 4F",
"Edit 2F title": "Edit 2F supplier codes",
"Edit 4F title": "Edit 4F supplier codes",
"2F supplier": "2/F supplier",
"4F supplier": "4/F supplier",
"Edit 2F": "Edit 2/F",
"Edit 4F": "Edit 4/F",
"Edit 2F title": "Edit 2/F supplier codes",
"Edit 4F title": "Edit 4/F supplier codes",
"Edit dialog title": "Edit floor–supplier mapping",
"Floor label": "Floor",
"Add mapping": "Add mapping",


+ 2
- 2
src/i18n/en/m18Sync.json Целия файл

@@ -20,10 +20,10 @@
"btnSyncDoExtra": "Sync DO (Extra) from M18",
"btnSyncProduct": "Sync product from M18",
"syncing": "Syncing...",
"placeholderPoCode": "e.g. PFP002PO26030341",
"placeholderPoCode": "e.g. DEMO-PO-001",
"placeholderDoCode": "e.g. same document code as M18 shop PO",
"placeholderDoExtraCode": "Enter multiple codes separated by comma or newline",
"placeholderProductCode": "e.g. PP1175 (M18 item code)",
"placeholderProductCode": "e.g. DEMO-FG-01",
"waiting": "Waiting for implementation...",
"noResponse": "No response received",
"alreadyExists": "{{docName}} already exists in system",


+ 26
- 23
src/i18n/en/report.json Целия файл

@@ -15,6 +15,7 @@
"noDataFoundTitle": "No Data Found / 查無資料",
"noDataFoundHint": "No inventory records match your search. Please try adjusting the filters.",
"ok": "OK",
"unavailableInDemo": "Unavailable in demo mode",
"missingRequired": "Missing required fields:\n- {{fields}}",
"requiredField": "This is a required field",
"dateNotBeforeToday": "Date cannot be before today:\n- {{fields}}",
@@ -57,17 +58,11 @@
"warehouse": "Warehouse",
"area": "Area",
"slot": "Slot",
"lotNo": "Lot No",
"poPrefix": "PP/PF"
"lotNo": "Lot No"
},
"options": {
"storeId": {
"All": "All"
},
"poPrefix": {
"All": "All",
"PP": "PP",
"PF": "PF"
}
},
"fieldHints": {
@@ -107,14 +102,11 @@
},
"type": {
"All": "All",
"PP": "PP",
"PF": "PF",
"TOA": "TOA",
"工廠生產": "Factory production",
"倉存調整": "Inventory adjustment",
"期初存貨": "Opening inventory",
"採購入倉": "Purchase inbound",
"其他入倉": "Other inbound"
"purchase": "Purchase",
"production": "Production",
"adjustment": "Inventory adjustment",
"opening": "Opening inventory",
"other": "Other"
}
}
},
@@ -208,6 +200,14 @@
},
"fieldHints": {
"itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines"
},
"options": {
"stockCategory": {
"All": "All",
"mat": "Material",
"sfg": "Semi-finished",
"fg": "Finished goods"
}
}
},
"rep-005": {
@@ -222,10 +222,17 @@
},
"fieldHints": {
"itemCodePaste": "Paste item codes from Excel. Separate with spaces, commas, or new lines"
},
"options": {
"stockCategory": {
"All": "All",
"sfg": "Semi-finished",
"fg": "Finished goods"
}
}
},
"rep-015": {
"title": "M18 BOM Shop Sync History",
"title": "BOM Shop Sync History",
"fields": {
"syncDateStart": "Sync Date Start",
"syncDateEnd": "Sync Date End",
@@ -295,8 +302,8 @@
},
"lotOrigin": {
"All": "All",
"PP": "PP",
"PF": "PF",
"purchase": "Purchase",
"production": "Production",
"other": "Other"
}
}
@@ -326,13 +333,9 @@
"unitPrice": "Unit Price",
"currency": "Currency",
"amount": "Amount",
"grnCode": "GRN Code / M18 Receipt No.",
"grnId": "GRN Id / M18 Record Id",
"poCreator": "PO creator (M18)",
"note": "Note",
"category": "Category",
"totalAmount": "Total Amount",
"grnCodes": "GRN Code(s) / M18 Receipt No."
"totalAmount": "Total Amount"
},
"bomSync": {
"sheetSync": "BOM Sync Log",


+ 6
- 6
src/i18n/zh/deliveryOrderFloor.json Целия файл

@@ -1,12 +1,12 @@
{
"title": "送貨單樓層設定(供應商代碼)",
"Intro": "管理各樓層的供應商代碼。點擊編輯按鈕可新增或移除供應商。",
"2F supplier": "2F 供應商",
"4F supplier": "4F 供應商",
"Edit 2F": "編輯 2F",
"Edit 4F": "編輯 4F",
"Edit 2F title": "編輯 2F 供應商代碼",
"Edit 4F title": "編輯 4F 供應商代碼",
"2F supplier": "2/F 供應商",
"4F supplier": "4/F 供應商",
"Edit 2F": "編輯 2/F",
"Edit 4F": "編輯 4/F",
"Edit 2F title": "編輯 2/F 供應商代碼",
"Edit 4F title": "編輯 4/F 供應商代碼",
"Edit dialog title": "編輯樓層供應商映射",
"Floor label": "樓層",
"Add mapping": "新增映射",


+ 1
- 1
src/i18n/zh/laserPrint.json Целия файл

@@ -1,3 +1,3 @@
{
"title": "檸檬機(激光機)"
"title": "激光機"
}

+ 2
- 2
src/i18n/zh/m18Sync.json Целия файл

@@ -20,10 +20,10 @@
"btnSyncDoExtra": "從 M18 同步送貨訂單(加單)",
"btnSyncProduct": "從 M18 同步貨品",
"syncing": "同步中…",
"placeholderPoCode": "例:PFP002PO26030341",
"placeholderPoCode": "例:DEMO-PO-001",
"placeholderDoCode": "例:與 M18 店鋪訂單相同的文件編號",
"placeholderDoExtraCode": "可輸入多個 code,用逗號或換行分隔",
"placeholderProductCode": "例:PP1175(M18 貨品編號)",
"placeholderProductCode": "例:DEMO-FG-01",
"waiting": "等待實作…",
"noResponse": "未收到回應",
"alreadyExists": "{{docName}}已存在系統",


+ 2
- 2
src/i18n/zh/navigation.json Целия файл

@@ -25,7 +25,7 @@
"nav.breadcrumb.joEdit": "工單詳情",
"nav.breadcrumb.joTesting": "工單測試",
"nav.breadcrumb.joWorkbench": "工單工作台",
"nav.breadcrumb.laserPrint": "檸檬機(激光機)",
"nav.breadcrumb.laserPrint": "激光機",
"nav.breadcrumb.m18Sync": "M18 同步",
"nav.breadcrumb.poEdit": "編輯",
"nav.breadcrumb.poWorkbench": "採購單工作台",
@@ -64,7 +64,7 @@
"nav.jobOrder.productionProcess": "工單生產流程",
"nav.jobOrder.searchCreate": "搜索工單/ 建立工單",
"nav.jobOrderManagement": "管理工單",
"nav.laserPrint": "檸檬機(激光機)",
"nav.laserPrint": "激光機",
"nav.m18Sync": "M18 同步",
"nav.productionServer": "正式伺服器",
"nav.report": "報告管理",


+ 26
- 23
src/i18n/zh/report.json Целия файл

@@ -15,6 +15,7 @@
"noDataFoundTitle": "查無資料",
"noDataFoundHint": "找不到符合您搜尋條件的庫存資料,請嘗試調整篩選條件。",
"ok": "確定",
"unavailableInDemo": "示範模式無法使用",
"missingRequired": "缺少必填條件:\n- {{fields}}",
"requiredField": "此為必填欄位",
"dateNotBeforeToday": "日期不可早於今天:\n- {{fields}}",
@@ -57,17 +58,11 @@
"warehouse": "倉庫",
"area": "區域",
"slot": "儲位",
"lotNo": "批號",
"poPrefix": "PP/PF 分類"
"lotNo": "批號"
},
"options": {
"storeId": {
"All": "全部"
},
"poPrefix": {
"All": "全部",
"PP": "PP",
"PF": "PF"
}
},
"fieldHints": {
@@ -107,14 +102,11 @@
},
"type": {
"All": "全部",
"PP": "PP",
"PF": "PF",
"TOA": "TOA",
"工廠生產": "工廠生產",
"倉存調整": "倉存調整",
"期初存貨": "期初存貨",
"採購入倉": "採購入倉",
"其他入倉": "其他入倉"
"purchase": "採購",
"production": "生產",
"adjustment": "倉存調整",
"opening": "期初存貨",
"other": "其他"
}
}
},
@@ -208,6 +200,14 @@
},
"fieldHints": {
"itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔"
},
"options": {
"stockCategory": {
"All": "全部",
"mat": "原料",
"sfg": "半成品",
"fg": "成品"
}
}
},
"rep-005": {
@@ -222,10 +222,17 @@
},
"fieldHints": {
"itemCodePaste": "可從 Excel 貼上多個貨品編號,以空格、逗號或換行分隔"
},
"options": {
"stockCategory": {
"All": "全部",
"sfg": "半成品",
"fg": "成品"
}
}
},
"rep-015": {
"title": "M18 BOM Shop 同步記錄",
"title": "BOM Shop 同步記錄",
"fields": {
"syncDateStart": "同步日期:由",
"syncDateEnd": "同步日期:至",
@@ -295,8 +302,8 @@
},
"lotOrigin": {
"All": "全部",
"PP": "PP",
"PF": "PF",
"purchase": "採購",
"production": "生產",
"other": "其他"
}
}
@@ -326,13 +333,9 @@
"unitPrice": "單價",
"currency": "貨幣",
"amount": "金額",
"grnCode": "M18 入倉單號",
"grnId": "M18 記錄編號",
"poCreator": "PO建立者(M18)",
"note": "備註",
"category": "類別",
"totalAmount": "金額",
"grnCodes": "M18 入倉單號"
"totalAmount": "金額"
},
"bomSync": {
"sheetSync": "BOM同步記錄",


Зареждане…
Отказ
Запис