|
- "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<typeof shopReplenishmentLabels>;
-
- function emptySheetRow(
- L: ShopReplenishmentLabels,
- note?: string,
- ): Record<string, unknown> {
- 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<string, unknown> {
- 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<string, string>,
- ): Promise<ShopOrderReplenishmentReportRow[]> {
- 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<string, string>,
- reportTitle: string = "店鋪訂單補貨記錄",
- t?: TFunction,
- ): Promise<void> {
- 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);
- }
|