PCE TW GUI MODULE — 銷項開立取號流程與邏輯
目的:說明「開立銷項發票」時發票號碼如何決定、如何對發票本、成功後回寫哪些欄位
實作:
PCE_LIB_AR_BookAllocate.js(入口 resolveInvoiceNumberForIssue) 操作摘要:使用說明 §5.5 · 藍圖 §9.5
開立後主檔上傳狀態(與取號無關、依表單
vacUpload):使用說明 §3.2.1 單元測試:如何自己加 test case
文件日期:2026-09-24
HTML:HTML 版
1. 發生時機
取號發生在開立流程鎖定來源交易之後、建立 PCE 銷項主檔之前。
- 使用者在 PCE GUI Portal → 銷項 → 開立發票/折讓單 查詢並勾選 Invoice/Credit Memo。
- 按 單張/合併開立,進入憑證開立表單,確認格式、加值中心、發票號碼、發票本分配條件。
- 送出後,
PCE_LIB_AR_Issue.js呼叫bookAllocateLib.resolveInvoiceNumberForIssue(...)。 - 取號失敗:丟出錯誤訊息,不建立銷項主檔。
- 取號成功:建立主檔/明細,回寫來源交易發票號碼與開立狀態;主檔 狀態=已開立,上傳狀態依表單「是否透過加值中心上傳」為 待上傳 或 不上傳(見 §3.2.1)。格式 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 不上傳可填 |
| 部門/類別/自定義配號 | 表單「發票本分配條件」;選填 | 三欄都要與發票本完全相同。空白只對空白。不沿用交易部門/類別 |
畫面欄位行為(開立 Suitelet Bootstrap 表單,格式看 option data-code):
| 格式與上傳 | 發票號碼欄 |
|---|---|
| 36 | 停用、清空、非必填;開立時由系統產生 SEUI 號碼 |
| 35 且加值中心=上傳 | 停用、清空、非必填 |
| 35 且加值中心=不上傳,或紙本(31/32 等) | 可填、必填 |
| 33/34(開立折讓單) | 可手填;留空則系統產生 PCE+時間戳後 13 碼;不找發票本 |
3. 路徑總覽
由 resolveInvoiceNumberForIssue 依格式與是否手填分流。
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:代碼=36requiresAutoInvoiceNumber:代碼=35且vacUpload為true/TrequiresManualInvoiceNumber:有代碼、且不是上兩種isAllowanceDocFormat:代碼=33或34
4. 折讓證明單號碼(33/34)
函式:resolveUniqueAllowanceNumber(入口 resolveInvoiceNumberForIssue,格式 33/34 且表單未填號碼時)。
4.1 產生規則
- 前綴來自該營業人 PCE 營業人系統設定 → 折讓單前綴(
custrecord_pce_bizsys_alw_prefix)。英數最多 3 字,使用時轉大寫。空白則不加前綴。 - 其後接時間戳數字字串的末 13 碼(不足左補 0)。有前綴時合計最多 16 字;無前綴時為 13 字。
- 與格式 36 的
SEUI類似,但折讓寫入custrecord_pce_ar_allowance_no,不寫custrecord_pce_ar_invoice_no。
4.2 唯一性(查重+重試)
- 以
findUsedInvoiceNumber查銷項主檔與 Invoice/Credit Memo 的發票號、折讓號欄位(與手填折讓相同)。 - 若已使用:在同一毫秒時間基礎上,將 13 碼後綴視為整數 +1(mod 10¹³) 再組下一組候選號,最多重試 100 次(
MAX_ALLOWANCE_NUMBER_RETRIES)。 - 仍無可用號碼 →
ALLOWANCE_NO_GENERATE_FAILED(請稍後重試或改手填)。
手填折讓號仍走原邏輯:大寫、最多 16 碼、查重後成功;不經發票本。
5. 自動取號(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 配號篩選(記憶體)
部門、類別、自定義配號都走 matchesExactAllocation:開立選了什麼,發票本就要是什麼。空白只對空白,不會改用其他組合的發票本。
| 開立選擇 | 會用的發票本 |
|---|---|
| 部門 A、類別 B、配號空白 | 部門 A、類別 B、配號空白 |
| 部門 A、類別空白、配號空白 | 部門 A、類別空白、配號空白 |
| 部門 A、類別 B、配號 CC | 部門 A、類別 B、配號 CC |
| 三欄皆空白 | 三欄皆空白 |
三欄都要通過。不沿用 NetSuite 交易上的部門/類別。多本完全相同時,仍取 numeric internal id 最小者。 另外再確認 ns_platform 為 T/true(搜尋漏網時的第二道過濾)。
沒有任何一本通過 → NO_ELIGIBLE_BOOK(訊息含統編、期別、部門、類別、自定義配號,並提示至發票本管理確認)。
4.4 多本只留一本
通過篩選後,只取 numeric internal id 最小者(pickBookWithSmallestId)。 這本後面任何驗證失敗,不會改試 id 較大的本。
4.5 對這本做最後驗證
- 日期:發票日期 ≥ 該本
last_issue_dt(只比日曆日;同日可接受;尚未取號則不限)。 失敗:DOC_DATE_BEFORE_LAST_ISSUE,例發票本 501:發票日期(2026/02/15)不可早於最新取號日期(2026/03/01)。 - 下一號
computeNextSequenceNo:last_no空白 → 下一號=起號- 否則下一號=
last_no + 1(皆先補成 8 碼再加減) - 下一號超過訖號 →
BOOK_EXHAUSTED:發票本 501:已無可用發票號碼。
- 回寫樂觀鎖
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、來源 Invoicecustbody_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 另當別論) |
下一號:
若 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 或流水 |
ALLOWANCE_NO_INVALID | 33/34 手填或自動 | 超過 16 碼 |
ALLOWANCE_NO_GENERATE_FAILED | 33/34 自動 | 查重重試用盡仍無唯一號碼 |
開立畫面會直接顯示 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 |
| 如何加測試 | 銷項取號單元測試指南 |
10. 刻意不做的事
- 自動取號不會在多本間 fallback(不用「使用中優先/字軌排序/起號排序」)。
- 自動取號不會使用未勾選「是否透過 NS傳至加值中心」的本。
- 手填不會要求號碼接續
last_no,也不會因日期早於last_issue_dt拒絕。 - 銷項主檔沒有發票本 List/Record 欄位。
- 取號本身不寫 MIG 匯出檔;一般發票準備主檔上的 10 碼號碼,格式 36 為 14 字
SEUI號碼。