做线上预约的人最容易有一个直觉:客户选好时段、付完钱,系统立刻生成会议就行。但企业版 appointment_account_payment 与 website_appointment_sale 明确把这条链拆成了预约暂存和最终成会两个阶段。
关键源码:
enterprise/appointment_account_payment/models/calendar_booking.pyenterprise/appointment_account_payment/controllers/appointment.pyenterprise/appointment_account_payment/controllers/payment.pyenterprise/website_appointment_sale/models/sale_order.pyenterprise/website_appointment_sale/models/calendar_booking.py
一、为什么付费预约先建 calendar.booking,而不是直接建 calendar.event
控制器 _handle_appointment_form_submission() 写得很直白:如果 appointment type 有 payment step,并且产品需要收费或服务追踪,系统会先创建 calendar.booking。
原因很现实:
- 客户可能支付失败;
- 支付可能处于 pending;
- 支付前后时间槽可能被别人抢走;
- 如果过早建
calendar.event,就会把未确认预约同步到日历、通知和后续业务对象。
所以企业版故意把 booking 做成“待确认的预约壳子”。
二、calendar.booking 真正存了什么
这个模型不是简化版 event,而是完整保留了预约转正所需信息:
appointment_type_idpartner_id、guest_idsstaff_user_idbooking_line_ids(资源 / 容量)appointment_answer_input_idsproduct_idaccount_move_idbooking_token
也就是说,支付链、容量链、问卷答案链、访客链都先挂在 booking 上,等支付成功后再统一转正。
三、为什么支付前后都还要重新检查可用性
calendar.booking._filter_unavailable_bookings() 是这条链里最容易被忽视、也最关键的防呆点。
它会按时间边界切段,再分别检查:
- staff user 在该区间的剩余容量;
- appointment resource 的剩余容量;
- 如果多种 appointment type 混在同一资源里,还要看
manage_capacity边界。
这意味着“前台页面上看起来还有空位”不等于“付款完成时仍然有效”。支付是异步过程,Odoo 必须在最终确认前重验一次。
四、纯支付版与网站销售版有什么区别
纯支付版:先开 draft invoice
_redirect_to_payment() 会调用 _make_invoice_from_booking(),给每个 booking 建一张 account.move,再跳到 /payment/pay。支付落地后,appointment_post_payment() 根据付款结果,决定跳去 booking summary 还是最终 event 页面。
网站销售版:由销售单接管最终确认
安装 website_appointment_sale 后,逻辑更进一步:
- booking 会关联到
sale.order.line; sale.order._check_cart_is_ready_to_be_paid()在支付前再次检查 booking 是否仍可用;sale.order._action_confirm()才调用calendar_booking_ids._make_event_from_paid_booking();- 生成的
calendar_event_id会回写到对应 SOL。
因此网站销售场景里,会议生成与其说由“付款回调”直接触发,不如说由销售确认统一收口。
五、为什么支付成功也不一定马上成会
_make_event_from_paid_booking() 里仍会先筛掉不可用 booking;对冲突项:
- 标记
not_available = True; - 在 invoice 或 sale order 上记日志;
- 不盲目创建 calendar event。
这很重要。Odoo 的优先级不是“只要收了钱就硬塞进日历”,而是先维护排程真实度,再让后续补救流程有据可查。
六、最容易误解的点
1. “booking 就是 event 的别名”
不是。booking 是等待支付 / 等待销售确认的过渡对象;event 才是最终会议事实。
2. “支付页能打开,就说明时段已锁死”
不一定。源码明确保留了支付后再验证的逻辑,因为异步支付期间容量可能变化。
3. “失败场景只会发生在支付失败”
也不对。支付成功后,资源冲突、配置变更、容量不足仍可能导致 booking 无法转成 event。
七、实战建议
- 如果客户卖的是咨询时段、席位或资源型预约,务必解释 booking 和 event 的双阶段模型。
- 支付成功后若客户没立即收到会议,不要先查支付网关,先查 booking collision 日志。
- 网站销售版要重点测试购物车里多个预约并存、同产品多时段重复下单的情况,因为 Odoo 特意避免把它们合并成一条购物车行。
- 任何预约支付项目都应把“支付成功但最终未成会”的补救流程写进运营 SOP。
八、结论
Odoo 企业版付费预约的关键,不是把付款接上,而是在支付、容量和最终会议事实之间保留一个可回退、可重检的 booking 层。这也是它比“表单 + 支付按钮”更可靠的原因。
DISCUSSION
评论区