先说结论
在 Odoo 里,Optional Products、Accessory Products、Cross-sell 不是三种说法,而是三种不同层级的机制。
它们的差异至少体现在:
- 出现在哪个入口
- 最终是否进入销售订单结构
- 推荐的是模板还是变体
- 面向销售员、客户报价页,还是电商购物车/商品页
如果把这三者都当成“推荐商品”,就会在设计时出现一连串问题:
- 报价单想要客户勾选加购,却错误地去配网站 accessory
- 购物车想自动推荐配件,却误用了报价模板 optional line
- 想做产品页替代推荐,却硬塞进 sale configurator
所以真正该理解的是:
这三者不是同一功能的不同按钮,而是服务于不同销售场景的三条链路。
这篇文章主要参考哪些源码
核心参考文件包括:
/home/ubuntu/odoo-temp/addons/sale/models/product_template.py/home/ubuntu/odoo-temp/addons/sale/controllers/product_configurator.py/home/ubuntu/odoo-temp/addons/sale_management/models/sale_order.py/home/ubuntu/odoo-temp/addons/website_sale/models/product_template.py/home/ubuntu/odoo-temp/addons/website_sale/models/sale_order.py/home/ubuntu/odoo-temp/addons/website_sale/models/website_snippet_filter.py
最关键的源码信号有:
product.template.optional_product_ids在 sale 中定义,帮助报价/配置器做加购建议sale_management的 quotation template 可以把is_optional行带入销售订单结构website_sale里有accessory_product_ids,用于购物车建议配件website_sale里还有alternative_product_ids与动态 snippet filter,负责产品页/站点层 cross-sell 展示
第一层:Optional Products 的核心语义是“报价/配置阶段可加购”
在 sale/models/product_template.py 里,optional_product_ids 的帮助文本写得很清楚:
- 当客户 Add to Cart 时会建议这些产品
- 这是一种 cross-sell 策略
但在 sale 体系里,它真正重要的地方,不只是“建议”,而是它会进入销售配置器逻辑。
sale/controllers/product_configurator.py 里专门有:
sale_product_configurator_get_optional_products(...)
它会根据主产品、属性组合、父组合等上下文,返回一组 optional products 信息。
这说明 Optional Products 在标准设计里更接近:
- 主商品旁边的结构化可加购项
- 与当前商品组合和销售配置器直接联动
它不是简单的“猜你喜欢”。
第二层:Quotation Template 里的 optional line 更进一步,它已经进入订单结构
在 sale_management 里,报价模板切换时会把模板行整体灌入订单。
如果模板行带 is_optional,那它不是前端卡片,而是正式的 sale.order.line 结构,只是语义上属于可选项。
这层非常重要,因为它解释了为什么很多 B2B 场景更应该用 Optional Products / optional lines:
- 销售员需要在报价阶段明确给客户一个加购结构
- 客户在 portal / quotation 页面看到的是与这张报价相关的可选项
- 这些项目可以参与当前报价语境,而不是网站通用推荐
所以 Optional Products 更偏:
- 与当前销售对象强绑定
- 服务于报价成交与方案扩展
第三层:Accessories 的核心语义是“购物车复核时推荐配件”
website_sale/models/product_template.py 里定义了:
accessory_product_ids
帮助文本写得很直接:
- Accessories 会在客户付款前查看购物车时出现
- 这是 cross-sell strategy
而 website_sale/models/sale_order.py 的 _cart_accessories() 也明确写了:
- 根据购物车中商品的 accessory products 来做推荐
这说明 Accessories 的职责边界是:
它属于电商购物车阶段的配件推荐机制。
也就是说,它重点服务的是:
- 客户已经把主商品放进购物车
- 系统此时再提示“你可能还需要这些配件”
这和报价阶段的 optional products 不一样。
Optional 更像“方案组成的一部分”。 Accessories 更像“临门一脚的配件补单”。
第四层:Cross-sell 在 website 里很多时候是页面展示策略,不等于订单结构
website_sale/models/website_snippet_filter.py 里有几类动态 filter:
- accessories
- recently sold with
- alternative products
这些 filter 的共同特点是:
- 面向网站页面展示
- 根据当前商品 / 购物车上下文取推荐商品
- 最终落地成 snippet 或页面推荐区块
这类 cross-sell 更偏站点运营语义。
它回答的问题是:
- 商品页应该展示什么替代品
- 页面区块该展示什么相关商品
- 购物车该出现什么建议内容
它本身并不等价于:
- 一定进入销售订单行
- 一定成为报价中的可选结构
所以把 website snippet cross-sell 和 sale optional products 混在一起,是非常典型的设计错误。
第五层:Optional 推荐的是 product template 语境,Accessories 更接近 website 可售变体
从字段定义上也能看出差异:
optional_product_ids关联的是product.templateaccessory_product_ids在 website_sale 里关联的是product.product
这背后的业务含义很大。
Optional Products
更适合围绕“主商品方案”做模板级推荐,再由配置器决定具体可行组合。
Accessories
更适合在电商上下文里推荐当前网站上可直接加入购物车的具体可售项。
这也是为什么 _cart_accessories() 里还会继续判断:
- 是否已在购物车中
- 是否可 quick add
- 变体组合是否可行
- 是否允许零价销售
Accessories 明显更贴近电商前台成交动作。
第六层:Alternative Products 解决的是“替代选择”,不是加购职责
虽然这篇重点是 Optional 与 Accessories,但 website_sale 同时还有:
alternative_product_ids
帮助文本把它定义成:
- 在商品页建议替代品
- 属于 upsell strategy
这里最容易混淆的是:
- Optional:和主商品一起加购
- Accessories:购物车里补配件
- Alternative Products:给客户另一个可能替代当前商品的选择
从商业动作看,这三者分别对应:
- 加上它
- 顺手带一个
- 换一个更合适/更高阶的
这三种话术在销售策略上根本不是一回事。
新手最容易误解的 6 件事
1. Optional、Accessories、Cross-sell 都是推荐商品,随便选一个就行
不对,它们服务的入口和目标动作不同。
2. 网站购物车推荐应该用报价模板的 optional line
不对,购物车阶段更接近 accessory 机制。
3. Optional Products 一定只是前台展示,不进订单
不对,在报价模板链路里它可以成为正式订单结构的一部分。
4. Accessories 就是所有加购的总称
不是,它在标准源码里有非常明确的购物车场景边界。
5. Alternative Products 和 Optional Products 差不多
不是,一个是替代,一个是加购。
6. Cross-sell 一定意味着订单行联动
不一定,很多 website cross-sell 只是页面展示策略。
实战里我建议这样选
想做 B2B 报价加项
优先看:
- Optional Products
- Quotation Template 的 optional lines
想做电商购物车补件
优先看:
- Accessory Products
_cart_accessories()这条链
想做商品页替代/站点运营推荐
优先看:
- Alternative Products
- website snippet filters
不要混着设计
如果你同时想满足:
- 销售报价加购
- 电商购物车补件
- 商品页替代推荐
那通常应该是三种机制并行,而不是强行让一个字段承担所有职责。
最后一句话
Odoo 里的 Optional Products、Accessories 与 Cross-sell,真正的边界不是名字,而是:
它们分别服务于报价结构、购物车补件和网站页面推荐三个不同动作。
一旦你按动作去理解,而不是按“都是推荐商品”去理解,整个方案设计会清楚很多,也更不容易把前台推荐和订单结构混成一锅。
DISCUSSION
评论区