線稿索引所有 spec最後更新 2026-07-04
specs/account-settlement.md

帳戶結算與補款

一句話:一筆錢「實際落在哪個帳戶」往往不是它「最終該歸屬的帳戶」(團費最終歸永豐、車 / 住宿 / 裝備歸中信…),系統由已落帳的每筆收款 + 費用類型路由自動推導各帳戶間應補淨額(月結互抵表),人工登錄實際的補款 / 代墊 / 回填轉帳並對帳,跨月未結的帳戶間欠款追到結清。全程 per-tenant 可設定,不寫死任何銀行 / 金流商 / 經手人。

畫面(UI = wireframe):多帳戶對帳 · 帳戶結算區 · 帳戶能力 / 路由設定見 設定 · 收款帳戶

模組歸屬:屬進階會計模組(受 module_accounting gate);不開模組的租戶用核心掛帳台帳 + Excel 自行結算。

1. 目標 / 問題

建構於 payment-accounts-reconciliation(已知「每筆錢落哪個帳戶」)與 currency-exchange(帳戶幣別 / 換匯)。荒野把收款拆到多個實際帳戶,但錢有「最終該歸屬的帳戶」,兩者常不一致 → 大量跨帳戶補款:

  • 通路 → 落地帳戶(實際入帳):綠界 → 台新;匯款到台新 → 台新;豐收款 → 永豐;匯款到永豐 → 永豐;匯款到中信 → 中信。
  • 費用類型 → 最終帳戶(資金歸集):團費 → 永豐;車費 / 住宿 / 裝備費 → 中信。
  • 帳戶能力不對稱:只有綠界能刷退、只有永豐能對外匯款、全部都能收款。退款只能走「有對應能力」的帳戶。
  • 廢帳戶(RU 台銀 / 登山社)仍零星收款,由台新代墊給永豐補平、持續記錄,最終由中信(小御)回填沖銷。
  • 跨月未結:月底常有「尾款待轉永豐」「旅行社代墊退費待小御回補」等掛帳,必須追到結清。

現行 GS 用手算淨額黑箱(中信給永豐 = −106,632 一格混了團費、車資、退費、住宿)連經手人都說不清。本檔把這層結構化成派生報表 + 可稽核的轉帳分類帳

範圍:帳戶能力欄 + 費用類型路由 + 收款費用類型 tag + 帳戶間移轉分類帳(含換匯,併 currency-exchangefx_exchanges)+ 帳戶間應收應付(掛帳)+ 月結互抵派生引擎 + 退款執行路由與對帳。 不做:正式總帳 / 合併財報(未來會計模組);自動串銀行 API 拉實際轉帳成交(先人工登錄);營收側多幣別(鐵守 TWD,見 order-money-model);客戶退款應收 / 沒收政策(在 refunds / order-money-model,本檔只管退款的執行帳戶與對帳)。

2. 資料模型

2.1 receiving_accounts 加能力欄

payment-accounts-reconciliation §2.1 的收款帳戶目錄補「帳戶能做什麼」——能力是帳戶屬性(不是寫死帳戶名):

欄位型別說明
can_receivebool default true可收款(幾乎都是 true)。
can_remitbool default false可對外匯款(ATM / 匯出退款 / 補款匯出)。荒野只有永豐。
can_chargebackbool default false可刷退(退回原信用卡)。荒野只有綠界(gateway)。
is_deprecatedbool default false廢帳戶:仍可零星收款,但其收款需由他帳戶代墊歸集(§3.4)。荒野=RU 台銀 / 登山社。
is_settlement_masterbool default false主結算帳戶:所有資金最終向它彙整、月結互抵表報為「各帳戶給 master」。每租戶恰一個(partial unique index WHERE is_settlement_master)。荒野=永豐。

既有 kind(bank / cash / gateway)+ 新增 currencycurrency-exchange §2.2)+ 本節能力欄,合起來描述一個帳戶。荒野範例:永豐 can_remit is_settlement_master、綠界 can_chargeback gateway、台新 can_receive only、RU 台銀 is_deprecated全 per-tenant 自填

兩層結算模型(重現 GS 補款總彙整的關鍵,見 §3.3):① 類別 final 帳戶=某費用類型的錢「該歸哪」(§2.2 routing;荒野 團費→永豐、車/住宿/裝備→中信);② 主結算帳戶=最終彙整池(永豐),是 final 帳戶之一但額外承擔「其他 final 帳戶與它互抵」的角色。GS「中信給永豐 −106,632」正是類別 final 帳戶(中信)對主結算帳戶(永豐)的有向淨額:中信代收的團費(欠永豐)減掉散在他帳戶的車/住宿/裝備(永豐等欠中信)。純帳戶對 pairwise 收不攏這條,必須有 master 概念。

2.2 費用類型路由 fee_category_routes(per-tenant config,新表)

「費用類型 → 最終該歸哪個帳戶」的租戶可設定路由表。費用類型由租戶自定(不寫死「團費 / 車費」字串):

欄位型別說明
idtext PK(fcr_<ulid>主鍵。
fee_categorytext NOT NULL費用類型代碼(租戶自定,如 tour_fee / lodging / equipment / transport_addon)。「類別 → 領域帳戶」通用映射:收款用它判該歸哪、成本代墊(§3.4)用它判屬哪個帳戶領域。
display_nametext NOT NULL顯示名(團費 / 住宿 / 裝備 / 加收接駁費)。
final_account_idtext FK → receiving_accounts此類型最終該歸屬的帳戶。荒野:團費 → 永豐、其餘 → 中信。
is_activebool default true停用不刪。

約束unique (fee_category)(每類型一條路由)。

2.3 payment_transactionsfee_category tag

每筆現金列(收 / 退)標一個費用類型,供結算引擎查 final_account

  • payment_transactions.fee_category:text nullable。null = 用結構派生的預設(§3.1);非 null = 經手人手動覆寫(覆蓋派生值)。

    append-only 狹窄豁免:比照 receiving_account_idpayment-accounts-reconciliation §2.2),protect_payment_transactions trigger 放寬為只准 UPDATE receiving_account_idfee_category 兩欄(逐欄 IS DISTINCT FROM,其餘任一變動即 RAISE EXCEPTION)。DELETE 仍全擋。fee_category 是「事後可補 / 可改的非金流標註」。 結算快照保護:月結 run 會把納入收款的 fee_category + final_account_id 快照進 settlement detail(§3.3);改 tag 不回頭動已產生的掛帳,要改已結期須重跑 run(受冪等護欄約束)。app 層對「已生成當期掛帳」的期間鎖 fee_category 編輯,避免無聲改寫已結算基礎。

收款載體(收入側):團費 = tour 訂單收款(派生 tour_fee);裝備 / 住宿(烏日) / 客人加收的接駁費是「現場代收」的獨立小額收款,掛 tour 訂單的 order_charge_lines(type equipment / lodging_onsite / transport_addon,見 order-money-modelcharge line type 需新增),其收款列派生對應 fee_category。GS 鐵證:裝備 / 住宿頁「現場付款」欄絕大多落中信,含合併收款(500+400(裝備)中信500+3600(尾款)永豐=住宿 / 尾款各自路由);客人加收接駁費見總表「加收 $400/人」。

車費的兩面(重要釐清):GS「車資 116,842」(202605記事 派車支出區塊 G888「車趟從永豐實拿」)主體是成本不是收入 —— 是付給車手 / 司機的派車支出(五月派車總額 479,997,月結車資 326,010 匯給管理人),由某帳戶(永豐)墊付、屬另一帳戶(中信 / 小御)領域 → 走 payables + 帳戶間成本代墊歸集(§3.4),不進收入路由表。只有「客人加收接駁費」才是 transport_addon 收入類型。兩者分開,勿混。

2.4 帳戶間移轉 account_transfers(新表,統一分類帳,append-only + 沖正)

帳戶間的一切資金移動 —— 換匯、補款、代墊、回填、退款匯出 —— 同一張表,kind 區分。併入 currency-exchange §2.3fx_exchanges(換匯 = kind='fx_conversion'):兩者本質都是「錢在帳戶間移動」,對帳頁本來就要一起呈現。

欄位型別說明
idtext PK(atr_<ulid>主鍵。
kindtext NOT NULLfx_conversion(換匯)/ replenishment(補款歸集)/ advance(廢帳戶代墊)/ backfill(回填沖銷代墊)/ refund_payout(退款匯出,§3.5)/ opening(外幣期初)/ reversal(沖正)。
from_account_idtext FK → receiving_accounts nullable出錢帳戶(opening 為 NULL)。
to_account_idtext FK → receiving_accounts nullable進錢帳戶。from / to 至少一個非 NULL。
amount_twdinteger NOT NULL實際移轉的台幣淨額(補款補淨額;換匯為換出台幣;退款匯出為退款額)。
fee_twdinteger NOT NULL default 0本筆轉帳本身的台幣手續費(匯款費 15 元等);amount_twd 已是淨額。不重記金流收款手續費(綠界手續費等已在成本帳認列,見 §3.3 結算本金)——避免雙重計成本。
currencytext nullable外幣幣別(僅 fx_conversion / opening;= to_account.currency)。
foreign_amountinteger nullable外幣實拿淨額(僅換匯)。
fee_foreigninteger nullable換匯外幣手續費(僅換匯)。
gross_ratenumeric nullable換匯牌價(provenance / 顯示)。
settlement_periodtext nullable對應哪個月結(YYYY-MMreplenishment / advance / backfill 用)。
obligation_idtext FK → account_obligations nullable此筆沖減哪條掛帳(§2.5)。
reverses_transfer_idtext nullable(self-ref,無硬 FK)沖正指向被沖的原移轉列(kind='reversal' 用);append-only audit 鏈,比照 order_charge_lines.reverses_charge_line_id 慣例。
occurred_atdate NOT NULL移轉日期。
notetext備註。
created_by_user_idtext FK → user(同庫)經手人。
created_at / updated_attimestamptz

約束 / 不變式amount_twd ≥ 0fee_twd ≥ 0kind='fx_conversion'to_account.currency = currency ≠ 'TWD'foreign_amount NOT NULL;kind='opening'from_account_id IS NULL;同幣移轉(補款 / 代墊 / 回填 / 退款匯出)currency 留 NULL(隱含 TWD)。

Append-only:DB-level trigger 擋 UPDATE / DELETE(比照 payment_transactions);改錯開 kind='reversal'(反向 from/to + 對沖金額)。換匯的 WAC 推導仍只看 kind ∈ {fx_conversion, opening, reversal} 列(currency-exchange §3.2);外幣帳戶期初跨期結轉純派生(本期期初 = 上期期末,靠這張 append-only 表的累計 cutoff,不寫合成 opening 列,見 currency-exchange §3.2)。

2.5 帳戶間應收應付 account_obligations(掛帳,一級實體,新表)

「誰欠誰多少、結清了沒」—— 跨月追蹤的帳戶間債權。月結派生產生(§3.3),轉帳沖減:

欄位型別說明
idtext PK(aob_<ulid>主鍵。
debtor_account_idtext FK → receiving_accounts欠錢方(錢在它手上、屬於 creditor)。
creditor_account_idtext FK → receiving_accounts收錢方(最終該收到的帳戶)。
amount_twdinteger NOT NULL > 0應補目標總額(恆正;方向只由 debtor/creditor 表達)。
settled_amount_twdinteger NOT NULL default 0已透過 account_transfers 沖減的累計。
reasontext NOT NULLperiod_settlement(月結互抵淨額)/ advance_repayment(廢帳戶代墊待回填)/ refund_advance(代墊退費待回補,如旅行社代墊 100,482)。
origin_periodtext NOT NULL產生月份(YYYY-MM)。
statustext NOT NULLoutstanding / partially_settled / settled(由 settled_amount vs amount 派生並落欄供查詢)。
notetext備註。
created_at / updated_attimestamptz

非 append-onlysettled_amount_twd / status 隨對應轉帳累加而更新(managed ledger,不是事實流水)。事實流水是 account_transfers;掛帳是其聚合的「待結目標」。約束 0 ≤ settled_amount_twd ≤ amount_twd方向只在 debtor/creditoramount_twd 恆正,不存負值。GS「中信給永豐 = −106,632」的負號是報表呈現(該方向其實永豐欠中信)——存成 debtor=永豐, creditor=中信, amount=106,632,負號只在 §4.1 報表 DTO 翻譯,不進儲存。

3. 邏輯 / 存取層

packages/core/src/orders/settlement.tsdb: ScopedDb)。純讀派生沿用 money helper 口徑,不另算金額。

3.1 收款費用類型派生 resolveFeeCategory(txn)

每筆收款列的預設費用類型由結構派生(可被 payment_transactions.fee_category 覆寫):

  • tour 訂單收款(含尾款)→ tour_fee
  • order_charge_lines 衍生收款 → 依 charge type 對應(lodging_onsitelodgingequipmentequipmenttransporttransport)。
  • lodging 直訂收款 → lodging
  • 查無對應 → null(結算時歸「未分類」桶,UI 提示補 tag,不靜默歸帳)。

3.2 帳戶歸屬比對

每筆收款有 landingreceiving_account_id,已落帳)vs finalfee_categoryfee_category_routes.final_account_id)。landing = final → 已在對的帳戶、不用搬。landing ≠ final → landing 欠 final 一筆。

3.3 月結互抵派生 computePeriodSettlement(db, period)

結算本金(net of fee):每筆收款進結算的金額用本金淨額 principal = payment_transactions.amount_twd − fee_twdorder-money-model §3 的金流手續費欄),不是 gross——綠界/豐收款手續費已在成本帳,補款只補實際落帳的淨額。

  1. 取 period 內收款列(現金分類帳 direction='in'),逐筆解析 final_account = route(fee_category)(§3.1–3.2),算 landing→final 的本金歸集。
  2. 退款執行(§3.5)與廢帳戶代墊(§3.4)併入帳戶的「收/出」位置。
  3. 兩層彙整(重現 GS):
    • spoke → 類別 final:每個非 final 帳戶持有的「他類別款」記為欠對應 final 帳戶。
    • 類別 final ↔ 主結算帳戶:每個非 master 的 final 帳戶(如中信)算對 master 的有向淨額 = (它代收、屬 master 類別的款) − (散在他帳戶、屬它類別的款);其餘帳戶直接欠 master。輸出即「各帳戶給 master」矩陣。
  4. 對每個非零淨額 upsert 一條 account_obligationsreason='period_settlement'origin_period=periodamount_twd>0 方向由 debtor/creditor)。
  5. outstanding = amount_twd − settled_amount_twd,由 settlement_period=periodaccount_transfers 沖減。

冪等重跑護欄:同 period 可重跑覆蓋派生(修正 fee_category/歸戶後),但 upsert 不得令 amount_twd < 已 settled_amount_twd——若派生目標降到已結額以下,保留差額為 adjustment 掛帳並標示,不靜默改寫已結歷史(§5)。每次 run 把納入的收款列 fee_category + final_account_id 快照進 settlement detail,之後改 tag 不回頭動已產生的掛帳。

跨期掛帳結轉(純派生滾動淨額,§5 定案):上期未結掛帳的殘額帶入本期互抵基數 —— 本期淨額 = 上期殘額 + 本期新增互抵。結轉基數來源嚴格限 reason='period_settlement' AND status ∈ {outstanding, partially_settled} AND origin_period < period 的掛帳殘額(amount_twd − settled_amount_twd);adjustment / advance_repayment / refund_advance 不入結轉。零 schema 變更、不動 obligation 寫入模型、不動 trigger

只入派生顯示 / 防雙重計數(關鍵不變式):結轉基數只種入互抵矩陣的顯示派生(月結互抵總覽 = getSettlementOverview,把上期殘額 seed 進 pairwise 淨額,展開列可見「上期結轉(來源期)」分量與本期新增區分),不進 computePeriodSettlement 的 obligation 寫入路徑——本期 upsert 的 origin_period=本期 掛帳金額恆只含本期新增。否則上期掛帳仍 open + 本期掛帳又含殘額 = 雙計。上期掛帳持續由 §2.5 未結追蹤追到結清(結清後自動退出下期結轉基數,滾動收斂)。

驗證對齊 GS 2026/05:登山社給永豐 6,150中信給永豐 −106,632(兩層彙整:中信淨欠永豐為負=永豐淨欠中信,主因車資 116,842)、旅行社給永豐 1,980,2056/5 已轉 879,723 → 差額 1,000,000。引擎輸出即這張「補款總彙整」。

3.4 代墊 / 回填(廢帳戶收款 + 跨帳戶成本墊付)

兩種代墊都用 kind='advance' + account_obligations(reason='advance_repayment'),回填開 kind='backfill' 沖減掛帳,皆人工登錄、追到結清:

  • 廢帳戶收款代墊is_deprecated 帳戶(RU 台銀 / 登山社)的收款由代墊帳戶(台新)先補給 final 帳戶(永豐),掛帳 debtor=回填方(中信)、creditor=代墊方(台新)。
  • 跨帳戶成本墊付:某帳戶(永豐)支付了屬於另一帳戶領域的成本(如車費=中信領域的派車支出,payables receiving_account_id=永豐、成本類別屬中信),→ 帳戶間掛帳 debtor=墊付帳戶(永豐)、creditor=領域帳戶(中信)(payer 欠 domain),併入月結互抵。方向以 GS 為準:GS 2026/05 車資 116,842 是「永豐淨欠中信 −106,632」的主因(§3.3),settlement.test 鎖定此 signed 值;computePeriodSettlementpayablesstatus=paidreceiving_account_id)× departure_costs.fee_categoryfee_category_routes.final_account 派生(paid-from ≠ domain final 時 emit,payer 欠 domain)。成本類別 → 領域 final 帳戶沿用 fee_category_routes(成本側 departure_costs.fee_category 登記路由,或人工指定領域)。

3.5 退款執行路由與對帳

客戶退款(order-money-model / refunds 已決定退多少、扣多少手續費)的執行

  • refunds 補欄(手續費以 refunds 為 canonicalaccount_transfers.fee_twd 只是快照):origin_account_id(原收款落地帳戶)、execution_methodchargeback 刷退 / remit 匯出)、admin_fee_twd(行政手續費 200 / 100…)、remit_fee_twd(匯款手續費,常 15)。
  • 退款行類別歸屬:退款掛 refund_lines(連回被退的 order_charge_lines 或帶 fee_category + amount_twd),才知道退的是團費 / 住宿 / 裝備 / 車費,進而知道該由哪個 final 帳戶承擔(退團費=永豐該吐、退住宿=中信該吐)。單一全額退款=一條行、類別 = 訂單主 fee_category。
  • 能力檢核chargeback 只能走 can_chargeback 帳戶(綠界);remit 只能走 can_remit 帳戶(永豐)。違反即 UI 擋下。
  • 代墊退費掛帳:執行帳戶(如永豐匯出 / 旅行社)≠ 退款行的 final 帳戶(如中信該吐的住宿退款)時,開一條 account_obligationsreason='refund_advance',debtor = final 帳戶、creditor = 執行帳戶),按 period 彙整 —— 這就是 GS「旅行社先代墊回補 −100,482(小御之後回補旅行社)」的來源。
  • remit 退款開 account_transfers kind='refund_payout'(從 can_remit 帳戶出,amount_twd=退款額、fee_twd=匯款費快照),併入 §3.3 月結(退款補款表的「永豐出 / 中信收」等)。chargeback 退款金流由 gateway 處理、不產生帳戶間移轉(只記對帳)——信用卡原路退刷已串真金流executeRefundmarkRefundPaid 的 tx 外先真打 ECPay DoAction R(per-tenant 憑證、test provider mock),成功寫 provider_refund_no、失敗自動降級手動並記 refund.gateway_refund_* audit(見 order-money-model 退款流程)。
  • 退款批次匯出(對齊 GS「月退款表單」M 欄「永豐匯出」):當月 execution_method='remit' 的退款可批次匯出同一張標準四欄轉帳檔(收款人戶名 / 帳號 / 銀行代號 / 退款額),與出款批次(control-finance §5.5共用匯出格式、各自清單(退款清單獨立於領隊 / 廠商出款)。收款銀行三欄來自退款登記時填的退款帳戶(refunds 退款帳號 / 戶名 / 銀行代號欄,對應 GS 月退款表單 N/O 欄——這些欄於 F4 補入 refunds,連同 createRefund 捕捉 execution_method / 退款帳戶;F3 退款執行引擎已 ready 但生產建立流程在 F4 接通)。chargeback 列不進匯出(gateway 自動刷退)。
  • 退款額派生:退款額 = 原收 − admin_fee_twd − remit_fee_twd(GS 實例 3,365 = 3,580 − 200 − 15),公式在 refunds,本檔只負責執行帳戶、類別歸屬與對帳落點。
  • 退款現金列的 fee_twd(收斂 order-money-model §4「退款 fee_twd 預設 0」)remit 退款的 payment_transactions(out, refund)fee_twd = remit_fee_twd(匯款費 15),使 net_cash 正確含此筆銀行成本。order-money-model 的「預設 0」指不退還原始收款手續費,與此筆退款自身的匯款費是兩回事。匯款費由客人承擔(已自退款額內扣,客人實拿 = 原收 − admin − remit),公司淨留 = admin_fee(不另計銀行費 opex,避免重複;客人已付)。chargeback 退款 fee_twd=0(gateway 處理)。

4. UI 與權限

4.1 版面:/admin/reconciliation 加「帳戶結算」區

對帳頁是帳戶層級視圖的家,結算疊在其下(不另開 nav):

  • 月結互抵總覽:選月份 → 各帳戶應補最終帳戶的淨額矩陣(= GS 補款總彙整表,派生);每格可展開組成(團費 from 綠界 / 台新…內扣手續費)。跨期結轉:上期未結掛帳殘額已折入本期淨額基數(§3.3),總覽上方以資訊列標示結轉小計 + 來源期明細,展開對應列可見「上期結轉(來源期)」分量與本期新增區分。
  • 跨帳戶移轉登錄:記補款 / 代墊 / 回填 / 換匯 / 退款匯出(from → to、淨額、內扣手續費、kind、對應月、沖正);與換匯紀錄(currency-exchange §4.1)同一張 account_transfers 列表。
  • 未結欠款追蹤outstanding / partially_settled 掛帳清單(誰欠誰、origin period、結清進度、剩餘待轉)。對齊 GS「尾款待轉 1,000,000」「小御欠旅行社 100,482」。
  • 退款執行:退款列表帶原收款帳戶 + 執行方式(刷退 / 匯出)+ 手續費,能力檢核(部分已存在 refunds.receiving_account_idpayment-accounts-reconciliation §5)。

4.2 設定:/admin/settings · 收款帳戶

帳戶能力欄(can_remit / can_chargeback / is_deprecated)與費用類型路由表(fee_category_routes)是租戶配置,掛在收款帳戶設定 tab(payment-accounts-reconciliation §4)。

4.3 權限

action用途opadminaccountant
account_transfer.read看移轉 / 結算 / 掛帳(對帳頁)
account_transfer.manage登錄補款 / 代墊 / 回填 / 退款匯出 / 換匯
account_settlement.read跑月結派生 / 看互抵與掛帳
  • 單一資源account_transfer.read/manage 涵蓋所有 kind(含換匯 fx_conversion)——同一張表只一套權限,避免雙權限在沖正 / 共用列表端點不一致。原 currency-exchange §4.2fx_exchange.* 收斂為本資源(換匯登錄 UI 是 kind 分流,不是另一資源)。
  • 費用類型路由 / 帳戶能力維護走設定頁既有 receiving_account.manage(admin / accountant)。
  • 退款執行沿用 refunds 既有權限,本檔只加帳戶能力檢核。

5. 開放議題

  • 月結觸發:人工按「產生本月互抵」還是排程月底自動跑?第一版人工觸發(可重跑覆蓋當月派生掛帳,已人工結的轉帳不動)。
  • 車費已釐清(2026-06-25):分兩面——(a) 派車支出(付車手 / 司機)= 成本,走 payables,帳戶間墊付經 §3.4「跨帳戶成本墊付」歸集(GS「車資 116,842」屬此,非收入);(b) 客人加收接駁費 = 收入 transport_addon fee_category,走收款路由。transport_addon charge type 與「成本類別→領域帳戶」對應待在 order-money-model 補。第一版可先人工 tag。
  • 跨期掛帳結轉(已定案 2026-07-03):obligation 跨月未結自動帶入下月互抵基數,採「純派生滾動淨額」模型 —— 本期淨額 = 上期殘額 + 本期新增(§3.3)。結轉基數只滾動 reason='period_settlement' 的未結殘額(origin_period < period),只入 getSettlementOverview 顯示派生、不進 computePeriodSettlement 寫入路徑(本期 upsert 恆只含本期新增,防與仍 open 的上期掛帳雙計);上期掛帳仍由未結追蹤追到結清,結清後自動退出結轉。零 schema 變更、不動寫入模型 / trigger
  • op 可否登錄補款:第一版鎖 admin / accountant;線控現場若需登錄代墊再放寬(比照換匯 §currency-exchange 開放議題)。
  • gateway 刷退對帳自動化:第一版人工核對綠界刷退;未來串 gateway 對帳檔半自動。
  • 與內外帳正交:帳戶結算是「現金真相 / 錢在哪」維度,與 internal-external-ledger「成本可不可申報」正交,不混。

Changelog

版本日期變更Commit
52026-07-04G2 深水兩點(§2.4 / §3.5):(1) 外幣帳戶期初跨期自動結轉純派生落地(account_transfers 累計 cutoff,本期期初 = 上期期末,不寫合成 opening 列;SSOT 在 currency-exchange §3.2);(2) chargeback 信用卡原路退刷已串真金流——executeRefund 先真打 ECPay DoAction R(per-tenant 憑證、test provider mock),失敗自動降級手動並記 audit(sandbox 實測 pending)。
42026-07-03跨期掛帳結轉定案(§3.3 / §4.1 / §5 開放議題收斂):採「純派生滾動淨額」—— 上期未結 period_settlement 殘額(origin_period < period AND status ∈ {outstanding, partially_settled})帶入本期互抵基數(本期淨額 = 上期殘額 + 本期新增)。只入 getSettlementOverview 顯示派生(月結互抵總覽加結轉小計 + 來源期明細,矩陣展開列見「上期結轉」分量)、不進 computePeriodSettlement 寫入路徑(本期 upsert 恆只含本期新增,防與仍 open 的上期掛帳雙計);上期掛帳由未結追蹤追到結清後自動退出結轉。零 schema 變更、不動 obligation 寫入模型 / triggerbuildSettlementMatrix 加選填 priorOutstanding 入參、新增 loadPriorOutstandingObligations
32026-06-28金流→會計流程一致性修正 #5(§3.5):明示 remit 退款的 payment_transactions(out, refund)fee_twd = remit_fee_twd(匯款費 15,使 net_cash 正確),收斂 order-money-model §4「退款 fee_twd 預設 0」的張力(該預設指不退還原始收款手續費);匯款費由客人承擔(已自退款額內扣),不另計銀行費 opex 避免重複。
22026-06-28§3.5 退款批次匯出:當月 remit 退款可批次匯出標準四欄轉帳檔(收款人戶名/帳號/銀行代號/退款額),與出款批次(control-finance §5.5共用匯出格式、各自清單(對齊 GS「月退款表單」M 欄「永豐匯出」+ N/O 退款帳號欄);chargeback 列不進匯出。未實作。
12026-06-25初版(子系統 B,spec / wireframe 先行):建構於 payment-accounts-reconciliation + currency-exchangereceiving_accounts 加能力欄(can_receive / can_remit / can_chargeback / is_deprecated);fee_category_routes(費用類型→最終帳戶,per-tenant);payment_transactions.fee_category(結構派生 + 可覆寫 tag,append-only 狹窄豁免擴為兩欄);統一 account_transfers 分類帳(併入 currency-exchange 原 fx_exchangeskind 區分換匯 / 補款 / 代墊 / 回填 / 退款匯出 / 期初 / 沖正,append-only + reversal);account_obligations(帳戶間掛帳一級實體,月結派生產生、轉帳沖減、追到結清)。結算引擎 computePeriodSettlement 自動推導月結互抵(對齊 GS 2026/05 補款總彙整:登山社 6,150 / 中信 −106,632 / 旅行社 1,980,205 / 差額 1,000,000)。退款執行路由(refundsorigin_account_id / execution_method / admin_fee_twd / remit_fee_twd + refund_lines 類別歸屬 + 能力檢核)。新 resource account_transfer(涵蓋換匯,收斂 fx_exchange.*)/ account_settlement。UI 在對帳頁加帳戶結算區、設定頁加能力 / 路由。已過 self / codex 對抗式審查強化:加主結算帳戶 is_settlement_master + 兩層彙整(重現 GS 中信↔永豐 −106,632,純 pairwise 收不攏);結算用本金淨額(amount − 金流手續費,不雙計成本);refund_advance 掛帳由退款執行依類別歸屬建立(GS 旅行社代墊 100,482);掛帳 amount_twd>0 方向僅 debtor/creditor;月結重跑冪等護欄(不得低於已結 + fee_category 快照鎖);車費釐清(查 GS 202605記事 G888「車趟從永豐實拿 116,842」=派車支出成本,非收入):派車支出走 payables + §3.4 跨帳戶成本墊付歸集,客人加收接駁費才是 transport_addon 收入類型。未實作。