在 Salesforce Spiff 计算中使用封装
封装是将佣金计算的一部分隔离到一个单独的命名计算字段中。此技术减少了内存使用,简化了调试,提高了可读性,并使您的配置在业务规则更改时更易于维护。
所需的 Edition
| 适用于:Salesforce Classic(并非在所有组织中可用)和 Lightning Experience |
| 适用于:Enterprise、Unlimited 和 Developer Edition |
| 另行付费后适用于:启用了 Web 服务 API 的 Professional Edition |
什么是封装?
当您编写佣金计算时,很容易构建一个结合了每个条件、关系和算术运算的大型表达式。封装采用相反的方法:将逻辑分解为最小的有意义的单元,命名每个单元,并从这些命名的单元组成最终计算。
封装发生在计算字段级别。在结构良好的计划中,每个计算字段执行一个操作。较低级别的计算会输入较高级别的计算,后者会输入最终支出值。
考虑一个计划,该计划向代表支付在当前期间结束的拆分交易的佣金,其中代表在年度经常性收入 (ARR) 中的份额超过阈值。在没有封装的情况下,单个表达式结合了每个条件和每个算术步骤。
Calculated field, without encapsulation:
=if(CloseDate >= BeginningOfPeriod
AND CloseDate <= EndOfPeriod
AND OwnerId == UserId
AND ContractLength_Months__c > 12
AND Split_Percent__c != null
AND (Split_Percent__c * ARR) >= 20000,
Split_Percent__c * ARR * CommissionRate,
0)该表达式难以读取,难以跟踪调试,并且脆弱 — 字段重命名需要查找并编辑引用它的每个计算。
通过封装,每个条件和每个计算值都会成为自己的命名字段。最终支出表达式直接从这些命名字段中读取。
以下示例使用筛选器表达式和计算字段。筛选器表达式返回真或假,并仅使用布尔逻辑 — 它们不支持 if() 语句。计算字段返回值并支持完整表达式语法。两者都受益于封装,但原因不同:计算字段获得了性能优势(引擎在何时评估每个字段并存储结果),而筛选器表达式获得了结构优势(更易于读取、维护和更新)。
With encapsulation:
// Step 1 — Qualify the period (filter expression)
ClosedInPeriod = CloseDate >= BeginningOfPeriod
AND CloseDate <= EndOfPeriod
// Step 2 — Qualify the rep and deal type (filter expression)
ByRepMultiYear = OwnerId == UserId
AND ContractLength_Months__c > 12
// Step 3 — Compute the split amount (calculated field)
SplitARR = Split_Percent__c * ARR
// Step 4 — Qualify the threshold (filter expression)
SplitARREligible = Split_Percent__c != null
AND SplitARR >= 20000
// Step 5 — Final payout (calculated field)
Payout = if(ClosedInPeriod AND ByRepMultiYear AND SplitARREligible,
SplitARR * CommissionRate,
0)对于计算字段,佣金引擎对每个字段评估一次并存储结果 — 后续引用读取存储的值,而不是重新计算逻辑。筛选器表达式不共享此行为,因为筛选器始终作为一个整体运行,而不管它们的结构如何。
Payout字段读取 4 个命名值,而不是内联重新计算相同的算术和日期比较。如果您重命名Split_Percent__c,仅更新SplitARR和SplitARREligible — 支出表达式不受影响。
封装的好处
封装提供了这些优于单片表达式的优势。
- 内存效率。佣金引擎对每个语句运行一次封装计算,并存储结果。后续计算引用读取存储的结果,而不是重新计算逻辑。这种方法对于引用大型数据集或遍历关系的计算尤其有价值。
- 更易于故障排除。当支出不正确时,单独跟踪每个命名计算,以找出逻辑偏离预期的地方。在前面的示例中,分别跟踪
ClosedInPeriod、SplitARR和SplitARREligible,以精确定位排除或包含记录的条件不正确。整体表达式难以检查,因为佣金引擎将其作为一个单元进行评估。 - 简化报告。命名计算字段可用作 Spiff 报表的离散数据点。单片表达式仅产生最终输出。封装字段可让您了解中间值,例如,独立于支出资格报告
SplitARR。 - 提高了可读性。读取
SplitARR * CommissionRate的最终支出计算比等效嵌套表达式更容易理解。命名字段会向未来审查计划的任何人传达意图。 - 可扩展性。当连接器字段更改或更新业务规则时,封装良好的配置需要在一处更改。在前面的示例中,将资格阈值从 20,000 更改为 25,000 意味着仅更新
SplitARREligible。嵌入阈值内联的整体表达式需要查找和编辑包含它的每个筛选器和计算。
将封装应用于关系
对象之间的关系是应用封装价值最高的地方之一。每次计算遍历关系检索字段时,都会消耗内存和处理时间。封装关系调用意味着工作发生一次。
在使用相关对象时,请考虑这些方法。
- 在可用时使用直接路径。如果您需要来自业务机会产品和业务机会拆分的数据,请使用直接的 OppProduct-to-OppSplit 关系,而不是通过业务机会对象路由。较短的路径意味着较少的遍历。
- 使用自引用关系。与其将 OppToAccount 和 AccountToOpps 创建为两个单独的关系,不如创建一个 OppToOpp 关系,其中唯一键是 AccountId。这种方法通过单一的关系定义实现了相同的结果。
- 对相关对象执行计算。不是从相关对象中提取三个字段并将其组合到当前对象的计算中,而是在相关对象上创建一个计算字段来产生您需要的值。然后,通过关系引用该单个字段。检索一个字段的一个关系调用比检索多个字段的一个关系调用代价更低。
例如,不是通过关系单独引用OppSplit.SplitPercent和OppSplit.ARR,并在业务机会计算中将它们相乘,而是在 OppSplit 对象上创建SplitARR计算字段。然后,业务机会计算进行单个关系调用,以读取一个预先计算的值。
Less efficient — two relationship traversals per record:
Payout = OppSplit.SplitPercent * OppSplit.ARR * Rate
More efficient — one relationship traversal per record:
// On OppSplit object:
SplitARR = SplitPercent * ARR
// On Opportunity object:
Payout = OppSplit.SplitARR * Rate使用捕获跟踪应用封装
封装和跟踪一起工作。因为封装的计算是单独的命名字段(字段是离散的且明确标识),所以您可以在字段级别有选择地打开跟踪。对于代表不需要查看的中间计算,以及涉及大型数据集的计算,您可以关闭跟踪。为代表在报表中查看的最终支出值和关键度量打开跟踪。
| 计算类型 | 跟踪推荐 | 原因 |
|---|---|---|
| 关系遍历 | 关闭 | 每个遍历存储额外的数据。关闭关系字段上的跟踪以减少每条语句的内存消耗。 |
| 累积变量 | 关闭 | 累积变量循环记录并存储中间值。跟踪它们会暴露内部逻辑代表不需要,并显著增加内存使用。 |
| 双重筛选器 | 关闭 | 双重筛选器涉及中间汇总计算和 ID 列表。对这些步骤的跟踪会在代表视图中产生干扰,而不会添加有用的上下文。 |
sum()、count()、let()函数 |
关闭 | 这些聚合函数在大型记录集中运行。跟踪存储每个评估记录的数据,这在记录计数高时代价高昂。 |
| YTD 和大数据集计算 | 关闭 | 年初至今或季度至今总计扫描大量历史记录。存储该集中每条记录的跟踪会消耗大量内存。在语句上显示输出值,但阻止引擎存储完整的内部历史记录。 |
| 费率表、定额数据或其他敏感值 | 关闭 | 跟踪使中间值对报表视图和导出中的代表可见。关闭任何引用代表无权查看的数据的字段的跟踪,例如他们不属于的计划的费率或定额。 |
| 最终付款字段和面向代表的度量 | 打开 | 代表依赖跟踪来了解如何计算佣金。对报表度量卡和义务列表中显示的值以及 Comp 管理员报告和导出所需的任何字段打开跟踪。 |
例如,如果支出逻辑包含年初至今的运行总计作为中间步骤,请将该运行总计封装为自己的字段,并关闭跟踪。引用它的Payout字段仍然可以打开跟踪,给代表一个最终的数字,而不会暴露内部累积逻辑。
// YTDCommissions — turn off trace (large dataset, internal only)
YTDCommissions = amounts_from(beginning_of_year(BeginningOfPeriod),
days_ago(BeginningOfPeriod, 1),
plan_name)
// CurrentPeriodPayout — turn on trace (rep-facing)
CurrentPeriodPayout = SplitARR * CommissionRate
// YTDTotal — turn on trace (rep-facing summary metric)
YTDTotal = YTDCommissions + CurrentPeriodPayout
