很多人以为付费预约的逻辑很直白:客户付款,系统立刻建一个 calendar event。可 enterprise/appointment_account_payment/models/calendar_booking.py 与 account_move.py 展示的是一条更谨慎的路径:先生成 booking 暂存,再开票,真正付款后才尝试成会;如果期间可用性发生变化,还要允许 booking 失败并记日志。
一、booking 先占住候选方案,不等于已经成会
calendar_booking.py 里单独存在 booking 模型,就已经说明官方把“预约意图”与“最终会议”拆开了。原因很简单:客户支付、会计过账、资源可用性检查,这三件事未必同一时间完成。
企业场景里最怕的是用户刚下单就看到会议已锁定,但后面支付失败、资源被抢占或配置改变,最终又得人工回滚。
二、付款后才触发 _make_event_from_paid_booking()
account_move.py 里重写 _post(),在发票过账后才调用 posted.calendar_booking_ids._make_event_from_paid_booking()。这一步很关键:业务上的“付款完成”被绑定在会计动作上,而不是绑定在前台支付页面的乐观提示上。
这让付费预约和财务状态保持一致。否则客户以为预约成功、财务却没收到有效凭证,后续纠纷会很难处理。
三、成会前还要再做一次 collision 检查
_make_event_from_paid_booking() 里先对没有 event 的 booking 做 _filter_unavailable_bookings()。后者会按用户和资源两个维度拆时间边界,检查这些 booking 是否真的还能塞进当前可用性里。
这说明系统默认承认一种现实:从用户下单到发票过账这段时间内,排期可能已经变了。哪怕客户已经付钱,也不能盲目建会,否则就会制造双重占用。
四、冲突 booking 不会悄悄消失,而是写回发票日志
_log_booking_collisions() 会在关联发票上 _message_log(),明确指出哪条 booking 因为可用性或配置变化而未被确认。这一点非常企业级:出现问题时,财务和客服需要共享同一份证据,不应只剩一个“系统没建会”的结果。
五、旧 booking 还要定期清理
_gc_calendar_booking() 会删除创建过久或结束时间过久的 booking。原因也很务实:booking 是中间态对象,不应该像正式会议一样长期保留。否则后面查预约失败原因、核对财务与日程时,噪音会越来越大。
实战注意事项
- 把 booking 当中间态:它负责衔接支付与排期,不是正式日历对象。
- 付款确认要以会计过账为准:不要只依赖支付回调页面。
- 允许付款后成会失败:关键是要把失败写清楚,而不是强行成功。
- 定期清理历史 booking:避免中间态对象污染运营视图。
新手误区
- 误以为支付成功就一定能立即建会。
- 误以为预约冲突只会发生在下单前。
- 误以为失败 booking 没必要保留痕迹。
- 误以为 booking 能直接当正式会议用。
主要源码参考
enterprise/appointment_account_payment/models/calendar_booking.pyenterprise/appointment_account_payment/models/account_move.py
DISCUSSION
评论区