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

租戶設定

一句話:租戶層級設定子系統的 source of truth;新功能若要動設定(例如 團號 的代碼表、訂單派發 的派發模式)應 extend 本檔,而非各自描述。

畫面(UI = wireframe):../apps/wireframes/app/admin/settings/page.tsx

1. 範圍與定位

設定是 租戶層級(tenant-scoped):每個租戶 DB 一列 singleton(tenant_settings.id = 'singleton'),整個租戶共用。

重要:本系統沒有 per-user 個人設定表。 使用者個人層級只有:

  • better-auth 帳號(email / 密碼 / 未來 2FA,存各租戶 DB 自己的 user,與商業表同庫;無共享 auth_db
  • 會員資料(member_profiles,見 會員

凡是「整個旅行社的設定」一律進 tenant_settings;凡是「某個人的偏好」目前無對應載體(YAGNI,真需要時再開 user_preferences)。

2. 資料模型

tenant_settings 單例表,欄位分兩處(符合專案 schema 慣例,見 系統總綱):

欄位語意來源說明
id ('singleton')packages/core/src/schema/business.ts主鍵,恆為 singleton
company_name / company_phone / tax_id / company_address / logo_url@schema公司抬頭(顯示於發票、請款單、Email 頁尾)
email_from / email_reply_to@schema寄件設定
updated_at@schema最後更新
site_tagline / seo_default_description / og_image_url@schema站台級 SEO 預設(無頁面覆寫時 fallback;見 SEO/JSON-LD)
assignment_mode (manual_review | auto_round_robin | auto_customer_choice)@schema訂單派發模式
default_assignee_user_id (REF user)@schema派發兜底業務
passport_expiry_warning_days (integer default 180)@schema證件效期 threshold(海外模組生效時才用到)

模組開通不在這裡:登山 / 海外模組「有沒有開通」是平台方決策,存 control_db.tenant(非 tenant_settings),由 provisioning CLI 管。租戶端只能唯讀看自己方案含哪些模組。見 模組化 §3.1。

派發兩欄(assignment_mode / default_assignee_user_id)已扶正進 drizzle schema(穩定業務欄位一律直接進 schema/business.ts,見 系統總綱);setup-business-extras.ts 現只 idempotent 建立 singleton 列、不再 ALTER 這兩欄。getTenantSettings 仍以顯式欄位清單的 raw SQL 讀取(避免 schema diff 意外遮蔽)。

3. 存取層

packages/core/src/settings/admin-repo.ts

  • getTenantSettings(db): TenantSettingsRow — 讀 singleton,不存在則 lazy insert
  • updateTenantSettings(db, input) — 公司 / Email / 站台 SEO 欄位 partial update(只 patch 表單實際帶到的欄位,缺欄不動 → 各 tab 互不洗資料);SEO 三欄(site_tagline / seo_default_description / og_image_url)由 seo tab 的 updateSeoSettingsAction 呼叫
  • updateAssignmentSettings(db, input) — 派發模式子集(單獨 action / form path)

第一個參數一律 db: ScopedDb(branded type,見 系統總綱)。

4. UI 與權限

/admin/settingssrc/app/(admin)/admin/settings/page.tsx),13 個 tab(順序同 code)

Tab(value可寫?後端
公司資料(company✏️ 可編輯updateTenantSettingsAction
訂單派發(assignment✏️ 可編輯updateAssignmentSettingsAction訂單派發
付款設定(payments🔒 唯讀 + ✏️ 可編輯provider 顯示唯讀PAYMENT_PROVIDER env-backed,改需重啟);ECPay 憑證(特店 ID / HashKey / HashIV)為 per-tenant 可寫表單updateEcpayCredentialsAction / clearEcpayCredentialsAction),三欄一次填齊、write-only 加密存租戶 DB,HashKey/HashIV 永不回讀(僅末 4 碼遮罩供顯示),無租戶憑證時退回平台 env 過渡(見下方註腳)
收款帳戶(accounts✏️ 可編輯收款帳戶目錄 + 費用類型路由維護(SettingsAccountsPanelreceiving_accounts CRUD 新增 / 改名 / 改性質 / 設幣別 / 停用 + fee_category_routes 費用路由設定,receiving_account.manage)。租戶自管、product-agnostic(不綁產品線);外幣帳戶=外幣池。從 多帳戶對帳 / 子系統 B 移入(對帳頁專注對帳、右上 link 過來)。唯一對 accountant(非 admin)也可見的 tab。
費用類別(expense-categories✏️ 可編輯公司營業費用科目目錄 + 月損益分組(SettingsExpenseCategoriesPanelexpense_categories 新增 / 改名 / 停用;operating_expense.read 可見、operating_expense.manage 可寫)。admin + accountant 可見。見 公司營業費用 §2.1
內外帳費率(ledger-fees✏️ 可編輯內外帳「淨利試算」預估費率(updateLedgerFeeRatesActionecpay_fee_bps / fongshou_fee_bps / profit_tax_bps,basis points 整數,0–10000)。見 內外帳 §2.3
郵件設定(email✏️ 可編輯不顯示任何 provider 憑證(Resend key 平台級 env、無 BYOK 欄位,決策見 emails §1.1);回覆 email_reply_to 與寄件 local-part(sender_local_part,預設 info)可編輯(updateTenantSettingsAction);自有網域寄件另配 SendingDomainActionForm。寄件 domain = 綁定站台 domain 派生,見 emails §2/§4
API / Webhook(api🔒 唯讀顯示 ECPay notify / return callback URL 與 App Base URL,供外部設定
方案模組(modules🔒 唯讀顯示平台授權的登山 / 海外 / 訂房模組(getTenantModules,來源 control_db.tenant.module_* 經 proxy header 注入);開通由平台 CLI 管、租戶不可自改 —— 只讓租戶管理者知道方案範圍。見 模組化 §3.1
PDPA / 同意書(consent🔗 外連非就地編輯;以 <Link href="/admin/consent"> 導向同意書範本管理頁(consent_templates 版本化由該頁維護)
SEO / 站台(seo✏️ 可編輯updateSeoSettingsActionsite_tagline / seo_default_description / og_image_url(站台級 SEO 預設,無頁面覆寫時 fallback;見 SEO/JSON-LD)
外觀(appearance✏️ 可編輯後台介面色彩模式(ThemeModeControl,next-themes attribute="data-theme";自動跟隨系統 / 固定淺色 / 深色)· 純前端偏好,不寫 tenant_settings
危險區域(danger⚠️ 操作purgeTestDataTRUNCATE CASCADE 訂單 / 付款 / 旅客 / 同意書簽署 / 稽核紀錄(保留 trips / departures / consent_templates);繞過 append-only trigger(TRUNCATE 不 fire row-level trigger)

進階會計模組閘(accounting-module §5/§6):收款帳戶(accounts,含費用路由)/ 費用類別(expense-categories)/ 內外帳費率(ledger-fees)三個 tab 屬 module_accountingaccountingActive(modules) 為 false 時三個 tab(trigger + content)server 端不渲染、目錄資料不抓,其 write action 亦掛 requireAccountingModule() fail-closed。accountant 於 module OFF 無任何可管 tab → 顯示「尚未開通進階會計模組」空狀態;admin 仍看得到其餘非會計 tab。

權限:進站閘 = requirePermission('receiving_account.manage')(admin 走 wildcard、accountant 通過;op / sales 被擋在整頁外)。不再是舊的整頁 role.manage —— F1 把收款帳戶管理從對帳頁移入本頁後,若仍用 role.manage(admin only)會把持有 receiving_account.manage 的 accountant 一併擋在整頁外。進站後再用 isAdminhasPermissionForRole(..., 'role', 'manage')二次守門敏感 tab:admin 看全部 13 tab;accountant 只看得到「收款帳戶」+「費用類別」兩 tab(後者再 gate operating_expense.read),其餘 admin-only tab 不渲染、props 不外漏defaultValue 對 accountant 落在 accounts)。寫入 action 各自 requirePermission(敏感 tab 對應 role.manage,帳戶 / 費用類別對應 receiving_account.manage / operating_expense.manage)。每次寫入記 audit_logtenant_settings.update / tenant_settings.assignment_mode.change 等)。

付款憑證已 per-tenant(KMS envelope 加密存租戶 DB,/admin/settings 付款 tab 自填 write-only)。郵件憑證決議維持平台級(寄信是平台代履行的服務職責,與「錢進租戶帳戶」的 ECPay 本質不同)—— per-tenant 的是寄件身分email_from / email_reply_to / 自有寄件 domain),見 emails

5. 擴充點(未來設定相關功能掛這)

  • 團號:region / airline 代碼表、團號規則 → 新增設定區塊 + 表
  • 訂單派發:已落地的派發模式(第四種「依目的地分配」UI 已留 disabled placeholder)
  • 模組化:登山 / 海外模組開通由平台方控制control_db,provisioning CLI),settings 只唯讀顯示方案範圍 —— 不是租戶可自改的設定
  • feature flags(未來多租戶規模化):tenant_settings.feature_* 欄,透過 CLI 改(見 memory wildtw-phase2-multitenant-strategy
  • 開發者 tab(API 金鑰自助申請 + CLI session 檢視 / 撤銷):設計定案於 tenant-cli §3.1@better-auth/api-key、PAT 式綁使用者、單一 Authorization: Bearer header 以 toko_sk_ 前綴分派、api_key.manage);MVP 建立限 admin(settings 既有進站閘不動;開放 sales / op 自建連同 UI 入口列 tenant-cli §11 開放議題)。wireframe 已有(settings 線稿「開發者」tab),code 未實作。資料不進 tenant_settings(key 存 plugin 的 apikey 表、CLI session 即 better-auth session 表)

Changelog

版本日期變更Commit
12026-05-29初版(reverse-document 已實作的 tenant_settings / /admin/settings)
22026-05-30整理:對齊現行 code + 路徑搬移
32026-06-01修分歧 D:§4 tab 表對齊 code 真相 7 tab(含 danger、移除 方案模組 進註腳當 Phase 3 擴充點);標題「7 個 tab」與表格列數一致;補各 tab value
42026-06-10Code-Is-Truth 對齊(P1 稽核 M3/M4):§4 補第 8 個 seo tab(updateSeoSettingsAction);§2 SEO 三欄入表、assignment_mode/default_assignee_user_id 來源由 setup-extras 改回 @schema(已扶正進 drizzle);§3 補 updateTenantSettings SEO 欄與「只 patch 帶到的欄」語意
52026-06-12郵件 tab 對接新 emails spec:郵件憑證決議維持平台級(不 BYO)、per-tenant 化的是寄件身分 + 自有寄件 domain;§4 註腳同步付款憑證已 per-tenant 的現狀
62026-06-12郵件 tab 移除 BYOK 唯讀欄位(Resend API key / SMTP Host 遮罩展示自 code 拔除);寄件身分目標模型改為「綁定 domain 派生 + sender local-part」(emails v2)
72026-06-20Code-Is-Truth 對齊:§4 付款 tab 列由「🔒 唯讀 / env-backed」改為「provider 唯讀(env)+ ECPay 憑證 per-tenant 可寫表單」,反映 code 的 updateEcpayCredentialsAction(特店 ID / HashKey / HashIV write-only 加密存租戶 DB、HashKey/HashIV 永不回讀),與既有註腳一致
82026-06-25§4 新增第 9 個 accounts(收款帳戶)tab:receiving_accounts 目錄維護從 多帳戶對帳 移入(租戶配置歸設定;對帳頁右上 link)。帳戶 product-agnostic(解綁產品線)、外幣帳戶=外幣池。(匯率改為獨立頁 /admin/exchange-rates,非 settings tab,見 currency-exchange §4.1。)未實作(code 目錄仍在對帳頁)。
102026-07-02進階會計模組閘落地:accounts / expense-categories / ledger-fees 三 tab 改 accountingActive server 端顯隱 + 目錄資料 module OFF 不抓;write action 補 requireAccountingModule();accountant + module OFF → 「尚未開通進階會計模組」空狀態。§4 加模組閘註記。見 accounting-module §5/§6。
112026-07-07§5 擴充點新增「開發者 tab」(API 金鑰自助申請 + CLI session 管理):設計在 tenant-cli §3.1、wireframe 先行、未實作、資料不進 tenant_settings
92026-07-01Code-Is-Truth 對齊(三方稽核 B2):§4 tab 表 9→13(順序同 code),補 expense-categories(費用類別)/ ledger-fees(內外帳費率)/ modules(方案模組,由註腳升為常駐 tab 列)/ appearance(外觀)四列;accounts 去除 stale「未實作 / code 目錄仍在對帳頁」(已落地 SettingsAccountsPanel + 費用路由)。進站權限段改寫:role.manage(整頁 admin)→ receiving_account.manage 進站(accountant 也進得來、只看帳戶 + 費用類別)+ isAdmin 二次守門敏感 tab。修檔頭 WF 連結 legacy .htmlapps/wireframes/app/admin/settings/page.tsx