先说结论
Odoo 供应商账单自动匹配采购单,不是“只认 PO 号”这么简单。
在 /home/ubuntu/odoo-temp/addons/purchase/models/account_invoice.py 里,系统会按一条逐层回退的策略去猜:
- 先找 采购单名称
name是否命中 - 不行再找 供应商参考号
partner_ref是否命中 - 命中后先看 剩余可开票总额 能不能和账单总额对上
- OCR/EDI 场景下,还可能继续做 子集匹配
- 最后实在不行,再尝试“同供应商 + 总额接近”的兜底识别
所以它本质上不是字段直连,而是一套“基于证据强弱逐步推断”的匹配算法。
第一层:它先相信“明确引用”
如果账单里提取到了采购参考号,系统会先去找:
purchase.order.name in po_references
如果没找到,再退一步:
purchase.order.partner_ref in po_references
这点非常关键。
很多实施同学只关注 PO 编号,却忽略了现实里供应商更常在发票上写的是:
- 他们自己的确认号
- 你录在采购单上的 vendor reference
Odoo 正是考虑到这一点,才把 partner_ref 作为第二匹配入口。
第二层:命中采购单后,不代表立刻整单关联
找到候选 PO 只是开始,系统接下来会看一个更硬的东西:
- 这些采购行还剩多少金额可开票
源码里会把每条采购行的可开票金额算出来,然后和账单总额比较。
如果在容差内,才会认定为:
total_match
这意味着 Odoo 关心的不是“你提到了这张采购单”,而是:
这张账单金额,是否真像是在给这些采购行开票。
这也是它比很多简单 ERP 匹配更稳的一点。
第三层:为什么 OCR 和 EDI 行为还不一样
源码明确区分了两类来源:
OCR 场景
文本识别不够稳定,行级结构也未必可信,所以系统更倾向于:
- 先做总额层面的子集求和
- 找到一组采购行,其金额和账单总额接近
EDI 场景
电子单据通常行结构更可靠,所以系统会继续尝试:
- 采购行与账单行逐行匹配
- 找到一部分可以一一对应的行
也就是说,Odoo 并不是“一套算法打天下”,而是按文档可信度调整策略。
这是非常实用的设计。
子集匹配为什么值得单独理解
很多团队会以为自动匹配要么成功、要么失败。
但 Odoo 中间还有两个很实用的状态:
subset_total_matchsubset_match
它们表达的不是“全对上了”,而是:
- 有一部分采购行,足以解释这张账单金额
- 或者有一部分采购行,能和账单中的部分行一一对应
这特别适合:
- 多张 PO 合并开票
- 一张 PO 分批开票
- OCR 漏识别部分行
- EDI 带了杂项费用/附加行
如果没有这层子集匹配,很多现实单据只能人工全重做。
匹配成功后,系统不是“打个标签”,而是会改账单行
这点最容易被低估。
在 _find_and_set_purchase_orders() 里,匹配成功后系统会按不同场景做不同处理:
- 整体替换成采购行
- 只保留已匹配的采购行
- 未匹配采购行数量设为 0
- 把原始电子单据里未归属采购行的内容放到一个
From Electronic Document分节下面
也就是说,自动匹配的结果会真正影响:
- 账单行结构
- 数量
- 税
- 采购行关联关系
它不是“界面智能提示”,而是会动业务数据。
最容易误解的 5 个点
1. 以为系统只认采购单编号
其实还认 partner_ref。
2. 以为识别到引用就一定全单自动挂上
还要过金额一致性检查。
3. 以为自动匹配只做整单匹配
其实还有子集匹配。
4. 以为 OCR 和 EDI 逻辑一样
源码里是分开处理的。
5. 以为匹配只影响显示
实际上它会重写账单行关联和数量。
排错顺序
如果 Vendor Bill 认错采购单,或者认不到,建议按这个顺序排:
- 账单里有没有可提取的采购参考号
- 这些参考号是 PO
name还是partner_ref - 候选采购单是否还处于可开票状态
- 剩余可开票金额与账单总额是否在容差内
- 单据来源是 OCR 还是 EDI
- 有没有部分行被识别、部分行被丢到电子单据分节下
这样查,能快速分清是“引用没识别到”,还是“金额/行匹配没通过”。
一句话记忆法
Odoo Vendor Bill 自动认采购单,本质上是一套先认引用、再验金额、必要时做子集匹配的推断链,而不是简单按 PO 号硬关联。
DISCUSSION
评论区