# cairn > cairn 是為台灣旅行社而生的營運系統(多租戶 SaaS ERP):訂單、金流、出團、內外帳一套走完。每個後台操作都同時是一條 `cairn` CLI 命令與一個版本化 /api/v1 端點——同一套核心邏輯、同權限、同稽核,人與 AI agent 都能操作。 重要事實: - API base URL 掛在各租戶自己的網域下:https:///api/v1 - 認證:OAuth 2.0 device flow 核發的 bearer token(權限沿用帳號角色) - 回應外殼一律 { "ok": true, "data": … } / { "ok": false, "error": { "code", "message" } } - 模組制:核心訂單金流每租戶都有;lodging / climbing / accounting / lottery / overseas 按方案啟用,未啟用回 403 module_disabled - 文件內容自 command registry 與路由樹生成,永遠跟著 code ## 文件 - [文件首頁](https://cairn-website-jtm.pages.dev/docs): 導覽、快速開始、依模組瀏覽 - [CLI 參考](https://cairn-website-jtm.pages.dev/cli): 259 條命令(含安裝、認證、production 安全) - [API 參考](https://cairn-website-jtm.pages.dev/api): 292 個端點(含認證、錯誤碼) ## 概念指南 - [平台架構總覽](https://cairn-website-jtm.pages.dev/guides/architecture): 多租戶、認證、權限與模組、錯誤格式——串接 cairn 前必讀的全局模型。 - [權限與角色](https://cairn-website-jtm.pages.dev/guides/rbac): 角色、權限字串、owner-scope 與模組閘如何決定你的 token 能呼叫什麼。 - [行程與梯次](https://cairn-website-jtm.pages.dev/guides/trips-departures): 行程建檔、梯次與席次、團體代碼、抽籤與未中籤備案的關係。 - [旅客與個資](https://cairn-website-jtm.pages.dev/guides/travelers): 旅客名單、證件與效期、PDPA 加密與授權揭露、登山審核。 - [出團準備與線控](https://cairn-website-jtm.pages.dev/guides/departure-prep): per-departure 的分房、護照、裝備、領隊、請款等批次線控作業。 - [訂單與金流模型](https://cairn-website-jtm.pages.dev/guides/orders-money-model): kind-agnostic 訂單外殼、應收明細、收款與現金、三軸狀態與退款。 - [付款流程](https://cairn-website-jtm.pages.dev/guides/payments): 收款生命週期、hosted redirect、ATM、webhook 與 per-tenant ECPay 設定。 - [折扣碼與分潤](https://cairn-website-jtm.pages.dev/guides/discounts): 折扣碼設定與套用規則,以及 KOL 分潤如何掛進訂單金流。 - [內外帳與損益](https://cairn-website-jtm.pages.dev/guides/accounting): 內帳 / 外帳、可申報額、支出證明、每梯次淨利與公司月損益。 - [收款帳戶與結算](https://cairn-website-jtm.pages.dev/guides/accounts-settlement): 多收款帳戶分流、對帳、跨帳戶補款 / 代墊與月結互抵。 - [成本與範本](https://cairn-website-jtm.pages.dev/guides/costs): 梯次成本登記、系列團成本範本與人頭公式自動重算。 - [請款與出款](https://cairn-website-jtm.pages.dev/guides/payouts): 供應商 / 嚮導 / 佣金出款與請款單核簽流程(與客戶退款的區別)。 - [訂房產品線](https://cairn-website-jtm.pages.dev/guides/lodging): 住宿庫存、散客直訂與行程配套,如何接上訂單與金流模型。 - [裝備租借](https://cairn-website-jtm.pages.dev/guides/equipment): 裝備主檔、租借與歸還、逾期,以及出團裝備需求。 - [嚮導與排班](https://cairn-website-jtm.pages.dev/guides/guides-roster): 嚮導建檔、梯次派班,以及嚮導受限 login portal。 - [同業(B2B)](https://cairn-website-jtm.pages.dev/guides/agencies): 同業建檔、業務歸屬、佣金與帳期,以及同業訂單。 - [設定與範本](https://cairn-website-jtm.pages.dev/guides/settings): 租戶設定、付款憑證、寄信網域,以及罐頭訊息與同意書範本。 --- export const metadata = { title: '平台架構總覽 — cairn 指南', description: '認證、租戶隔離、權限與模組、錯誤格式——開始串接 cairn API / CLI 前的基本心智模型。', }; # 總覽 cairn 是多租戶旅行社營運平台。你用 bearer token 對租戶自己的網域呼叫 [API](/api) 或 [CLI](/cli),操作那個租戶——和租戶在後台做的事完全等價。 每個旅行社(租戶)有自己的網域與資料,彼此完全隔離。本篇建立四個基本概念:認證與租戶、權限、模組、錯誤格式;其餘各篇深入單一領域([訂單與金流](/guides/orders-money-model)、[付款](/guides/payments)…)。 ## API 等同後台 凡是人在後台網頁能做的操作,API 與 CLI 都做得到——而且走**相同的業務規則、相同的權限檢查、相同的稽核**。API 不是另一條捷徑或子集,是後台的同義傳輸層。 (圖:流程圖,略) CLI 只是 API 的命令列封裝;[CLI 參考](/cli)每個命令都標了它對應的 `/api/v1` 端點。 ## 認證與租戶 - 每個請求帶 `Authorization: Bearer `。 - **你打哪個租戶的網域,就操作哪個租戶**。token 與工作階段隨租戶網域天然隔離;你無法用一個租戶的 token 存取另一個租戶。 - token 對應一個使用者與其角色,決定你能呼叫哪些端點。 ```bash curl https://your-tenant.example.com/api/v1/orders \ -H "Authorization: Bearer $CAIRN_TOKEN" ``` ## 權限與角色 每個 token 屬於一個使用者,使用者有角色;角色決定授權範圍。 | 角色 | 典型範圍 | | --- | --- | | `admin` | 全功能 + 使用者管理 | | `sales` | 自己經手的訂單與客戶 | | `op` | 出團作業、線控 | | `accountant` | 財務、核簽、報表 | - [API 參考](/api)每個端點都標了它要求的權限(如 `order.read`、`order.write`)。token 權限不足時回 `403 forbidden`。 - 部分端點有 **owner-scope**:例如業務只看得到自己經手的訂單。範圍由伺服器依 token 的使用者重新判定,不接受用參數覆寫。 ## 模組 部分進階功能屬**可選模組**,租戶可能未啟用: | 模組 | 涵蓋 | | --- | --- | | 訂房(lodging) | 住宿庫存與直訂 | | 進階會計(accounting) | 內外帳、損益、結算 | | 登山(climbing) | 登山審核、裝備 | | 海外(overseas) | 海外團 | | 抽籤(lottery) | 名額抽籤 | 呼叫未啟用模組的端點會回 `403 module_disabled`。 ## 錯誤格式 所有 `/api/v1` 回應都是統一信封。成功: ```json { "ok": true, "data": { /* … */ } } ``` 失敗: ```json { "ok": false, "error": { "code": "validation", "message": "…", "issues": [ /* … */ ] } } ``` 常見錯誤碼: | code | HTTP | 意義 | | --- | --- | --- | | `unauthorized` | 401 | 無效或缺少 bearer token | | `forbidden` | 403 | 權限不足 | | `module_disabled` | 403 | 模組未啟用 | | `not_found` | 404 | 資源不存在,或不在你的可見範圍 | | `conflict` / `invariant` | 409 | 狀態衝突(如重複、座位不足) | | `validation` | 422 | 輸入未通過驗證(附 `issues[]` 逐欄說明) | ## 主要資源 | 領域 | 資源 | | --- | --- | | 銷售與出團 | [trips](/api#trips)、[departures](/api#departures)、[orders](/api#orders) | | 客戶與名單 | [members](/api#members)、travelers(掛在 order 下)、[agencies](/api#agencies) | | 金流 | [payments](/api#payments)、[payment-requests](/api#payment-requests)、[payouts](/api#payouts) | | 出團作業 | [guides](/api#guides)、[guide-roster](/api#guide-roster)、[equipment](/api#equipment) | | 財務會計 | [pnl](/api#pnl)、[internal-external-ledger](/api#internal-external-ledger)、[reconciliation](/api#reconciliation) | | 設定 | [settings](/api#settings)、[roles](/api#roles)、[discount-codes](/api#discount-codes) | ## 下一步 - [訂單與金流模型](/guides/orders-money-model):訂單物件、應收與收款、狀態與退款。 - [付款流程](/guides/payments):收款、redirect、webhook。 - [API 參考](/api) · [CLI 參考](/cli):逐端點 / 逐命令的參數與回應。 --- export const metadata = { title: '權限與角色 — cairn 指南', description: '角色與典型範圍、權限字串如何對應端點、owner-scope、模組閘,以及自訂角色——理解 403 forbidden / module_disabled 的來源。', }; # 權限與角色 每個 token 屬於一個使用者,使用者有一個角色;角色決定你能呼叫哪些端點、看得到誰的資料。 本篇說明角色與其典型範圍、權限字串怎麼對應到端點、為何業務只看得到自己的訂單,以及哪些功能受模組與自訂角色控制。 ## 角色總覽 cairn 內建六種角色。`admin` / `sales` / `op` / `accountant` 是後台員工角色,`customer` 是顧客,`guide` 是嚮導受限登入。 | 角色 | 典型範圍 | | --- | --- | | `admin` | 全功能 + 使用者與角色管理 | | `sales` | 自己經手的訂單與客戶、同業、行程內容 | | `op` | 出團作業、線控(梯次、排班、備辦、裝備、審核裁決) | | `accountant` | 財務(收款、請款核簽、退款核准、內外帳、損益、對帳) | | `customer` | 只看自己的訂單與個人資料 | | `guide` | 受限登入:只進嚮導 portal、登記自己帶團的成本 | 只有 `sales` 與 `admin` 可被指派為訂單的負責人。`guide` 是受限登入,**不能**進後台或管理其他使用者;`customer` 只能操作自己的資料。 ## 權限字串如何對應端點 每個端點都要求一個 **權限字串**,格式是 `resource.action`(資源 + 動作)。[API 參考](/api)逐端點標了它要求的權限——你的角色被授予該權限,才呼叫得動。 | 權限 | 允許的動作 | 範例端點 | | --- | --- | --- | | `order.read` | 讀訂單 | [`GET /orders`](/api#orders) | | `order.create` | 建訂單 | [`POST /orders`](/api#orders) | | `order.adjust` | 掛帳 / 折讓 / 加收 / 沖正 | 訂單應收調整 | | `trip.manage` | 建 / 編 / 刪行程與梯次 | [trips](/api#trips) | | `payable.approve` | 核准請款 | [payment-requests](/api#payment-requests) | | `refund.approve` | 核准退款 | [`/orders/{id}/refunds`](/api#orders) | | `ledger.read` | 看內外帳 / 淨利 | [internal-external-ledger](/api#internal-external-ledger) | 一個資源的不同動作可分別授權:例如 `op` 有 `order.read` / `order.update` / `order.adjust`,但沒有 `order.create`;`accountant` 只有 `order.read`。**讀與寫分開**——能看不代表能改。 呼叫端點時,伺服器依你 token 的角色檢查所需權限;不足即回 `403 forbidden`。 (圖:流程圖,略) ## Owner-scope:只看自己的 部分端點除了權限檢查,還會依 token 的使用者**自動限縮可見範圍**。最典型的是訂單: - `sales` 有 `order.read`,但只看得到**自己經手**的訂單。 - `customer` 只看得到**自己名下**的訂單與個人資料。 範圍由伺服器依 token 的使用者重新判定,**不接受用參數覆寫**。落在範圍外的資源,對你而言形同不存在——查詢列表時不會出現,直接取用會回 `404 not_found`(而非 `403`,避免洩漏存在性)。 不要用 owner-scope 當唯一授權假設。`admin` / `op` / `accountant` 等管理角色看得到全部訂單;owner-scope 是**業務心理隔離**的範圍限縮,不是逐租戶隔離(租戶隔離由網域天然完成,見[總覽](/guides/architecture))。 ## 模組閘 部分進階功能屬**可選模組**,租戶可能未啟用。即使你的角色有對應權限,模組關閉時相關端點仍會回 `403 module_disabled`: | 模組 | 涵蓋 | | --- | --- | | 訂房(lodging) | 住宿庫存與直訂 | | 進階會計(accounting) | 內外帳、損益、結算 | | 登山(climbing) | 登山審核、裝備 | | 海外(overseas) | 海外團 | | 抽籤(lottery) | 名額抽籤 | 模組閘**先於**權限檢查:先確認租戶啟用了模組,再看你的角色有沒有權限。 (圖:流程圖,略) 兩個 `403` 要分開處理:`module_disabled` 代表「整個功能未開通」(要租戶在設定啟用模組),`forbidden` 代表「你這個角色沒權限」(要換有權限的 token 或調整角色)。 ## 自訂角色 除了六個內建角色,`admin` 可以建立**自訂角色**,用「資源 × 動作」矩陣勾選要授予哪些 `resource.action`。 - 自訂角色只能引用**既有的權限**——權限目錄由平台定義,你無法憑空造出新的 `resource.action`。 - 內建六角色**唯讀**:可檢視其權限矩陣,但不能改名或刪除。 - 編輯角色權限本身需要 `role.manage` 權限(僅 `admin`)。 透過 [roles API](/api#roles) 可列出角色(內建 + 自訂)並讀取單一角色的完整權限矩陣;指派使用者角色與建立員工帳號走 [staff API](/api#staff)。 要查「某個 token 為何被擋」,先看 [API 參考](/api)該端點標的權限字串,再用 [roles API](/api#roles) 對照該使用者角色的權限矩陣有沒有那一格;若端點屬模組功能,也確認租戶啟用了該模組。 ## 下一步 - [總覽](/guides/architecture):認證、租戶隔離、模組、錯誤格式的全貌。 - [roles API](/api#roles) · [staff API](/api#staff) · [members API](/api#members):角色、員工、會員的逐端點參數與回應。 --- export const metadata = { title: '行程與梯次 — cairn 指南', description: '行程是產品定義,梯次是可賣的出團單位;席次保座、價格通路、團號、抽籤與備案——從 API 的角度理解一個團怎麼被賣出去。', }; # 行程與梯次 一個「行程」是你上架的產品定義;真正被報名、被扣席、被開團號的是它底下的「梯次」。客人買的永遠是某一個梯次。 理解這層關係,你才能正確使用 [`GET /trips`](/api#trips) 與梯次相關端點——行程帶目錄與行銷內容,梯次帶日期、名額與售價。 ## 行程與梯次的關係 一個**行程(trip)**是產品目錄上的一張卡:標題、目的地、天數、起價、上架狀態。它不是可賣的單位——它底下掛**梯次(departure)**,每個梯次有自己的出發日、回程日、名額與售價,才是客人實際報名的對象。 (圖:流程圖,略) | 概念 | 是什麼 | 在 API 上 | | --- | --- | --- | | 行程 trip | 產品定義(目錄 / 行銷內容) | [`/trips`](/api#trips) | | 梯次 departure | 可賣的出團單位(日期 + 名額 + 售價) | [`/departures`](/api#departures) | 行程的 `status` 是 `draft`(草稿)/ `published`(上架)/ `archived`(封存);梯次的 `status` 是 `selling`(銷售中)/ `closed` / `cancelled` / `completed`。列表回傳的 `activeDepartureCount`、`nextDepartureDate` 讓你不必逐筆展開就知道一個行程有沒有在賣、下一梯何時出發。 **訂房(lodging)不走這個模型。** 住宿產品是每晚房型庫存,沒有「梯次」概念。本篇只談梯次團(tour)。 ## 席次與保座 每個梯次有名額(`capacity`)與已報名數(`bookedCount`)。報名時平台用**保座**方式扣席——不是「先讀再寫」,而是條件式扣減,所以多人同時搶最後幾個位子不會超賣。 名額不足時,建立訂單會回 **`409 conflict`**。這是預期行為,不是錯誤——代表這個梯次在你送出的當下已經滿了。請以重新整理名額、或改報其他梯次來處理,不要重試同一筆。 取消訂單會把席次釋回該梯次,名額即時反映。 ## 價格通路(直客 / 同業) 同一個梯次對不同客戶可能有不同售價。成交單價由平台依**通路**解析,順序大致是:梯次優惠價 → 梯次價 → 行程起價,缺價則拒絕結帳。 | 通路 | 意義 | | --- | --- | | 直客(direct) | 一般客人價 | | 同業(agency) | 旅行同業窗口價 | | 優惠(promo) | 梯次層的活動覆寫價 | 成交當下,平台會把**當時的單價與通路快照**寫進訂單明細——之後你改梯次或行程價格,**不會回寫**已成立的訂單。客戶分類只是結帳時的預設通路,不是訂單事實;訂單的價格事實永遠在那張單的明細上。 ## 團號 團號是梯次的**對外識別碼**,貫穿報名、開票、估價、出團通知與對帳。它編碼了出團年份、地區(山域)、出發日與同日同地的流水序;海外線多帶一段航空碼: ```txt 國內團(無航空):26 YS 1010 A → 26YS1010A 海外團(有航空):26 EG TK 1010 A → 26EGTK1010A YY 地區 [航空] MMDD 序號 ``` - **自動產生**:選好地區、(海外才有的)航空與出發日,平台拼出團號並配同地區同日的序號(A / B / C…)。團號在租戶內**唯一**,併發產生不會撞號。 - **可覆寫**:需要沿用舊系統團號或特例時可手填,仍須過唯一與格式檢查。 - **可搜尋**:[`GET /trips`](/api#trips) 的 `groupCode` 參數用團號(前綴亦可)回查含該團號梯次的行程;列表的 `representativeGroupCode` 是該行程近期的代表團號。 地區碼與航空碼是**每個租戶自行維護**的對照表([`/group-codes`](/api#group-codes)),新增線路不需要改任何程式。停用的碼不再出現在新梯次的選項,但既有團號保留。 ### 團號格式可自訂 上面的 `26YS1010A` / `26EGTK1010A` 只是**內建預設**。團號由哪些段組成、序號用什麼樣式,都可以在後台**依租戶自訂**——例如把年改成四碼、加分隔符、序號改成補零數字(`001`)或跳過易混淆的 `I` / `O`,甚至完全不放地區與航空段(如年 + 月 + 序號)。 改團號格式**不會回寫既有梯次的團號**,只影響之後新產生的團號;歷史團號一律保留、繼續唯一。 ## 抽籤與備案行程 熱門梯次(國家公園山屋床位、permit 配額)的名額不是先到先得,而是**抽籤**決定。把梯次的分配模式設成抽籤([`PATCH /departures/{id}/allocation-mode`](/api#departures) 的 `mode = lottery`),它底下的訂單就從「報名」變成「**應募**」。 抽籤演算法本身**不在 cairn 裡**——它留在租戶既有的試算表流程。cairn 負責的是抽籤的**地基**與**結果落地**:每筆應募訂單有自己的抽籤狀態(已應募 → 中籤 / 未中籤),由線控逐單裁決。應募即扣**本梯次**的席(代表「誰報了這梯」),抽籤搶的是外部稀缺資源、不是 cairn 的席次。 裁決有三個出口: (圖:流程圖,略) 旅客報名抽籤梯次時,可從**同行程的其他梯次**自選一串**有序備案**(可混先到先得與抽籤梯次)。未中籤時,平台沿這串清單**鏈式自動轉單**:遇先到先得且有位即當下成立、遇抽籤梯次則重新應募等下一輪、清單走完才退訂金。轉單過程訂金一路結轉、自動催尾款,全程不用人工逐筆搬。備案候選一律與主梯**同價**,所以跨單結轉不會產生價差。 ## 與訂單的銜接 客人報名某個梯次,就是建立一張 tour 訂單:訂單明細指向該梯次,人數(`partySize`)掛在訂單上。價格、訂金 / 尾款怎麼拆、收了多少,全部由訂單的金流模型接手——梯次只提供「賣什麼、什麼價、還有沒有位」。 (圖:流程圖,略) ## 下一步 - [訂單與金流模型](/guides/orders-money-model):報名之後,應收、現金與三軸狀態的全貌。 - [付款流程](/guides/payments):收訂金 / 尾款、redirect、webhook。 - [trips API](/api#trips) · [departures API](/api#departures) · [group-codes API](/api#group-codes)。 --- export const metadata = { title: '旅客與個資 — cairn 指南', description: '旅客掛在訂單下、證件號為加密個資、揭露需授權並留稽核——從 API 的角度安全地處理旅客名單與個人資料。', }; # 旅客與個資 旅客是掛在訂單下的出行名單,不是帳號;他們的證件號、護照號是受保護的個資——平台只回傳遮罩值,要看明文得另外申請、而且每次都留稽核。 一筆訂單帶一份旅客名單(`travelers[]`)。同一個人可能橫跨多筆訂單、出現為多筆旅客,所以旅客**不是**會員或客戶的錨點——它只描述「這筆訂單上要出行的這些人」。本篇說明旅客資料模型、增刪改、分房、個資揭露、證件效期與登山審核,以及會員端的 PDPA 自助。 ## 旅客掛在訂單下 旅客沒有獨立資源,一律隨訂單帶出。讀一筆訂單 [`GET /orders/{id}`](/api#orders) 就拿到 `travelers[]`,每位旅客包含: | 欄位(回傳) | 意義 | | --- | --- | | `id` | 旅客 id(在這筆訂單內) | | `fullName` | 姓名 | | `idNumberMask` / `passportNoMask` | 身分證 / 護照號的**遮罩值**(非明文,見下) | | `birthDate` | 生日 | | `phone` / `email` | 聯絡方式 | | `emergencyContactName` / `emergencyContactPhone` | 緊急聯絡人 | | `gender` | 性別(分房硬約束用) | | `roomPreference` / `roommatePref` / `roomLabel` | 分房偏好與已配房間 | | `address` | 通訊地址(入山 / 投保名冊用) | | `isPrimary` | 是否為訂購人本人 | (圖:流程圖,略) **會員 ≠ 旅客**。會員([members](/api#members))是登入帳號、是訂單的訂購人;旅客是那筆訂單上實際出行的人。一個人可以同時是會員與旅客,但兩者在資料上是分開的——別拿旅客當客戶主檔。 ## 新增、更新、移除旅客 旅客的增刪改都掛在訂單下,且都要求 `order.update` 權限: | 操作 | 端點 | | --- | --- | | 新增旅客 | [`POST /orders/{id}/travelers`](/api#orders) | | 更新旅客 | [`PATCH /orders/{id}/travelers/{travelerId}`](/api#orders) | | 移除旅客 | [`DELETE /orders/{id}/travelers/{travelerId}`](/api#orders) | 新增 / 更新可帶 `fullName`、`phone`、`email`、`birthDate`、`emergencyContactName`、`emergencyContactPhone`、`gender`、`address`,以及 `idNumber` / `passportNo`(證件號,寫入即加密保存)。`POST` 回傳新建的 `travelerId`。 ```bash curl -X POST "https://your-tenant.example.com/api/v1/orders/$ORDER_ID/travelers" \ -H "Authorization: Bearer $CAIRN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "fullName": "王小明", "birthDate": "1990-05-30", "idNumber": "A123456789" }' ``` 旅客名冊有**可改窗口**:出發前約一週仍可調整(給入山證、投保名冊留緩衝),逼近出發後修改會被收斂。建檔時平台會逐欄檢查(身分證檢查碼、生日合理性、緊急聯絡人不可是同團隊員等)並把問題標出來,但**不會擋存**——你可以分次把資料補齊。 ## 分房偏好 分房偏好可在新增 / 更新旅客時一併帶,也可以單獨用 [`PATCH /orders/{id}/travelers/{travelerId}/rooming`](/api#orders)(同樣 `order.update`)只改 `roomPreference` / `roommatePref`。`gender` 是分房的硬約束、`roomLabel` 則反映實際配到的房間——實際配房作業在團控端進行,這裡只負責表達偏好。 ## 個資的揭露與授權 證件號與護照號是受保護的個資:平台**加密保存**,列表與訂單詳情一律只回**遮罩值**(`idNumberMask` / `passportNoMask`)。要看明文,得針對該旅客、該欄位單獨申請: [`POST /orders/{id}/travelers/{travelerId}/reveal`](/api#orders) 帶 `field`(`idNumber` 或 `passportNo`),回傳 `{ field, value }`。 (圖:流程圖,略) 揭露明文是 **order-scoped 授權 + 留稽核**的操作:你只能揭露你看得到的那筆訂單上的旅客,且每一次揭露都會記進稽核軌跡(防止有人靠猜 id 跨訂單撈個資)。reveal 要求 `order.read`,但它不是免費的讀——把它當成「需要時才申請、會被記錄」的動作,不要批次預先解密整份名單。 ## 證件效期提醒 海外團多數國家要求入境時護照效期還有半年以上。當[海外模組](/guides/architecture#模組)啟用時,平台用旅客的護照到期日比對出發日,被動標出「效期不足 / 出發前已過期」,門檻(預設約 180 天)可由租戶調整。 跨梯次想一次撈出「名下有即將出團、且護照效期會出問題」的會員,用 [`GET /members/expiry-warnings`](/api#members),回傳一組 `userIds`,再順著會員去查對應訂單與旅客。效期欄位本身隨旅客更新(沿用上面的旅客更新端點),未填到期日視為「未知」、不算警示。 ## 登山審核 當[登山模組](/guides/architecture#模組)啟用、且行程標了難度時,建立訂單會自動跑一次資格審核:以客人的登山經歷(自填 + 過往完成的訂單)算出能力分,對照行程難度給出判定。判定**不硬擋下單**——能力不足只會標記為待人工審核。 訂單上的審核結果有幾種:`not_required`(行程未評級)、`auto_pass`(自動通過)、`needs_review`(待人工)、`manual_approved` / `manual_rejected`(人工核可 / 駁回)。`needs_review` 的單由有權限者用 [`POST /orders/{id}/review-screening`](/api#orders)(`screening.review`)核可或駁回。 審核**不擋金流、不擋建單**——它是安全把關的提醒,不是付款前置條件。一筆 `needs_review` 的訂單照樣能收款,由團隊在出團前處理(補經歷、勸退、或改報難度較低的團)。 ## 會員與 PDPA 自助 會員(訂購帳號)這一側透過 [members](/api#members) 資源讀取:[`GET /members`](/api#members) 列表、[`GET /members/{id}`](/api#members) 取單一會員(含其訂單、同意書 `consents`)。回傳欄位含 `pdpaSignedAt`(同意條款時間)與 `anonymizedAt`(是否已被匿名化)。 會員享有 PDPA 的兩項基本權利,平台都支援: - **資料可攜**:匯出該會員名下的帳號、訂單、旅客、同意與金流紀錄。 - **終止 / 刪除權**:[`POST /members/{id}/anonymize`](/api#members)(`member.update`)做**真正的匿名化軟刪**——抹除可識別 PII(會員與其名下旅客)、封鎖登入、撤銷憑證。 匿名化會**保留**訂單外殼、金流與請款紀錄、不可竄改的同意書與稽核——這些是法遵與對帳必須留存的軌跡。換句話說:人被去識別化,但帳務與「曾取得同意」的證據完整保留。匿名化不可逆,呼叫前請確認。 ## 下一步 - [訂單與金流模型](/guides/orders-money-model):旅客所掛的訂單外殼、應收與收款、狀態與退款。 - [orders API](/api#orders):旅客子路由(travelers / reveal / rooming)與審核端點的逐欄參數。 - [members API](/api#members):會員列表、明細、效期提醒與匿名化。 --- export const metadata = { title: '出團準備與線控 — cairn 指南', description: '逐梯次的出團備辦:從線控進入,辦分房、證件、裝備需求、領隊指派、派車餐食投保各項清單,到對款請款入口。', }; # 出團準備與線控 出團準備是**逐梯次(per-departure)的批次作業**——你不是一張訂單一張訂單處理,而是鎖定一個梯次,把這團要辦的事一次辦完。所有準備作業的入口都是「線控」。 本篇說明線控怎麼運作,以及分房、證件、裝備、領隊與各項營運清單各自接哪個 API。 ## 從線控進入 先用 [`GET /departures/control`](/api#departures) 取得待辦梯次清單(可依 `range` / `trip` / `status` 篩選,或只看需要關注的梯次)。點進某個梯次後,所有準備作業都掛在 `/departures/{depId}/...` 之下: (圖:流程圖,略) - 一份**出團名單**(旅客、應收、收款)由 [`GET /departures/{depId}/manifest`](/api#departures) 取得;名單體檢(缺證件 / 缺同意書等)看 [`.../validation-summary`](/api#departures)。 - per-departure 作業一律從線控進,不是各自獨立的功能頁。 ## 行前總檢查與模組界線 每個梯次有一份固定的**行前總檢查**清單(收款、領隊、交通、餐食、保險、證件、裝備、名單八項),逐項勾稽完成度,經 [`PUT /departures/{depId}/prep/checklist`](/api#departures) 切換。 準備作業分兩層: | 區塊 | 是否受模組 gate | | --- | --- | | 分房、領隊、對款、派車、餐食、投保、行前檢查 | **核心骨幹**——永遠可用 | | 證件追蹤、期限警戒、裝備需求 | **登山模組**專屬——關閉時不出現 | 讀取準備工作台需 `departure_prep.read`;寫入(分房以外的各軸)走 `departure_prep.manage`。證件 / 期限 / 裝備三項另受租戶的**登山模組**開關控制——租戶沒開登山模組時,這些子路由不適用。權限與模組詳見 [總覽](/guides/architecture)。 ## 分房(含自動配對預覽) 分房分兩階段:**下單時登記偏好**(自動配房 / 指定房友 / 加價單人房),**線控才權威排房**。 (圖:流程圖,略) - [`GET .../rooms/auto-pair-preview`](/api#departures) 依同團同性別與指定房友**只回建議分組**,不直接寫入;你拿建議的旅客分組去 [`.../rooms/auto-pair-apply`](/api#departures) 一次建房並整組指派。 - 也可手動 [`POST .../rooms`](/api#departures) 建房後逐位指派 / 取消指派。房間不可超過床位數、性別為硬約束。 - 把旅客排進升級 / 單人房型會**新增一筆應收**並產生補款入口(不會靜默改大已付訂單),金流規則見 [訂單與金流模型](/guides/orders-money-model)。 - [`GET .../rooming-export`](/api#departures) 匯出分房表(CSV / JSON)給山屋、嚮導、保險文件用;敏感欄位依遮罩規則。 ## 證件與文件(登山) 登山團要追蹤入山證、入園證、床位核准證、營位核准證。經 [`PUT .../prep/documents`](/api#departures) 記每種證件的申請狀態(`todo` → `applying` → `approved`)、檔案是否就緒與雲端連結。 期限警戒由 [`PUT .../prep/deadlines`](/api#departures) 設定入山申請 / 入山證下載 / 登山險 / 旅責險四個期限;系統以**工作天倒數**(扣週末與國定假日)派生緊迫程度,提醒你別錯過送件窗口。 ## 裝備需求 線控在梯次盤點要借多少裝備,經 [`POST .../prep/equipment-needs`](/api#departures) 逐品項登記需求量(同品項再送為覆寫)。 回應會帶**跨梯次彙總警示**:把同日期窗各梯次對同款裝備的需求加總,比對可租庫存——需求超過庫存標為**缺**(shortage),剩餘 ≤ 2 標為**低水位**(lowStock)。這讓你在排某梯次時就看到鄰近梯次會不會把同款裝備借光。旅客真正借出走訂單的租借流程(見 [裝備管理](/api#equipment)),與這裡的需求調查並存不重複扣庫存。 ## 領隊指派 經 [`PUT .../prep/guides`](/api#departures) 把嚮導指派為**領隊**或**隨隊**,狀態分**暫定**(tentative)與**確認**(confirmed)。確認的領隊可登入嚮導 portal 回填現場成本與資料。 ## 各項營運清單:派車 · 餐食 · 投保 派車、餐食、投保是三組 per-departure 的**營運資訊**(記安排狀態,**不存成本**): | 軸 | API | 記什麼 | | --- | --- | --- | | 派車 | [`.../prep/transport`](/api#departures) | 路段、駕駛、車輛、車資備註、狀態 | | 餐食 | [`.../prep/catering`](/api#departures) | 供應商、餐食方案、人數備註、狀態 | | 投保 | [`.../prep/insurance`](/api#departures) | 險種(旅平 / 旅責)、保險公司、保單號、狀態 | 這三軸只記**營運安排**,不是成本。實際車資 / 餐費 / 保費等成本一律走梯次成本登記(下節),別把金額塞進這裡的備註欄。 ## 對款與請款入口 線控的「對款」是**唯讀對帳**視圖:[`GET .../finance-summary`](/api#departures) 派生團費收齊度,以及供應商 / 裝備住宿的應付未付(已付 / 已轉未付 / 未轉)。它不寫任何成本。 成本的登記與請款是另一條路: (圖:流程圖,略) - 用 [`POST .../costs`](/api#departures) 登記梯次成本,再用 [`.../payables`](/api#departures) 把選定成本轉成對供應商 / 嚮導 / 佣金的出款請款。 - 請款單的核簽流程見 [payment-requests](/api#payment-requests);對客人收款與退款是另一回事,見 [付款流程](/guides/payments)。 ## 下一步 - [行程與梯次](/guides/trips-departures):梯次怎麼來、席次與分配模式。 - [訂單與金流模型](/guides/orders-money-model):房升補款、應收與三軸狀態。 - [付款流程](/guides/payments) · [departures API](/api#departures)。 --- export const metadata = { title: '訂單與金流模型 — cairn 指南', description: '訂單物件、應收明細、收款與現金、三軸狀態與退款生命週期——從 API 的角度理解一張訂單的錢。', }; # 訂單與金流模型 一張訂單把「賣了什麼」「客人欠多少」「收了多少」「退了多少」拆成獨立明細;訂單上的金額與狀態都是這些明細算出來的結果。 理解這點,你才能正確解讀 [`GET /orders/{id}`](/api#orders) 回傳的欄位——外殼上的 `*Twd` 與狀態都是派生值,不是你直接設定的欄位。 ## 訂單物件 一張訂單(order)是與產品種類無關的外殼(`kind` 為 `tour` 或 `lodging`)。它**本身不帶總額**——金額來自下面幾組明細,訂單上看到的 `*Twd` 與狀態欄位都是派生值: (圖:流程圖,略) | 欄位(回傳) | 意義 | | --- | --- | | `orderNumber` | 訂單編號 | | `kind` | `tour`(梯次團)或 `lodging`(住宿) | | `bookingState` / `paymentState` / `refundState` | 三軸狀態(見下) | | `grossReceivableTwd` | 應收總額(`chargeLines` 加總) | | `collectedTwd` | 已收現金 | | `customerCashAppliedTwd` | 已套用到此單的客人現金(已收 − 已退) | | `outstandingTwd` | 尚欠 = 應收 − 已套用現金 | | `chargeLines` / `schedules` / `intents` / `transactions` / `travelers` | 各類明細陣列 | 訂單外殼沒有 `total`、沒有 `paidAt`。要知道客人欠多少看 `outstandingTwd`,收了多少看 `collectedTwd`——這些都由明細即時算出。 ## 金額怎麼來 金額都是**整數台幣**(客人一律付台幣)。回傳的金額欄位對應這組白話關係: ```txt 應收 grossReceivableTwd = 所有 chargeLines 金額加總(含折扣為負) 已收 collectedTwd = 所有收進來的現金 已套用 customerCashAppliedTwd = 已收 − 退給客人的現金 尚欠 outstandingTwd = 應收 − 已套用 ``` 所以折扣、加價、房升補款都是 `chargeLines` 裡的一筆(折扣是負數),不是訂單上某個彙總欄位。 ## 三軸狀態 訂單沒有單一「狀態」。顯示狀態由**三個獨立軸**組成: ```txt bookingState : pending_payment → confirmed → completed ;(任一 → cancelled) paymentState : unpaid | partially_paid | deposit_paid | paid | overpaid refundState : none | partially_refunded | refunded ``` (圖:流程圖,略) **`paymentState = paid` 不代表錢還在手上**——它只代表「應收已被現金覆蓋」。一張全額退款的訂單,`paymentState` 仍是 `paid`,退款狀態由 `refundState` 表達(與 Stripe 全退的 PaymentIntent 仍 `succeeded` 同理)。畫狀態標籤請三軸一起看,不要只看 `paymentState`。 ## 退款的兩種意義 退款分兩種,務必分清: | 種類 | 動應收嗎? | 算進 `refundState`? | | --- | --- | --- | | 減應收退款(取消 / 降價 / 服務未提供) | 是——同時調降 `grossReceivableTwd` | 是 | | 溢收退還(退多收的錢) | 否——只退現金 | 否 | 退款本身有自己的工作流狀態: ```txt draft → submitted → approved → processing → paid submitted → rejected ; approved → cancelled ; processing → failed ``` 對應 API:[`POST /orders/{id}/refunds`](/api#orders) 建立、`.../refunds/{refundId}/approve`、`/mark-paid`、`/reject`。 ## 常見流程 **建立 tour 訂單**:`POST /orders` 帶 `tripId` / `departureId` / `travelers` / `paymentMethod` 等,平台會以**保座**方式建單(座位不足回 `409 conflict`),並依付款方案產生收款計畫。 (圖:流程圖,略) **付款後加價(如房型升級)**:平台新增一筆 `chargeLines` → 訂單重新出現 `outstandingTwd` → 產生補款連結。**不會**靜默把已付訂單金額改大。 **讀取**:[`GET /orders`](/api#orders) 列表(依角色自動限縮可見範圍),[`GET /orders/{id}`](/api#orders) 取完整訂單(含旅客、應收、現金、收款計畫、退款、同意書)。每個欄位的型別見 API 參考的回應欄位表。 ## 下一步 - [付款流程](/guides/payments):收款、redirect、webhook、ECPay 設定。 - [總覽](/guides/architecture):認證、權限、模組。 - [orders API](/api#orders) · [CLI orders](/cli#orders)。 --- export const metadata = { title: '付款流程 — cairn 指南', description: '收款生命週期、hosted redirect、ATM 虛擬帳號、webhook 與冪等、per-tenant ECPay 設定與測試模式。', }; # 付款流程 當一張訂單有應收(`outstandingTwd > 0`),你向客人收款。收款是金流商無關的——流程一致,ECPay 等只是底層付款管道。 本篇說明收款的生命週期、redirect 與 webhook 怎麼運作,以及租戶如何設定自己的 ECPay。 ## 收款的生命週期 一筆收款會經過三個階段,反映在訂單的 `intents` 與 `transactions` 上: (圖:流程圖,略) | 階段 | 是什麼 | 在訂單上 | | --- | --- | --- | | 收款 intent | 「打算收的一筆錢」(**還不是收到**) | `order.intents[]` | | 付款嘗試 | 一次實際付款(redirect / 手動) | — | | 現金紀錄 | **真正收到 / 退出的現金**——報表基準 | `order.transactions[]` | 只有 `transactions` 是現金真相。intent 是「打算收」、嘗試是「進行中」,**都不能當收款證明**。intent 成功後即終態;下一筆收款(例如尾款、補款)會是一筆**新的** intent。 ## Hosted redirect 流程 線上刷卡 / 轉帳最常見的是把客人導去金流商頁面付款: (圖:流程圖,略) - 付款結果以 **webhook** 為準(return URL 只是把客人導回)。 - webhook 經**驗章與金額核對**才認列;重送同一筆不會重複入帳(**冪等**)。 - 認列後訂單的 `paymentState` / `outstandingTwd` 即時更新。 ## ATM 虛擬帳號(延遲付款) ATM 轉帳是**兩步**:先取號、客人去繳費、入帳才算收到: (圖:流程圖,略) 取號**還沒有現金**——拿到虛擬帳號只代表等待繳費。要等入帳 webhook 才會記一筆現金紀錄、才更新訂單狀態。 ## 設定 ECPay(per-tenant) ECPay 憑證是**每個租戶各自設定**的,不是平台共用: - 租戶在後台付款設定填入自己的**特店 ID、HashKey、HashIV**(也可經 [settings API](/api#settings))。 - HashKey / HashIV 加密保存,讀回只回遮罩值(write-only)。 - 上線後若未設定憑證,收款會被擋下(不會用平台預設)。 重送同一筆付款時,ECPay 每次會用**新的交易編號**(重送同號會被 ECPay 退件)。你的整合不需要自己管這個——cairn 重建付款時會處理。 ## 測試模式 開發 / 測試可切到 mock 付款,不實際打 ECPay: | 設定 | 行為 | | --- | --- | | 付款商 = `test` | mock:直接把訂單標為已付,方便端到端測試 | | 付款商 = `ecpay` + staging | 真打 ECPay sandbox | | 付款商 = `ecpay` + production | 正式收款 | ## 請款、退款、出款(別混) | 概念 | 方向 | API | | --- | --- | --- | | 跟客人收款 | 收進來 | 訂單收款(上述流程) | | 退客人錢 | 退出去 | [`/orders/{id}/refunds`](/api#orders) | | 對供應商 / 嚮導 / 佣金出款 | 出去(**非**客戶退款) | [payouts](/api#payouts) | | 請款單核簽流程 | — | [payment-requests](/api#payment-requests) | 退客人錢一律走訂單的退款流程(會反映在 `refundState`);對外付款走 payouts,兩者不混用。 ## 下一步 - [訂單與金流模型](/guides/orders-money-model):應收、現金、三軸狀態的全貌。 - [總覽](/guides/architecture):認證、權限、模組。 - [payments API](/api#payments) · [payment-requests API](/api#payment-requests)。 --- export const metadata = { title: '折扣碼與分潤 — cairn 指南', description: '折扣碼的設定規則、結帳時的試算與套用、折扣如何成為訂單應收裡的一筆負額,以及 KOL 分潤怎麼凍結快照。', }; # 折扣碼與分潤 折扣碼是一條規則:結帳時若通過驗證,就在訂單應收裡記下一筆**負額明細**,把客人要付的錢往下調。折扣不是訂單上某個彙總欄位,而是金流模型裡和商品同層的一筆 charge line。 理解這點,你才能正確解讀折扣——它和加價、房升補款一樣,都是 [訂單金流](/guides/orders-money-model) 裡的 `chargeLines` 之一,只是金額為負。本篇說明怎麼設定折扣碼、結帳時怎麼試算與套用、折扣如何反映在訂單,以及 KOL 分潤的概念。 ## 折扣碼物件 一張折扣碼(discount code)由三件事組成:**折抵規則**(折多少)、**適用限制**(誰、什麼時候、哪些商品能用)、以及選用的**分潤設定**(推薦人能拿多少)。建立後用 [`GET /discount-codes`](/api#discount-codes) 列表、[`GET /discount-codes/{id}`](/api#discount-codes) 取單一明細(含適用 trip 與兌換紀錄)。 | 欄位(回傳) | 意義 | | --- | --- | | `code` / `label` | 結帳輸入碼(不分大小寫須唯一)/後台辨識名稱 | | `discountType` / `discountValue` | `fixed`(定額 TWD)或 `percentage`(1–100 百分比) | | `isActive` | 是否啟用;停用後前台無法套用 | | `validFrom` / `validUntil` | 生效起迄日(留空為不限) | | `maxRedemptions` / `redemptionCount` | 總兌換上限/已兌換次數 | | `maxRedemptionsPerCustomer` | 每位客戶兌換上限 | | `minOrderTwd` | 最低應收門檻 | | `appliesToAll` / `tripIds` | 全站適用,或限定的行程清單 | | `allowedPriceChannels` / `allowedCustomerTypes` | 限定的價格通路/客戶分類 | | `stackable` | 是否可與其他折扣併用 | | `profitShareType` / `profitShareValue` / `profitSharePayee` | 分潤方式/數值/對象(見下) | ## 設定折扣碼 建立用 [`POST /discount-codes`](/api#discount-codes),至少帶 `code`、`label`、`discountType`、`discountValue`。其餘欄位都是用來收窄「什麼情況下這碼有效」: (圖:流程圖,略) - **折抵方式**:`fixed` 把 `discountValue` 當作折抵的台幣金額;`percentage` 把它當作百分比(1–100)套在折扣前應收上。 - **使用限制**:用 `maxRedemptions` 限總量、`maxRedemptionsPerCustomer` 限單客、`validFrom` / `validUntil` 限期間、`minOrderTwd` 設低消門檻。 - **適用範圍**:`appliesToAll` 為全站碼;或設 `tripIds` 把它綁定特定行程。 - **併用政策**:`stackable` 決定能否和別張折扣一起用。 更新用 [`PATCH /discount-codes/{id}`](/api#discount-codes)(送出完整欄位),單純啟停用 [`set-active`](/api#discount-codes)。 改規則**不回寫已成交的訂單**。每筆兌換在成交當下就凍結了一份快照(折多少、依什麼規則、分潤給誰),所以你事後調整折扣碼,不會動到歷史訂單的金額或分潤。 ## 結帳前試算 在真正套用前,先用 [`GET /discount-codes/preview`](/api#discount-codes) 做唯讀試算——它跑和正式結帳**同一套**驗證與計算,但**不消耗兌換名額**。帶上 `code`、`kind`、`total`,以及視情況的 `tripId` / `priceChannel` / `customerType` / `userId`。 回傳是一個結果信封,讓你能依機器可讀的理由分支: ```txt 成功: { ok: true, discountTwd, newTotalTwd, profitShareTwd } 失敗: { ok: false, reason } // 例如過期 / 額滿 / 未達門檻 / 通路不符 ``` 試算成功**不保證**結帳一定成功。名額是在結帳那一刻 race-safe 地鎖定的——兩個人同時用最後一個名額,只有一個會成功。把 preview 當成 UI 的即時提示,不要當成保留。 ## 折扣怎麼反映在訂單 折扣在結帳時和商品同一筆交易寫入:商品先記正向應收明細,折扣再記一筆**負額** charge line。於是訂單的應收自然往下調,你不需要自己算總額。 (圖:流程圖,略) - 折扣明細是 `order.chargeLines` 裡金額為負的一筆,和加價、房升補款並列——所以在 [`GET /orders/{id}`](/api#orders) 看到的 `grossReceivableTwd` 已經是折後應收。 - 折扣不會把應收壓到負數:折抵金額會被 floor 到讓應收最低為 0。 - 收款計畫(訂金 / 尾款)會跟著最新應收重算,你拿到的 `outstandingTwd` 就是折後尚欠。 不要在自己的 UI 重算折後金額——一律讀訂單回傳的 `grossReceivableTwd` / `outstandingTwd`。折扣的真相在訂單的應收明細,不在折扣碼本身。 ## KOL 分潤 折扣碼可同時當作**推薦分潤**的載體:當一張碼設了 `profitShareType` / `profitShareValue` / `profitSharePayee`,每次兌換就會在成交快照裡凍結一筆 `profitShareTwd` 與受款對象。這讓「給客人的折扣」和「給推薦人的分潤」能各自記帳。 用 [`GET /discount-codes/{id}/redemptions`](/api#discount-codes) 取兌換 / 分潤報表: | 欄位 | 意義 | | --- | --- | | `redemptions[]` | 每筆兌換:`orderNumber`、`originalTotalTwd`、`discountTwd`、`profitShareTwd`、`orderStatus` | | `totalDiscountTwd` | 此碼累計折抵 | | `totalProfitShareTwd` | 此碼累計分潤 | | `totalPaidTwd` | 對應訂單累計已收現金 | 分潤的「應付」和「實付」是兩回事。報表給你應分潤的金額,但**實際出款**走 cairn 的 payouts 流程(對推薦人 / 合作方付款),不是客戶退款。兩者不要混用——退客人錢看 [訂單退款](/guides/orders-money-model),付分潤看 [payouts](/api#payouts)。 ## 下一步 - [訂單與金流模型](/guides/orders-money-model):應收明細、現金、三軸狀態的全貌——折扣是其中的一筆負額。 - [付款流程](/guides/payments):折後應收怎麼向客人收款。 - [discount-codes API](/api#discount-codes) · [CLI discount-codes](/cli#discount-codes)。 --- export const metadata = { title: '內外帳與損益 — cairn 指南', description: '同一筆成本兩個金額(內帳真實 vs 外帳可申報)、支出證明、外幣雙率、每梯次毛利到公司月損益——從 API 的角度理解進階會計。', }; # 內外帳與損益 cairn 的財務分兩層:每梯次只算到「毛利」(收入 − 真實成本),公司層才把手續費、稅、辦公室費用扣一次算出「淨利」。同一筆成本同時帶「內帳真實額」與「外帳可申報額」兩個金額,據此派生真實利潤與報稅邊界。 理解這層分工,你才能正確解讀損益相關端點——每梯次的 `grossProfit`、公司的淨利,都是成本明細與費用台帳算出來的派生值。 ## 這是進階會計模組 本篇的所有端點都屬**進階會計模組(accounting)**。租戶未啟用此模組時,內外帳、損益、結算相關端點會回 `403 module_disabled`——即使你的角色有對應權限。這與「權限不足」的 `403 forbidden` 是兩回事:`module_disabled` 要租戶在設定啟用模組,`forbidden` 要換有權限的 token。另外,內外帳與損益屬**敏感全局財務**,只有 `admin` / `accountant` 角色可讀(線控、嚮導端看不到淨利)。 ## 內帳 vs 外帳:一筆成本兩個金額 每一筆成本同時承載兩個台幣金額,不分裂成兩套帳: | 金額 | 是什麼 | 用途 | | --- | --- | --- | | 內帳真實額 | 實際花掉的錢(含拿不到發票的支出) | 算**真實毛利 / 淨利** | | 外帳可申報額 | 取得合法憑證、可報稅的部分(無憑證為 0) | 對外**申報 / 報稅** | 實務上常有拿不到發票的支出(停車費、補貼、自然人嚮導 / 司機勞務),這些是真實成本必須據實記錄,但對外申報只能列有合法憑證的部分——兩個金額的差就是「無憑證真實成本」。 可申報額**可上修也可下修**:通常等於真實額,但有時拿到高於實際支出的發票(可上修),或一筆完全無發票(為 0)。所以「外帳 > 內帳」不是錯誤,是合法的匯率 / 憑證落差。 無發票的支出會掛一張**支出證明單**作為內帳的合法內部憑證(記受款對象、原因、摘要),可列印、走經手與核准雙職分離。 ## 成本要先覆核才入帳 不是所有成本都「送出即生效」。嚮導 / 領隊在現場填報的成本,先進「待覆核」狀態,**不計入淨利、不可申報、不可轉請款**,要等會計覆核通過才入帳;後台或團控直接登記的成本則直接視為已覆核。 (圖:流程圖,略) 對應 API:[`GET /internal-external-ledger/declarations/pending`](/api#internal-external-ledger) 取待覆核佇列,[`POST /internal-external-ledger/costs/{costId}/approve-declaration`](/api#internal-external-ledger) 覆核單筆(覆核人必須 ≠ 經手人)。覆核後的嚮導代墊成本可一鍵轉成請款(payable)退還嚮導。 ## 外幣成本:雙率派生台幣 海外團的真實成本常以外幣發生(地接日幣、印尼盾)。成本可原生記**外幣金額 + 匯率**,兩個台幣金額由同一筆外幣各自派生: (圖:流程圖,略) - **內帳匯率**取真實取得成本:外幣帳戶的加權平均成本、當日台銀牌告、或手填成交率(三選一,不寫死團型)。 - **外帳匯率**一律台銀牌告。 - 兩個匯率都在登記當下**快照**進成本列,事後牌告變動不回改既有成本。 多幣別**只進成本側**。營收一律台幣——訂單、收款、退款都不收外幣,客人付的永遠是 TWD。 查匯率走 [`GET /exchange-rates`](/api#exchange-rates)(台銀牌告表,含現金 / 即期 × 買入 / 賣出四報價)與 [`GET /exchange-rates/resolve`](/api#exchange-rates)(解析某幣別某日的「1 外幣 = ? 台幣」)。`resolve` 的 fail-safe 約定:查無匯率回 `200` 帶 `rate: null`(不是 `404`),呼叫端請對 `null` 分支,不要靜默用 0 或 1。 ## 每梯次只算到毛利 每一梯的權威數字是**毛利 = 本團收入 − 真實成本**,不扣手續費 / 稅: ```txt 本團收入 = 該梯次「成交應收」(不是當期收到的現金) 真實成本 = 已覆核成本的內帳真實額加總(含房費) 毛利 = 本團收入 − 真實成本 ``` **收入認列口徑要記牢**:本團收入是該梯次的**成交應收**,不是當期收到的現金(訂金 / 尾款常跨月分次收)。期間歸屬一律看**出團月**——某梯 5 月出團、3 月收訂金、6 月收尾款,全額營收與毛利都落在 5 月。 讀每梯次內外帳走 [`GET /internal-external-ledger/departures`](/api#internal-external-ledger)(列表)與 [`.../departures/{departureId}`](/api#internal-external-ledger)(單梯派生:`revenue` / `realCost` / `declarable` / `grossProfit` / `estimatedNetProfit`)。`estimatedNetProfit` 只是給現場快速感覺的**參考預估**(毛利減預估費率),不是公司財務真相。 掛帳台帳的跨梯彙整走 [`GET /accrual-ledger`](/api#accrual-ledger)(單期間四區塊)與 [`.../departure-summary`](/api#accrual-ledger)(梯次彙總:收入 / 真實成本 / 毛利)。 ### 取消梯次的成本認列 **取消的梯次不入毛利**——它沒有出團月,0 收入配上已發生的成本會讓各區毛利失真,所以內外帳列表與損益表的各區毛利一律排除取消梯次(掛帳台帳除外:欠廠商的錢照樣要追,台帳彙總仍含取消梯次)。退訂沖銷後**殘餘不可退**的成本(沒收的訂金、床位費等,含分房房費)由承辦人**人工確認**,一鍵結轉成一筆「特殊損益」費用,落在公司月損益表的特殊損益列——不會自動結轉,因為退訂協商還在進行時提早認列會把還拿得回來的錢記成損失。 | 端點 | 用途 | | --- | --- | | [`GET /internal-external-ledger/cancelled-departures`](/api#internal-external-ledger) | 取消梯次待結轉清單(殘餘成本 / 是否已結轉) | | [`POST .../cancelled-departures/convert`](/api#internal-external-ledger) | 結轉殘餘成本為特殊損益(一梯次限一筆) | | [`POST .../cancelled-departures/revert`](/api#internal-external-ledger) | 撤銷結轉(可重新結轉) | 結轉後整個梯次進入凍結:不能改回銷售中、不能結團、成本與房費也不能再登記或修改——否則殘餘會同時(或都不)出現在毛利與特殊損益兩邊。結轉出來的特殊損益列同樣不能用一般費用編輯改動。要調整任何一項,一律走「撤銷結轉 → 修改 → 重新結轉」。 ## 公司月損益 公司真實淨利在公司層算,把手續費 / 稅 / 辦公室費用用**實際值**扣一次(避免與每梯次重複計): (圖:流程圖,略) 公司營業費用是與梯次無關的支出,逐筆登錄成台帳,按月歸屬;費用科目由租戶自訂。對應 API: | 端點 | 用途 | | --- | --- | | [`GET /pnl/statement`](/api#pnl) | 公司月損益表(各區毛利 → 公司淨利) | | [`GET /pnl/expenses`](/api#pnl) | 公司營業費用台帳(可依月 / 大類 / 科目篩選) | | [`GET /pnl/expense-categories`](/api#pnl) | 費用科目目錄(租戶自訂) | | `POST /pnl/expenses` · `PATCH /pnl/expenses/{id}` | 登錄 / 更正費用 | 費用大類(`kind`)分 `opex`(一般營業費用)、`tax`(稅)、`platform_fee`(平台手續費實際月結)、`special`(一次性特殊損益)。損益表按大類 roll-up 成幾格,點開再 drill-down 到逐筆。「人事」這格只裝行政 / 內勤固定薪——領隊費屬每梯次直接成本,已算進該梯毛利,**不要重算進公司人事**。 ### 月固定費用自動生成草稿 每月都會重複的公司費用(辦公室租金、行政固定薪、電信、網路…)不必每月手動重登。租戶可在設定建一份**月固定費用範本**(預設科目 / 金額 / 出帳帳戶),之後系統每月**月底自動為每個範本生成下月的費用草稿**——月中就能先過目、確認下個月的固定支出。 (圖:流程圖,略) 這對 API 消費者的意義,在 [`GET /pnl/expenses`](/api#pnl) 的每一列回傳欄位上看得到: | 欄位 | 值 | 意義 | | --- | --- | --- | | `status` | `"draft"` / `"confirmed"` | 草稿 = 待確認、**不計入損益**;已確認 = 入帳 | | `origin` | `"manual"` / `"recurring"` / `"departure_conversion"` | `recurring` = 月固定範本自動生成 | | `sourceTemplateId` | 範本 ID / `null` | `origin` 為 `recurring` 時指回生成它的範本 | **損益表只算已確認的費用**。[`GET /pnl/statement`](/api#pnl) 一律排除 `status: "draft"` 的列——自動生成的草稿在有人確認前不會進帳,這是「避免無人確認的鬼列」的安全前提。所以你在 `pnl/expenses` 台帳看到某筆 `recurring` 草稿,它**還沒**反映在當月損益表上,要等確認。確認是後台的人工步驟(確認時可順手改金額)。 經 [`POST /pnl/expenses`](/api#pnl) 手動登錄的費用預設**直接是已確認**(`status: "confirmed"`),立即入帳——只有月固定範本自動生成的那批才會落在待確認草稿。自動生成**同範本同月至多一列**(冪等),所以月底排程重跑、或補算某個歷史月份,都不會產生重複列。 ## 下一步 - [成本與範本](/guides/costs):成本怎麼登記、行程綁範本自動帶成本行。 - [收款帳戶與結算](/guides/accounts-settlement):多帳戶分流、跨帳戶補款、月結互抵與換匯。 - [付款流程](/guides/payments):客人收款、退款、出款(payables)的分界。 - [internal-external-ledger API](/api#internal-external-ledger) · [pnl API](/api#pnl)。 --- export const metadata = { title: '收款帳戶與結算 — cairn 指南', description: '多收款帳戶分流、逐帳戶對帳、帳戶間移轉(補款/代墊/回填/換匯/沖正)、月結互抵掛帳與退款執行。', }; # 收款帳戶與結算 一筆錢「實際落在哪個帳戶」往往不是它「最終該歸屬的帳戶」。cairn 由每筆收款 + 費用類型路由**自動推導**各帳戶間的應補淨額,你登錄實際的補款/代墊/回填,並把跨月未結的欠款追到結清。 這層屬**進階會計模組**:未開模組的租戶只用核心對帳視圖,月結互抵與帳戶間移轉登錄需開模組。相關端點都掛在 [reconciliation](/api#reconciliation) 與 [settings](/api#settings) 之下。 ## 收款帳戶與分流 每個租戶自己維護一份**收款帳戶目錄**(銀行 / 現金 / 金流商沉澱帳),用 [`GET /reconciliation/receiving-accounts`](/api#reconciliation) 取得。帳戶沒有內建枚舉——你想用幾個、怎麼組合都行。 每個帳戶帶一組**能力旗標**,決定它能做什麼(不是寫死帳戶名): | 欄位 | 意義 | | --- | --- | | `canReceive` | 可收款(幾乎都是 true) | | `canRemit` | 可對外匯款(退款匯出 / 補款匯出) | | `canChargeback` | 可刷退(退回原信用卡,通常只有金流商帳戶) | | `isDeprecated` | 廢帳戶:仍可零星收款,但其收款需由他帳戶代墊歸集 | | `isSettlementMaster` | 主結算帳戶:所有資金最終向它彙整,每租戶恰一個 | | `currency` | 帳戶幣別(ISO 4217,預設 `TWD`;外幣帳戶=外幣池) | 收款落帳時,金流商收款自動掛對應的沉澱帳戶;手動入帳(ATM / 臨櫃 / 現金)則由經手人從**啟用帳戶**中選一個——帳戶與產品線無關,不依產品線篩選。 帳戶目錄與能力是**租戶配置**,經 [settings](/api#settings) 維護: - [`GET /settings/receiving-accounts`](/api#settings) 列出、`POST` 新增、`PATCH /settings/receiving-accounts/{id}` 改名 / 改能力 / 設幣別 / 停用。 - [`PUT /settings/fee-category-routes`](/api#settings) 設定「費用類型 → 最終該歸哪個帳戶」的路由(如團費歸主結算帳戶、車費 / 住宿 / 裝備歸另一帳戶)。 帳戶**停用不刪**——被現金列引用的帳戶會被鎖住、不能硬刪,避免抹掉歷史歸戶。能力旗標與費用類型路由都需要 `receiving_account.manage` 權限。 ## 逐帳戶對帳 對帳是「錢在哪」的視圖。用 [`GET /reconciliation/accounts`](/api#reconciliation)(可帶 `dateFrom` / `dateTo`)取每個帳戶在期間內的進出與淨額: (圖:流程圖,略) | 欄位 | 意義 | | --- | --- | | `inTwd` | 期間內收進的現金 | | `outTwd` | 退款 / 金流出的現金 | | `payableOutTwd` | 供應商 / 嚮導出款(與訂單收款分源呈現) | | `netTwd` | `inTwd − outTwd − payableOutTwd` | | `txCount` | 該帳戶的交易筆數 | 對帳是**純讀派生**——把現金紀錄與供應商出款依帳戶彙整,給你和銀行 / 金流商實際入帳核對。差異由你人工確認,系統不假設銀行端為真值。歸不到帳戶的收款會落「未歸戶桶」(`accountId` 為 `null`),提示你補歸戶,而不是靜默吞掉。 ## 帳戶間移轉 帳戶之間的一切資金移動都記在同一張流水上,用 `kind` 區分。讀取走 [`GET /reconciliation/transfers`](/api#reconciliation)(可依 `kind` / `account` / `settlementPeriod` / `dateFrom` / `dateTo` 篩),登錄走 [`POST /reconciliation/transfers`](/api#reconciliation): | `kind` | 是什麼 | | --- | --- | | `replenishment` | 補款歸集(把代收的款補給最終帳戶) | | `advance` | 廢帳戶代墊(代墊帳戶先補給最終帳戶) | | `backfill` | 回填沖銷代墊 | | `refund_payout` | 退款匯出(從可匯款帳戶出) | | `fx_conversion` | 換匯(換出台幣 + 外幣實拿) | | `opening` | 外幣期初餘額 | | `reversal` | 沖正(反向對沖一筆原移轉) | 一筆移轉帶 `amountTwd`(實際移轉的台幣淨額)與 `feeTwd`(**這筆轉帳本身**的手續費,如匯款費)。`amountTwd` 已是淨額——金流收款手續費已在成本帳認列,這裡不重記。換匯則另帶 `currency` / `foreignAmount` / `grossRate`。 移轉登錄後**不可修改或刪除**。記錯就開一筆 `kind` 為 `reversal` 的沖正(反向 `from` / `to` + 對沖金額)指回原列,保留可稽核的鏈,而不是改寫歷史。 登錄移轉(`POST`)需要 `account_transfer.manage` 權限,**且需開啟進階會計模組**。換匯不是另一種資源——它只是 `kind` 為 `fx_conversion` 的一筆移轉,共用同一套權限與列表。 ## 月結互抵 你不用手算各帳戶該補多少。選一個月份,cairn 由當月收款 + 費用類型路由派生**互抵矩陣**: (圖:流程圖,略) - [`GET /reconciliation/settlement-overview?period=YYYY-MM`](/api#reconciliation) 取當期矩陣與掛帳(純讀,不寫入)。 - [`POST /reconciliation/settlement/compute`](/api#reconciliation) 跑派生並 `upsert` 掛帳,回傳每筆的 `action`(`created` / `updated` / `floored_to_settled`)。 重跑同一期可覆蓋派生(你修正了歸戶或路由之後),但**護欄不會把目標降到已結金額以下**——若派生目標低於已沖減額,差額會以 `floored_to_settled` 保留為調整掛帳,不靜默改寫已結歷史。 派生出的帳戶間欠款是一級實體,用 [`GET /reconciliation/obligations`](/api#reconciliation)(可帶 `period` / `reason` / `status`)追蹤到結清: | 欄位 | 意義 | | --- | --- | | `debtorAccountId` / `creditorAccountId` | 欠錢方 / 收錢方(方向只由這兩欄表達) | | `amountTwd` | 應補目標總額(恆正) | | `settledAmountTwd` | 已透過移轉沖減的累計 | | `outstandingTwd` | 尚欠 = `amountTwd − settledAmountTwd` | | `reason` | `period_settlement`(月結淨額)/ `advance_repayment`(代墊待回填)/ `refund_advance`(代墊退費待回補) | | `status` | `outstanding` / `partially_settled` / `settled` | 你登錄的補款 / 代墊 / 回填移轉(上一節)會沖減對應掛帳,`outstandingTwd` 隨之下降,直到結清。 ## 銀行對帳單匯入 逐帳戶對帳(上一節)是系統內視角;銀行對帳單匯入則是把**銀行實際流水**拿來跟系統內的現金紀錄逐行核對,取代人工拿對帳單逐筆對照 Excel 的作業。 流程分兩步: 1. **上傳範本**:下載固定欄位範本(日期、存入 / 支出金額、摘要、備註),從網銀匯出資料整理進去後上傳。系統不解析任何銀行原生格式——每個租戶用自己的流程把對帳單轉成這份範本,不綁特定銀行。 2. **預覽再確認**:上傳後先進入 dry-run 預覽(期間重疊會被擋下、逐行解析錯誤會列出、可先看到會自動配對哪些行),確認無誤才送出正式匯入。 匯入後,系統把每一行對帳單金額拿去跟三種候選逐一比對:一般收款 / 退款、已付供應商出款、帳戶間移轉。同帳戶、同金額、同方向、日期相差在兩個工作天以內、且雙方都還沒被配過的,會自動勾上;其餘留給人工處理——逐行可以**手動配對**(金額仍須相符)、**解除配對**、或**略過**(例如銀行利息、手續費這類系統不記帳的行,需填略過原因)。 帳戶間移轉是雙邊事實:一筆補款會同時出現在出帳帳戶的支出行與入帳帳戶的存入行,兩邊各自核銷是正確的——出帳側核對的金額含這筆轉帳本身的手續費(即銀行實際扣款的金額),入帳側核對對方收到的淨額。 對帳單一經匯入即為歷史紀錄;已有人工配對或略過痕跡的批次不能整批刪除,只有整批仍是自動配對/未配對狀態時才能刪除重傳。此功能目前只在後台操作,尚未開放對應的 API / CLI 命令。 ## 退款執行 退多少、扣多少手續費由[訂單退款流程](/guides/orders-money-model)決定;這裡管的是退款的**執行帳戶與對帳**。用 [`GET /reconciliation/refund-executions`](/api#reconciliation)(可帶 `status=approved|paid`)取待執行與已執行的退款: | 欄位 | 意義 | | --- | --- | | `executionMethod` | `chargeback`(刷退)或 `remit`(匯出) | | `originAccountId` | 原收款落地帳戶 | | `adminFeeTwd` / `remitFeeTwd` | 行政手續費 / 匯款手續費 | | `payeeAccountName` / `payeeBankCode` / `payeeAccountNumber` | 匯出退款的收款人三欄 | **能力檢核**:`chargeback` 只能走 `canChargeback` 的帳戶、`remit` 只能走 `canRemit` 的帳戶,違反會被擋下。`remit` 退款會產生一筆 `kind` 為 `refund_payout` 的帳戶間移轉並併入月結;`chargeback` 由金流商處理、不產生帳戶間移轉,只記對帳。 標記 `chargeback` 退款已付時,若原始收款是信用卡,系統會**自動向金流商發起原卡退刷**(把款退回客人原本刷卡的那張卡);退刷失敗、或原交易不是可退刷的信用卡(例如 ATM/現金)時,會**自動改為人工匯款**並留下稽核紀錄——退款一定會完成,不會卡住。 當執行帳戶不是退款該由的最終帳戶時(例如主結算帳戶先代墊另一帳戶該吐的退款),系統開一條 `reason` 為 `refund_advance` 的掛帳,按月彙整、追到回補。 ## 下一步 - [內外帳與損益](/guides/accounting):成本可不可申報、淨利試算——與「錢在哪」正交的另一軸。 - [請款與出款](/guides/payouts):對供應商 / 嚮導的出款核簽,與帳戶間移轉的差別。 - [訂單與金流模型](/guides/orders-money-model):應收、現金、退款生命週期的全貌。 - [reconciliation API](/api#reconciliation) · [settings API](/api#settings)。 --- export const metadata = { title: '成本與範本 — cairn 指南', description: '梯次成本登記、可重用成本範本、人頭公式動態重算與逐行 override——從 API 的角度理解每一梯的成本怎麼來。', }; # 成本與範本 每一梯次的成本是一行行登記出來的:你可以逐筆手填,也可以讓行程綁定一份「成本範本」,開梯時自動帶入成本行;其中數量還能依當下人數動態算出來。 理解這點,你才知道一筆梯次成本是怎麼產生的——是手填的 ad-hoc 列,還是範本套出來、會隨人數浮動的動態列。相關 API 是 [cost-templates](/api#cost-templates) 與 [departures](/api#departures) 的成本端點。 ## 梯次成本登記 成本掛在**梯次(departure)**上,不在行程、也不在訂單上。你向某梯次登記一筆成本行(品項 × 金額),它就計入該梯的成本: | 操作 | 端點 | | --- | --- | | 登記一筆成本 | [`POST /departures/{depId}/costs`](/api#departures) | | 修改 / 刪除一筆 | `.../costs/{costId}`(PATCH / DELETE) | 每一筆成本行都帶**品項**(`category`)、**供應商**(`vendorName`)、**金額**(`amountTwd`)等欄位。這是「現場作業」:線控、會計在團控頁逐筆登記實際支出,與下面的「範本設定」是兩件事、兩種權限。 成本登記是「成本側」——你**付給供應商 / 嚮導**的錢。別跟「應收 / 收款」(客人付你的錢)混在一起;那一套是訂單金流,見 [付款流程](/guides/payments)。 ## 成本範本(綁行程) 系列團(常態固定會開的團)的成本結構幾乎一樣:車資、餐費、保險、嚮導費、入山證……。與其每開一梯重打一次,你建立一份**成本範本**,把這些成本行的定義存起來,再綁到行程上: (圖:流程圖,略) | 操作 | 端點 | | --- | --- | | 列出範本 / 取綁定清單(`?active=1`) | [`GET /cost-templates`](/api#cost-templates) | | 建立範本 | `POST /cost-templates` | | 取範本完整內容(含成本行 `lines[]`) | `GET /cost-templates/{id}` | | 改範本 head | `PATCH /cost-templates/{id}` | | 加 / 改 / 刪一條成本行 | `.../{id}/lines`、`.../lines/{lineId}` | 範本的每一條成本行(`lines[]`)定義 **品項 × 單價 × 數量規則**:`category`、`vendorName`、`unitPriceTwd`,加上決定「要帶幾份」的 `quantityMode`。範本只是「設定」——管理範本是後台 setup 層的權限(`cost_template.manage`),與逐梯成本登記分開。 範本綁定是**一對一**:一個行程綁至多一份範本。範本停用(`status` 設 `archived`)後不能再綁新行程,但已套到既有梯次的成本行不受影響。 ## 人頭公式與動態重算 範本成本行的「數量」有兩種: - `quantityMode: "fixed"`——固定份數(`quantityFixed`),不隨人數變。 - `quantityMode: "headcount"`——**人頭公式**:把選定的人頭相加,再選填組距: ```txt 數量 = ceil( Σ(選定人頭) / 組距 ) ``` 選定人頭來自 `headcountComponents`,可多選 `passenger`(旅客)、`leader`(領隊)、`assistant`(協作)、`driver`(司機);`groupDivisor` 不填就是直接用人頭總和,填 N 就是「每 N 人一組」(無條件進位,不足一組仍算一組)。 | 想要的數量 | `headcountComponents` | `groupDivisor` | | --- | --- | --- | | 旅客數 | `["passenger"]` | — | | 旅客 + 領隊數 | `["passenger","leader"]` | — | | 每 8 客配 1 領隊 `ceil(旅客/8)` | `["passenger"]` | 8 | | `ceil((旅客+司機)/8)` 台車 | `["passenger","driver"]` | 8 | 套用後,`headcount` 列的金額會**隨人數即時重算**:報名成立 / 取消、領隊或協作指派異動、派車異動,平台都會在同一個動作裡重算這些列的 `quantity` 與 `amountTwd`。你不需要自己呼叫重算——它跟著人頭變動自動發生。 (圖:流程圖,略) 把範本套到既有梯次(補套新加的成本行、或為尚未套用的梯次套用)用 [`POST /departures/{depId}/template-costs/apply`](/api#departures),回傳 `inserted` 告訴你補了幾行。**套用是冪等的**——已存在的範本來源行不會重複插入。 ## 外幣成本範本行 海外團的成本常以外幣發生(地接日幣、印尼盾)。一條範本成本行除了台幣,也可以直接定義成**外幣行**——不再手動換算,套用到梯次時由平台解析當下匯率、快照台幣金額。 每條範本行用 `currency` 決定幣別(ISO 4217,預設 `"TWD"`): - **台幣行**(`currency: "TWD"`):填 `unitPriceTwd`,外幣欄位留空。就是前面講的一般成本行。 - **外幣行**(`currency` 非 TWD,如 `"JPY"`):改填 `unitPriceForeign`(外幣每單位單價,最小單位整數),並用 `internalRateSource` 指定**內帳匯率**怎麼來: | `internalRateSource` | 內帳匯率取自 | 額外欄位 | | --- | --- | --- | | `"fx_account"` | 指定外幣帳戶的加權平均成本 | `fxAccountId`(該外幣帳戶,幣別須與成本幣別相符) | | `"published"` | 當日台銀牌告 | — | 範本層的外幣行**只有這兩種內帳匯率來源**,沒有「手填成交率」——手填實際成交率本質是逐筆交易當下的事,不適合存進可重用範本。真的要用手填率,套用後在梯次那一列逐筆改即可。外帳(可申報)匯率一律台銀牌告,不用選。 ### 套用時解析、快照雙率 外幣範本行**套用到梯次的當下**(台北當天)才解析匯率,並把兩個匯率一起快照進該梯成本列: ```txt 外帳可申報額 = round( 外幣金額 × 台銀牌告即期賣出率 ) 內帳真實額 = round( 外幣金額 × internalRateSource 解出的內帳率 ) ``` 快照之後,這一列的匯率就**固定**了:日後牌告變動、或帳戶加權平均成本浮動,都不回改已套用的成本列。連 `headcount` 外幣列因人數重算金額時,也是沿用快照匯率重乘新數量,**匯率不漂移**——只有數量在變。 外幣行**套用時 fail-closed**:解析不出匯率(牌告當天還沒同步、或指定外幣帳戶還沒有期初餘額)時,平台會**跳過**那一條外幣行、不會默默用 0 或 1 帶過。套用回傳的 `skippedForeign` 會告訴你跳過幾行;其餘台幣行照常套用、不受影響。補齊匯率後再套用一次(冪等),被跳過的外幣行會自動補回。 多幣別只在**成本側**。營收一律台幣——這條界線與 [內外帳與損益](/guides/accounting) 講的一致,外幣雙率的完整語意(內外帳兩個匯率、查匯率端點)也在那篇。 ## 逐行 override 自動算出來的金額不一定剛好。你可以**手改某一行**,覆蓋掉公式結果: | 操作 | 端點 | 效果 | | --- | --- | --- | | 手改某行金額 | [`POST /departures/{depId}/template-costs/{id}/override`](/api#departures)(帶 `amountTwd`) | 該行 `isManualOverride` 變 `true`,**停止自動重算** | | 恢復動態 | `.../template-costs/{id}/clear-override` | 清除手改,下次人數變動重新依公式算 | 要看一梯目前所有範本來源的成本行(含是否手改、是否已轉請款),用 [`GET /departures/{depId}/template-costs`](/api#departures)。每一行回傳 `quantity` / `unitPriceTwd` / `amountTwd`、`isManualOverride`、以及 `payableId`。 一旦 `isManualOverride` 為 `true`,這行就**凍結**——人數再怎麼變都不覆蓋你手改的值,直到你明確 clear-override。另外,已轉請款(`payableId` 非 `null`)的成本行也會被鎖定、不再重算。 ## 與內外帳 / 損益的銜接 範本套出來的成本行**就是一般梯次成本行**,沒有特殊金流語意。它一樣帶內外帳維度:`isDeclarable`(是否可申報)與 `invoiceAmountTwd`(可申報額)。`headcount` 列重算金額時,可申報額預設跟著真實金額走;手改或設為不可申報則各自凍結。 也就是說,不論成本是手填還是範本帶出來的,往下游的內外帳、淨利、損益計算看到的都是同一種成本行——範本只負責產生初值,不改變它之後怎麼被認列。 ## 下一步 - [內外帳與損益](/guides/accounting):成本如何分內外帳、算可申報額與淨利。 - [出團準備與線控](/guides/departure-prep):梯次的排班、派車與現場作業(人頭的來源)。 - [cost-templates API](/api#cost-templates) · [departures API](/api#departures)。 --- export const metadata = { title: '請款與出款 — cairn 指南', description: '請款單的建立與核簽生命週期、出款對象(供應商 / 嚮導 / 佣金)、與客戶退款的區別、供應商資料與月結出款批次。', }; # 請款與出款 當旅行社要付錢給**第三方**——供應商、嚮導、業務佣金——你開一張**請款單**,經核簽後付款。這跟「退錢給客人」是兩回事:退客人錢動的是訂單金流,請款出款動的是對外應付。 本篇說明請款單的核簽生命週期、出款對象、供應商資料,以及把當月多筆請款月結成一批匯出的出款批次。 ## 出款 ≠ 退款 兩者方向都是「錢出去」,但資料源與流程完全不同,別混用: | 概念 | 付給誰 | 走哪條流程 | API | | --- | --- | --- | --- | | 請款 / 出款 | 供應商 / 嚮導 / 佣金等**第三方** | 請款單核簽 | [payment-requests](/api#payment-requests) · [payouts](/api#payouts) | | 客戶退款 | **客人本人** | 訂單退款工作流 | [`/orders/{id}/refunds`](/api#orders) | 出款核簽**不是**客戶退款的資料源。退客人錢一律走訂單的退款流程(反映在訂單的 `refundState`);對供應商 / 嚮導 / 佣金付款一律走請款單。即使金額相同,也不要拿請款單代替退款,反之亦然。 ## 請款單的生命週期 一張請款單(payment request)代表一筆要付給第三方的款,從草稿到付清會經過幾個狀態。**建單、核准、付款分屬不同人**:建單人不能核准自己的單,會計只對已核准的單付款。 (圖:流程圖,略) | 狀態 | 意義 | 誰推進 | | --- | --- | --- | | `draft` | 建單人編輯中,尚未送審 | 建單人 | | `submitted` | 已送出,等候核准 | 建單人 `submit` | | `approved` | 已核准,可付款 | 核准人 `approve`(**非建單人**) | | `paid` | 已依紙本付款並回填匯款參照,**終態** | 會計 `mark-paid` | | `rejected` | 送審後被退回 | 核准人 `reject`(須附原因) | | `cancelled` | 非 `paid` 的單被作廢 | 建單人 / 主管 `cancel` | 對應動作端點:[`POST /payment-requests/{id}/submit`](/api#payment-requests)、`/approve`、`/reject`、`/mark-paid`、`/cancel`。每次狀態轉換都會留下稽核紀錄(請款單詳情回傳的 `audit[]`)。 ## 建立請款單 以 [`POST /payment-requests`](/api#payment-requests) 開單,帶上出款對象與明細: - `payeeType` / `payeeName`:出款對象的類型與名稱(見下節)。 - `payeeBankAccount` / `payeeBankName` / `payeeBankBranch` / `payeeTaxId`:收款銀行與稅籍資料(選填)。 - `departureId`:若這筆款歸屬某個梯次,帶上以利歸帳(選填)。 - `description`:請款事由(必填)。 - `items[]`:請款明細,每筆有 `category` / `description` / `quantity` / `unitPriceTwd`;金額逐筆加總成請款總額。 - `taxAmountTwd`:稅額;扣稅後得淨額。 - `submitImmediately`:建單即送審(等同建完直接 `submit`)。 回傳的請款單上,`amountTwd` 是明細總額、`taxAmountTwd` 是稅額、`netAmountTwd` 是實付淨額。金額一律**整數台幣**。 請款明細可以手 key,也可以從梯次成本帶入。用 [`GET /payment-requests/supplier-suggestions?departureId=...`](/api#payment-requests) 取某梯次尚未轉請款的成本,做為 `items` 的建議草稿,再開單。 ## 核簽與付款 請款單**核准**後,會計依紙本流程實際匯款,再回系統把單標記為已付: - [`POST /payment-requests/{id}/mark-paid`](/api#payment-requests) 必須帶 `bankReference`(匯款參照號),可選 `paperSignedBy`(紙本簽核人)與 `receivingAccountId`(從哪個帳戶匯出)。標記後即進入終態 `paid`。 - 請款單**可列印**用於紙本簽核與存查。`mark-printed` / `mark-signed` 記錄列印與用印動作(請款單詳情回傳 `paperPrintedAt` / `paperSignedBy`)。 系統記錄的是核簽與匯款的**事實**,不代表系統替你完成轉帳。`mark-paid` 是「我已在網銀付了這筆、參照號是這個」的回填,實際匯款仍由會計在銀行端執行。 權限以 `payable.*` 控管,角色分工: | 角色 | 讀 | 建單 / 送審 | 核准 / 退回 | 標記付款 | 列印 / 取消 | | --- | :--: | :--: | :--: | :--: | :--: | | `admin` | ✅ | ✅ | ✅ | ✅ | ✅ | | `op`(線控) | ✅ | ✅ | ❌ | ❌ | ✅ | | `sales`(業務) | ✅ | ✅ | ❌ | ❌ | ✅ | | `accountant`(會計) | ✅ | ❌ | ✅ | ✅ | ✅ | 讀取走 [`GET /payment-requests`](/api#payment-requests)(可依 `status` / `payeeType` / `departureId` 篩選)與 [`GET /payment-requests/{id}`](/api#payment-requests)(含明細、核簽時間軸、稽核)。彙總計數看 [`GET /payment-requests/stats`](/api#payment-requests)。 ## 出款對象 請款單的 `payeeType` 標明你在付給誰: | `payeeType` | 用途 | | --- | --- | | `supplier` | 旅宿、交通、餐飲、保險、地接等供應商 | | `guide` | 嚮導 / 領隊費 | | `staff_commission` | 業務 / 內部佣金 | | `other` | 其他需走核簽的出款 | 收款對象的銀行資料隨對象走:供應商建在供應商目錄、嚮導與員工掛在各自紀錄上。同一筆請款的銀行欄位也可在開單時直接帶入。 ## 供應商資料 供應商會**跨月重複出現**,所以建一次檔、之後直接引用,收款銀行資料隨供應商走,免每月重 key: - [`GET /suppliers`](/api#suppliers) 列出供應商(可依 `status` / `q` 搜尋),[`POST /suppliers`](/api#suppliers) 建檔,[`PATCH /suppliers/{id}`](/api#suppliers) 維護。 - 每筆供應商有 `name`、`categoryHint`(慣用類別提示,自由文字)、收款銀行三欄 `payeeAccountName` / `payeeBankCode` / `payeeAccountNumber`,以及 `status`(停用以 `inactive`,不刪除)。 - 供應商目錄受 `accounting` 模組與 `supplier.*` 權限控管。**全部 per-tenant 自填,不預載任何廠商或銀行**。 ## 出款批次(月結匯出) 同一個對象當月常有多筆已核准請款,實務上**月結一次匯出**。出款批次把這些彙整成一筆出納動作,產出可匯入網銀的轉帳檔: (圖:流程圖,略) - [`GET /payouts/aggregate?period=YYYY-MM&scope=...`](/api#payouts):把某月某範圍的對象,各自加總已核准請款成 `grossTwd`,並列出組成的請款明細(日期 / 團名 / 金額)供對帳。 - [`POST /payouts/batches`](/api#payouts):以彙整結果建批次,指定 `paidFromAccountId`(從哪個可匯款帳戶出,見 [`GET /payouts/remit-accounts`](/api#payouts))與各 `lines`。 - [`POST /payouts/lines/{lineId}/confirm-token`](/api#payouts):產對外確認連結,讓供應商免登入核對自己當月明細再確認。 - [`POST /payouts/batches/{id}/confirm`](/api#payouts):確認批次,**把納入的請款一併標記為已付**並回填匯款參照。 - [`GET /payouts/batches/{id}/transfer-file`](/api#payouts):下載標準格式的批次轉帳檔(收款人戶名 / 帳號 / 銀行代號 / 金額),匯入網銀執行轉帳。 出款批次是**出納**動作,不是算薪。扣款項(如自付保費、借支)是建批次時手 key 的通用欄位,系統不內建任何薪資級距或法規費率——這些一律 per-tenant 自填或由外部系統提供。 ## 下一步 - [付款流程](/guides/payments):跟客人**收款**的 redirect、webhook 與 ECPay 設定。 - [收款帳戶與結算](/guides/accounts-settlement):帳戶能力、可匯款帳戶與跨帳戶結算。 - [payouts API](/api#payouts) · [payment-requests API](/api#payment-requests)。 --- export const metadata = { title: '訂房產品線 — cairn 指南', description: '住宿物件、房型與每晚庫存、散客直訂與行程配套——訂房不是行程,但一樣接 orders 與金流模型,逐晚計價。', }; # 訂房產品線 訂房賣的是「某房型的某一晚」,不是梯次席次。它有自己的庫存脊椎(物件 → 房型 → 每晚房況),但成交後一樣是一張訂單,接同一套金流模型。 如果你已經熟悉 [訂單與金流模型](/guides/orders-money-model),訂房只多了「住宿物件與每晚庫存」這層;訂單與收款的部分完全沿用。所有端點見 [lodging API](/api#lodging)。 **訂房受「訂房模組」閘控。** 讀取端點(`GET`)只要 `lodging.read` 權限;建立 / 修改類端點(建物件、改房型、覆寫房況、建訂房單)額外要求租戶已啟用訂房模組,否則回 `403 module_disabled`。 ## 訂房不是行程 cairn 的行程產品走 `trips → departures`,訂單訂的是固定出發日的「席次」。訂房不套這個模型: | | 行程(tour) | 訂房(lodging) | | --- | --- | --- | | 庫存單位 | 梯次席次 | 某房型的「每一晚」 | | 客人選什麼 | 出發梯次 | 入住 / 退房日期區間 | | 資源主檔 | trips / departures | properties / room-types | | 訂單 `kind` | `tour` | `lodging` | 兩者最終都收斂成一張 [訂單](/guides/orders-money-model),差別只在「賣的是什麼」。 ## 住宿物件與房型 訂房的資源是兩層: (圖:流程圖,略) - **物件**(property)— 一處自家住宿。`kind`(如 `camp` 露營 / `hotel` 旅館)、`isOverseas`、地點與內容。用 [`GET /lodging/properties`](/api#lodging) 列出、`POST` 建立。 - **房型**(room-type)— 物件底下的可售單位。關鍵欄位 `inventoryUnit`(`room` 賣整間 / `bed` 賣床位)、`unitsTotal`(共幾間或幾床)、`capacityPerUnit`(每單元可住幾人)、`basePriceTwd`(基準晚價)。用 [`GET /lodging/properties/{id}/room-types`](/api#lodging) 取得。 物件與房型都帶相簿(images)與設施等內容欄位,供前台呈現。 ## 每晚庫存與房況 每個房型每一晚有一筆房況,記「容量 / 已訂 / 當晚價」,是訂房庫存的**單一真相**。平日 / 假日 / 旺季靠逐晚覆寫價,封房則把該晚關閉。 查可訂量與報價走房況查詢: ```txt GET /lodging/room-types/{id}/availability?checkIn=YYYY-MM-DD&checkOut=YYYY-MM-DD → available 區間可訂單元數(跨晚取最小;0 表不可訂) nights 晚數(不含退房日) totalPriceTwd 區間總價(逐晚價加總) perNight[] 每一晚的剩餘量與當晚價 ``` 日期區間是**左閉右開** `[checkIn, checkOut)`——退房日不算一晚。報價是逐晚累加,不是「晚數 × 單一價」,所以跨平假日 / 旺季的區間會自動反映各晚的覆寫價。 庫存扣減是 race-safe 的:訂一段區間時,**每一晚都要夠**才成立,任一晚不足則整筆失敗(不超賣、不部分成立)。 ## 散客直訂 散客直訂是訂房最直接的成交路徑:選物件 / 房型 / 日期,扣每晚庫存,開一張 `kind='lodging'` 的訂單。 (圖:流程圖,略) [`POST /lodging/bookings`](/api#lodging) 帶 `propertyId` / `roomTypeId` / `checkIn` / `checkOut` / `units`(間數或床數)/ `guestCount`(入住人數)/ `bookerEmail` / `paymentMethod`(`cash` 現金 / `atm` 虛擬帳號 / `manual` 其他人工)。庫存不足會被擋下、不建單。 其餘端點:[`GET /lodging/bookings`](/api#lodging) 列表(可帶 `status` / `q` 篩選)、`.../{orderId}` 取單筆詳情、`.../{orderId}/cancel` 取消。**取消會釋回每晚庫存,但不自動退款**——退款另走訂單的退款流程。 ## 行程配套 第二條通路是把住宿綁進滑雪等行程:行程設計時固定指定一間物件 + 房型 + 入住幾晚,出團時對那幾晚**整塊預留**團體床位。 - 客人報的是**行程訂單**(`kind='tour'`),住宿由團體 block 涵蓋,報名時不再各自扣房況。 - 配套的 block 與散客直訂**扣同一份每晚庫存**——所以同一物件不會被兩條通路超賣。 - 前台行程頁顯示所指定物件的細節(設施 / 房型 / 照片),純資訊、不在行程頁單獨下訂住宿。 散客直訂與行程配套是訂房的兩條通路,**共用同一份每晚房況**。這是訂房模型的核心不變量:任何一晚的「已訂」是兩條通路相加,CHECK 守住不超賣。 ## 訂房訂單如何接金流 訂房單成交後就是一張普通訂單,套用 [訂單與金流模型](/guides/orders-money-model) 的全部規則,差別只在「賣的是住宿、逐晚計價」: - 訂單外殼是 `kind='lodging'`,一樣帶三軸狀態(`bookingState` / `paymentState` / `refundState`)。 - 每一晚的應收是一筆獨立的應收明細;折扣、加價、退款都是明細,外殼不存彙總額。 - 收款走 [付款流程](/guides/payments) 同一套機制——現金即時入帳、ATM 取號後等繳費、線上刷卡走 redirect + webhook。 - 已付款後變更住宿內容,新增一筆應收明細產生補款,**不會**靜默改大原訂單。 (圖:流程圖,略) 所以你不需要為訂房學一套新的金流語意:拿到 `orderId` 後,讀訂單、收款、退款都跟 tour 訂單一致。 ## 下一步 - [訂單與金流模型](/guides/orders-money-model):應收、現金、三軸狀態與退款的全貌。 - [付款流程](/guides/payments):收款生命週期、redirect、webhook。 - [lodging API](/api#lodging) · [orders API](/api#orders)。 --- export const metadata = { title: '裝備租借 — cairn 指南', description: '裝備主檔與庫存、租借與歸還(押金、損壞費)、逾期、以及出團前的 per-departure 裝備需求盤點。', }; # 裝備租借 裝備租借讓你把登山裝備(睡袋、冰爪、頭燈⋯⋯)借給旅客:租金與押金自動掛上訂單金流,庫存即時扣減;同時讓線控盤點每個梯次要借多少、跨梯次庫存夠不夠。 本篇從串接的角度說明裝備主檔、租借與歸還的生命週期,以及出團前的 per-departure 裝備需求盤點。裝備租借屬**登山模組**——未開通該模組的租戶,相關端點不存在。 ## 裝備主檔 裝備主檔是可租借品項的清單([`GET /equipment/items`](/api#equipment))。每個品項帶價格與一組庫存數字: | 欄位(回傳) | 意義 | | --- | --- | | `name` / `category` / `spec` | 品名、分類、規格 | | `rentPriceTwd` | 每日租金(整數台幣) | | `depositTwd` | 每件押金(整數台幣) | | `totalCount` | 庫存總數 | | `outCount` | 在租中(借出未還) | | `maintenanceCount` | 維修中、不可租借 | | `availableCount` | **可租數量**(派生) | | `isActive` | 是否啟用 | `availableCount = totalCount − outCount − maintenanceCount`,是算出來的可租量。`outCount` 由租借單自動扣減——你**不直接設定**在租量;要調整庫存改 `totalCount` 或 `maintenanceCount`([`PATCH /equipment/items/{id}`](/api#equipment))。若把數字改到 `在租 + 維修 > 總數`,會被擋下回 `409`。 ## 租借與歸還 一張租借單(rental)綁定一張**訂單**:你借出哪些品項、各借幾件,租金與押金就掛上那張訂單的應收。生命週期由 `returnState`(在租 / 已還 / 逾期)與 `reconState`(驗收結算)兩個狀態表達: (圖:流程圖,略) **開租借單**([`POST /equipment/rentals`](/api#equipment))帶 `orderId` 與 `lines`(每筆 `equipmentItemId` + 數量),可選 `departureId`、`renterName`、`rentStartDate` / `rentEndDate`: - 借出會**即時扣庫存**。若可租量不足,整筆失敗回 `409`(不會部分借出);品項或訂單不存在回 `404`。 - 成交當下會把每筆的租金與押金**快照**進明細(`rentPriceTwdSnapshot` / `depositTwdSnapshot`)——事後改主檔價格**不回頭改**已開的租借單。 - 開單會在綁定訂單上產生**租金應收**與**押金應收**,並接上收款計畫。押金是代收暫收的一筆錢,跟租金一樣向客人收。 開租借單會動到綁定**訂單的金流**(新增應收、收款計畫與收款)。它不是孤立的庫存動作——租金與押金一律經訂單收款,請搭配[訂單與金流模型](/guides/orders-money-model)理解。 **歸還驗收**([`POST /equipment/rentals/{id}/return`](/api#equipment))帶 `returnedLines`(各明細歸還數量),可選 `damageCostTwd`: - 歸還會把對應數量**回補可租量**;歸還數超過在租量回 `409`。 - 無破損 → `reconState` 結為 `reconciled`,押金照退款流程退還客人。 - 有破損 → 登記 `damageCostTwd`、`reconState` 轉 `deposit_pending`(待結押金);損壞費從押金扣抵,未退的部分留作收入。 歸還**本身不退錢、不自動沖帳**。押金的退還(或損壞扣抵後的餘額)一律走訂單的[退款流程](/guides/payments),由會計事後發起——驗收只負責盤點與結算狀態。 讀取用 [`GET /equipment/rentals`](/api#equipment)(可依 `returnState` / `reconState` / `orderId` / `departureId` 篩選)與 [`GET /equipment/rentals/{id}`](/api#equipment)(含明細與派生的 `rentTotalTwd` / `depositTotalTwd`)。 ## 逾期 過了租期末日仍在租的單,會被標為逾期([`POST /equipment/rentals/{id}/mark-overdue`](/api#equipment),平台也有排程自動掃描): - 逾期**只改狀態**(`returnState = overdue`),**不動庫存**——東西還在客人手上、可租量不會回補。 - 逾期單照常可以走歸還驗收結案。 用 `returnState=overdue` 篩 [`GET /equipment/rentals`](/api#equipment) 即可列出所有逾期未還的單。 ## 出團裝備需求(per-departure) 租借單是「旅客真的借了」;裝備需求則是線控在**某個梯次**盤點「預計要借多少」——兩條線各自獨立、不重複扣款。需求掛在梯次上([`POST /api/v1/departures/{depId}/prep/equipment-needs`](/api#departures)): - 每個梯次、每個品項一筆需求量;對同品項再送一次是**覆寫**數量,不是累加。把數量設為 0 或刪除該筆即移除需求。 - 平台會把**同一段日期窗內**各梯次對同一品項的需求加總,對比主檔可租量,提示是否超收或庫存吃緊——讓你在某梯次調查需求時,看見同窗其他梯次也借同款導致的缺口。 裝備需求屬於出團前準備的一環,由線控在梯次層維護;它是盤點與預警工具,**不扣庫存、不產生應收**。真正動庫存與金流的是上面的租借單。 ## 下一步 - [出團準備與線控](/guides/departure-prep):per-departure 的裝備、分房、護照等盤點軸怎麼一起跑。 - [訂單與金流模型](/guides/orders-money-model):租金與押金如何掛上訂單的應收與收款。 - [付款流程](/guides/payments):押金退還與損壞扣抵走的退款流程。 - [equipment API](/api#equipment)。 --- export const metadata = { title: '嚮導與排班 — cairn 指南', description: '嚮導建檔與開通、梯次派班與確認,以及嚮導受限 login portal 能看什麼、能做什麼。', }; # 嚮導與排班 嚮導(領隊)在 cairn 有兩種身分:一個是**名錄上的實體**——你替他建檔、把他派到梯次帶團;另一個是**受限 login 角色**——開通帳號後,他只能透過自己的 portal 看到自己帶的梯次、填報代墊成本。本篇說明這兩者怎麼接起來。 整條流程是:先**建檔**,需要時**開通登入帳號**,把他**派到梯次**並**確認**,確認後該梯次才會進入他的 portal 可見範圍。 (圖:流程圖,略) ## 嚮導建檔 嚮導名錄走 [guides](/api#guides) 資源——和後台的嚮導管理頁完全等價,租戶範圍內共用(無 owner-scope)。 | 動作 | 端點 | 權限 | | --- | --- | --- | | 列出 / 搜尋(可分頁、依狀態篩選) | `GET /guides` | `guide.read` | | 依 slug 查單筆 | `GET /guides?slug=…` | `guide.read` | | 建檔 | `POST /guides` | `guide.manage` | | 明細 | `GET /guides/{id}` | `guide.read` | | 編輯 | `PATCH /guides/{id}` | `guide.manage` | | 封存(狀態轉 `inactive`) | `POST /guides/{id}/archive` | `guide.manage` | 建檔只需 `name`,其餘(`slug`、`bio`、`specialties`、`yearsExperience`、`contactPhone`、`contactEmail`、`status`…)皆選填。封存不刪資料,只把 `status` 轉成 `inactive`;要重啟用就 `PATCH` 回 `active`。 ```bash curl -X POST https://your-tenant.example.com/api/v1/guides \ -H "Authorization: Bearer $CAIRN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "王嚮導", "specialties": "高山,溯溪" }' ``` 建檔只是名錄——這時嚮導**還不能登入**。能不能登入、能看到什麼,由下面的「開通帳號」與「派班」決定。 ## 開通登入帳號(邀請) 要讓某個既有嚮導能登入自己的 portal,對他開通帳號: - `POST /guides/{id}/invite-account`(`guide.manage`),帶 `email` 與顯示 `name`。 - 回傳一個**一次性暫時密碼**(僅此一次回傳,請當下轉交給嚮導)。 - 這不是自助註冊——一律由後台 / 串接端主動邀請;一個嚮導至多綁一個登入帳號。 開通後嚮導以 `guide` 角色登入。`guide` 是**受限角色**:不在後台管理角色集合裡,無法碰使用者管理或其他後台端點,且**強制 2FA**(屬 staff-tier)。沒開通帳號的嚮導,就只是名錄上一筆可被派班的實體。 ## 梯次派班與確認 把嚮導排進梯次走 [guide-roster](/api#guide-roster) 資源(鏡像後台排班頁,租戶範圍內共用)。一筆指派有兩個維度: - **角色** `role`:`leader`(領隊)或 `assistant`(隨隊助理)。 - **狀態** `status`:`tentative`(預塞暫定)或 `confirmed`(正式確認)。 | 動作 | 端點 | 權限 | | --- | --- | --- | | 當月名冊 + 帶團負荷 / 可用度 | `GET /guide-roster/guides` | `guide_roster.read` | | 當月梯次 + 其指派(領隊優先) | `GET /guide-roster/departures` | `guide_roster.read` | | 指派 / 重派(upsert) | `POST /guide-roster/assignments` | `guide_roster.manage` | | 確認某筆指派 | `POST /guide-roster/assignments/{id}/confirm` | `guide_roster.manage` | | 移除某筆指派 | `DELETE /guide-roster/assignments/{id}` | `guide_roster.manage` | | 換領隊 | `POST /guide-roster/change-leader` | `guide_roster.manage` | (圖:流程圖,略) `tentative` 是先把人預塞進去、還不算數;`confirmed` 才是正式確認。**只有 confirmed 的指派才會讓該梯次進入嚮導 portal 的可見範圍**(見下節)。 ### 檔期衝突 同一個嚮導若在**日期重疊**的兩個梯次都是 `confirmed`,就構成排班衝突。`confirm` 在確認時會擋下衝突(fail-closed);你也可以**事先預檢**: - `GET /guide-roster/conflict-check?departureId=…&guideId=…`:回傳會撞到的那個梯次,或 `null`(沒撞)。 - `GET /guide-roster/conflicts?month=YYYY-MM`:列出當月所有衝突配對,對應後台排班頁的衝突警示。 ### 換領隊 換領隊用 `change-leader` 一個動作完成(移除原領隊、把新領隊升為 confirmed leader),需附 `reason`(記入稽核)。比起先 `DELETE` 舊指派再 `POST` 新指派,這個端點是原子的,且把換人原因留下軌跡。 ## 嚮導 portal:能看什麼、能做什麼 開通帳號的嚮導用自己的 `guide` token 登入後,看到的是一個**受限 portal**,不是後台。範圍由伺服器依他的身分重新判定,**不接受用參數覆寫**:他只能看到自己被 `confirmed` 指派的梯次。存取別人的梯次一律 fail-closed(`404` / `403`)。 | 嚮導能做 | 說明 | | --- | --- | | 看自己帶的梯次 | 出團資訊、車 / 食 / 宿、成員名單、證件清單(唯讀) | | 揭露旅客個資 | 名單預設遮罩身分證 / 電話;點開才解密,且寫入揭露稽核(只限自己梯次的旅客) | | 填報代墊成本 / 發票 | 現場登記墊付金額與憑證,**送出後即進後台覆核佇列** | | 嚮導**看不到 / 不能做** | 在哪裡 | | --- | --- | | 淨利、毛利、應收 / 實收等金流真相 | 後台(`admin` / `accountant`) | | 覆核成本、轉請款、出款核簽 | 後台財務流程,見 [請款與出款](/guides/payouts) | | 別人帶的梯次、別的旅客 | 永遠超出範圍 | 嚮導填報的成本是「經手」紀錄,**送出即進覆核、不會自動生效**。它要等後台會計覆核通過後才計入帳;覆核、淨利分析、退款給嚮導全在後台完成,嚮導端不顯示這些。 ## 與權限角色的關係 這篇牽涉到兩組不同的權限,別混淆: - **管理嚮導與排班的權限**(`guide.read` / `guide.manage` / `guide_roster.read` / `guide_roster.manage`)給的是**後台 / 串接端**——你拿管理用的 token 建檔、派班、換領隊。 - **嚮導本人的權限**(進 portal 讀自己梯次、填報成本)綁在 `guide` 角色上,且額外受「只限自己 `confirmed` 梯次」的範圍閘二次收斂。 換句話說:派班是「你」在做的事,需要 roster 的管理權限;portal 裡看到的內容是「嚮導」在做的事,受他自己的角色與範圍限制。角色與權限的全貌(角色集、權限字串、owner-scope、模組閘)見 [權限與角色](/guides/rbac)。 ## 下一步 - [出團準備與線控](/guides/departure-prep):分房、護照、裝備、請款等 per-departure 批次作業——嚮導 portal 看到的 dossier 多半源自這裡。 - [權限與角色](/guides/rbac):`guide` 角色、權限字串與範圍閘怎麼決定誰能呼叫什麼。 - [guides API](/api#guides) · [guide-roster API](/api#guide-roster):逐端點的參數與回應。 --- export const metadata = { title: '同業(B2B) — cairn 指南', description: '同業建檔、業務歸屬與可見範圍、帳期與結算、同業通路訂單、上下架——從 API 角度經營你的 B2B 客群。', }; # 同業(B2B) 同業是你的 B2B 客群——合作的旅行社同業、票務代理、揪團團主。你用 `agencies` 資源把他們建成檔,指派負責業務、記下帳期,並讓他們的訂單走同業通路價。 同業是**跨梯次的租戶層客戶資料**,不是某一張訂單的明細。理解這層分離,你才能正確使用 [`agencies` API](/api#agencies):建檔與訂單是兩件事,訂單透過「同業通路」把兩者接起來。 ## 同業建檔 一筆同業(agency)記錄一家 B2B 客戶的基本資料與一位負責業務。建立:[`POST /agencies`](/api#agencies)(只有 `name` 必填,其餘選填),需 `agency.manage` 權限。 讀取:[`GET /agencies`](/api#agencies) 列表、[`GET /agencies/{id}`](/api#agencies) 取單筆,需 `agency.read`。 | 欄位(回傳) | 意義 | | --- | --- | | `name` | 同業公司/團主名稱 | | `taxId` | 統一編號(選填;個人團主可能沒有) | | `contactName` / `phone` / `email` / `address` | 主要聯絡人與聯絡方式 | | `paymentTerm` | 帳期/付款條件(自由文字,如「月結 30 天」) | | `ownerUserId` / `ownerName` | 負責業務(見下) | | `status` | `active`(啟用中)或 `archived`(已封存) | | `note` | 內部備註 | | `orderCount` | 此同業累計訂單數(派生) | 同業是租戶層實體,**沒有 owner-scope**——只要有 `agency.read`,你看得到全部同業,不會因角色被限縮到「自己的同業」。可見範圍的限縮發生在**訂單**那一層,不在同業名單。 ## 業務歸屬與可見範圍 每家同業可指派一位**負責業務**(`ownerUserId`),代表「這家同業長期由誰維護」——這是 CRM 歸屬,不是訂單業績歸屬。候選名單來自 [`GET /agencies/assignable-staff`](/api#agencies)(回傳可指派的員工 `userId` / `name` / `role`),把它當作指派下拉的資料來源。 業務歸屬有兩個獨立層次,別混淆: | 層次 | 欄位 | 回答的問題 | | --- | --- | --- | | 客戶層(同業) | 同業的 `ownerUserId` | 這家同業長期歸誰維護 | | 訂單層 | 訂單的經手業務 | 這張單算誰的業績 | (圖:流程圖,略) 同業的負責業務會在下單時**作為訂單派發的來源之一**帶入,但最終誰拿到這張單仍由租戶的派發規則決定。換手同業負責業務**不會**改寫歷史訂單的業績歸屬,反之亦然。 **可見範圍看的是訂單層,不是同業層。** 一位 `sales` 看得到同業名單(`agency.read`),但在訂單那邊只看得到自己經手的單。所以「指派同業負責業務」與「誰能看到這家同業的訂單」是兩回事——後者由訂單的經手業務與 owner-scope 決定(見[總覽](/guides/architecture#權限與角色))。 ## 帳期、結算與佣金 `paymentTerm` 是一個**自由文字**欄位,用來記下你與同業約定的帳期(例如「月結 30 天」「貨到付款」)。它是給人看的備註,平台目前**不**據此自動產生月結對帳或信用額度控管——結構化的同業帳期/月結屬未來的會計對帳能力。 佣金與分潤同樣**不掛在同業身上**: - **業績**跟著訂單的經手業務走,不是同業的 `ownerUserId`。 - **佣金/出款核簽**走[請款與出款](/guides/payouts);**通路分潤**(如 KOL)走 [discount-codes](/api#discount-codes) 的分潤設定。 換句話說,同業這層只負責「這是哪一家客戶、約定什麼帳期、歸誰維護」,錢的流向由訂單與金流那幾條線各自表達。 ## 同業訂單(agency 通路價) 同業要下單時,你照常用 [`POST /orders`](/api#orders) 建單,差別在把 `customerType` 設為 `agency`(預設 `direct` 直客)——這讓整張單走**同業通路價**,而非牌價。 同業通路價是行程定價裡與直客價、優惠價並列的一組價格;把 `customerType` 設成 `agency` 後,成交價會以同業通路計算並快照在該訂單的商品明細上(金額怎麼派生見[訂單與金流模型](/guides/orders-money-model))。除了通路不同,建單流程(保座、應收、收款計畫)與直客單完全一致。 (圖:流程圖,略) 要回頭盤點某家同業的訂單,用 [`GET /orders`](/api#orders) 並以 `agencyId` 篩選([`GET /orders/export`](/api#orders) 同樣支援),不必逐張單翻找。 ## 上下架 不再合作的同業用**封存**而非刪除,以保留歷史訂單關聯。切換狀態:[`POST /agencies/{id}/status`](/api#agencies),body 帶 `status`(`active` 上架/`archived` 封存),需 `agency.manage`。 也可以用 [`PATCH /agencies/{id}`](/api#agencies) 做真・部分更新——只送你要改的欄位(含 `status`、`ownerUserId`),省略的欄位不動。 封存是軟下架:同業仍在資料裡、歷史訂單關聯不變,只是從「啟用中」名單隱去。需要時用 `status=active` 重新上架。 匯出整份名單(沿用列表的 `status` / `owner` / `hasTaxId` 篩選)用 [`GET /agencies/export`](/api#agencies)。 ## 下一步 - [訂單與金流模型](/guides/orders-money-model):同業訂單的應收、收款與狀態怎麼派生。 - [請款與出款](/guides/payouts):佣金與出款核簽走這條線,不掛在同業上。 - [agencies API](/api#agencies):逐端點的參數與回應欄位。 --- export const metadata = { title: '設定與範本 — cairn 指南', description: '租戶設定 singleton、付款憑證(per-tenant、遮罩)、寄件網域、訂單指派、收款帳戶與費用分類、罐頭訊息與同意書範本。', }; # 設定與範本 設定是**整個租戶共用的一份**——公司抬頭、寄件身分、訂單怎麼指派、錢收進哪個帳戶。你用 [settings API](/api#settings) 讀寫的,和管理者在後台設定頁做的完全等價。 本篇帶你看租戶設定的全貌,以及兩種「範本」資源:給客人發訊息的[罐頭訊息](/api#message-templates),與要客人簽署的[同意書](/api#consent-templates)。 ## 租戶設定是一份 singleton 每個租戶只有**一份**設定,不是 per-user: - 讀整份:`GET /api/v1/settings`;改一部分:`PATCH /api/v1/settings`(partial update——只帶你要改的欄位,沒帶到的不動)。 - 可寫欄位包含公司抬頭(`companyName` / `companyPhone` / `taxId` / `companyAddress` / `logoUrl`)、回覆信箱(`emailReplyTo`)、寄件人 local-part(`senderLocalPart`)、證件效期提醒天數(`passportExpiryWarningDays`)。 - 站台 SEO 預設另走 `PATCH /api/v1/settings/seo`。 設定屬管理範圍:`/settings` 多數端點要 `role.manage` 權限,連讀取都擋(與後台「機密遮罩」一致)。少數子資源用更細的權限——收款帳戶與費用路由要 `receiving_account.manage`、費用類別要 `operating_expense.*`,下面各段會標。 你方案啟用了哪些模組(訂房 / 會計 / 登山…)是**平台方決定**的,你只能唯讀查:`GET /api/v1/settings/modules`。租戶端改不了模組開通。 ## 付款憑證(per-tenant、只回遮罩) ECPay 收款憑證是**每個租戶各自設定**的,不是平台共用: - 一次填齊特店 ID、HashKey、HashIV:`PUT /api/v1/settings/ecpay-credentials`;清除:`DELETE`。 - HashKey / HashIV 加密保存、**永不回讀**,查詢只回末幾碼的遮罩值供顯示確認(write-only)。 詳細的收款生命週期、測試模式與「上線未設定憑證即擋下收款」的行為,見 [付款流程](/guides/payments)。 ## 寄件網域 寄交易信(訂單確認、付款成功、同意書提醒…)不需要你準備任何憑證——平台代為寄送。你能控制的是**寄件身分**,分兩層: - **預設(零設定)**:從平台共用網域寄出,顯示名稱用你的公司名、回信導去你的 `emailReplyTo`。客人不會看到平台網域以外的東西,你也不必動任何 DNS。 - **自有網域(可選升級)**:若你已綁定自有站台網域,可改用 `info@<你的網域>` 寄件。 升級走一條 DNS 驗證生命週期: (圖:流程圖,略) | 動作 | 端點 | | --- | --- | | 查目前狀態與待設 DNS record | `GET /api/v1/settings/sending-domain` | | 啟用(**無網域參數**,用你綁定的網域) | `POST /api/v1/settings/sending-domain/enable` | | 設好 DNS 後重新檢查驗證 | `POST /api/v1/settings/sending-domain/verify` | | 停用、退回平台網域 | `POST /api/v1/settings/sending-domain/disable` | 寄信是 **fail-open**:自有網域驗證一旦失效,會自動退回平台網域寄送,信照寄不中斷——和付款憑證「設錯就擋下」的 fail-closed 取捨相反。寄件人 local-part 用 `senderLocalPart` 調整(預設 `info`)。 ## 訂單指派 新訂單要落到哪個業務手上,由租戶的指派模式決定: - 設定模式與兜底業務:`PATCH /api/v1/settings/assignment`。 - 可被指派的業務名單:`GET /api/v1/settings/assignable-staff`。 | 模式(`assignmentMode`) | 行為 | | --- | --- | | `manual_review` | 進待審池,由人工指派 | | `auto_round_robin` | 自動輪流分配給可受派業務 | | `auto_customer_choice` | 由客人選擇承辦業務 | ## 收款帳戶與費用分類 你可以維護自己的**收款帳戶目錄**,並決定不同性質的款項各自路由到哪個帳戶;公司營業費用也有一套可自訂的分類。 | 用途 | 端點 | 權限 | | --- | --- | --- | | 收款帳戶目錄(銀行 / 現金 / 外幣池…) | `GET` · `POST` · `PATCH /api/v1/settings/receiving-accounts` | `receiving_account.manage` | | 費用性質 → 最終收款帳戶的路由 | `GET` · `PUT /api/v1/settings/fee-category-routes` | `receiving_account.manage` | | 公司營業費用類別目錄 | `GET` · `POST` · `PATCH /api/v1/settings/expense-categories` | `operating_expense.read` / `.manage` | | 內外帳利潤試算的費率 | `PATCH /api/v1/settings/ledger-fees` | `role.manage` | 收款帳戶是**租戶自管、與產品種類無關**的——同一個帳戶可收任何訂單的款。停用某帳戶不刪歷史,只是讓它不再出現在新的收款選項中。 ## 罐頭訊息與同意書範本 兩種「範本」資源,性質很不一樣——一個可改可刪,一個只增不改。 ### 罐頭訊息範本(可編輯) 給客人發 LINE / Email 通知用的可重複文字: - 列表 / 建立:`GET` · `POST /api/v1/message-templates`;單筆 / 改 / 刪:`/api/v1/message-templates/{id}`;啟停:`POST /api/v1/message-templates/{id}/active`。 - 一筆範本帶 `stage`(對應 SOP 階段)、`channel`(`line` 或 `email`)、`title`、`bodySingle`(單人版內文),可選 `bodyMulti`(多人版,留空沿用單人版)、`key`(程式化引用識別碼)、`isActive`。 - 讀取要 `message_template.read`,異動要 `message_template.manage`。 ### 同意書範本(版本化、不可竄改) 要客人簽署的同意書是**只增不改**的——你不會「編輯」一份已存在的同意書,而是**發布新版本**: (圖:流程圖,略) - 列表(同類別最新版在前,每筆帶其綁定的簽署份數):`GET /api/v1/consent-templates`,可用 `kind` 過濾類別。 - 發布新版本:`POST /api/v1/consent-templates`;看單一版本:`GET /api/v1/consent-templates/{id}`。 - 一律 `consent_template.create` 權限(後台讀寫共用同一把)。 同意書範本沒有改 / 刪端點——這是刻意的。已簽署的版本必須原封保留(簽署當下客人同意的是哪份文字,是法遵的證據),任何文字調整都要走「發布新版本」。 ## 下一步 - [付款流程](/guides/payments):ECPay 憑證、收款 redirect、webhook 與測試模式。 - [權限與角色](/guides/rbac):`role.manage`、`receiving_account.manage` 等權限怎麼分派給角色。 - [settings API](/api#settings) · [message-templates](/api#message-templates) · [consent-templates](/api#consent-templates):逐端點的參數與回應。 --- # CLI 參考 ## cairn accrual-ledger — 掛帳台帳:四區塊派生、梯次彙總(純讀) - `cairn accrual-ledger get --period` — 單一期間掛帳台帳四區塊 - 對應端點:GET /api/v1/accrual-ledger - `cairn accrual-ledger departure-summary --period` — 梯次彙總區塊(收入/成本/毛利) - 對應端點:GET /api/v1/accrual-ledger/departure-summary ## cairn agencies — 同業:查詢、明細、指派候選、匯出 - `cairn agencies list` — 列出/篩選同業 - 對應端點:GET /api/v1/agencies - `cairn agencies get ` — 單一同業完整明細 - 對應端點:GET /api/v1/agencies/{id} - `cairn agencies assignable-staff` — 負責業務指派候選清單 - 對應端點:GET /api/v1/agencies/assignable-staff - `cairn agencies export` — 匯出同業名單(當前篩選視圖) - 對應端點:GET /api/v1/agencies/export - `cairn agencies create --name ` — 新增同業(鏡射 createAgency;agency.manage) - 對應端點:POST /api/v1/agencies - `cairn agencies update ` — 更新同業(真 partial;鏡射 updateAgency;agency.manage) - 對應端點:PATCH /api/v1/agencies/{id} - `cairn agencies archive ` — 下架同業(鏡射 setAgencyStatus archived;agency.manage) - 對應端點:POST /api/v1/agencies/{id}/status - `cairn agencies restore ` — 上架同業(鏡射 setAgencyStatus active;agency.manage) - 對應端點:POST /api/v1/agencies/{id}/status ## cairn audit — 稽核日誌:查詢、統計(唯讀) - `cairn audit list` — 列出/篩選稽核紀錄 - 對應端點:GET /api/v1/audit/log - `cairn audit stats` — 稽核 KPI 統計 - 對應端點:GET /api/v1/audit/stats ## cairn consent — 同意書範本:查詢、明細 - `cairn consent list` — 列出同意書範本(各版本) - 對應端點:GET /api/v1/consent-templates - `cairn consent get ` — 單一同意書範本明細 - 對應端點:GET /api/v1/consent-templates/{id} - `cairn consent create --kind --content-mdx ` — 新增同意書範本版本(append-only,版號自動遞增) - 對應端點:POST /api/v1/consent-templates ## cairn control — 團控:梯次列表、名單、驗證、分房、財務、報表 - `cairn control departures` — 近期梯次主列表(團控面板) - 對應端點:GET /api/v1/departures/control - `cairn control manifest ` — 梯次旅客名單 - 對應端點:GET /api/v1/departures/{depId}/manifest - `cairn control validation ` — 梯次旅客資料驗證摘要 - 對應端點:GET /api/v1/departures/{depId}/validation-summary - `cairn control lottery-orders ` — 梯次抽籤訂單清單 - 對應端點:GET /api/v1/departures/{depId}/lottery-orders - `cairn control rooms ` — 梯次分房檢視 - 對應端點:GET /api/v1/departures/{depId}/rooms - `cairn control room-stats ` — 多梯次分房進度統計 - 對應端點:GET /api/v1/departures/room-stats - `cairn control finance ` — 梯次財務摘要(唯讀) - 對應端點:GET /api/v1/departures/{depId}/finance-summary - `cairn control template-costs ` — 梯次成本範本列 - 對應端點:GET /api/v1/departures/{depId}/template-costs - `cairn control manifest-export ` — 梯次旅客名單匯出(csv|json) - 對應端點:GET /api/v1/departures/{depId}/manifest - `cairn control rooming-export ` — 分房表匯出(csv|json) - 對應端點:GET /api/v1/departures/{depId}/rooming-export - `cairn control cost-add --category --amount ` — 登記梯次成本(現場作業) - 對應端點:POST /api/v1/departures/{depId}/costs - `cairn control cost-update --cost --category --amount ` — 編輯梯次成本(會計) - 對應端點:PATCH /api/v1/departures/{depId}/costs/{costId} - `cairn control cost-rm --cost ` — 刪除梯次成本(會計) - 對應端點:DELETE /api/v1/departures/{depId}/costs/{costId} - `cairn control template-apply ` — 套用行程綁定範本到本梯次(冪等) - 對應端點:POST /api/v1/departures/{depId}/template-costs/apply - `cairn control template-override --id --amount ` — 手改某範本成本列金額(停自動重算) - 對應端點:POST /api/v1/departures/{depId}/template-costs/{id}/override - `cairn control template-clear-override --id ` — 重設範本成本列為範本算法(清手改鎖) - 對應端點:POST /api/v1/departures/{depId}/template-costs/{id}/clear-override - `cairn control cost-to-payable --cost-ids --payee-type --payee-name ` — 成本轉請款(建立 payable) - 對應端點:POST /api/v1/departures/{depId}/payables - `cairn control room-add --label --type --beds ` — 建立梯次房間 - 對應端點:POST /api/v1/departures/{depId}/rooms - `cairn control room-update --room ` — 編輯房間 - 對應端點:PATCH /api/v1/departures/{depId}/rooms/{roomId} - `cairn control room-rm --room ` — 刪除房間 - 對應端點:DELETE /api/v1/departures/{depId}/rooms/{roomId} - `cairn control room-assign --room --traveler ` — 指派旅客進房 - 對應端點:POST /api/v1/rooms/{roomId}/travelers - `cairn control room-unassign --traveler ` — 把旅客移出房間 - 對應端點:DELETE /api/v1/rooms/travelers/{travelerId} - `cairn control room-auto-pair-preview ` — auto-pair 建議分組預覽(純讀取;供 apply 取 travelerIds) - 對應端點:GET /api/v1/departures/{depId}/rooms/auto-pair-preview - `cairn control room-auto-pair-apply --travelers ` — 套用 auto-pair 建議分組(單一交易建房+整組指派) - 對應端點:POST /api/v1/departures/{depId}/rooms/auto-pair-apply - `cairn control lottery-resolve --outcome ` — 抽籤手動裁決(won|lost) - 對應端點:POST /api/v1/orders/{id}/lottery/resolve - `cairn control lottery-set-mode --mode ` — 設定梯次分配模式(fcfs|lottery) - 對應端點:PATCH /api/v1/departures/{depId}/allocation-mode - `cairn control prep-checklist --item --checked ` — 設定行前總檢查項勾選 - 對應端點:POST /api/v1/departures/{depId}/prep/checklist - `cairn control prep-document --type ` — upsert 證件追蹤狀態 - 對應端點:POST /api/v1/departures/{depId}/prep/documents - `cairn control prep-deadlines ` — 設定梯次期限警戒(登山專屬;全量取代,空值=清除) - 對應端點:POST /api/v1/departures/{depId}/prep/deadlines - `cairn control prep-equipment-need --item --qty ` — upsert 裝備需求數 - 對應端點:POST /api/v1/departures/{depId}/prep/equipment-needs - `cairn control prep-equipment-need-rm --id ` — 刪除裝備需求列 - 對應端點:DELETE /api/v1/departures/{depId}/prep/equipment-needs/{id} - `cairn control prep-guide-assign --guide --role ` — 指派領隊到梯次 - 對應端點:POST /api/v1/departures/{depId}/prep/guides - `cairn control prep-rental --order --lines ` — 從裝備需求開租借單(含金流接入) - 對應端點:POST /api/v1/departures/{depId}/prep/rentals - `cairn control prep-insurance-add --kind ` — 新增投保紀錄 - 對應端點:POST /api/v1/departures/{depId}/prep/insurance - `cairn control prep-insurance-update --id ` — 更新投保紀錄 - 對應端點:PATCH /api/v1/departures/{depId}/prep/insurance/{id} - `cairn control prep-insurance-rm --id ` — 刪除投保紀錄 - 對應端點:DELETE /api/v1/departures/{depId}/prep/insurance/{id} - `cairn control prep-transport-add ` — 新增派車紀錄 - 對應端點:POST /api/v1/departures/{depId}/prep/transport - `cairn control prep-transport-update --id ` — 更新派車紀錄 - 對應端點:PATCH /api/v1/departures/{depId}/prep/transport/{id} - `cairn control prep-transport-rm --id ` — 刪除派車紀錄 - 對應端點:DELETE /api/v1/departures/{depId}/prep/transport/{id} - `cairn control prep-catering-add ` — 新增餐食紀錄 - 對應端點:POST /api/v1/departures/{depId}/prep/catering - `cairn control prep-catering-update --id ` — 更新餐食紀錄 - 對應端點:PATCH /api/v1/departures/{depId}/prep/catering/{id} - `cairn control prep-catering-rm --id ` — 刪除餐食紀錄 - 對應端點:DELETE /api/v1/departures/{depId}/prep/catering/{id} ## cairn cost-templates — 成本範本:查詢、明細 - `cairn cost-templates list` — 列出成本範本(--active 只回啟用中精簡清單) - 對應端點:GET /api/v1/cost-templates - `cairn cost-templates get ` — 單一成本範本完整明細(含成本行) - 對應端點:GET /api/v1/cost-templates/{id} - `cairn cost-templates create --name <名稱>` — 建立成本範本 - 對應端點:POST /api/v1/cost-templates - `cairn cost-templates update --name <名稱> --status` — 更新成本範本(名稱/說明/狀態) - 對應端點:PATCH /api/v1/cost-templates/{id} - `cairn cost-templates add-line --line` — 新增成本行(--line 為 JSON:{category,unitPriceTwd,quantityMode,...}) - 對應端點:POST /api/v1/cost-templates/{id}/lines - `cairn cost-templates update-line --line` — 更新成本行(--line 為 JSON) - 對應端點:PATCH /api/v1/cost-templates/lines/{lineId} - `cairn cost-templates delete-line ` — 刪除成本行 - 對應端點:DELETE /api/v1/cost-templates/lines/{lineId} ## cairn discount-codes — 折扣碼:列表、明細、預覽試算、兌換報表 - `cairn discount-codes list` — 列出所有折扣碼 - 對應端點:GET /api/v1/discount-codes - `cairn discount-codes get ` — 單一折扣碼完整明細 - 對應端點:GET /api/v1/discount-codes/{id} - `cairn discount-codes preview --code --kind --total ` — 預覽試算折抵(唯讀,不消耗名額) - 對應端點:GET /api/v1/discount-codes/preview - `cairn discount-codes redemptions ` — 折扣碼兌換 / 分潤報表 - 對應端點:GET /api/v1/discount-codes/{id}/redemptions - `cairn discount-codes create --code --label --discount-type --discount-value ` — 建立折扣碼 - 對應端點:POST /api/v1/discount-codes - `cairn discount-codes update --code --label --discount-type --discount-value ` — 更新折扣碼(送出完整欄位,未帶旗標的欄位沿用表單預設) - 對應端點:PATCH /api/v1/discount-codes/{id} - `cairn discount-codes set-active --active` — 啟用 / 停用折扣碼 - 對應端點:POST /api/v1/discount-codes/{id}/set-active ## cairn equipment — 裝備租借:品項主檔、租借單查詢 - `cairn equipment items ` — 裝備品項主檔:list / get / create / update - `cairn equipment rentals --line ` — 租借單:list / get / order-options / create / return / mark-overdue ## cairn exchange-rates — 牌告匯率:列出、解析 - `cairn exchange-rates list` — 列出牌告匯率(台銀 bot + manual 覆蓋) - 對應端點:GET /api/v1/exchange-rates - `cairn exchange-rates resolve --currency --date` — 解析某幣別某日報價(manual 優先 + carry-forward) - 對應端點:GET /api/v1/exchange-rates/resolve - `cairn exchange-rates set-manual --date --currency` — 手動覆蓋某日某幣別匯率(source=manual,可重存覆蓋) - 對應端點:PUT /api/v1/exchange-rates/manual ## cairn group-codes — 團號:碼表(地區/航空)與團號反查 - `cairn group-codes regions` — 地區碼對照表 - 對應端點:GET /api/v1/group-codes/regions:param - `cairn group-codes airlines` — 航空碼對照表 - 對應端點:GET /api/v1/group-codes/airlines:param - `cairn group-codes lookup ` — 精確團號反查單一梯次 - 對應端點:GET /api/v1/group-codes/departures/{id} - `cairn group-codes search ` — 前綴團號反查多梯次 - 對應端點:GET /api/v1/group-codes/departures - `cairn group-codes region-create --name <名>` — 新增地區碼 - 對應端點:POST /api/v1/group-codes/regions - `cairn group-codes region-update --name <名>` — 更新地區碼(name / 啟用 / 排序,不改碼) - 對應端點:PATCH /api/v1/group-codes/regions - `cairn group-codes airline-create --name <名>` — 新增航空碼 - 對應端點:POST /api/v1/group-codes/airlines - `cairn group-codes airline-update --name <名>` — 更新航空碼(name / 啟用 / 排序,不改碼) - 對應端點:PATCH /api/v1/group-codes/airlines ## cairn guide-roster — 帶團排班:名冊載量、月梯次、檔期衝突 - `cairn guide-roster guides --month ` — 名冊 + 該月帶團載量 - 對應端點:GET /api/v1/guide-roster/guides - `cairn guide-roster departures --month ` — 該月梯次 + 已排指派 - 對應端點:GET /api/v1/guide-roster/departures - `cairn guide-roster conflicts --month ` — 該月檔期衝突偵測 - 對應端點:GET /api/v1/guide-roster/conflicts - `cairn guide-roster check-conflict --departure --guide ` — 撞期預檢(指定梯次×領隊) - 對應端點:GET /api/v1/guide-roster/conflict-check - `cairn guide-roster assign --departure --guide --role --status ` — 指派 / 重派領隊到梯次(upsert) - 對應端點:POST /api/v1/guide-roster/assignments - `cairn guide-roster confirm ` — 正式確認某筆指派 - 對應端點:POST /api/v1/guide-roster/assignments/{id}/confirm - `cairn guide-roster remove ` — 移除某筆指派 - 對應端點:DELETE /api/v1/guide-roster/assignments/{id} - `cairn guide-roster change-leader --departure --to --reason ` — 換領隊(升任新領隊為 confirmed leader) - 對應端點:POST /api/v1/guide-roster/change-leader ## cairn guides — 嚮導:查詢、明細 - `cairn guides list` — 列出/搜尋嚮導 - 對應端點:GET /api/v1/guides - `cairn guides get --slug ` — 單一嚮導明細(依 id 或 --slug) - `cairn guides create --name ` — 建立嚮導(建檔) - 對應端點:POST /api/v1/guides - `cairn guides update ` — 更新嚮導(建檔編輯;只送有帶的欄位) - 對應端點:PATCH /api/v1/guides/{id} - `cairn guides archive ` — 封存嚮導(status→inactive;重啟用請用 guides update --status active) - 對應端點:POST /api/v1/guides/{id}/archive - `cairn guides invite-account --email --name ` — 開通嚮導 portal 登入帳號(回傳一次性暫時密碼,僅此一次) - 對應端點:POST /api/v1/guides/{id}/invite-account ## cairn holidays — 國定假日 / 補班表:查詢、年度統計 - `cairn holidays list` — 列出假日 / 補班(可依年度篩選) - `cairn holidays stats --year` — 某年度假日 / 補班計數 - 對應端點:GET /api/v1/holidays/stats - `cairn holidays upsert --date --name` — 新增 / 覆寫一筆假日或補班(date 為自然鍵) - 對應端點:PUT /api/v1/holidays/{date} - `cairn holidays delete --date` — 刪除一筆假日 / 補班 - 對應端點:DELETE /api/v1/holidays/{date} ## cairn images — 圖片庫:查詢圖片、行程選單 - `cairn images list` — 列出/搜尋行程圖片 - 對應端點:GET /api/v1/images - `cairn images trips` — 可掛圖的行程選單(draft + published) - 對應端點:GET /api/v1/images/trips - `cairn images create --trip-id --url ` — 掛圖到行程(以網址) - 對應端點:POST /api/v1/images - `cairn images delete ` — 刪除行程圖片 - 對應端點:DELETE /api/v1/images/{id} ## cairn journal — 嚮導手記:查詢、明細、作者選項 - `cairn journal list` — 列出/搜尋手記 - 對應端點:GET /api/v1/journal - `cairn journal get ` — 單一手記完整明細 - 對應端點:GET /api/v1/journal/{id} - `cairn journal authors` — 可選作者(active 嚮導)清單 - 對應端點:GET /api/v1/journal/authors - `cairn journal create --title --content-mdx ` — 建立手記 - 對應端點:POST /api/v1/journal - `cairn journal update --title --content-mdx ` — 更新手記(整體取代,欄位比照建立) - 對應端點:PATCH /api/v1/journal/{id} - `cairn journal archive ` — 封存手記 - 對應端點:POST /api/v1/journal/{id}/archive ## cairn leaderboard — 業績排行:每位業務成交應收業績、待派發 KPI(admin-only) - `cairn leaderboard list` — 全公司業務業績排行 - 對應端點:GET /api/v1/leaderboard - `cairn leaderboard review-stats` — 待派發訂單 KPI - 對應端點:GET /api/v1/leaderboard/review-stats ## cairn ledger — 內外帳:梯次毛利、逐筆成本、支出證明單、待覆核申報 - `cairn ledger list` — 列出各梯次內外帳摘要(毛利 / 可申報 / pending 證明單) - `cairn ledger get ` — 單一梯次內外帳派生明細 - `cairn ledger costs ` — 列出某梯次逐筆成本登記 - `cairn ledger proofs ` — 列出某梯次支出證明單 - `cairn ledger declarations` — 列出跨梯次待覆核的嚮導申報成本 - `cairn ledger cancelled-departures` — 列出取消梯次待結轉(殘餘成本 / 是否已結轉特殊損益) - `cairn ledger cost-create --category <類別> --amount ` — 登記一筆成本(departure_cost.manage) - `cairn ledger cost-update --category <類別>` — 更新一筆成本(departure_cost.manage) - `cairn ledger cost-delete ` — 刪除一筆未轉請款的成本(departure_cost.manage) - `cairn ledger approve-proof ` — 核准支出證明單(ledger.approve;責任分離) - `cairn ledger approve-declaration ` — 覆核嚮導自報待覆核成本(ledger.approve;責任分離) - `cairn ledger convert-to-payable --costs --payee-name` — 成本轉請款核簽(departure_cost.manage 且 payable.create) - `cairn ledger convert-cancelled --departure --category --amount --period --description` — 結轉取消梯次殘餘成本為公司特殊損益(operating_expense.manage) - `cairn ledger revert-cancelled --departure ` — 撤銷取消梯次的結轉(operating_expense.manage) ## cairn lodging — 旅宿:物件、房型、可用量、訂房、統計 - `cairn lodging properties` — 列出旅宿物件 - 對應端點:GET /api/v1/lodging/properties - `cairn lodging property ` — 單一旅宿物件 - 對應端點:GET /api/v1/lodging/properties/{id} - `cairn lodging room-types --property ` — 列出某物件的房型 - 對應端點:GET /api/v1/lodging/properties/{id}/room-types - `cairn lodging room-type ` — 單一房型 - 對應端點:GET /api/v1/lodging/room-types/{id} - `cairn lodging availability --room-type --check-in --check-out ` — 房型逐夜可用量+價格 [check-in, check-out) - 對應端點:GET /api/v1/lodging/room-types/{id}/availability - `cairn lodging breakdown --room-type --from --to ` — 逐夜已訂量(直訂 vs 套裝)[from, to) - 對應端點:GET /api/v1/lodging/room-types/{id}/availability/breakdown - `cairn lodging images --room-type ` — 列出物件或房型的圖片 - `cairn lodging bookings` — 列出旅宿訂房(代訂) - 對應端點:GET /api/v1/lodging/bookings - `cairn lodging booking ` — 單一旅宿訂房明細 - 對應端點:GET /api/v1/lodging/bookings/{orderId} - `cairn lodging stats` — 旅宿 KPI 統計 - 對應端點:GET /api/v1/lodging/stats - `cairn lodging property-create --slug --name ` — 新增旅宿物件 - 對應端點:POST /api/v1/lodging/properties - `cairn lodging property-update ` — 更新旅宿物件(patch 語意;只送有帶的欄位) - 對應端點:PATCH /api/v1/lodging/properties/{id} - `cairn lodging property-archive ` — 封存旅宿物件(status→inactive) - 對應端點:POST /api/v1/lodging/properties/{id}/archive - `cairn lodging room-type-create --property --name --units-total --base-price ` — 新增房型 - 對應端點:POST /api/v1/lodging/properties/{id}/room-types - `cairn lodging room-type-update ` — 更新房型(patch;送 --facilities= 空字串可清空) - 對應端點:PATCH /api/v1/lodging/room-types/{id} - `cairn lodging room-type-archive ` — 封存房型(status→inactive) - 對應端點:POST /api/v1/lodging/room-types/{id}/archive - `cairn lodging nights-override --room-type --from --to ` — 批次覆寫房型一段區間每晚的價/容量/封房 [from, to](inclusive) - 對應端點:POST /api/v1/lodging/room-types/{id}/nights/override - `cairn lodging booking-cancel ` — 取消旅宿訂房(釋回保留夜;owner-scope) - 對應端點:POST /api/v1/lodging/bookings/{orderId}/cancel - `cairn lodging book --property --room-type --check-in --check-out --units --guests --email --payment-method ` — 代客建立旅宿訂房(訂房人須為既有會員) - 對應端點:POST /api/v1/lodging/bookings - `cairn lodging image-add --room-type --url ` — 新增物件或房型圖片 - `cairn lodging image-delete --room-type` — 刪除物件或房型圖片 - 對應端點:DELETE /api/v1/lodging/images/{id} ## cairn members — 會員(客戶):查詢、明細、統計 - `cairn members list` — 列出/搜尋會員 - 對應端點:GET /api/v1/members - `cairn members get ` — 單一會員完整明細(含訂單/同意紀錄) - 對應端點:GET /api/v1/members/{id} - `cairn members stats` — 會員 KPI 統計 - 對應端點:GET /api/v1/members/stats - `cairn members expiry-flags --ids` — 標記護照即將到期的會員(給定 userId 批次) - 對應端點:GET /api/v1/members/expiry-warnings - `cairn members anonymize ` — PDPA 匿名化會員(不可逆:抹除 PII + 封鎖登入 + 撤銷憑證) - 對應端點:POST /api/v1/members/{id}/anonymize ## cairn message-templates — 罐頭訊息範本:查詢、明細 - `cairn message-templates list` — 列出訊息範本(可依通道過濾) - 對應端點:GET /api/v1/message-templates${query - `cairn message-templates get ` — 單一訊息範本完整明細 - 對應端點:GET /api/v1/message-templates/{id} - `cairn message-templates create --stage --channel --title --body-single ` — 新增訊息範本 - 對應端點:POST /api/v1/message-templates - `cairn message-templates update --stage --channel --title --body-single ` — 更新訊息範本(全量覆寫) - 對應端點:PATCH /api/v1/message-templates/{id} - `cairn message-templates set-active --active` — 啟用 / 停用訊息範本 - 對應端點:POST /api/v1/message-templates/{id}/active - `cairn message-templates delete ` — 刪除訊息範本(硬刪,清除草稿) - 對應端點:DELETE /api/v1/message-templates/{id} ## cairn orders — 訂單:查詢、明細、統計 - `cairn orders list` — 列出/搜尋訂單 - 對應端點:GET /api/v1/orders - `cairn orders get ` — 單一訂單完整明細 - 對應端點:GET /api/v1/orders/{id} - `cairn orders reveal --field ` — 顯示旅客證號/護照全碼(owner-scope;寫 traveler.decrypt 稽核) - 對應端點:POST /api/v1/orders/{id}/travelers/{travelerId}/reveal - `cairn orders create --trip --departure --email --party-size --payment-method --travelers ` — 代客 K 單(手動建立 tour 訂單;訂購人須為既有會員) - 對應端點:POST /api/v1/orders - `cairn orders mark-paid ` — 手動入帳標記訂單已付(idempotent) - 對應端點:POST /api/v1/orders/{id}/mark-paid - `cairn orders cancel ` — 取消訂單(釋回席次,不自動退款) - 對應端點:POST /api/v1/orders/{id}/cancel - `cairn orders adjust --kind --amount --description ` — 掛帳:加收 / 折讓(append charge line + 重算金流) - 對應端點:POST /api/v1/orders/{id}/adjust - `cairn orders reverse --charge-line --description ` — 沖正既有應收項目(append reversal line) - 對應端點:POST /api/v1/orders/{id}/reverse - `cairn orders refund-create --amount --reason --kind ` — 建立並送審客戶退款(draft→submitted) - 對應端點:POST /api/v1/orders/{id}/refunds - `cairn orders refund-approve --refund ` — 核准退款(責任分離:非建單人) - 對應端點:POST /api/v1/orders/{id}/refunds/{refundId}/approve - `cairn orders refund-reject --refund ` — 退件退款(submitted→cancelled) - 對應端點:POST /api/v1/orders/{id}/refunds/{refundId}/reject - `cairn orders refund-paid --refund ` — 標記退款已付(approved→paid,現金整合點) - 對應端點:POST /api/v1/orders/{id}/refunds/{refundId}/mark-paid - `cairn orders assign --assignee ` — 指派訂單給承辦業務(首派;pending_review→assigned) - 對應端點:POST /api/v1/orders/{id}/assign - `cairn orders reassign --assignee ` — 重新指派已派發訂單(admin-only;→reassigned) - 對應端點:POST /api/v1/orders/{id}/reassign - `cairn orders bulk-assign --assignee --orders ` — 批次把多張訂單派給同一位業務(all-or-nothing) - 對應端點:POST /api/v1/orders/bulk-assign - `cairn orders review-screening --decision ` — 人工核可/駁回登山資格審核(駁回不自動退單) - 對應端點:POST /api/v1/orders/{id}/review-screening - `cairn orders balance-link ` — 產生/重用尾款付款連結(回待補金額;owner-scope) - 對應端點:POST /api/v1/orders/{id}/balance-link - `cairn orders traveler-add --full-name ` — 新增旅客到訂單(owner-scope;出發前 7 天凍結) - 對應端點:POST /api/v1/orders/{id}/travelers - `cairn orders traveler-update ` — 更新旅客資料(owner-scope;出發前 7 天凍結) - 對應端點:PATCH /api/v1/orders/{id}/travelers/{travelerId} - `cairn orders traveler-remove ` — 移除旅客(owner-scope;至少保留一位、不可移主要聯絡人) - 對應端點:DELETE /api/v1/orders/{id}/travelers/{travelerId} - `cairn orders rooming-pref ` — 更新旅客分房需求(房型偏好/指定室友;owner-scope) - 對應端點:PATCH /api/v1/orders/{id}/travelers/{travelerId}/rooming - `cairn orders stats` — 訂單 KPI 統計 - 對應端點:GET /api/v1/orders/stats - `cairn orders export` — 匯出訂單為 CSV/JSON(同 list 篩選 + owner-scope;取全頁) - 對應端點:GET /api/v1/orders/export ## cairn payment-requests — 對外應付請款單:查詢、明細、統計、廠商建議 - `cairn payment-requests list` — 列出/搜尋對外應付請款單 - 對應端點:GET /api/v1/payment-requests - `cairn payment-requests get ` — 單一請款單完整明細 - 對應端點:GET /api/v1/payment-requests/{id} - `cairn payment-requests stats` — 對外應付請款單 KPI 統計 - 對應端點:GET /api/v1/payment-requests/stats - `cairn payment-requests suggest-items --departure ` — 由梯次住宿成本推導供應商請款細項建議 - 對應端點:GET /api/v1/payment-requests/supplier-suggestions - `cairn payment-requests create --payee-type --payee-name --description ` — 建立對外應付請款單(draft,或 --submit 同步送審) - 對應端點:POST /api/v1/payment-requests - `cairn payment-requests approve ` — 核可請款單(submitted→approved;責任分離,需 payable.approve) - 對應端點:POST /api/v1/payment-requests/{id}/approve - `cairn payment-requests submit ` — 送審請款單(draft→submitted) - 對應端點:POST /api/v1/payment-requests/{id}/submit - `cairn payment-requests reject --reason ` — 退件請款單(submitted→draft) - 對應端點:POST /api/v1/payment-requests/{id}/reject - `cairn payment-requests mark-paid --bank-reference ` — 標記請款單已付(approved→paid) - 對應端點:POST /api/v1/payment-requests/{id}/mark-paid - `cairn payment-requests cancel ` — 取消請款單(非 paid;僅建單人或 admin) - 對應端點:POST /api/v1/payment-requests/{id}/cancel - `cairn payment-requests mark-signed --signed-by ` — 記錄紙本簽核人 - 對應端點:POST /api/v1/payment-requests/{id}/mark-signed - `cairn payment-requests mark-printed ` — 標記 PDF/紙本已產生(idempotent) - 對應端點:POST /api/v1/payment-requests/{id}/mark-printed ## cairn payments — 收款流水帳:查詢、統計 - `cairn payments list` — 列出/搜尋收款流水 - 對應端點:GET /api/v1/payments - `cairn payments stats` — 收款 KPI 統計 - 對應端點:GET /api/v1/payments/stats - `cairn payments export` — 匯出金流分類帳為 CSV/JSON(同 list 篩選;取全頁) - 對應端點:GET /api/v1/payments/export ## cairn payouts — 出款批次:彙整 preview、出帳帳戶、批次轉帳檔 - `cairn payouts aggregate --period --scope ` — 建批前彙整 preview(當月已核准未付 payable 依收款對象 group) - 對應端點:GET /api/v1/payouts/aggregate - `cairn payouts remit-accounts` — 出帳帳戶選項(收款帳戶目錄) - 對應端點:GET /api/v1/payouts/remit-accounts - `cairn payouts transfer-file ` — 出款批次標準四欄批次轉帳檔 - 對應端點:GET /api/v1/payouts/batches/{id}/transfer-file - `cairn payouts create-batch --period --scope --lines ` — 建立出款批次草稿(gross + 通用扣款項 → draft,不 mark paid) - 對應端點:POST /api/v1/payouts/batches - `cairn payouts confirm-batch ` — 確認出款批次(draft → confirmed:mark paid + 記出帳) - 對應端點:POST /api/v1/payouts/batches/{id}/confirm - `cairn payouts issue-confirm-token ` — 為供應商批次列簽發廠商自助確認 token(idempotent,回 token) - 對應端點:POST /api/v1/payouts/lines/{lineId}/confirm-token ## cairn pnl — 公司損益:營業費用台帳、費用科目、月損益表 - `cairn pnl expenses` — 列出公司營業費用(list) - 對應端點:GET /api/v1/pnl/expenses - `cairn pnl expense-categories` — 列出費用科目目錄(list) - 對應端點:GET /api/v1/pnl/expense-categories - `cairn pnl expense-create --category --period --description --amount ` — 登錄一筆公司營業費用(與梯次無關;歸屬月供月損益彙整) - 對應端點:POST /api/v1/pnl/expenses - `cairn pnl expense-update --category --period --description --amount ` — 更正一筆公司營業費用(整列覆寫;payable 連結須帶回) - 對應端點:PATCH /api/v1/pnl/expenses/{id} - `cairn pnl statement --period` — 公司月損益表(各區毛利→公司淨利) - 對應端點:GET /api/v1/pnl/statement ## cairn reconciliation — 收款帳戶對帳 + 帳戶結算(讀取 / 報表) - `cairn reconciliation accounts` — 逐收款帳戶現金進出對帳 - 對應端點:GET /api/v1/reconciliation/accounts - `cairn reconciliation list-accounts` — 收款帳戶目錄 - 對應端點:GET /api/v1/reconciliation/receiving-accounts - `cairn reconciliation settlement-overview --period` — 月結互抵派生總覽 - 對應端點:GET /api/v1/reconciliation/settlement-overview - `cairn reconciliation transfers` — 帳戶間移轉統一分類帳 - 對應端點:GET /api/v1/reconciliation/transfers - `cairn reconciliation obligations` — 未結掛帳追蹤 - 對應端點:GET /api/v1/reconciliation/obligations - `cairn reconciliation refund-execs` — 退款執行清單(approved + paid) - 對應端點:GET /api/v1/reconciliation/refund-executions - `cairn reconciliation settlement-report --period` — 月結互抵矩陣報表(同 settlement-overview 資料源) - 對應端點:GET /api/v1/reconciliation/settlement-overview - `cairn reconciliation compute-settlement --period` — 重跑當期月結互抵派生 + upsert 掛帳(冪等) - 對應端點:POST /api/v1/reconciliation/settlement/compute - `cairn reconciliation create-transfer --kind --amount --occurred-at ` — 登錄帳戶間移轉(補款/代墊/回填/退款匯出/換匯/沖正;可選沖減掛帳) - 對應端點:POST /api/v1/reconciliation/transfers - `cairn reconciliation statement --account --out --period-start --period-end --file --source-type --source-id --reason ` — 銀行對帳單匯入 + 配對(範本 / 匯入 / 自動配對 / 人工配對 / 略過 / 刪批) - 對應端點:GET /api/v1/reconciliation/statement-imports - 對應端點:GET /api/v1/reconciliation/statement-imports/candidates - 對應端點:GET /api/v1/reconciliation/statement-imports/template - 對應端點:POST /api/v1/reconciliation/statement-imports - 對應端點:POST /api/v1/reconciliation/statement-imports/{importId}/auto-match - 對應端點:POST /api/v1/reconciliation/statement-lines/{lineId}/match - 對應端點:POST /api/v1/reconciliation/statement-lines/{lineId}/unmatch - 對應端點:POST /api/v1/reconciliation/statement-lines/{lineId}/ignore - 對應端點:DELETE /api/v1/reconciliation/statement-imports/{importId} - 對應端點:POST /api/v1/reconciliation/statement-imports/validate ## cairn reports — 財務報表:月結摘要(實收現金口徑,tour-scoped) - `cairn reports monthly --year --month <1-12>` — 月結財務摘要(KPI + 行程排行 + 金流商分布) - 對應端點:GET /api/v1/reports/monthly ## cairn roles — RBAC 角色:列表、明細、權限目錄 - `cairn roles list` — 列出所有角色(內建 + 自訂) - 對應端點:GET /api/v1/roles:param - `cairn roles get ` — 單一角色 + 完整權限矩陣 - 對應端點:GET /api/v1/roles/{id} - `cairn roles catalog` — 列出權限目錄(每個 resource 的可授權 action) - 對應端點:GET /api/v1/roles/catalog - `cairn roles create --name --label <顯示名>` — 新增自訂角色(name + label + 權限) - 對應端點:POST /api/v1/roles - `cairn roles update --label <顯示名>` — 更新自訂角色顯示名與權限(整組覆寫) - 對應端點:PATCH /api/v1/roles/{id} - `cairn roles delete ` — 刪除自訂角色(仍有成員使用時拒絕) - 對應端點:DELETE /api/v1/roles/{id} ## cairn settings — 租戶設定:公司檔 / 收款帳戶 / 費用類別 / 模組 / 寄件網域 - `cairn settings get` — 租戶設定單例(公司檔 / 指派模式 / SEO / 寄件網域;ECPay 憑證一律遮罩) - 對應端點:GET /api/v1/settings - `cairn settings accounts` — 收款帳戶目錄 - 對應端點:GET /api/v1/settings/receiving-accounts:param - `cairn settings fee-routes` — 費用類別 → 收款帳戶路由表 - 對應端點:GET /api/v1/settings/fee-category-routes - `cairn settings expense-categories` — 公司營業費用類別目錄 - 對應端點:GET /api/v1/settings/expense-categories:param - `cairn settings modules` — 租戶啟用的方案模組(唯讀;供應由平台 CLI 管) - 對應端點:GET /api/v1/settings/modules - `cairn settings assignable-staff` — 可被指派訂單的員工名單 - 對應端點:GET /api/v1/settings/assignable-staff - `cairn settings sending-domain` — 寄件網域狀態(含 DNS record;網域未設定時 records 為空) - 對應端點:GET /api/v1/settings/sending-domain - `cairn settings update` — 更新公司檔 / 寄件身分(只送有帶的欄;空字串=清空) - 對應端點:PATCH /api/v1/settings - `cairn settings seo` — 更新 SEO 預設(tagline / description / og 圖;空字串=清空) - 對應端點:PATCH /api/v1/settings/seo - `cairn settings ledger-fees` — 更新內外帳試算費率(bps 0..10000;空=不變) - 對應端點:PATCH /api/v1/settings/ledger-fees - `cairn settings assignment --mode ` — 設定訂單指派模式 + 預設指派人 - 對應端點:PATCH /api/v1/settings/assignment - `cairn settings ecpay --merchant-id --hash-key --hash-iv` — ECPay 憑證(set 三欄一起寫 / clear 解除綁定;write-only) - 對應端點:DELETE /api/v1/settings/ecpay-credentials - 對應端點:PUT /api/v1/settings/ecpay-credentials - `cairn settings sending-domain-manage ` — 寄件網域:啟用 / 驗證 / 停用 - 對應端點:POST /api/v1/settings/sending-domain/:param - `cairn settings accounts-write --code --kind ` — 收款帳戶寫入:create / update - 對應端點:POST /api/v1/settings/receiving-accounts - 對應端點:PATCH /api/v1/settings/receiving-accounts/{id} - `cairn settings fee-routes-write --category --name --final-account` — 費用類型路由:upsert / set-active - 對應端點:PUT /api/v1/settings/fee-category-routes - 對應端點:PATCH /api/v1/settings/fee-category-routes/{id}/active - `cairn settings expense-categories-write --code --name --kind --active ` — 費用類別:create / update / set-active - 對應端點:POST /api/v1/settings/expense-categories - 對應端點:PATCH /api/v1/settings/expense-categories/{id} - 對應端點:PATCH /api/v1/settings/expense-categories/{id}/active ## cairn staff — 員工:查詢租戶內部員工名單 - `cairn staff list` — 列出租戶所有員工 - 對應端點:GET /api/v1/staff - `cairn staff invite --email --name --role ` — 建立新員工帳號(指定角色);回一次性暫時密碼 - 對應端點:POST /api/v1/staff/invite - `cairn staff set-role --role ` — 變更員工角色(admin|sales|op|accountant) - 對應端點:PATCH /api/v1/staff/{userId}/role - `cairn staff remove ` — 移除員工(降為 customer,保留 user 紀錄) - 對應端點:DELETE /api/v1/staff/{userId} ## cairn suppliers — 廠商目錄:查詢、明細 - `cairn suppliers list` — 列出/搜尋廠商目錄 - 對應端點:GET /api/v1/suppliers - `cairn suppliers get ` — 單一廠商明細(含遮罩收款資料) - 對應端點:GET /api/v1/suppliers/{id} - `cairn suppliers create --name ` — 新增廠商(出款對象主檔;進階會計模組) - 對應端點:POST /api/v1/suppliers - `cairn suppliers update ` — 編輯廠商(partial:省略=保留;帳號 write-only,留空不變更) - 對應端點:PATCH /api/v1/suppliers/{id} ## cairn trips — 行程:查詢、明細、團號梯次、成本範本 - `cairn trips list` — 列出/搜尋行程 - 對應端點:GET /api/v1/trips - `cairn trips get ` — 單一行程完整明細 - 對應端點:GET /api/v1/trips/{id} - `cairn trips departures --group-code --group-code-prefix --status --region --airline --departure-date ` — 梯次:查詢(find/search)、建立、編輯、狀態、團號 - 對應端點:POST /api/v1/trips/{id}/departures - 對應端點:PATCH /api/v1/trips/departures/{departureId} - 對應端點:POST /api/v1/trips/departures/{departureId}/status - 對應端點:POST /api/v1/trips/departures/{departureId}/group-code/generate - 對應端點:POST /api/v1/trips/departures/{departureId}/group-code - 對應端點:GET /api/v1/trips/departures - 對應端點:POST /api/v1/trips/{id}/departures/{depId}/lodging - 對應端點:DELETE /api/v1/trips/{id}/lodging/{dlId} - `cairn trips create --slug --title --destination --duration-days --price-from-twd ` — 建立行程 - 對應端點:POST /api/v1/trips - `cairn trips update ` — 更新行程(null = 清空、未帶 = 不動) - 對應端點:PATCH /api/v1/trips/{id} - `cairn trips set-cost-template ` — 綁定 / 解綁行程的成本範本(可多範本疊加) - 對應端點:PUT /api/v1/trips/{id}/cost-template - `cairn trips cost-templates` — 列出可綁定的 active 成本範本 - 對應端點:GET /api/v1/trips/cost-templates - `cairn trips import --file ` — 批次匯入行程(.xlsx/.csv;預設 dry-run,--commit 才建檔) - 對應端點:POST /api/v1/trips/import - 對應端點:POST /api/v1/trips/import/validate - `cairn trips export` — 匯出行程為 CSV/JSON(同 list 篩選) - 對應端點:GET /api/v1/trips/export --- # API 參考(/api/v1) ## /accrual-ledger - GET /api/v1/accrual-ledger — 掛帳台帳(權限 ledger.read) - GET /api/v1/accrual-ledger/departure-summary — 掛帳台帳的「梯次彙總」(權限 ledger.read) ## /agencies - GET /api/v1/agencies — 同業(權限 agency.read) - POST /api/v1/agencies(權限 agency.manage) - GET /api/v1/agencies/{id} — 單一同業完整資料(權限 agency.read) - PATCH /api/v1/agencies/{id}(權限 agency.manage) - POST /api/v1/agencies/{id}/status — 上下架同業(權限 agency.manage) - GET /api/v1/agencies/assignable-staff — 負責業務指派候選清單(權限 agency.read) - GET /api/v1/agencies/export — 同業名單匯出(權限 agency.read) ## /audit - GET /api/v1/audit/log — list/filter the append-only tenant audit trail(權限 audit.read) - GET /api/v1/audit/stats — audit KPI counts(權限 audit.read) ## /consent-templates - GET /api/v1/consent-templates — list every consent-document template version(權限 consent_template.create) - POST /api/v1/consent-templates(權限 consent_template.create) - GET /api/v1/consent-templates/{id} — a single consent-template version(權限 consent_template.create) ## /cost-templates - GET /api/v1/cost-templates — list reusable series-tour cost templates(權限 cost_template.read) - POST /api/v1/cost-templates(權限 cost_template.manage) - GET /api/v1/cost-templates/{id} — full cost-template detail(權限 cost_template.read) - PATCH /api/v1/cost-templates/{id}(權限 cost_template.manage) - POST /api/v1/cost-templates/{id}/lines — add a cost line to a template.(權限 cost_template.manage) - PATCH /api/v1/cost-templates/lines/{lineId} — update or delete a(權限 cost_template.manage) - DELETE /api/v1/cost-templates/lines/{lineId}(權限 cost_template.manage) ## /departures - PATCH /api/v1/departures/{depId}/allocation-mode — 設定梯次分配模式(權限 lottery.manage、模組 lottery) - POST /api/v1/departures/{depId}/costs — 登錄梯次成本(權限 departure_cost.create) - PATCH /api/v1/departures/{depId}/costs/{costId}(權限 departure_cost.manage) - DELETE /api/v1/departures/{depId}/costs/{costId}(權限 departure_cost.manage) - GET /api/v1/departures/{depId}/finance-summary — 該梯次財務摘要(權限 departure.update) - GET /api/v1/departures/{depId}/lottery-orders — 該梯次抽籤訂單清單(權限 order.read) - GET /api/v1/departures/{depId}/manifest — 該梯次旅客名單(權限 order.read) - POST /api/v1/departures/{depId}/payables — 成本轉請款:以選定的未轉成本列建立一張(權限 payable.create) - POST /api/v1/departures/{depId}/prep/catering — 新增餐食紀錄(權限 departure_prep.manage) - PATCH /api/v1/departures/{depId}/prep/catering/{id} — 更新餐食紀錄(權限 departure_prep.manage) - DELETE /api/v1/departures/{depId}/prep/catering/{id} — 刪除餐食紀錄(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/checklist — 設定行前總檢查某項勾選狀態(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/deadlines — 設定梯次期限警戒(權限 departure_prep.manage、模組 climbing) - POST /api/v1/departures/{depId}/prep/documents — upsert 一種證件追蹤狀態(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/equipment-needs — upsert 某裝備品項的需求數(權限 departure_prep.manage) - DELETE /api/v1/departures/{depId}/prep/equipment-needs/{id} — 刪除裝備需求列(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/guides — 指派 / 重派領隊到梯次(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/insurance — 新增投保紀錄(權限 departure_prep.manage) - PATCH /api/v1/departures/{depId}/prep/insurance/{id} — 更新投保紀錄(權限 departure_prep.manage) - DELETE /api/v1/departures/{depId}/prep/insurance/{id} — 刪除投保紀錄(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/rentals — 從裝備需求開租借單(權限 departure_prep.manage) - POST /api/v1/departures/{depId}/prep/transport — 新增派車紀錄(權限 departure_prep.manage) - PATCH /api/v1/departures/{depId}/prep/transport/{id} — 更新派車紀錄(權限 departure_prep.manage) - DELETE /api/v1/departures/{depId}/prep/transport/{id} — 刪除派車紀錄(權限 departure_prep.manage) - GET /api/v1/departures/{depId}/rooming-export — 分房表匯出資料列(權限 departure.update) - GET /api/v1/departures/{depId}/rooms — 該梯次分房檢視(權限 departure.update) - POST /api/v1/departures/{depId}/rooms — 建立一間梯次房間(權限 departure.update) - PATCH /api/v1/departures/{depId}/rooms/{roomId} — 編輯房間(權限 departure.update) - DELETE /api/v1/departures/{depId}/rooms/{roomId} — 刪除房間(權限 departure.update) - POST /api/v1/departures/{depId}/rooms/auto-pair-apply — 線控確認 auto-pair 建議分組:(權限 departure.update) - GET /api/v1/departures/{depId}/rooms/auto-pair-preview — 對某梯次「尚未排房」的旅客(權限 departure.update) - GET /api/v1/departures/{depId}/template-costs — 該梯次套用的成本範本列(權限 departure.update) - POST /api/v1/departures/{depId}/template-costs/{id}/clear-override — 重設為範本算法(權限 departure_cost.manage) - POST /api/v1/departures/{depId}/template-costs/{id}/override — 線控手改某範本成本列(權限 departure_cost.manage) - POST /api/v1/departures/{depId}/template-costs/apply — 套用行程綁定範本到本梯次(權限 departure_cost.create) - GET /api/v1/departures/{depId}/validation-summary — 該梯次各訂單旅客資料完整度(權限 order.read) - GET /api/v1/departures/control — 團控主列表:以出發日為主軸、彙整 order/付款/同意書(權限 order.read) - GET /api/v1/departures/room-stats — 多梯次的分房進度統計(權限 order.read) ## /discount-codes - GET /api/v1/discount-codes — list all discount codes(權限 discount.read) - POST /api/v1/discount-codes — create a discount code(權限 discount.manage) - GET /api/v1/discount-codes/{id} — full discount code detail(權限 discount.read) - PATCH /api/v1/discount-codes/{id} — update a discount code(權限 discount.manage) - GET /api/v1/discount-codes/{id}/redemptions — redemption/profit-share report for(權限 discount.read) - POST /api/v1/discount-codes/{id}/set-active — activate / deactivate a discount(權限 discount.manage) - GET /api/v1/discount-codes/preview — read-only checkout preview of a code's(權限 discount.read) ## /equipment - GET /api/v1/equipment/items — list the equipment master(權限 equipment.read) - POST /api/v1/equipment/items — create a master item(權限 equipment.manage) - GET /api/v1/equipment/items/{id} — single equipment master item detail.(權限 equipment.read) - PATCH /api/v1/equipment/items/{id} — update a master item(權限 equipment.manage) - GET /api/v1/equipment/rentals — list rental tickets(權限 equipment.read) - POST /api/v1/equipment/rentals — open a rental ticket(權限 equipment.manage) - GET /api/v1/equipment/rentals/{id} — single rental ticket detail(權限 equipment.read) - POST /api/v1/equipment/rentals/{id}/mark-overdue — flag a single rented ticket(權限 equipment.manage) - POST /api/v1/equipment/rentals/{id}/return — receive a rental back(權限 equipment.manage) - GET /api/v1/equipment/rentals/order-options — the minimal order picker(權限 equipment.read) ## /exchange-rates - GET /api/v1/exchange-rates — list the Bank-of-Taiwan published FX rate board(權限 account_transfer.read、模組 accounting) - PUT /api/v1/exchange-rates/manual — manual FX rate override(權限 account_transfer.manage) - GET /api/v1/exchange-rates/resolve — resolve the canonical "1 foreign unit =(權限 account_transfer.read) ## /group-codes - GET /api/v1/group-codes/airlines — 航空碼對照表(權限 group_code.manage) - POST /api/v1/group-codes/airlines(權限 group_code.manage) - PATCH /api/v1/group-codes/airlines(權限 group_code.manage) - GET /api/v1/group-codes/departures — 前綴團號反查多梯次(權限 group_code.manage) - GET /api/v1/group-codes/departures/{id} — 精確團號反查單一梯次(權限 group_code.manage) - GET /api/v1/group-codes/regions — 地區碼對照表(權限 group_code.manage) - POST /api/v1/group-codes/regions(權限 group_code.manage) - PATCH /api/v1/group-codes/regions(權限 group_code.manage) ## /guide-roster - POST /api/v1/guide-roster/assignments — 指派 / 重派 guide 到梯次(權限 guide_roster.manage) - DELETE /api/v1/guide-roster/assignments/{id} — 移除某筆指派(權限 guide_roster.manage) - POST /api/v1/guide-roster/assignments/{id}/confirm — 正式確認某筆指派(權限 guide_roster.manage) - POST /api/v1/guide-roster/change-leader — 換領隊(權限 guide_roster.manage) - GET /api/v1/guide-roster/conflict-check — pre-check whether assigning a guide(權限 guide_roster.read) - GET /api/v1/guide-roster/conflicts — schedule conflicts for the selected(權限 guide_roster.read) - GET /api/v1/guide-roster/departures — the selected month's departures(權限 guide_roster.read) - GET /api/v1/guide-roster/guides — roster(權限 guide_roster.read) ## /guides - GET /api/v1/guides — list/search guides(權限 guide.read) - POST /api/v1/guides — create a guide(權限 guide.manage) - GET /api/v1/guides/{id} — single guide detail(權限 guide.read) - PATCH /api/v1/guides/{id} — update a guide(權限 guide.manage) - POST /api/v1/guides/{id}/archive — archive a guide(權限 guide.manage) - POST /api/v1/guides/{id}/invite-account — 開通某既有 guide 的嚮導 portal 登入帳號(權限 guide.manage) ## /holidays - GET /api/v1/holidays — per-tenant national-holiday / makeup-workday table(權限 public_holiday.read) - PUT /api/v1/holidays/{date} — upsert / delete a single per-tenant(權限 public_holiday.manage) - DELETE /api/v1/holidays/{date}(權限 public_holiday.manage) - GET /api/v1/holidays/stats — holiday / makeup-workday counts for a(權限 public_holiday.read) ## /images - GET /api/v1/images — list/search the trip image gallery(權限 image.read) - POST /api/v1/images(權限 image.manage) - DELETE /api/v1/images/{id} — remove a trip image(權限 image.manage) - GET /api/v1/images/trips — the trip picker for attaching images: draft +(權限 image.read) ## /internal-external-ledger - GET /api/v1/internal-external-ledger/cancelled-departures — 取消梯次待結轉列表(權限 ledger.read、模組 accounting) - POST /api/v1/internal-external-ledger/cancelled-departures/convert — 結轉取消(權限 operating_expense.manage、模組 accounting) - POST /api/v1/internal-external-ledger/cancelled-departures/revert — 撤銷取消(權限 operating_expense.manage、模組 accounting) - PATCH /api/v1/internal-external-ledger/costs/{costId}(權限 departure_cost.manage) - DELETE /api/v1/internal-external-ledger/costs/{costId}(權限 departure_cost.manage) - POST /api/v1/internal-external-ledger/costs/{costId}/approve-declaration(權限 ledger.approve) - GET /api/v1/internal-external-ledger/declarations/pending — 待覆核的嚮導申報(權限 ledger.read、模組 accounting) - GET /api/v1/internal-external-ledger/departures — per-departure 內外帳列表(權限 ledger.read、模組 accounting) - GET /api/v1/internal-external-ledger/departures/{departureId} — single(權限 ledger.read、模組 accounting) - POST /api/v1/internal-external-ledger/departures/{departureId}/convert-to-payable(權限 departure_cost.manage) - GET /api/v1/internal-external-ledger/departures/{departureId}/costs(權限 ledger.read) - POST /api/v1/internal-external-ledger/departures/{departureId}/costs(權限 departure_cost.manage) - GET /api/v1/internal-external-ledger/departures/{departureId}/proofs — 支出(權限 ledger.read、模組 accounting) - GET /api/v1/internal-external-ledger/departures/{departureId}/proofs/{proofId}(權限 ledger.read、模組 accounting) - POST /api/v1/internal-external-ledger/proofs/{proofId}/approve(權限 ledger.approve) ## /journal - GET /api/v1/journal — list/search 嚮導手記 posts(權限 journal.read) - POST /api/v1/journal — create a 嚮導手記 post(權限 journal.manage) - GET /api/v1/journal/{id} — full 嚮導手記 post detail(權限 journal.read) - PATCH /api/v1/journal/{id} — update a 嚮導手記 post(權限 journal.manage) - POST /api/v1/journal/{id}/archive — archive a 嚮導手記 post(權限 journal.manage) - GET /api/v1/journal/authors — active guides as author options for journal(權限 journal.read) ## /leaderboard - GET /api/v1/leaderboard — 全公司業務業績排行(權限 leaderboard.read) - GET /api/v1/leaderboard/review-stats — 待派發訂單 KPI(權限 leaderboard.read) ## /lodging - GET /api/v1/lodging/bookings — list staff-side direct lodging bookings(權限 lodging.read) - POST /api/v1/lodging/bookings(權限 order.create、模組 lodging) - GET /api/v1/lodging/bookings/{orderId} — single lodging booking detail.(權限 lodging.read) - POST /api/v1/lodging/bookings/{orderId}/cancel — cancel a direct lodging(權限 order.update) - DELETE /api/v1/lodging/images/{id} — delete a gallery(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/properties — list tenant lodging properties(權限 lodging.read) - POST /api/v1/lodging/properties — create a property(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/properties/{id} — single lodging property(權限 lodging.read) - PATCH /api/v1/lodging/properties/{id} — update a property(權限 lodging.manage、模組 lodging) - POST /api/v1/lodging/properties/{id}/archive — archive(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/properties/{id}/images — property gallery images.(權限 lodging.read、模組 lodging) - POST /api/v1/lodging/properties/{id}/images — add a property gallery image(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/properties/{id}/room-types — room types for one property(權限 lodging.read) - POST /api/v1/lodging/properties/{id}/room-types — create a room type under this property(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/room-types/{id} — single room type(權限 lodging.read) - PATCH /api/v1/lodging/room-types/{id} — update a room type(權限 lodging.manage、模組 lodging) - POST /api/v1/lodging/room-types/{id}/archive — archive(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/room-types/{id}/availability — per-night availability +(權限 lodging.read) - GET /api/v1/lodging/room-types/{id}/availability/breakdown — per-night(權限 lodging.read) - GET /api/v1/lodging/room-types/{id}/images — room-type gallery images.(權限 lodging.read、模組 lodging) - POST /api/v1/lodging/room-types/{id}/images — add a room-type gallery image(權限 lodging.manage、模組 lodging) - POST /api/v1/lodging/room-types/{id}/nights/override — batch-override the(權限 lodging.manage、模組 lodging) - GET /api/v1/lodging/stats — 旅宿 dashboard overview(權限 lodging.read) ## /members - GET /api/v1/members — list/search the tenant customer(權限 member.read) - GET /api/v1/members/{id} — full member detail(權限 member.read) - POST /api/v1/members/{id}/anonymize — irreversible PDPA anonymization of one(權限 member.update) - GET /api/v1/members/expiry-warnings — given a batch of member(權限 member.read) - GET /api/v1/members/stats — member directory KPI cards(權限 member.read) ## /message-templates - GET /api/v1/message-templates — list canned message templates(權限 message_template.read) - POST /api/v1/message-templates(權限 message_template.manage) - GET /api/v1/message-templates/{id} — single canned message template detail.(權限 message_template.read) - PATCH /api/v1/message-templates/{id}(權限 message_template.manage) - DELETE /api/v1/message-templates/{id}(權限 message_template.manage) - POST /api/v1/message-templates/{id}/active — enable / disable(權限 message_template.manage) ## /orders - GET /api/v1/orders — list/search/filter orders(權限 order.read) - POST /api/v1/orders(權限 order.create) - GET /api/v1/orders/{id} — full kind-aware order detail(權限 order.read) - POST /api/v1/orders/{id}/adjust — manual charge adjustment(權限 order.adjust) - POST /api/v1/orders/{id}/assign — 首次派發訂單給承辦業務(權限 order.assign) - POST /api/v1/orders/{id}/balance-link — 產生/重用尾款(權限 order.update) - POST /api/v1/orders/{id}/cancel — cancel a tour order(權限 order.update) - POST /api/v1/orders/{id}/claim — 業務自派搶單(權限 order.claim) - POST /api/v1/orders/{id}/lottery/resolve — 線控手動抽籤裁決:won(權限 lottery.manage) - POST /api/v1/orders/{id}/mark-paid — mark an order paid by manual(權限 payment.update) - POST /api/v1/orders/{id}/reassign — 重新指派已派發訂單(權限 order.reassign) - POST /api/v1/orders/{id}/refunds — create + submit a customer refund(權限 refund.create) - POST /api/v1/orders/{id}/refunds/{refundId}/approve — approve a submitted(權限 refund.approve) - POST /api/v1/orders/{id}/refunds/{refundId}/mark-paid — mark an approved(權限 refund.mark_paid) - POST /api/v1/orders/{id}/refunds/{refundId}/reject — reject a submitted(權限 refund.reject) - POST /api/v1/orders/{id}/reverse — reverse an existing charge line(權限 order.adjust) - POST /api/v1/orders/{id}/review-screening — 人工核可/駁回登山資格審核(權限 screening.review) - POST /api/v1/orders/{id}/travelers — 新增旅客到訂單(權限 order.update) - PATCH /api/v1/orders/{id}/travelers/{travelerId} — 更新旅客資料(權限 order.update) - DELETE /api/v1/orders/{id}/travelers/{travelerId} — 移除旅客(權限 order.update) - POST /api/v1/orders/{id}/travelers/{travelerId}/reveal — 顯示旅客證號/護照全碼(權限 order.read) - PATCH /api/v1/orders/{id}/travelers/{travelerId}/rooming — 更新旅客分房需求(權限 order.update) - POST /api/v1/orders/bulk-assign — 批次把多張訂單派給同一位業務(權限 order.assign) - GET /api/v1/orders/export — 匯出訂單(權限 order.read) - GET /api/v1/orders/stats — order KPI counts(權限 order.read) ## /payment-requests - GET /api/v1/payment-requests — list/search/filter 對外應付(權限 payable.read) - POST /api/v1/payment-requests(權限 payable.create) - GET /api/v1/payment-requests/{id} — 單一對外應付請款單完整明細(權限 payable.read) - POST /api/v1/payment-requests/{id}/approve — 核可請款單(權限 payable.approve) - POST /api/v1/payment-requests/{id}/cancel — 取消請款單(權限 payable.create) - POST /api/v1/payment-requests/{id}/mark-paid — 標記請款單已付(權限 payable.mark_paid) - POST /api/v1/payment-requests/{id}/mark-printed — 標記 PDF/紙本已產生(權限 payable.print) - POST /api/v1/payment-requests/{id}/mark-signed — 記錄紙本簽核人(權限 payable.create) - POST /api/v1/payment-requests/{id}/reject — 退件請款單(權限 payable.approve) - POST /api/v1/payment-requests/{id}/submit — 送審請款單(權限 payable.create) - GET /api/v1/payment-requests/stats — 對外應付請款單 KPI 計數(權限 payable.read) - GET /api/v1/payment-requests/supplier-suggestions — 由某梯次的(權限 payable.create) ## /payments - GET /api/v1/payments — list/search/filter the customer→agency payment ledger(權限 payment.read) - GET /api/v1/payments/export — 匯出金流分類帳(權限 payment.read) - GET /api/v1/payments/stats — payment KPI counts(權限 payment.read) ## /payouts - GET /api/v1/payouts/aggregate — 派生當月(權限 supplier.read、模組 accounting) - POST /api/v1/payouts/batches — 建立出款批次草稿(權限 supplier.read、模組 accounting) - POST /api/v1/payouts/batches/{id}/confirm — 確認出款批次(權限 payable.mark_paid、模組 accounting) - GET /api/v1/payouts/batches/{id}/transfer-file — 出款批次的標準四欄批次轉帳檔(權限 payable.mark_paid、模組 accounting) - POST /api/v1/payouts/lines/{lineId}/confirm-token — 為某出款批次列簽發廠商自助確認(權限 supplier.read、模組 accounting) - GET /api/v1/payouts/remit-accounts — 出款批次的「出帳帳戶」選項(權限 supplier.read、模組 accounting) ## /pnl - GET /api/v1/pnl/expense-categories — list the company expense-category(權限 operating_expense.read) - GET /api/v1/pnl/expenses — list company operating-expense ledger rows(權限 operating_expense.read) - POST /api/v1/pnl/expenses(權限 operating_expense.manage、模組 accounting) - PATCH /api/v1/pnl/expenses/{id} — 更正一筆公司營業費用(權限 operating_expense.manage、模組 accounting) - GET /api/v1/pnl/statement — the company monthly P&L statement(權限 operating_expense.read) ## /reconciliation - GET /api/v1/reconciliation/accounts — 逐收款帳戶現金進出對帳(權限 receiving_account.read) - GET /api/v1/reconciliation/obligations — 未結掛帳追蹤(權限 account_settlement.read、模組 accounting) - GET /api/v1/reconciliation/receiving-accounts — 收款帳戶目錄清單(權限 receiving_account.read) - GET /api/v1/reconciliation/refund-executions — 退款執行清單(權限 account_settlement.read、模組 accounting) - GET /api/v1/reconciliation/settlement-overview — 子系統 B 月結互抵派生總覽(權限 account_settlement.read、模組 accounting) - POST /api/v1/reconciliation/settlement/compute — 跑當期月結互抵派生 + upsert(權限 account_settlement.read、模組 accounting) - GET /api/v1/reconciliation/statement-imports(權限 bank_statement.read、模組 accounting) - POST /api/v1/reconciliation/statement-imports(權限 bank_statement.manage、模組 accounting) - DELETE /api/v1/reconciliation/statement-imports/{importId} — 刪整批(權限 bank_statement.manage、模組 accounting) - POST /api/v1/reconciliation/statement-imports/{importId}/auto-match — 對該批(權限 bank_statement.manage、模組 accounting) - GET /api/v1/reconciliation/statement-imports/candidates — 某帳戶內、未被(權限 bank_statement.read、模組 accounting) - GET /api/v1/reconciliation/statement-imports/template — 銀行對帳單匯入固定範本下載(權限 bank_statement.read、模組 accounting) - POST /api/v1/reconciliation/statement-imports/validate — 兩階段匯入的第一階段 dry-run(權限 bank_statement.manage、模組 accounting) - POST /api/v1/reconciliation/statement-lines/{lineId}/ignore — 略過一條對帳單行 →(權限 bank_statement.manage、模組 accounting) - POST /api/v1/reconciliation/statement-lines/{lineId}/match — 人工配對一條對帳單行到指定(權限 bank_statement.manage、模組 accounting) - POST /api/v1/reconciliation/statement-lines/{lineId}/unmatch — 解除配對 → 'excluded'(權限 bank_statement.manage、模組 accounting) - GET /api/v1/reconciliation/transfers — 帳戶間移轉統一分類帳查詢(權限 account_settlement.read) - POST /api/v1/reconciliation/transfers(權限 account_transfer.manage、模組 accounting) ## /reports - GET /api/v1/reports/monthly — tenant monthly(權限 order.read) ## /roles - GET /api/v1/roles — list all roles(權限 role.manage) - POST /api/v1/roles(權限 role.manage) - GET /api/v1/roles/{id} — one role + its full(權限 role.manage) - PATCH /api/v1/roles/{id}(權限 role.manage) - DELETE /api/v1/roles/{id}(權限 role.manage) - GET /api/v1/roles/catalog — the code-defined permission catalog: every legal(權限 role.manage) ## /rooms - POST /api/v1/rooms/{roomId}/travelers — 指派旅客進房(權限 departure.update) - DELETE /api/v1/rooms/travelers/{travelerId} — 把旅客移出房間(權限 departure.update) ## /settings - GET /api/v1/settings — the tenant_settings singleton(權限 role.manage) - PATCH /api/v1/settings(權限 role.manage) - GET /api/v1/settings/assignable-staff — the staff pool eligible to be an(權限 role.manage) - PATCH /api/v1/settings/assignment — S5 order-assignment mode + default(權限 role.manage) - PUT /api/v1/settings/ecpay-credentials(權限 role.manage) - DELETE /api/v1/settings/ecpay-credentials(權限 role.manage) - GET /api/v1/settings/expense-categories — 公司營業費用類別目錄(權限 operating_expense.read、模組 accounting) - POST /api/v1/settings/expense-categories(權限 operating_expense.manage、模組 accounting) - PATCH /api/v1/settings/expense-categories/{id} — 更新費用類別的顯示名 / kind +(權限 operating_expense.manage、模組 accounting) - PATCH /api/v1/settings/expense-categories/{id}/active — toggle a費用類別(權限 operating_expense.manage、模組 accounting) - GET /api/v1/settings/fee-category-routes — fee-category → final receiving(權限 receiving_account.manage、模組 accounting) - PUT /api/v1/settings/fee-category-routes(權限 receiving_account.manage、模組 accounting) - PATCH /api/v1/settings/fee-category-routes/{id}/active — toggle a fee-category(權限 receiving_account.manage、模組 accounting) - PATCH /api/v1/settings/ledger-fees — internal/external-ledger profit estimate(權限 role.manage、模組 accounting) - GET /api/v1/settings/modules — the tenant's enabled plan modules(權限 role.manage) - GET /api/v1/settings/receiving-accounts — the收款帳戶目錄(權限 receiving_account.manage、模組 accounting) - POST /api/v1/settings/receiving-accounts(權限 receiving_account.manage、模組 accounting) - PATCH /api/v1/settings/receiving-accounts/{id} — update a收款帳戶(權限 receiving_account.manage、模組 accounting) - GET /api/v1/settings/sending-domain — the tenant's custom email sending-domain(權限 role.manage) - POST /api/v1/settings/sending-domain/disable — tear down the tenant's custom(權限 role.manage) - POST /api/v1/settings/sending-domain/enable — provision the tenant's custom(權限 role.manage) - POST /api/v1/settings/sending-domain/verify — re-sync DNS verification status(權限 role.manage) - PATCH /api/v1/settings/seo — SEO defaults subset of tenant_settings(權限 role.manage) ## /staff - GET /api/v1/staff — list every staffer in the tenant DB(權限 role.manage) - DELETE /api/v1/staff/{userId} — remove a staffer(權限 role.manage) - PATCH /api/v1/staff/{userId}/role — change a staffer's role(權限 role.manage) - POST /api/v1/staff/invite — 建立一個新員工帳號(權限 role.manage) ## /suppliers - GET /api/v1/suppliers — 廠商目錄列表(權限 supplier.read、模組 accounting) - POST /api/v1/suppliers(權限 supplier.manage、模組 accounting) - GET /api/v1/suppliers/{id} — 單一廠商主檔(權限 supplier.read) - PATCH /api/v1/suppliers/{id}(權限 supplier.manage、模組 accounting) ## /trips - GET /api/v1/trips(權限 trip.read) - POST /api/v1/trips — same core op + audit as the back office(權限 trip.manage) - GET /api/v1/trips/{id} — full admin trip detail(權限 trip.read) - PATCH /api/v1/trips/{id}(權限 trip.manage) - PUT /api/v1/trips/{id}/cost-template — bind / unbind a trip's cost templates(權限 cost_template.manage) - POST /api/v1/trips/{id}/departures — add a departure to a trip(權限 trip.manage) - POST /api/v1/trips/{id}/departures/{depId}/lodging — 為某梯次預留自家旅宿配套(權限 trip.manage、模組 lodging) - DELETE /api/v1/trips/{id}/lodging/{dlId} — 釋回某梯次的自家旅宿配套預留(權限 trip.manage、模組 lodging) - GET /api/v1/trips/cost-templates — active cost templates for(權限 cost_template.read) - GET /api/v1/trips/departures — group-code lookup over HTTP.(權限 trip.read) - PATCH /api/v1/trips/departures/{departureId} — per-row pricing / deposit /(權限 trip.manage) - POST /api/v1/trips/departures/{departureId}/group-code — manual group-code(權限 departure.update) - POST /api/v1/trips/departures/{departureId}/group-code/generate — auto(權限 departure.update) - POST /api/v1/trips/departures/{departureId}/status — change a departure's(權限 trip.manage) - GET /api/v1/trips/export — 匯出行程(權限 trip.read) - POST /api/v1/trips/import — 行程批次匯入 commit:上傳檔(權限 trip.manage) - POST /api/v1/trips/import/validate — 行程批次匯入 dry-run:上傳檔(權限 trip.manage)