@pagefront/lint-commerce 0.7.0 → 0.9.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/README.md +57 -2
  2. package/dist/catalogue.js +19 -2
  3. package/dist/organization-catalogue.js +1 -1
  4. package/dist/product-schema.js +164 -25
  5. package/dist/rules/configurator-pricing.d.ts +133 -0
  6. package/dist/rules/configurator-pricing.js +385 -0
  7. package/dist/rules/pdp-e-001.js +12 -3
  8. package/dist/rules/pdp-e-007.d.ts +12 -0
  9. package/dist/rules/pdp-e-007.js +53 -0
  10. package/dist/rules/pdp-e-008.d.ts +10 -0
  11. package/dist/rules/pdp-e-008.js +41 -0
  12. package/dist/rules/pdp-e-009.d.ts +12 -0
  13. package/dist/rules/pdp-e-009.js +62 -0
  14. package/dist/rules/pdp-e-010.d.ts +10 -0
  15. package/dist/rules/pdp-e-010.js +37 -0
  16. package/dist/rules/pdp-e-011.d.ts +11 -0
  17. package/dist/rules/pdp-e-011.js +53 -0
  18. package/dist/rules/pdp-e-012.d.ts +2 -0
  19. package/dist/rules/pdp-e-012.js +32 -0
  20. package/dist/rules/pdp-e-013.d.ts +9 -0
  21. package/dist/rules/pdp-e-013.js +26 -0
  22. package/dist/rules/pdp-e-014.d.ts +11 -0
  23. package/dist/rules/pdp-e-014.js +34 -0
  24. package/dist/rules/pdp-e-015.d.ts +2 -0
  25. package/dist/rules/pdp-e-015.js +91 -0
  26. package/dist/rules/pdp-w-032.d.ts +11 -0
  27. package/dist/rules/pdp-w-032.js +50 -0
  28. package/dist/rules/pdp-w-033.d.ts +10 -0
  29. package/dist/rules/pdp-w-033.js +30 -0
  30. package/dist/rules/pdp-w-034.d.ts +10 -0
  31. package/dist/rules/pdp-w-034.js +28 -0
  32. package/dist/rules/pdp-w-035.d.ts +11 -0
  33. package/dist/rules/pdp-w-035.js +41 -0
  34. package/dist/rules/pdp-w-036.d.ts +2 -0
  35. package/dist/rules/pdp-w-036.js +31 -0
  36. package/dist/rules/pdp-w-037.d.ts +2 -0
  37. package/dist/rules/pdp-w-037.js +37 -0
  38. package/dist/rules/pdp-w-038.d.ts +2 -0
  39. package/dist/rules/pdp-w-038.js +30 -0
  40. package/dist/rules/pdp-w-039.d.ts +10 -0
  41. package/dist/rules/pdp-w-039.js +29 -0
  42. package/dist/schema-validators.js +8 -2
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -26,8 +26,8 @@ lint with their own catalogue, dispatched by sheet type.
26
26
 
27
27
  ## Catalogue
28
28
 
29
- Catalogue 0.10.0, targeting format 0.9: 38 product rules (`PDP-E-001` to
30
- `PDP-E-006`, `PDP-W-001` to `PDP-W-031`, `PDP-I-001`) and 7 organization
29
+ Catalogue 0.14.0, targeting format 0.9: 55 product rules (`PDP-E-001` to
30
+ `PDP-E-015`, `PDP-W-001` to `PDP-W-039`, `PDP-I-001`) and 7 organization
31
31
  rules (`ORG-E-001`, `ORG-W-001` to `ORG-W-006`), each defined in
32
32
  [`rules.md`](https://github.com/themachineweb/pagefront/blob/main/commerce/spec/rules.md)
33
33
  with its target, check, message and remediation. Every report carries the
@@ -65,6 +65,61 @@ section:
65
65
  `pagefront:sheetUrl`, and the Offer states no return or shipping terms
66
66
  of its own
67
67
 
68
+ Added in 0.8.0 (catalogue 0.11.0), from the product spec's
69
+ ["Configurator pricing and price bands"](https://themachineweb.org/pagefront/spec/commerce/product#configurator-pricing-and-price-bands)
70
+ section. Configurator pricing moved from `pagefront:configurator` into the
71
+ offer's `pagefront:configuratorPricing`; a sheet carrying prices inside the
72
+ configurator now fails Layer 1.
73
+
74
+ - `PDP-E-001` now also covers the pricing block's `configurator`
75
+ reference, which must equal the `@id` of the Sheet's
76
+ `pagefront:configurator`
77
+ - `PDP-E-007` — `baseConfiguration` omits a priced dimension, or names a
78
+ dimension or option the configurator does not declare
79
+ - `PDP-E-008` — the modifier of a base option is not zero
80
+ - `PDP-E-009` — a modifier names an unknown dimension or option, or a
81
+ dimension declared `affectsPrice: false`
82
+ - `PDP-E-010` — an Offer's `price` differs from its pricing block's
83
+ `basePrice`
84
+ - `PDP-E-011` — an AggregateOffer's `lowPrice` or `highPrice` differs
85
+ from the band computed from a complete pricing block
86
+ - `PDP-E-012` — more than one offer prices the same configurator
87
+ - `PDP-W-032` — a priced option has no modifier and `pricing` is not
88
+ `partial`
89
+ - `PDP-W-033` — a configurator has priced dimensions and no offer prices it
90
+ - `PDP-W-034` — an offer states a price beside a configurator and no
91
+ offer carries a pricing block
92
+ - `PDP-W-035` — `basePrice` looks like a component price (heuristic)
93
+
94
+ Amounts are summed and compared as exact decimals.
95
+
96
+ Added in 0.9.0 (catalogue 0.14.0), from the same section and from
97
+ ["Size guidance"](https://themachineweb.org/pagefront/spec/commerce/product#size-guidance-pagefrontsizechart-and-pagefrontsizerecommendation):
98
+ option values are identifiers, free-text dimensions, order-time services
99
+ kept out of `options`, notes as facts, and size advice in the sizing
100
+ dimension's system. From 0.9.0 every amount is a JSON number or a plain
101
+ decimal string, one carrier per Sheet, and amounts are compared numerically
102
+ whatever the carrier; 0.8.0 validated pricing-block amounts as decimal
103
+ strings only. An Offer's `price`, a variant's `price` and a promotion's
104
+ `discountAmount` no longer accept a string that is not a plain decimal, and
105
+ prices, bounds and base prices no longer accept a negative amount.
106
+
107
+ - `PDP-E-013` — a dimension declares `inputType: "text"` and enumerates
108
+ options
109
+ - `PDP-W-036` — an option value looks like a display label (whitespace,
110
+ an uppercase letter, or a unit suffix); sizing dimensions are exempt
111
+ - `PDP-W-037` — an option in a sizing dimension (`system` of `UK`, `EU`,
112
+ `US` or `JP`) is not written as a size in that system
113
+ - `PDP-W-032`, `PDP-E-009` and the computed band of `PDP-E-011` now cover
114
+ the `"*"` modifier of a priced text dimension
115
+ - `PDP-E-014` — a size adjustment's `sizeSystem` is another system than
116
+ the configurator's sizing dimension
117
+ - `PDP-E-015` — the Sheet's amounts mix JSON numbers and strings
118
+ - `PDP-W-038` — a configurator dimension's `note` reads as page copy
119
+ (English Sheets: `you`, `your`, `select`, `order`, `contact`)
120
+ - `PDP-W-039` — size advice carries a `description` and no
121
+ `sizeAdjustment` beside a sizing dimension
122
+
68
123
  ## API
69
124
 
70
125
  The public surface — everything importable from the package root, and
package/dist/catalogue.js CHANGED
@@ -4,6 +4,15 @@ import { pdpE003 } from "./rules/pdp-e-003.js";
4
4
  import { pdpE004 } from "./rules/pdp-e-004.js";
5
5
  import { pdpE005 } from "./rules/pdp-e-005.js";
6
6
  import { pdpE006 } from "./rules/pdp-e-006.js";
7
+ import { pdpE007 } from "./rules/pdp-e-007.js";
8
+ import { pdpE008 } from "./rules/pdp-e-008.js";
9
+ import { pdpE009 } from "./rules/pdp-e-009.js";
10
+ import { pdpE010 } from "./rules/pdp-e-010.js";
11
+ import { pdpE011 } from "./rules/pdp-e-011.js";
12
+ import { pdpE012 } from "./rules/pdp-e-012.js";
13
+ import { pdpE013 } from "./rules/pdp-e-013.js";
14
+ import { pdpE014 } from "./rules/pdp-e-014.js";
15
+ import { pdpE015 } from "./rules/pdp-e-015.js";
7
16
  import { pdpW001 } from "./rules/pdp-w-001.js";
8
17
  import { pdpW002 } from "./rules/pdp-w-002.js";
9
18
  import { pdpW003 } from "./rules/pdp-w-003.js";
@@ -35,6 +44,14 @@ import { pdpW028 } from "./rules/pdp-w-028.js";
35
44
  import { pdpW029 } from "./rules/pdp-w-029.js";
36
45
  import { pdpW030 } from "./rules/pdp-w-030.js";
37
46
  import { pdpW031 } from "./rules/pdp-w-031.js";
47
+ import { pdpW032 } from "./rules/pdp-w-032.js";
48
+ import { pdpW033 } from "./rules/pdp-w-033.js";
49
+ import { pdpW034 } from "./rules/pdp-w-034.js";
50
+ import { pdpW035 } from "./rules/pdp-w-035.js";
51
+ import { pdpW036 } from "./rules/pdp-w-036.js";
52
+ import { pdpW037 } from "./rules/pdp-w-037.js";
53
+ import { pdpW038 } from "./rules/pdp-w-038.js";
54
+ import { pdpW039 } from "./rules/pdp-w-039.js";
38
55
  import { pdpI001 } from "./rules/pdp-i-001.js";
39
56
  /**
40
57
  * The commerce product-sheet rule catalogue — the executable form of
@@ -46,6 +63,6 @@ import { pdpI001 } from "./rules/pdp-i-001.js";
46
63
  export const commerceCatalogue = {
47
64
  vertical: "commerce",
48
65
  formatVersion: "0.9",
49
- catalogueVersion: "0.10.0",
50
- rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpE005, pdpE006, pdpW001, pdpW002, pdpW003, pdpW004, pdpW005, pdpW006, pdpW007, pdpW008, pdpW009, pdpW010, pdpW011, pdpW012, pdpW013, pdpW014, pdpW015, pdpW016, pdpW017, pdpW018, pdpW019, pdpW020, pdpW021, pdpW022, pdpW023, pdpW024, pdpW025, pdpW026, pdpW027, pdpW028, pdpW029, pdpW030, pdpW031, pdpI001],
66
+ catalogueVersion: "0.14.0",
67
+ rules: [pdpE001, pdpE002, pdpE003, pdpE004, pdpE005, pdpE006, pdpE007, pdpE008, pdpE009, pdpE010, pdpE011, pdpE012, pdpE013, pdpE014, pdpE015, pdpW001, pdpW002, pdpW003, pdpW004, pdpW005, pdpW006, pdpW007, pdpW008, pdpW009, pdpW010, pdpW011, pdpW012, pdpW013, pdpW014, pdpW015, pdpW016, pdpW017, pdpW018, pdpW019, pdpW020, pdpW021, pdpW022, pdpW023, pdpW024, pdpW025, pdpW026, pdpW027, pdpW028, pdpW029, pdpW030, pdpW031, pdpW032, pdpW033, pdpW034, pdpW035, pdpW036, pdpW037, pdpW038, pdpW039, pdpI001],
51
68
  };
@@ -17,6 +17,6 @@ import { orgW006 } from "./rules/org-w-006.js";
17
17
  export const organizationCatalogue = {
18
18
  vertical: "commerce",
19
19
  formatVersion: "0.9",
20
- catalogueVersion: "0.10.0",
20
+ catalogueVersion: "0.14.0",
21
21
  rules: [orgE001, orgW001, orgW002, orgW003, orgW004, orgW005, orgW006],
22
22
  };
@@ -10,7 +10,7 @@ export const PRODUCT_SCHEMA = {
10
10
  "$schema": "https://json-schema.org/draft/2020-12/schema",
11
11
  "$id": "https://themachineweb.org/pagefront/spec/commerce/product/v0.9.json",
12
12
  "title": "Pagefront Product Sheet v0.9",
13
- "description": "Formal schema for the Pagefront product sheet. v0.9 is an additive release on top of v0.8: the legalEntity entry's organization now also validates in reference-stub form (@type + @id, no name) so entries can reference a Commerce Organization Sheet; the inline form and all other constraints are unchanged, and v0.8 sheets remain valid once their format_version is updated. v0.8 is an additive release on top of v0.7: it adds a pagefront:featureGroup[] array for grouped, ordered technical specifications (groups of Schema.org PropertyValue entries, with an optional pagefront:presentationValue display variant per entry), a pagefront:productHighlights[] array of ordered publisher-authored selling-point strings, and a pagefront:legalEntity[] array for role-typed legal entities behind a product (closed role enum: brand_owner, licensee, authorized_representative, importer, manufacturer — EU GPSR responsible persons and similar). gtin and gtin13 now accept a string or an array of strings (regional packaging, re-releases); category now accepts the display string, a Schema.org CategoryCode object, or a mixed array of both. The pagefront:Document role enum gains repairability_index, recall_notice, and declaration_of_conformity; the pagefront:RelatedProduct relationship enum gains service. v0.8 also adds two GPSR-driven safety fields: a pagefront:safetyWarning[] array for typed, language-scoped safety warnings (text required; optional pictogram URL and open-vocabulary hazardCategory) and a pagefront:safetyNotice[] array for structured recall/safety notices (closed noticeType enum: recall, warning; closed remedy enum: repair, replacement, refund). v0.7 sheets remain valid under v0.8 once their format_version is updated. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
13
+ "description": "Formal schema for the Pagefront product sheet. Revision of 2026-10-01 (breaking, within v0.9): configurator pricing moves from pagefront:configurator to the offer. The configurator loses basePrice, basePriceCurrency and priceFormula, its options lose priceModifier and currency, and it gains an optional @id and an exclusions[] array; configurator dimensions gain affectsPrice, inputType (select or text, for free-text dimensions) and maxLength; Offer and AggregateOffer accept pagefront:configuratorPricing, which names the configurator it prices and carries baseConfiguration, basePrice, modifiers, optional adjustments[] and a pricing completeness flag; every amount field (Offer price, AggregateOffer lowPrice and highPrice, variant price hints, promotion discountAmount, and the pricing block's basePrice, modifiers and adjustment amounts) references a shared definition, a JSON number or a plain decimal string: nonNegativeAmount for prices, bounds, base prices, price hints and discount amounts, and the signed amount for modifiers and adjustment amounts. v0.9 sheets that carried prices inside the configurator no longer validate. v0.9 was otherwise an additive release on top of v0.8: the legalEntity entry's organization now also validates in reference-stub form (@type + @id, no name) so entries can reference a Commerce Organization Sheet; the inline form and all other constraints are unchanged, and v0.8 sheets remain valid once their format_version is updated. v0.8 is an additive release on top of v0.7: it adds a pagefront:featureGroup[] array for grouped, ordered technical specifications (groups of Schema.org PropertyValue entries, with an optional pagefront:presentationValue display variant per entry), a pagefront:productHighlights[] array of ordered publisher-authored selling-point strings, and a pagefront:legalEntity[] array for role-typed legal entities behind a product (closed role enum: brand_owner, licensee, authorized_representative, importer, manufacturer — EU GPSR responsible persons and similar). gtin and gtin13 now accept a string or an array of strings (regional packaging, re-releases); category now accepts the display string, a Schema.org CategoryCode object, or a mixed array of both. The pagefront:Document role enum gains repairability_index, recall_notice, and declaration_of_conformity; the pagefront:RelatedProduct relationship enum gains service. v0.8 also adds two GPSR-driven safety fields: a pagefront:safetyWarning[] array for typed, language-scoped safety warnings (text required; optional pictogram URL and open-vocabulary hazardCategory) and a pagefront:safetyNotice[] array for structured recall/safety notices (closed noticeType enum: recall, warning; closed remedy enum: repair, replacement, refund). v0.7 sheets remain valid under v0.8 once their format_version is updated. Validates the envelope and pagefront-namespaced extensions strictly; Schema.org-typed blocks permit additional unspecified properties.",
14
14
  "type": "object",
15
15
  "required": [
16
16
  "format_version",
@@ -433,6 +433,31 @@ export const PRODUCT_SCHEMA = {
433
433
  },
434
434
  "additionalProperties": true
435
435
  },
436
+ "amount": {
437
+ "description": "A currency amount: a JSON number, or a decimal string with a dot as decimal point, no thousands separators and no currency symbol. A Sheet uses one of the two carriers for all its amounts.",
438
+ "oneOf": [
439
+ {
440
+ "type": "number"
441
+ },
442
+ {
443
+ "type": "string",
444
+ "pattern": "^-?\\d+(\\.\\d+)?$"
445
+ }
446
+ ]
447
+ },
448
+ "nonNegativeAmount": {
449
+ "description": "An amount that is zero or positive: the same two carriers as amount, with no leading minus. Used for prices, price bounds, base prices, price hints and discount amounts; only modifiers and adjustment amounts are signed.",
450
+ "oneOf": [
451
+ {
452
+ "type": "number",
453
+ "minimum": 0
454
+ },
455
+ {
456
+ "type": "string",
457
+ "pattern": "^\\d+(\\.\\d+)?$"
458
+ }
459
+ ]
460
+ },
436
461
  "variantEntry": {
437
462
  "type": "object",
438
463
  "required": [
@@ -482,10 +507,7 @@ export const PRODUCT_SCHEMA = {
482
507
  "type": "string"
483
508
  },
484
509
  "price": {
485
- "type": [
486
- "string",
487
- "number"
488
- ]
510
+ "$ref": "#/$defs/nonNegativeAmount"
489
511
  },
490
512
  "image": {
491
513
  "type": "string",
@@ -1181,7 +1203,7 @@ export const PRODUCT_SCHEMA = {
1181
1203
  "type": "object"
1182
1204
  },
1183
1205
  "price": {
1184
- "type": "string"
1206
+ "$ref": "#/$defs/nonNegativeAmount"
1185
1207
  },
1186
1208
  "priceCurrency": {
1187
1209
  "type": "string",
@@ -1254,6 +1276,9 @@ export const PRODUCT_SCHEMA = {
1254
1276
  },
1255
1277
  "pagefront:priceOnRequest": {
1256
1278
  "$ref": "#/$defs/priceOnRequest"
1279
+ },
1280
+ "pagefront:configuratorPricing": {
1281
+ "$ref": "#/$defs/configuratorPricing"
1257
1282
  }
1258
1283
  },
1259
1284
  "anyOf": [
@@ -1274,6 +1299,12 @@ export const PRODUCT_SCHEMA = {
1274
1299
  ]
1275
1300
  }
1276
1301
  ],
1302
+ "dependentRequired": {
1303
+ "pagefront:configuratorPricing": [
1304
+ "price",
1305
+ "priceCurrency"
1306
+ ]
1307
+ },
1277
1308
  "additionalProperties": true
1278
1309
  },
1279
1310
  "aggregateOffer": {
@@ -1292,10 +1323,10 @@ export const PRODUCT_SCHEMA = {
1292
1323
  "type": "string"
1293
1324
  },
1294
1325
  "lowPrice": {
1295
- "type": "number"
1326
+ "$ref": "#/$defs/nonNegativeAmount"
1296
1327
  },
1297
1328
  "highPrice": {
1298
- "type": "number"
1329
+ "$ref": "#/$defs/nonNegativeAmount"
1299
1330
  },
1300
1331
  "priceCurrency": {
1301
1332
  "type": "string",
@@ -1324,6 +1355,9 @@ export const PRODUCT_SCHEMA = {
1324
1355
  },
1325
1356
  "seller": {
1326
1357
  "type": "object"
1358
+ },
1359
+ "pagefront:configuratorPricing": {
1360
+ "$ref": "#/$defs/configuratorPricing"
1327
1361
  }
1328
1362
  },
1329
1363
  "additionalProperties": true
@@ -1657,15 +1691,9 @@ export const PRODUCT_SCHEMA = {
1657
1691
  "@type": {
1658
1692
  "const": "pagefront:Configurator"
1659
1693
  },
1660
- "basePrice": {
1661
- "type": "string"
1662
- },
1663
- "basePriceCurrency": {
1694
+ "@id": {
1664
1695
  "type": "string",
1665
- "pattern": "^[A-Z]{3}$"
1666
- },
1667
- "priceFormula": {
1668
- "type": "string"
1696
+ "minLength": 1
1669
1697
  },
1670
1698
  "dimensions": {
1671
1699
  "type": "array",
@@ -1673,6 +1701,14 @@ export const PRODUCT_SCHEMA = {
1673
1701
  "$ref": "#/$defs/configuratorDimension"
1674
1702
  },
1675
1703
  "minItems": 1
1704
+ },
1705
+ "exclusions": {
1706
+ "type": "array",
1707
+ "items": {
1708
+ "$ref": "#/$defs/configurationSelection",
1709
+ "type": "object",
1710
+ "minProperties": 1
1711
+ }
1676
1712
  }
1677
1713
  },
1678
1714
  "additionalProperties": false
@@ -1707,20 +1743,31 @@ export const PRODUCT_SCHEMA = {
1707
1743
  }
1708
1744
  ]
1709
1745
  },
1710
- "priceModifier": {
1711
- "type": "string"
1712
- },
1713
- "currency": {
1714
- "type": "string",
1715
- "pattern": "^[A-Z]{3}$"
1716
- },
1717
1746
  "description": {
1718
1747
  "type": "string"
1719
- }
1748
+ },
1749
+ "priceModifier": false,
1750
+ "currency": false
1720
1751
  },
1721
1752
  "additionalProperties": true
1722
1753
  }
1723
1754
  },
1755
+ "affectsPrice": {
1756
+ "type": "boolean",
1757
+ "default": true
1758
+ },
1759
+ "inputType": {
1760
+ "type": "string",
1761
+ "enum": [
1762
+ "select",
1763
+ "text"
1764
+ ],
1765
+ "default": "select"
1766
+ },
1767
+ "maxLength": {
1768
+ "type": "integer",
1769
+ "minimum": 1
1770
+ },
1724
1771
  "system": {
1725
1772
  "type": "string"
1726
1773
  },
@@ -1730,6 +1777,98 @@ export const PRODUCT_SCHEMA = {
1730
1777
  },
1731
1778
  "additionalProperties": false
1732
1779
  },
1780
+ "configurationSelection": {
1781
+ "type": "object",
1782
+ "additionalProperties": {
1783
+ "anyOf": [
1784
+ {
1785
+ "type": "string"
1786
+ },
1787
+ {
1788
+ "type": "number"
1789
+ },
1790
+ {
1791
+ "type": "boolean"
1792
+ }
1793
+ ]
1794
+ }
1795
+ },
1796
+ "configuratorPricing": {
1797
+ "type": "object",
1798
+ "required": [
1799
+ "@type",
1800
+ "configurator",
1801
+ "baseConfiguration",
1802
+ "basePrice",
1803
+ "modifiers"
1804
+ ],
1805
+ "properties": {
1806
+ "@type": {
1807
+ "const": "pagefront:ConfiguratorPricing"
1808
+ },
1809
+ "configurator": {
1810
+ "type": "object",
1811
+ "required": [
1812
+ "@id"
1813
+ ],
1814
+ "properties": {
1815
+ "@type": {
1816
+ "const": "pagefront:Configurator"
1817
+ },
1818
+ "@id": {
1819
+ "type": "string",
1820
+ "minLength": 1
1821
+ }
1822
+ },
1823
+ "additionalProperties": false
1824
+ },
1825
+ "baseConfiguration": {
1826
+ "$ref": "#/$defs/configurationSelection"
1827
+ },
1828
+ "basePrice": {
1829
+ "$ref": "#/$defs/nonNegativeAmount"
1830
+ },
1831
+ "modifiers": {
1832
+ "type": "object",
1833
+ "additionalProperties": {
1834
+ "type": "object",
1835
+ "additionalProperties": {
1836
+ "$ref": "#/$defs/amount"
1837
+ }
1838
+ }
1839
+ },
1840
+ "adjustments": {
1841
+ "type": "array",
1842
+ "items": {
1843
+ "type": "object",
1844
+ "required": [
1845
+ "when",
1846
+ "priceModifier"
1847
+ ],
1848
+ "properties": {
1849
+ "when": {
1850
+ "$ref": "#/$defs/configurationSelection",
1851
+ "type": "object",
1852
+ "minProperties": 1
1853
+ },
1854
+ "priceModifier": {
1855
+ "$ref": "#/$defs/amount"
1856
+ }
1857
+ },
1858
+ "additionalProperties": false
1859
+ }
1860
+ },
1861
+ "pricing": {
1862
+ "type": "string",
1863
+ "enum": [
1864
+ "complete",
1865
+ "partial"
1866
+ ],
1867
+ "default": "complete"
1868
+ }
1869
+ },
1870
+ "additionalProperties": false
1871
+ },
1733
1872
  "madeToOrderTerms": {
1734
1873
  "type": "object",
1735
1874
  "required": [
@@ -1847,7 +1986,7 @@ export const PRODUCT_SCHEMA = {
1847
1986
  "maximum": 100
1848
1987
  },
1849
1988
  "discountAmount": {
1850
- "type": "string"
1989
+ "$ref": "#/$defs/nonNegativeAmount"
1851
1990
  },
1852
1991
  "discountCurrency": {
1853
1992
  "type": "string",
@@ -0,0 +1,133 @@
1
+ import type { Sheet } from "@pagefront/lint-core";
2
+ /**
3
+ * Shared helpers for the configurator-pricing rules (PDP-E-001's
4
+ * `configurator` arm, PDP-E-007 to PDP-E-012, PDP-W-032 to PDP-W-035).
5
+ * They implement the catalogue's Definitions: *pricing block*, *priced
6
+ * dimension*, *option key*, *computed total* and *computed band*.
7
+ */
8
+ type Obj = Record<string, unknown>;
9
+ export declare function asObject(value: unknown): Obj | null;
10
+ /** Own-property lookup: option keys are publisher strings (`constructor`, …). */
11
+ export declare function own(obj: Obj | null, key: string): unknown;
12
+ /**
13
+ * A name selector in the engine's path style (RFC 9535 normalized
14
+ * paths). Option keys are publisher strings, so the quote, the
15
+ * backslash and control characters are escaped as the RFC prescribes.
16
+ */
17
+ export declare function seg(key: string): string;
18
+ export interface Decimal {
19
+ units: bigint;
20
+ /** Number of decimal places `units` is scaled by. */
21
+ scale: number;
22
+ }
23
+ /**
24
+ * A decimal reading of an amount: a plain decimal string, or a JSON
25
+ * number whose shortest serialization is a plain decimal. Anything else
26
+ * is not comparable and yields undefined.
27
+ */
28
+ export declare function parseDecimal(value: unknown): Decimal | undefined;
29
+ export declare function addDecimal(a: Decimal, b: Decimal): Decimal;
30
+ /** Negative, zero or positive as `a` is below, equal to or above `b`. */
31
+ export declare function compareDecimal(a: Decimal, b: Decimal): number;
32
+ export declare function isZero(d: Decimal): boolean;
33
+ /** Renders with the decimal's own number of places (`859.00` at scale 2). */
34
+ export declare function formatDecimal(d: Decimal): string;
35
+ /** The string form an option value is matched by (Definitions: *option key*). */
36
+ export declare function optionKey(value: unknown): string | undefined;
37
+ export interface Dimension {
38
+ name: string;
39
+ /** `affectsPrice` is not `false`. */
40
+ priced: boolean;
41
+ /** `inputType` is `text` (Definitions: *text dimension*). */
42
+ text: boolean;
43
+ /** The declared sizing `system`, when the dimension is a sizing dimension. */
44
+ system: string | undefined;
45
+ /**
46
+ * Option keys in document order; null when the dimension enumerates
47
+ * none. Always null for a text dimension, which takes free input.
48
+ */
49
+ optionKeys: string[] | null;
50
+ }
51
+ /**
52
+ * `system` → the `pagefront:SizeAdjustment.sizeSystem` naming the same
53
+ * system (Definitions: *corresponding size system*).
54
+ */
55
+ export declare const CORRESPONDING_SIZE_SYSTEM: Record<string, string>;
56
+ /** The one modifier key of a text dimension: any non-empty input. */
57
+ export declare const TEXT_KEY = "*";
58
+ export interface Configurator {
59
+ id: string | undefined;
60
+ dimensions: Dimension[];
61
+ byName: Map<string, Dimension>;
62
+ /** Each exclusion as dimension name → option key. */
63
+ exclusions: Map<string, string>[];
64
+ }
65
+ /** The Sheet's `pagefront:configurator`, or null when absent or shapeless. */
66
+ export declare function readConfigurator(sheet: Sheet): Configurator | null;
67
+ export declare function hasPricedDimension(configurator: Configurator): boolean;
68
+ /** The pricing block of an `offers` entry, or null. */
69
+ export declare function pricingBlock(offer: unknown): Obj | null;
70
+ /** The `configurator.@id` a pricing block names, when it is a string. */
71
+ export declare function pricedConfiguratorId(block: Obj): string | undefined;
72
+ /**
73
+ * The configurator a pricing block prices: the Sheet's configurator when
74
+ * the block's reference equals its `@id` by exact string match. Null is
75
+ * the unresolved reference PDP-E-001 reports; the pricing rules are
76
+ * silent on it.
77
+ */
78
+ export declare function resolveConfigurator(block: Obj, sheet: Sheet): Configurator | null;
79
+ export declare function offerEntries(sheet: Sheet): unknown[];
80
+ /**
81
+ * True when an `offers` entry states a price, in the sense PDP-W-033 and
82
+ * PDP-W-034 share: an `AggregateOffer`, or an `Offer` with `price` or a
83
+ * non-empty `priceSpecification`. Price-on-request offers state none.
84
+ */
85
+ export declare function entryStatesPrice(entry: unknown): boolean;
86
+ export declare function isComplete(block: Obj): boolean;
87
+ export type BaseConfigurationProblem = {
88
+ kind: "omitted";
89
+ dimension: string;
90
+ } | {
91
+ kind: "unknown-dimension";
92
+ dimension: string;
93
+ } | {
94
+ kind: "unknown-option";
95
+ dimension: string;
96
+ value: string;
97
+ };
98
+ /** PDP-E-007's findings: omitted dimensions first, then pairs in document order. */
99
+ export declare function baseConfigurationProblems(configurator: Configurator, block: Obj): BaseConfigurationProblem[];
100
+ export type MissingModifierProblem = {
101
+ kind: "no-options";
102
+ dimension: string;
103
+ } | {
104
+ kind: "no-text-modifier";
105
+ dimension: string;
106
+ } | {
107
+ kind: "missing";
108
+ dimension: string;
109
+ option: string;
110
+ };
111
+ /** PDP-W-032's findings, whatever the block's `pricing` value. */
112
+ export declare function missingModifierProblems(configurator: Configurator, block: Obj): MissingModifierProblem[];
113
+ export interface Band {
114
+ min: Decimal;
115
+ max: Decimal;
116
+ }
117
+ /**
118
+ * The minimum and maximum computed total over all non-excluded
119
+ * configurations, or null when it cannot be computed (an amount is not
120
+ * a decimal, a modifier is missing, or every configuration is
121
+ * excluded). The caller has established that the block resolves to
122
+ * `configurator`.
123
+ *
124
+ * Only the dimensions named in an exclusion or an adjustment are
125
+ * enumerated together. Every other priced dimension contributes its
126
+ * smallest modifier to the minimum and its largest to the maximum,
127
+ * independently of the rest, which yields the same band as the full
128
+ * enumeration. A priced text dimension is empty or non-empty: its `*`
129
+ * modifier lowers the minimum when negative and raises the maximum
130
+ * when positive.
131
+ */
132
+ export declare function computeBand(configurator: Configurator, block: Obj): Band | null;
133
+ export {};