Project

General

Profile

CR #208 » CR-007_帳單交易未依期間過濾致重複計收.md

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

 

CR-007 帳單交易未依期間過濾 → 每期重複計收(含循環息)

項目 內容
編號 CR-007
版本 1.2(2026-09-20 核定並執行完成)
狀態 ✅ 已核定/已執行並實測通過
提出日期 2026-09-20
核定日期 2026-09-20(BEAM PMO:採 A——posting 交易查詢加 from/to,statement 以本期區間查詢;逾時改可組態+重試)
提出者 WorkBuddy AI
影響文件 SCS-011 §7/§8.1/§10、SCS-007(新增交易查詢之期間參數)、contracts/openapi/posting-service.yaml(由 tools/gen_openapi.py 產生)、PostingQueryPort/PostingServiceClient
關聯 CR-006(已核定並執行——本次缺陷由其「每月計息」而放大為利息重複計收)、HANDOVER §3.C #C-38
嚴重度 🔴 P0(帳務金額錯誤)——客戶端多收款,且循環息每期累加

1. 背景

CR-006 核定後,statement-service 已於結帳前觸發計息(SCS-011 §8.1),
實機執行 10 月帳單週期(M01/businessDate=2026-10-15)成功:
帳單產生、finance_charges=2064。

驗證金額時發現該數字不對——10 月帳單把兩個月的利息都算進去了。
追查後確認根因不在計息,而在帳單彙總交易時沒有任何期間過濾。


2. 實測證據(真實 Oracle,2026-09-20)

2.1 帳單金額(同一帳戶 018f2a3b-…-0010)

帳單月份 前期餘額 繳款 消費 循環息 費用 新餘額
2026-09 0 12,345 55,500 0 300 43,455
2026-10 43,455 12,345 67,845 2,064 300 101,319
  • 繳款 12,345 與費用 300 兩期完全相同 —— 同一筆交易被兩期帳單重複計收。
  • 帳單明細筆數:9 月 3 行 → 10 月 6 行(舊的 3 行原封不動再列一次)。

2.2 循環息

CC_POSTING.TRANSACTIONS 之 INTEREST 交易:

TX 2026-09-15  amt=1032   (CR-006 實測時手動觸發)
TX 2026-10-15  amt=1032   (本次 10 月週期自動觸發)

10 月帳單 finance_charges = 1032 + 1032 = 2064
——9 月已發生之利息又在 10 月帳單計收一次。

依此機制,第 N 期帳單會計收第 1~N 期全部利息,呈等差累加(第 3 期 = 3096、第 4 期 = 4128…)。


3. 根因

SCS-011 §7 明載:「由 posting-service 取當期交易與餘額」。
但實作並未做到:

// StatementGenerationService.generateOne
var transactions = postingClient.getTransactions(accountId);   // 無任何日期參數

// PostingQueryPort
List<TxnSnapshot> getTransactions(UUID accountId);              // 簽章本身就沒有期間

// PostingServiceClient
get("/api/v1/accounts/" + accountId + "/transactions?page=1&size=200");  // 僅分頁

contracts/openapi/posting-service.yaml 之
/api/v1/accounts/{accountId}/transactions 僅有 page/size 兩個查詢參數——
契約層就不支援期間過濾,故這不是「忘了傳參數」,而是端到端都沒有這個能力。

結論:文件(§7「當期」)與實物(抓全部)不一致,屬實作缺陷,非設計取捨。


4. 為何現在才爆(與 CR-006 之關係)

  • 9 月帳單產生時,posting 尚無任何 INTEREST 交易 → finance_charges=0,
    缺陷存在但被掩蓋(消費/繳款重複計收其實早已發生,見 §2.1)。
  • CR-006 讓利息每期自動產生後,缺陷轉為利息逐期累加,金額放大且持續惡化。

5. 選項與建議

# 方案 做法 優點 代價/風險
A ⭐ 查詢端加期間參數 posting 交易查詢端點新增 from/to(postingDate 區間);statement 以本期區間查詢 語意最正確(與 §7「當期」一致);無跨服務寫入 須改契約(gen_openapi.py)、posting 查詢邏輯、PostingQueryPort 簽章、statement 客戶端;分頁上限仍須處理
B 已出帳標記 posting 交易表加 statement_id,產生後回寫標記 不會遺漏 跨服務寫入權威表(posting 擁有資料所有權),一致性複雜,須 Saga
C 帳單端水位 statement 自存 LAST_BILLED_AT,依 postingDate > 水位 過濾 不動 posting 契約 水位與分頁上限(200 筆)疊加,仍可能截斷;新增狀態須與 Checkpoint 續跑一致

建議採 A:
① 與 SCS-011 §7 既有文字一致(改的是實物,不是文件);
② 不讓 statement 寫入 posting 的權威資料——符合「權威來源單一」原則;
③ 期間條件下推至 SQL,可避開分頁截斷(C 辦不到)。

A 之配套(須同時定奪):分頁 size=200 之上限——期間過濾後單期交易仍可能 >200 筆,
建議改為「依期間查完為止」(分頁迴圈)或將 size 上限提高,並於 SCS-011 §10 明列。


6. 影響範圍(若採 A)

產出 變更
tools/gen_openapi.py posting-service 交易查詢端點新增 from/to 參數(不可手改 YAML)
contracts/openapi/posting-service.yaml 由產生器重產
posting-service 查詢邏輯支援 postingDate 區間(SCS-007 須補對應章節)
statement-service PostingQueryPort.getTransactions 簽章加期間;PostingServiceClient 帶參數
SCS-011 §7/§8.1/§10 明列期間條件與「計息日 periodEnd 須落在本期區間內」
測試 新增「跨期不重複計收」之整合與單元測試(TRP-001 須補編號)

7. 風險揭露(未核定前之現況)

⚠️ 在核定前,statement-service 之帳單週期批次會產出金額不正確之帳單
(重複計收歷史交易與歷史利息)。
目前僅於本機開發環境執行過,未於任何客戶環境啟用。
建議核定前不要再對同一帳戶重複執行週期批次(每次執行都會再累加一層重複金額)。


8. 核定結果

# 核定 日期
5(方案選擇) ✅ 採 A——posting 交易查詢加 from/to,statement 以本期區間查詢 2026-09-20
A 配套(分頁上限) ✅ 逐頁取至「不足一頁」為止(size=200),頁數上限 20(4,000 筆/期),超限丟錯(CC-STM-5001)。不靜默截斷、亦不調高共用 SizeParam 上限以免牽動全平台 2026-09-20
相關:#C-40 逾時 ✅ 改為可組態+重試(批次語意下放寬預設值) 2026-09-20

9. 實作結果與實測證據

9.1 區間語意(半開 (from, to])

邊界 語意 理由
to POSTING_DATE <= to(含) 結帳日當日計收之循環息 postingDate 即為結帳日,不含將漏收當期利息
from POSTING_DATE > from(不含) 上期結帳日(含)前之交易已於上期計收,含入將重複計收

9.2 實測(真實 Oracle,2026-09-20 07:40)

① 交易查詢端點之區間語意

帳戶 018f2a3b-…-0010,帳上共 7 筆交易(含 09-15 與 10-15 兩筆 INTEREST):

查詢 筆數 09-15 利息 10-15 利息
無 from/to 7 ✅ 在 ✅ 在
from=2026-09-15&to=2026-10-15 6 ❌ 已排除(from 不含) ✅ 保留(to 含)

→ 半開 (from, to] 之語意經實機證實,且兩個邊界各自發揮作用。

② 帳單週期(關鍵:不再累加)

執行 11 月週期(businessDate=2026-11-15):

帳單月 前期餘額 繳款 消費 循環息 費用 明細
2026-09 0 12,345 55,500 0 300 3
2026-10 43,455 12,345 67,845 2,064(累加) 300 6
2026-11 101,319 0 0 1,066 0 1
  • 11 月帳單只含本期之 1 筆利息,歷史繳款/消費/費用全數不再重複計收。
    (若缺陷仍在,finance_charges 應為 1032+1032+1066 = 3,130、明細 8 筆。)
  • 利息為 1,066 而非 1,032 係正確:10/15→11/15 為 31 天
    (67,845 × 18.5% × 31/365 = 1,066.4 → 1,066)。

③ 逾時(#C-40 一併驗證)

11 月週期為重啟後之首次呼叫(冷路徑),過去會於 800ms 逾時而致
「帳戶被跳過、statementCount=0」;本次首次即成功(statementCount=1)。

⚠️ 歷史髒資料仍在

10 月帳單之 2,064 與 11 月帳單之 prev_balance=101,319 皆為修復前產生,
金額仍是錯的。修復僅保證往後之週期不再重複計收;
驗證成效一律以新月份為準。


9. 修訂記錄

版本 日期 說明
1.0 2026-09-20 初版。實測發現帳單未依期間過濾交易,致繳款/費用/循環息重複計收;提列三方案並建議採 A
1.2 2026-09-20 BEAM PMO 核定採 A 後執行完成並實機實測:①交易查詢新增 from/to,半開 (from, to] 經實機證實(09-15 排除、10-15 保留);②11 月帳單僅含本期 1 筆利息 1,066、明細 1 筆,不再累加(缺陷仍在時應為 3,130/8 筆);③逾時改可組態後冷路徑首次呼叫即成功。並揭露歷史髒資料仍在
    (1-1/1)