采购预算最常见的误解,就是把它想成“采购单金额 > 预算余额”这么直接的一道 if 判断。
account_budget_purchase 明确告诉你不是。采购行先有 analytic_distribution,然后 _compute_analytic_json() 会把它转换成一组结构化记录:每段分摊里既有 rate,也有按根分析计划展开后的列名和值。也就是说,预算匹配的基础并不是一个简单的 analytic account 字段,而是一份能映射到多个分析计划列的 JSON 视图。
接下来 _compute_budget_line_ids() 才会真正去找预算行。这里的条件很严格:预算必须是确认状态、不是 revenue、日期要覆盖采购单日期,而且只有 product_qty - qty_received > 0 时才继续匹配。更关键的是,源码会把当前分摊里没出现的计划列显式补成 False 去查,这意味着“没填某维度”和“填了别的维度”不是一回事。
采购单头上的 _compute_above_budget() 也不是拿整张单金额去撞预算。它先计算 uncommitted_amount,即 price_unit * (product_qty - qty_invoiced);只有单据还没到 purchase/done 完全落稳时,这个未开票敞口才继续影响超预算判断。随后再把这个值叠到已匹配预算行的 committed_amount 上,看是否超过 budget_amount。
这就解释了很多新手困惑:为什么已经部分收货、部分开票后,预算提醒还会变?因为企业版关心的不是“你当时下了多少”,而是“这条采购链还剩多少预算承诺尚未结转”。测试里关于多币种、重叠 account、信用单回冲的场景,本质都是在守这个口径。
action_budget() 也很能说明设计意图。它不是跳去看一张抽象总表,而是把采购单里实际用到的分析账户拆出来,直接带域打开预算报表。也就是说,企业版把采购预算设计成“从采购单追到预算语义”,而不是“从预算看板猜这张采购单算哪一笔”。
实施时最容易踩的坑有三个:把 analytic distribution 配得太随意;忽略日期覆盖条件;以及把 qty_received 和 qty_invoiced 的差异当成同一件事。前者会导致预算行根本匹配不上,后两者则会让团队误判“为什么提醒忽隐忽现”。
结论是:企业版采购预算控制的核心,不是金额本身,而是分析维度、预算期间和未结转承诺三者同时成立时,系统才敢说你真的超了。
DISCUSSION
评论区