Skip to Content
核心概念業務日曆

公司業務行事曆

ct.calendars major 1 結合可選用的公司業務時區、伺服器指派的流水號與期間封鎖。已合併至後端來源 3e2b00eb,不代表已部署至 staging。

讀取與設定時區

使用 GET 與 PUT /private/module/custom_tables/company/{company_id}/core/calendar。兩者都要求同公司的登入使用者;只有公司管理者能 PUT。這是公司層級的 CT 設定,不是表格 grant,也不是匿名或外部客戶端的能力。

{ "business_timezone": "Asia/Taipei", "expected_version": 0 }

回應為 {company_id, business_timezone, version, updated_at}。尚無行事曆設定列的公司會讀到 business_timezone: null、version: 0、updated_at: null。PUT 必須提供 business_timezone,但可設為 null 停用;非 null 值必須是伺服器時區資料庫中的標準 IANA 名稱。UTC+8 這類數值偏移不是時區名稱。

expected_version 可省略,且不得為負數。過期版本以 409、detail.error: "ct.calendar_version_conflict" 拒絕,並附上 current_version;省略則採最後寫入者優先。無效時區回傳 422、detail.error: "ct.calendar_timezone_invalid"。一般請求形狀驗證也使用 422,但 detail 是清單,不是型別化物件。見行事曆 API catalog。

時鐘優先序與評估單位

設定業務時區後,每個 CT 評估單位固定使用同一個 UTC 時刻與公司時區/版本。Row-policy 日期 token、公式 TODAY()、Command $today、未指定時區運算元的 Command $time_of_day 與 $local_date,以及未自行指定時區的排程,使用公司業務時鐘。Trigger 排程若明確指定時區,優先於公司時區。

null 保留原有時鐘:row-policy 日期使用 UTC+08:00,公式 TODAY() 使用伺服器本機時間,Command $today 與排程保留原有 UTC 行為;未指定時區運算元的 Command $time_of_day 與 $local_date 以 UTC 轉換。這是主動選用的設定,不會默默重新解讀既有資料。

時區變更在 commit 後開始的評估才生效,不會改寫已儲存日期、流水號、排程錨點或已消耗的排程視窗。時區設定不是假日/工作日算術引擎;期間涵蓋範圍由 CT 資料列與期間封鎖規則定義。

工作日行事曆

公式 v2 的 WORKDAY、NETWORKDAYS 與 ISWORKDAY 讀取具名的工作日行事曆。行事曆屬公司所有,每次儲存都會新增一個不可變版本;在有人儲存之前,default 於版本 0 讀為週一至週五、無例外。這些路由只存在於 company scope,位於 /private/module/custom_tables/company/{company_id}/core/work-calendars 之下:

路由回應
GET …/work-calendars{ "default_key": "default", "calendars": [...] }:default 與每一份已儲存的行事曆,依 key 排序
GET …/work-calendars/{key}目前版本,或以 ?version=N(至少為 1 的整數)取得某個不可變版本。未知的 key 或版本回 404,detail.error: "ct.work_calendar_not_found"
PUT …/work-calendars/{key}新增一個版本。僅限公司管理者,且需 JWT;低於公司管理者的使用者回 403

key 以小寫字母開頭,其後為小寫字母、數字、_ 或 -,最長 64 字元。兩種讀取都回傳行事曆:

{ "name": "Taipei office", "weekdays": ["mon", "tue", "wed", "thu", "fri"], "exceptions": [ { "date": "2027-01-01", "working": false, "label": "New Year's Day" }, { "date": "2027-01-09", "working": true } ], "company_id": "11111111-1111-4111-8111-111111111111", "key": "taipei-office", "version": 3, "business_timezone": "Asia/Taipei", "actor_user_id": "22222222-2222-4222-8222-222222222222", "created_at": "2026-10-08T03:15:00" }

business_timezone 是讀取與設定時區一節的公司業務時區(未設定時為 null);從未儲存過的 default,其 actor_user_id 與 created_at 為 null。PUT 接受 name(1 至 128 字元,不得含 U+0000 至 U+001F 或 U+007F)、weekdays(mon 至 sun 的不重複值)、exceptions(最多 10,000 筆,每個日期一筆,每筆為 {date, working} 加上下述選用的 label;working 必須是 JSON 布林值),以及選用的 expected_version,為至少 0 的整數(從未儲存過的行事曆用 0)。伺服器會將兩個清單排序,未知欄位回 422。過期的 expected_version 回 409,detail.error: "ct.calendar_version_conflict";與公司時區不同,它不帶 current_version,請重新讀取。儲存與目前版本完全相同的定義不會新增版本,並回傳該版本。每個新儲存的版本也會重算使用該行事曆的 stored 公式(見公式 v2)。

MCP 工具 work_calendar_get 與 Agent 唯讀工具 custom_tables_get_work_calendar 以 key(預設 default)與選用的 version 讀取同一份文件。

唯讀 API key 存取

自後端 5.21.0 起,scope 為 work_calendar_read 的 API key 只能讀取上述兩條 GET 路由,其他一律不行。簽發方式與任何整合 API key 相同(POST /private/auth/apikey,scope=work_calendar_read)。這把 key 以簽發它的使用者身分運作,因此路徑上的 company_id 必須是該使用者所屬的公司;任何其他 id(不論存在與否)都回 404 {"detail": "Company not found"}。讀取既不需要管理者角色,也不需要資料表 grant。

使用 work_calendar_read key 的請求回應
GET …/work-calendars、GET …/work-calendars/{key}(可帶或不帶 ?version=)200
PUT …/work-calendars/{key}403 {"detail": "Access denied, invalid scope"}
其他路由,例如 …/core/capabilities 與 …/core/calendar405 {"detail": "Access to <METHOD> <path> is not allowed for your role"}

Custom Tables 的 input、artifact 與 package 路由(…/core/input-*、…/core/artifacts、…/packages)則對 work_calendar_read、third_party 或 specific_chatroom 的 key 回 403 {"detail": "Access denied, invalid scope"}。其他 scope 的 key 都無法到達行事曆路由。third_party、specific_chatroom 或舊版 full key 在兩條 GET 路由都回 403 {"detail": "Access denied, invalid scope"},所以既有的 key 不會多出行事曆存取權。寫入一律需要公司管理者的 JWT。這把 key 的到期與允許來源檢查與任何 API key 相同(見驗證)。以 scoped key 讀取會回傳 actor_user_id(儲存該版本的使用者)與 business_timezone,因此只應把這個 scope 授予可以看到這兩者的整合。

例外標籤

自後端 5.21.0 起,例外可以帶選用的 label,是純文字備註,例如假日名稱。長度最多 64 個 Unicode 字元,原樣儲存,不會去除空白或正規化。允許 tab 與換行;U+0000 至 U+0008、U+000B 至 U+001F(因此也包含 carriage return)以及 U+007F 會被拒絕,過長的 label 同樣如此,回傳一般的清單形 422(錯誤的 loc 依序以 exceptions、索引與 label 結尾)。label 可為 null;沒有 label 的例外在讀取時會省略該鍵。label 不會改變任何結果:WORKDAY、NETWORKDAYS 與 ISWORKDAY 只讀 date 與 working。只改 label 仍會儲存新版本,較早的版本保留當時的 label。IaC 的 work_calendar line 使用相同的 spec,因此套用同樣的限制。

探索與宣告

GET …/core/capabilities 回報 { "id": "ct.calendars", "major": 1 },不帶 exemptions;該選用欄位屬於 ct.action_policy。

IaC 沒有公司時區專用的 line kind。公司時區透過 calendar PUT 設定;column line 使用 spec.sequence,rule line 使用 type: "period_fence"。工作日行事曆是例外:company scope 的 work_calendar line 用來宣告它(ref 為 calendars. 加上 name,也就是行事曆 key;spec 與 PUT 一樣是 name、weekdays 與 exceptions)。它的 plan action 是 create、update 或 noop,在其他 scope 會被拒絕。所有資料列寫入途徑,包括 bulk/import、Command、callback、trigger、Agent、MCP 與 restore/revert,都受目前 schema 的流水號與期間規則約束;錯誤包裝依介面而異。

Last updated on