# PCE TW GUI MODULE — 銷項開立取號流程與邏輯

> **對象**：產品／顧問／開發／測試  
> **目的**：說明「開立銷項發票」時發票號碼如何決定、如何對發票本、成功後回寫哪些欄位  
> **實作**：`PCE_LIB_AR_BookAllocate.js`（入口 `resolveInvoiceNumberForIssue`）  
> **操作摘要**：[使用說明 §5.5](./PCE_GUI_MODULE_suiteapp-user-guide.md) · [藍圖 §9.5](./PCE_GUI_MODULE_ar-blueprint.md)  
> **單元測試**：[如何自己加 test case](./PCE_GUI_MODULE_ar-invoice-numbering-tests.md)  
> **文件日期**：2026-09-13

---

## 1. 發生時機

取號發生在開立流程**鎖定來源交易之後、建立 PCE 銷項主檔之前**。

1. 使用者在 **PCE GUI → 銷項 → 開立銷項發票/折讓單** 查詢並勾選 Invoice／Credit Memo。
2. 按 **單張／合併開立**，進入憑證開立表單，確認格式、加值中心、發票號碼、發票本分配條件。
3. 送出後，`PCE_LIB_AR_Issue.js` 呼叫 `bookAllocateLib.resolveInvoiceNumberForIssue(...)`。
4. 取號失敗：丟出錯誤訊息，**不建立**銷項主檔。
5. 取號成功：建立主檔／明細，回寫來源交易發票號碼與開立狀態。格式 36 的主檔 **Name** 與發票號碼皆為 `SEUI`＋時間戳後 10 碼（共 14 字）。

發票開立（只選 Invoice）格式下拉**不含 33／34**。開立折讓單（只選 Credit Memo）**只顯示 33／34**。發票本管理不含 **33／34／36**。

---



## 2. 開立條件從哪裡來


| 條件                         | 來源                      | 備註                            |
| -------------------------- | ----------------------- | ----------------------------- |
| 賣方統編 `taxId`               | 子公司對應 PCE 營業人           | 不可覆寫                          |
| 格式 record id `docFormatId` | 表單「格式代號」                | 自動取號必填                        |
| 格式代碼 `docFormatCode`       | 該格式的代碼值（31／32／35／36…）   | 決定走哪一條路徑                      |
| 加值中心上傳 `vacUpload`         | 表單「是否透過加值中心上傳」          | 格式 **35**、**33** 可選；其餘鎖成「不上傳」 |
| 發票日期 `docDate`             | 表單「發票日期」                | 自動取號必填；並換算雙月期別                |
| 發票號碼                       | 表單「發票號碼」                | 僅紙本／35 不上傳可填                  |
| 部門／類別                      | 表單「發票本分配條件」；**選填**，空白＝通用本 | 僅在表單**有填**時才套用配號篩選；不沿用交易部門／類別 |
| 自定義配號 `profileId`          | 表單「自定義發票本配號」            | 空白＝不指定                        |


畫面欄位行為（開立 Suitelet Bootstrap 表單，格式看 option `data-code`）：


| 格式與上傳                             | 發票號碼欄                        |
| --------------------------------- | ---------------------------- |
| **36**                            | 停用、清空、非必填；開立時由系統產生 `SEUI` 號碼 |
| **35** 且加值中心＝**上傳**               | 停用、清空、非必填                    |
| **35** 且加值中心＝**不上傳**，或紙本（31／32 等） | 可填、**必填**                    |
| **33／34**（開立折讓單）                  | 可填、**必填**折讓證明單號碼；不找發票本       |


---



## 3. 路徑總覽

由 `resolveInvoiceNumberForIssue` 依格式與是否手填分流。

```mermaid
flowchart TD
  start[resolveInvoiceNumberForIssue] --> fmt{格式代碼?}
  fmt -->|36| skipFill{有手填號碼?}
  skipFill -->|有| err36[拒絕: 請勿手填]
  skipFill -->|無| ok36[成功: SEUI＋時間戳 10 碼 / 不找本]
  fmt -->|35 且上傳| autoGate{有手填?}
  autoGate -->|有| err35[拒絕: 不可手動填寫]
  autoGate -->|無| auto[allocateInvoiceNumber 自動取號]
  fmt -->|其餘紙本或 35 不上傳| paper{有手填?}
  paper -->|無| errReq[拒絕: 請手填發票號碼]
  paper -->|有| manual[applyManualInvoiceNumber 手填反查]
```




| 格式與上傳                             | 路徑   | 發票號碼                          | 發票本                                  |
| --------------------------------- | ---- | ----------------------------- | ------------------------------------ |
| **36**（銷項免用統一發票）                  | 系統產生 | `SEUI`＋時間戳後 10 碼（共 14 字）；不可手填 | 不找、不回寫                               |
| **35** 且加值中心＝**上傳**               | 自動取號 | 系統配號（字軌 2＋流水 8）               | 依條件找本；只取 **internal id 最小** 且已勾選傳至加值中心 |
| **35** 且加值中心＝**不上傳**，或紙本（31／32 等） | 手填   | **必填**（例 `CA00000075`）        | 依號碼反查；只驗已用；不要求接續 `last_no`           |


判斷函式：

- `skipsInvoiceNumber`：代碼＝`36`
- `requiresAutoInvoiceNumber`：代碼＝`35` 且 `vacUpload` 為 `true`／`T`
- `requiresManualInvoiceNumber`：有代碼、且不是上兩種

---



## 4. 自動取號（35＋上傳）

函式：`allocateInvoiceNumber`。

### 4.1 必要條件

缺一不可：


| 條件           | 失敗代碼                       | 訊息重點        |
| ------------ | -------------------------- | ----------- |
| 賣方統編         | `SELLER_TAX_ID_REQUIRED`   | 無法判定賣方統一編號  |
| 格式 record id | `DOC_FORMAT_REQUIRED`      | 請選擇格式代號     |
| 可由日期算出期別     | `DOC_DATE_PERIOD_REQUIRED` | 無法依發票日期判定期別 |
| 發票日期可解析為日曆日  | `DOC_DATE_INVALID`         | 發票日期格式無效    |


**期別**：發票日期 → 民國年月 `YYYMM` → 財政部**雙月期別**（奇數月進到下一個偶數月，例 11501／11502 皆為 `11502`）。

### 4.2 搜尋硬條件（`searchCandidateBooks`）

只找同時符合：

- 未停用（`isinactive = F`）
- 統編＝賣方統編
- 期別＝上一步雙月期別
- 格式＝開立格式
- 狀態 **0-未使用** 或 **1-使用中**（不含 2 用盡、9 停用）
- `**custrecord_pce_ar_book_ns_platform`＝T**（是否透過 NS傳至加值中心已勾選）

紙本／35 不上傳的手填反查**不**要求此勾選。

### 4.3 配號篩選（記憶體）

對部門、類別、自定義配號各做一次「選填比對」`matchesOptionalAllocationFilter`：


| 發票本欄位   | 開立條件 | 結果       |
| ------- | ---- | -------- |
| 空白（通用本） | 任意   | 可用       |
| 有值      | 相同   | 可用       |
| 有值      | 空白   | **不用這本** |
| 有值      | 不同   | **不用這本** |


三欄都要通過。表單三欄皆空白時只會落到「未限定」的通用本（**不**自動帶入 NetSuite 交易上的部門／類別）。  
另外再確認 `ns_platform` 為 `T`／`true`（搜尋漏網時的第二道過濾）。

沒有任何一本通過 → `NO_ELIGIBLE_BOOK`（訊息含統編、期別，並提示確認「是否透過 NS傳至加值中心」）。

### 4.4 多本只留一本

通過篩選後，**只取 numeric internal id 最小者**（`pickBookWithSmallestId`）。  
這本後面任何驗證失敗，**不會改試 id 較大的本**。

### 4.5 對這本做最後驗證

1. **日期**：發票日期 ≥ 該本 `last_issue_dt`（只比日曆日；同日可接受；尚未取號則不限）。
  失敗：`DOC_DATE_BEFORE_LAST_ISSUE`，例  
   `發票本 501：發票日期（2026/02/15）不可早於最新取號日期（2026/03/01）。`
2. **下一號** `computeNextSequenceNo`：
  - `last_no` 空白 → 下一號＝起號
  - 否則下一號＝`last_no + 1`（皆先補成 8 碼再加減）
  - 下一號超過訖號 → `BOOK_EXHAUSTED`：`發票本 501：已無可用發票號碼。`
3. **回寫樂觀鎖** `commitBookAllocation`：再 `load` 該本，重算下一號必須仍是剛才那一號；日期再驗一次。
  已被其他開立取走 → `BOOK_NUMBER_CONFLICT`：`發票本號碼已被其他開立流程取用，請重試`。  
   **只對同一本、同一預計號碼重試最多 3 次**（`MAX_ALLOC_RETRIES`），仍不換本。



### 4.6 成功時寫什麼

- 發票號碼＝字軌 2 碼英文大寫＋流水 8 碼（例 `CA`＋`00000075` → `CA00000075`）
- 發票本 `custrecord_pce_ar_book_last_no`＝本次流水
- 發票本 `custrecord_pce_ar_book_last_issue_dt`＝本次發票日期
- 狀態：`0` → `1`；若本次已是訖號 → `2-使用完畢`
- 銷項主檔 `custrecord_pce_ar_invoice_no`、來源 Invoice `custbody_pce_ar_invoice_no`（Credit Memo 開立折讓單則回寫 `custbody_pce_ar_allowance_no`／`custbody_pce_ar_orig_invoice_no`，不寫發票號碼）

銷項主檔**不**連結發票本欄位；本 id 只在取號結果與稽核 log 中。

---



## 5. 手填號碼（紙本／35 不上傳）

函式：`applyManualInvoiceNumber`。  
**不**用 `last_no`、也**不**用最新取號日判斷能不能用。只確認「這個號碼還沒被用過，且能唯一對到一本」。

### 5.1 解析

- 去空白、去 `-`、轉大寫
- 須為字軌 2 碼英文＋1～8 位數字（不足 8 碼補零）
- 例：`ca-75` → `CA00000075`
- 失敗：`INVALID_INVOICE_NUMBER`



### 5.2 已用檢查 `findUsedInvoiceNumber`

任一處已有同一號碼（不分大小寫正規化後）就拒絕 `INVOICE_NO_ALREADY_USED`：

- PCE 銷項主檔 `custrecord_pce_ar_invoice_no` 或 `custrecord_pce_ar_allowance_no`（未停用）
- Invoice／Credit Memo 主列 `custbody_pce_ar_invoice_no` 或 `custbody_pce_ar_allowance_no`



### 5.3 反查發票本

`searchBooksByTrack` → 再以流水落在起訖範圍篩選：

- 依**字軌**搜尋未停用發票本（**不限狀態 0／1**，也不要求勾選傳至加值中心）
- 有帶統編／期別／格式時一併縮小範圍（期別可由發票日期推雙月）
- 表單**有填**部門／類別／配號時，才套用配號篩選；三欄皆空白則不套

結果：


| 涵蓋本數  | 代碼               | 處理              |
| ----- | ---------------- | --------------- |
| 0     | `BOOK_NOT_FOUND` | 請確認字軌、起訖號與統編／期別 |
| 1     | —                | 用這本             |
| 2 本以上 | `BOOK_AMBIGUOUS` | 請再指定統編、期別或格式代號  |




### 5.4 成功時回寫 `commitManualBookUsage`

- 流水必須仍在該本起訖範圍內，否則 `INVOICE_NO_OUT_OF_RANGE`
- `last_no`＝max(既有 last_no, 本次流水)（避免之後自動取號倒退）
- `last_issue_dt`＝本次日期（即使比舊的早也會覆寫）
- 狀態：未使用可改使用中；若 last 已達訖號可改使用完畢

---



## 6. 格式 36

- 不找發票本、不寫 `last_no`／`last_issue_dt`
- 系統產生號碼：`SEUI`（Sales Exempt from Uniform Invoices）＋時間戳後 10 碼，共 **14** 字（例 `SEUI4567890123`）
- 主檔 **Name**、`externalid`、`custrecord_pce_ar_invoice_no` 與來源交易 `custbody_pce_ar_invoice_no` 皆寫入同一組號碼
- 若表單仍帶入手填號碼 → `MANUAL_INVOICE_NO_NOT_ALLOWED`
- 仍建立銷項主檔／明細，並回寫交易開立狀態

---



## 7. 發票本狀態與號碼計算

發票本狀態（`customrecord_pce_ar_book_status`）：


| 代碼  | 意義   | 自動取號搜尋 | 手填反查                           |
| --- | ---- | ------ | ------------------------------ |
| 0   | 未使用  | 可      | 可（不限狀態）                        |
| 1   | 使用中  | 可      | 可                              |
| 2   | 使用完畢 | 否      | 可（只要號碼落在起訖內且未使用）               |
| 9   | 停用   | 否      | 可（未停用紀錄；停用列 `isinactive` 另當別論） |


下一號：

```text
若 last_no 空白 → begin_no
否則 → last_no + 1
若結果 > end_no → 已用盡
```

日期比對只看年月日，不含時分秒。

---



## 8. 錯誤代碼一覽


| 代碼                              | 路徑            | 含義               |
| ------------------------------- | ------------- | ---------------- |
| `MANUAL_INVOICE_NO_NOT_ALLOWED` | 36 或 35＋上傳卻手填 | 此路徑不可手填          |
| `INVOICE_NO_REQUIRED`           | 紙本沒填／解析空白     | 須手填              |
| `INVALID_INVOICE_NUMBER`        | 手填格式錯／組合字軌失敗  | 須字軌 2＋流水         |
| `SELLER_TAX_ID_REQUIRED`        | 自動            | 無賣方統編            |
| `DOC_FORMAT_REQUIRED`           | 自動            | 無格式              |
| `DOC_DATE_PERIOD_REQUIRED`      | 自動            | 無法算期別            |
| `DOC_DATE_INVALID`              | 自動            | 日期無效             |
| `NO_ELIGIBLE_BOOK`              | 自動            | 搜尋＋配號＋傳至加值中心勾選後沒有本 |
| `DOC_DATE_BEFORE_LAST_ISSUE`    | 自動（或回寫再驗）     | 最小 id 本日期擋下；不換本  |
| `BOOK_EXHAUSTED`                | 自動            | 最小 id 本沒號了；不換本   |
| `BOOK_NUMBER_CONFLICT`          | 自動回寫          | 下一號已被搶；同一本重試 3 次 |
| `BOOK_ALLOCATE_FAILED`          | 自動            | 回寫失敗且無更具體代碼      |
| `INVOICE_NO_ALREADY_USED`       | 手填            | 主檔或 NS 交易已有同一號   |
| `BOOK_NOT_FOUND`                | 手填            | 沒有本涵蓋該字軌＋流水      |
| `BOOK_AMBIGUOUS`                | 手填            | 多本同時涵蓋           |
| `INVOICE_NO_OUT_OF_RANGE`       | 手填回寫          | load 後發現不在起訖內    |
| `BOOK_COMMIT_INVALID`           | 回寫            | 缺本 id 或流水        |


開立畫面會直接顯示 `error.message`。自動取號失敗訊息通常以 `發票本 {id}：` 開頭。

---



## 9. 相關程式與物件


| 項目          | 位置                                                                        |
| ----------- | ------------------------------------------------------------------------- |
| 取號核心        | `ar/libraries/PCE_LIB_AR_BookAllocate.js`                                 |
| 開立呼叫點       | `ar/libraries/PCE_LIB_AR_Issue.js`（取號後才 create 銷項主檔）                      |
| 開立 Suitelet | `ar/suitelets/PCE_SL_AR_Issue.js`                                         |
| 表單欄位        | `ar/libraries/PCE_LIB_AR_IssueForm.js`                                    |
| 開立畫面        | `ar/libraries/PCE_LIB_AR_IssueHtml.js`（Bootstrap 5 全頁）                    |
| 發票本紀錄       | `customrecord_pce_ar_invoice_book`                                        |
| 單元測試        | `test/ar-book-allocate.test.js`、`test/ar-book-allocate-scenarios.test.js` |
| 如何加測試       | [銷項取號單元測試指南](./PCE_GUI_MODULE_ar-invoice-numbering-tests.md)              |


---



## 10. 刻意不做的事

- 自動取號**不會**在多本間 fallback（不用「使用中優先／字軌排序／起號排序」）。
- 自動取號**不會**使用未勾選「是否透過 NS傳至加值中心」的本。
- 手填**不會**要求號碼接續 `last_no`，也**不會**因日期早於 `last_issue_dt` 拒絕。
- 銷項主檔**沒有**發票本 List/Record 欄位。
- 取號本身不寫 MIG 匯出檔；一般發票準備主檔上的 10 碼號碼，格式 36 為 14 字 `SEUI` 號碼。

