FPSMS-frontend
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 

100 linhas
3.2 KiB

  1. "use client";
  2. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  3. import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
  4. import { exportChartToXlsx } from "@/app/(main)/chart/_components/exportChartToXlsx";
  5. export interface GrnReportRow {
  6. poCode?: string;
  7. deliveryNoteNo?: string;
  8. receiptDate?: string;
  9. itemCode?: string;
  10. itemName?: string;
  11. acceptedQty?: number;
  12. receivedQty?: number;
  13. demandQty?: number;
  14. uom?: string;
  15. purchaseUomDesc?: string;
  16. stockUomDesc?: string;
  17. productLotNo?: string;
  18. expiryDate?: string;
  19. supplierCode?: string;
  20. supplier?: string;
  21. status?: string;
  22. grnId?: number | string;
  23. [key: string]: unknown;
  24. }
  25. export interface GrnReportResponse {
  26. rows: GrnReportRow[];
  27. }
  28. /**
  29. * Fetch GRN (Goods Received Note) report data by date range.
  30. * Backend: GET /report/grn-report?receiptDateStart=&receiptDateEnd=&itemCode=
  31. */
  32. export async function fetchGrnReportData(
  33. criteria: Record<string, string>
  34. ): Promise<GrnReportRow[]> {
  35. const queryParams = new URLSearchParams(criteria).toString();
  36. const url = `${NEXT_PUBLIC_API_URL}/report/grn-report?${queryParams}`;
  37. const response = await clientAuthFetch(url, {
  38. method: "GET",
  39. headers: { Accept: "application/json" },
  40. });
  41. if (response.status === 401 || response.status === 403)
  42. throw new Error("Unauthorized");
  43. if (!response.ok)
  44. throw new Error(`HTTP error! status: ${response.status}`);
  45. const data = (await response.json()) as GrnReportResponse | GrnReportRow[];
  46. const rows = Array.isArray(data) ? data : (data as GrnReportResponse).rows ?? [];
  47. return rows;
  48. }
  49. /** Excel column headers (bilingual) for GRN report */
  50. function toExcelRow(r: GrnReportRow): Record<string, string | number | undefined> {
  51. return {
  52. "PO No. / 訂單編號": r.poCode ?? "",
  53. "Supplier Code / 供應商編號": r.supplierCode ?? "",
  54. "Delivery Note No. / 送貨單編號": r.deliveryNoteNo ?? "",
  55. "Receipt Date / 收貨日期": r.receiptDate ?? "",
  56. "Item Code / 物料編號": r.itemCode ?? "",
  57. "Item Name / 物料名稱": r.itemName ?? "",
  58. "Qty / 數量": r.acceptedQty ?? r.receivedQty ?? "",
  59. "Demand Qty / 訂單數量": r.demandQty ?? "",
  60. "UOM / 單位": r.uom ?? r.purchaseUomDesc ?? r.stockUomDesc ?? "",
  61. "Product Lot No. / 批次": r.productLotNo ?? "",
  62. "Expiry Date / 到期日": r.expiryDate ?? "",
  63. "Supplier / 供應商": r.supplier ?? "",
  64. "Status / 狀態": r.status ?? "",
  65. "GRN Id / M18 單號": r.grnId ?? "",
  66. };
  67. }
  68. /**
  69. * Generate and download GRN report as Excel.
  70. */
  71. export async function generateGrnReportExcel(
  72. criteria: Record<string, string>,
  73. reportTitle: string = "PO 入倉記錄"
  74. ): Promise<void> {
  75. const rows = await fetchGrnReportData(criteria);
  76. const excelRows = rows.map(toExcelRow);
  77. const start = criteria.receiptDateStart;
  78. const end = criteria.receiptDateEnd;
  79. let datePart: string;
  80. if (start && end && start === end) {
  81. datePart = start;
  82. } else if (start || end) {
  83. datePart = `${start || ""}_to_${end || ""}`;
  84. } else {
  85. datePart = new Date().toISOString().slice(0, 10);
  86. }
  87. const safeDatePart = datePart.replace(/[^\d\-_/]/g, "");
  88. const filename = `${reportTitle}_${safeDatePart}`;
  89. exportChartToXlsx(excelRows, filename, "GRN");
  90. }