線稿索引所有 spec最後更新 2026-07-01
specs/control-finance.md

團控財務與請款核簽

一句話:團控財務是每個梯次的營運財務工作台(訂單掛帳、梯次成本、梯次優惠價、供應商 / 嚮導 / 佣金請款核簽);金額與狀態計算以 order-money-model 為 SSOT,本檔只定義團控如何產生、審核與呈現這些金流事實。

畫面(UI = wireframe):團控 · 請款列表 · 請款詳情 · 請款列印

1. 範圍

團控財務包含四個彼此相關、但資料責任不同的功能:

功能入口資料責任SSOT
訂單掛帳 / 折讓 / 加收/admin/control、訂單詳情對特定訂單增減應收,保留原因與 actororder_charge_lines(type='manual_adjustment'/'reversal')
梯次成本/admin/control/[departureId]/costs登記房費以外的供應商成本,供彙總與轉請款departure_costs(設計表)+ room cost 來源
梯次優惠價/admin/control、梯次設定主管對整梯設定促銷覆寫價;新單才套用departures promo 欄位 + resolveDeparturePrice
請款核簽/admin/payments / payment-request UI線控建單、主管核准、會計依紙本付款並 mark paidpayables / payable_items

不在本檔重做

2. 設計原則

  • 應收只寫分類帳:任何掛帳、折讓、房升補款、沖正都 append order_charge_lines;不改訂單外殼金額,也不另開第二套應收公式。
  • 改錯靠反向列:已入帳的金額列不可 UPDATE / DELETE;錯誤以 type='reversal' 或相反 signed amount 表達。
  • 成本登記與請款核簽分離departure_costs 是成本簿;payables 是要交給主管 / 會計的付款核簽單。成本可轉成 payable,但兩者不是同一件事。
  • 紙本流程是業務事實:請款單要可列印,簽核與匯款責任分離;系統記錄 audit,但不取代網銀付款。
  • per-departure 工作留在團控:掛帳、成本、請款從 /admin/control 進;跨梯次財務總覽才是獨立 nav。

3. 訂單掛帳

掛帳是主管對單筆訂單做一次性應收調整:

  • 折讓:負的 order_charge_lines
  • 加收:正的 order_charge_lines
  • 沖正:append reversal,指向或描述被沖正的原列。

必要欄位語意:

欄位語意說明
order_id / order_line_id可掛整單,也可掛某個商品 line。
amount_twdsigned integer TWD;負數減應收,正數增應收。
source_type / source_idadmin_manualroom_upgradediscount_code 等來源;方便 audit 與報表分群。
description必填,說明調整原因;UI 不允許空白原因。
created_by_user_id同庫 user FK;保留責任歸屬。

掛帳後必須在同一交易中:

  1. append charge line;
  2. reconcile payment_schedules 到最新應收;
  3. 重算 payment_state / refund_state
  4. 執行 assertOrderFinancialInvariants
  5. audit_log

已落地:掛帳 / 折讓 / 沖正全套在 packages/core/src/orders/manual-charge.tsapplyManualAdjustment / reverseChargeLine,上述五步協定一個 tx,b2-finance 測試齊)。

3.1 掛帳角色矩陣(order.adjust

權限 resource 為 order,action adjust(掛帳 / 折讓 / 加收 / 沖正,append order_charge_lines)。對齊 packages/core/src/permissions-ac.ts

角色掛帳 / 折讓 / 沖正(order.adjust說明
admin全權
op線控可對單筆訂單掛帳 / 折讓 / 沖正
accountant❌(唯讀)只持 order.read;看得到 charge line,不能寫
sales無 adjust(只 order.read/create/update
customer

4. 梯次成本

梯次成本是線控在出團前後登記的供應商應付項目,例如山屋、旅宿、巴士、餐廳、保險、入山證、嚮導、地接與雜支。

成本彙總分兩個來源:

  • 房費成本:由分房資料持有,團控財務只讀取彙總,不複製。
  • 非房費成本departure_costs 登記多筆成本明細,允許出團前修正;每次 create/update/delete 都寫 audit。

departure_costs 的基礎設計語意:

欄位語意說明
departure_id成本屬於哪個梯次。
category住宿、交通、餐飲、保險、門票、入山證、嚮導、地接、雜支、其他。
vendor_name供應商名稱,可帶入 payable payee。
description成本摘要。
amount_twd真實成本,整數 TWD(一律據實登記,含無發票支出)。
payable_id若已轉請款核簽,連到 payables.id,避免重複請款。

轉請款時,系統以成本列產生 payables(source_type='departure_cost') 與對應 payable_items;成本列只記 payable_id,不複製 payable 狀態。

內外帳維度另立departure_costs 的「可申報額 / 是否有憑證 / 支出證明單 / 淨利試算 / 申報 / 嚮導申報覆核(review_status)/ 外幣維度(currency / foreign_amount / 內外帳匯率快照)」屬內外帳層,移至 internal-external-ledger(外幣的匯率 / 換匯 / 外幣帳戶機制再下放 currency-exchange);「範本來源維度(template_line_id / quantity_mode / headcount_components / group_divisor / quantity / unit_price_twd / is_manual_override)」屬 cost-template(系列團成本範本,行程綁範本後梯次自動帶入成本行;數量=固定值或 ceil(Σ人頭/組距),人頭含旅客 / 領隊 / 協作 / 司機,動態重算)。本檔只擁有梯次成本的資料 SSOT(基礎欄 + 轉請款)。amount_twd 仍是真實成本存量真相(外幣列由外幣 × 內帳匯率快照 round 派生);團控顯示(§7)聚合 amount_twd 真實額成本明細,但淨利 / 收入 / 毛利不在團控顯示(敏感財務)。

成本登記 UI 落點(2026-06-24 決策,覆寫 2026-06-23):成本+憑證登記是 per-departure 現場作業(§2「成本從 /admin/control 進」原則),回到團控:線控走團控成本登記頁/admin/control/[departureId]/costsdeparture_cost.create),帶隊嚮導 / 領隊走嚮導 portal(scoped 到自己梯次)。獨立 nav「內外帳 / 淨利」保留淨利分析 + 證明單核簽(ledger.read / ledger.approve,僅 admin / accountant)。三 surface 寫同一份 departure_costs。2026-06-23「成本登記不在團控、團控只讀」已反轉(過度收斂使線控 / op / 嚮導無入口)。

5. 請款核簽(Payables)

UI 名稱可沿用「請款單」,資料層是 payables / payable_items。它代表旅行社要付款給第三方,不代表客戶退款本身。

5.1 付款對象

payee_type用途
supplier旅宿、交通、餐飲、保險、地接等供應商。
guide嚮導費。
staff_commission業務 / 內部佣金。
other其他需走請款核簽的出款。

5.2 狀態機

draft -> submitted -> approved -> paid
submitted -> rejected
draft/submitted/approved/rejected -> cancelled
paid is terminal

狀態轉換規則:

  • 建單人可以建立、編輯草稿、送審、取消自己的非 paid 單。
  • 建單人不得核准自己的單。
  • 會計只能依已核准請款單 mark paid,並輸入 bank_reference
  • 每個狀態轉換都寫 audit_log,target type 為 payable

5.3 紙本與 PDF

  • 現行列印是 HTML @media print + window.print()。請款單、支出證明單、內外帳成本報表(見 internal-external-ledger)共用此列印路徑。
  • pdf_generated_at / paper_printed_at 記錄列印動作;不寫永久 PDF URL。
  • 若未來要 server-side PDF 或 GCS archive,另開文件與 route handler,不在本檔偷渡。

5.4 權限

權限 resource 為 payable,格式為 payable.action

角色readcreatesubmitapprove/rejectmark_paidprint/cancel
admin
op
sales
accountant
customer

5.5 出款批次(月結匯出)

歸屬:§5.5 出款批次 / 批次轉帳匯款檔 / §5.6 廠商目錄屬進階會計模組(受 module_accounting gate);§5.1–5.4 的 payables 請款核簽留核心(掛帳台帳)。

現實裡同一收款對象的多筆 payable 常月結一次匯出(GS:嚮導每月 5 號前把當月各梯次領隊費彙整成一筆匯出,扣勞健保自付後實拿,並產批次轉帳清單)。本節在 payables 上加「出款批次」這層出納動作 —— 不是 HR / 算薪(薪資級距 / 勞健保投保 / 最低薪資 / 考勤一律不在此,由外部記帳 / payroll 提供數字)。

payout_batches(新表)

欄位說明
id主鍵(pob_<ulid>)。
period出款歸屬月(YYYY-MM)。
payee_scope此批次涵蓋的對象(如 guide 全體 / 指定對象集)。
paid_from_account_id從哪個帳戶匯出(FK receiving_accounts,須 can_remit,如永豐)。
bank_reference批次轉帳參照號。
statusdraft → confirmed(confirmed 即把納入的 payable 一併 mark paid)。
created_by_user_id / created_at

payout_batch_lines(每對象一列):彙整一個收款對象(嚮導 / 員工 / 供應商)當月應付 → gross_twd = Σ 已核准 payable + Σ 直接外加(行政薪);減通用扣款項 deductions(JSON / 子表,每筆 {label, amount_twd}label 租戶自定:荒野填「勞健保自付」「借支」,系統不知道勞健保是什麼,零法規寫死)→ net_twd = gross − Σ deductions。確認時把該對象當月納入的 payables 全部 mark paid、連 payout_batch_id,並依 net_twdaccount-settlement 記一筆出帳(接對帳)。

payout_batch_lines 補欄說明
payee_type / payee_ref_id收款對象(guide / staff / supplier)+ 對應目錄列(取其收款銀行三欄,§5.6)。
confirmed_at / confirmed_by_user_id已對該對象確認(廠商走自助 token 頁確認,confirmed_by=null;出納亦可代勾)。僅 confirmed 列得進批次轉帳檔(§下)。嚮導 / 員工薪資視租戶政策可預設免確認。
confirm_token / supplier_confirm_note廠商自助確認頁的時效 token + 廠商回填文字(確認備註 / 異議)。
  • 自動彙整明細(派生,重現 GS「廠商結帳」AG2:AH10):一列的 gross_twd 由其納入的已核准 payable 加總,每筆 payable 經 departure_costs → departures 派生「日期 + 團名 + 金額」明細列,UI 自動組出「0522奇萊主北 29,400 / 0530加羅湖 17,640 / 共 47,040」對廠商核對。明細是唯讀派生,不另存。

  • 廠商結帳流程:當月各梯次的供應商成本 departure_costs(supplier) → 覆核 approvedconvertCostsToPayable(payee_type='supplier')(同一供應商可跨梯次多筆)→ 出款批次依供應商 group 成一列 → 廠商自助確認(token URL) → 勾 confirmed → 納入批次轉帳檔。

  • 廠商自助確認頁(tokenized URL,免登入,比照同意書 /consent/[orderId]:每個廠商月結列產一個 confirm_token,對外頁 /supplier-confirm/[token](綁 payout_batch_line_id、時效性)。廠商看到本月明細(日期 + 團名 + 金額 + 合計)→ 輸入文字(確認備註 / 異議)→ 按「確認」→ 回寫 confirmed_at / confirmed_by_user_id(=null,對外確認標記為廠商)/ supplier_confirm_note。出款端有「複製罐頭訊息 + 網址」鈕(罐頭文走 emails / message-templates,出納貼進 LINE 給廠商)。對外頁只露該列明細、不露其他廠商 / 帳務(比照 order-scoped 防 IDOR)。confirmed 列進批次轉帳檔。

  • 行政薪「直接外加」的輸入(GS 領隊薪資申請單底部「行政薪水(直接外加)」):純內勤 / 兼帶隊者的固定行政薪沒有 per-trip payable 來源,在出款批次頁手動加一筆「直接外加」(出納輸入、上月可帶入 placeholder),與通用扣款項一起 key。這是出納輸入、非 HR 固定薪主檔(不存薪資級距 / 不算勞健保 / 不管投保,守住不碰 HR)。領隊費則由已核准 payable 自動帶入、不在此手加。

    • 單一真相連結(避免與損益重複出帳):「直接外加」確認時生成 / 連結對應的 operating_expenses(salary_admin)payout_batch_line.operating_expense_id),毛額 = 損益費用列、淨額 = 批次匯出。行政薪不得在損益頁與批次頁各登一次(operating-expenses §2.2 型態④)。
  • 批次轉帳檔(標準銀行格式,per-tenant 出款帳戶,非寫死永豐):confirmed 列產可下載批次轉帳檔(CSV / 剪貼),台灣銀行批次匯款的通用四欄(對齊 GS 領隊薪資申請單 X60–AA60、永豐企業網銀 import 格式,但任何銀行批次匯款皆吃這四欄,不綁特定銀行):

    內容寫入來源長度
    (1) 收款人戶名payee 收款銀行三欄之戶名§5.6 目錄1~65
    (2) 收款帳號payee 帳號§5.6 目錄2~16
    (3) 收款銀行代號payee 銀行代號§5.6 目錄7
    (4) 交易金額net_twd本列整數

    匯出帳戶 = 批次 paid_from_account_id(須 can_remit,荒野=永豐)。退款匯出(account-settlement §3.5)共用同一張四欄格式、各自清單。缺收款三欄者匯出前 UI 標紅擋下。亦保留 HTML print 對帳用。

  • 與認列分離:領隊費的成本認列仍是 per-trip departure_costs(進毛利,internal-external-ledger);本批次只是把這些已核准 payable 的出款執行月結成一筆。雇主勞健保負擔(86,144 例)走 operating-expenses 辦公室 / 人事 opex 逐筆,與本批次的「自付扣款」連動但不耦合。

  • 多租戶:純出納 + 通用扣款項,任何租戶可用(無 jurisdiction payroll)。HR 算薪 / 投保 / 考勤留外部或未來整合。

權限:沿用 payablepayable.mark_paid 才能 confirm 批次,admin / accountant)。

5.6 出款對象收款銀行資料 + 廠商目錄

批次轉帳檔(§5.5)需收款人「戶名 / 帳號 / 銀行代號」。這是出款對象資料、非 HR 員工主檔(不存薪資級距 / 投保)。三類收款對象各自掛三欄收款銀行資訊:

收款對象落點收款銀行欄(皆 nullable)
嚮導 / 領隊guidesguide-portalpayee_account_name / payee_bank_code / payee_account_number
員工(行政薪)staff 出款資料同三欄
供應商 / 廠商suppliers 目錄同三欄

suppliers(廠商目錄,新表)——廠商會跨月重複出現(阿凱登山企業社 / 協作包餐 / 儷萊春陽 / 接駁車外派…),建檔一次免月月重 key,收款銀行資料隨廠商走:

欄位型別說明
idtext PK(sup_<ulid>主鍵。
nametext NOT NULL廠商名。
category_hinttext nullable慣用類別提示(協作 / 車 / 住宿 / 裝備…,租戶自定,非硬枚舉)。
payee_account_name / payee_bank_code / payee_account_numbertext nullable收款銀行三欄(同上)。
statustext NOT NULL default 'active'active / inactive(停用不刪)。
created_at / updated_attimestamptz
  • departure_costs.supplier_id(新欄,nullable,FK → suppliers:成本登記時可選已建檔廠商(自動帶名 + 收款資料),亦保留 vendor_name 自由文字(未建檔的一次性廠商)。月結 group 以 supplier_id(已建檔)優先、否則 fallback vendor_name
  • 維護:廠商目錄維護在設定 / 廠商頁(supplier.manage,admin / accountant / op);收款銀行資料 write 後遮罩顯示(比照其他收款資訊)。全 per-tenant 自填,不預載任何廠商 / 銀行
  • 新 permission resource supplierread / manage)。

6. 梯次優惠價

梯次優惠價是主管對整個 departure 設定的促銷覆寫價,只影響設定後的新訂單

  • 基準定價與通路解析由 trip-pricingresolveDeparturePrice 擁有。
  • promo 覆寫是 resolver 的最上層分支;本檔不另寫第二份價格解析。
  • 成交時,實際使用的價格通路與單價快照寫入 product line;之後修改 promo 不回寫既有訂單。
  • 前台可呈現原價 / 優惠價對照;報表若要分辨促銷來源,讀 line snapshot 與 audit。

7. 團控顯示

團控頁的財務區塊應顯示:

  • 梯次應收:tour 訂單的 order_charge_lines 聚合。
  • 實收:tour 訂單的 payment_transactions(direction='in') 聚合,扣退款視報表語意決定。
  • 待收:走 orders/money helper,不 inline 算式。
  • 成本:房費成本 + departure_costs成本合計只計 review_status='approved' 的列internal-external-ledger §2.1.3);嚮導送出未覆核(pending)的成本另以「待覆核」計數 / 金額分列呈現,不混入合計。
  • 已轉請款 / 未轉請款:以 departure_costs.payable_idpayables.status 呈現。
  • 異常:overpaid、partially_refunded、refund pending、schedule invariant 失敗等,均連回訂單或 payable 詳情。

tour 視角的彙總必須經 order_linestour_booking_lines 篩出該梯次;customer 或跨產品視角不得用 tour-only join 排除訂房單。

8. Changelog

版本日期變更Commit
回填2026-07-01狀態回填:§5.5 出款批次(payout_batches/payout_batch_lines 月結彙整 + 廠商自動彙整 + 批次轉帳檔)+ §5.6 廠商目錄 suppliers(收款銀行三欄 + departure_costs.supplier_id)+ /supplier-confirm/[token] 廠商自助確認(免登入 tokenized 頁)已全鏈落地;v7–v9 條目的「未實作」註至此收斂(狀態以 roadmap 為準)。
42026-06-20釐清邊界:departure_costs 維持基礎梯次成本(真實 amount_twd + 轉請款);內外帳維度(可申報額 / 支出證明單 / 淨利試算 / 申報)獨立至 internal-external-ledger,多收款帳戶分流獨立至 payment-accounts-reconciliation
52026-06-23§3 補「掛帳 / 折讓 / 沖正已落地」(manual-charge.ts)+ §3.1 掛帳角色矩陣(order.adjust = admin / op;accountant 唯讀;sales 無),對齊 permissions-ac.ts
62026-06-24§4 成本登記 UI 落點反轉(覆寫 2026-06-23):成本+憑證登記回團控(/admin/control/[departureId]/costsdeparture_cost.create,線控)+嚮導 portal(領隊現場填,scoped);內外帳 nav 保留淨利分析 + 證明單核簽(admin/accountant)。呼應 §2「成本從 /admin/control 進」原則。詳見 internal-external-ledger §4
72026-06-25§5.5 新增出款批次(月結匯出)payout_batches / payout_batch_lines 把同一收款對象當月已核准 payable 彙整成一筆匯出,減通用扣款項(label 租戶自定如「勞健保自付」,零法規寫死)→ net,confirm 時一併 mark paid + 記出帳進對帳 + 產批次轉帳清單(戶名/帳號/銀行代號/金額,對齊 GS 領隊薪資申請單批次轉帳區)。收款對象銀行資訊掛既有 guide/staff/供應商紀錄(出款對象資料、非 HR 員工主檔)。做出納面、不做 HR 算薪(薪資級距/勞健保投保/最低薪資/考勤留外部)。領隊費成本認列仍 per-trip departure_costs未實作。
82026-06-28§5.5 / §5.6 匯款流程具體化(對齊 GS 202605記事「廠商結帳」AG2:AH10 + 領隊薪資申請單批次轉帳區):(1) payout_batch_linespayee_type/payee_ref_id + confirmed_at/confirmed_by僅 confirmed 列進批次檔,廠商月結走 LINE 重複確認後勾選)+ 自動彙整明細(payable→departure_costs→departures 派生「日期+團名+金額+合計」,重現 AG/AH 對廠商核對)+ 廠商結帳流程(成本→approve→convertToPayable(supplier)→group→confirm→匯出)。(2) 批次轉帳檔標準四欄(收款人戶名 165 / 帳號 216 / 銀行代號 7 / 金額;通用銀行格式不寫死永豐,從 can_remit 帳戶匯出,缺收款資料擋下)。(3) 新 §5.6 廠商目錄 suppliers(跨月重複廠商建檔一次 + 收款銀行三欄)+ departure_costs.supplier_id(保留 vendor_name 自由填)+ guides/staff 收款銀行三欄 + 新 resource supplier。退款匯出(account-settlement §3.5)共用同一張四欄格式。未實作。
102026-06-28金流→會計流程一致性修正 #3(§5.5):行政薪「直接外加」確認時生成 / 連結 operating_expenses(salary_admin)payout_batch_line.operating_expense_id),明訂單一真相避免損益頁與批次頁重複出帳(對齊領隊費 departure_costs → payable → 批次 單鏈,補上行政薪缺的連結)。
92026-06-28廠商自助確認(token URL)+ 模組歸屬:§5.5 廠商月結確認改對外 tokenized 頁 /supplier-confirm/[token](免登入、綁 payout_batch_line_id、廠商輸入文字按確認 → 回寫 confirmed_at / supplier_confirm_note,比照同意書 /consent/[orderId] + order-scoped 防 IDOR)+ 出款端「複製罐頭訊息 + 網址」鈕(貼 LINE);payout_batch_linesconfirm_token / supplier_confirm_note。標模組歸屬:§5.5 出款批次 / §5.6 廠商目錄屬進階會計模組module_accounting gate),§5.1–5.4 payables 核簽留核心。未實作。
32026-06-05整併請款核簽;改為 payables / payable_items 與金流分類帳現在式 SSOT,移除舊應收與請款表重複建模。
22026-05-30補與 trip-pricing 的 promo 欄位邊界、權限與測試計畫。
12026-05-30初版:掛帳、成本、優惠價三層團控財務設計。