[iDempiere] 開源預約管理外掛:Google Calendar 風格行事曆,內建 ERP 計費整合

本篇文章更新時間: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 流程)

預約完成後,可直接從預約詳情建立帳單:

  1. 點擊「建立帳單」
  2. 系統自動建立 C_Order + C_OrderLine(帶 S_ResourceAssignment_ID,產品為資源對應的 M_Product)
  3. 後續走 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;
}

使用方式

  1. 登入 iDempiere WebUI
  2. 在選單找到「Partner Relations」→「預約管理」
  3. 點擊即開啟行事曆

與上一篇的關聯:看板 + 預約的整合場景

如果你同時安裝了 idempiere-kanban 和 idempiere-appointment,可以實現這樣的工作流程:

  1. 客戶來電 → 在預約管理中建立預約
  2. 預約觸發內部任務 → 建立 R_Request(工單),在看板上追蹤準備工作
  3. 服務完成 → 預約狀態改為「已完成」,建立帳單走 O2C
  4. 後續跟進 → R_Request 追蹤回訪、客訴、後續服務

兩個外掛各司其職:預約管理負責「時間排程」,看板負責「任務流程」。資料都在 iDempiere 裡面,天然整合。


授權

GPL-2.0 — 和 iDempiere 相同。完全開源,自由使用。


需要導入協助?

無論你是想在診所、顧問公司、共享空間或任何需要排程管理的場景中導入這個系統,或是需要客製化功能(加入簡訊通知、報表、特殊排程邏輯),歡迎透過聯絡我們頁面洽詢,或參閱服務與商品了解顧問與開發服務。


Share:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon