供应链日期传播

Odoo 补货链路里的日期为什么会自己变化:stock.rule 的 delay、延误预警与 deadline 传播

很多人知道 stock.rule 有 lead time,却没真正串起来看:拉式规则创建 move 时会把计划日期往前推,push 规则又会在下一步把日期往后带,前序 move 延误后还会触发 delay alert 和 deadline 变更日志。本文结合 stock_rule.py 与 stock_move.py 讲清这条日期传播链。

库存 采购
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

结论先行

在 Odoo 的库存 / 补货链路里,日期不是“单据上填一次就一路原样传下去”。

至少有三套机制会一起影响它:

  1. stock.rule.delay:在 pull / pull_push 规则创建 move 时,把计划日期往前推
  2. push 规则的新 move 日期:按上一条 move 的日期再往后加 delay
  3. 前序 move 延误感知:如果上游 move 日期晚于当前 move,系统会算出 delay_alert_date,并在相关单据上记录“deadline 因前序延误被更新”的日志

所以很多人看到“交期怎么自己变了”“为什么这里有延误预警”“为什么链路后段的日期会跟前段联动”,答案往往就在 stock_rule.pystock_move.py 这条日期传播链里。


第一层:pull 规则创建 move 时,计划日期会先被往前扣掉 delay

/home/ubuntu/odoo-temp/addons/stock/models/stock_rule.py_get_stock_move_values() 里,核心逻辑很直白:

date_scheduled = fields.Datetime.to_string(
    fields.Datetime.from_string(values['date_planned']) - relativedelta(days=self.delay or 0)
)

如果还带 date_deadline,也会一起减:

date_deadline = values.get('date_deadline') and (
    fields.Datetime.to_datetime(values['date_deadline']) - relativedelta(days=self.delay or 0)
) or False

翻译成人话就是:

下游需要在某天到货,那上游这条 move 就得更早启动,所以规则 lead time 会把计划日期往前推。

这也是补货链路里很常见的“倒排日期”思路。


第二层:push 规则不是前推,而是基于上一条 move 日期往后算

同一个文件里,push 规则走的是另一套逻辑:

return fields.Datetime.to_string(move.date + relativedelta(days=self.delay))

也就是说:

  • pull 更像“为了赶上下游目标时间,先把当前动作排早一点”
  • push 更像“上一段什么时候完成,下一段就在那个基础上再加几天”

这两者虽然都叫 delay / lead time,但语义方向不一样。

如果把它们混为一谈,就很容易在多步仓路线里看不懂为什么有的日期往前倒、有的日期往后顺。


第三层:propagate_cancel 不只是取消链,也会影响你怎么理解整条补货依赖

_get_stock_move_values()_push_prepare_move_copy_values() 里,都会把:

'propagate_cancel': self.propagate_cancel,

带到新建 move 上。

这说明 rule 上的 propagate_cancel 不是 UI 装饰,而是链路依赖的一部分:

  • 上游取消时,下游是不是跟着取消
  • 这条 move 链是不是仍然保持紧耦合

它虽然不直接“计算日期”,但会影响你如何理解日期变化是否还值得继续传播。

因为一条已经不再有效的依赖链,后续延误和 deadline 联动的业务意义也就不同了。


第四层:系统怎么判断“你延误了”

/home/ubuntu/odoo-temp/addons/stock/models/stock_move.py 里,_compute_delay_alert_date() 的逻辑非常朴素:

prev_moves = move.move_orig_ids.filtered(lambda m: m.state not in ('done', 'cancel') and m.date)
prev_max_date = max(prev_moves.mapped('date'), default=False)
if prev_max_date and prev_max_date > move.date:
    move.delay_alert_date = prev_max_date

意思就是:

  • 找前序 move
  • 取最晚的那个日期
  • 如果它比当前 move 自己计划的日期还晚
  • 那当前 move 就应该亮起延误预警

这个判断很像现实里的依赖任务管理:

前一步最晚都得那天才能完成,那你这一步还写更早的日期,本质上就是已经晚了。


第五层:延误不只是算个字段,还会在业务单据上留痕

stock_move.py 里还有一个很实战的方法:_propagate_date_log_note()

它会在相关文档上发日志:

msg = _("The deadline has been automatically updated due to a delay on %s.", ...)

也就是说,Odoo 不是只在 move 上悄悄改时间,而是会尽量把“为什么 deadline 变了”这件事留出一条可追溯痕迹。

这对实际项目特别重要,因为业务方最怕的是:

  • 交期变了
  • 但没人知道为什么变
  • 最后只能怀疑系统乱算

有了这条日志,至少链路解释是可追溯的。


第六层:为什么这件事容易被误解

因为开发者常把下面几种时间混在一起:

  • 销售承诺时间
  • 补货需求时间 date_planned
  • stock.rule 的 delay
  • 供应商交期
  • move 的 date
  • move 的 date_deadline
  • 延误预警 delay_alert_date

它们不是同一个概念,只是在同一条链上互相换算、互相影响。

一旦你只盯某一个表单字段,很容易觉得“系统在偷偷改时间”;但如果回到源码链路,其实大多都有明确的推导关系。


实战排查建议

如果你在项目里遇到日期异常,建议按这个顺序查:

  1. 这条 move 是 pull 生成的,还是 push 生成的
  2. 相关 rule 的 delay 配了多少
  3. 上游传进来的 date_planned / date_deadline 是什么
  4. 这条 move 有没有 move_orig_ids
  5. 上游 move 的最晚日期是否晚于当前 move 日期
  6. 文档 chatter 里有没有自动写入的 deadline 变更说明

这样查,一般比只盯表单界面高效得多。


总结

Odoo 的供应链日期传播,本质上是一条“依赖链上的时间换算规则”:

  • pull 规则为了满足下游时间,先把当前动作往前排
  • push 规则在上一动作日期基础上往后推
  • 上游延误会被当前 move 感知成 delay_alert_date
  • 关键 deadline 变更还会留日志解释来源

理解了这条链,项目里很多“交期为什么自己变了”的问题,就不再像黑盒,而会变成一条能顺着源码讲清楚的业务逻辑。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。