项目计费

Odoo 里程碑计费为什么不是“完成一个点就开票”:milestone、销售行与 delivered quantity 到底怎么联动

很多人以为 Odoo 的 milestone billing 只是达到里程碑后允许开票,但源码里真正联动的是 project milestone、sale.order.line 和 qty_delivered。本文把这条链路讲清楚。

Odoo 开发 销售 项目
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

很多人理解 Odoo 的 milestone billing,会说成:

  • “里程碑完成了,就可以开票”

这话不算错,但太粗了。

sale_project 源码看,真正发生的事情是:

  1. 项目里程碑 project.milestone 可以绑定一个 sale.order.line
  2. 每个 milestone 可以定义自己占销售行数量的百分比 quantity_percentage
  3. 销售行的 qty_delivered_method = 'milestones'
  4. sale.order.line 的 delivered quantity,会按“已 reached 里程碑的百分比总和 × 订单数量”计算

所以更准确的说法是:

Odoo 里程碑计费不是“达标后允许开票”,而是“里程碑 reached 之后,自动推动销售行的 delivered 数量,从而推动可开票数量”。


第一层:milestone billing 的核心,不在 project,而在 sale_project 扩展

基础 project.milestone 主要关心:

  • 截止日期
  • reached
  • 任务归属

真正把它接到销售计费上的,是 sale_project 给 milestone 加的这些字段:

  • sale_line_id
  • quantity_percentage
  • product_uom_qty

这一步很关键。

因为 Odoo 没有把“里程碑计费”做成一个单独的开票按钮,而是把 milestone 变成销售行已交付数量的一个来源


第二层:为什么 sale_line_id 只能选 milestones 交付法的销售行

源码里 sale_line_id 的 domain 明确要求:

  • qty_delivered_method = 'milestones'

而在产品 / 销售行侧,服务产品如果采用基于 milestone 的交付政策,就会让该销售行走 milestones delivered method。

这说明 Odoo 的设计很严格:

不是任意销售行都能绑 milestone,只有“交付数量由里程碑驱动”的服务行才可以。

也就是说,里程碑不是附加说明,而是交付量计算机制的一部分。


第三层:里程碑完成后,真正变化的不是 invoice state,而是 qty_delivered

这是最值得讲清楚的一点。

sale.order.line 里,源码通过:

  • reached_milestones_ids
  • _prepare_qty_delivered()

来计算已交付数量。

逻辑是:

  • 找出当前销售行下所有 is_reached = True 的 milestone
  • 汇总它们的 quantity_percentage
  • 再乘以 product_uom_qty
  • 得到这条销售行的 qty_delivered

所以 milestone reached 不是直接改“能不能开票”,而是先改:

  • 这条销售行已经交付了多少

后续发票能力,是销售模块基于 delivered quantity 再继续推出来的。

这就把边界讲清楚了。


第四层:为什么 Odoo 用 percentage,而不是“每个里程碑固定金额”

quantity_percentage 的设计说明,Odoo 默认把里程碑计费理解成:

  • 销售行总交付量,被拆成几个 milestone 百分比

比如一条销售行数量是 100:

  • milestone A 占 30%
  • milestone B 占 20%
  • milestone C 占 50%

那么 reached 一个 milestone,本质上是把该销售行的已交付数量推进一部分。

这和很多专业服务业务很接近:

  • 不是每完成一个任务就单独开票
  • 而是按项目阶段分批确认可计费交付

第五层:为什么 quantity_percentage 和 product_uom_qty 互相可推导

sale_project.models.project_milestone 里:

  • _compute_quantity_percentage()
  • _compute_product_uom_qty()

会让“百分比”和“该 milestone 对应的数量”互相换算。

这说明 Odoo 在 UI 层其实兼顾了两类人:

  • 有人习惯按比例思考:这个里程碑占 25%
  • 有人习惯按数量思考:这个阶段交付 40 小时 / 4 个单位

但无论界面怎么填,底层最后都要回到一个统一问题:

这次 milestone reached 后,要把销售行的已交付数量推进多少。


第六层:为什么它适合项目分阶段收费,不适合“一个里程碑一个独立账单产品”

从源码结构看,milestone billing 绑定的是同一条销售行的交付推进。

也就是说,它更适合这种业务:

  • 一项服务卖给客户
  • 但分几个阶段确认交付与开票

而不是这种模式:

  • 每个里程碑本身就是完全独立的产品行

后者不是不能做,而是那种情况下,直接拆成多条 sale order line 往往更直观。

Odoo 这里的设计重点,是“把一条服务销售行切成多个计费节点”,不是“拿里程碑替代产品行”。


新手最容易误解的 4 件事

1. 以为 reached milestone 会直接生成发票

不对。它先推进的是 delivered quantity。

2. 以为 milestone billing 只是 project 模块功能

不对。关键联动在 sale_projectsale.order.line

3. 以为每个 milestone 都必须对应独立销售行

不对。一个销售行可以被多个里程碑按比例推进。

4. 以为 percentage 只是前端展示

不对。它直接参与 qty_delivered 计算。


实战上最该注意什么

1. 销售产品的交付政策一定要选对

如果不是 milestones 驱动,后面整条 delivered 链路就不会按这个逻辑走。

2. 里程碑比例总和要有业务纪律

源码允许逐个累加 reached 百分比,所以实施时最好确保总和设计清楚,不要出现失控的重叠和超配。

3. 报表解释时,要把“reached → delivered → to invoice”这三层讲给业务听

否则业务很容易误以为“系统怎么没直接开票”。


一句话记忆法

Odoo milestone billing 的核心不是“达到里程碑就开票”,而是“里程碑 reached 后推进销售行已交付数量,再由销售开票逻辑继续往下走”。

DISCUSSION

评论区

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