Project

General

Profile

CR #217 » CR-016_清算執行與對帳結果無對應表且org值域脫鉤.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

CR-016 clearing-service 契約所需之「清算執行」與「對帳結果」無對應表,且 org 值域脫鉤

項目 內容
文件編號 CR-016
版本 1.2(C-14 動工前查證攔下「契約與實作脫鉤」:對帳結果回應型別——實作回陣列**、契約誤宣告單一物件;已修正產生器並真機複驗。見 §8)**
提出日 2026-09-22
提出者 WorkBuddy AI(開發團隊)
緣起 GOV-006 2.3.3(C-5 clearing-service)動工前之查證
影響文件 contracts/openapi/clearing-service.yaml(產生器 tools/gen_openapi.py)、SCS-013、SDD-001(clr schema)
關聯前例 CR-015(商戶契約與 DDL 脫鉤 → 先立 CR 再實作)、CR-012(值域完全脫鉤)

1. 緣起

clearing-service 目前僅有骨架(ClearingServiceApplication + LayeringTest),
CC_CLR 已有 3 張表(CLEARING_BATCHES/CLEARING_ITEMS/SETTLEMENTS),
契約有 3 端點。動工前逐項比對後發現:契約所要求的兩個資源在 DDL 完全不存在,
若直接實作,POST /clearing/runs 之 runId 無處可存、
GET /clearing/reconciliations/{runId} 無資料來源——兩端點將無法誠實實作。


2. 缺口明細(DDL 為 2026-09-22 實測值)

2.1 ⛔ 缺表一:清算執行(ClearingRun)——契約之核心資源

契約元素 用途 DDL 現況
POST /clearing/runs → ClearingRunView.runId 每次清算執行之識別 無(CLEARING_BATCHES 為單一批次檔,非一次執行)
status:RUNNING/MATCHED/DIFFERENCE/FAILED 執行結果 無(CLEARING_BATCHES.status 為 RECEIVED/PARSED/MATCHED/SETTLED/REJECTED,語意為檔案處理,非執行)
txCount/totalAmount/differenceCount/differenceAmount 執行彙總 無(批次表有 tx_count/total_amount,惟無差異欄)

釐清:一次「清算執行」可涵蓋多個批次檔(多組織、多檔型),
與 CLEARING_BATCHES(一檔一列)為一對多關係——故不可挪用批次表冒充執行。

2.2 ⛔ 缺表二:對帳結果(Reconciliation)

契約元素 用途 DDL 現況
GET /clearing/reconciliations/{runId} → ReconciliationView 依執行查各組織之差異 無表
org/differenceType(TIMING/FX_RATE/SUBSTANTIVE)/differenceCount/differenceAmount/resolution 差異分類與處理 無欄(CLEARING_ITEMS.status 僅有 MATCHED/UNMATCHED/EXCEPTION,無差異分類)

2.3 org 值域脫鉤(6 vs 7)

來源 值域 差異
DDL(實測) VISA/MASTERCARD/NCCC/AMEX/DFS/JCB(6) 有 DFS
契約(ClearingRunRequest.orgs[]) VISA/MASTERCARD/NCCC/AMEX/JCB/DINERS/DISCOVER(7) 有 DINERS/DISCOVER,無 DFS

判讀:DFS 即 Discover Financial Services(部分系統沿用之交換碼),
故 DFS 與 DISCOVER 為同一組織之兩種寫法;DINERS(Diners Club)
則為獨立組織,DDL 確實缺漏。三者非同一件事,須明確處置。

2.4 settlements 缺 direction 欄

契約 SettlementRequest.required = [runId, direction],
direction ∈ ACQUIRER_PAYOUT/ISSUER_RECEIVABLE;
DDL SETTLEMENTS 無此欄(僅 ORG/SETTLEMENT_DATE/金額/STATUS)。

2.5 契約示例錯誤(複製貼上)

SettlementRequest.example 為 {settlementType: EARLY, reason: 提前結清}——
① settlementType 不在 該 schema 之 properties(僅 runId/direction);
② 語意為分期提前結清,與清算結算無關。屬產生器之示例殘留,須更正。


3. 建議方案(供裁定)

# 項目 建議 理由
A 清算執行 新增 CLEARING_RUNS 表(RUN_NO UNIQUE、BUSINESS_DATE、STATUS 4 值 CHECK、TX_COUNT/TOTAL_AMOUNT/DIFFERENCE_COUNT/DIFFERENCE_AMOUNT、ORGS) 契約之核心資源;與批次表為一對多,不可挪用
B 對帳結果 新增 CLEARING_RECONCILIATIONS 表(RUN_ID FK、ORG、DIFFERENCE_TYPE 3 值 CHECK、DIFFERENCE_COUNT/DIFFERENCE_AMOUNT、RESOLUTION) 同上;差異分類為契約明載之語意
C org 值域 DDL 增列 DISCOVER/DINERS(保留 DFS 供相容),契約值域不動;服務層以契約值寫入 契約 7 值為對外語意;DFS 為既有資料之遺留碼,保留以免破壞既有列(CR-015 同一原則:不為相容而刪值)
D settlements.direction DDL 新增 DIRECTION(CHECK 2 值) 契約必要欄位,且為結算之方向語意(收單撥付/發卡應收)
E 契約示例 修正 SettlementRequest.example 為 {runId: …, direction: ACQUIRER_PAYOUT} 現例之欄位不存在且語意錯誤(分期),會誤導實作
F 對帳之資料來源 本期以 CLEARING_ITEMS 彙總(依 batch_id 所屬 org 分組、status='EXCEPTION' 計數與金額);differenceType 暫以 SUBSTANTIVE 標記,並於 SCS-013 §10 揭露「時序差(TIMING)與匯差(FX_RATE)之自動判別需帳務交易面資訊(posting 之未入帳交易、匯率表),本期不具備」 不靜默假造分類;寧可誠實標為實質差異

交付方式:一律以 V2__clearing_runs_and_reconciliations.sql 追加
(V1 基線已凍結,SAD-003 §9);契約改 tools/gen_openapi.py 後重生;
SCS-013 升版並補 §5/§7/§10。


4. 誠實揭露

  • 本 CR 之 DDL 為 Oracle 實測(CC_CLR),非文件抄錄。
  • CC_CLR 三表目前資料量待實測確認(若有既有列,settlements 加 DIRECTION 需處理 NOT NULL 之回填或設為可空)。
  • 清算檔解析(VCF/EMS/SETTLEMENT/CHARGEBACK/FEES)不在本期範圍
    (GOV-006 C-5 明載「檔格式除外」)——故 CLEARING_BATCHES/ITEMS 之寫入者
    (檔解析器)本期不實作;本期之對帳以既有批次資料為輸入,並在 SCS-013 §10 揭露。
  • CLEARING_BATCHES/ITEMS 狀態轉換未建模為 SDD-005 正式狀態機(SCS-013 §10 已載)。

5. 施行中追加之事項(我方依「替代方案推進」原則處理,供事後覆核)

# 事項 處理 理由
G 結算之費用來源 INTERCHANGE_FEE = Σ 清算明細之 CLEARING_ITEMS.FEE;NETWORK_FEE 固定 0 CC_CLR 無費率設定表,網路費率無從取得;明細之逐筆費用為檔案帶入之事實,可作 interchange。不假造費率(SCS-013 §10)
H 執行之成功碼 POST /clearing/runs 回 202(非 201) 契約明訂(產生器端即 202)——契約優先;原實作一度寫 201,查證契約後更正
I 契約 schema 命名碰撞 清算之 SettlementRequest 改名 ClearingSettlementRequest 產生器之示例登錄表以 schema 名為鍵;原命名與 installment-service 同名 → 清算撿到分期之示例({settlementType: EARLY, reason: 提前結清})。此為 §2.5 之真因,非單純示例筆誤
J 零筆數之執行 允許完成為 MATCHED 但 txCount = 0,並必記 WARN 業務日無明細時,MATCHED 與「全數相符」於狀態欄位無法區分;不新增狀態以免虛構語意,改以 txCount 與日誌辨識(比照 #C-42「零產出必須可區分」)
K 結算回應不含結算單識別碼 依契約回 ClearingRunView(202) 契約未定義結算單視圖;已產生之結算單 ID 不回傳(列為 SCS-013 §10 限制)

6. 執行清單(2026-09-22)

項 交付
遷移 V2__clearing_runs_and_reconciliations.sql——新增 CLEARING_RUNS(契約之執行資源)與 CLEARING_RECONCILIATIONS(對帳結果,含 DIFFERENCE_TYPE 3 值 CHECK);SETTLEMENTS 補 DIRECTION(2 值 CHECK);clearing_batches.org 之 CHECK 動態尋得後重建為具名(CK_CLEARING_BATCHES_ORG,增列 DISCOVER/DINERS);新增 clearing_run_no_seq
契約 改 tools/gen_openapi.py(schema 改名避碰撞)後重生;驗證 0 錯誤、全案 74 端點
程式 DDD 四層:ClearingRun(狀態由結果推導)/Reconciliation/Settlement/Org(8 值);4 埠+4 JDBC(含明細彙總);3 應用服務+組裝器;1 控制器(3 端點,成功碼 202/200/202)
品質 單測 24 例 0 失敗(領域 9/應用 15;含狀態推導、差異落列、零筆數、冪等、404/400);scan_scaffold/scan_faketest 0 命中;validate_sql 0 違規

7. 真機 e2e(8094/FREEPDB1,2026-09-22)

種子(3 批次/6 明細,業務日 2026-09-17):VISA 3 筆(含 1 筆 EXCEPTION)/MASTERCARD 2 筆/DISCOVER 1 筆。

# 情境 結果
⓪ 遷移前插入 org='DISCOVER' 之批次 被拒(舊 CHECK 6 值無 DISCOVER)——證明值域確為約束所管
① 遷移後重插 成功;CK_CLEARING_BATCHES_ORG 為具名約束、8 值
② POST /clearing/runs(3 組織) HTTP 202;CLR20260922000001/DIFFERENCE/txCount=6/totalAmount=750000/differenceCount=1/differenceAmount=100000——與手算逐項相符
③ GET /clearing/reconciliations/{runId} 1 列:VISA/SUBSTANTIVE/1/100000/「待人工覆核」
④ POST /clearing/settlements(ACQUIRER_PAYOUT) HTTP 202
⑤ 同冪等鍵重送執行 回同一 runId(未新增、未重抽序號)
⑥ DB 複核 CLEARING_RUNS:1 列,欄位與 ② 全同;CLEARING_RECONCILIATIONS:1 列(VISA);SETTLEMENTS:3 列——VISA 500000/12500/0/487500、MASTERCARD 200000/5000/0/195000、DISCOVER 50000/1250/0/48750,DIRECTION=ACQUIRER_PAYOUT

8. 追加修正(2026-09-22,v1.2):對帳結果之回應型別應為陣列

緣起:C-14(WebUI 清算與請款頁)動工前之查證——欲接 GET /clearing/reconciliations/{runId}
時發現契約與實作脫鉤:契約宣告 data 為單一物件($ref: ReconciliationView),
實作卻回陣列(List<ReconciliationView>)。

# 項目 內容
a 實測(2026-09-22) GET /api/v1/clearing/reconciliations/{runId} → 200,data 為 list(1 元素)。與原契約之單一物件不符
b 設計意圖判定:應為陣列 ①CLEARING_RECONCILIATIONS 以**(執行 × 組織)為鍵(CR-016 裁定 B)→ 一執行必然可有多列;②ReconciliationView 含 org 欄位——單一物件無從表達多組織;③SCS-013 §6 之語意為「對帳結果查詢**」(清單)。→ 實作正確,契約為缺陷
c 真因 產生器之 data_schema() 支援以 Schema[] 表達陣列(其 docstring 明載「原僅支援單一 $ref,致『分頁查帳單』等清單端點誤輸出單一物件」),但本端點於 CR-016 新增時漏了 [] 後綴——同類缺陷再次發生
d 修正 tools/gen_openapi.py:"ReconciliationView" → "ReconciliationView[]" → 重生契約
e 影響範圍(已嚴格驗證) 以「還原原始碼 → 重生 → diff -rq 全目錄比對 → 重新套用 → 再比對」確認:18 份契約中僅 clearing-service.yaml 變動(data 改為 type: array + items.$ref);OpenAPI 驗證 0 錯誤/74 端點
f 修正後複驗(真機) 同端點 → 200,data 型別 list(1 元素,org=VISA/SUBSTANTIVE/1/100000)——與修正後契約相符
g 未變更 後端程式不動(原即回陣列);SCS-013 §6 之敘述與實作一致,不須改;SDD-001 不動

⚠️ 教訓(供後續服務參考):CR-016 之 §2.5 已記錄「契約示例錯誤」之真因(示例登錄表以 schema 名為鍵),
惟同一支服務之另一端點仍有型別缺陷——顯示「新增端點時逐端點核對回應型別」尚未形成檢查點。
建議:gen_openapi.py 之 svc(...) 呼叫處,凡路徑含 {id} 之 GET 或語意為「清單/明細/歷史」者,
一律複核是否需 [] 後綴。


9. 修訂記錄

版本 日期 修訂內容
1.0 2026-09-22 提出。動工前查證發現:契約之「清算執行」與「對帳結果」兩個資源在 DDL 完全不存在、org 值域脫鉤、settlements 缺 direction、契約示例為分期殘留。建議新增兩表+補欄+修示例;對帳分類之 TIMING/FX_RATE 自動判別建議本期揭露為限制。待 BEAM PMO 裁定
1.2 2026-09-22 C-14 動工前查證攔下「契約與實作脫鉤」:對帳結果之回應型別——GET /clearing/reconciliations/{runId} 契約宣告單一物件、實作回陣列。判定實作正確、契約為缺陷(表以「執行 × 組織」為鍵、視圖含 org、語意為清單);真因為產生器之 Schema[] 陣列語法漏用(同類缺陷再次發生)。修正 gen_openapi.py → 重生;嚴格驗證僅 clearing-service.yaml 變動、0 錯誤/74 端點;真機複驗 data 為 list。後端程式不動。⚠️ 併修本文件標題編號重複(## 4. 出現兩次、## 3.1 層級錯誤)→ §5~§9 重新編號,並將兩處懸空之「§10」明確為「SCS-013 §10」
1.1 2026-09-22 BEAM PMO 裁定:A/B/D/E 一次核准;org 由 DDL 增列、契約不動。新增 §5 施行中追加事項(G 費用來源/H 成功碼 202/I 命名碰撞之真因/J 零筆數/K 結算單 ID 不回傳,均附理由)、§6 執行清單、§7 真機 e2e 七項(含遷移前後對照與 DB 複核)。單測 24 例 0 失敗;SCS-013 升 1.1、SDD-001 升 1.9
    (1-1/1)