預約管理系統 — iDempiere 內建的 Google Calendar 風格行事曆。
登入 iDempiere 後,從選單點「預約管理」即可使用。背後資料全部存在 iDempiere 標準表(S_Resource*),計費走標準 O2C 流程,不需要額外登入或外站。
- 📅 FullCalendar 日/週/月/列表檢視
- 🎨 預約狀態顏色(預約中、已確認、已到場、進行中、已完成、未到、取消)
- 👥 多資源管理,依類型分組
- 🔍 搜尋預約、跳轉日期
- ➕ 點擊時段新增預約,選擇服務項目自動計算時長
- ✏️ 點擊事件編輯狀態、時間、備註
- 🚫 衝突檢測(IsSingleAssignment 資源)
- 📋 複製到下週(定期回訪)
- 🔐 Session-based JWT — 誰登入就用誰的權限,不需要 service account
- iDempiere 12
- JDK 17+
- Maven 3.9+
- PostgreSQL
git clone https://github.com/idempiere/idempiere.git
cd idempiere
mvn verify
# 產出:org.idempiere.p2/target/repository/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建置成功後,p2 repository 在:
com.mxp.idempiere.appointments.p2/target/repository/
cd /path/to/idempiere-server
# 使用 iDempiere 標準部署工具(注意:用 update-prd.sh,不是 update-rest-extensions.sh)
./update-prd.sh file:///path/to/idempiere-appointment/com.mxp.idempiere.appointments.p2/target/repository/ com.mxp.idempiere.appointmentssystemctl restart idempiere首次啟動時,外掛會自動:
- 建立 AD_Reference「X_AppointmentStatus」及 7 個狀態值(含顏色)
- 建立 AD_Column「X_AppointmentStatus」和「C_BPartner_ID」在 S_ResourceAssignment 表
- 執行 ALTER TABLE 新增實際 DB 欄位
- 建立 AD_Form「預約管理」
- 建立 AD_Menu 並掛載到選單樹
- 建立角色權限(AD_Form_Access)
- 建立翻譯記錄(AD_Menu_Trl、AD_Form_Trl)
不需要手動執行任何 SQL。
如果 iDempiere 前面有 nginx reverse proxy,需要加上 /appointment/ 路徑的轉發:
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-appointment/
├── pom.xml # Maven root
├── com.mxp.idempiere.appointments.parent/ # Tycho 建置設定
│ └── pom.xml
├── com.mxp.idempiere.appointments/ # 外掛本體
│ ├── META-INF/MANIFEST.MF # OSGi bundle 設定
│ ├── WEB-INF/web.xml # Token servlet mapping
│ ├── OSGI-INF/ # SCR component
│ ├── build.properties
│ ├── src/com/mxp/appointments/
│ │ ├── AppointmentActivator.java # Bundle 啟動 + 自動 migration
│ │ ├── AppointmentForm.java # ZK Form(iframe 容器)
│ │ ├── AppointmentFormController.java # Token 取得 + postMessage 橋接
│ │ └── TokenServlet.java # AD_Session_ID → JWT token
│ ├── web/appointments/
│ │ ├── index.html # SPA 入口
│ │ ├── app.js # FullCalendar + 業務邏輯
│ │ ├── api.js # iDempiere REST API 封裝
│ │ └── style.css # 樣式
│ └── migration/
│ └── 001_appointment_ad_setup.sql # AD 設定(首次啟動自動執行)
├── com.mxp.idempiere.appointments.p2/ # p2 repository 產出
│ ├── pom.xml
│ └── category.xml
├── tests/ # Playwright E2E 測試
│ ├── appointment.spec.ts
│ └── helpers.ts
├── DESIGN.md # 設計規劃文件
└── ACCEPTANCE.md # 驗收規格表
ZK Session(已登入使用者)
└── AppointmentForm(ZK Form)
├── 從 Env.getCtx() 取得 AD_Session_ID
├── POST /appointment/token → JWT token(純 Java 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 加入資源
└── GET /appointment/bpartners 搜尋客戶
- 不需要 service account — 用當前 ZK session 的
AD_Session_ID換發 JWT - Token 由
TokenServlet簽發,使用和 iDempiere REST API 相同的 HMAC-SHA512 密鑰(REST_TOKEN_SECRET) - Token 有效期 7 天(遠超 ZK session 壽命,iframe 關閉即失效)
- 備有 postMessage 橋接機制,可在需要時自動續期
| 代碼 | 名稱 | 顏色 | 佔位 |
|---|---|---|---|
| SCH | 預約中 | 🟡 #FBBF24 | ✅ |
| CFM | 已確認 | 🔵 #3B82F6 | ✅ |
| CHK | 已到場 | 🟢 #10B981 | ✅ |
| INP | 進行中 | 🟠 #F97316 | ✅ |
| DON | 已完成 | ⚪ #9CA3AF | ✅ |
| ABS | 未到 | 🔴 #EF4444 | ❌ |
| CXL | 取消 | ⬜ #D1D5DB | ❌ |
狀態和顏色可在 iDempiere 後台(AD_Ref_List)自行修改,不需要改程式碼。
- Node.js 18+(SPA 建置)
- JDK 17+(Java 編譯)
- Maven 3.9+(Tycho 建置)
- iDempiere 12 原始碼已建置(提供 p2 repository)
idempiere-appointment/
├── spa/ ← React SPA 原始碼
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│ ├── App.tsx
│ ├── api.ts
│ ├── types.ts
│ ├── components/
│ └── hooks/
├── com.mxp.idempiere.appointments/ ← OSGi bundle(Java + 靜態檔)
│ ├── META-INF/MANIFEST.MF
│ ├── WEB-INF/web.xml
│ ├── src/com/mxp/appointments/ ← Java servlets
│ ├── web/appointments/ ← Vite build 產出(不要手動編輯)
│ └── migration/ ← SQL migration
├── com.mxp.idempiere.appointments.parent/ ← Maven parent
├── com.mxp.idempiere.appointments.p2/ ← p2 repository 產出
└── tests/ ← Playwright E2E 測試
cd spa
npm install # 首次或 package.json 變更後
npm run dev # 開發模式(http://localhost:5173,需要 iDempiere 後端)
npm run build # 建置到 ../com.mxp.idempiere.appointments/web/appointments/npm run build 會清空 web/appointments/ 並產出新的 index.html + assets/。
Java 原始碼在 com.mxp.idempiere.appointments/src/。修改後需要重新建置 OSGi bundle。
# 先建置 SPA(如果有修改)
cd spa && npm run build && cd ..
# 建置 Java + 打包 OSGi bundle + 產出 p2 repository
mvn verify -Didempiere.core.repository.url=file:///path/to/iDempiere/org.idempiere.p2/target/repository產出:
com.mxp.idempiere.appointments/target/*.jar— OSGi bundlecom.mxp.idempiere.appointments.p2/target/repository/— p2 update site
正式部署(使用 p2 update site):
cd /path/to/idempiere-server
./update-prd.sh file:///path/to/com.mxp.idempiere.appointments.p2/target/repository/ com.mxp.idempiere.appointments
systemctl restart idempiere開發快速部署(直接複製檔案):
# 在 iDempiere 伺服器上
cd /path/to/idempiere-appointment
git pull
# 只更新 SPA 靜態檔(不需重啟)
cp -r com.mxp.idempiere.appointments/web/appointments/* \
/path/to/idempiere/plugins/com.mxp.idempiere.appointments_*/web/appointments/
# 更新 Java class(需要重啟)
mvn verify -q
cp com.mxp.idempiere.appointments/target/classes/com/mxp/appointments/*.class \
/path/to/idempiere/plugins/com.mxp.idempiere.appointments_*/com/mxp/appointments/
systemctl restart idempiere注意:SPA 靜態檔更新不需要重啟 iDempiere(NoCacheFilter 確保瀏覽器不快取)。Java class 更新需要重啟。
# E2E 測試(需要 iDempiere 後端運行中)
IDEMPIERE_URL=https://your-server.com \
IDEMPIERE_USER=admin \
IDEMPIERE_PASS=admin \
npx playwright test如果 iDempiere 前面有 nginx,需要轉發 /appointment/ 路徑:
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;
}GPL-2.0(與 iDempiere 相同)