|
|
|
@@ -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, |
|
|
|
); |
|
|
|
} |