很多人看到 Odoo 企业版网站租赁页,会天然把“可租”理解成库存问题:
- 仓里有货,就能租;
- 仓里没货,就不能租。
但 website_sale_renting_planning 的实现不是这个思路。它把 网站日期选择、租赁订单、资源排班、员工日历 几层信息折到一起,最后才投影成前台的 availability。也就是说,前端看到的“这段时间还能租几个”,并不是单纯算库存,而是算:
在这段时间里,有多少资源既没被已有租单占掉,又符合排班/工作日历约束。
这就是它为什么是一个跨模块链路题,而不是“库存显示优化”。
主要参考:
enterprise/website_sale_renting_planning/controllers/main.pyenterprise/website_sale_renting/models/sale_order.pyenterprise/sale_renting_planning/models/planning_slot.py
一、先说结论:网站上的 availability 是“投影值”,不是底层唯一真相
网站租赁页对访客展示的是一个相对友好的结果:某个产品在某段时间内还能不能租、还能租多少。
但后端并不会把这个结果直接存成一个长期字段,而是在请求时按当前日期区间动态计算:
- 先看产品是不是
service + rent_ok + planning_enabled; - 再找这个产品背后的
planning_role与可用resource; - 再把已有
planning.slot、员工日历、请假/停工区间折成 unavailable intervals; - 最后才把这些 unavailable 区间映射成网站端可显示的 availability 段。
所以它不是“库存字段透传到网站”,而是 网站 → 排班 → 资源 → 日历 的一条投影链。
二、入口在网站控制器,但真正决定数量的是 planning 资源层
website_sale_renting_planning/controllers/main.py 覆盖了 renting_product_availabilities()。
这里有个关键判断:只有当产品满足以下条件时,才会走资源排班版逻辑:
type == 'service'rent_okplanning_enabledplanning_role_id.sync_shift_rental
这说明一个重要边界:
- 普通租赁产品 可以按库存/租出数量去算;
- 启用了排班同步的服务型租赁,则必须把资源可用性也算进去。
这一步实际上是在前台请求和后端资源模型之间插了一道门:不是所有租赁都进入排班世界,只有明确配置成“排班驱动租赁”的产品才会。
三、已有排班槽位不是“背景数据”,而是网站 availability 的直接约束
控制器里会先取出相关 planning.slot,并按 resource_id 分组,再把每个资源已有的占用区间整理成 Intervals()。
这意味着网站前台看到的 availability,不只是“已存在销售订单”的结果,还会受到 已经排进去但未必显式展示在网站上的 shift 影响。
换句话说:
- 销售单只是业务承诺;
planning.slot才是资源真正被占掉的时间事实;- 网站 availability 必须以这个事实为基础投影。
这就是为什么它不是“只看 sale.order 的租期”。
四、员工/资源日历也会反向压缩网站可租区间
更有意思的是,这个控制器不只看已存在的 planning.slot,还会继续看 human_resources 的 calendar_id。
如果某天资源本来就不上班,或者有 leave interval,系统会把这些区间也并进 unavailable intervals。
所以它不是说:
- 只要这个资源没被租出去,就算可用。
而是说:
- 这个资源要同时满足:
- 没被其他 slot 占用;
- 在工作日历里可工作;
- 没处于 leave / unavailable 时间段;
- 才会被算进网站 availability。
这一步直接把 HR/资源日历语义 引进了网站租赁页。对于用户来说只是“今天还能订吗”,但源码层面其实是“资源今天有没有资格被算作可服务容量”。
五、为什么不能只看 qty_available
website_sale_renting/models/sale_order.py 里的 _get_cart_and_free_qty() 已经说明:租赁商品的可下单数量不是普通库存逻辑。
它会考虑:
- 当前订单本身在同时间段里已经租了多少;
- 其他订单在相同区间里占了多少;
- 准备时间
preparation_time; qty_in_rent与期间内的最小可用量。
而到了 website_sale_renting_planning,还要再叠一层“资源排班”约束。也就是说,网站租赁产品在企业版里至少有两层语义:
- 库存/租赁量语义:这件东西在这段时间物理上够不够;
- 资源/排班语义:有没有合适资源在这段时间提供它。
如果只看 qty_available,你会得到一个过于乐观的答案。
六、排班侧回写租赁订单,前台 availability 也会因此变化
sale_renting_planning/models/planning_slot.py 里,slot 在 write() 时如果改了开始/结束时间,会反向更新租赁订单的:
rental_start_daterental_return_date
这意味着网站 availability 不是一个只读投影,它的底层事实会被排班修改反向改变。
链路是这样的:
- 网站最初创建租赁语境;
- 排班槽位承接这个订单;
- 后台有人改 slot;
- 租赁订单时间边界跟着变;
- 下一次网站 availability 再计算时,结果也会变。
这就是一个完整的双向链路:
网站选期 → 订单/排班落地 → 排班改期 → 订单边界回写 → 网站 availability 重投影
七、新手最容易误解的 4 个点
1)以为网站上显示“available”就一定能下单
不是。那只是当前区间、当前资源、当前日历条件下的投影结果,结账前仍可能被进一步校验。
2)以为排班只是交付阶段的事
也不是。只要产品启用了 planning_enabled + sync_shift_rental,排班资源从网站选期那一刻就已经参与决策。
3)以为人力资源日历和网站租赁无关
恰恰相反。员工不上班、资源休假、日历不工作,都会直接压缩网站端看到的可租区间。
4)以为改 slot 只影响后台
不对。改 slot 会回写租赁订单时间边界,进而影响下一轮网站 availability 计算。
八、实战建议
- 不要把服务型租赁和纯库存型租赁混着理解。前者受资源排班约束,后者偏库存约束。
- 如果前台 availability 看起来“偏少”,优先查资源日历和 leave,而不只是查库存。
- 如果后台改过 planning slot,要意识到它可能已经悄悄改变了网站前台的可租判断。
- 给业务同事讲清“网站 availability 是投影,不是承诺本身”,避免把前台提示当成最终合同口径。
九、结论
Odoo 企业版网站租赁不是“库存够就能下单”。一旦接上 website_sale_renting_planning,网站看到的 availability 就变成一份跨模块投影:前台日期选择只是入口,真正决定结果的是租赁订单、排班槽位、资源日历和不可用区间的联合计算。它值钱的地方,不在于多显示一个数量,而在于把“可卖”和“可服务”这两件事在网站层面提前对齐。
DISCUSSION
评论区