一句話:每梯次只算到「毛利」(收入 − 直接成本);公司層級的辦公室 / 人事(行政固定薪)/ 實繳稅 / 平台手續費實際月結這些與梯次無關的費用進一本獨立的營業費用分類帳,月損益表 = Σ各區毛利 + 其他收入 − 公司營業費用 = 公司真實淨利。手續費 / 稅只在公司層用實際值扣一次,每梯次不重複估算。
畫面(UI = wireframe):
損益表 / 財務總覽(獨立 nav,跨梯次)· 費用類別設定見設定模組歸屬:屬進階會計模組(受
module_accountinggate);不開模組的租戶用核心掛帳台帳(per-departure 毛利)+ Excel 自行做損益。
toko 現有財務只到每梯次層:internal-external-ledger 的「真實成本 / 可申報成本 / 預估淨利」、control-finance 的 departure_costs、account-settlement 的帳戶補款。缺公司層級:GS「2026荒野旅人五月份總結算」的損益區(B2–B39)有,toko 沒有。
GS 損益結構(2026/05 實證):
一、利潤總計
國內登山 843,966 + 東南亞 89,775 + 中東非洲 0 + 日本 109,481 = 1,043,222 ← 各區毛利
− 辦公室支出 643,307 − 稅金 339,480 − 平台手續費 44,959 = −1,027,746 ← 公司營業費用
= 淨額 15,476 元
明細:辦公室 643,307 = 台新辦公室 88,845 + 永豐辦公室 554,462;稅金 339,480 = 營業稅 204,219(雙月)+ 營所稅 135,261(年,5 月實繳);平台手續費 44,959 = 綠界 43,519 + 豐收款 0 + 寰宇 1,440。
缺的載體:辦公室支出、人事(行政 / 內勤固定薪,非領隊費)、實繳稅、平台手續費實際月結,全無處可記;公司月損益表不存在。
雙重計陷阱(必避):每梯次「預估淨利」已扣預估綠界 / 豐收款手續費 + 預估營所稅(internal-external-ledger §3.1;GS 0501 玉山:淨利未扣稅手續 14,200 → 預估實際淨利 12,487)。若公司層又用實際手續費 / 稅扣一次 → 雙重計。本檔把每梯次降為毛利(收入 − 直接成本,不扣手續費 / 稅),手續費 / 稅 / overhead 全移公司層用實際值扣一次。
「人員成本」釐清(GS 0501 玉山鐵證):出團成本計算表的「人員成本」(C40)= 領隊費(領隊 夏鼎 4025/天 × 2 天 = 8,050),是每梯次直接成本,且由領隊經 guide-portal 自填(領隊薪資申請單 Google 表單「本團領隊費總額」= 同數),admin 端鏡像進 departure_costs。領隊費屬每梯次毛利、非公司人事;公司人事只裝「行政 / 內勤固定薪 + 勞健保」(領隊薪資申請單底部「行政薪水(直接外加)」,無 per-trip 家)。勿把領隊費重算進公司人事(雙重計)。
範圍:公司營業費用分類帳(類別 per-tenant 可設)+ 月損益表(各區毛利 + 其他收入 − 公司費用)+ 每梯次「淨利 → 毛利」降級。 不做:HR 算薪 / payroll(薪資級距、勞健保投保管理、最低薪資自動算、考勤、員工主檔;jurisdiction-specific,由外部記帳 / payroll 提供數字);正式總帳 / 合併財報 / 跨年結轉(未來會計模組);自動串國稅局 / 銀行拉實繳數(人工登錄)。
嚮導 / 領隊費「月結批次匯出」做的是出納面(彙整當月已核准 payable + 通用扣款項 → 一筆匯出 + 批次轉帳清單 + 進對帳),在 control-finance §5.5 出款批次;非 HR(不算薪 / 不管投保)。領隊費的成本認列仍是 per-trip
departure_costs(guide-portal)。
expense_categories(費用類別,per-tenant config,新表)公司營業費用的類別,租戶自定不寫死,且夠細到逐筆明細的科目(GS 辦公室支出 643,307 底下是約 30 筆逐項:薪資 / 廣告 / 勞健保 / 房租 / 水電 / 電信 / 網路 / 保險 / 招募 / 文具 / 法遵 / 交通…):
| 欄位 | 型別 | 說明 |
|---|---|---|
id | text PK(exc_<ulid>) | 主鍵。 |
code | text NOT NULL | 科目代碼(租戶內唯一,如 rent / salary_admin / labor_insurance / marketing / utility / telecom / insurance / recruiting / supplies / tax_vat / tax_income / gateway_fee…)。 |
display_name | text NOT NULL | 顯示名(房租 / 行政薪資 / 勞健保 / 行銷廣告 / 水電 / 電信 / 保險 / 招募 / 文具 / 營業稅 / 營所稅 / 平台手續費…)。 |
kind | text NOT NULL default 'opex' | opex(一般營業費用)/ tax(稅)/ platform_fee(金流商手續費實際月結)/ special(一次性特殊損益,§3.4)。損益表按 kind roll-up:opex 細項彙整成「辦公室支出 / 營業費用」、tax→稅金、platform_fee→平台手續費(= GS 損益區三大格)、special→特殊損益獨立列。 |
is_active | bool default true | 停用不刪。 |
兩層:細科目(房租 / 薪資 / 廣告…)是逐筆登錄的維度;損益表上只顯示
kind三 bucket(辦公室・稅・手續費),點開 drill-down 到細項。tax/platform_fee是 hint 不是硬枚舉——租戶可只用opex。「人事(行政固定薪)」是opex下的一個科目(GS 就把行政薪資逐筆列在辦公室支出內),不另立頂層;領隊費不在此(per-trip 直接成本)。
operating_expenses(公司營業費用,新表,無 departure / order)與梯次無關的公司費用,逐筆登錄成明細台帳(對齊 GS 辦公室支出明細 B673–B713:每筆 廠商 / 品名 + 金額 + 代墊者 + 出帳帳戶 + 匯出日)。
direction 欄('expense' | 'income',default 'expense'):特殊損益可損可益——一次性收益(GS「額外收入總計」)記 direction='income' 的列,金額欄維持恆正(amount >= 0 CHECK 不動),方向由 direction 表達。income 僅允許掛 kind='special' 科目(DB constraint trigger + app 層 assertIncomeAllowed,INCOME_NOT_SPECIAL 擋)——經常性收入不得偽裝成負費用。
| 欄位 | 型別 | 說明 |
|---|---|---|
id | text PK(opx_<ulid>) | 主鍵。 |
category_id | text FK → expense_categories | 費用科目(細科目,§2.1)。 |
period | text NOT NULL | 歸屬月(YYYY-MM,損益表據此彙整;與支付日可不同,如年營所稅攤)。 |
incurred_on | date(nullable) | 發生 / 支付日期;金流商內扣 / 攤提無單一發生日者可留空(UI 標「選填」)。 |
amount_twd | integer NOT NULL | 金額(整數 TWD)。 |
description | text NOT NULL | 摘要(廠商 / 品名,如 台中房租 / 委外廣告投放 / 林宥潔行政薪 / 履約保證保險…)。 |
paid_from_account_id | text FK → receiving_accounts nullable | 出帳帳戶(→ 接 account-settlement / 對帳;金流商內扣者可留空)。 |
fronted_by_user_id | text FK → user(同庫)nullable | 代墊者(GS「代墊者」欄:曾帥 / 乃文 / 小御先墊付)。非空 = 此筆由人代墊待回補 → 走請款回補代墊者(payable_id,payee = 代墊者);空 = 公司帳戶直接出帳。 |
payable_id | text FK → payables nullable | 若走出款核簽(租金 / 薪資 / 稅 / 代墊回補),連到 payables;金流商自動內扣的手續費不走核簽、留空。 |
recurring | bool default false | 月固定標記(人工登記時可打勾備註「這是月固定」;真正的自動生成走 §2.2.1 範本表,非此欄)。 |
status | text default 'confirmed' | 'draft'(月固定範本自動生成、待確認,不入損益)| 'confirmed'(計入損益)。手動登記預設 confirmed(零回歸)。 |
origin | text default 'manual' | 'manual'(手動登記)| 'recurring'(月固定範本自動生成,§2.2.1)。 |
source_template_id | text FK → recurring_expense_templates nullable | 生成來源範本(origin=recurring)。partial unique (source_template_id, period):同範本同月至多一列(生成冪等)。 |
note | text | 備註。 |
created_by_user_id | text FK → user(同庫) | 經手人。 |
created_at / updated_at | timestamptz | — |
約束:amount_twd ≥ 0、status IN ('draft','confirmed')、origin IN ('manual','recurring')。非 append-only(公司費用可更正;更正記 audit_log operating_expense.update,敏感金額留軌跡)。損益只計 status='confirmed'(computeCompanyPnL 的 computeExpensesByKind / computeSpecial 都帶 AND status='confirmed')—— 這是「避免無人確認的鬼列」的安全前提:自動生成的草稿不確認不入帳。
逐筆明細,不是一坨:辦公室支出在損益表是一格(GS 643,307),但底下是約 30 筆
operating_expenses(薪資逐人 / 廣告 / 勞健保 / 房租 / 水電 / 文具…)。損益表顯示 bucket 合計、點開 drill-down 到逐筆。 四種結付型態:① 公司帳戶直接出帳(paid_from_account_id,fronted_by空,如房租自永豐匯出);② 人代墊待回補(fronted_by_user_id非空 →payable_id走請款回補代墊者,如台北房租曾帥代墊);③ 金流商自動內扣(手續費,paid_from留空、不走核簽);④ 薪資走月結出款批次(行政 / 內勤固定薪不走逐筆請款——是一條合計,per-person 明細 + 扣勞健保在 出款批次 §5.5「直接外加」;逐筆請款是給供應商 / 代墊回補的)。 行政薪單一真相(避免損益列與出款批次重複出帳):行政薪有兩個面 ——「損益費用列」(本表operating_expenses,kind=opex、code=salary_admin)與「出款執行」(出款批次 §5.5「直接外加」現金)。真相只記一次:出款批次「直接外加」列確認時生成 / 連結對應的operating_expenses(salary_admin)列(payout_batch_line帶operating_expense_id,反向operating_expenses.payable-側不重開),毛額(未扣勞健保自付)= 損益費用、淨額 = 批次匯出金額。不得由會計在損益頁、出納在批次頁各登一次。這對齊領隊費的單鏈(departure_costs → payable → 批次),補上行政薪原本缺的連結。 成本拆分(公司贊助 vs 老闆個人,GS 電子簽 / 機票):一筆支出由公司贊助一部分、老闆 / 員工個人自付一部分時,operating_expenses只記公司負擔的那筆(kind=opex),個人自付部分不入公司帳;description註明拆分(如「爪哇電子簽 · 公司贊助 2,078/老闆自付另計」)。不建模個人帳,保持公司損益乾淨。 與payables的分工:payables是「出款核簽 workflow」(誰建單 / 誰核准 / 誰付款);operating_expenses是「損益表費用列」。需核簽 / 代墊回補的開 payable、付款後填payable_id連結(避免重複出款)。兩者一對一可選掛,不是同一張表。多筆 payable 可經 出款批次 §5.5 月結一次匯出。
recurring_expense_templates(月固定費用範本,新表)+ 自動生成 draft + 確認流每月會重複的公司費用(台中房租 / 固定薪 / 電信 / 網路…)不必每月手動重登:建一份範本(預設值),排程每月月底(25 號)生成下月草稿(月中即可預先確認下月固定支出),人工確認(可先改金額)後才計入損益。
| 欄位 | 型別 | 說明 |
|---|---|---|
id | text PK(ret_<ulid>) | 主鍵。 |
category_id | text FK → expense_categories(restrict) | 費用科目。 |
amount_twd | integer NOT NULL(≥ 0) | 預設金額(生成的草稿可逐月修正)。 |
description | text NOT NULL | 摘要(帶進草稿的 description)。 |
paid_from_account_id | text FK → receiving_accounts(restrict)nullable | 預設出帳帳戶(帶進草稿,可改)。 |
is_active | bool default true | 停用不刪:停用後不再生成,既有草稿 / 費用列不受影響。 |
created_by_user_id | text FK → user(同庫) | 建立者。 |
created_at / updated_at | timestamptz | — |
自動生成(materialize):排程 POST /api/jobs/generate-recurring-expenses(GCP Cloud Scheduler,每月 25 號 06:00 Asia/Taipei,生成下月——sweep 預設 period = 下月,補生成歷史月走 period 參數)逐 active 租戶 fan-out(比照台銀牌告 sync-bot-rates 的四件套:recurring-sweep.ts fan-out + recurring-expenses.ts 的 materializeRecurringDrafts + job route + ops script cron:generate-recurring-expenses)。對每個 active 範本 INSERT 一列 operating_expenses 草稿(status='draft'、origin='recurring'、source_template_id 連回範本、金額 / 摘要 / 帳戶承範本、created_by_user_id 承範本建立者),ON CONFLICT (source_template_id, period) DO NOTHING 保證冪等(同月重跑 / 單一租戶失敗不中斷其餘)。
確認流:/admin/pnl 頁列出「待確認月固定費用」草稿卡(status='draft' 該期列),每列一個「確認」鈕 → confirmOperatingExpenseAction(閘 operating_expense.manage + 進階會計模組)→ confirmOperatingExpense(FOR UPDATE 鎖、只確認 draft 列 [NOT_DRAFT 拒重複]、status → confirmed、可同時覆寫金額、寫 audit_log operating_expense.confirm)。確認後即計入損益。範本管理在 設定 · 月固定費用 tab(CRUD,operating_expense.manage)。
internal-external-ledger §3.1 的「預估淨利」降級:
Σ departure_costs.amount_twd + 房費,只計 approved)。不扣手續費 / 稅(那些移公司層)。ecpay_fee_bps / fongshou_fee_bps / profit_tax_bps 三費率保留但降為「參考預估」:團控 / 線控頁可顯示一個明確標示「預估」的 per-trip 淨利(毛利 − 預估費率),純給現場快速感覺,不是公司財務真相。公司淨利一律以本檔月損益表(實際 overhead)為準。packages/core/src/finance/company-pnl.ts(db: ScopedDb)。純讀派生,沿用 money helper 口徑。
computeCompanyPnL(db, period)營收
團費收入 = Σ tour 訂單該期收入
住宿 / 裝備收入 = Σ lodging / equipment / 現場代收
其他收入 = 行政手續費收入(Σ refunds.admin_fee_twd,§3.3)
直接成本(各區毛利)
逐梯次毛利 = 收入 − 真實成本(§2.3),按【區】分組彙整 ← 國內登山 / 東南亞 / 日本…
公司營業費用
Σ operating_expenses(kind=opex/tax/platform_fee) ← 辦公室 / 人事 / 稅 / 平台手續費(實際)
特殊損益(一次性,§3.4,帶號)
specialTotal = Σ(kind=special, direction=expense) − Σ(kind=special, direction=income)
← 取消床位沒收 / 一次性外幣損失為損;
GS「額外收入總計」= income 列為益
= 公司淨利 = Σ各區毛利 + 其他收入 − Σ公司營業費用 − specialTotal
額外收入的載體已定:GS「二、額外收入總計」=
kind='special'且direction='income'的operating_expenses列(§2.2);computeCompanyPnL原本的opts.extraIncomeTwd手填參數已退場(簽名收斂為computeCompanyPnL(db, period)),其他收入只剩行政手續費,不再有無載體的手填數。
代收代付(pass-through)不入營收:第三方代收(露營區代收款等)是代收代付負債、非旅行社收入,不計入上面任何收入列;其收 / 匯回 / 代退款見 §3.5。
pnl_regions 目錄表(pnlr_<ulid>、code 租戶內唯一穩定識別、display_name、display_order、is_active,停用不刪;CRUD 在 設定 · 損益分區 tab,operating_expense.manage)+ trips.pnl_region_id 指派(set null)。computeRegionMargins 的 region key / label 以 COALESCE(pnl_regions 目錄, 既有派生) 決定:行程有指派分區 → 用目錄('region:'||code key、display_name label、display_order 排序);未指派 → fallback 既有派生(is_overseas → 'overseas:'||destination、is_climbing → 國內登山、其餘 → 國內其他)。向後相容:未建目錄 / 未指派的租戶行為與第一版逐字一致。行程指派欄在行程編輯頁基本 tab(僅進階會計模組授權時顯示;目錄來自 DB 用 Select)。kind='platform_fee' 的 operating_expenses 列;輔助「依交易量試算」鈕(費率 × 該期金流額)供對照,不取代實際數。kind='tax',period 歸屬攤提月(GS 5 月實繳 2025 營所稅 135,261)。departures.status='cancelled');取消梯次的殘餘不可退成本結轉 kind='special'(§3.4),留存行政手續費認列其他收入(§3.3)。gross_receivable),期間歸屬 = 出團月(非收款月 / 付款月),見 internal-external-ledger §3.1。上面 code 的「Σ tour 訂單該期收入」即「Σ 出團月落於 period 的梯次成交應收」。其他收入(額外收入 / 行政手續費)與公司費用則按其 period 歸屬。純讀派生,不寫第二套金額;成本沿用 internal-external-ledger 毛利。驗證對齊 GS 2026/05:各區毛利 1,043,222 − 公司費用(辦公室 643,307 + 稅 339,480 + 平台手續費 44,959 = 1,027,746)= 淨額 15,476。引擎輸出即這張損益總表。
退款 / 改期收的行政手續費(200 / 100 元/筆)是公司留存的收入(不退給客人那部分)。refunds.admin_fee_twd(account-settlement §3.5)已逐筆記;損益表把 Σ refunds.admin_fee_twd(該 period)認列進其他收入(§3.1)。純派生、不另存。對齊 GS「登山總收款(含行政手續)」的行政手續費 11,375。
避免與毛利雙計:只認列未出團 / 取消單(未中籤 / 未成團 / 取消,該梯不在各區毛利)的
admin_fee_twd。已出團梯次的退款 / 改期(其成交應收已計入該梯毛利收入、且receivable_reduction只減退掉的部分、留存的手續費仍在應收內)其admin_fee不另認列,否則與毛利收入重複。實作以「退款所屬 departure 是否成行」分流。
非經常性的一次性損益(GS:取消 2026/6/17–21 神山床位虧損 RM$4,000 沒收訂金)——operating_expenses 加 kind='special',損益表獨立列「特殊損益」(在公司營業費用之外、淨利之上)。可為損(沒收 / 賠償)或益(一次性收益)。外幣事件沿用成本側多幣別(currency + foreign_amount + 匯率快照,currency-exchange)—— RM$4,000 × 當日匯率 round 成台幣。與經常性 opex 分開,避免污染營運費用率。
取消梯次殘餘成本結轉(2026-07-03 已定案,選項 C):取消的梯次排除於各區毛利(computeRegionMargins / internal-external-ledger 的梯次列表過濾 departures.status='cancelled')——取消梯次沒有「出團月」,其 0 收入 / 正成本混入毛利會讓區間比較失真。已發生、退訂沖銷後殘餘不可退的成本(含分房房費 departure_rooms.base_cost_twd)在梯次取消時結轉一筆 kind='special' 的 operating_expenses(period = 取消月、摘要含團名 / 梯次連結、寫 audit;GS 神山床位虧損即此型)。與 §3.3「行政手續費只認未出團 / 取消單」對稱:取消梯次的收入面(留存手續費)與成本面(殘餘損失)都在公司層認列,毛利只反映實際出團。結轉是人工確認動作(線控 / 會計在取消流程沖銷可退成本後執行),不自動——自動結轉會把「還在退訂協商中」的成本提早認列為損失。
結轉列的生命週期規則(防雙計 / 防漂移):
source_departure_id 連回梯次,partial unique 保一梯次至多一筆(併發雙結轉 DB 層擋下);origin='departure_conversion'、status='confirmed'(即計入損益)。payable_id 者拒絕)。撤銷後可重新結轉。updateDepartureStatus)與結團(completeDepartureInTx——取消梯次一律不可結團,不只已結轉者;結團述詞若只擋 completed 會把取消梯次翻成 completed 繞過守衛)。base_cost_twd)、範本顯式套用與手改——否則 residual 與 special 脫鉤的 delta 在毛利(梯次已排除)與 special(金額不動)兩邊都看不到=靜默漏失。要改一律「撤銷結轉 → 改 → 重新結轉」。自動路徑(建單 / 取消 / 排班 / 派車觸發的範本重算、建梯次 auto-apply)對已結轉梯次靜默跳過(不可炸掉宿主交易;跳過即凍結)——跳過檢查必須先 FOR UPDATE 鎖梯次列(與結轉同鎖序)再讀結轉狀態,純讀檢查會與進行中的結轉競態(重算改了成本、結轉卻用舊殘餘=靜默 delta)。所有守衛與結轉本身都以 departures 列鎖為序列化錨點。DB 層另有 origin='departure_conversion' ⇔ source_departure_id IS NOT NULL 雙向 CHECK 防禦縱深。amountTwd 預設殘餘、人工可調(部分殘餘可能事後拿回 / 有帳外損失);audit metadata 同時記 residualCostTwd 與 amountTwd 留差額軌跡。結轉凍結使 residual 不再能事後漂移——差異只剩結轉當下的人為判斷。替合作方(露營區等)代收款項、再匯回對方、處理代退款 —— 錢是過路、非旅行社收入 / 費用:
is_passthrough,不入損益營收(§3.1 已排除)。account_transfers 帳戶間移轉(GS:永豐 → 台新 186,982)。/admin/pnl):跨梯次功能 → 獨立 nav(線控 UX 原則:跨梯次才獨立 nav)。選月份 → 上半「各區毛利 + 其他收入」、下半「公司營業費用(按類別)」、底「公司淨利」。對齊 GS 損益總表。operating_expenses(類別 / 月 / 金額 / 出帳帳戶 / 是否走核簽 / 摘要);月固定費用(租金 / 固定薪)可從上月帶入 placeholder。expense_categories CRUD),租戶自管。| action | 用途 | op | admin | accountant |
|---|---|---|---|---|
operating_expense.read | 看公司費用 / 損益表 | ❌ | ✅ | ✅ |
operating_expense.manage | 登錄 / 更正公司費用、維護費用類別 | ❌ | ✅ | ✅ |
admin / accountant;op / sales / guide 無(連每梯次淨利都不對線控顯示,見 internal-external-ledger §4)。recurring_expense_templates + 每月月底排程生成下月 status='draft' 草稿(origin='recurring'、source_template_id 冪等鍵)+ 人工確認流(confirmOperatingExpense,可先改金額)。損益只計 confirmed 保住「避免無人確認的鬼列」。fan-out 比照台銀牌告 sync(單一租戶失敗不中斷、同月重跑冪等)。pnl_regions per-tenant 目錄 + trips.pnl_region_id 指派;computeRegionMargins 用 COALESCE(目錄, 既有派生) 分組,未指派向後相容 fallback。