# Excel export standard (FPSMS frontend) This document defines how **client-side** `.xlsx` exports should look and behave. **Implementation:** `exportChartToXlsx.ts` and `exportMultiSheetToXlsx()` — use these helpers for new reports so styling stays consistent. ## Scope (important) | Export path | Follows this `.md`? | |-------------|---------------------| | **Next.js** builds the file via `exportChartToXlsx` / `exportMultiSheetToXlsx` (e.g. **PO 入倉記錄** / `rep-014`) | **Yes** — rules are enforced in code. | | **Backend** returns ready-made `.xlsx` or Excel bytes (JasperReports, Apache POI, etc.; most `print-*` report endpoints) | **No — not automatically.** That code does **not** use this TypeScript module. To match the same **look** (grey headers, number formats, alignment), implement equivalent styling in Java/Kotlin or Jasper templates. See the backend companion doc below. | **Backend companion (visual parity):** `FPSMS-backend/docs/EXCEL_EXPORT_STANDARD.md` — same *rules*, for POI/Jasper implementers. --- ## 1. Library | Item | Value | |------|--------| | Package | **`xlsx-js-style`** (not the plain `xlsx` community build) | | Reason | Plain SheetJS **does not persist** cell styles (`fill`, `alignment`, `numFmt`) in the written file. `xlsx-js-style` is a compatible fork that **does**. | --- ## 2. Data shape - Rows are **`Record[]`** (array of plain objects). - **First object’s keys** become the **header row** (column titles). Every row should use the **same keys** in the same order for a rectangular sheet. - Prefer **real JavaScript `number`** values for amounts where possible; the exporter will apply number formats. Strings that look like numbers (e.g. `"1,234.56"`) are parsed for money columns. --- ## 3. Processing order (per sheet) After `json_to_sheet(rows)`: 1. **`!cols`** — column width heuristic (see §4). 2. **`applyHeaderRowStyle`** — header row styling (see §5). 3. **`applyMoneyColumnNumberFormats`** — money columns only, data rows (see §6). 4. **`applyNumericColumnRightAlign`** — money + quantity columns, **all rows including header** (see §7). --- ## 4. Column width (`!cols`) - For each column: `wch = max(12, headerText.length + 4)`. - Adjust if a report needs fixed widths; default keeps bilingual headers readable. --- ## 5. Header row style (row 0) Applied to **every** header cell first; numeric columns get alignment overridden in step 4. | Property | Value | |----------|--------| | Font | Bold, black `rgb: "000000"` | | Fill | Solid, `fgColor: "D9D9D9"` (light grey) | | Alignment (default) | Horizontal **center**, vertical **center**, `wrapText: true` | --- ## 6. Money / amount columns — number format **Detection:** header label matches (case-insensitive): ```text 金額 | 單價 | Amount | Unit Price | Total Amount ``` (Also matches bilingual headers that contain these fragments, e.g. `Amount / 金額`, `Unit Price / 單價`, `Total Amount / 金額`.) **Rules:** - Applies to **data rows only** (not row 0). - Excel format string: **`#,##0.00`** (thousands separator + 2 decimals). Stored on the cell as `z`. - Cell type `t: "n"` for numeric values. - If the cell is a **string**, commas are stripped and the value is parsed to a number when possible. **Naming new reports:** use header text that matches the patterns above so columns pick up formatting automatically. --- ## 7. Quantity columns — alignment only **Detection:** header label matches: ```text Qty | 數量 | Demand ``` (Covers e.g. `Qty / 數量`, `Demand Qty / 訂單數量`.) - No default thousands format (quantities may have up to 4 decimals in app code). - These columns are **right-aligned** together with money columns (see §8). --- ## 8. Alignment — numeric columns (header + data) **Detection:** union of **money** (§6) and **quantity** (§7) header patterns. | Alignment | Value | |-----------|--------| | Horizontal | **`right`** | | Vertical | **`center`** | | `wrapText` | Preserved / defaulted to `true` where applicable | Existing style objects are **merged** (fill, font from header styling are kept). --- ## 9. Multi-sheet workbook | Rule | Detail | |------|--------| | API | `exportMultiSheetToXlsx({ name, rows }[], filename)` | | Sheet name length | Truncated to **31** characters (Excel limit) | | Each sheet | Same pipeline as §3 if `rows.length > 0` | --- ## 10. Empty sheets If `rows.length === 0`, a minimal sheet is written; no header styling pipeline runs. --- ## 11. Reports using this standard today | Feature | Location | |---------|----------| | Chart export | Uses `exportChartToXlsx` | | GRN / PO 入倉記錄 (`rep-014`) | `src/app/(main)/report/grnReportApi.ts` — builds row objects, calls `exportChartToXlsx` / `exportMultiSheetToXlsx` | Most other reports on `/report` download Excel/PDF **generated on the server** (Jasper, etc.). Those **do not** run this TypeScript pipeline; use **`FPSMS-backend/docs/EXCEL_EXPORT_STANDARD.md`** if you want the same visual rules there. --- ## 12. Checklist for new Excel exports 1. Import from **`xlsx-js-style`** only if building sheets manually; otherwise call **`exportChartToXlsx`** or **`exportMultiSheetToXlsx`**. 2. Use **stable header strings** that match §6 / §7 if the column is amount or quantity. 3. Pass **numbers** for amounts when possible. 4. If you need a **new** category (e.g. “Rate”, “折扣”), extend the regex constants in `exportChartToXlsx.ts` and **update this document**. 5. Keep filenames and sheet names user-readable; remember the **31-character** sheet limit. --- ## 13. Related files | File | Role | |------|------| | `exportChartToXlsx.ts` | Single-sheet export + styling pipeline | | `grnReportApi.ts` | Example: bilingual headers, money values, multi-sheet GRN report | | `FPSMS-backend/docs/EXCEL_EXPORT_STANDARD.md` | Backend Excel (POI/Jasper) — same *rules*, separate code | --- *Last aligned with implementation in `exportChartToXlsx.ts` (header fill `#D9D9D9`, money format `#,##0.00`, right-align numeric columns).*