本篇文章更新時間:2026/06/02
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持 。
內容目錄
前言:ERP 裡面為什麼需要預約管理
如果你的業務涉及「時間 × 資源」的排程 — 診所預約、顧問諮詢、場地租借、設備排程、教學課程 — 你一定遇過這個問題:預約資訊和客戶/帳務資料分散在不同系統。
Google Calendar 管預約、Excel 管客戶名單、另一套系統管帳務。每天花時間在三個系統之間來回對照:這個病患今天有預約嗎?上次看診的帳單付了嗎?下週的排程和醫師休假有衝突嗎?
iDempiere 其實已經有一套完整的「資源排程」資料模型(S_Resource 系列表),問題是:沒有一個像樣的 UI 來操作它。原生介面就是一個表格清單,和 Google Calendar 的體驗天差地遠。
所以我寫了這個外掛。
idempiere-appointment 是一個 iDempiere 外掛,在 ERP 內部提供 Google Calendar 風格的行事曆介面。預約資料存在 iDempiere 標準表,計費走標準 O2C 流程(訂單 → 發票 → 收款),不需要額外登入、不需要外站、不需要第三方串接。
這篇文章完整介紹這個外掛的設計思維、功能細節、資料模型與技術架構。
設計哲學:不建新表,善用 iDempiere 既有架構
這個外掛最核心的設計決策是:不建立自己的預約表,而是直接使用 iDempiere 標準的 S_Resource 系列表。
為什麼?因為 iDempiere 已經有一套完善的資源排程資料模型:
S_ResourceType(資源類型)
│ 例:「醫師」「診間」「會議室」「顧問」
│ ├── 工作日設定:OnMonday ~ OnSunday
│ ├── 時段設定:IsTimeSlot, TimeSlotStart / TimeSlotEnd
│ └── 衝突控制:IsSingleAssignment(同時段是否只允許一人)
│
▼
S_Resource(個別資源)
│ 例:「王醫師」「A 診間」「大會議室」
│ ├── S_ResourceType_ID → 歸屬類型
│ ├── IsAvailable → 是否可預約
│ └── 系統自動產生 M_Product(ProductType=R,可計費)
│
▼
S_ResourceAssignment(預約佔位 = 行事曆上的每一個事件)
│ ├── S_Resource_ID → 佔哪個資源
│ ├── AssignDateFrom / AssignDateTo → 時段
│ ├── Name → 顯示文字
│ ├── X_AppointmentStatus → 預約狀態(外掛新增的自訂欄位)
│ └── C_BPartner_ID → 客戶/病患(外掛新增的自訂欄位)
│
S_ResourceUnAvailable(不可用時段)
├── 休假、維修、封鎖時段
└── 行事曆上以灰色顯示
使用標準表的好處:
- 計費整合:S_Resource 會自動產生對應的 M_Product,可以直接建立 C_Order / C_OrderLine,走標準的 O2C 計費流程。不用另外建產品。
- 權限控制:iDempiere 原生的角色權限(AD_Role_OrgAccess)直接適用,不用自己寫權限邏輯。
- 多租戶:REST API 自動依登入者的 AD_Client_ID 過濾資料,不同租戶完全隔離。
- 報表:任何針對 S_ResourceAssignment 的報表工具都可以直接使用。
外掛只新增了兩個自訂欄位(X_AppointmentStatus 和 C_BPartner_ID),首次啟動時透過 migration 自動建立,完全不動核心表結構。
功能概覽
行事曆介面
- 四種檢視:日檢視 / 週檢視 / 月檢視 / 今日列表,一鍵切換。
- 時段範圍:依 S_ResourceType 的 TimeSlotStart / TimeSlotEnd 設定顯示範圍(例如 09:00~18:00),不顯示非營業時段。
- 最小粒度:15 分鐘(slotDuration=00:15:00)。
- 響應式設計(RWD):桌面(≥1024px)、平板(768~1024px)、手機(<768px)三段式自適應。
預約操作
- 點擊新增:點擊行事曆空白時段 → 彈出對話框 → 選擇客戶、資源、服務項目 → 自動計算結束時間 → 儲存。
- 拖拉改時間:直接拖拉事件到新時段,前端自動衝突檢測,無衝突即更新。
- 狀態切換:點擊事件 → 下拉選單切換狀態 → 事件顏色即時更新。
- 複製到下週:一鍵複製預約到下週同時段,適合定期回訪(復健、矯正、定期諮詢)。
- 搜尋預約:輸入客戶姓名快速搜尋,跳轉到該預約的日期。
多資源管理
- 資源面板:左側顯示所有資源,依類型分組(醫師、診間、會議室...)。
- 篩選顯示:勾選/取消勾選資源,行事曆即時顯示/隱藏對應事件。
- 合併預約:一次預約可佔用多個資源(例如:王醫師 + A 診間),系統用 group_id 關聯,行事曆上顯示為一個事件。
- 衝突檢測:IsSingleAssignment 的資源同時段只能有一筆有效預約。取消(CXL)和爽約(ABS)的時段自動釋放。
預約狀態系統
透過 iDempiere 的 AD_Ref_List 定義狀態,每個狀態有對應顏色,可在後台自行修改:
| 代碼 | 名稱 | 顏色 | 佔位 | 說明 |
|---|---|---|---|---|
| SCH | 預約中 | 🟡 #FBBF24 | ✅ | 初始狀態 |
| CFM | 已確認 | 🔵 #3B82F6 | ✅ | 電話/訊息確認後 |
| CHK | 已到場 | 🟢 #10B981 | ✅ | 客戶報到 |
| INP | 進行中 | 🟠 #F97316 | ✅ | 服務進行中 |
| DON | 已完成 | ⚪ #9CA3AF | ✅ | 服務結束 |
| ABS | 未到 | 🔴 #EF4444 | ❌ | 爽約,時段釋放 |
| CXL | 取消 | ⬜ #D1D5DB | ❌ | 主動取消,時段釋放 |
佔位規則:衝突檢測時排除「不佔位」的終結狀態(CXL、ABS)。這些預約的時段自動釋放,可直接被新預約覆蓋。取消不刪除記錄,保留完整歷史。
計費整合(O2C 流程)
預約完成後,可直接從預約詳情建立帳單:
- 點擊「建立帳單」
- 系統自動建立 C_Order + C_OrderLine(帶 S_ResourceAssignment_ID,產品為資源對應的 M_Product)
- 後續走 iDempiere 標準流程:完成訂單(CO)→ 建立發票 → 收款
計費是「按需觸發」的,不強制每筆預約都建立訂單。適合先看診後結帳的場景。
技術架構
整體架構
ZK Session(已登入使用者)
└── AppointmentForm(ZK Form)
├── 從 Env.getCtx() 取得 AD_Session_ID
├── POST /appointment/token → JWT(HMAC-SHA512 簽發)
└── <iframe src="appointments/index.html#token=...">
└── SPA(React + TypeScript + FullCalendar)
├── GET /appointment/init 初始資料(資源、狀態、顏色)
├── GET /appointment/events 預約查詢(按日期範圍)
├── POST /appointment/book 建立預約(原子操作 + 衝突檢測)
├── PUT /appointment/update 更新預約(群組同步)
├── POST /appointment/group-add 加入資源(衝突檢測)
├── DELETE /appointment/group-remove 移除資源
└── GET /appointment/bpartners 搜尋客戶
為什麼用自訂 API 而非純 REST API
iDempiere REST API 適合簡單的單表 CRUD。但預約系統有幾個場景需要自訂 Servlet:
| 場景 | REST API 的問題 | 自訂 API 的好處 |
|---|---|---|
| 建立多資源預約 | 無事務性,中途失敗殘留資料 | 單一事務,全成功或全回滾 |
| 衝突檢測 | 前端拼 OData filter,容易出錯 | Server-side SQL,可靠且安全 |
| 多表 JOIN 查詢 | 每張表各呼叫一次 | 一次查詢回傳組合結果 |
設計上是「自訂 API 處理複雜操作,REST API 處理簡單讀寫」的混合模式。
Token 機制
- 不需要 service account — 用當前 ZK session 的 AD_Session_ID 換發 JWT。
- 使用 HMAC-SHA512 簽發,密鑰來自 AD_SysConfig。
- 備有 postMessage 橋接機制:token 過期時 SPA 向 ZK Form 請求新 token,對使用者完全透明。
衝突檢測策略
前端 JavaScript 負責衝突判斷,不額外呼叫 API:
// 從已載入的行事曆資料中篩選衝突
const conflicts = assignments.filter(a =>
a.S_Resource_ID === targetResourceId &&
a.AssignDateFrom < desiredEnd &&
a.AssignDateTo > desiredStart &&
!['CXL', 'ABS'].includes(a.X_AppointmentStatus)
);
為什麼不用 DB 層級鎖?因為預約系統的並發量通常不高(診所、顧問場景),前端檢測已經足夠。如果是高並發場景(如演唱會訂票),才需要 DB 層級的鎖機制。
技術堆疊
| 層面 | 技術 |
|---|---|
| 前端 | React 18 + TypeScript + Vite 5 + @fullcalendar/react 6 |
| 後端 | WAB Servlet(Java)+ iDempiere REST API |
| 認證 | JWT(HMAC-SHA512),橋接 ZK session |
| 資料儲存 | iDempiere 標準表(S_ResourceAssignment)+ 2 個自訂欄位 |
| 建置 | Maven/Tycho + Vite |
| 測試 | Playwright E2E |
通用性設計:不只是診所
雖然設計文件的起點是「診所預約」,但這個外掛完全不綁定特定產業。它的通用性來自:
- 零硬編碼 ID:程式碼中沒有任何特定租戶或產業的 ID。
- 動態設定:資源類型、個別資源、預約狀態、顏色、工作日、時段,全部在 iDempiere 後台設定,不改程式碼。
- REST API 自動隔離:多租戶環境中,同一個外掛部署到不同租戶,各自獨立運作。
適用場景對照
| 場景 | 資源類型範例 | 資源範例 | 狀態可改為 |
|---|---|---|---|
| 診所/牙醫 | 醫師、診間 | 王醫師、A診間 | 預約→報到→看診→完成 |
| 顧問公司 | 顧問 | 資深顧問 A | 排程→確認→執行→完成 |
| 共享空間 | 會議室、工作區 | 大會議室、VIP室 | 預約→使用中→結束 |
| 美容/美髮 | 設計師、座位 | Kelly、3號座 | 預約→到場→服務中→完成 |
| 設備租借 | 設備類型 | 投影機A、攝影棚 | 預約→確認→使用→歸還 |
安裝與部署
系統需求
- iDempiere 12
- JDK 17+
- Maven 3.9+
- PostgreSQL
- Node.js 18+(建置 SPA 時需要)
建置
git clone https://github.com/nczz/idempiere-appointment.git
cd idempiere-appointment
# 指向 iDempiere p2 repository
mvn verify -Didempiere.core.repository.url=file:///path/to/idempiere/org.idempiere.p2/target/repository
部署
cd /path/to/idempiere-server
./update-prd.sh file:///path/to/idempiere-appointment/com.mxp.idempiere.appointments.p2/target/repository/ com.mxp.idempiere.appointments
systemctl restart idempiere
首次啟動自動建立
外掛首次啟動時自動完成所有初始化,不需要手動執行任何 SQL:
- 建立 AD_Reference「X_AppointmentStatus」及 7 個狀態值(含顏色 hex code)
- 建立 AD_Column「X_AppointmentStatus」和「C_BPartner_ID」在 S_ResourceAssignment 表
- 執行 ALTER TABLE 新增實際 DB 欄位
- 建立 AD_Form「預約管理」+ AD_Menu + 角色權限 + 翻譯記錄
Nginx Reverse Proxy 設定(如有需要)
location /appointment/ {
proxy_pass http://idempiere-backend:8080/appointment/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
使用方式
- 登入 iDempiere WebUI
- 在選單找到「Partner Relations」→「預約管理」
- 點擊即開啟行事曆
與上一篇的關聯:看板 + 預約的整合場景
如果你同時安裝了 idempiere-kanban 和 idempiere-appointment,可以實現這樣的工作流程:
- 客戶來電 → 在預約管理中建立預約
- 預約觸發內部任務 → 建立 R_Request(工單),在看板上追蹤準備工作
- 服務完成 → 預約狀態改為「已完成」,建立帳單走 O2C
- 後續跟進 → R_Request 追蹤回訪、客訴、後續服務
兩個外掛各司其職:預約管理負責「時間排程」,看板負責「任務流程」。資料都在 iDempiere 裡面,天然整合。
授權
GPL-2.0 — 和 iDempiere 相同。完全開源,自由使用。
需要導入協助?
無論你是想在診所、顧問公司、共享空間或任何需要排程管理的場景中導入這個系統,或是需要客製化功能(加入簡訊通知、報表、特殊排程邏輯),歡迎透過聯絡我們頁面洽詢,或參閱服務與商品了解顧問與開發服務。
