# PCE TW GUI MODULE — 銷項 Phase 1 單元測試清單

> **對象**：開發／測試／顧問  
> **目的**：彙整 Phase 1（開立、取號、發票本、折讓／作廢核心）本機 Node 單元測試範圍、執行方式與案例清單  
> **相關**：[銷項藍圖](./PCE_GUI_MODULE_ar-blueprint.md) · [取號測試指南](./PCE_GUI_MODULE_ar-invoice-numbering-tests.md) · [取號業務規則](./PCE_GUI_MODULE_ar-invoice-numbering.md)  
> **文件日期**：2026-09-14

這些測試**不連 NetSuite 帳號**。`test/helpers/amd-loader.js` 在 Node 載入 SuiteScript `define()`；各模組 harness mock `N/record`、`N/search` 等相依模組。

---

## 1. 怎麼跑

在專案根目錄：

```bash
# Phase 1 核心套件（195 案，2026-09-14）
npm run test:ar-phase1

# 開立核心（單元 + issueFromSources E2E，49 案）
npm run test:ar-issue
npm run test:ar-issue-from-sources

# 分模組
npm run test:ar-book-allocate    # 取號／手填（86 案）
npm run test:ar-validate         # 銷項驗證（6 案）
npm run test:ar-feature          # 子公司 feature flag（6 案）
npm run test:ar-issue-search     # 開立 Saved Search（4 案）
npm run test:ar-book-csv         # 發票本 CSV（21 案）
npm run test:ar-book-import      # 發票本匯入（4 案）
npm run test:ar-book-import-log  # 匯入紀錄（7 案）
npm run test:ar-book-search      # 發票本查詢／配號（12 案）

# Phase 1 延伸（未納入 test:ar-phase1，但屬同一交付範圍）
npm run test:ar-allowance        # 折讓取號／閘道／餘額（33 案）
npm run test:ar-void             # 作廢（15 案）
npm run test:ar-custom-lines     # 自訂商品明細 lib（31 案）

# 全專案（含 AP 與上述銷項）
npm test
```

依名稱篩選（Node 20+）：

```bash
node --test --test-name-pattern="淨額" test/ar-issue-from-sources.test.js
```

---

## 2. 覆蓋範圍總覽

### 2.1 `npm run test:ar-phase1`（195 案）

| 區塊 | 測試檔 | 案數 | 主要 library／模組 |
|------|--------|------|-------------------|
| 開立核心（單元） | `test/ar-issue.test.js` | 21 | `PCE_LIB_AR_Issue.js` 工具函式 |
| 開立 E2E | `test/ar-issue-from-sources.test.js` | 28 | `issueFromSources` 端到端 |
| 銷項驗證 | `test/ar-validate.test.js` | 6 | `PCE_LIB_AR_Validate.js` |
| Feature flag | `test/ar-feature.test.js` | 6 | `PCE_LIB_AR_Feature.js` |
| 開立 Search | `test/ar-issue-search.test.js` | 4 | `PCE_LIB_AR_IssueSearch.js` |
| 取號（函式） | `test/ar-book-allocate.test.js` | 56 | `PCE_LIB_AR_BookAllocate.js` |
| 取號（情境） | `test/ar-book-allocate-scenarios.test.js` | 30 | 格式 31–36 路徑分流 |
| 發票本 CSV | `test/ar-book-csv.test.js` | 21 | `PCE_LIB_AR_BookCsv.js` |
| 發票本匯入 | `test/ar-book-import.test.js` | 4 | `PCE_LIB_AR_BookImport.js` |
| 匯入紀錄 | `test/ar-book-import-log.test.js` | 7 | `PCE_LIB_AR_BookImportLog.js` |
| 發票本查詢 | `test/ar-book-search.test.js` | 12 | `PCE_LIB_AR_BookSearch.js` |

### 2.2 Phase 1 延伸（79 案，建議 regression 一併跑）

| 區塊 | 測試檔 | 案數 | 說明 |
|------|--------|------|------|
| 折讓 | `test/ar-allowance.test.js` | 33 | 格式 33/34、折讓閘道、可折餘額、表單解析 |
| 作廢 | `test/ar-void.test.js` | 15 | 作廢資格、executeVoid、失敗報告 |
| 自訂明細 | `test/ar-custom-lines.test.js` | 31 | 明細轉換、合併 key、表頭對帳 |

**Phase 1 單元測試合計（含延伸）**：195 + 79 = **274 案**（2026-09-14）。

### 2.3 尚未覆蓋（需帳上／手動）

| 項目 | 說明 |
|------|------|
| Suitelet UI 開立表單 | 按鈕、預覽 sublist、POST 參數組裝 |
| Map/Reduce 背景明細 | `customscript_pce_mr_ar_issue_lines` 帳上執行 |
| Script Deployment Audience | XML 部署後 UI 是否載入 CS／UE |
| 冒煙／Browser MCP | 實際 Invoice 頁開立一張發票 |
| Customer Deposit 轉開／含稅反推 | Phase 2，未建 |
| 空白字軌／空白發票下載 | [營業稅申報進度](./PCE_GUI_MODULE_vat-filing-status.md)，未建 |
| 每期申報對帳報表 | Phase 4，未建 |
| 憑證 PDF 下載（B2B／折讓／零稅率等） | Phase 5，未建 |
| 銷項 CSV／Write API | Phase 6（外部已開立寫入 NS），未建 |
| 自動／批次開立發票／折讓 | Phase 7（設計規劃），未建 |

---

## 3. Harness 對照

| Harness | 路徑 | 用途 |
|---------|------|------|
| amd-loader | `test/helpers/amd-loader.js` | Node 載入 SuiteScript AMD |
| 開立（單元） | `test/helpers/ar-issue-harness.js` → `loadIssueLib()` | 回寫、查主檔、鎖定等片段 |
| 開立（E2E） | 同上 → `loadIssueLibForSources()` | mock 來源交易、自訂明細、取號 |
| 取號 | `test/helpers/ar-book-allocate-harness.js` | 預設發票本 `DEFAULT_BOOK` |
| 作廢 | `test/helpers/ar-void-harness.js` | 作廢流程 mock |

E2E 常用 factory：

- `defaultSourceTransaction(txId, overrides)` — Invoice mock
- `defaultAllowanceTransaction(txId, overrides)` — Credit Memo mock
- `customLinesByTxId` — `customrecord_pce_ar_custom_line` search mock

---

## 4. 案例清單 — 開立（`test:ar-issue`）

### 4.1 `ar-issue.test.js`（21 案）

| # | 案例 | 驗證重點 |
|---|------|----------|
| 1 | resolveMergeMode：空／單筆 → single | 合併模式判定 |
| 2 | resolveMergeMode：多 Invoice → merge | |
| 3 | resolveMergeMode：Invoice + CM → net | |
| 4 | resolveMergeMode：多 CM → merge | |
| 5 | parseSourceEntries：custpage_inv_ids | 表單來源解析 |
| 6 | parseSourceEntries：custpage_tx_id 預設 invoice | |
| 7 | parseSourceEntries：混用 inv + cm | |
| 8 | parseSourceEntries：無效 JSON → 拋錯 | |
| 9 | parseSourceEntries：無來源 → 空陣列 | |
| 10 | resolveIssueActionLabel：單張 | UI 文案 |
| 11 | resolveIssueActionLabel：合併 | |
| 12 | writeBackTransactionAfterIssue：Invoice 回寫 | invoice_no、解鎖、狀態 |
| 13 | findArInvoiceByInvoiceNo：大小寫正規化 | |
| 14 | findArInvoiceByInvoiceNo：空號碼 → null | |
| 15 | formatLineSeq 補零 | |
| 16 | resolveLineRecordName | |
| 17 | lockTransactionIssue／release | |
| 18 | shouldScheduleAsyncLineCreation：明細閾值 | MR 決策 |
| 19 | shouldScheduleAsyncLineCreation：合計閾值 | |
| 20 | formatSuiteScriptError：Error | |
| 21 | formatSuiteScriptError：空值 | |

### 4.2 `ar-issue-from-sources.test.js`（28 案）

#### 單張／合併／淨額

| # | 案例 | 驗證重點 |
|---|------|----------|
| 1 | 單張 Invoice happy path | 主檔、明細、來源、回寫 |
| 2 | 兩筆 Invoice merge | 加總、雙來源、雙回寫 |
| 3 | Invoice + CM net | CM 扣減（840） |
| 4 | Invoice 自訂 + CM NS（net） | 正負兩列、doc_kind=發票 |
| 5 | 雙方皆自訂相同品項（net） | **不**跨單合併、總計 0 |
| 6 | CM 全額抵扣 | 總計 0、allow_remain=0 |
| 7 | CM 超過 Invoice | 負總計 −1050 |
| 8 | Invoice 自訂表頭不符（net） | 失敗、不建主檔 |
| 9 | 僅 CM 自訂 + Invoice NS（net） | 1000 / −200 |

#### 失敗情境

| # | 案例 | 驗證重點 |
|---|------|----------|
| 10 | 空來源 | |
| 11 | 子公司不一致 | |
| 12 | 交易已鎖定 | |
| 13 | 已有發票號碼 | |
| 14 | 取號失敗 | |

#### 折讓開立（格式 33）

| # | 案例 | 驗證重點 |
|---|------|----------|
| 15 | happy path | 回寫 CM、扣減原發票餘額 |
| 16 | 未填 origInvoiceNo | |
| 17 | 超過可折餘額 | |
| 18 | 合併兩筆 CM | merge、雙回寫、餘額 893 |
| 19 | 非當期無主檔 | warning、不扣餘額 |

#### 自訂明細

| # | 案例 | 驗證重點 |
|---|------|----------|
| 20 | happy path | customrecord 明細 |
| 21 | 表頭不符 | |
| 22 | 合併：相同品項單價 → 1 列 | qty=2 |
| 23 | 合併：不同單價 → 2 列 | |
| 24 | 合併：不同課稅別 → 2 列 | 表頭混合稅 |
| 25 | 混用自訂 + NS | 不跨單合併 |
| 26 | 同單多列相同 key | 單內合併 |
| 27 | 已勾選無有效列 | |

#### 背景明細

| # | 案例 | 驗證重點 |
|---|------|----------|
| 28 | ≥ SYNC_LINE_THRESHOLD | asyncLines、提交 MR task |

---

## 5. 案例清單 — 驗證／Feature／Search

### 5.1 `ar-validate.test.js`（6 案）

- validateRecord 統編：合法、B2C 略過、買方錯、賣方錯
- applySalesAmountDefaultsToNsRecord：空值補 0
- formatErrorsAsText／Html

### 5.2 `ar-feature.test.js`（6 案）

- isArFeatureEnabledForSubsidiary：啟用／未啟用／空
- shouldIssueInvoiceForTransaction：有客戶且啟用、無客戶、applyIssueInvoiceFlag

### 5.3 `ar-issue-search.test.js`（4 案）

- Saved Search 開立資格（issue=T、未鎖、無號碼）
- buildListCriteriaFilters：有條件／空
- buildInternalIdFilter

---

## 6. 案例清單 — 發票本／取號

取號詳細案例與 mock 發票本欄位見 **[取號測試指南](./PCE_GUI_MODULE_ar-invoice-numbering-tests.md)**。

### 6.1 摘要

| 檔案 | 案數 | 主題 |
|------|------|------|
| `ar-book-allocate.test.js` | 56 | 配號篩選、下一號、樂觀鎖、手填反查、衝突重試 |
| `ar-book-allocate-scenarios.test.js` | 30 | 格式 31–36 路徑、手填／自動、錯誤碼 |
| `ar-book-csv.test.js` | 21 | CSV 解析、字軌拆分、範本 |
| `ar-book-import.test.js` | 4 | 列→job、validateOnly、MR 提交 |
| `ar-book-import-log.test.js` | 7 | pending／completed／partial、UI snapshot |
| `ar-book-search.test.js` | 12 | 查詢條件、配號欄位、bulk MR |

---

## 7. 案例清單 — Phase 1 延伸

### 7.1 `ar-allowance.test.js`（33 案）

| 群組 | 內容 |
|------|------|
| 格式判定 | 33/34 折讓、31/35/36 非折讓 |
| 取號 | 手填、16 碼、已使用、不走發票本 |
| 回寫 | CM allowance_no／orig_invoice_no；Invoice invoice_no |
| 表單 | 文案、cm_ids 解析、格式 36 稅率 |
| 可折餘額 | 足夠／超額／當期無主檔／非當期自管／lookupOrigInvoice |

### 7.2 `ar-void.test.js`（15 案）

| 群組 | 內容 |
|------|------|
| resolveVoidMigMsg | MIG 訊息對照 |
| 日期預設 | 台灣時區 |
| validateVoidEligibility | 資格、狀態、已作廢 |
| buildTxFailureReport | 失敗彙整 |
| executeVoid | 發票／折讓作廢、回沖 |

### 7.3 `ar-custom-lines.test.js`（31 案）

| 群組 | 內容 |
|------|------|
| mapRawLinesToIssueLines | 零元過濾、taxTypeId |
| resolveIssueLinesForSource | NS／自訂、空列拋錯 |
| validateCustomLineTotalsMatchHeader | 未稅／稅額、容差 0.02 |
| mergeIssueLinesByItemAndUnitPrice | 同品項合併、不同單價／課稅別 |
| fillMissingLineTaxAmounts | 補稅、對帳 |

---

## 8. 建議 regression 順序

1. `npm run test:ar-phase1` — 開立＋取號＋發票本核心（CI 必綠）
2. `npm run test:ar-allowance && npm run test:ar-void && npm run test:ar-custom-lines` — 同交付範圍延伸
3. 部署後帳上：開立 Suitelet 單張／合併／淨額各一筆（手動 checklist）

---

## 9. 新增 E2E 案例指引

在 `test/ar-issue-from-sources.test.js` 新增案例時：

1. 使用 `loadIssueLibForSources()`，每案獨立 mock，避免 state 汙染。
2. 來源交易用 `defaultSourceTransaction`／`defaultAllowanceTransaction`。
3. 自訂明細補 `customLinesByTxId` 並將 `useCustomLines` 設為 `T`。
4. 折讓需 `arInvoiceRows` 模擬原發票銷項主檔。
5. 斷言優先查 `issueState.createdRecords`、`issueState.submitFieldsCalls`。

範本見 `ar-issue-from-sources.test.js` 第一個 happy path 案例。

---

## 10. 修訂紀錄

| 日期 | 說明 |
|------|------|
| 2026-09-14 | 初版：彙整 test:ar-phase1（195 案）與 issueFromSources E2E（28 案）清單 |
