PCE AP Write API — 技術與測試文件(內部)
1. 服務概要
| 項目 | 內容 |
|---|---|
| 用途 | 外部系統透過 RESTlet 寫入進項發票(customrecord_pce_ap_invoice)與/或 AP 交易 |
| Script | customscript_pce_rl_ap_write |
| Deployment | customdeploy_pce_rl_ap_write |
| Entry | ap/restlets/PCE_RL_AP_Write.js |
| Payload 契約 | ap/libraries/PCE_LIB_AP_WritePayload.js |
| 驗證 | ap/libraries/PCE_LIB_AP_Validate.js(寫入前 + UE beforeSubmit 再驗) |
| 呼叫紀錄 | customrecord_pce_ap_write_log(每次呼叫自動寫入 request/response;含被拒的 GET) |
| HTTP | 僅 POST(GET 回 PCE_METHOD_NOT_ALLOWED) |
1.1 action
| action | 行為 |
|---|---|
apInvoiceOnly | 只建進項發票;不可帶「要新建的」transaction |
transactionOnly | 只建/引用交易;不可帶 apInvoices |
transactionWithApInvoices | 先解析/建立交易,再寫一至多筆進項並掛 parent |
公版只接受上表三種 action。stageApIntake、updateApIntake、createPurchaseReq、updatePurchaseReq 會回 INVALID_ACTION。API進項審核與請購單不在這份套件。
支援交易類型:vendorbill|expensereport|vendorcredit|vendorprepayment。
2. 程式結構
ap/
restlets/
PCE_RL_AP_Write.js # RESTlet 入口(每次呼叫寫入 API Log)
suitelets/
PCE_SL_ApWriteSmoke.js # AP Write smoke Suitelet
PCE_SL_AP_CsvImport.js # CSV 匯入 Suitelet(POST→GET PRG)
libraries/
PCE_LIB_AP_CsvImportReplay.js # CSV 結果頁自 write log 還原 UI
PCE_LIB_AP_WriteReturnCode.js # returnCode 對照與 applyReturnCode
PCE_LIB_AP_WritePayload.js # action/shape/alias 正規化
PCE_LIB_AP_InvoiceSave.js # 進項驗證與寫入 PCE 進項資料
PCE_LIB_AP_WriteDfo.js # 公版不做事;客戶專用分支才換成審核與請購單
PCE_LIB_AP_Validate.js # 進項業務驗證(MOF 對齊)
PCE_LIB_AP_Lookup.js # 格式/稅別/扣抵/字軌 code↔id
PCE_LIB_AP_Period.js # 民國年月/申報期
PCE_LIB_AP_WriteLog.js # 寫入 customrecord_pce_ap_write_log
PCE_AP_Fields.js # 欄位 script id 常數
clientscripts/ # UI(與本 API 分離)
userevents/
PCE_UE_APInvoice_DefaultValues.js # beforeSubmit 再呼叫 Validate
common/
PCE_LIB_TaxId.js # 統編檢查(由 Validate 引用)2.0 API 呼叫紀錄(customrecord_pce_ap_write_log)
每次 POST(以及被拒的 GET)結束後由 RESTlet 自動建立一筆紀錄。寫入失敗只記 Execution Log,不影響 API 回應。
| 欄位 | Script ID | 說明 |
|---|---|---|
| Method | custrecord_pce_apwl_method | GET(應被拒)/POST |
| Action | custrecord_pce_apwl_action | POST 的 action |
| Request ID | custrecord_pce_apwl_request_id | 冪等鍵 requestId |
| Success | custrecord_pce_apwl_success | 回應 success |
| Error Code | custrecord_pce_apwl_error_code | 失敗時 error.code |
| Duration (ms) | custrecord_pce_apwl_duration_ms | 處理耗時 |
| Governance (units) | custrecord_pce_apwl_governance | 本次 API 消耗的 SuiteScript governance units(寫入本紀錄前計算) |
| User | custrecord_pce_apwl_user | 執行使用者 |
| Request | custrecord_pce_apwl_request | request JSON(過長截斷) |
| Response | custrecord_pce_apwl_response | response JSON(過長截斷) |
UI:Lists → Custom → PCE AP Write 呼叫紀錄。 部署後請確認 Integration/Role 對此 custom record 有 Create 權限。
2.1 POST 處理流程
flowchart TD
A[POST body] --> B[validatePayloadShape]
B -->|invalid| F[fail shape error]
B -->|valid| C{action}
C -->|apInvoiceOnly| D[normalize + validate + create invoices]
C -->|transactionOnly| E[resolveOrCreateTransaction]
C -->|transactionWithApInvoices| G[resolveOrCreateTransaction]
G --> D2[normalize + validate + create invoices with parent]
D --> H[ok + warnings]
E --> H
D2 --> H
D -->|validate error| X[fail PCE_AP_INVOICE_VALIDATION_ERROR]
D2 -->|validate error| X流程圖原文
resolveOrCreateTransaction:有 transaction.id 則引用既有交易;否則若 type=vendorbill 且帶 purchaseOrderId,則 record.transform(purchaseorder → vendorbill),再依 itemLines[].orderline/quantity 覆寫(未列之 PO 行 quantity=0);其餘情況走既有 record.create + fields/lines。
2.2 驗證雙關卡
- RESTlet:
buildValidatedInvoiceData→validateRecord;失敗不 save。 - UE:實際
record.save時beforeSubmit再驗一次(CSV/WS/UI/RESTLET 皆會跑,視部署 context)。
逐條規則(必填、禁填、擇一、稅額容差、字軌、期別、錯誤代碼)見 進項憑證檢核規則。
Select 欄位(格式/課稅別/扣抵/彙加註記)可傳 代碼(如 "21")或 internal id;由 resolveSelectValue + Lookup 解析。進項格式 21/25 已拆 2 筆,僅傳代碼時須加 invCatCode 或 docFormatName。
3. 部署與環境
3.1 SDF / SuiteCloud
- Script 物件:
src/Objects/Scripts/customscript_pce_rl_ap_write.xml - FileCabinet:
ap/restlets/、ap/suitelets/、ap/libraries/*(見deploy.xml) - Deploy SuiteApp 後確認 Script Deployment Released、
isdeployed = T
3.2 權限與 Integration
| 檢查項 | 說明 |
|---|---|
| Integration Application | OAuth 2.0(建議 M2M Client Credentials;測試可用 Authorization Code) |
| Scope | 至少勾選 RESTlets |
| Role | 需有 RESTlet 執行權、進項 custom record 建立/編輯、目標交易類型 create/edit;M2M mapping 綁定此 Role |
| Audience | 目前 deployment 設 allroles = T;正式環境建議收斂角色 |
| Log level | 預設 DEBUG;正式可改 ERROR/AUDIT |
3.3 取得 External URL
Customization → Scripting → Script Deployments → PCE RL AP Write → 複製 External URL。 格式大致為:
https://{accountId}.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script={scriptId}&deploy={deployId}帳號 ID、script/deploy 內部 id 以環境為準(scriptid 字串不等於 URL 上的數字 id)。
4. 錯誤碼(技術對照)
實作:ap/libraries/PCE_LIB_AP_WriteReturnCode.js;RESTlet ok()/fail() 與 enrichResponseWithRequest() 結尾皆呼叫 applyReturnCode()。
4.0 returnCode 區間
| 區間 | 語意 |
|---|---|
0000–0099 | 成功(0000 OK、0001 validateOnly、0002 冪等重播、0003 有 warnings、0004 partial) |
1000–1999 | Payload shape |
2000–2999 | 進項驗證 |
3000–3999 | Lookup |
4000–4999 | NetSuite 執行期 |
9000–9999 | 未預期 |
4.1 Payload shape(寫入前、不進 try 內業務)
| code | returnCode | 時機 |
|---|---|---|
INVALID_BODY | 1001 | body 非物件 |
INVALID_ACTION | 1002 | action 非法 |
REQUEST_ID_REQUIRED | 1003 | 缺 requestId |
REQUEST_ID_INVALID | 1004 | requestId 格式不合(需 8–64 字元 A–Z a–z 0–9 - _) |
REQUEST_ID_PAYLOAD_MISMATCH | 1005 | 同 requestId 已成功寫入,但本次 payload 指紋不同 |
AP_INVOICES_REQUIRED | 1006 | 需要發票陣列卻空 |
AP_INVOICES_NOT_ALLOWED | 1007 | transactionOnly 帶了發票 |
AP_INVOICES_TOP_LEVEL_NOT_ALLOWED | 1008 | transactionWithApInvoices 仍使用 top-level 發票欄位 |
TRANSACTION_REQUIRED | 1009 | 缺 transaction.id/type |
TRANSACTION_NOT_ALLOWED | 1010 | apInvoiceOnly 卻帶新建交易內容 |
INVALID_TRANSACTION_TYPE | 1011 | type 不在白名單 |
TRANSACTION_LIMIT_EXCEEDED | 1012 | 一次交易筆數超過上限(10) |
CLIENT_REF_INVALID | 1013 | clientRef 非 1–64 字元字串 |
PCE_METHOD_NOT_ALLOWED | 1014 | 不支援的 HTTP 方法 |
PURCHASE_ORDER_NOT_ALLOWED | 1015 | 非 vendorbill 卻帶 purchaseOrderId |
PURCHASE_ORDER_WITH_EXISTING_ID | 1016 | 同時帶 id 與 purchaseOrderId |
PO_ITEM_LINES_REQUIRED | 1017 | 從 PO 建立時缺非空 itemLines |
PO_ORDERLINE_REQUIRED | 1018 | item 列缺 orderline/line |
PO_QUANTITY_REQUIRED | 1019 | item 列缺合法 quantity |
1020–1027 留給 API進項審核與請購單。公版不會回這些代碼。
4.2 執行期(catch → fail)
| name / code | returnCode | 說明 |
|---|---|---|
PCE_AP_INVOICE_VALIDATION_ERROR | 2001 | Validate 失敗;error.details = 欄位錯誤物件(亦出現在 results[i].error) |
BATCH_FAILED | 2100 | 批次內全部 leaf 失敗(多筆) |
PARTIAL_SUCCESS | 0004 | 部分成功;partialSuccess: true,仍回 results[] |
PCE_LOOKUP_RESOLVE_ERROR | 3001 | code/id 無法對到 lookup |
PCE_INVALID_TRANSACTION_TYPE | 3002 | create 時 type 對不到 record.Type |
SKIPPED_PARENT_FAILED | 4001 | 父交易失敗,巢狀發票未處理 |
PO_ORDERLINE_NOT_FOUND | 4002 | request orderline 不在 transform 後 VB |
PO_TRANSFORM_FAILED | 4003 | PO→VB transform/save 失敗 |
| NetSuite 原生錯誤 | 4000 | 權限、必填、record.save 失敗等 |
UNEXPECTED_ERROR | 9999 | 無 name 的例外 |
Partial success 行為:processApInvoices/多筆 transaction 逐筆 try/catch,不因單筆失敗中斷整包。findSuccessfulWriteByRequestId 亦會把 returnCode=0004/partialSuccess 視為已提交,避免同 requestId 重試重複建單。
回應型態一律含 returnCode/returnMessage:
{
"success": false,
"returnCode": "2001",
"returnMessage": "AP_INVOICE_VALIDATION_FAILED",
"error": {
"code": "PCE_AP_INVOICE_VALIDATION_ERROR",
"message": "...",
"details": null
}
}5. 測試策略
5.1 單元測試(本機,不需 NetSuite)
npm test # 或分開跑 npm run test:ap-validate npm run test:ap-write-payload npm run test:ap-write-return-code npm run test:ap-csv
| 檔案 | 覆蓋 |
|---|---|
test/ap-write-payload.test.js | action/tx type/alias/shape |
test/ap-write-return-code.test.js | returnCode 對照與成功優先序 |
test/ap-csv.test.js | CSV 解析/表頭/100 列上限 |
test/ap-validate.test.js | 進項驗證規則(MOF) |
test/helpers/amd-loader.js | SuiteScript AMD → Node |
變更契約時:先改 PCE_LIB_AP_WritePayload.js + 對應單元測試,再改 RESTlet 與文件。
5.2 帳號內冒煙測試(顧問/QA)
建議順序:
- GET → 應回
success: false、error.code: PCE_METHOD_NOT_ALLOWED(本 API 不允許 GET)。 - POST
options.validateOnly: true+apInvoiceOnly合法 payload → 不應產生 record。 - POST 故意錯統編/字軌 →
PCE_AP_INVOICE_VALIDATION_ERROR+details。 - POST 真實寫入一筆
apInvoiceOnly(測試統編/字軌表需齊)→ UI 開進項確認。 - POST
transactionWithApInvoices→ 確認 VB/ER/VC/Prepayment parent 與子清單。 - 用既有
transaction.id再掛第二張進項 → 驗證 1:N。 - (可選)有可用 PO 時:
vendorbill+purchaseOrderId+itemLines[].orderline/quantity→ 確認部分開帳與未列行 quantity=0。
5.3 Postman / curl 注意
- 匯入
docs/postman/PCE-AP-Write.postman_collection.json(可選環境檔同目錄) - 先跑 0. Auth → Get Access Token;Collection Auth = Bearer
{{accessToken}} - M2M JWT:
docs/postman/generate-client-assertion.js - Content-Type:
application/json validateOnly對transactionOnly:只檢查 shape,不模擬 NetSuite 交易必填
5.4 建議測試資料檢查清單
- [x] Lookup:憑證格式 21–29、課稅別、扣抵、彙加註記(Round C validate/寫入通過)
- [x] 字軌表:對應申報期/格式有字軌(11508 格式 21:CA、CB)
- [x] Vendor/Subsidiary/Account(建 VB 時;Round C fixtures)
- [x] UE 部署含 RESTLET execution context(寫入路徑驗證通過)
- [x] Client Script 不影響 RESTlet 路徑(預期;RESTlet 寫入成功)
5.5 帳號內實測紀錄(transactionOnly)
帳號:TD2905095(Chesley's Dev Environment)。 RESTlet:customscript_pce_rl_ap_write/customdeploy_pce_rl_ap_write。 Action:transactionOnly(實際建立交易)。 主要測試資料:Vendor 944(Lancome)、Subsidiary 8(US East)、Employee 1086、Expense account 415、Bank 422、Currency 1、Location 11。
Round A — SDF Installation smoke(2026-08-04)
| 類型 | 結果 | Internal ID |
|---|---|---|
| Vendor Bill | ✅ Pass | 25647 |
| Expense Report | ✅ Pass | 25648 |
| Vendor Credit | ✅ Pass | 25649 |
| Vendor Prepayment | ✅ Pass | 25551 |
原始 JSON:test/results/ap-write-smoke-result.json
Round B — Browser MCP + Suitelet smoke(2026-08-04)
入口:/app/site/hosting/scriptlet.nl?script=customscript_pce_sl_ap_write_smoke&deploy=customdeploy_pce_sl_ap_write_smoke&action=run (Suitelet 以登入 session 呼叫 https.requestRestlet → 正式 AP Write RESTlet。)
| 類型 | 結果 | Internal ID | UI 確認 |
|---|---|---|---|
| Vendor Bill | ✅ Pass | 25747 | Bill #188,Lancome,$1,050,Pending Approval |
| Expense Report | ✅ Pass | 25847 | Employee Abigail Khan |
| Vendor Credit | ✅ Pass | 25848 | Bill Credit #15,memo 含 browser smoke |
| Vendor Prepayment | ✅ Pass | 25947 | VP #26,PAID,$1,050,Bank US East Checking |
原始 JSON:test/results/ap-write-browser-smoke-result.json
Round C — Browser MCP full checklist §5.2(2026-08-04)
入口:…&action=checklist 本機單元測試:npm test → 49/49 Pass。
| Step | 項目 | 結果 | 備註 |
|---|---|---|---|
| 5.4 | 測試資料 fixtures | ✅ | vendor 944 / emp 1086 / bank 422 |
| 1 | GET 不允許 | ✅/⚠️ | 預期 PCE_METHOD_NOT_ALLOWED |
| 2 | apInvoiceOnly + validateOnly | ✅ | 不建檔 |
| 3a | 錯誤統編 | ✅ | PCE_AP_INVOICE_VALIDATION_ERROR |
| 3b | 錯誤字軌 AA | ✅ | GuiTrackNotAllowed;可用 CA、CB |
| 4 | apInvoiceOnly 真實寫入 | ✅ | apInvoiceId 27(CA26632189) |
| 5.1 | transactionWithApInvoices VB | ✅ | tx 26149 + inv 127 |
| 5.2 | ER | ✅ | tx 26150 + inv 128 |
| 5.3 | VC | ✅ | tx 26151 + inv 129 |
| 5.4 | VP | ✅ | tx 26152 + inv 130 |
| 6 | 既有 transaction.id 掛第 2 張(1:N) | ✅ | tx 26149 + inv 131;UI 子清單可見 CA26632688、CA26642463 |
JSON:test/results/ap-write-checklist-result.json
測試中一併修正:fileBizTax 改以 boolean setValue;docDate 以 N/format 轉成 Date 再寫入。
備註
- GET 不允許;應回
PCE_METHOD_NOT_ALLOWED(契約/健康檢查改以文件與validateOnlyPOST 為準)。 - Vendor Prepayment 需設 Bank account(非 AP),且
account/payment應在subsidiary/entity之後寫入(sourcing 會清掉過早設值)。 - Smoke Suitelet 必須略過 internal id ≤ 0 的 vendor(例如
-3),否則會INVALID_FLD_VALUE。
6. 除錯指南
| 現象 | 可能原因 | 處理 |
|---|---|---|
| 401 Unauthorized | access token 無效/過期/缺 Bearer | 重新打 token endpoint;檢查 Scope=restlets |
| 403 / INSUFFICIENT_PERMISSION | Role 缺權限 | 加 custom record/交易權限;檢查 M2M Role mapping |
PCE_LOOKUP_RESOLVE_ERROR | 代碼未建 lookup 或傳錯 | 查 PCE_LIB_AP_Lookup 與安裝 lookup |
| Validate 過但 save 失敗 | UE/NS 必填/ sourcing | Script Execution Log;比對 UI 同資料 |
| 有 warnings 但仍 success | 稅額差額 1~5 元等「可過提示」 | 屬預期;見 檢核規則 |
| shape OK 但交易沒建 | validateOnly: true | 拿掉或設 false |
apInvoiceOnly 帶 transaction | shape 擋 TRANSACTION_NOT_ALLOWED | 改用 transactionWithApInvoices |
| 無呼叫紀錄 | Role 缺 custom record Create | 開 customrecord_pce_ap_write_log 權限;查 PCE AP Write log failed |
Execution Log 關鍵字:PCE AP Write RESTlet failed、PCE AP Write log failed。 除錯時優先開 PCE AP Write 呼叫紀錄 看 Request/Response。
7. 已知限制與設計取捨
- 交易欄位不經 PCE Validate(含客製欄):
transaction.fields/expenseLines/itemLines的 key 直接setValue/setCurrentSublistValue,無白名單。標準欄與客製欄(custbody_/custcol_等)皆可透傳;必填、型別與權限由 NetSuite/客戶表單決定。詳見介接手冊 §6.0。 transactionOnly+validateOnly:不驗證交易內容,只確認 payload shape。- 不更新已存在的進項:payload 的
id/externalId略過。已建立的 PCE 進項資料不經這支 API 修改。 - 不刪除/作廢:需另開流程。
- 並發重複發票:Validate 有重複檢核;極端並發仍可能 race,正式環境宜由上游控管。
- requestId 冪等:先用
requestId、POST、成功或PARTIAL_SUCCESS搜呼叫紀錄,再record.load讀 Request/Response(Long Text 不能當 Search 欄位,否則查詢失敗會當成沒有舊紀錄而再建立一次)。custom record 無真正 unique constraint,極端並發同requestId仍可能 race(實務上串列重試+UUID 可接受)。 - Script ID 更名:若帳號曾部署舊 BPM 命名 script,需停用舊 deployment,改用
customscript_pce_rl_ap_write。
8. 變更影響評估(給顧問)
| 變更類型 | 影響 |
|---|---|
| 新增 alias | 低;更新 payload lib + 單元測試 + 介接手冊 |
| 改 action 語意 | 高;破壞性,需版控與通知廠商 |
| Validate 規則加嚴 | 中高;既有整合可能大量失敗,需先 validateOnly 試跑 |
| Lookup 資料缺漏 | 高;立即 PCE_LOOKUP_RESOLVE_ERROR |
9. 相關內部資產
| 資產 | 路徑 |
|---|---|
| UI 測試 Runbook | test/UI-TEST-RUNBOOK.md |
| UI Checklist | test/UI-TEST-CHECKLIST.md |
| 進項驗證單元測試 | test/ap-validate.test.js |
| Payload 單元測試 | test/ap-write-payload.test.js |
| AP Write smoke 結果(SDF) | test/results/ap-write-smoke-result.json |
| AP Write smoke 結果(Browser) | test/results/ap-write-browser-smoke-result.json |
| AP Write smoke Suitelet | ap/suitelets/PCE_SL_ApWriteSmoke.js |
| AP CSV 匯入 Suitelet | ap/suitelets/PCE_SL_AP_CsvImport.js + ap/libraries/PCE_LIB_AP_Csv.js |
| AP CSV 結果還原 | ap/libraries/PCE_LIB_AP_CsvImportReplay.js(write log _csvUi 快照) |
| AP CSV 結果還原測試 | test/ap-csv-import-replay.test.js |
| AP Write 呼叫紀錄 | customrecord_pce_ap_write_log/PCE_LIB_AP_WriteLog.js |
| 廠商介接手冊 | docs/PCE_GUI_MODULE_ap-write-api-integration-guide.md |
| Technical (EN) | docs/PCE_GUI_MODULE_ap-write-api-technical.en.md |
| Integration (EN) | docs/PCE_GUI_MODULE_ap-write-api-integration-guide.en.md |
| Postman collection | docs/postman/PCE-AP-Write.postman_collection.json |
文件版本:與 SuiteApp com.netsuite.pceguitw AP Write RESTlet 現行實作同步。