先说结论
Odoo 的分包补料不是“给供应商发一张内部调拨”这么简单。
它真正要解决的是三个问题:
- 组件该从哪个仓位送出去
- 送到哪个分包商位置才算库存语义正确
- 分包收货时,系统如何把外部加工结果和内部制造链重新接起来
所以源码里你会同时看到:
- 仓库级 resupply route
property_stock_subcontractor- 公司级
subcontracting_location_id - 收货 move 自动识别 subcontract BOM
- 收货后创建/驱动分包 MO
一句话概括:
分包补料的难点不是“发货”,而是“把库存位置、补料路线和外协制造语义对齐”。
为什么很多项目会觉得“补料跑错位置”
因为实施现场常把三个概念混成一个:
- 供应商地址
- 供应商普通库存位置
- 供应商分包专属位置
但 Odoo 在 mrp_subcontracting 里不是这么想的。
在 res.partner 与相关模型上,源码给分包商准备了专门位置语义;在 picking 计算位置时,又会优先取:
- partner 的
property_stock_subcontractor - 如果没配,再回落到 company 的
subcontracting_location_id
也就是说,Odoo 想表达的不是“货去供应商那边就行”,而是:
这批货进入的是‘我司视角下,寄存在这个分包商处的库存位置’。
这就是为什么位置一旦配错,后续所有补料、收货、追溯都会显得别扭。
仓库为什么还要多出一套分包补料路线
在 addons/mrp_subcontracting/models/stock_warehouse.py 里,仓库被扩展了几组关键字段:
subcontracting_to_resupplysubcontracting_route_idsubcontracting_type_idsubcontracting_resupply_type_id
这说明 Odoo 并没有把分包补料塞进普通内部调拨里糊弄过去,而是给它一套专门仓库规则。
更关键的是 _generate_global_route_rules_values():
一条规则:把本仓库存拉到分包商位置
有一条规则会把:
location_src_id = lot_stock_idlocation_dest_id = subcontract_location_id
用于 Resupply Subcontractor 的补料动作。
另一条规则:在按单补料场景下,把分包商位置和生产语义连起来
还有一条全局 route 会把分包商位置与生产位置语义串起来,支撑按单触发的链路。
这说明 Odoo 不是只在做“库存移动”,而是在做:
- 组件送去外协点
- 外协点接住制造语义
- 成品再从外协制造结果回到我方收货链
收货单为什么会反向触发分包制造单
在 mrp_subcontracting/models/stock_move.py 里,_action_confirm() 会扫描满足条件的 move:
- 来源位置是 supplier
- 目的位置不是 supplier
- 存在可匹配的 subcontract BOM
一旦识别成立,系统会:
is_subcontract = True- 把 move 的
location_id改成分包商位置 - 然后调用 picking 的
_subcontracted_produce()
这个动作非常关键。
它表示:
Odoo 不是等你手工再建一张外协 MO,而是把这张收货 move 直接识别成“某个分包制造结果的入口”。
所以分包收货不是链路终点,而是制造回流的触发点。
picking 为什么还要二次修正 destination location
在 stock_picking.py 的 _compute_location_id() 里,如果这是分包 resupply transfer,并且 partner 上配了 property_stock_subcontractor,系统会把 location_dest_id 指向那个供应商分包位置。
这说明 Odoo 对位置语义很执着:
- 单据的作业类型是补料,还不够
- partner 维度的分包位置还要覆盖进来
如果你在项目上看到“同样是补料,有的去公司默认分包位置,有的去供应商专属位置”,这往往不是随机现象,而是配置层级在生效。
为什么回写收货日期还会影响分包 MO
stock_move.py 里还做了一件很多人容易忽略的事:
- 如果分包收货 move 的日期被改
- 系统会把关联分包 MO 的
date_start/date_finished同步写过去
这意味着对 Odoo 来说,分包收货日期不是普通收货字段,而是外协制造完成时点的重要代理值。
所以你在历史修单、回填日期时,实际上可能在改制造侧时间语义。
新手最容易误解的点
1)分包补料不是普通内部调拨
它背后绑定了专门 route、专门 operation type 和分包位置语义。
2)供应商地址不等于分包库存位置
property_stock_subcontractor 才是库存链路真正关心的位置。
3)分包收货不是单纯收成品
收货 move 会被识别、转义,并驱动分包制造链闭环。
4)日期、位置、partner 三者是联动的
你以为只是改收货时间或 partner,实际上可能在改分包制造链路的时间和库存归属。
实施和开发注意点
一,优先设计“位置语义”而不是只设计单据流程
很多项目图上把 PO、送货单、收货单画清楚了,但没有先想明白:
- 分包商在系统里是哪个库存位置
- 默认公司级位置够不够
- 哪些供应商需要自己的专属分包位置
这一步不清,后面 route 再精致也会乱。
二,做二开时不要只盯采购单
分包逻辑真正的主链路大量落在:
- stock rule
- stock move
- picking
- subcontract MO
如果只在采购单按钮上加逻辑,很容易补不全。
三,排错时按这个顺序查
- partner 是否配置
property_stock_subcontractor - company 默认
subcontracting_location_id是否合理 - warehouse 的
subcontracting_to_resupply和 resupply picking type 是否启用 - 收货 move 是否被识别成
is_subcontract _subcontracted_produce()是否真的建出了对应分包 MO
最后总结
Odoo 的分包补料设计,比很多人以为的要“库存化”得多。
它不是把委外加工当成采购备注,而是明确建模为:
- 组件送往分包位置
- 分包位置承接外部制造语义
- 收货单触发制造回流
- 位置、路线、partner 配置共同决定链路是否成立
一句话记住:
分包补料的关键不是把料送出去,而是把料送到“正确的外协库存语义里”。
DISCUSSION
评论区