@endora-commerce/mod-price-lists 0.100.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/components/ApplicationRuleBuilder.d.ts +24 -0
  4. package/dist/admin/components/ApplicationRuleBuilder.d.ts.map +1 -0
  5. package/dist/admin/components/ApplicationRuleBuilder.js +249 -0
  6. package/dist/admin/components/ApplicationRuleBuilder.js.map +1 -0
  7. package/dist/admin/components/BracketGrid.d.ts +29 -0
  8. package/dist/admin/components/BracketGrid.d.ts.map +1 -0
  9. package/dist/admin/components/BracketGrid.js +252 -0
  10. package/dist/admin/components/BracketGrid.js.map +1 -0
  11. package/dist/admin/components/DisplayModeOverrideRow.d.ts +46 -0
  12. package/dist/admin/components/DisplayModeOverrideRow.d.ts.map +1 -0
  13. package/dist/admin/components/DisplayModeOverrideRow.js +109 -0
  14. package/dist/admin/components/DisplayModeOverrideRow.js.map +1 -0
  15. package/dist/admin/components/LinkedPriceListsPanel.d.ts +55 -0
  16. package/dist/admin/components/LinkedPriceListsPanel.d.ts.map +1 -0
  17. package/dist/admin/components/LinkedPriceListsPanel.js +131 -0
  18. package/dist/admin/components/LinkedPriceListsPanel.js.map +1 -0
  19. package/dist/admin/index.d.ts +34 -0
  20. package/dist/admin/index.d.ts.map +1 -0
  21. package/dist/admin/index.js +119 -0
  22. package/dist/admin/index.js.map +1 -0
  23. package/dist/admin/pages/DisplayModeOverridesPage.d.ts +25 -0
  24. package/dist/admin/pages/DisplayModeOverridesPage.d.ts.map +1 -0
  25. package/dist/admin/pages/DisplayModeOverridesPage.js +266 -0
  26. package/dist/admin/pages/DisplayModeOverridesPage.js.map +1 -0
  27. package/dist/admin/pages/PriceListDetailPage.d.ts +23 -0
  28. package/dist/admin/pages/PriceListDetailPage.d.ts.map +1 -0
  29. package/dist/admin/pages/PriceListDetailPage.js +421 -0
  30. package/dist/admin/pages/PriceListDetailPage.js.map +1 -0
  31. package/dist/admin/pages/PriceListsPage.d.ts +17 -0
  32. package/dist/admin/pages/PriceListsPage.d.ts.map +1 -0
  33. package/dist/admin/pages/PriceListsPage.js +198 -0
  34. package/dist/admin/pages/PriceListsPage.js.map +1 -0
  35. package/dist/admin/zones/CategoryDisplayMode.d.ts +27 -0
  36. package/dist/admin/zones/CategoryDisplayMode.d.ts.map +1 -0
  37. package/dist/admin/zones/CategoryDisplayMode.js +11 -0
  38. package/dist/admin/zones/CategoryDisplayMode.js.map +1 -0
  39. package/dist/admin/zones/OrganizationDisplayMode.d.ts +36 -0
  40. package/dist/admin/zones/OrganizationDisplayMode.d.ts.map +1 -0
  41. package/dist/admin/zones/OrganizationDisplayMode.js +12 -0
  42. package/dist/admin/zones/OrganizationDisplayMode.js.map +1 -0
  43. package/dist/admin/zones/ProductLinkedPriceLists.d.ts +16 -0
  44. package/dist/admin/zones/ProductLinkedPriceLists.d.ts.map +1 -0
  45. package/dist/admin/zones/ProductLinkedPriceLists.js +7 -0
  46. package/dist/admin/zones/ProductLinkedPriceLists.js.map +1 -0
  47. package/dist/backend/entities/price-display-mode-override.entity.d.ts +23 -0
  48. package/dist/backend/entities/price-display-mode-override.entity.d.ts.map +1 -0
  49. package/dist/backend/entities/price-display-mode-override.entity.js +58 -0
  50. package/dist/backend/entities/price-display-mode-override.entity.js.map +1 -0
  51. package/dist/backend/entities/price-list-price-bracket.entity.d.ts +26 -0
  52. package/dist/backend/entities/price-list-price-bracket.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/price-list-price-bracket.entity.js +73 -0
  54. package/dist/backend/entities/price-list-price-bracket.entity.js.map +1 -0
  55. package/dist/backend/entities/price-list-product.entity.d.ts +15 -0
  56. package/dist/backend/entities/price-list-product.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/price-list-product.entity.js +43 -0
  58. package/dist/backend/entities/price-list-product.entity.js.map +1 -0
  59. package/dist/backend/entities/price-list.entity.d.ts +34 -0
  60. package/dist/backend/entities/price-list.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/price-list.entity.js +115 -0
  62. package/dist/backend/entities/price-list.entity.js.map +1 -0
  63. package/dist/backend/index.d.ts +125 -0
  64. package/dist/backend/index.d.ts.map +1 -0
  65. package/dist/backend/index.js +179 -0
  66. package/dist/backend/index.js.map +1 -0
  67. package/dist/backend/plugin.d.ts +68 -0
  68. package/dist/backend/plugin.d.ts.map +1 -0
  69. package/dist/backend/plugin.js +88 -0
  70. package/dist/backend/plugin.js.map +1 -0
  71. package/dist/backend/routes.d.ts +40 -0
  72. package/dist/backend/routes.d.ts.map +1 -0
  73. package/dist/backend/routes.js +447 -0
  74. package/dist/backend/routes.js.map +1 -0
  75. package/dist/backend/routes.storefront.d.ts +47 -0
  76. package/dist/backend/routes.storefront.d.ts.map +1 -0
  77. package/dist/backend/routes.storefront.js +131 -0
  78. package/dist/backend/routes.storefront.js.map +1 -0
  79. package/dist/backend/services/application-rule-evaluator.d.ts +25 -0
  80. package/dist/backend/services/application-rule-evaluator.d.ts.map +1 -0
  81. package/dist/backend/services/application-rule-evaluator.js +2 -0
  82. package/dist/backend/services/application-rule-evaluator.js.map +1 -0
  83. package/dist/backend/services/audit-references.d.ts +11 -0
  84. package/dist/backend/services/audit-references.d.ts.map +1 -0
  85. package/dist/backend/services/audit-references.js +23 -0
  86. package/dist/backend/services/audit-references.js.map +1 -0
  87. package/dist/backend/services/default-price-list-migration.d.ts +70 -0
  88. package/dist/backend/services/default-price-list-migration.d.ts.map +1 -0
  89. package/dist/backend/services/default-price-list-migration.js +177 -0
  90. package/dist/backend/services/default-price-list-migration.js.map +1 -0
  91. package/dist/backend/services/display-mode-resolver.d.ts +78 -0
  92. package/dist/backend/services/display-mode-resolver.d.ts.map +1 -0
  93. package/dist/backend/services/display-mode-resolver.js +71 -0
  94. package/dist/backend/services/display-mode-resolver.js.map +1 -0
  95. package/dist/backend/services/listing-price-chain.d.ts +26 -0
  96. package/dist/backend/services/listing-price-chain.d.ts.map +1 -0
  97. package/dist/backend/services/listing-price-chain.js +55 -0
  98. package/dist/backend/services/listing-price-chain.js.map +1 -0
  99. package/dist/backend/services/price-bracket-resolver.d.ts +24 -0
  100. package/dist/backend/services/price-bracket-resolver.d.ts.map +1 -0
  101. package/dist/backend/services/price-bracket-resolver.js +16 -0
  102. package/dist/backend/services/price-bracket-resolver.js.map +1 -0
  103. package/dist/backend/services/price-list-candidate-vector.d.ts +116 -0
  104. package/dist/backend/services/price-list-candidate-vector.d.ts.map +1 -0
  105. package/dist/backend/services/price-list-candidate-vector.js +133 -0
  106. package/dist/backend/services/price-list-candidate-vector.js.map +1 -0
  107. package/dist/backend/services/price-list-currency-reference.d.ts +12 -0
  108. package/dist/backend/services/price-list-currency-reference.d.ts.map +1 -0
  109. package/dist/backend/services/price-list-currency-reference.js +24 -0
  110. package/dist/backend/services/price-list-currency-reference.js.map +1 -0
  111. package/dist/backend/services/price-list-read-port.d.ts +29 -0
  112. package/dist/backend/services/price-list-read-port.d.ts.map +1 -0
  113. package/dist/backend/services/price-list-read-port.js +64 -0
  114. package/dist/backend/services/price-list-read-port.js.map +1 -0
  115. package/dist/backend/services/price-list-resolver.d.ts +26 -0
  116. package/dist/backend/services/price-list-resolver.d.ts.map +1 -0
  117. package/dist/backend/services/price-list-resolver.js +37 -0
  118. package/dist/backend/services/price-list-resolver.js.map +1 -0
  119. package/dist/backend/services/price-list-service.d.ts +431 -0
  120. package/dist/backend/services/price-list-service.d.ts.map +1 -0
  121. package/dist/backend/services/price-list-service.js +1238 -0
  122. package/dist/backend/services/price-list-service.js.map +1 -0
  123. package/dist/backend/services/price-list-status-worker.d.ts +28 -0
  124. package/dist/backend/services/price-list-status-worker.d.ts.map +1 -0
  125. package/dist/backend/services/price-list-status-worker.js +38 -0
  126. package/dist/backend/services/price-list-status-worker.js.map +1 -0
  127. package/dist/backend/services/pricing-cache.d.ts +53 -0
  128. package/dist/backend/services/pricing-cache.d.ts.map +1 -0
  129. package/dist/backend/services/pricing-cache.js +88 -0
  130. package/dist/backend/services/pricing-cache.js.map +1 -0
  131. package/dist/backend/services/pricing-service.d.ts +304 -0
  132. package/dist/backend/services/pricing-service.d.ts.map +1 -0
  133. package/dist/backend/services/pricing-service.interface.d.ts +151 -0
  134. package/dist/backend/services/pricing-service.interface.d.ts.map +1 -0
  135. package/dist/backend/services/pricing-service.interface.js +10 -0
  136. package/dist/backend/services/pricing-service.interface.js.map +1 -0
  137. package/dist/backend/services/pricing-service.js +803 -0
  138. package/dist/backend/services/pricing-service.js.map +1 -0
  139. package/dist/backend/services/unit-price-ordering.d.ts +121 -0
  140. package/dist/backend/services/unit-price-ordering.d.ts.map +1 -0
  141. package/dist/backend/services/unit-price-ordering.js +164 -0
  142. package/dist/backend/services/unit-price-ordering.js.map +1 -0
  143. package/dist/manifest.d.ts +218 -0
  144. package/dist/manifest.d.ts.map +1 -0
  145. package/dist/manifest.js +228 -0
  146. package/dist/manifest.js.map +1 -0
  147. package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts +24 -0
  148. package/dist/migrations/20260426T075235_price_lists_pricing_init.d.ts.map +1 -0
  149. package/dist/migrations/20260426T075235_price_lists_pricing_init.js +89 -0
  150. package/dist/migrations/20260426T075235_price_lists_pricing_init.js.map +1 -0
  151. package/dist/migrations/20260504T125655_price_lists_engine.d.ts +6 -0
  152. package/dist/migrations/20260504T125655_price_lists_engine.d.ts.map +1 -0
  153. package/dist/migrations/20260504T125655_price_lists_engine.js +319 -0
  154. package/dist/migrations/20260504T125655_price_lists_engine.js.map +1 -0
  155. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts +15 -0
  156. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.d.ts.map +1 -0
  157. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js +58 -0
  158. package/dist/migrations/20260817T055457_price_lists_single_system_price_list.js.map +1 -0
  159. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts +40 -0
  160. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.d.ts.map +1 -0
  161. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js +46 -0
  162. package/dist/migrations/20260821T135907_price_lists_unit_price_amount_index.js.map +1 -0
  163. package/dist/migrations/index.d.ts +30 -0
  164. package/dist/migrations/index.d.ts.map +1 -0
  165. package/dist/migrations/index.js +35 -0
  166. package/dist/migrations/index.js.map +1 -0
  167. package/docs/price_lists.md +278 -0
  168. package/i18n/en.json +14 -0
  169. package/i18n/pl.json +14 -0
  170. package/package.json +99 -0
  171. package/tailwind.css +14 -0
@@ -0,0 +1,71 @@
1
+ export function resolveDisplayMode(customer, overrides, settings) {
2
+ if (overrides.productOverride !== null) {
3
+ return { mode: overrides.productOverride, resolvedFrom: 'product' };
4
+ }
5
+ if (overrides.categoryOverride !== null) {
6
+ return { mode: overrides.categoryOverride, resolvedFrom: 'category' };
7
+ }
8
+ if (customer === 'signed_in' && overrides.organizationOverride !== null) {
9
+ return { mode: overrides.organizationOverride, resolvedFrom: 'organization' };
10
+ }
11
+ if (customer === 'guest') {
12
+ return {
13
+ mode: settings.unauthenticatedDisplayMode,
14
+ resolvedFrom: 'settings.unauthenticated',
15
+ };
16
+ }
17
+ return { mode: settings.defaultDisplayMode, resolvedFrom: 'settings.default' };
18
+ }
19
+ /** Most specific first: deepest, then `(sort_order asc, id asc)` as tie-break. */
20
+ export function pickCategoryOverride(candidates) {
21
+ let best = null;
22
+ for (const candidate of candidates) {
23
+ if (best === null) {
24
+ best = candidate;
25
+ continue;
26
+ }
27
+ if (candidate.depth !== best.depth) {
28
+ if (candidate.depth > best.depth)
29
+ best = candidate;
30
+ continue;
31
+ }
32
+ if (candidate.sortOrder !== best.sortOrder) {
33
+ if (candidate.sortOrder < best.sortOrder)
34
+ best = candidate;
35
+ continue;
36
+ }
37
+ if (candidate.categoryId.localeCompare(best.categoryId) < 0)
38
+ best = candidate;
39
+ }
40
+ return best;
41
+ }
42
+ /**
43
+ * The chain's decision with the settings tier left **unread**.
44
+ *
45
+ * The eager `resolveDisplayMode` above wants both settings values in hand,
46
+ * which is one query per product on a listing page and two more on a cart line
47
+ * that an override already answered. This form names the settings step instead
48
+ * of taking its value, so the per-product path reads the pair only when it is
49
+ * reached and the batched path reads it once for the whole page.
50
+ *
51
+ * `organizationOverride` is the caller's answer to "is this a signed-in
52
+ * customer with an organization" — a guest passes `null`, exactly as the
53
+ * eager resolver's `customer === 'signed_in'` guard requires.
54
+ */
55
+ export function decideDisplayMode(inputs) {
56
+ if (inputs.productOverride !== null) {
57
+ return { source: 'product', mode: inputs.productOverride };
58
+ }
59
+ const category = pickCategoryOverride(inputs.categoryCandidates);
60
+ if (category !== null)
61
+ return { source: 'category', mode: category.mode };
62
+ if (inputs.organizationOverride !== null) {
63
+ return { source: 'organization', mode: inputs.organizationOverride };
64
+ }
65
+ return { source: 'settings' };
66
+ }
67
+ /** Which `pricing.*` settings key answers for this customer kind. */
68
+ export function settingsDisplayModeKey(customer) {
69
+ return customer === 'guest' ? 'unauthenticated_display_mode' : 'default_display_mode';
70
+ }
71
+ //# sourceMappingURL=display-mode-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display-mode-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/display-mode-resolver.ts"],"names":[],"mappings":"AA4CA,MAAM,UAAU,kBAAkB,CAChC,QAAsB,EACtB,SAAoC,EACpC,QAA0B;IAE1B,IAAI,SAAS,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IACtE,CAAC;IACD,IAAI,SAAS,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,gBAAgB,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;IACxE,CAAC;IACD,IAAI,QAAQ,KAAK,WAAW,IAAI,SAAS,CAAC,oBAAoB,KAAK,IAAI,EAAE,CAAC;QACxE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,oBAAoB,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,0BAA0B;YACzC,YAAY,EAAE,0BAA0B;SACzC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,kBAAkB,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AACjF,CAAC;AAkBD,kFAAkF;AAClF,MAAM,UAAU,oBAAoB,CAClC,UAAmD;IAEnD,IAAI,IAAI,GAAwC,IAAI,CAAC;IACrD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,IAAI,GAAG,SAAS,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YACnC,IAAI,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;gBAAE,IAAI,GAAG,SAAS,CAAC;YACnD,SAAS;QACX,CAAC;QACD,IAAI,SAAS,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,IAAI,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS;gBAAE,IAAI,GAAG,SAAS,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,GAAG,SAAS,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAMD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAIjC;IACC,IAAI,MAAM,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC;IAC7D,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IACjE,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,MAAM,CAAC,oBAAoB,KAAK,IAAI,EAAE,CAAC;QACzC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,oBAAoB,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,sBAAsB,CACpC,QAAsB;IAEtB,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,sBAAsB,CAAC;AACxF,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ListingPrice } from '@endora-commerce/contracts';
2
+ import type { PricingLineResult } from './pricing-service.interface.js';
3
+ /**
4
+ * The catalogue-listing pricing chain (issue #132), as a pure function.
5
+ *
6
+ * The product ruling: **applicable price list → the price assigned directly to
7
+ * the Product → nothing.** There is always one default price list — a system
8
+ * list `PriceListService` refuses to delete, refuses to move out of `active`
9
+ * and pins to a `kind: 'all'` rule — so the first step is the path a listing
10
+ * normally takes and the other two are a genuine margin.
11
+ *
12
+ * The chain lives here, in the module that owns pricing, rather than in the
13
+ * four listing paths that need it. That is the same rule the composition
14
+ * checklist states for a degrade: it belongs inside the owner's implementation
15
+ * and is expressed in the return type, so no consumer can invent a step of its
16
+ * own — which is precisely how four read paths came to render a figure no
17
+ * price list supported.
18
+ *
19
+ * `null` for `line` is the engine's own "nothing applies" answer, which is
20
+ * distinct from "the module is absent": the port gate throws before this
21
+ * function is ever reached.
22
+ */
23
+ export declare function listingPriceFrom(line: PricingLineResult | null, product: {
24
+ attributeValues: Record<string, unknown>;
25
+ }, currencyCode: string): ListingPrice;
26
+ //# sourceMappingURL=listing-price-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listing-price-chain.d.ts","sourceRoot":"","sources":["../../../src/backend/services/listing-price-chain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAExE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,iBAAiB,GAAG,IAAI,EAC9B,OAAO,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EACrD,YAAY,EAAE,MAAM,GACnB,YAAY,CAad"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The catalogue-listing pricing chain (issue #132), as a pure function.
3
+ *
4
+ * The product ruling: **applicable price list → the price assigned directly to
5
+ * the Product → nothing.** There is always one default price list — a system
6
+ * list `PriceListService` refuses to delete, refuses to move out of `active`
7
+ * and pins to a `kind: 'all'` rule — so the first step is the path a listing
8
+ * normally takes and the other two are a genuine margin.
9
+ *
10
+ * The chain lives here, in the module that owns pricing, rather than in the
11
+ * four listing paths that need it. That is the same rule the composition
12
+ * checklist states for a degrade: it belongs inside the owner's implementation
13
+ * and is expressed in the return type, so no consumer can invent a step of its
14
+ * own — which is precisely how four read paths came to render a figure no
15
+ * price list supported.
16
+ *
17
+ * `null` for `line` is the engine's own "nothing applies" answer, which is
18
+ * distinct from "the module is absent": the port gate throws before this
19
+ * function is ever reached.
20
+ */
21
+ export function listingPriceFrom(line, product, currencyCode) {
22
+ if (line) {
23
+ return {
24
+ source: 'price_list',
25
+ amount: line.amount,
26
+ currency: line.currency.toUpperCase(),
27
+ priceListId: line.priceListId,
28
+ isSale: line.isSale,
29
+ };
30
+ }
31
+ const own = productOwnAmount(product);
32
+ if (own === null)
33
+ return { source: 'none' };
34
+ return { source: 'product', amount: own, currency: currencyCode.toUpperCase() };
35
+ }
36
+ /**
37
+ * The price assigned directly to the Product — the legacy `defaultPrice` (and
38
+ * older `price`) attribute, read in exactly one place now that the chain is the
39
+ * owner's.
40
+ *
41
+ * Returns a decimal string so the `price_list` and `product` arms of the union
42
+ * carry the same kind of amount, and `null` when there is no number to carry.
43
+ * Zero is a number: it comes back as `'0.00'` and reaches the listing as zero,
44
+ * never as an absence.
45
+ */
46
+ function productOwnAmount(product) {
47
+ const raw = product.attributeValues['defaultPrice'] ?? product.attributeValues['price'];
48
+ if (typeof raw !== 'number' && typeof raw !== 'string')
49
+ return null;
50
+ const value = Number(raw);
51
+ if (!Number.isFinite(value) || value < 0)
52
+ return null;
53
+ return value.toFixed(2);
54
+ }
55
+ //# sourceMappingURL=listing-price-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listing-price-chain.js","sourceRoot":"","sources":["../../../src/backend/services/listing-price-chain.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAA8B,EAC9B,OAAqD,EACrD,YAAoB;IAEpB,IAAI,IAAI,EAAE,CAAC;QACT,OAAO;YACL,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;YACrC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,CAAC,WAAW,EAAE,EAAE,CAAC;AAClF,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,OAAqD;IAC7E,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IACxF,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pure bracket resolver (feature 011 / FR-014, FR-016, FR-031).
3
+ *
4
+ * Given a list of brackets across currencies, returns the bracket whose
5
+ * `(currency, minQuantity, maxQuantity)` window covers the requested
6
+ * `(currency, qty)`. Returns null when the requested currency is missing
7
+ * from the bracket set OR the requested quantity falls outside every
8
+ * bracket on that currency (the bracket-gap fall-through case from
9
+ * research §R5 — the resolver advances to the next-priority list).
10
+ *
11
+ * Brackets are expected to be non-overlapping within the same currency
12
+ * (overlap-freedom is enforced at the service layer when brackets are
13
+ * written; this resolver assumes valid input).
14
+ */
15
+ export interface PriceBracketRow {
16
+ priceListId: string;
17
+ productId: string;
18
+ currencyCode: string;
19
+ minQuantity: number;
20
+ maxQuantity: number | null;
21
+ amount: string;
22
+ }
23
+ export declare function resolvePriceBracket(brackets: readonly PriceBracketRow[], currencyCode: string, quantity: number): PriceBracketRow | null;
24
+ //# sourceMappingURL=price-bracket-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-bracket-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/price-bracket-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,eAAe,GAAG,IAAI,CAUxB"}
@@ -0,0 +1,16 @@
1
+ export function resolvePriceBracket(brackets, currencyCode, quantity) {
2
+ if (quantity < 1)
3
+ return null;
4
+ const inCurrency = brackets.filter((b) => b.currencyCode === currencyCode);
5
+ if (inCurrency.length === 0)
6
+ return null;
7
+ for (const b of inCurrency) {
8
+ if (b.minQuantity > quantity)
9
+ continue;
10
+ if (b.maxQuantity != null && b.maxQuantity < quantity)
11
+ continue;
12
+ return b;
13
+ }
14
+ return null;
15
+ }
16
+ //# sourceMappingURL=price-bracket-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-bracket-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/price-bracket-resolver.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,mBAAmB,CACjC,QAAoC,EACpC,YAAoB,EACpB,QAAgB;IAEhB,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,CAAC;IAC3E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,WAAW,GAAG,QAAQ;YAAE,SAAS;QACvC,IAAI,CAAC,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,CAAC,WAAW,GAAG,QAAQ;YAAE,SAAS;QAChE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,116 @@
1
+ import { type ApplicationRule } from '@endora-commerce/contracts';
2
+ /**
3
+ * The viewer's ordered price-list candidates (feature 086, research §R1), as a
4
+ * **pure** function of the active lists and the viewer's rule dimensions.
5
+ *
6
+ * ## The structural fact this rests on
7
+ *
8
+ * `evaluateApplicationRule` has five criterion types — `salesChannel`,
9
+ * `organization`, `customerGroup`, `currency`, `category` — and four of them
10
+ * read only the viewer. **`category` is the only one that reads the product.**
11
+ * So for a fixed viewer the set of lists that match, and the priority each
12
+ * matches at, is the same for every product *except* where a rule names a
13
+ * category; and `pickPriorityChain`'s walk is a total order over candidates, so
14
+ * FR-031's fall-through ("drop the list with no usable bracket and re-pick") is
15
+ * exactly "take the first candidate in that order that has a bracket" —
16
+ * removing elements from a totally ordered set does not reorder what remains.
17
+ *
18
+ * That is what turns the resolution into a merge of ordered streams instead of
19
+ * a per-product loop, and it is why this file exists at all.
20
+ *
21
+ * ## The order is produced by the walk, not by a copy of it
22
+ *
23
+ * The vector is built by calling {@link pickPriorityChain} repeatedly and
24
+ * removing the winner — which *is* the fall-through walk, run to exhaustion.
25
+ * Writing the priority chain out as a sort key would be a second implementation
26
+ * of the resolver's ranking, free to drift from it; this cannot drift, because
27
+ * it is the same function.
28
+ *
29
+ * ## The sale partition leads the base partition, wholesale
30
+ *
31
+ * `lineFromEngine` charges the sale price whenever the sale partition resolves
32
+ * *any* bracket, and only otherwise the base one. So "first candidate that
33
+ * prices the product wins" reproduces it exactly when every sale candidate is
34
+ * ordered above every base candidate — not by interleaving the two partitions
35
+ * by priority.
36
+ *
37
+ * ## Category criteria become membership predicates, not evaluations
38
+ *
39
+ * A rule that names a category matches some products and not others, so one
40
+ * price list can appear in the vector **more than once**: once per subset of
41
+ * its category criteria that could be true, each entry carrying the memberships
42
+ * that subset requires. Two properties make that safe and make the entries
43
+ * cheap:
44
+ *
45
+ * - the rule AST is **monotone** (`all`, `criterion`, `AND`, `OR` — there is no
46
+ * negation), so making one more criterion true can only move `explicitOn`
47
+ * up the priority chain, never down. An entry therefore never over-ranks the
48
+ * products it covers: its rank is a floor, and the entry that carries the
49
+ * exact rank is enumerated too and sorts ahead of it;
50
+ * - which means an entry needs no *negative* membership predicate. It says
51
+ * which categories a product must be in, never which it must be out of, and
52
+ * a product that also satisfies a criterion the entry left out is answered by
53
+ * the better-ranked entry that includes it. Both entries name the same price
54
+ * list and therefore the same amount, so a tie between them changes nothing.
55
+ *
56
+ * The synthetic category set each subset is evaluated against still has to
57
+ * exclude the criteria outside the subset, or the evaluation would come back
58
+ * with a rank the subset has not earned. A subset whose criteria cannot all be
59
+ * true once the others are excluded is unreachable and is skipped — the entry
60
+ * that covers those products is the larger subset.
61
+ */
62
+ /** A price list, as the vector reads it. */
63
+ export interface CandidateListInput {
64
+ id: string;
65
+ type: 'base' | 'sale';
66
+ applicationRule: ApplicationRule;
67
+ modifiedAt: Date;
68
+ name: string;
69
+ isSystem: boolean;
70
+ }
71
+ /** The viewer's rule dimensions — everything except the product. */
72
+ export interface CandidateViewerContext {
73
+ organizationId: string | null;
74
+ organizationChain: readonly string[];
75
+ customerGroupId: string | null;
76
+ salesChannelId: string;
77
+ currencyCode: string;
78
+ }
79
+ /**
80
+ * One stream of the merge: a price list, and the category memberships a product
81
+ * must satisfy for this list to apply to it at this rank.
82
+ */
83
+ export interface OrderedCandidate {
84
+ priceListId: string;
85
+ /** The partition the candidate came from — the row's `isSale`. */
86
+ isSale: boolean;
87
+ /**
88
+ * Each group is one `category` criterion's value list. A product qualifies
89
+ * when it belongs to **at least one** category in **every** group. An empty
90
+ * array means the candidate applies to every product this viewer sees.
91
+ */
92
+ requireAnyOf: readonly (readonly string[])[];
93
+ }
94
+ /**
95
+ * The most `category` criteria one rule may carry before this module refuses to
96
+ * express the ordering.
97
+ *
98
+ * The enumeration is `2^k` per rule, so it needs a ceiling; 8 is far above
99
+ * anything an operator writes (the reference catalogue's rules carry zero) and
100
+ * still bounded at 256 evaluations of a pure function. Above it the answer is a
101
+ * refusal rather than an approximation: an ordering that quietly ranked a list
102
+ * wrongly is precisely the failure this feature exists to avoid, and an
103
+ * unorderable page is visible while a mis-ordered one is not.
104
+ */
105
+ export declare const MAX_CATEGORY_CRITERIA_PER_RULE = 8;
106
+ /** Thrown when a rule carries more `category` criteria than the vector enumerates. */
107
+ export declare class UnorderableRuleError extends Error {
108
+ readonly priceListId: string;
109
+ constructor(priceListId: string);
110
+ }
111
+ /**
112
+ * The viewer's candidates, best first: every sale candidate in priority order,
113
+ * then every base candidate in priority order.
114
+ */
115
+ export declare function buildCandidateVector(lists: readonly CandidateListInput[], viewer: CandidateViewerContext): OrderedCandidate[];
116
+ //# sourceMappingURL=price-list-candidate-vector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-candidate-vector.d.ts","sourceRoot":"","sources":["../../../src/backend/services/price-list-candidate-vector.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,eAAe,EAAE,eAAe,CAAC;IACjC,UAAU,EAAE,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,YAAY,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAEhD,sFAAsF;AACtF,qBAAa,oBAAqB,SAAQ,KAAK;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM;gBAAnB,WAAW,EAAE,MAAM;CAOzC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,MAAM,EAAE,sBAAsB,GAC7B,gBAAgB,EAAE,CASpB"}
@@ -0,0 +1,133 @@
1
+ import { evaluateApplicationRule, } from '@endora-commerce/contracts';
2
+ import { pickPriorityChain } from './price-list-resolver.js';
3
+ /**
4
+ * The most `category` criteria one rule may carry before this module refuses to
5
+ * express the ordering.
6
+ *
7
+ * The enumeration is `2^k` per rule, so it needs a ceiling; 8 is far above
8
+ * anything an operator writes (the reference catalogue's rules carry zero) and
9
+ * still bounded at 256 evaluations of a pure function. Above it the answer is a
10
+ * refusal rather than an approximation: an ordering that quietly ranked a list
11
+ * wrongly is precisely the failure this feature exists to avoid, and an
12
+ * unorderable page is visible while a mis-ordered one is not.
13
+ */
14
+ export const MAX_CATEGORY_CRITERIA_PER_RULE = 8;
15
+ /** Thrown when a rule carries more `category` criteria than the vector enumerates. */
16
+ export class UnorderableRuleError extends Error {
17
+ priceListId;
18
+ constructor(priceListId) {
19
+ super(`Price list ${priceListId} carries more than ${MAX_CATEGORY_CRITERIA_PER_RULE} category ` +
20
+ 'criteria, so its applicability cannot be expressed as an ordered stream.');
21
+ this.priceListId = priceListId;
22
+ this.name = 'UnorderableRuleError';
23
+ }
24
+ }
25
+ /**
26
+ * The viewer's candidates, best first: every sale candidate in priority order,
27
+ * then every base candidate in priority order.
28
+ */
29
+ export function buildCandidateVector(lists, viewer) {
30
+ const sale = [];
31
+ const base = [];
32
+ for (const list of lists) {
33
+ for (const entry of entriesFor(list, viewer)) {
34
+ (list.type === 'sale' ? sale : base).push(entry);
35
+ }
36
+ }
37
+ return [...walk(sale), ...walk(base)];
38
+ }
39
+ /** `pickPriorityChain` run to exhaustion — the fall-through walk itself. */
40
+ function walk(candidates) {
41
+ const remaining = [...candidates];
42
+ const out = [];
43
+ while (remaining.length > 0) {
44
+ const picked = pickPriorityChain(remaining);
45
+ if (!picked)
46
+ break;
47
+ out.push(picked.list);
48
+ remaining.splice(remaining.indexOf(picked), 1);
49
+ }
50
+ return out;
51
+ }
52
+ function entriesFor(list, viewer) {
53
+ const groups = categoryCriterionGroups(list.applicationRule);
54
+ if (groups.length > MAX_CATEGORY_CRITERIA_PER_RULE)
55
+ throw new UnorderableRuleError(list.id);
56
+ const out = [];
57
+ const seen = new Set();
58
+ for (let mask = 0; mask < 1 << groups.length; mask += 1) {
59
+ const included = [];
60
+ const excluded = new Set();
61
+ for (let i = 0; i < groups.length; i += 1) {
62
+ if ((mask >> i) & 1)
63
+ included.push(groups[i]);
64
+ else
65
+ for (const value of groups[i])
66
+ excluded.add(value);
67
+ }
68
+ // The categories a product covered by this subset may be assumed to carry:
69
+ // everything the included criteria name, minus everything the excluded ones
70
+ // do, so the evaluation below awards this subset's rank and no better one.
71
+ const synthetic = new Set();
72
+ for (const group of included) {
73
+ for (const value of group)
74
+ if (!excluded.has(value))
75
+ synthetic.add(value);
76
+ }
77
+ // A criterion left with no representative cannot be true while the excluded
78
+ // ones are false: the subset describes no product, and the products it was
79
+ // meant to cover belong to a larger subset that is enumerated too.
80
+ if (included.some((group) => !group.some((value) => synthetic.has(value))))
81
+ continue;
82
+ const ctx = {
83
+ organizationId: viewer.organizationId,
84
+ organizationChain: viewer.organizationChain,
85
+ customerGroupId: viewer.customerGroupId,
86
+ salesChannelId: viewer.salesChannelId,
87
+ currencyCode: viewer.currencyCode,
88
+ productCategoryIds: synthetic,
89
+ };
90
+ const evaluation = evaluateApplicationRule(list.applicationRule, ctx);
91
+ if (!evaluation.matched)
92
+ continue;
93
+ const requireAnyOf = included.map((group) => [...group].sort());
94
+ const signature = `${[...evaluation.explicitOn].sort().join('+')}|${evaluation.organizationRank ?? ''}|${requireAnyOf.map((g) => g.join(',')).join(';')}`;
95
+ if (seen.has(signature))
96
+ continue;
97
+ seen.add(signature);
98
+ out.push({
99
+ list: { priceListId: list.id, isSale: list.type === 'sale', requireAnyOf },
100
+ evaluation,
101
+ modifiedAt: list.modifiedAt,
102
+ name: list.name,
103
+ isSystem: list.isSystem,
104
+ });
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * The distinct `category` criteria in a rule, as value groups.
110
+ *
111
+ * De-duplicated by value set: two criteria naming the same categories are the
112
+ * same predicate, and collapsing them keeps the enumeration below its ceiling
113
+ * in the shape an operator most plausibly writes one (the same category named
114
+ * in both halves of an `OR`).
115
+ */
116
+ function categoryCriterionGroups(rule) {
117
+ const byKey = new Map();
118
+ const visit = (node) => {
119
+ if (node.kind === 'criterion') {
120
+ if (node.type !== 'category' || node.values.length === 0)
121
+ return;
122
+ const values = [...new Set(node.values)].sort();
123
+ byKey.set(values.join(','), values);
124
+ return;
125
+ }
126
+ if (node.kind === 'group')
127
+ for (const child of node.children)
128
+ visit(child);
129
+ };
130
+ visit(rule);
131
+ return [...byKey.values()];
132
+ }
133
+ //# sourceMappingURL=price-list-candidate-vector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-candidate-vector.js","sourceRoot":"","sources":["../../../src/backend/services/price-list-candidate-vector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ /**
4
+ * Who in `price_lists` still points at a currency (feature 077, D-87).
5
+ *
6
+ * `currencies` used to ask this with `select count(*) from "price_lists"` of
7
+ * its own — this module's table, named in a string no import-level boundary
8
+ * check can see. A price list denominated in a currency the platform no
9
+ * longer has is not a price list, so the reference blocks the delete.
10
+ */
11
+ export declare function registerPriceListCurrencyReferences(registry: DictionaryReferenceRegistryPort, emFactory: () => EntityManager): void;
12
+ //# sourceMappingURL=price-list-currency-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-currency-reference.d.ts","sourceRoot":"","sources":["../../../src/backend/services/price-list-currency-reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,+BAA+B,EACzC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAoBN"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Who in `price_lists` still points at a currency (feature 077, D-87).
3
+ *
4
+ * `currencies` used to ask this with `select count(*) from "price_lists"` of
5
+ * its own — this module's table, named in a string no import-level boundary
6
+ * check can see. A price list denominated in a currency the platform no
7
+ * longer has is not a price list, so the reference blocks the delete.
8
+ */
9
+ export function registerPriceListCurrencyReferences(registry, emFactory) {
10
+ registry.register({
11
+ ownerModuleId: 'price_lists',
12
+ consumer: 'price_lists',
13
+ tableName: 'price_lists',
14
+ columnName: 'currency',
15
+ blocking: true,
16
+ countReferences: async (code) => {
17
+ const rows = (await emFactory().execute(`select count(*)::int as n from "price_lists" where "currency" = ?`, [code]));
18
+ return rows[0]?.n ?? 0;
19
+ },
20
+ usedCodes: async () => (await emFactory().execute(`select "currency" as code, count(*)::int as count
21
+ from "price_lists" where "currency" is not null group by "currency"`)),
22
+ });
23
+ }
24
+ //# sourceMappingURL=price-list-currency-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-currency-reference.js","sourceRoot":"","sources":["../../../src/backend/services/price-list-currency-reference.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,UAAU,mCAAmC,CACjD,QAAyC,EACzC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC;QAChB,aAAa,EAAE,aAAa;QAC5B,QAAQ,EAAE,aAAa;QACvB,SAAS,EAAE,aAAa;QACxB,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACrC,mEAAmE,EACnE,CAAC,IAAI,CAAC,CACP,CAAyB,CAAC;YAC3B,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACxB;+EACuE,CACxE,CAA2C;KAC/C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PriceListReadPort, PriceListRecord } from '@endora-commerce/contracts';
3
+ import { PriceList } from '../entities/price-list.entity.js';
4
+ /**
5
+ * The row-level read model `price_lists` publishes (feature 075, Phase P).
6
+ *
7
+ * `organizations` resolves which lists apply to an organisation and
8
+ * `product_feeds` reads the list a feed prices from; both reached the entity
9
+ * class before this port existed.
10
+ *
11
+ * The customer-group half left with the entity in feature 076 (D-79) — see
12
+ * `customer_accounts/services/customer-group-read-port.ts`.
13
+ *
14
+ * `listActive` exists on the price-list side because "active" is a status the
15
+ * owning module defines and the schedule can move — a caller filtering on
16
+ * `status === 'active'` itself is a caller that will not notice the day
17
+ * `scheduled` starts counting.
18
+ */
19
+ export declare class PriceListReadService implements PriceListReadPort {
20
+ private readonly emFactory;
21
+ constructor(emFactory: () => EntityManager);
22
+ findById(id: string): Promise<PriceListRecord | null>;
23
+ findByIds(ids: readonly string[]): Promise<PriceListRecord[]>;
24
+ findByCode(code: string): Promise<PriceListRecord | null>;
25
+ listAll(): Promise<PriceListRecord[]>;
26
+ listActive(): Promise<PriceListRecord[]>;
27
+ }
28
+ export declare function toPriceListRecord(list: PriceList): PriceListRecord;
29
+ //# sourceMappingURL=price-list-read-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-read-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/price-list-read-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAKrD,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAM7D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAKzD,OAAO,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IASrC,UAAU,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;CAQ/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAkBlE"}
@@ -0,0 +1,64 @@
1
+ import { PriceList } from '../entities/price-list.entity.js';
2
+ /**
3
+ * The row-level read model `price_lists` publishes (feature 075, Phase P).
4
+ *
5
+ * `organizations` resolves which lists apply to an organisation and
6
+ * `product_feeds` reads the list a feed prices from; both reached the entity
7
+ * class before this port existed.
8
+ *
9
+ * The customer-group half left with the entity in feature 076 (D-79) — see
10
+ * `customer_accounts/services/customer-group-read-port.ts`.
11
+ *
12
+ * `listActive` exists on the price-list side because "active" is a status the
13
+ * owning module defines and the schedule can move — a caller filtering on
14
+ * `status === 'active'` itself is a caller that will not notice the day
15
+ * `scheduled` starts counting.
16
+ */
17
+ export class PriceListReadService {
18
+ emFactory;
19
+ constructor(emFactory) {
20
+ this.emFactory = emFactory;
21
+ }
22
+ async findById(id) {
23
+ const list = await this.emFactory().findOne(PriceList, { id });
24
+ return list ? toPriceListRecord(list) : null;
25
+ }
26
+ async findByIds(ids) {
27
+ if (ids.length === 0)
28
+ return [];
29
+ const lists = await this.emFactory().find(PriceList, { id: { $in: [...ids] } });
30
+ return lists.map(toPriceListRecord);
31
+ }
32
+ async findByCode(code) {
33
+ const list = await this.emFactory().findOne(PriceList, { code });
34
+ return list ? toPriceListRecord(list) : null;
35
+ }
36
+ async listAll() {
37
+ const lists = await this.emFactory().find(PriceList, {}, { orderBy: { priority: 'desc', code: 'asc' } });
38
+ return lists.map(toPriceListRecord);
39
+ }
40
+ async listActive() {
41
+ const lists = await this.emFactory().find(PriceList, { status: 'active' }, { orderBy: { priority: 'desc', code: 'asc' } });
42
+ return lists.map(toPriceListRecord);
43
+ }
44
+ }
45
+ export function toPriceListRecord(list) {
46
+ return {
47
+ id: list.id,
48
+ code: list.code,
49
+ name: list.name,
50
+ currency: list.currency,
51
+ isDefault: list.isDefault,
52
+ priority: list.priority,
53
+ type: list.type,
54
+ status: list.status,
55
+ startsAt: list.startsAt ?? null,
56
+ endsAt: list.endsAt ?? null,
57
+ applicationRule: list.applicationRule,
58
+ isSystem: list.isSystem,
59
+ modifiedAt: list.modifiedAt,
60
+ createdAt: list.createdAt,
61
+ updatedAt: list.updatedAt,
62
+ };
63
+ }
64
+ //# sourceMappingURL=price-list-read-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-list-read-port.js","sourceRoot":"","sources":["../../../src/backend/services/price-list-read-port.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,SAA8B;QAA9B,cAAS,GAAT,SAAS,CAAqB;IAAG,CAAC;IAE/D,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAsB;QACpC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;QAChF,OAAO,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACvC,SAAS,EACT,EAAE,EACF,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC/C,CAAC;QACF,OAAO,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CACvC,SAAS,EACT,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC/C,CAAC;QACF,OAAO,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACtC,CAAC;CACF;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAe;IAC/C,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;QAC3B,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC"}