销售变体

Odoo Sales Grid Entry 为什么不只是“批量填数量”:Variant Matrix 的设计边界讲透

很多团队把 Odoo 的 Sales Grid Entry 当成一个好看的批量录单界面,但官方源码里它真正解决的是多变体商品在大矩阵下的创建、合并、删除和报表展示边界。本文把 Variant Matrix 的设计思想讲透。

销售
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

Odoo 的 Sales Grid Entry,不是一个“更方便填数量”的小功能。

它真正解决的是:

当一个产品有大量变体组合时,销售如何用矩阵方式下单,同时又不破坏 sale.order.line 原本的业务语义。

所以 Variant Matrix 的重点,不只是快,而是:

  • 快速录入
  • 仍然尊重变体逻辑
  • 尽量不破坏已有销售行的业务含义

这篇文章主要参考了哪些源码

主要看的是:

  • /home/ubuntu/odoo-temp/addons/sale_product_matrix/models/sale_order.py
  • /home/ubuntu/odoo-temp/addons/sale_product_matrix/models/product_template.py
  • /home/ubuntu/odoo-temp/addons/sale/wizard/res_config_settings.py

最有意思的一段注释就在 sale_product_matrix/models/sale_order.py

  • 这套 matrix 功能刻意放在 Python / server side 做
  • 原因是前端只加载前几行时,不适合处理很大的矩阵

这段注释其实已经把设计取向讲得很清楚:

它优先保证业务正确性和大矩阵可处理性,而不是追求前端炫酷交互。


为什么销售录单需要“矩阵”,而不是一行一行加

如果你卖的是简单商品,一行一行加确实足够。

但如果你卖的是:

  • 颜色 × 尺码
  • 材质 × 规格
  • 型号 × 配置
  • 两个甚至三个属性组合

销售面对的就不是几个 SKU,而是一整张二维甚至准多维组合表。

这时候逐行录入的问题不是慢而已,而是:

  • 容易漏组合
  • 容易重复录
  • 很难整体看数量结构
  • 客户确认时也看不清变体分布

所以矩阵录单解决的是“组合型商品的下单视图”问题。


Odoo 为什么把它做在服务端

源码里的注释非常诚实:

  • 如果在 JS 里做,大矩阵场景下只加载前面若干行
  • 后面的数据拿不到,改动就不完整
  • 想彻底解决,要 hack 前端框架

于是 Odoo 选择了另一条路:

  • 矩阵本身由服务端生成
  • 前端只是带着结构回传变更后的 cell
  • 服务端再统一应用这些变化

这很“朴素”,但我觉得是对的。

因为销售矩阵这种场景,最怕的是:

  • 看起来改成功了
  • 实际只有一部分 cell 真写进订单

那会比界面不好看危险得多。


一个 cell 被改动后,系统真正做了什么

_apply_grid() 里,系统会对每个 dirty cell 做这些事:

  1. 根据 ptav_ids 找到属性组合
  2. product_template._create_product_variant(combination) 找到或创建对应 variant
  3. 在当前订单行里查找这个 variant 对应的 line
  4. 比较新数量和旧数量
  5. 决定是更新、删除还是新增 sale.order.line

这说明 matrix cell 不是独立数据模型。

它最后还是会落回标准的 sale.order.line

也就是说:

矩阵只是销售录入视图,不是另一套订单存储结构。

这点非常重要。

因为它解释了为什么矩阵功能虽然好用,但不能随便脱离原有业务模型乱搞。


为什么“多条相同变体销售行”会触发报错

源码里有个很关键的判断:

  • 如果同一个变体在订单里已经出现了多条 line
  • 又想通过 matrix 直接改数量
  • 系统会抛 ValidationError

很多人看到这里会嫌 Odoo“保守”。

但我反而觉得这是理性的。

因为当同一变体对应多条销售行时,通常意味着这些行已经带有额外业务语义,比如:

  • 不同折扣
  • 不同交期
  • 不同说明
  • 不同促销来源
  • 不同组合包关系

这时如果 matrix 粗暴地把它们合成一条或随便改一条,等于把原本行级语义抹平了。

所以 Odoo 宁愿报错,也不愿偷偷替你“猜”该怎么合并。

这正是它的设计边界:

矩阵负责批量录入,但不负责消灭业务差异。


为什么数量改成 0 的处理还分状态

_apply_grid() 里,如果某个 cell 的数量被改成 0:

  • 订单还在 draft/sent,可以直接移除对应行
  • 订单不是草稿态,就改成 product_uom_qty = 0.0

这个细节也很说明问题。

因为草稿阶段,销售行还只是意图;确认之后,销售行往往已经跟:

  • 交付
  • 开票
  • 追踪
  • 审计

产生关联。

所以确认后的单据,系统就不再随意物理移除行,而是更保守地保留结构。

这不是麻烦,而是对后续链路负责。


为什么报表里还要保留矩阵视图

get_report_matrixes() 会把订单行重新聚合成 matrix,用于报告展示。

这意味着 Odoo 不只是想让销售录单时方便,还希望:

  • 客户看报价时,也能按矩阵理解变体结构
  • 团队内部打印单据时,也能看见整张组合分布

这很重要。

如果系统只支持“矩阵录入”,最后报价输出却回到几十条零散销售行,客户体验会很割裂。

所以 Sales Grid Entry 的完整价值是:

  • 输入端是矩阵
  • 存储端是标准销售行
  • 输出端还能重新恢复矩阵视图

这才闭环。


新手最容易误解的 4 件事

1. 以为 matrix 是另一套订单模型

不是。它最终还是写回 sale.order.line

2. 以为矩阵应该自动帮你合并所有重复行

如果行上已经承载不同业务语义,强行合并反而危险。

3. 以为前端做矩阵一定更先进

在大数据量和复杂组合下,服务端方案反而更稳。

4. 以为 grid 只是为了内部录单快

实际上它也服务于报价展示和客户理解。


什么时候应该启用它

我会把 Sales Grid Entry 用在这些场景:

  • 典型服饰 / 家具 / 配件变体销售
  • 客户习惯按颜色 × 尺码整体看单
  • 一张单里同模板商品会大量重复出现
  • 销售更关心组合分布,而不是一条条 SKU 明细

如果你的商品本身没有明显矩阵结构,硬开这个功能反而会让流程更重。


一句话记忆法

Sales Grid Entry 不是批量填数量的小工具,而是让“矩阵化录单”与 sale.order.line 的业务语义和平共处。

DISCUSSION

评论区

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