@quotivity/cpq-inventory-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/assets/report/assets/index-CKPi--yh.css +1 -0
  2. package/assets/report/assets/index-CqUjsqpS.js +9 -0
  3. package/assets/report/fonts/LexendDeca-VariableFont_wght.ttf +0 -0
  4. package/assets/report/index.html +14 -0
  5. package/lib/banner.d.ts +7 -0
  6. package/lib/banner.js +13 -0
  7. package/lib/classify/advancedCondition.d.ts +34 -0
  8. package/lib/classify/advancedCondition.js +112 -0
  9. package/lib/classify/formula.d.ts +29 -0
  10. package/lib/classify/formula.js +125 -0
  11. package/lib/classify/index.d.ts +80 -0
  12. package/lib/classify/index.js +662 -0
  13. package/lib/classify/rules.d.ts +31 -0
  14. package/lib/classify/rules.js +868 -0
  15. package/lib/config/outbound.d.ts +31 -0
  16. package/lib/config/outbound.js +35 -0
  17. package/lib/connection.d.ts +42 -0
  18. package/lib/connection.js +52 -0
  19. package/lib/extract/index.d.ts +112 -0
  20. package/lib/extract/index.js +373 -0
  21. package/lib/extract/records.d.ts +421 -0
  22. package/lib/extract/records.js +327 -0
  23. package/lib/index.d.ts +30 -0
  24. package/lib/index.js +51 -0
  25. package/lib/liveness.d.ts +19 -0
  26. package/lib/liveness.js +34 -0
  27. package/lib/mapping.d.ts +14 -0
  28. package/lib/mapping.js +272 -0
  29. package/lib/report/assemble.d.ts +16 -0
  30. package/lib/report/assemble.js +499 -0
  31. package/lib/report/printDoc.d.ts +13 -0
  32. package/lib/report/printDoc.js +82 -0
  33. package/lib/report/shareSummary.d.ts +8 -0
  34. package/lib/report/shareSummary.js +54 -0
  35. package/lib/report/types.d.ts +146 -0
  36. package/lib/report/types.js +2 -0
  37. package/lib/server.d.ts +32 -0
  38. package/lib/server.js +185 -0
  39. package/lib/soql.d.ts +50 -0
  40. package/lib/soql.js +76 -0
  41. package/lib/summary.d.ts +14 -0
  42. package/lib/summary.js +143 -0
  43. package/lib/types.d.ts +22 -0
  44. package/lib/types.js +85 -0
  45. package/package.json +35 -0
@@ -0,0 +1,868 @@
1
+ const row = (r) => r;
2
+ export const RULE_ROWS = [
3
+ // ── Catalog ─────────────────────────────────────────────────────────────
4
+ row({
5
+ id: 'cat.products',
6
+ label: 'Active products',
7
+ source: 'Product2',
8
+ discriminator: 'active',
9
+ bucket: 'catalog',
10
+ lands: 'HubSpot product',
11
+ verdict: 'Clear path',
12
+ shape: '1:1',
13
+ mapping: 'catalog.products',
14
+ }),
15
+ row({
16
+ id: 'cat.pricebooks',
17
+ label: 'Price books',
18
+ source: 'Pricebook2',
19
+ discriminator: '—',
20
+ bucket: 'catalog',
21
+ lands: 'Quotivity price book',
22
+ verdict: 'Clear path',
23
+ shape: '1:1',
24
+ mapping: 'catalog.products',
25
+ }),
26
+ row({
27
+ id: 'cat.currencies',
28
+ label: 'Enabled currencies',
29
+ source: 'CurrencyType',
30
+ discriminator: 'more than one enabled',
31
+ bucket: 'catalog',
32
+ lands: 'Price book entry prices per enabled portal currency',
33
+ verdict: 'Clear path',
34
+ shape: '1:1',
35
+ mapping: 'catalog.multicurrency',
36
+ }),
37
+ row({
38
+ id: 'cat.dcrStatic',
39
+ label: 'Dated conversion rates, one period per currency',
40
+ source: 'DatedConversionRate',
41
+ discriminator: 'one rate period per currency',
42
+ bucket: 'catalog',
43
+ lands: 'A manually set exchange rate — equivalent to a static rate',
44
+ verdict: 'Clear path',
45
+ shape: '1:1',
46
+ unit: 'currencies',
47
+ mapping: 'catalog.dcrStatic',
48
+ }),
49
+ row({
50
+ id: 'cat.dcrTable',
51
+ label: 'Dated conversion rates, maintained rate table',
52
+ source: 'DatedConversionRate',
53
+ discriminator: 'multiple maintained rate periods per currency',
54
+ bucket: 'catalog',
55
+ lands: 'HubSpot holds one current rate per currency. There is no way to author a rate for a named past or future period',
56
+ verdict: 'Degraded',
57
+ unit: 'currencies',
58
+ mapping: 'catalog.dcrTable',
59
+ }),
60
+ // ── Configuration ───────────────────────────────────────────────────────
61
+ row({
62
+ id: 'cfg.features',
63
+ label: 'Bundle options (CPQ product features), optional selection',
64
+ source: 'SBQQ__ProductFeature__c',
65
+ discriminator: 'no selection minimum',
66
+ bucket: 'configuration',
67
+ lands: 'Bundle option',
68
+ verdict: 'Clear path',
69
+ shape: '1:1',
70
+ mapping: 'configuration.bundles',
71
+ }),
72
+ row({
73
+ id: 'cfg.featurePickOne',
74
+ label: 'Features — pick one',
75
+ source: 'SBQQ__ProductFeature__c',
76
+ discriminator: 'MinOptionCount = 1, MaxOptionCount = 1',
77
+ bucket: 'configuration',
78
+ lands: 'Included option with a default product — "Pick one"',
79
+ verdict: 'Clear path',
80
+ shape: '1:1',
81
+ mapping: 'configuration.minMax',
82
+ }),
83
+ row({
84
+ id: 'cfg.featurePickMany',
85
+ label: 'Features — pick one or more',
86
+ source: 'SBQQ__ProductFeature__c',
87
+ discriminator: 'MinOptionCount = 1, MaxOptionCount blank',
88
+ bucket: 'configuration',
89
+ lands: 'Included option, allow multiple — "Pick one or more"',
90
+ verdict: 'Clear path',
91
+ shape: '1:1',
92
+ mapping: 'configuration.minMax',
93
+ }),
94
+ row({
95
+ id: 'cfg.featureCap',
96
+ label: 'Features with a numeric option cap',
97
+ source: 'SBQQ__ProductFeature__c',
98
+ discriminator: 'MinOptionCount > 1, or MaxOptionCount is a finite number > 1',
99
+ bucket: 'configuration',
100
+ lands: 'Allow-multiple, plus roll-up + Block to enforce the count',
101
+ verdict: 'Degraded',
102
+ mapping: 'configuration.minMax',
103
+ }),
104
+ row({
105
+ id: 'cfg.optionsPlain',
106
+ label: 'Option products, not configurable',
107
+ source: 'SBQQ__ProductOption__c',
108
+ discriminator: 'OptionalSKU is not configurable',
109
+ bucket: 'configuration',
110
+ lands: 'Option product',
111
+ verdict: 'Clear path',
112
+ shape: '1:1',
113
+ mapping: 'configuration.bundles',
114
+ }),
115
+ row({
116
+ id: 'cfg.optionsNested',
117
+ label: 'Option products that are themselves bundles',
118
+ source: 'SBQQ__ProductOption__c',
119
+ discriminator: 'OptionalSKU is itself configurable',
120
+ bucket: 'configuration',
121
+ lands: '—',
122
+ verdict: 'No target',
123
+ mapping: 'configuration.nested',
124
+ }),
125
+ row({
126
+ id: 'cfg.configRules',
127
+ label: 'Configuration rules',
128
+ source: 'SBQQ__ConfigurationRule__c',
129
+ discriminator: '—',
130
+ bucket: 'configuration',
131
+ lands: 'Absorbed — scope is implicit, since Compatibility Rules execute within the bundle',
132
+ verdict: 'Clear path',
133
+ shape: '1:1',
134
+ mapping: 'configuration.bundles',
135
+ }),
136
+ row({
137
+ id: 'cfg.constraintExclusion',
138
+ label: 'Option constraints — exclusion',
139
+ source: 'SBQQ__OptionConstraint__c',
140
+ discriminator: 'Type = Exclusion',
141
+ bucket: 'configuration',
142
+ lands: 'Compatibility Rule (Incompatible)',
143
+ verdict: 'Clear path',
144
+ shape: '1:1',
145
+ mapping: 'configuration.exclusion',
146
+ }),
147
+ row({
148
+ id: 'cfg.constraintDepSatisfied',
149
+ label: 'Option constraints — dependency, already satisfied',
150
+ source: 'SBQQ__OptionConstraint__c',
151
+ discriminator: 'Type = Dependency, required option is on an included option with a default',
152
+ bucket: 'configuration',
153
+ lands: 'Already satisfied — the option is always present',
154
+ verdict: 'Clear path',
155
+ shape: '1:1',
156
+ mapping: 'configuration.dependency',
157
+ }),
158
+ row({
159
+ id: 'cfg.constraintDepOther',
160
+ label: 'Option constraints — dependency, any other shape',
161
+ source: 'SBQQ__OptionConstraint__c',
162
+ discriminator: 'Type = Dependency, any other shape',
163
+ bucket: 'configuration',
164
+ lands: 'Add add-on bundle product, or Block',
165
+ verdict: 'Degraded',
166
+ mapping: 'configuration.dependency',
167
+ }),
168
+ row({
169
+ id: 'cfg.selectionRules',
170
+ label: 'Selection rules',
171
+ source: 'SBQQ__ProductRule__c',
172
+ discriminator: 'Type = Selection',
173
+ bucket: 'configuration',
174
+ lands: 'Add add-on bundle product / Swap bundle product',
175
+ verdict: 'Clear path',
176
+ shape: '1:1',
177
+ mapping: 'configuration.bundles',
178
+ }),
179
+ row({
180
+ id: 'cfg.filterRules',
181
+ label: 'Filter rules — dynamic option sets',
182
+ source: 'SBQQ__ProductRule__c',
183
+ discriminator: 'Type = Filter',
184
+ bucket: 'configuration',
185
+ lands: '—',
186
+ verdict: 'No target',
187
+ mapping: 'configuration.filter',
188
+ }),
189
+ row({
190
+ id: 'cfg.actionSwap',
191
+ label: 'Remove or disable actions with a replacement',
192
+ source: 'SBQQ__ProductAction__c',
193
+ discriminator: 'Type contains Remove or Disable, replacement exists in the option',
194
+ bucket: 'configuration',
195
+ lands: 'Swap bundle product',
196
+ verdict: 'Clear path',
197
+ shape: '1:1',
198
+ mapping: 'configuration.bundles',
199
+ }),
200
+ row({
201
+ id: 'cfg.actionRemoveNoReplacement',
202
+ label: 'Remove or disable actions with no replacement',
203
+ source: 'SBQQ__ProductAction__c',
204
+ discriminator: 'Type contains Remove or Disable, no replacement',
205
+ bucket: 'configuration',
206
+ lands: '—',
207
+ verdict: 'No target',
208
+ mapping: 'configuration.nested',
209
+ }),
210
+ row({
211
+ id: 'cfg.lookupProductRule',
212
+ label: 'Lookup queries on a product rule',
213
+ source: 'SBQQ__LookupQuery__c',
214
+ discriminator: 'on a product rule',
215
+ bucket: 'configuration',
216
+ crossBucket: 'code',
217
+ lands: 'Depends on the custom object behind them, which the tool cannot read',
218
+ verdict: 'Further review',
219
+ review: 'Unread',
220
+ mapping: 'configuration.lookup',
221
+ }),
222
+ row({
223
+ id: 'cfg.priceRuleConfigurator',
224
+ label: 'Price rules evaluated in the configurator',
225
+ source: 'SBQQ__PriceRule__c',
226
+ discriminator: 'TargetObject = Configurator',
227
+ bucket: 'configuration',
228
+ lands: 'Compatibility Rules and Dynamic Property Sets evaluate in the configurator; pricing does not resolve until the bundle lands on the quote',
229
+ verdict: 'Degraded',
230
+ mapping: 'configuration.dependency',
231
+ }),
232
+ // ── Discovery & capture ─────────────────────────────────────────────────
233
+ row({
234
+ id: 'disc.attributesGuided',
235
+ label: 'Attributes not applied to product options',
236
+ source: 'SBQQ__ConfigurationAttribute__c',
237
+ discriminator: 'not applied to product options',
238
+ bucket: 'discovery',
239
+ lands: 'Guided Selling question',
240
+ verdict: 'Clear path',
241
+ shape: '1:1',
242
+ mapping: 'discovery.attributes',
243
+ }),
244
+ row({
245
+ id: 'disc.attributesOnOptions',
246
+ label: 'Attributes applied to product options',
247
+ source: 'SBQQ__ConfigurationAttribute__c',
248
+ discriminator: 'applied to product options',
249
+ bucket: 'discovery',
250
+ crossBucket: 'configuration',
251
+ lands: 'Bundle header property + Update Bundle Members',
252
+ verdict: 'Clear path',
253
+ shape: '1:1',
254
+ mapping: 'discovery.attributes',
255
+ }),
256
+ row({
257
+ id: 'disc.quoteProcesses',
258
+ label: 'Quote processes',
259
+ source: 'SBQQ__QuoteProcess__c',
260
+ discriminator: '—',
261
+ bucket: 'discovery',
262
+ lands: 'Guided Selling flow',
263
+ verdict: 'Clear path',
264
+ shape: '1:1',
265
+ mapping: 'discovery.guided',
266
+ }),
267
+ row({
268
+ id: 'disc.processInputs',
269
+ label: 'Process inputs',
270
+ source: 'SBQQ__ProcessInput__c',
271
+ discriminator: '—',
272
+ bucket: 'discovery',
273
+ lands: 'Guided Selling questions',
274
+ verdict: 'Clear path',
275
+ shape: '1:1',
276
+ mapping: 'discovery.guided',
277
+ }),
278
+ row({
279
+ id: 'disc.priceRulesDefaulting',
280
+ label: 'Price rules writing a non-money field',
281
+ source: 'SBQQ__PriceRule__c',
282
+ discriminator: 'actions write a non-money field',
283
+ bucket: 'discovery',
284
+ lands: 'Update Line Items',
285
+ verdict: 'Clear path',
286
+ shape: '1:1',
287
+ mapping: 'discovery.attributes',
288
+ }),
289
+ // ── Price determination (counts mechanisms) ─────────────────────────────
290
+ row({
291
+ id: 'price.costPlus',
292
+ label: 'Cost-plus pricing',
293
+ source: 'Product2',
294
+ discriminator: 'PricingMethod = Cost',
295
+ bucket: 'price',
296
+ lands: 'Calculated pricing formula',
297
+ verdict: 'Clear path',
298
+ shape: 'Fan-out',
299
+ unit: 'products',
300
+ mapping: 'price.calculated',
301
+ }),
302
+ row({
303
+ id: 'price.percentOfTotal',
304
+ label: 'Percent-of-total pricing',
305
+ source: 'Product2',
306
+ discriminator: 'PricingMethod = Percent Of Total',
307
+ bucket: 'price',
308
+ lands: 'Roll-up + calculated pricing formula',
309
+ verdict: 'Clear path',
310
+ shape: 'Fan-out',
311
+ unit: 'products',
312
+ mapping: 'price.calculated',
313
+ }),
314
+ row({
315
+ id: 'price.block',
316
+ label: 'Block pricing',
317
+ source: 'Product2',
318
+ discriminator: 'PricingMethod = Block, or BlockPrice__c rows',
319
+ bucket: 'price',
320
+ lands: 'Calculated pricing formula',
321
+ verdict: 'Clear path',
322
+ shape: 'Fan-out',
323
+ unit: 'products',
324
+ mapping: 'price.calculated',
325
+ }),
326
+ row({
327
+ id: 'price.attributeDriven',
328
+ label: 'Attribute-driven price',
329
+ source: 'SBQQ__PriceRule__c',
330
+ discriminator: 'price written from a configuration attribute',
331
+ bucket: 'price',
332
+ lands: 'Pricing table (value or range), or product variants',
333
+ verdict: 'Clear path',
334
+ shape: '1:1',
335
+ unit: 'rules',
336
+ mapping: 'price.tiers',
337
+ }),
338
+ row({
339
+ id: 'price.priceRulesPrice',
340
+ label: 'Price rules writing a price field',
341
+ source: 'SBQQ__PriceRule__c',
342
+ discriminator: 'actions write unit / list / net price, or a price formula',
343
+ bucket: 'price',
344
+ lands: 'Calculated pricing formula',
345
+ verdict: 'Clear path',
346
+ shape: 'Fan-out',
347
+ unit: 'rules',
348
+ mapping: 'price.calculated',
349
+ }),
350
+ row({
351
+ id: 'price.actionsOverLimit',
352
+ label: 'Price actions over the formula limits',
353
+ source: 'SBQQ__PriceAction__c',
354
+ discriminator: 'formula outside the supported grammar, or over the IF / length / nesting limits',
355
+ bucket: 'price',
356
+ lands: '—',
357
+ verdict: 'No target',
358
+ mapping: 'price.term',
359
+ }),
360
+ row({
361
+ id: 'price.actionsQuoteRollup',
362
+ label: 'Summary variables rolled up to the quote',
363
+ source: 'SBQQ__PriceAction__c',
364
+ discriminator: 'TargetObject = Quote, SourceVariable populated',
365
+ bucket: 'price',
366
+ lands: 'Roll-up, which writes its result to the quote',
367
+ verdict: 'Clear path',
368
+ shape: 'Fan-out',
369
+ mapping: 'price.calculated',
370
+ }),
371
+ row({
372
+ id: 'price.actionsQuoteArbitrary',
373
+ label: 'Price actions writing an arbitrary quote field',
374
+ source: 'SBQQ__PriceAction__c',
375
+ discriminator: 'TargetObject = Quote, SourceVariable empty',
376
+ bucket: 'price',
377
+ lands: '— no outcome writes an arbitrary quote property',
378
+ verdict: 'No target',
379
+ mapping: 'price.term',
380
+ }),
381
+ row({
382
+ id: 'price.subscriptionUnbundled',
383
+ label: 'Subscription term, not a bundle option',
384
+ source: 'Product2',
385
+ discriminator: 'SubscriptionPricing, SubscriptionTerm, not a bundle option',
386
+ bucket: 'price',
387
+ crossBucket: 'lifecycle',
388
+ lands: 'Ramp pricing',
389
+ verdict: 'Degraded',
390
+ unit: 'products',
391
+ mapping: 'price.mdq',
392
+ }),
393
+ row({
394
+ id: 'price.subscriptionBundled',
395
+ label: 'Subscription term on a bundle header or option',
396
+ source: 'Product2',
397
+ discriminator: 'SubscriptionPricing, and the product is a bundle header or option product',
398
+ bucket: 'price',
399
+ crossBucket: 'lifecycle',
400
+ lands: 'Stepped periods built with Update Bundle Members rules — the Ramp Pricing feature itself cannot be applied to a bundle',
401
+ verdict: 'Degraded',
402
+ unit: 'products',
403
+ mapping: 'price.mdq',
404
+ }),
405
+ row({
406
+ id: 'price.usage',
407
+ label: 'Usage-based charge type',
408
+ source: 'Product2',
409
+ discriminator: 'ChargeType = Usage',
410
+ bucket: 'price',
411
+ lands: '—',
412
+ verdict: 'No target',
413
+ unit: 'products',
414
+ mapping: 'price.term',
415
+ }),
416
+ row({
417
+ id: 'price.mdqSame',
418
+ label: 'MDQ segments, same quantity every segment',
419
+ source: 'SBQQ__Dimension__c',
420
+ discriminator: 'MDQ segments, same quantity in every segment',
421
+ bucket: 'price',
422
+ lands: 'Ramp pricing; Update Bundle Members rules when the product is bundled',
423
+ verdict: 'Clear path',
424
+ shape: '1:1',
425
+ unit: 'dimensions',
426
+ mapping: 'price.mdq',
427
+ }),
428
+ row({
429
+ id: 'price.mdqDiffering',
430
+ label: 'MDQ segments with differing quantities',
431
+ source: 'SBQQ__Dimension__c',
432
+ discriminator: 'MDQ segments with differing quantities',
433
+ bucket: 'price',
434
+ lands: 'Quantity is locked across ramp periods — stepped quantity does not carry',
435
+ verdict: 'Degraded',
436
+ unit: 'dimensions',
437
+ mapping: 'price.mdq',
438
+ }),
439
+ row({
440
+ id: 'price.lookupPriceRule',
441
+ label: 'Lookup queries on a price rule',
442
+ source: 'SBQQ__LookupQuery__c',
443
+ discriminator: 'on a price rule',
444
+ bucket: 'price',
445
+ crossBucket: 'code',
446
+ lands: 'Depends on the custom object behind it, and custom object values cannot be inputs to a pricing formula',
447
+ verdict: 'Further review',
448
+ review: 'Unread',
449
+ mapping: 'price.lookup',
450
+ }),
451
+ // ── Summary variables (follow the rule that uses them) ──────────────────
452
+ row({
453
+ id: 'sv.plain',
454
+ label: 'Summary variables over quote lines',
455
+ source: 'SBQQ__SummaryVariable__c',
456
+ discriminator: 'TargetObject = Quote Line, no constraint field',
457
+ bucket: 'follows',
458
+ lands: 'Roll-up field, with conditions when FilterField is set',
459
+ verdict: 'Clear path',
460
+ shape: '1:1',
461
+ mapping: 'price.calculated',
462
+ }),
463
+ row({
464
+ id: 'sv.constraint',
465
+ label: 'Summary variables constrained to a quote value',
466
+ source: 'SBQQ__SummaryVariable__c',
467
+ discriminator: 'ConstraintField populated',
468
+ bucket: 'follows',
469
+ lands: 'Roll-up conditions compare a line property to a static value, not to a quote property. Needs Update Line Items to stamp the quote value onto lines first, then filter on it',
470
+ verdict: 'Degraded',
471
+ mapping: 'price.mdq',
472
+ }),
473
+ row({
474
+ id: 'sv.composite',
475
+ label: 'Composite summary variables',
476
+ source: 'SBQQ__SummaryVariable__c',
477
+ discriminator: 'CombineWith or ValueElement populated',
478
+ bucket: 'follows',
479
+ lands: 'Two roll-ups, composed in a calculated pricing formula',
480
+ verdict: 'Clear path',
481
+ shape: 'Fan-out',
482
+ mapping: 'price.calculated',
483
+ }),
484
+ row({
485
+ id: 'sv.productOption',
486
+ label: 'Summary variables over product options',
487
+ source: 'SBQQ__SummaryVariable__c',
488
+ discriminator: 'TargetObject = Product Option',
489
+ bucket: 'follows',
490
+ lands: 'Aggregates catalog records at configuration time; roll-ups aggregate quote lines',
491
+ verdict: 'Degraded',
492
+ mapping: 'configuration.dependency',
493
+ }),
494
+ row({
495
+ id: 'sv.asset',
496
+ label: 'Summary variables over assets or subscriptions',
497
+ source: 'SBQQ__SummaryVariable__c',
498
+ discriminator: 'TargetObject = Asset or Subscription, or Scope = Assets',
499
+ bucket: 'follows',
500
+ lands: '— no asset model',
501
+ verdict: 'No target',
502
+ mapping: 'lifecycle.orders',
503
+ }),
504
+ // ── Discounting ─────────────────────────────────────────────────────────
505
+ row({
506
+ id: 'dsc.perLine',
507
+ label: 'Per-line schedules',
508
+ source: 'SBQQ__DiscountSchedule__c',
509
+ discriminator: 'on Product2 or ProductOption, per-line',
510
+ bucket: 'discounting',
511
+ lands: 'Volume pricing tiers',
512
+ verdict: 'Clear path',
513
+ shape: '1:1',
514
+ mapping: 'discounting.tiers',
515
+ }),
516
+ row({
517
+ id: 'dsc.quoteScope',
518
+ label: 'Schedules aggregating across the quote',
519
+ source: 'SBQQ__DiscountSchedule__c',
520
+ discriminator: 'AggregationScope = Quote',
521
+ bucket: 'discounting',
522
+ lands: 'Quote-wide roll-up, then a calculated pricing formula over it',
523
+ verdict: 'Clear path',
524
+ shape: 'Fan-out',
525
+ mapping: 'discounting.quote',
526
+ }),
527
+ row({
528
+ id: 'dsc.groupScope',
529
+ label: 'Schedules aggregating across a quote line group',
530
+ source: 'SBQQ__DiscountSchedule__c',
531
+ discriminator: 'AggregationScope = Group',
532
+ bucket: 'discounting',
533
+ lands: 'Roll-ups aggregate quote-wide or per bundle. Line item grouping is a template display feature and carries no functional aggregate',
534
+ verdict: 'Degraded',
535
+ mapping: 'discounting.group',
536
+ }),
537
+ row({
538
+ id: 'dsc.slab',
539
+ label: 'Slab-type schedules',
540
+ source: 'SBQQ__DiscountSchedule__c',
541
+ discriminator: 'Type = Slab',
542
+ bucket: 'discounting',
543
+ lands: 'Volume tiers, converted to per-unit',
544
+ verdict: 'Degraded',
545
+ mapping: 'discounting.tiers',
546
+ }),
547
+ row({
548
+ id: 'dsc.contractedPrices',
549
+ label: 'Contracted prices',
550
+ source: 'SBQQ__ContractedPrice__c',
551
+ discriminator: '—',
552
+ bucket: 'discounting',
553
+ lands: 'Price book assigned to a company',
554
+ verdict: 'Clear path',
555
+ shape: 'Fan-out',
556
+ mapping: 'discounting.contracted',
557
+ }),
558
+ row({
559
+ id: 'dsc.priceRulesDiscount',
560
+ label: 'Price rules writing a discount or markup field',
561
+ source: 'SBQQ__PriceRule__c',
562
+ discriminator: 'actions write a discount or markup field',
563
+ bucket: 'discounting',
564
+ lands: 'Volume tiers or Add One-time Discount',
565
+ verdict: 'Clear path',
566
+ shape: '1:1',
567
+ mapping: 'discounting.tiers',
568
+ }),
569
+ // ── Guardrails ──────────────────────────────────────────────────────────
570
+ row({
571
+ id: 'grd.validation',
572
+ label: 'Validation rules',
573
+ source: 'SBQQ__ProductRule__c',
574
+ discriminator: 'Type = Validation',
575
+ bucket: 'guardrails',
576
+ lands: 'Block outcome',
577
+ verdict: 'Clear path',
578
+ shape: '1:1',
579
+ mapping: 'guardrails.validation',
580
+ }),
581
+ row({
582
+ id: 'grd.alert',
583
+ label: 'Alert rules',
584
+ source: 'SBQQ__ProductRule__c',
585
+ discriminator: 'Type = Alert',
586
+ bucket: 'guardrails',
587
+ lands: 'Notify outcome',
588
+ verdict: 'Clear path',
589
+ shape: '1:1',
590
+ mapping: 'guardrails.alert',
591
+ }),
592
+ row({
593
+ id: 'grd.quoteScope',
594
+ label: 'Quote-scope rules, no bundle context',
595
+ source: 'SBQQ__ProductRule__c',
596
+ discriminator: 'Scope = Quote, no bundle context',
597
+ bucket: 'guardrails',
598
+ lands: 'Block or Require Approval outcome',
599
+ verdict: 'Clear path',
600
+ shape: '1:1',
601
+ mapping: 'guardrails.validation',
602
+ }),
603
+ row({
604
+ id: 'grd.floorCeiling',
605
+ label: 'Floors and ceilings via price rules',
606
+ source: 'SBQQ__PriceRule__c',
607
+ discriminator: 'actions write a field a validation rule then tests',
608
+ bucket: 'guardrails',
609
+ lands: 'Roll-up + Block outcome',
610
+ verdict: 'Clear path',
611
+ shape: 'Fan-out',
612
+ mapping: 'guardrails.validation',
613
+ }),
614
+ row({
615
+ id: 'grd.customLogic',
616
+ label: 'Custom boolean condition logic on a rule',
617
+ source: 'SBQQ__ProductRule__c, SBQQ__PriceRule__c',
618
+ discriminator: 'ConditionsMet = Custom',
619
+ bucket: 'guardrails',
620
+ lands: 'Condition groups, the expression expanded to disjunctive normal form',
621
+ verdict: 'Clear path',
622
+ shape: 'Fan-out',
623
+ mapping: 'guardrails.custom',
624
+ }),
625
+ row({
626
+ id: 'grd.platformValidation',
627
+ label: 'Platform validation rules on CPQ objects',
628
+ source: 'ValidationRule',
629
+ discriminator: 'on an SBQQ object, NamespacePrefix null',
630
+ bucket: 'guardrails',
631
+ crossBucket: 'code',
632
+ lands: 'Block outcome',
633
+ verdict: 'Clear path',
634
+ shape: '1:1',
635
+ mapping: 'guardrails.validation',
636
+ }),
637
+ row({
638
+ id: 'grd.productFlags',
639
+ label: 'Non-discountable / not price-editable',
640
+ source: 'Product2',
641
+ discriminator: 'NonDiscountable, PriceEditable = false',
642
+ bucket: 'guardrails',
643
+ lands: 'Price book unit price editability',
644
+ verdict: 'Clear path',
645
+ shape: '1:1',
646
+ unit: 'products',
647
+ mapping: 'guardrails.flags',
648
+ }),
649
+ row({
650
+ id: 'grd.groupGated',
651
+ label: 'Rules and schedules gating on a quote line group',
652
+ source: 'SBQQ__QuoteLineGroup__c',
653
+ discriminator: 'referenced by a discount schedule, rule or approval',
654
+ bucket: 'guardrails',
655
+ lands: 'Grouping is display-only; anything that aggregates or gates by group has to be rebuilt on a quote-wide or per-bundle roll-up',
656
+ verdict: 'Degraded',
657
+ mapping: 'discounting.group',
658
+ }),
659
+ // ── Approvals ───────────────────────────────────────────────────────────
660
+ row({
661
+ id: 'apr.rules',
662
+ label: 'Approval rules and their conditions',
663
+ source: 'sbaa__ApprovalRule__c',
664
+ discriminator: '—',
665
+ bucket: 'approvals',
666
+ lands: 'Require Approval outcome + approval queue',
667
+ verdict: 'Clear path',
668
+ shape: '1:1',
669
+ mapping: 'approvals.rules',
670
+ }),
671
+ row({
672
+ id: 'apr.derivedApprover',
673
+ label: 'Approvers derived from a field',
674
+ source: 'sbaa__ApprovalRule__c',
675
+ discriminator: 'approver derived from a field',
676
+ bucket: 'approvals',
677
+ lands: 'Workflow routing',
678
+ verdict: 'Degraded',
679
+ mapping: 'approvals.derived',
680
+ }),
681
+ row({
682
+ id: 'apr.chains',
683
+ label: 'Chains with step-gated ordering',
684
+ source: 'sbaa__ApprovalChain__c',
685
+ discriminator: 'step gated on a prior step',
686
+ bucket: 'approvals',
687
+ lands: 'Stacked Request Quote Approval actions in a HubSpot workflow',
688
+ verdict: 'Clear path',
689
+ shape: 'Rebuild',
690
+ mapping: 'approvals.chains',
691
+ }),
692
+ row({
693
+ id: 'apr.trackedFields',
694
+ label: 'Tracked fields',
695
+ source: 'sbaa__TrackedField__c',
696
+ discriminator: '—',
697
+ bucket: 'approvals',
698
+ lands: 'Rule conditions + roll-ups',
699
+ verdict: 'Degraded',
700
+ mapping: 'approvals.derived',
701
+ }),
702
+ row({
703
+ id: 'apr.variables',
704
+ label: 'Approval variables',
705
+ source: 'sbaa__ApprovalVariable__c',
706
+ discriminator: '—',
707
+ bucket: 'approvals',
708
+ lands: 'Roll-up field, read by the rule conditions',
709
+ verdict: 'Clear path',
710
+ shape: '1:1',
711
+ mapping: 'approvals.rules',
712
+ }),
713
+ row({
714
+ id: 'apr.native',
715
+ label: 'Native approval processes',
716
+ source: 'ApprovalProcess',
717
+ discriminator: 'native, on Quote or Opportunity',
718
+ bucket: 'approvals',
719
+ lands: 'Require Approval + HubSpot workflow',
720
+ verdict: 'Clear path',
721
+ shape: '1:1',
722
+ mapping: 'approvals.rules',
723
+ }),
724
+ // ── Quote output ────────────────────────────────────────────────────────
725
+ row({
726
+ id: 'out.templates',
727
+ label: 'Templates, sections, content',
728
+ source: 'SBQQ__QuoteTemplate__c',
729
+ discriminator: '—',
730
+ bucket: 'output',
731
+ lands: 'Quotivity Template',
732
+ verdict: 'Clear path',
733
+ shape: 'Rebuild',
734
+ mapping: 'output.templates',
735
+ }),
736
+ row({
737
+ id: 'out.lineColumns',
738
+ label: 'Line columns',
739
+ source: 'SBQQ__LineColumn__c',
740
+ discriminator: '—',
741
+ bucket: 'output',
742
+ lands: 'Line item columns + Dynamic Property Sets',
743
+ verdict: 'Clear path',
744
+ shape: '1:1',
745
+ mapping: 'output.templates',
746
+ }),
747
+ row({
748
+ id: 'out.terms',
749
+ label: 'Quote terms',
750
+ source: 'SBQQ__QuoteTerm__c',
751
+ discriminator: '—',
752
+ bucket: 'output',
753
+ lands: 'Terms Library + Insert Template Terms',
754
+ verdict: 'Clear path',
755
+ shape: '1:1',
756
+ mapping: 'output.templates',
757
+ }),
758
+ row({
759
+ id: 'out.termConditions',
760
+ label: 'Term conditions',
761
+ source: 'SBQQ__TermCondition__c',
762
+ discriminator: '—',
763
+ bucket: 'output',
764
+ lands: 'Template module visibility — condition groups combined with AND / OR',
765
+ verdict: 'Clear path',
766
+ shape: '1:1',
767
+ mapping: 'output.templates',
768
+ }),
769
+ row({
770
+ id: 'out.groups',
771
+ label: 'Quote line groups used for presentation',
772
+ source: 'SBQQ__QuoteLineGroup__c',
773
+ discriminator: 'groups used for presentation',
774
+ bucket: 'output',
775
+ lands: 'Template grouping of line items by a line item property',
776
+ verdict: 'Clear path',
777
+ shape: '1:1',
778
+ mapping: 'output.groups',
779
+ }),
780
+ // ── Contract lifecycle ──────────────────────────────────────────────────
781
+ row({
782
+ id: 'life.contracts',
783
+ label: 'Contracts and subscriptions',
784
+ source: 'Contract, SBQQ__Subscription__c',
785
+ discriminator: 'active',
786
+ bucket: 'lifecycle',
787
+ lands: 'HubSpot Contract + contract line items',
788
+ verdict: 'Clear path',
789
+ shape: '1:1',
790
+ mapping: 'lifecycle.renewals',
791
+ }),
792
+ row({
793
+ id: 'life.amendRenew',
794
+ label: 'Amendment and renewal quotes',
795
+ source: 'SBQQ__Quote__c',
796
+ discriminator: 'Type = Amendment, Renewal',
797
+ bucket: 'lifecycle',
798
+ lands: 'Change quote, renewal quote',
799
+ verdict: 'Clear path',
800
+ shape: '1:1',
801
+ mapping: 'lifecycle.renewals',
802
+ }),
803
+ row({
804
+ id: 'life.ordersAssets',
805
+ label: 'Orders and assets',
806
+ source: 'Order, Asset',
807
+ discriminator: '—',
808
+ bucket: 'lifecycle',
809
+ lands: '—',
810
+ verdict: 'No target',
811
+ mapping: 'lifecycle.orders',
812
+ }),
813
+ // ── Custom code & UI ────────────────────────────────────────────────────
814
+ row({
815
+ id: 'code.qcp',
816
+ label: 'Quote Calculator Plugins',
817
+ source: 'SBQQ__CustomScript__c',
818
+ discriminator: '—',
819
+ bucket: 'code',
820
+ lands: 'Flagged for review — likely calculated pricing',
821
+ verdict: 'Further review',
822
+ review: 'Unread',
823
+ mapping: 'code.custom',
824
+ }),
825
+ row({
826
+ id: 'code.triggers',
827
+ label: 'Apex triggers on CPQ objects',
828
+ source: 'ApexTrigger',
829
+ discriminator: 'TableEnumOrId is an SBQQ object, NamespacePrefix null',
830
+ bucket: 'code',
831
+ lands: 'Flagged for review — trigger bodies are unread',
832
+ verdict: 'Further review',
833
+ review: 'Unread',
834
+ mapping: 'code.custom',
835
+ }),
836
+ row({
837
+ id: 'code.customActions',
838
+ label: 'Custom actions',
839
+ source: 'SBQQ__CustomAction__c',
840
+ discriminator: 'not package-owned',
841
+ bucket: 'code',
842
+ lands: 'Flagged for review',
843
+ verdict: 'Further review',
844
+ review: 'Unread',
845
+ mapping: 'code.custom',
846
+ }),
847
+ row({
848
+ id: 'code.ui',
849
+ label: 'Layouts, buttons, record pages',
850
+ source: 'Layout, WebLink, FlexiPage',
851
+ discriminator: 'on an SBQQ object, not package-owned',
852
+ bucket: 'code',
853
+ lands: 'Flagged for review',
854
+ verdict: 'Further review',
855
+ review: 'Unread',
856
+ mapping: 'code.custom',
857
+ }),
858
+ ];
859
+ const byId = new Map(RULE_ROWS.map((r) => [r.id, r]));
860
+ export function ruleRow(id) {
861
+ const r = byId.get(id);
862
+ if (!r)
863
+ throw new Error(`unknown rule row ${id}`);
864
+ return r;
865
+ }
866
+ /** Buckets whose Exists value counts mechanisms (non-empty rows) rather than records. */
867
+ export const MECHANISM_BUCKETS = new Set(['price']);
868
+ //# sourceMappingURL=rules.js.map