先说结论
Odoo 采购单上的 Expected Arrival,并不是“整张 PO 的统一承诺日期”。
在 /home/ubuntu/odoo-temp/addons/purchase/models/purchase_order.py 里,订单级 date_planned 的计算规则非常直接:
取所有非展示行
order_line.date_planned的最小值。
也就是说,采购单头上的预计到货,本质上是:
- 整单里最早要到的一行
- 不是最晚
- 不是平均
- 更不是“用户脑海里这张单的主日期”
这就是为什么一张多行 PO 里,只要有一行交期更早,单头日期就会被拉到更早的那个时间点。
核心链路:订单头不是主数据,采购行才是主语义
源码里有两条很关键的逻辑:
purchase.order._compute_date_planned():单头取最早行日期purchase.order.onchange_date_planned():如果你手动改单头,会把日期写回所有非展示行
这说明 Odoo 对 date_planned 的态度很明确:
- 真实业务语义在行上
- 单头只是一个方便查看和整体编辑的聚合入口
这跟很多实施人员的直觉正好相反。
很多人会把采购单头当成“主日期”,然后默认各行自动继承;而源码的真实世界更像是:
- 各行有自己的计划到货时间
- 单头只是把最早那个提炼出来给你看
为什么新建采购行时,日期会看起来“自动算好了”
在 purchase_order_line.py 里,_get_date_planned() 的逻辑很简单:
- 如果 PO 有
date_order - 就用
date_order + seller.delay - 如果没有
date_order - 就用当前时间 + 供应商 delay
所以采购行默认日期不是凭空出现,而是建立在:
- 下单日期
- 供应商交期 delay
这条链路之上。
于是很多现场问题的根因也就很清晰了:
- 不是系统瞎写了计划日期
- 而是你改了供应商、采购单位、选中的 seller,导致行级默认日期重新推导
最容易被忽略的一点:改单头和改行,语义完全不同
改单头日期
onchange_date_planned() 会把单头日期同步给全部非展示行。
这更像一种“批量覆盖”。
改某一行日期
订单重新计算后,单头会变成所有行里最早的那个。
这更像“局部变化影响聚合结果”。
所以很多用户会觉得 Odoo 的日期“来回跳”,其实它只是同时支持了:
- 自上而下的批量覆盖
- 自下而上的最早值聚合
如果脑子里没有这两层模型,就会觉得它反直觉。
为什么源码还专门写了一个 onchange 覆盖保护
purchase.order.onchange() 里有一段很关键的保护逻辑:
- 当 PO 行变化导致单头
date_planned跟着变时 - 不要反过来把所有行又重写一遍
这段代码的存在,本身就说明 Odoo 开发者也知道这个场景很容易出现“递归污染”:
- 行更新了
- 单头变了
- 单头再回写行
- 行再触发单头变化
如果不做保护,用户改一行交期,可能会意外污染整张单的所有行日期。
所以这不是多余代码,而是在守住“聚合字段”和“批量编辑入口”之间的边界。
供应商通过门户更新交期,为什么不是只改单头
在同一个文件里还能看到:
get_update_url()_update_date_planned_for_lines()get_localized_date_planned()
这说明 Odoo 甚至考虑了供应商或外部用户通过门户去更新计划日期的场景。
而且更新的核心对象仍然是:
- 具体采购行
- 然后系统再记录 activity,并让订单级日期重新聚合
这再次证明:
Odoo 真正认定的“交期事实”,在采购行,不在单头。
业务误区:把单头日期当成承诺日期总开关
很多团队会这么用:
- 改了单头日期,就以为整张单的所有收货、提醒、延期判断都会自然准确
这在单行 PO 时问题不大;但在多行、多品类、多供应商包装条件不同的采购单里,风险很大。
因为真实情况可能是:
- A 行三天到
- B 行十天到
- 单头显示三天
如果团队把这个字段当“整单都会三天到”,后面的催货、承诺、仓库排班都可能被带偏。
排错顺序应该怎么走
如果你遇到“为什么预计到货不对”,建议按这个顺序查:
- 先看单头
date_planned是不是被手工改过 - 逐行看
order_line.date_planned哪一行最早 - 确认行级
selected_seller_id和seller.delay是否变化 - 看是不是改了供应商、单位、数量后触发了行级重算
- 如果是门户更新场景,确认实际被更新的是哪些行
- 再回头评估提醒邮件、late 状态是不是基于最早日期在工作
对实施和二开的提醒
如果业务真的需要“整单承诺到货日”,最好单独建一个字段,不要直接把 Odoo 的 date_planned 当它用。
因为源码语义已经很明确:
- 它是采购行交期的聚合视图
- 聚合规则还是“最早值”
你硬把它当“整单承诺日期”,就会在多行采购里不断碰壁。
一句话记忆法
Odoo 采购单头的 Expected Arrival 不是整单统一到货承诺,而是所有采购行计划日期里的最早值。
DISCUSSION
评论区