Explorar el Código

added a report to see the bom sync history

production
[email protected] hace 1 semana
padre
commit
dd3b55711d
Se han modificado 5 ficheros con 233 adiciones y 2 borrados
  1. +1
    -0
      src/app/(main)/report/ReportSelectionDashboard.tsx
  2. +205
    -0
      src/app/(main)/report/bomShopSyncReportApi.ts
  3. +3
    -0
      src/app/(main)/report/page.tsx
  4. +1
    -1
      src/app/(main)/report/reportCategories.ts
  5. +23
    -1
      src/config/reportConfig.ts

+ 1
- 0
src/app/(main)/report/ReportSelectionDashboard.tsx Ver fichero

@@ -32,6 +32,7 @@ const REPORT_ICON_MAP: Record<string, SvgIconComponent> = {
"rep-013": LocalShippingOutlinedIcon,
"rep-006": BarChartOutlinedIcon,
"rep-005": PieChartOutlineOutlinedIcon,
"rep-015": LayersOutlinedIcon,
};

const reportById = Object.fromEntries(REPORTS.map((r) => [r.id, r]));


+ 205
- 0
src/app/(main)/report/bomShopSyncReportApi.ts Ver fichero

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

import { NEXT_PUBLIC_API_URL } from "@/config/api";
import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
import {
exportMultiSheetToXlsx,
} from "@/app/(main)/chart/_components/exportChartToXlsx";

export interface BomShopSyncReportSummary {
totalAttempts?: number;
success?: number;
skippedUnchanged?: number;
failed?: number;
syncDateStart?: string;
syncDateEnd?: string;
}

export interface BomShopSyncRow {
syncLogId?: number;
syncDateTime?: string;
bomId?: number;
bomRoutingCode?: string;
finishedItemCode?: string;
finishedItemName?: string;
m18HeaderCode?: string;
version?: string;
m18RecordId?: number;
syncStatus?: string;
synced?: boolean;
m18ApiStatus?: boolean;
failureReason?: string;
message?: string;
}

export interface BomShopSyncMaterialRow {
syncLogId?: number;
syncDateTime?: string;
bomId?: number;
finishedItemCode?: string;
m18HeaderCode?: string;
version?: string;
syncStatus?: string;
lineNo?: string;
materialName?: string;
udfProductM18Id?: number;
udfBaseUnit?: string;
udfQty?: number;
udfSupplierM18Id?: number;
udfPurchaseUnitM18Id?: number;
}

export interface BomShopSyncReportResponse {
summary?: BomShopSyncReportSummary;
syncRows?: BomShopSyncRow[];
materialRows?: BomShopSyncMaterialRow[];
}

const SHEET_SYNC = "BOM同步記錄";
const SHEET_MATERIALS = "BOM物料明細";

const NO_DATA_NOTE =
"(篩選範圍內無資料 / No records in the selected range)";

/** Column keys for sheet 1 — used for headers when there are no data rows. */
function emptySyncSheetRow(note: string = NO_DATA_NOTE): Record<string, unknown> {
return {
同步時間: note,
成品貨號: "",
成品名稱: "",
BOM路由編號: "",
"M18 BOM Code": "",
版本: "",
"M18 Record Id": "",
狀態: "",
失敗原因: "",
訊息: "",
"BOM Id": "",
"Sync Log Id": "",
};
}

/** Column keys for sheet 2 — used for headers when there are no data rows. */
function emptyMaterialSheetRow(note: string = NO_DATA_NOTE): Record<string, unknown> {
return {
同步時間: note,
成品貨號: "",
"M18 BOM Code": "",
版本: "",
狀態: "",
行號: "",
物料名稱: "",
"M18 Product Id": "",
單位: "",
用量: "",
"M18 Supplier Id": "",
"M18 Purchase Unit Id": "",
"Sync Log Id": "",
};
}

export async function fetchBomShopSyncReportData(
criteria: Record<string, string>,
): Promise<BomShopSyncReportResponse> {
const queryParams = new URLSearchParams(criteria).toString();
const url = `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history?${queryParams}`;

const response = await clientAuthFetch(url, {
method: "GET",
headers: { Accept: "application/json" },
});

if (response.status === 401 || response.status === 403)
throw new Error("Unauthorized");
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);

return (await response.json()) as BomShopSyncReportResponse;
}

function syncStatusLabel(status: string | undefined): string {
switch (status) {
case "SUCCESS":
return "成功";
case "SKIPPED_UNCHANGED":
return "略過(內容未變)";
case "FAILED":
return "失敗";
default:
return status ?? "";
}
}

function toSyncExcelRow(r: BomShopSyncRow): Record<string, unknown> {
const base = emptySyncSheetRow("");
return {
...base,
同步時間: r.syncDateTime ?? "",
成品貨號: r.finishedItemCode ?? "",
成品名稱: r.finishedItemName ?? "",
BOM路由編號: r.bomRoutingCode ?? "",
"M18 BOM Code": r.m18HeaderCode ?? "",
版本: r.version ?? "",
"M18 Record Id": r.m18RecordId ?? "",
狀態: syncStatusLabel(r.syncStatus),
失敗原因: r.failureReason ?? "",
訊息: r.message ?? "",
"BOM Id": r.bomId ?? "",
"Sync Log Id": r.syncLogId ?? "",
};
}

function toMaterialExcelRow(r: BomShopSyncMaterialRow): Record<string, unknown> {
const base = emptyMaterialSheetRow("");
return {
...base,
同步時間: r.syncDateTime ?? "",
成品貨號: r.finishedItemCode ?? "",
"M18 BOM Code": r.m18HeaderCode ?? "",
版本: r.version ?? "",
狀態: syncStatusLabel(r.syncStatus),
行號: r.lineNo ?? "",
物料名稱: r.materialName ?? "",
"M18 Product Id": r.udfProductM18Id ?? "",
單位: r.udfBaseUnit ?? "",
用量: r.udfQty ?? "",
"M18 Supplier Id": r.udfSupplierM18Id ?? "",
"M18 Purchase Unit Id": r.udfPurchaseUnitM18Id ?? "",
"Sync Log Id": r.syncLogId ?? "",
};
}

export async function generateBomShopSyncReportExcel(
criteria: Record<string, string>,
reportTitle: string = "M18 BOM Shop 同步記錄",
): Promise<void> {
const data = await fetchBomShopSyncReportData(criteria);
const syncRows =
(data.syncRows ?? []).length > 0
? (data.syncRows ?? []).map(toSyncExcelRow)
: [emptySyncSheetRow()];
const materialRows =
(data.materialRows ?? []).length > 0
? (data.materialRows ?? []).map(toMaterialExcelRow)
: [emptyMaterialSheetRow()];

const start = criteria.syncDateStart;
const end = criteria.syncDateEnd;
let datePart: string;
if (start && end && start === end) {
datePart = start;
} else if (start || end) {
datePart = `${start || ""}_to_${end || ""}`;
} else {
datePart = new Date().toISOString().slice(0, 10);
}
const filename = `${reportTitle}_${datePart.replace(/[^\d\-_/]/g, "")}`;

exportMultiSheetToXlsx(
[
{ name: SHEET_SYNC, rows: syncRows },
{ name: SHEET_MATERIALS, rows: materialRows },
],
filename,
);
}

+ 3
- 0
src/app/(main)/report/page.tsx Ver fichero

@@ -30,6 +30,7 @@ import {
fetchSemiFGItemCodesWithCategory
} from './semiFGProductionAnalysisApi';
import { generateGrnReportExcel } from './grnReportApi';
import { generateBomShopSyncReportExcel } from './bomShopSyncReportApi';
import {
FEATURE_USAGE,
FEATURE_USAGE_ACTION,
@@ -261,6 +262,8 @@ export default function ReportPage() {
currentReport.title,
includeGrnFinancialColumns
);
} else if (currentReport.id === 'rep-015') {
await generateBomShopSyncReportExcel(criteria, currentReport.title);
} else {
// Backend returns actual .xlsx bytes for this Excel endpoint.
const queryParams =


+ 1
- 1
src/app/(main)/report/reportCategories.ts Ver fichero

@@ -33,6 +33,6 @@ export const REPORT_CATEGORIES: ReportCategoryConfig[] = [
headerBg: "#f5d4a8",
bodyBg: "#fdf6ec",
accent: "#e65100",
reportIds: ["rep-006", "rep-005"],
reportIds: ["rep-006", "rep-005", "rep-015"],
},
];

+ 23
- 1
src/config/reportConfig.ts Ver fichero

@@ -290,5 +290,27 @@ export const REPORTS: ReportDefinition[] = [
dynamicOptionsParam: "stockCategory",
options: [] },
]
}
},
{
id: "rep-015",
title: "M18 BOM Shop 同步記錄",
apiEndpoint: `${NEXT_PUBLIC_API_URL}/report/bom-shop-sync-history`,
responseType: "excel",
fields: [
{ label: "同步日期:由 Sync Date Start", name: "syncDateStart", type: "date", required: false },
{ label: "同步日期:至 Sync Date End", name: "syncDateEnd", type: "date", required: false },
{ label: "成品貨號 Finished Item Code", name: "finishedItemCode", type: "text", required: false },
{
label: "同步狀態 Sync Status",
name: "syncStatus",
type: "select",
required: false,
options: [
{ label: "全部 All", value: "all" },
{ label: "成功 Success", value: "success" },
{ label: "失敗 Failed", value: "failed" },
],
},
],
},
]

Cargando…
Cancelar
Guardar