采购收货链路

采购单审批后,收货单、已收数量与发票状态是怎么串起来的

从 purchase.order.button_approve、_create_or_update_picking 到 _prepare_qty_received 和 receipt_status,讲清采购、收货、退货与发票之间的连接。

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

先说结论

采购单在 Odoo 里不是“点一下审批就结束”。

它会把业务拆成几层:

  • 采购单头部确认承诺
  • 收货单负责真正的实物流转
  • 已收数量负责回写采购明细
  • 发票和退货再基于这些数量继续往下走

所以采购的核心不是单据本身,而是 采购单、收货单、库存 move、已收数量、发票状态 之间的联动。


审批按钮后,button_approve() 立即接上收货链

addons/purchase_stock/models/purchase_order.py 里,button_approve() 做完父类逻辑后,会接:

self._create_picking()

这说明审批不是终点,而是采购履约链路的开关。

一旦订单进入 purchase 状态,系统就开始检查:

  • 有没有现成的 picking 可以继续用
  • 有没有需要创建的新收货单
  • 采购行该怎么对应到 stock.move

从这个角度看,采购审批更像是在说:

这张采购单正式成立,下面请把它接入库存执行链。


_create_or_update_picking() 负责把采购行落到收货单上

purchase.order.line 里,真正把收货单组织起来的是 _create_or_update_picking()

这个方法会做几件特别实用的事情:

  1. 如果采购行是 consumable,会检查数量和发票之间的关系
  2. 尝试把已有的 stock.move 重新挂到当前采购行上
  3. 找到合适的 picking,没有就新建
  4. 再创建或更新对应的 stock moves
  5. 最后对这些 moves 依次 _action_confirm()._action_assign()

这里很重要的一点是:

采购行不是直接“生成收货单”,而是把自己绑定到一组库存 move 上。

收货单只是承载这些 move 的容器。


为什么 qty_received 不是简单数 done 数量

很多人以为 qty_received 就是“收货单 done 了多少”。

实际上,Odoo 在 _prepare_qty_received() 里算得更细。

它会:

  • 只看和当前采购行匹配的 move
  • 把 done 的入库数量加进去
  • 把退货、供应商返货、dropship 回流等边界情况单独处理
  • 最后再写回 qty_received

这个设计的意义是:

已收数量不是界面上看见的简单累计,而是采购链路真实结果的归纳值。

所以你会看到,Odoo 对退货场景特别小心,避免重复计算。


qty_received_method = 'stock_moves' 是关键开关

purchase.order.line 里,某些产品会把 qty_received_method 切到 stock_moves

这意味着:

  • 已收数量不再靠人工填
  • 而是直接从库存 move 反推
  • 对于 consu 或需要库存链的行尤其重要

这就是 Odoo 采购和库存联动的核心。

采购行并不自己“记住收了多少”,而是从库存执行结果里推导。

这样做的好处是,采购、收货、退货三条线不会各算各的。


receipt_status 其实是 picking 状态的摘要

purchase.order 里,_compute_receipt_status() 会根据 picking_ids 的状态给订单打摘要:

  • 没有收货单,或者全 cancel:空
  • 全 done/cancel:full
  • 有 done,但不是全部 done:partial
  • 还没 done:pending

这就解释了为什么采购单列表里你看到的是一个“收货状态”,但它本质上来自 pickings。

同样,effective_date 也不是自己算出来的,而是从 done 的 receipt 里取最早日期。


新手最容易混淆的 3 个概念

1)审批不等于收货

审批只是让系统把采购单接入库存链路。

2)收货单不等于已收数量

收货单是单据;qty_received 是结果。

3)发票状态不完全独立

采购行的收货和退货结果,会影响是否该开票、是否要退款、以及是否出现欠收或多收的边界判断。


实战开发建议

如果你在改采购逻辑,建议按这条顺序检查:

  1. button_approve() 有没有进入收货链
  2. picking_ids 有没有被创建或更新
  3. stock.move 有没有正确连到 purchase_line_id
  4. qty_received_method 是不是走 stock_moves
  5. 退货和 dropship 的边界有没有被误算

采购的难点,不是“有没有单”,而是 单据、库存、发票三套状态如何保持一致

DISCUSSION

评论区

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