很多人第一次看到企业版跨公司采购,会以为逻辑很简单:A 公司下采购单,B 公司自动收到一张销售单。
但 sale_purchase_inter_company_rules 的实现比“自动抄一份单据”严格得多。主入口在 button_approve():只有采购单真正审批通过后,系统才会根据供应商 partner 反查对应公司,再判断对方公司是否启用了 intercompany_generate_sales_orders,并且当前采购单不能是 auto_generated,这样才能避免来回互生死循环。
这条链最关键的一步不是创建 SO,而是切身份、切公司、切上下文。源码用 with_user(company_rec.intercompany_user_id).with_context(default_company_id=company_rec.id).with_company(company_rec) 去执行 inter_company_create_sale_order()。这说明触发用户不一定拥有对方公司的销售权限,真正落单的是跨公司专用用户;如果这个用户没配置,或者没有 sale.order 的创建权限,系统会直接抛错,而不是悄悄失败。
进入 inter_company_create_sale_order() 后,Odoo 还会做一个容易被忽略的硬校验:对方公司 partner 的销售价目表币种,必须和当前采购单币种一致。也就是说,跨公司单据不是“先建出来再慢慢修”,而是先守住定价语义一致,防止采购价和销售价在货币层面错位。
_prepare_sale_order_data() 则说明这张 SO 并非采购单字段原样搬运:client_order_ref 用原采购参考,partner_shipping_id 优先取 direct_delivery_address,没有才回退到 partner 默认收货地址,commitment_date 则来自采购侧的 date_planned。这就是为什么直运场景里,地址问题常常比价格问题更早暴露——系统优先保的是履约目的地,而不是页面看起来像不像同一张单。
行数据也不是简单复制。_prepare_sale_order_line_data() 会把采购行数量和价格换算到产品主单位,再带上折扣和 display_type。新手最容易犯的错误,是以为 UoM 只影响显示;但跨公司单据一旦两边单位体系不完全一致,不做换算就会出现“数量差一点、金额差很多”的假异常。
实施时建议按这个顺序排查:先看 partner 能否反查到目标公司;再看 intercompany user 与销售权限;接着核对价目表币种;最后再看 shipping address 和行单位换算。很多所谓“自动生成失灵”,根因都不是按钮没触发,而是企业版刻意在更前面把不安全的单据挡住了。
结论很简单:企业版跨公司采购审批的价值,不在于“自动建一张销售单”,而在于它把审批、权限、公司边界、地址语义和币种一致性绑成了一条受控链路。
DISCUSSION
评论区