一句話:租戶層級設定子系統的 source of truth;新功能若要動設定(例如 團號 的代碼表、訂單派發 的派發模式)應 extend 本檔,而非各自描述。
畫面(UI = wireframe):
../apps/wireframes/app/admin/settings/page.tsx
設定是 租戶層級(tenant-scoped):每個租戶 DB 一列 singleton(tenant_settings.id = 'singleton'),整個租戶共用。
重要:本系統沒有 per-user 個人設定表。 使用者個人層級只有:
user 表,與商業表同庫;無共享 auth_db)member_profiles,見 會員)凡是「整個旅行社的設定」一律進 tenant_settings;凡是「某個人的偏好」目前無對應載體(YAGNI,真需要時再開 user_preferences)。
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 意外遮蔽)。
packages/core/src/settings/admin-repo.ts:
getTenantSettings(db): TenantSettingsRow — 讀 singleton,不存在則 lazy insertupdateTenantSettings(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,見 系統總綱)。
/admin/settings(src/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) | ✏️ 可編輯 | 收款帳戶目錄 + 費用類型路由維護(SettingsAccountsPanel:receiving_accounts CRUD 新增 / 改名 / 改性質 / 設幣別 / 停用 + fee_category_routes 費用路由設定,receiving_account.manage)。租戶自管、product-agnostic(不綁產品線);外幣帳戶=外幣池。從 多帳戶對帳 / 子系統 B 移入(對帳頁專注對帳、右上 link 過來)。唯一對 accountant(非 admin)也可見的 tab。 |
費用類別(expense-categories) | ✏️ 可編輯 | 公司營業費用科目目錄 + 月損益分組(SettingsExpenseCategoriesPanel:expense_categories 新增 / 改名 / 停用;operating_expense.read 可見、operating_expense.manage 可寫)。admin + accountant 可見。見 公司營業費用 §2.1 |
內外帳費率(ledger-fees) | ✏️ 可編輯 | 內外帳「淨利試算」預估費率(updateLedgerFeeRatesAction 寫 ecpay_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) | ✏️ 可編輯 | updateSeoSettingsAction 寫 site_tagline / seo_default_description / og_image_url(站台級 SEO 預設,無頁面覆寫時 fallback;見 SEO/JSON-LD) |
外觀(appearance) | ✏️ 可編輯 | 後台介面色彩模式(ThemeModeControl,next-themes attribute="data-theme";自動跟隨系統 / 固定淺色 / 深色)· 純前端偏好,不寫 tenant_settings |
危險區域(danger) | ⚠️ 操作 | purgeTestData:TRUNCATE CASCADE 訂單 / 付款 / 旅客 / 同意書簽署 / 稽核紀錄(保留 trips / departures / consent_templates);繞過 append-only trigger(TRUNCATE 不 fire row-level trigger) |
進階會計模組閘(accounting-module §5/§6):收款帳戶(
accounts,含費用路由)/ 費用類別(expense-categories)/ 內外帳費率(ledger-fees)三個 tab 屬module_accounting。accountingActive(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 一併擋在整頁外。進站後再用 isAdmin(hasPermissionForRole(..., '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_log(tenant_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。
control_db,provisioning CLI),settings 只唯讀顯示方案範圍 —— 不是租戶可自改的設定tenant_settings.feature_* 欄,透過 CLI 改(見 memory wildtw-phase2-multitenant-strategy)@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 表)| 版本 | 日期 | 變更 | Commit |
|---|---|---|---|
| 1 | 2026-05-29 | 初版(reverse-document 已實作的 tenant_settings / /admin/settings) | — |
| 2 | 2026-05-30 | 整理:對齊現行 code + 路徑搬移 | — |
| 3 | 2026-06-01 | 修分歧 D:§4 tab 表對齊 code 真相 7 tab(含 danger、移除 方案模組 進註腳當 Phase 3 擴充點);標題「7 個 tab」與表格列數一致;補各 tab value | — |
| 4 | 2026-06-10 | Code-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 帶到的欄」語意 | — |
| 5 | 2026-06-12 | 郵件 tab 對接新 emails spec:郵件憑證決議維持平台級(不 BYO)、per-tenant 化的是寄件身分 + 自有寄件 domain;§4 註腳同步付款憑證已 per-tenant 的現狀 | — |
| 6 | 2026-06-12 | 郵件 tab 移除 BYOK 唯讀欄位(Resend API key / SMTP Host 遮罩展示自 code 拔除);寄件身分目標模型改為「綁定 domain 派生 + sender local-part」(emails v2) | — |
| 7 | 2026-06-20 | Code-Is-Truth 對齊:§4 付款 tab 列由「🔒 唯讀 / env-backed」改為「provider 唯讀(env)+ ECPay 憑證 per-tenant 可寫表單」,反映 code 的 updateEcpayCredentialsAction(特店 ID / HashKey / HashIV write-only 加密存租戶 DB、HashKey/HashIV 永不回讀),與既有註腳一致 | — |
| 8 | 2026-06-25 | §4 新增第 9 個 accounts(收款帳戶)tab:receiving_accounts 目錄維護從 多帳戶對帳 移入(租戶配置歸設定;對帳頁右上 link)。帳戶 product-agnostic(解綁產品線)、外幣帳戶=外幣池。(匯率改為獨立頁 /admin/exchange-rates,非 settings tab,見 currency-exchange §4.1。)未實作(code 目錄仍在對帳頁)。 | — |
| 10 | 2026-07-02 | 進階會計模組閘落地:accounts / expense-categories / ledger-fees 三 tab 改 accountingActive server 端顯隱 + 目錄資料 module OFF 不抓;write action 補 requireAccountingModule();accountant + module OFF → 「尚未開通進階會計模組」空狀態。§4 加模組閘註記。見 accounting-module §5/§6。 | — |
| 11 | 2026-07-07 | §5 擴充點新增「開發者 tab」(API 金鑰自助申請 + CLI session 管理):設計在 tenant-cli §3.1、wireframe 先行、未實作、資料不進 tenant_settings | — |
| 9 | 2026-07-01 | Code-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 .html → apps/wireframes/app/admin/settings/page.tsx。 | — |