"use client"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; import { exportChartToXlsx } from "@/app/(main)/chart/_components/exportChartToXlsx"; import { reportExcelT as tx } from "./reportI18n"; import type { TFunction } from "i18next"; export interface ShopOrderReplenishmentReportRow { shopNo?: string; shopName?: string; shopOrderDate?: string; shopOrderNo?: string; itemNo?: string; itemName?: string; firstOrderQty?: number | string; firstOrderActualPickQty?: number | string; firstOrderPickerHandler?: string; reorderQty?: number | string; reorderDate?: string; reason?: string; actualDeliveredQty?: number | string; actualDeliveredHandler?: string; deliveredDate?: string; [key: string]: unknown; } export interface ShopOrderReplenishmentReportResponse { rows: ShopOrderReplenishmentReportRow[]; } function shopReplenishmentLabels(t?: TFunction) { return { sheetName: tx(t, "excel.shopReplenishment.sheetName", "店鋪訂單補貨記錄"), noData: tx(t, "excel.noData", "(篩選範圍內無資料)"), shopCode: tx(t, "excel.shopReplenishment.shopCode", "店鋪編號"), shopName: tx(t, "excel.shopReplenishment.shopName", "店鋪名稱"), shopOrderDate: tx(t, "excel.shopReplenishment.shopOrderDate", "店鋪訂單日期"), shopOrderNo: tx(t, "excel.shopReplenishment.shopOrderNo", "店鋪訂單編號"), itemCode: tx(t, "excel.shopReplenishment.itemCode", "貨品編號"), itemName: tx(t, "excel.shopReplenishment.itemName", "貨品名稱"), firstOrderQty: tx(t, "excel.shopReplenishment.firstOrderQty", "原訂單數量"), firstOrderActualPickQty: tx( t, "excel.shopReplenishment.firstOrderActualPickQty", "原單實際提料數量", ), firstOrderPicker: tx(t, "excel.shopReplenishment.firstOrderPicker", "原單提料人"), reorderQty: tx(t, "excel.shopReplenishment.reorderQty", "補貨數量"), reorderDate: tx(t, "excel.shopReplenishment.reorderDate", "補貨日期"), reason: tx(t, "excel.shopReplenishment.reason", "補貨原因"), actualDeliveredQty: tx(t, "excel.shopReplenishment.actualDeliveredQty", "實際補貨數量"), actualDeliveredHandler: tx( t, "excel.shopReplenishment.actualDeliveredHandler", "實際補貨提料人", ), deliveredDate: tx(t, "excel.shopReplenishment.deliveredDate", "送貨日期"), reasonQuality: tx(t, "excel.shopReplenishment.reasonQuality", "質素問題"), reasonOutOfStock: tx(t, "excel.shopReplenishment.reasonOutOfStock", "缺貨"), reasonOther: tx(t, "excel.shopReplenishment.reasonOther", "其他"), }; } type ShopReplenishmentLabels = ReturnType; function emptySheetRow( L: ShopReplenishmentLabels, note?: string, ): Record { return { [L.shopCode]: note ?? L.noData, [L.shopName]: "", [L.shopOrderDate]: "", [L.shopOrderNo]: "", [L.itemCode]: "", [L.itemName]: "", [L.firstOrderQty]: "", [L.firstOrderActualPickQty]: "", [L.firstOrderPicker]: "", [L.reorderQty]: "", [L.reorderDate]: "", [L.reason]: "", [L.actualDeliveredQty]: "", [L.actualDeliveredHandler]: "", [L.deliveredDate]: "", }; } function formatReason( reason: string | undefined, L: ShopReplenishmentLabels, ): string { switch ((reason ?? "").trim()) { case "quality_issue": return L.reasonQuality; case "out_of_stock": return L.reasonOutOfStock; case "other": return L.reasonOther; default: return reason ?? ""; } } function formatDateCell(value: unknown): string { if (value == null || value === "") return ""; if (typeof value === "number" && Number.isFinite(value)) { // Jackson may serialize java.sql.Date as epoch millis or seconds const ms = value > 1e12 ? value : value * 1000; const d = new Date(ms); if (Number.isNaN(d.getTime())) return String(value); return d.toISOString().slice(0, 10); } const s = String(value).trim(); if (/^\d{10,13}$/.test(s)) { const n = Number(s); const ms = s.length >= 13 ? n : n * 1000; const d = new Date(ms); if (!Number.isNaN(d.getTime())) return d.toISOString().slice(0, 10); } // JDBC / string date: "2026-08-01" or "2026-08-01 00:00:00.0" return s.length >= 10 ? s.slice(0, 10) : s; } function formatQty(value: unknown): string | number { if (value === null || value === undefined || value === "") return ""; const n = typeof value === "number" ? value : Number(value); if (!Number.isFinite(n)) return String(value); return n; } function toExcelRow( r: ShopOrderReplenishmentReportRow, L: ShopReplenishmentLabels, ): Record { return { ...emptySheetRow(L, ""), [L.shopCode]: r.shopNo ?? "", [L.shopName]: r.shopName ?? "", [L.shopOrderDate]: formatDateCell(r.shopOrderDate), [L.shopOrderNo]: r.shopOrderNo ?? "", [L.itemCode]: r.itemNo ?? "", [L.itemName]: r.itemName ?? "", [L.firstOrderQty]: formatQty(r.firstOrderQty), [L.firstOrderActualPickQty]: formatQty(r.firstOrderActualPickQty), [L.firstOrderPicker]: r.firstOrderPickerHandler ?? "", [L.reorderQty]: formatQty(r.reorderQty), [L.reorderDate]: formatDateCell(r.reorderDate), [L.reason]: formatReason(r.reason, L), [L.actualDeliveredQty]: formatQty(r.actualDeliveredQty), [L.actualDeliveredHandler]: r.actualDeliveredHandler ?? "", [L.deliveredDate]: formatDateCell(r.deliveredDate), }; } export async function fetchShopOrderReplenishmentReportData( criteria: Record, ): Promise { const queryParams = new URLSearchParams(criteria).toString(); const url = `${NEXT_PUBLIC_API_URL}/report/shop-order-replenishment?${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}`); const data = (await response.json()) as | ShopOrderReplenishmentReportResponse | ShopOrderReplenishmentReportRow[]; if (Array.isArray(data)) return data; return data.rows ?? []; } /** * FP-MTMS Version Checklist | Functions Ref. No. 49 | v1.0.1 | 2026-08-10 * Generate and download Shop Orders Replenishment Records as Excel. */ export async function generateShopOrderReplenishmentReportExcel( criteria: Record, reportTitle: string = "店鋪訂單補貨記錄", t?: TFunction, ): Promise { const L = shopReplenishmentLabels(t); const rows = await fetchShopOrderReplenishmentReportData(criteria); const excelRows = rows.length > 0 ? rows.map((r) => toExcelRow(r, L)) : [emptySheetRow(L)]; const dateCandidates = [ criteria.reorderDateStart, criteria.reorderDateEnd, criteria.shopOrderDateStart, criteria.shopOrderDateEnd, criteria.deliveredDateStart, criteria.deliveredDateEnd, ].filter((v) => (v ?? "").trim().length > 0); const datePart = dateCandidates.length > 0 ? dateCandidates.join("_") : new Date().toISOString().slice(0, 10); const safeDatePart = datePart.replace(/[^\d\-_/]/g, ""); const filename = `${reportTitle}_${safeDatePart}`; exportChartToXlsx(excelRows, filename, L.sheetName); }