很多人第一次做 Odoo 企业版网站预约,都会把问题想成一句话:
只要这个时间段还有资源,客户就应该能约上。
但标准企业版并不是这么粗暴。它真正处理的是四件事同时成立:
- 这个时间段里,哪些资源本身可用;
- 这些资源和它们的 linked resources 组合起来,能不能凑出客户要的人数
asked_capacity; - 前台应该展示“哪几个资源看起来可选”,而不是把所有候选都丢给客户;
- 客户提交表单的那一刻,要把总容量拆成一组
booking_line,确保日历事件、占用容量和后续冲突检查都对得上。
所以企业版 Appointment 解决的不是“有没有空”,而是:
网站前台的可预约结果,必须和后台真正落库的容量占用保持同一套口径。
这也是为什么你会看到前台明明只选了一个时段,后台却可能生成多条 appointment.booking.line。
先抓主链路
1)前台不是先看“资源数”,而是先看“容量是否可能凑够”
在 appointment.type._slots_fill_resources_availability() 里,系统会先遍历某个 slot 下当前可用的资源,再调用 _get_resources_remaining_capacity() 计算每个资源及其联动资源的剩余容量。
这里关键不是“资源还活着”,而是:
- 该资源在这个时段有没有撞 booking;
- 如果开启
manage_capacity,它自己还剩多少容量; - 如果资源之间存在 linked relationship,组合后总容量够不够支撑
asked_capacity。
如果某个资源单独看还能用,但总剩余容量凑不出客户要的 3 人、4 人,它就不会进入最终的 capacity_info。
2)系统会主动挑“最合适的一组资源”,不是简单全选
真正决定“这个 slot 展示哪组资源”的,是 appointment.type._slot_availability_select_best_resources()。
它的策略很有代表性:
- 如果没开
manage_capacity,通常直接选排序后的第一个资源; - 如果存在“刚好等于 asked_capacity 的完美匹配”,优先用这一个,避免浪费大资源;
- 如果单个资源不够,就通过
appointment.resource._get_filtered_possible_capacity_combinations()枚举 linked resource 的组合; - 能找到“正好够用”的组合时,优先用精确组合;找不到才退而求其次,选容量更大的最优组合。
这说明企业版不是按“谁空着就给谁”,而是在做一次轻量的容量匹配。
对业务来说,这很重要:如果你有大会议室 + 小隔间、主设备 + 附属工位、摄影棚 + 灯光位这样的组合资源,不做这一步就会经常出现“大资源被小预约浪费掉”的情况。
3)客户看到的人数上限,和后台真实能分摊的结果不是两套逻辑
很多预约系统的坑在于:前台显示一个人数下拉框,提交时再用另一套规则校验,结果客户经常在最后一步被打回。
企业版 Appointment 尽量避免这个问题。
前台 slot 选择阶段根据容量信息给出可选人数和可选资源;到 appointment_form_submit() 真正提交时,又会重新取一次选中资源在该时段的剩余容量,防止并发下被别人抢走。
如果 resources_remaining_capacity['total_remaining_capacity'] < asked_capacity,系统不会硬创建事件,而是回跳到预约页并给出 failed-resource 状态。这就是典型的“展示一次、落单前再校验一次”的双重防线。
4)真正落库时,asked_capacity 会被拆成多条 booking line
最容易被忽略的是 appointment_form_submit() 最后的 booking_line_values 构造。
当预约类型按资源排程时,代码不是只写一条“总容量=4”的记录,而是按选中的资源逐个分摊:
capacity_reserved代表这次预约从该资源上预留了多少容量;capacity_used则按是否shareable与是否启用manage_capacity决定实际占用口径。
也就是说:
- shareable 资源 + manage_capacity 开启时,占多少就记多少;
- 非 shareable 资源即使只预约了部分容量,也可能直接按整资源占用;
- 这样后续冲突检测、Gantt 展示、资源不可用判断,都会沿用同一套 booking line 数据。
这一步决定了前台“选 3 人”最后到底是落成“2+1”还是“3+1 但只用 3”的占用形态。
这套设计为什么合理
它优先保证“前台承诺”和“后台占用”一致
网站预约最怕的不是没有空位,而是前台说能约、后台落不下来。企业版先在 slot 阶段挑最优资源组合,再在提交时重新核验,最后把容量拆成 booking lines。三段逻辑虽然复杂,但目标很统一:同一个预约不能在不同阶段换规则。
它兼容“可共享资源”和“独占资源”混搭
如果所有资源都只是“一个房间一次一单”,系统根本不需要 capacity_reserved / capacity_used 这种细粒度结构。企业版之所以这么做,是因为它要同时兼容:
- 一张桌子 4 个座位,可拆给 1~4 人;
- 一台设备一旦被占用就整台锁死;
- 主资源和附属资源必须联动;
- 一个预约跨多资源共同完成。
这就是它必须引入组合容量和 booking line 分摊的根本原因。
实战上最容易踩的几个坑
坑 1:只给资源设置 capacity,却没想清楚 shareable
很多实施会把容量填成 4、8、12,以为这样前台人数就自动合理。实际上如果 shareable 没开或业务上不该开,系统可能仍按整资源占用。最后看起来像“明明还剩 2 个名额,为什么下一个客户还是约不上”。
坑 2:linked resources 配错后,前台会出现“看得见、选不上”的错觉
资源组合能力来自 _get_possible_capacity_combinations()。如果主资源和附属资源关系没配全,前台可能能看到某些 slot,但人数一加大就突然消失,团队会误以为是时区或缓存问题,实际上是组合容量根本没算出来。
坑 3:自定义前端只改展示,不复用提交校验
有些项目会魔改预约页,把资源列表和人数控件做得很花。但如果只改前台展示,不保留 appointment_form_submit() 那套并发校验与失败回跳,最终会把“最后一步失败”问题重新引回来。
给实施和二开的建议
- 先画资源关系图,再配 Appointment。 先分清哪些资源是独占、哪些可共享、哪些要联动,不要直接在表单里边试边猜。
- 把
asked_capacity当业务变量,不要当 UI 变量。 它贯穿 slot 计算、最终校验和 booking line 落库,不只是前台一个人数下拉框。 - 二开时优先复用 booking line 结构。 不要另外造一套“资源占用表”,否则后续冲突判断会分裂。
- 压测并发抢位。 特别是热门培训、摄影棚、面谈室场景,要验证提交瞬间的二次校验是否符合预期。
一句话总结
Odoo 企业版网站预约不是“这个时段有空资源就能约”。
它的真实设计是:先根据 shareable / linked resource / asked_capacity 算出最合适的资源组合,再在提交时重做容量校验,并把结果拆成可追踪的 booking lines。
你理解了这条链,才能真正解释为什么同一个 slot 对 1 人可约、对 4 人却不可约;也才能在做企业级预约项目时,把“资源管理”而不是“时间选择”设计对。
DISCUSSION
评论区