一句話:同一筆成本同時承載「內帳真實額」與「外帳可申報額」,據以算真實淨利、並餵代收轉付 / 報稅。建構於 control-finance 的
departure_costs之上,不另起第二套成本表。畫面(UI = wireframe):
內外帳 / 淨利
荒野(與台灣旅行社實務)有「內外帳」需求:實務上常有拿不到發票的支出(停車費、補貼、自然人嚮導 / 司機勞務…),但這些是真實成本,必須據實記錄才能算出真正利潤;同時對外申報只能列有合法憑證的部分。本檔讓系統一筆成本兩個金額並存,派生內帳(真實 / 淨利)與外帳(可申報 / 報稅)兩視圖。
範圍:成本的內外帳維度(含外幣成本 → 雙率派生台幣,§2.1.4)+ 支出證明單 + 淨利試算 + 對外申報邊界 + 成本登記 / 內外帳分析的後台 UI(獨立 nav)。 不做:正式毛利結算 / 總帳 / 分公司合併(control-finance 已劃界,未來會計模組)。營收側多幣別(orders / 收款一律 TWD);成本側多幣別的匯率 / 換匯 / 外幣帳戶機制在 currency-exchange,本檔只用其派生成本台幣。淨利是出團前後的毛估,不是帳務真相。收據實際開立見 travelinvoice-integration。
departure_costs 內外帳維度(擴充欄)SSOT 歸屬:
departure_costs的基礎欄(payable_id/category/vendor_name/amount_twd等)主檔在 control-finance §4,已落地(packages/core/src/schema/business.ts+packages/core/src/departure-costs/repo.ts)。本檔只擴充內外帳維度欄(invoice_amount_twd/is_declarable/expense_proof_id,以及覆核維度review_status+ 覆核 actor 欄created_by_user_id/reviewed_by_user_id/reviewed_at,見 §2.1.3),不重定義基礎欄、不另起第二套成本表。範本來源維度(template_line_id等)另屬 cost-template。
在 control-finance 的基礎 departure_costs(amount_twd = 真實成本)上加:
| 欄位 | 型別 | 說明 |
|---|---|---|
invoice_amount_twd | integer NOT NULL default 0 | 外帳可申報額:此筆取得合法憑證、可報稅的金額(無憑證填 0)。可上修可下修:預設 = amount_twd,但不限於 ≤ amount_twd——實務上有時拿到高於實際支出的發票,可上修(§3.3)。 |
is_declarable | boolean NOT NULL default true | 是否取得合法憑證(發票 / 收據)。false 者須掛支出證明單(§2.2)。 |
expense_proof_id | text FK → expense_proofs.id nullable | 無發票支出時連到 §2.2 的支出證明單。 |
覆核維度(
review_status+ 覆核 actor 欄)見 §2.1.3;範本來源維度見 cost-template §2.4。
內外帳一張表:
amount_twd(內帳真實)與invoice_amount_twd(外帳可申報)並存於同一筆成本,不分裂成兩套帳。is_declarable=true預設invoice_amount_twd = amount_twd(可上修 / 下修);無發票支出invoice_amount_twd = 0但amount_twd仍計入真實成本。UI 命名(嚮導端與後台一致):
amount_twd在表單顯示為「代墊 / 實付額」(嚮導墊付、公司退還的錢,= GS 領隊薪資申請單「申請總額」);invoice_amount_twd顯示為「憑證 / 發票額」(可報稅憑證面額,= GS「發票總金額」)。嚮導 guide-portal §3.2 現場填、後台 §4.1 成本登記頁,同一組欄位、同一組命名,兩 surface 寫同一份departure_costs。
review_status + 覆核 actor 欄)嚮導 / 領隊在 guide-portal 現場填報的成本,不再「送出即生效」——先進 review_status='pending'(待覆核),不計入淨利 / 可申報 / 轉請款,待會計覆核 approve 後轉 approved(「代入帳表」)。線控 / op / admin / accountant 在後台或團控直接登記的成本 review_status='approved'(現場營運即生效,無額外 gate)。
departure_costs 為此再加覆核 actor 欄(責任分離需要 handler / approver 來源):
| 欄位 | 型別 | 說明 |
|---|---|---|
review_status | text NOT NULL default 'approved' | pending(嚮導送出待覆核)/ approved(已覆核 / 直接登記)。 |
created_by_user_id | text FK → user(同庫) | 經手人(建此成本列者;嚮導送出時 = 嚮導本人)。覆核責任分離的 handler。 |
reviewed_by_user_id | text FK → user nullable | 覆核人;approve 時寫入,強制 ≠ created_by_user_id。 |
reviewed_at | timestamptz nullable | 覆核時間。 |
ledger.approve(強制 reviewed_by_user_id ≠ created_by_user_id,比照支出證明單;嚮導 / op 經手,admin / accountant 覆核)。無發票成本另有 expense_proofs 的 pending → approved(§2.2);兩者語意對齊:覆核成本列時,若掛 pending 證明單一併核准(仍受 approver ≠ handler 與 append-only 約束)。expense_proofs 是憑證文件、不自行產生 payable;轉請款的來源永遠是 departure_costs 成本列(一列至多一張 payable,payable_id 去重)。證明單只隨其 linked 成本列一起 approve,不另開第二筆出款。approve 後該列可一鍵 convertCostsToPayable(payee_type='guide')(control-finance §5),進「請款管理」走出款核簽,把代墊款退還嚮導。詳見 guide-portal §3.2。海外團的真實成本常以外幣發生(地接日幣、印尼盾、未來美金)。成本列可原生記外幣 + 匯率,amount_twd(內帳)與 invoice_amount_twd(外帳)改由外幣派生——內帳用真實取得成本、外帳用台銀牌告。匯率機制(牌告表 / 換匯 / 外幣帳戶 WAC)見 currency-exchange,本檔只擁有成本列上的外幣欄與雙率換算。
departure_costs(與 expense_proofs)擴充:
| 欄位 | 型別 | 說明 |
|---|---|---|
currency | text NOT NULL default 'TWD' | 成本幣別(ISO 4217)。TWD = 現行行為(直接填 amount_twd,rate 欄留空)。 |
foreign_amount | integer nullable | 外幣金額(最小單位整數);currency='TWD' 時 NULL。 |
internal_rate_source | text nullable | 內帳匯率來源 fx_account / published / manual(currency-exchange §3.3);TWD 列 NULL。 |
fx_account_id | text FK → receiving_accounts nullable | internal_rate_source='fx_account' 時連到支出的外幣帳戶(取其 WAC)。 |
internal_rate | numeric nullable | 內帳匯率快照(canonical twd_per_unit):登記當下依來源解析(帳戶 WAC / 台銀 / 手填)後快照,不漂移。 |
external_rate | numeric nullable | 外帳匯率快照:登記當下 resolveExchangeRate(currency, 成本日)(台銀牌告)。 |
amount_twd/invoice_amount_twd仍是存量真相(SQL 直接SUM,既有聚合全不動);外幣列只改「這兩個整數怎麼算出來」:
amount_twd = round(foreign_amount × internal_rate)(內帳真實成本)invoice_amount_twd = is_declarable ? round(foreign_amount × external_rate) : 0(外帳可申報)兩個整數台幣由同一筆外幣金額、各自的匯率派生——這正是 GS「內帳 254,511 / 外帳 168,942」黑箱的結構化還原(內外帳差異來自「憑證」與「匯率」兩個正交來源)。
GS 內外帳 / 玉山工作表把保險分二類:旅平險(per-head 旅遊平安險)/ 旅責險(per-梯次 旅行業責任險)。兩類保費都進 departure_costs(category='保險'),是真實成本、計入淨利;登山團另有登山險(per-head 山域意外險)同走 category='保險'。
departure_insurance(kind = travel_accident / liability,不存金額);保費金額走 departure_costs(category='保險',vendor_name 記保險公司)。兩表以 departure_id + 種類對應,不在 departure_insurance 重存成本。departure_costs 逐筆登記,不另在 departure_costs 加 insurance_type 維度欄(種類落 departure_insurance;成本表只需 category='保險')。headcount(單價 × ceil(Σ選定人頭 / 組距))或 fixed(單價 × 固定數量)。Option B(存公式 + 動態重算):手動登記的 per-head 列持久化 quantity_mode / headcount_components / group_divisor / quantity / unit_price_twd(template_line_id 為 NULL 標記手動列),amount_twd = unit_price × resolveQuantity(headcounts);人頭變動由既有 recalculateDepartureTemplateCosts 自然重算(手動列天然納入其 quantity_mode='headcount' AND NOT is_manual_override AND payable_id IS NULL 過濾)。手改金額即設 is_manual_override 停止重算;手填總額路徑(不選公式)完全不變。第一版 TWD-only(per-head × 外幣互斥,repo 拒 PERHEAD_FOREIGN)。只放內外帳頁(團控 finance 頁維持唯讀、嚮導 portal 代墊實付語意不動)。GS 內外帳表的領隊 / 司機 / 嚮導人力成本算法:roundup((人數 + counta(...)) / 8, 0) × 單價 —— 每 8 人一組配一名(不足 8 仍進位一組),與席次 / 團號的 8 人分組同源。
headcount 公式 ceil(Σ人頭 / 組距) 表達(如領隊費 = headcount_components={leader} 或 roundup((旅客+司機)/8)),套用後人數變動自動重算 amount_twd。仍可線控手填總額(手改 → is_manual_override);兩者寫同一 departure_costs.amount_twd(單一存量真相不變)。recalculateDepartureTemplateCosts(不靠 template_line_id),行為一致。payables(staff_commission),與此處的對外人力成本分開。expense_proofs(新表)無法取得發票的支出走支出證明單,是內帳真實成本的合法內部憑證、可列印。比照 payables 的責任分離與編號:
| 欄位 | 型別 | 說明 |
|---|---|---|
id | text PK(ep_<ulid>) | 主鍵。 |
proof_number | text unique(EP-YYMMDD-NNN) | 人類可讀單號,per-DB sequence(比照 payable_number)。 |
departure_id | text FK → departures | 屬於哪個梯次。 |
payee_name | text NOT NULL | 受款對象(自然人 / 廠商)。 |
amount_twd | integer NOT NULL | 金額;與所掛 departure_costs.amount_twd 對應。 |
reason_no_receipt | text NOT NULL | 無法取得單據的原因(如「自然人」)。 |
summary | text | 支出內容及摘要。 |
handler_user_id | text FK → user(同庫) | 經手人(建單)。 |
approver_user_id | text FK → user nullable | 核准人;強制 ≠ handler_user_id(責任分離)。 |
status | text NOT NULL default 'pending' | 狀態機 pending(待核准)→ approved(已核准)。 |
approved_at | timestamptz nullable | 核准時間。 |
created_at / updated_at | timestamptz | — |
狀態機:pending → approved(單向)。
Append-only:pending 期間可編輯;核准後不可改、不可刪(DB-level trigger 阻擋 approved 列的 UPDATE/DELETE,比照 payment_transactions 的窄放寬模式 —— 一律擋,無例外欄)。更正走反向 / 重開(開一張新證明單沖正,不回改原單)。
tenant_settings 預估費率(新欄)參考預估費率存租戶設定(每梯次權威數是毛利,§3.1;這三費率只供 per-trip「參考預估淨利」,公司真實淨利在 operating-expenses 月損益表用實際值)。單一平面費率,不分通路(預估是毛估,分刷卡 / ATM 不同費率的精度沒有意義)。以 basis points(萬分之一,整數) 存,沿用「金額不浮點」原則:
| 欄位 | 型別 | 說明 |
|---|---|---|
ecpay_fee_bps | integer NOT NULL default 270 | 預估綠界手續費率(270 = 2.70%)。 |
fongshou_fee_bps | integer NOT NULL default 50 | 預估豐收款手續費率(50 = 0.50%)。 |
profit_tax_bps | integer NOT NULL default 500 | 預估發票 / 營所稅率(500 = 5.00%,以本團收入毛估)。 |
Σ departure_costs.amount_twd。
每梯次權威數=毛利 = 本團收入 − 真實成本(不扣手續費 / 稅)。手續費 / 稅 / overhead 是公司層級費用,移 operating-expenses 用實際值在月損益表扣一次(避免雙重計)。
營收認列口徑(單一真相,避免現金 vs 出團月混淆):「本團收入」= 該梯次成交應收的攤提份額(訂單
order_charge_lines經orders/allocation的orderMoneyByDeparture()按票價×人數份額攤到各出團;跨出團訂單只計本梯次那份、Σ 守恆 —— 非「掛在此 departure 的訂單全額」;單一出團訂單即整張 order-money-model 的gross_receivable),不是當期收到的現金(訂金 / 尾款常跨月分次收)。損益的期間歸屬 = 出團月(departure 出團日所屬月),與收款月、付款月皆無關——某梯 5 月出團、3 月收訂金、6 月收尾款,全額營收與毛利都落 5 月。order-money-model §1 的「報表用收到現金」僅指訂單付款狀態報表,不涵蓋此處毛利 / 損益的營收認列。全退(receivable_reduction)使該單應收歸 0、自然退出毛利收入。 §2.3 三費率(綠界 / 豐收款 / 營所稅)保留但降為「參考預估」:團控 / 線控頁可顯示明確標示「預估」的 per-trip 淨利(毛利 − 預估費率)給現場快速感覺,非公司財務真相——公司淨利以 operating-expenses 月損益表為準。 取消梯次不入毛利(2026-07-03 已定案,選項 C):梯次列表 / 毛利彙整(listDepartureLedgers/computeRegionMargins)過濾departures.status='cancelled'(listDepartureLedgers提供includeCancelledopt-in,僅供掛帳台帳等現金 / 應付追蹤視圖與殘餘檢視用;單梯明細頁不受影響)——取消梯次沒有出團月,「0 收入 / 正成本」會污染區間毛利比較。其殘餘不可退成本(含房費)由內外帳頁「取消梯次待結轉」卡人工確認結轉公司層特殊損益(結轉 / 撤銷 / 反向守衛 / 編輯鎖見 operating-expenses §3.4)、留存行政手續費認列其他收入(§3.3)。
Σ departure_costs.invoice_amount_twd;對應 travelinvoice-integration 的開立與申報。review_status='approved' 的列(§2.1.3);pending(嚮導待覆核)不入淨利 / 可申報 / 轉請款。nonDeclarable = Σ max(0, amount_twd − invoice_amount_twd)(只計 approved 列)。逐列取差額是刻意的——避免某列上修發票(invoice > amount)跨列吃掉另一列的無憑證成本;上修列該項貢獻 0,整體不出現負數。(勿寫成 max(0, Σamount − Σinvoice),那會跨列淨額抵減。)amount_twd / invoice_amount_twd 已是登記當下由外幣 × 內 / 外帳匯率快照 round 出的整數台幣,照常進 SUM。內帳吃真實取得成本(WAC / 當日 / 手填),外帳吃台銀牌告;同一筆外幣可使「外帳 > 內帳」或「內帳 > 外帳」(匯率落差 + 憑證可上修),皆合法。兩視圖經 helper 派生(比照 orders/money 不 inline 算式);團控成本登記頁與嚮導 portal 顯示該梯次成本明細+合計(內帳真實額),但淨利 / 收入 / 毛利不在團控 / 嚮導端顯示(敏感財務,見 §4)。
內外帳表的「派車人(智傑 / 小御)+ 派車抽成」走既有結構、不開新表 / 新欄:
departure_costs 一筆(category 交通 / 嚮導,vendor_name 記司機 / 派車人),同內外帳維度(有無發票)。payables(payee_type='staff_commission') 出款,payee_name 記派車人;走既有請款核簽狀態機與責任分離。兩者各自落既有分類帳,不另建「派車」專屬模型。
departure_costs:invoice_amount_twd ≥ 0,且 is_declarable=false ⟺ invoice_amount_twd=0 且必有 expense_proof_id。is_declarable=true 時 invoice_amount_twd 預設等於 amount_twd,使用者可下修或上修(無 ≤ amount_twd 上限——實務上可能拿到高於實際支出的發票);僅要求非負整數。「可申報 > 真實」不再視為錯誤,派生的無憑證真實成本夾到 0(§3.1)。is_declarable=false 時強制 0 且必同步開立一張 expense_proofs。B1 不另設審核覆寫。review_status='pending',不計入淨利 / 可申報 / 轉請款,待 ledger.approve(approver ≠ handler)後轉 approved(§2.1.3)。approver_user_id ≠ handler_user_id;核准後 append-only。currency='TWD' ⟺ foreign_amount IS NULL 且 rate 欄全 NULL(TWD 列直接填台幣);currency≠'TWD' 時必有 foreign_amount ≥ 0、internal_rate_source、internal_rate、external_rate,且 amount_twd = round(foreign_amount × internal_rate)、invoice_amount_twd = is_declarable ? round(foreign_amount × external_rate) : 0。internal_rate_source='fx_account' 時必有 fx_account_id 且其帳戶 currency=本列 currency。匯率為登記快照(currency-exchange §3.2/§3.3),事後 WAC / 牌告變動不回改既有列(除非該列未鎖定且使用者明確重算)。決策(2026-06-24,覆寫 2026-06-23):把「成本登記(操作)」與「內外帳 / 淨利(敏感分析)」分成兩個 surface、按敏感度分權限 —— 成本+憑證登記是 per-departure 現場作業(GS「0501玉山」工作表由現場領隊本人逐筆填車資 / 餐費 / 接駁+發票繳回),跟對款 / 派車 / 訂餐同層,回到團控(線控的團控成本登記頁)與嚮導 portal(領隊現場填);此獨立 nav 保留收入 / 淨利 / 毛利派生、跨梯次損益、支出證明單核簽(敏感財務),僅
admin/accountant。三個 surface(團控 / 嚮導 portal / 此頁)寫同一份departure_costs。2026-06-23 那版「成本登記只放此 nav、團控只讀」過度收斂(導致線控 / op / 嚮導無入口登記成本),已反轉。
payable_id 的列鎖定不可刪 / 改,更正走沖正);右側淨利試算卡(§3.1)。invoice ≥ 0;上修時 UI 提示「可申報 > 真實」供覆核)+ 憑證類型;選「無發票」→ 可申報額強制 0,並同步內嵌開立一張支出證明單(受款 / 原因 / 摘要 / 經手)送核。單價 × ceil(Σ人頭 / 組距)(真相在 server 重算);選公式時可申報額 = 派生總額(不開手動覆寫欄)。外幣強制一次性總額(per-head TWD-only)。review_status='pending' 成本佇列,會計逐筆 / 批次 approve(經手 ≠ 核准)→ 代入帳表;approve 後可一鍵轉請款給嚮導(payee=guide,進請款管理)。approved)。@media print(比照請款單,見 control-finance §5.3)。成本登記(操作) 與 內外帳 / 淨利(敏感分析) 拆成不同 resource,前者開給現場營運角色、後者鎖 admin / accountant:
| action | 用途 | guide(自己梯次) | op | admin | accountant | sales |
|---|---|---|---|---|---|---|
departure_cost.create | 登記成本 + 開支出證明單(經手) | ✅(scoped) | ✅ | ✅ | ✅ | ❌ |
departure_cost.manage | 編輯 / 刪除 / 轉請款(跨梯次) | ❌ | ❌ | ✅ | ✅ | ❌ |
ledger.read | 內外帳列表 / 淨利 / 內帳真實額 / 跨梯次損益 | ❌ | ❌ | ✅ | ✅ | ❌ |
ledger.approve | 支出證明單核准(強制 approver ≠ handler) | ❌ | ❌ | ✅ | ✅ | ❌ |
departure_cost.create:成本+憑證是 per-departure 現場作業,開給 op(團控成本頁)、guide(嚮導 portal,scoped 到自己帶的梯次,見 guide-portal)、admin / accountant。departure_cost.manage:跨梯次編輯 / 刪除 / 轉請款留 admin / accountant(2026-07-07 拍板 op 維持 create-only)。原 ledger.manage 已收斂改名於此(內外帳詳情頁與 api/v1 成本寫入閘同名;存量自訂角色 grants 由 migration 0052 映射)。ledger.read:淨利 / 收入 / 毛利屬敏感財務,整個獨立 nav 經此 gate,僅 admin / accountant;不顯示於團控 / 嚮導 portal。ledger.approve:支出證明單核准,強制 approver ≠ handler(嚮導 / op 經手,admin / accountant 核准)。departure_cost.manage 且 payable.create;建立後的 payable 仍走 control-finance §5 的 payable.* 核簽狀態機。tenant_settings 的 單一平面費率(§2.3:ecpay_fee_bps / fongshou_fee_bps / profit_tax_bps,basis points 整數),不依通路(刷卡 vs ATM)分。is_declarable=true 預設 invoice=amount,可上修可下修(無 ≤ amount 上限);is_declarable=false 強制 0 + 必掛證明單。B1 不設審核覆寫。review_status 覆核 gate(pending → approved),approve 後計入帳並可轉請款給嚮導(§2.1.3);後台 / 團控直接登記者直接 approved。