@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 0.9.1-develop.7475.1.28ca93ed97

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 (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
  3. package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
  4. package/dist/generated/entities/customer_group_terms/index.js +2 -0
  5. package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
  6. package/dist/generated/entities/ecommerce_store/index.js +33 -0
  7. package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
  8. package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
  9. package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
  10. package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
  11. package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
  12. package/dist/generated/entities.ids.generated.js +8 -1
  13. package/dist/generated/entities.ids.generated.js.map +2 -2
  14. package/dist/generated/entity-fields-registry.js +72 -0
  15. package/dist/generated/entity-fields-registry.js.map +2 -2
  16. package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
  17. package/dist/modules/catalog/acl.js +8 -1
  18. package/dist/modules/catalog/acl.js.map +2 -2
  19. package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
  20. package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
  21. package/dist/modules/catalog/api/prices/history/route.js +131 -0
  22. package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
  23. package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
  24. package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
  25. package/dist/modules/catalog/api/products/route.js +54 -245
  26. package/dist/modules/catalog/api/products/route.js.map +3 -3
  27. package/dist/modules/catalog/api/tags/route.js +6 -0
  28. package/dist/modules/catalog/api/tags/route.js.map +2 -2
  29. package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
  30. package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
  31. package/dist/modules/catalog/cli.js +80 -2
  32. package/dist/modules/catalog/cli.js.map +2 -2
  33. package/dist/modules/catalog/commands/categories.js +51 -2
  34. package/dist/modules/catalog/commands/categories.js.map +2 -2
  35. package/dist/modules/catalog/commands/prices.js +43 -1
  36. package/dist/modules/catalog/commands/prices.js.map +2 -2
  37. package/dist/modules/catalog/commands/products.js +71 -0
  38. package/dist/modules/catalog/commands/products.js.map +2 -2
  39. package/dist/modules/catalog/commands/variants.js +7 -0
  40. package/dist/modules/catalog/commands/variants.js.map +2 -2
  41. package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
  42. package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
  43. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
  44. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
  45. package/dist/modules/catalog/components/lookupSources.js +31 -0
  46. package/dist/modules/catalog/components/lookupSources.js.map +7 -0
  47. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
  48. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
  49. package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
  50. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  51. package/dist/modules/catalog/data/entities.js +122 -1
  52. package/dist/modules/catalog/data/entities.js.map +2 -2
  53. package/dist/modules/catalog/data/validators.js +78 -0
  54. package/dist/modules/catalog/data/validators.js.map +2 -2
  55. package/dist/modules/catalog/di.js +18 -0
  56. package/dist/modules/catalog/di.js.map +2 -2
  57. package/dist/modules/catalog/lib/omnibus.js +285 -0
  58. package/dist/modules/catalog/lib/omnibus.js.map +7 -0
  59. package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
  60. package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
  61. package/dist/modules/catalog/lib/omnibusCache.js +89 -0
  62. package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
  63. package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
  64. package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
  65. package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
  66. package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
  67. package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
  68. package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
  69. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
  70. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
  71. package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
  72. package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
  73. package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
  74. package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
  75. package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
  76. package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
  77. package/dist/modules/catalog/lib/pricing.js +26 -2
  78. package/dist/modules/catalog/lib/pricing.js.map +2 -2
  79. package/dist/modules/catalog/lib/productFilters.js +273 -0
  80. package/dist/modules/catalog/lib/productFilters.js.map +7 -0
  81. package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
  82. package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
  83. package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
  84. package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
  85. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
  86. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
  87. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
  88. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
  89. package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
  90. package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
  91. package/dist/modules/catalog/setup.js +3 -1
  92. package/dist/modules/catalog/setup.js.map +2 -2
  93. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
  94. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
  95. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
  96. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
  97. package/dist/modules/catalog/translations.js +10 -0
  98. package/dist/modules/catalog/translations.js.map +2 -2
  99. package/dist/modules/configs/lib/upgrade-actions.js +28 -0
  100. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  101. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
  102. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
  103. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
  104. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
  105. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
  106. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
  107. package/dist/modules/customer_groups/data/entities.js +3 -0
  108. package/dist/modules/customer_groups/data/entities.js.map +2 -2
  109. package/dist/modules/customer_groups/data/validators.js +9 -0
  110. package/dist/modules/customer_groups/data/validators.js.map +2 -2
  111. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
  112. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
  113. package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
  114. package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
  115. package/dist/modules/ecommerce/acl.js +13 -0
  116. package/dist/modules/ecommerce/acl.js.map +7 -0
  117. package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
  118. package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
  119. package/dist/modules/ecommerce/api/openapi.js +27 -0
  120. package/dist/modules/ecommerce/api/openapi.js.map +7 -0
  121. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
  122. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
  123. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
  124. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
  125. package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
  126. package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
  127. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
  128. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
  129. package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
  130. package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
  131. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
  132. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
  133. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
  134. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
  135. package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
  136. package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
  137. package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
  138. package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
  139. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
  140. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
  141. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
  142. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
  143. package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
  144. package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
  145. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
  146. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
  147. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
  148. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
  149. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
  150. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
  151. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
  152. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
  153. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
  154. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
  155. package/dist/modules/ecommerce/api/stores/crud.js +401 -0
  156. package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
  157. package/dist/modules/ecommerce/api/stores/route.js +64 -0
  158. package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
  159. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
  160. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
  161. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
  162. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
  163. package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
  164. package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
  165. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
  166. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
  167. package/dist/modules/ecommerce/commands/stores.js +110 -0
  168. package/dist/modules/ecommerce/commands/stores.js.map +7 -0
  169. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
  170. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
  171. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
  172. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
  173. package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
  174. package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
  175. package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
  176. package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
  177. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
  178. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
  179. package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
  180. package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
  181. package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
  182. package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
  183. package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
  184. package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
  185. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
  186. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
  187. package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
  188. package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
  189. package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
  190. package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
  191. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
  192. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
  193. package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
  194. package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
  195. package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
  196. package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
  197. package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
  198. package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
  199. package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
  200. package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
  201. package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
  202. package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
  203. package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
  204. package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
  205. package/dist/modules/ecommerce/components/channelLookups.js +28 -0
  206. package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
  207. package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
  208. package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
  209. package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
  210. package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
  211. package/dist/modules/ecommerce/components/storeChannels.js +140 -0
  212. package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
  213. package/dist/modules/ecommerce/components/storeDomains.js +167 -0
  214. package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
  215. package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
  216. package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
  217. package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
  218. package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
  219. package/dist/modules/ecommerce/components/storeSeo.js +69 -0
  220. package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
  221. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
  222. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
  223. package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
  224. package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
  225. package/dist/modules/ecommerce/data/enrichers.js +157 -0
  226. package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
  227. package/dist/modules/ecommerce/data/entities.js +197 -0
  228. package/dist/modules/ecommerce/data/entities.js.map +7 -0
  229. package/dist/modules/ecommerce/data/validators.js +406 -0
  230. package/dist/modules/ecommerce/data/validators.js.map +7 -0
  231. package/dist/modules/ecommerce/di.js +11 -0
  232. package/dist/modules/ecommerce/di.js.map +7 -0
  233. package/dist/modules/ecommerce/events.js +27 -0
  234. package/dist/modules/ecommerce/events.js.map +7 -0
  235. package/dist/modules/ecommerce/index.js +16 -0
  236. package/dist/modules/ecommerce/index.js.map +7 -0
  237. package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
  238. package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
  239. package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
  240. package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
  241. package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
  242. package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
  243. package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
  244. package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
  245. package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
  246. package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
  247. package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
  248. package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
  249. package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
  250. package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
  251. package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
  252. package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
  253. package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
  254. package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
  255. package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
  256. package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
  257. package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
  258. package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
  259. package/dist/modules/ecommerce/lib/references.js +70 -0
  260. package/dist/modules/ecommerce/lib/references.js.map +7 -0
  261. package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
  262. package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
  263. package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
  264. package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
  265. package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
  266. package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
  267. package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
  268. package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
  269. package/dist/modules/ecommerce/lib/storeContext.js +393 -0
  270. package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
  271. package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
  272. package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
  273. package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
  274. package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
  275. package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
  276. package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
  277. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
  278. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
  279. package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
  280. package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
  281. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
  282. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
  283. package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
  284. package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
  285. package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
  286. package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
  287. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
  288. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
  289. package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
  290. package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
  291. package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
  292. package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
  293. package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
  294. package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
  295. package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
  296. package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
  297. package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
  298. package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
  299. package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
  300. package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
  301. package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
  302. package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
  303. package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
  304. package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
  305. package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
  306. package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
  307. package/dist/modules/ecommerce/lib/types.js +1 -0
  308. package/dist/modules/ecommerce/lib/types.js.map +7 -0
  309. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
  310. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
  311. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
  312. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
  313. package/dist/modules/ecommerce/notifications.client.js +47 -0
  314. package/dist/modules/ecommerce/notifications.client.js.map +7 -0
  315. package/dist/modules/ecommerce/notifications.js +48 -0
  316. package/dist/modules/ecommerce/notifications.js.map +7 -0
  317. package/dist/modules/ecommerce/search.js +83 -0
  318. package/dist/modules/ecommerce/search.js.map +7 -0
  319. package/dist/modules/ecommerce/setup.js +17 -0
  320. package/dist/modules/ecommerce/setup.js.map +7 -0
  321. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
  322. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
  323. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
  324. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
  325. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
  326. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
  327. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
  328. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
  329. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
  330. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
  331. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
  332. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
  333. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
  334. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
  335. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
  336. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
  337. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
  338. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
  339. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
  340. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
  341. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
  342. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
  343. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
  344. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
  345. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
  346. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
  347. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
  348. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
  349. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
  350. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
  351. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
  352. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
  353. package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
  354. package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
  355. package/dist/modules/query_index/lib/batch.js +23 -9
  356. package/dist/modules/query_index/lib/batch.js.map +2 -2
  357. package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
  358. package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
  359. package/dist/modules/query_index/lib/document.js +2 -0
  360. package/dist/modules/query_index/lib/document.js.map +2 -2
  361. package/dist/modules/query_index/lib/engine.js +68 -1
  362. package/dist/modules/query_index/lib/engine.js.map +2 -2
  363. package/dist/modules/query_index/lib/indexer.js +7 -0
  364. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  365. package/dist/modules/query_index/lib/search-tokens.js +2 -0
  366. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  367. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
  368. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
  369. package/dist/modules/translations/components/TranslationManager.js +3 -3
  370. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  371. package/dist/modules/translations/data/validators.js +1 -1
  372. package/dist/modules/translations/data/validators.js.map +1 -1
  373. package/dist/modules/translations/lib/batch.js +21 -1
  374. package/dist/modules/translations/lib/batch.js.map +2 -2
  375. package/dist/modules/translations/lib/helpers.js +3 -0
  376. package/dist/modules/translations/lib/helpers.js.map +2 -2
  377. package/dist/modules/translations/lib/resolve-field-list.js +14 -2
  378. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  379. package/generated/entities/catalog_price_history_entry/index.ts +25 -0
  380. package/generated/entities/customer_group_terms/index.ts +1 -0
  381. package/generated/entities/ecommerce_store/index.ts +15 -0
  382. package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
  383. package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
  384. package/generated/entities.ids.generated.ts +8 -1
  385. package/generated/entity-fields-registry.ts +72 -0
  386. package/package.json +7 -7
  387. package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
  388. package/src/modules/auth/i18n/de.json +8 -0
  389. package/src/modules/auth/i18n/en.json +8 -0
  390. package/src/modules/auth/i18n/es.json +8 -0
  391. package/src/modules/auth/i18n/ko.json +8 -0
  392. package/src/modules/auth/i18n/pl.json +8 -0
  393. package/src/modules/catalog/AGENTS.md +5 -2
  394. package/src/modules/catalog/acl.ts +7 -0
  395. package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
  396. package/src/modules/catalog/api/prices/history/route.ts +139 -0
  397. package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
  398. package/src/modules/catalog/api/products/route.ts +53 -308
  399. package/src/modules/catalog/api/tags/route.ts +6 -0
  400. package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
  401. package/src/modules/catalog/cli.ts +84 -1
  402. package/src/modules/catalog/commands/categories.ts +54 -2
  403. package/src/modules/catalog/commands/prices.ts +43 -1
  404. package/src/modules/catalog/commands/products.ts +84 -1
  405. package/src/modules/catalog/commands/variants.ts +7 -0
  406. package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
  407. package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
  408. package/src/modules/catalog/components/lookupSources.ts +29 -0
  409. package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
  410. package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
  411. package/src/modules/catalog/data/entities.ts +123 -1
  412. package/src/modules/catalog/data/validators.ts +118 -0
  413. package/src/modules/catalog/di.ts +23 -0
  414. package/src/modules/catalog/i18n/de.json +77 -0
  415. package/src/modules/catalog/i18n/en.json +77 -0
  416. package/src/modules/catalog/i18n/es.json +77 -0
  417. package/src/modules/catalog/i18n/ko.json +77 -0
  418. package/src/modules/catalog/i18n/pl.json +77 -0
  419. package/src/modules/catalog/lib/omnibus.ts +406 -0
  420. package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
  421. package/src/modules/catalog/lib/omnibusCache.ts +111 -0
  422. package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
  423. package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
  424. package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
  425. package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
  426. package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
  427. package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
  428. package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
  429. package/src/modules/catalog/lib/pricing.ts +42 -4
  430. package/src/modules/catalog/lib/productFilters.ts +349 -0
  431. package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
  432. package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
  433. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
  434. package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
  435. package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
  436. package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
  437. package/src/modules/catalog/setup.ts +2 -0
  438. package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
  439. package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
  440. package/src/modules/catalog/translations.ts +12 -0
  441. package/src/modules/configs/i18n/de.json +5 -1
  442. package/src/modules/configs/i18n/en.json +5 -1
  443. package/src/modules/configs/i18n/es.json +5 -1
  444. package/src/modules/configs/i18n/ko.json +5 -1
  445. package/src/modules/configs/i18n/pl.json +5 -1
  446. package/src/modules/configs/lib/upgrade-actions.ts +35 -0
  447. package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
  448. package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
  449. package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
  450. package/src/modules/customer_groups/data/entities.ts +4 -0
  451. package/src/modules/customer_groups/data/validators.ts +14 -0
  452. package/src/modules/customer_groups/i18n/de.json +12 -0
  453. package/src/modules/customer_groups/i18n/en.json +12 -0
  454. package/src/modules/customer_groups/i18n/es.json +12 -0
  455. package/src/modules/customer_groups/i18n/ko.json +12 -0
  456. package/src/modules/customer_groups/i18n/pl.json +12 -0
  457. package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
  458. package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
  459. package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
  460. package/src/modules/ecommerce/acl.ts +9 -0
  461. package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
  462. package/src/modules/ecommerce/api/openapi.ts +28 -0
  463. package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
  464. package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
  465. package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
  466. package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
  467. package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
  468. package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
  469. package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
  470. package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
  471. package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
  472. package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
  473. package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
  474. package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
  475. package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
  476. package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
  477. package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
  478. package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
  479. package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
  480. package/src/modules/ecommerce/api/stores/crud.ts +457 -0
  481. package/src/modules/ecommerce/api/stores/route.ts +66 -0
  482. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
  483. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
  484. package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
  485. package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
  486. package/src/modules/ecommerce/commands/stores.ts +154 -0
  487. package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
  488. package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
  489. package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
  490. package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
  491. package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
  492. package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
  493. package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
  494. package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
  495. package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
  496. package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
  497. package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
  498. package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
  499. package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
  500. package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
  501. package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
  502. package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
  503. package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
  504. package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
  505. package/src/modules/ecommerce/components/channelLookups.ts +24 -0
  506. package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
  507. package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
  508. package/src/modules/ecommerce/components/storeChannels.ts +210 -0
  509. package/src/modules/ecommerce/components/storeDomains.ts +221 -0
  510. package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
  511. package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
  512. package/src/modules/ecommerce/components/storeSeo.ts +78 -0
  513. package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
  514. package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
  515. package/src/modules/ecommerce/data/enrichers.ts +213 -0
  516. package/src/modules/ecommerce/data/entities.ts +168 -0
  517. package/src/modules/ecommerce/data/validators.ts +587 -0
  518. package/src/modules/ecommerce/di.ts +9 -0
  519. package/src/modules/ecommerce/events.ts +27 -0
  520. package/src/modules/ecommerce/i18n/de.json +341 -0
  521. package/src/modules/ecommerce/i18n/en.json +341 -0
  522. package/src/modules/ecommerce/i18n/es.json +341 -0
  523. package/src/modules/ecommerce/i18n/ko.json +341 -0
  524. package/src/modules/ecommerce/i18n/pl.json +341 -0
  525. package/src/modules/ecommerce/index.ts +14 -0
  526. package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
  527. package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
  528. package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
  529. package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
  530. package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
  531. package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
  532. package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
  533. package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
  534. package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
  535. package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
  536. package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
  537. package/src/modules/ecommerce/lib/references.ts +93 -0
  538. package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
  539. package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
  540. package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
  541. package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
  542. package/src/modules/ecommerce/lib/storeContext.ts +669 -0
  543. package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
  544. package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
  545. package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
  546. package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
  547. package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
  548. package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
  549. package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
  550. package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
  551. package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
  552. package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
  553. package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
  554. package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
  555. package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
  556. package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
  557. package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
  558. package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
  559. package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
  560. package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
  561. package/src/modules/ecommerce/lib/types.ts +69 -0
  562. package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
  563. package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
  564. package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
  565. package/src/modules/ecommerce/notifications.client.ts +46 -0
  566. package/src/modules/ecommerce/notifications.ts +46 -0
  567. package/src/modules/ecommerce/search.ts +93 -0
  568. package/src/modules/ecommerce/setup.ts +15 -0
  569. package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
  570. package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
  571. package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
  572. package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
  573. package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
  574. package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
  575. package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
  576. package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
  577. package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
  578. package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
  579. package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
  580. package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
  581. package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
  582. package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
  583. package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
  584. package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
  585. package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
  586. package/src/modules/query_index/lib/batch.ts +25 -9
  587. package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
  588. package/src/modules/query_index/lib/document.ts +2 -0
  589. package/src/modules/query_index/lib/engine.ts +58 -1
  590. package/src/modules/query_index/lib/indexer.ts +8 -0
  591. package/src/modules/query_index/lib/search-tokens.ts +2 -0
  592. package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
  593. package/src/modules/translations/components/TranslationManager.tsx +3 -3
  594. package/src/modules/translations/data/validators.ts +1 -1
  595. package/src/modules/translations/lib/batch.ts +47 -0
  596. package/src/modules/translations/lib/helpers.ts +4 -0
  597. package/src/modules/translations/lib/resolve-field-list.ts +16 -2
@@ -0,0 +1,43 @@
1
+ const ASSORTMENT_SCOPE_FIELDS = [
2
+ "categoryIds",
3
+ "tagIds",
4
+ "excludeProductIds",
5
+ "excludeCategoryIds",
6
+ "excludeTagIds"
7
+ ];
8
+ function normalizeIds(value) {
9
+ if (!Array.isArray(value)) return [];
10
+ const seen = /* @__PURE__ */ new Set();
11
+ const ids = [];
12
+ for (const entry of value) {
13
+ if (typeof entry !== "string") continue;
14
+ const trimmed = entry.trim();
15
+ if (!trimmed || seen.has(trimmed)) continue;
16
+ seen.add(trimmed);
17
+ ids.push(trimmed);
18
+ }
19
+ return ids;
20
+ }
21
+ function mapAssortmentScopeToFormValues(scope) {
22
+ return {
23
+ categoryIds: normalizeIds(scope?.categoryIds),
24
+ tagIds: normalizeIds(scope?.tagIds),
25
+ excludeProductIds: normalizeIds(scope?.excludeProductIds),
26
+ excludeCategoryIds: normalizeIds(scope?.excludeCategoryIds),
27
+ excludeTagIds: normalizeIds(scope?.excludeTagIds)
28
+ };
29
+ }
30
+ function buildAssortmentScopePayload(values) {
31
+ const scope = {};
32
+ for (const field of ASSORTMENT_SCOPE_FIELDS) {
33
+ const ids = normalizeIds(values[field]);
34
+ if (ids.length > 0) scope[field] = ids;
35
+ }
36
+ return Object.keys(scope).length > 0 ? scope : null;
37
+ }
38
+ export {
39
+ ASSORTMENT_SCOPE_FIELDS,
40
+ buildAssortmentScopePayload,
41
+ mapAssortmentScopeToFormValues
42
+ };
43
+ //# sourceMappingURL=customerGroupAssortmentScope.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_groups/components/customerGroupAssortmentScope.ts"],
4
+ "sourcesContent": ["export const ASSORTMENT_SCOPE_FIELDS = [\n 'categoryIds',\n 'tagIds',\n 'excludeProductIds',\n 'excludeCategoryIds',\n 'excludeTagIds',\n] as const\n\nexport type AssortmentScopeField = (typeof ASSORTMENT_SCOPE_FIELDS)[number]\n\nexport type GroupAssortmentScope = Partial<Record<AssortmentScopeField, string[]>>\n\nexport type AssortmentScopeFormValues = Record<AssortmentScopeField, string[]>\n\nfunction normalizeIds(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n const ids: string[] = []\n for (const entry of value) {\n if (typeof entry !== 'string') continue\n const trimmed = entry.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n ids.push(trimmed)\n }\n return ids\n}\n\nexport function mapAssortmentScopeToFormValues(scope: GroupAssortmentScope | null | undefined): AssortmentScopeFormValues {\n return {\n categoryIds: normalizeIds(scope?.categoryIds),\n tagIds: normalizeIds(scope?.tagIds),\n excludeProductIds: normalizeIds(scope?.excludeProductIds),\n excludeCategoryIds: normalizeIds(scope?.excludeCategoryIds),\n excludeTagIds: normalizeIds(scope?.excludeTagIds),\n }\n}\n\nexport function buildAssortmentScopePayload(values: Partial<Record<AssortmentScopeField, unknown>>): GroupAssortmentScope | null {\n const scope: GroupAssortmentScope = {}\n for (const field of ASSORTMENT_SCOPE_FIELDS) {\n const ids = normalizeIds(values[field])\n if (ids.length > 0) scope[field] = ids\n }\n return Object.keys(scope).length > 0 ? scope : null\n}\n"],
5
+ "mappings": "AAAO,MAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAS,aAAa,OAA0B;AAC9C,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,OAAO;AACzB,QAAI,OAAO,UAAU,SAAU;AAC/B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,WAAW,KAAK,IAAI,OAAO,EAAG;AACnC,SAAK,IAAI,OAAO;AAChB,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,SAAO;AACT;AAEO,SAAS,+BAA+B,OAA2E;AACxH,SAAO;AAAA,IACL,aAAa,aAAa,OAAO,WAAW;AAAA,IAC5C,QAAQ,aAAa,OAAO,MAAM;AAAA,IAClC,mBAAmB,aAAa,OAAO,iBAAiB;AAAA,IACxD,oBAAoB,aAAa,OAAO,kBAAkB;AAAA,IAC1D,eAAe,aAAa,OAAO,aAAa;AAAA,EAClD;AACF;AAEO,SAAS,4BAA4B,QAAqF;AAC/H,QAAM,QAA8B,CAAC;AACrC,aAAW,SAAS,yBAAyB;AAC3C,UAAM,MAAM,aAAa,OAAO,KAAK,CAAC;AACtC,QAAI,IAAI,SAAS,EAAG,OAAM,KAAK,IAAI;AAAA,EACrC;AACA,SAAO,OAAO,KAAK,KAAK,EAAE,SAAS,IAAI,QAAQ;AACjD;",
6
+ "names": []
7
+ }
@@ -198,6 +198,9 @@ __decorateClass([
198
198
  __decorateClass([
199
199
  Property({ name: "min_order_value", type: "numeric", precision: 16, scale: 2, nullable: true })
200
200
  ], CustomerGroupTerms.prototype, "minOrderValue", 2);
201
+ __decorateClass([
202
+ Property({ name: "assortment_scope", type: "jsonb", nullable: true })
203
+ ], CustomerGroupTerms.prototype, "assortmentScope", 2);
201
204
  __decorateClass([
202
205
  Property({ type: "jsonb", nullable: true })
203
206
  ], CustomerGroupTerms.prototype, "metadata", 2);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_groups/data/entities.ts"],
4
- "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\n\nexport type CustomerGroupKind = 'b2c' | 'b2b' | 'internal' | 'partner'\nexport type CustomerGroupMembershipSource = 'manual' | 'import' | 'rule' | 'onboarding'\n\n// Groups are tenant-scoped, not organization-scoped \u2014 spec \u00A75 note: \"groups are\n// tenant-scoped here, consistent with `CatalogPriceKind`, which allows a null\n// `organization_id`\" (see .ai/specs/2026-08-14-customer-groups-and-b2b-terms.md \u00A7Open\n// Questions #2). `organizationId` is kept as a nullable column (mirroring\n// `CatalogPriceKind`) rather than dropped, so a future per-organization private-group\n// phase (explicitly out of scope here) can scope rows without a schema change.\n@Entity({ tableName: 'customer_groups' })\n@Index({ name: 'customer_groups_tenant_idx', properties: ['tenantId'] })\n@Index({ name: 'customer_groups_parent_idx', properties: ['parentId'] })\n@Index({\n // Partial for the same reason as the priority index below: a soft-deleted group must\n // not keep its code reserved, otherwise recreating it hits a raw unique violation.\n name: 'customer_groups_tenant_code_unique',\n expression:\n 'create unique index \"customer_groups_tenant_code_unique\" on \"customer_groups\" (\"tenant_id\", \"code\") where \"deleted_at\" is null',\n})\n@Index({\n // Partial (not @Unique) so a soft-deleted group's old priority value doesn't stay\n // permanently reserved \u2014 found during Step 1.6/1.7: the admin list's drag-reorder\n // rewrites priorities in gaps of 10, which would otherwise collide with any\n // previously-deleted row's still-unique priority and 409 forever, not just on a\n // transient race. Mirrors `customer_groups_tenant_default_unique` below.\n name: 'customer_groups_tenant_priority_unique',\n expression:\n 'create unique index \"customer_groups_tenant_priority_unique\" on \"customer_groups\" (\"tenant_id\", \"priority\") where \"deleted_at\" is null',\n})\n@Index({\n // \"At most one is_default per tenant\" (spec \u00A75.1) is primarily an application-level\n // invariant enforced in the create/update command, but a partial unique index gives\n // defense-in-depth against races/direct writes at negligible cost \u2014 this repo already\n // has the identical shape for \"at most one is_primary per deal\" (see\n // `customer_deal_people_primary_uq` in the customers module). Soft-deleted rows are\n // excluded so a deleted former default doesn't block promoting a new one.\n name: 'customer_groups_tenant_default_unique',\n expression:\n 'create unique index \"customer_groups_tenant_default_unique\" on \"customer_groups\" (\"tenant_id\") where \"is_default\" and \"deleted_at\" is null',\n})\nexport class CustomerGroup {\n [OptionalProps]?: 'isDefault' | 'isActive' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ type: 'text' })\n code!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ type: 'text' })\n kind!: CustomerGroupKind\n\n // Self-referencing hierarchy stored as a plain uuid column (not a MikroORM\n // `@ManyToOne` self-relation) to match this codebase's established pattern for\n // parent/child ids within the same module \u2014 see `CatalogCategory.parentId` in\n // `packages/core/src/modules/catalog/data/entities.ts`. Cycle prevention (depth\n // capped at 5 per spec \u00A75.1) is enforced in the create/update command, not the ORM.\n @Property({ name: 'parent_id', type: 'uuid', nullable: true })\n parentId?: string | null\n\n @Property({ type: 'int' })\n priority!: number\n\n // DB-level defense-in-depth is `customer_groups_tenant_default_unique` above; the\n // primary enforcement (clear-and-set semantics when reassigning the default) is the\n // create/update command's job, not the ORM's.\n @Property({ name: 'is_default', type: 'boolean', default: false })\n isDefault: boolean = false\n\n @Property({ name: 'is_active', type: 'boolean', default: true })\n isActive: boolean = true\n\n @Property({ type: 'jsonb', nullable: true })\n metadata?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// `group_id`/`customer_id` are plain uuid columns rather than MikroORM relations:\n// `customerId` crosses into the `customers` module, which the root AGENTS.md forbids\n// (\"Never create direct ORM relationships between modules\" \u2014 FK id only, resolved\n// separately). `groupId` stays a plain column too (not `@ManyToOne(() => CustomerGroup)`)\n// for symmetry with the customer-id column and to match this codebase's convention for\n// scoped junction/assignment tables that reference a same-module parent by id only\n// (e.g. `CustomerPipelineStage.pipelineId` in the customers module).\n@Entity({ tableName: 'customer_group_memberships' })\n@Index({ name: 'customer_group_memberships_tenant_idx', properties: ['tenantId'] })\n@Index({\n name: 'customer_group_memberships_hot_path_idx',\n properties: ['tenantId', 'customerId', 'validFrom', 'validUntil'],\n})\n@Index({\n // Spec \u00A75.2: unique `(tenant_id, group_id, customer_id)` among rows with\n // `deleted_at IS NULL` \u2014 expressed as a partial unique index (not `@Unique`, which\n // has no WHERE clause) so a removed (soft-deleted) membership can be re-added without\n // colliding with its own tombstoned row. Mirrors\n // `customer_person_company_links_active_unique` in the customers module.\n name: 'customer_group_memberships_active_unique',\n expression:\n 'create unique index \"customer_group_memberships_active_unique\" on \"customer_group_memberships\" (\"tenant_id\", \"group_id\", \"customer_id\") where \"deleted_at\" is null',\n})\nexport class CustomerGroupMembership {\n [OptionalProps]?: 'source' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'group_id', type: 'uuid' })\n groupId!: string\n\n @Property({ name: 'customer_id', type: 'uuid' })\n customerId!: string\n\n @Property({ type: 'text', default: 'manual' })\n source: CustomerGroupMembershipSource = 'manual'\n\n @Property({ name: 'valid_from', type: Date, nullable: true })\n validFrom?: Date | null\n\n @Property({ name: 'valid_until', type: Date, nullable: true })\n validUntil?: Date | null\n\n @Property({ name: 'assigned_by_user_id', type: 'uuid', nullable: true })\n assignedByUserId?: string | null\n\n @Property({ type: 'text', nullable: true })\n notes?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// `group_id` is a plain uuid column rather than a MikroORM relation, for the same\n// reason as `CustomerGroupMembership.groupId` above: this repo's convention for\n// scoped assignment tables that reference a same-module parent by id only (e.g.\n// `CustomerPipelineStage.pipelineId`). `priceKindId` crosses into the `catalog`\n// module, so it stays an FK id only (no relation) per the root AGENTS.md ban on\n// cross-module ORM relationships.\n//\n// Terms rows are optional per group (spec: \"a group with no terms row yet shows...\n// inheriting\"), so a terms row can be removed and later re-created for the same\n// group \u2014 the unique constraint on `group_id` is therefore a partial index scoped\n// to `deleted_at IS NULL`, mirroring `customer_group_memberships_active_unique`\n// above (and `customer_groups_tenant_priority_unique`'s soft-delete fix), rather\n// than a plain `@Unique`.\n//\n// `organizationId` mirrors `CustomerGroup.organizationId`: tenant-scoped, not\n// organization-scoped (spec \u00A75 note re: `CatalogPriceKind`'s nullable\n// `organization_id`), kept nullable rather than dropped for a future\n// per-organization phase.\n@Entity({ tableName: 'customer_group_terms' })\n@Index({ name: 'customer_group_terms_tenant_idx', properties: ['tenantId'] })\n@Index({\n name: 'customer_group_terms_group_unique',\n expression:\n 'create unique index \"customer_group_terms_group_unique\" on \"customer_group_terms\" (\"group_id\") where \"deleted_at\" is null',\n})\nexport class CustomerGroupTerms {\n [OptionalProps]?:\n | 'createdAt'\n | 'updatedAt'\n | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'group_id', type: 'uuid' })\n groupId!: string\n\n @Property({ name: 'price_kind_id', type: 'uuid', nullable: true })\n priceKindId?: string | null\n\n @Property({ name: 'payment_terms_days', type: 'int', nullable: true })\n paymentTermsDays?: number | null\n\n // Nullable like every other terms field: `null` means \"not set on this group, inherit\n // from the parent chain / tenant default\", so a group can override only some fields.\n @Property({ name: 'allow_purchase_on_account', type: 'boolean', nullable: true })\n allowPurchaseOnAccount?: boolean | null\n\n @Property({ name: 'default_credit_limit', type: 'numeric', precision: 16, scale: 2, nullable: true })\n defaultCreditLimit?: string | null\n\n @Property({ name: 'credit_currency_code', type: 'text', nullable: true })\n creditCurrencyCode?: string | null\n\n @Property({ name: 'approval_required_above', type: 'numeric', precision: 16, scale: 2, nullable: true })\n approvalRequiredAbove?: string | null\n\n @Property({ name: 'min_order_value', type: 'numeric', precision: 16, scale: 2, nullable: true })\n minOrderValue?: string | null\n\n @Property({ type: 'jsonb', nullable: true })\n metadata?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { OptionalProps } from '@mikro-orm/core'\nimport { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\nimport type { AssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\n\nexport type CustomerGroupKind = 'b2c' | 'b2b' | 'internal' | 'partner'\nexport type CustomerGroupMembershipSource = 'manual' | 'import' | 'rule' | 'onboarding'\n\n// Groups are tenant-scoped, not organization-scoped \u2014 spec \u00A75 note: \"groups are\n// tenant-scoped here, consistent with `CatalogPriceKind`, which allows a null\n// `organization_id`\" (see .ai/specs/2026-08-14-customer-groups-and-b2b-terms.md \u00A7Open\n// Questions #2). `organizationId` is kept as a nullable column (mirroring\n// `CatalogPriceKind`) rather than dropped, so a future per-organization private-group\n// phase (explicitly out of scope here) can scope rows without a schema change.\n@Entity({ tableName: 'customer_groups' })\n@Index({ name: 'customer_groups_tenant_idx', properties: ['tenantId'] })\n@Index({ name: 'customer_groups_parent_idx', properties: ['parentId'] })\n@Index({\n // Partial for the same reason as the priority index below: a soft-deleted group must\n // not keep its code reserved, otherwise recreating it hits a raw unique violation.\n name: 'customer_groups_tenant_code_unique',\n expression:\n 'create unique index \"customer_groups_tenant_code_unique\" on \"customer_groups\" (\"tenant_id\", \"code\") where \"deleted_at\" is null',\n})\n@Index({\n // Partial (not @Unique) so a soft-deleted group's old priority value doesn't stay\n // permanently reserved \u2014 found during Step 1.6/1.7: the admin list's drag-reorder\n // rewrites priorities in gaps of 10, which would otherwise collide with any\n // previously-deleted row's still-unique priority and 409 forever, not just on a\n // transient race. Mirrors `customer_groups_tenant_default_unique` below.\n name: 'customer_groups_tenant_priority_unique',\n expression:\n 'create unique index \"customer_groups_tenant_priority_unique\" on \"customer_groups\" (\"tenant_id\", \"priority\") where \"deleted_at\" is null',\n})\n@Index({\n // \"At most one is_default per tenant\" (spec \u00A75.1) is primarily an application-level\n // invariant enforced in the create/update command, but a partial unique index gives\n // defense-in-depth against races/direct writes at negligible cost \u2014 this repo already\n // has the identical shape for \"at most one is_primary per deal\" (see\n // `customer_deal_people_primary_uq` in the customers module). Soft-deleted rows are\n // excluded so a deleted former default doesn't block promoting a new one.\n name: 'customer_groups_tenant_default_unique',\n expression:\n 'create unique index \"customer_groups_tenant_default_unique\" on \"customer_groups\" (\"tenant_id\") where \"is_default\" and \"deleted_at\" is null',\n})\nexport class CustomerGroup {\n [OptionalProps]?: 'isDefault' | 'isActive' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ type: 'text' })\n code!: string\n\n @Property({ type: 'text' })\n name!: string\n\n @Property({ type: 'text', nullable: true })\n description?: string | null\n\n @Property({ type: 'text' })\n kind!: CustomerGroupKind\n\n // Self-referencing hierarchy stored as a plain uuid column (not a MikroORM\n // `@ManyToOne` self-relation) to match this codebase's established pattern for\n // parent/child ids within the same module \u2014 see `CatalogCategory.parentId` in\n // `packages/core/src/modules/catalog/data/entities.ts`. Cycle prevention (depth\n // capped at 5 per spec \u00A75.1) is enforced in the create/update command, not the ORM.\n @Property({ name: 'parent_id', type: 'uuid', nullable: true })\n parentId?: string | null\n\n @Property({ type: 'int' })\n priority!: number\n\n // DB-level defense-in-depth is `customer_groups_tenant_default_unique` above; the\n // primary enforcement (clear-and-set semantics when reassigning the default) is the\n // create/update command's job, not the ORM's.\n @Property({ name: 'is_default', type: 'boolean', default: false })\n isDefault: boolean = false\n\n @Property({ name: 'is_active', type: 'boolean', default: true })\n isActive: boolean = true\n\n @Property({ type: 'jsonb', nullable: true })\n metadata?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// `group_id`/`customer_id` are plain uuid columns rather than MikroORM relations:\n// `customerId` crosses into the `customers` module, which the root AGENTS.md forbids\n// (\"Never create direct ORM relationships between modules\" \u2014 FK id only, resolved\n// separately). `groupId` stays a plain column too (not `@ManyToOne(() => CustomerGroup)`)\n// for symmetry with the customer-id column and to match this codebase's convention for\n// scoped junction/assignment tables that reference a same-module parent by id only\n// (e.g. `CustomerPipelineStage.pipelineId` in the customers module).\n@Entity({ tableName: 'customer_group_memberships' })\n@Index({ name: 'customer_group_memberships_tenant_idx', properties: ['tenantId'] })\n@Index({\n name: 'customer_group_memberships_hot_path_idx',\n properties: ['tenantId', 'customerId', 'validFrom', 'validUntil'],\n})\n@Index({\n // Spec \u00A75.2: unique `(tenant_id, group_id, customer_id)` among rows with\n // `deleted_at IS NULL` \u2014 expressed as a partial unique index (not `@Unique`, which\n // has no WHERE clause) so a removed (soft-deleted) membership can be re-added without\n // colliding with its own tombstoned row. Mirrors\n // `customer_person_company_links_active_unique` in the customers module.\n name: 'customer_group_memberships_active_unique',\n expression:\n 'create unique index \"customer_group_memberships_active_unique\" on \"customer_group_memberships\" (\"tenant_id\", \"group_id\", \"customer_id\") where \"deleted_at\" is null',\n})\nexport class CustomerGroupMembership {\n [OptionalProps]?: 'source' | 'createdAt' | 'updatedAt' | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'group_id', type: 'uuid' })\n groupId!: string\n\n @Property({ name: 'customer_id', type: 'uuid' })\n customerId!: string\n\n @Property({ type: 'text', default: 'manual' })\n source: CustomerGroupMembershipSource = 'manual'\n\n @Property({ name: 'valid_from', type: Date, nullable: true })\n validFrom?: Date | null\n\n @Property({ name: 'valid_until', type: Date, nullable: true })\n validUntil?: Date | null\n\n @Property({ name: 'assigned_by_user_id', type: 'uuid', nullable: true })\n assignedByUserId?: string | null\n\n @Property({ type: 'text', nullable: true })\n notes?: string | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n\n// `group_id` is a plain uuid column rather than a MikroORM relation, for the same\n// reason as `CustomerGroupMembership.groupId` above: this repo's convention for\n// scoped assignment tables that reference a same-module parent by id only (e.g.\n// `CustomerPipelineStage.pipelineId`). `priceKindId` crosses into the `catalog`\n// module, so it stays an FK id only (no relation) per the root AGENTS.md ban on\n// cross-module ORM relationships.\n//\n// Terms rows are optional per group (spec: \"a group with no terms row yet shows...\n// inheriting\"), so a terms row can be removed and later re-created for the same\n// group \u2014 the unique constraint on `group_id` is therefore a partial index scoped\n// to `deleted_at IS NULL`, mirroring `customer_group_memberships_active_unique`\n// above (and `customer_groups_tenant_priority_unique`'s soft-delete fix), rather\n// than a plain `@Unique`.\n//\n// `organizationId` mirrors `CustomerGroup.organizationId`: tenant-scoped, not\n// organization-scoped (spec \u00A75 note re: `CatalogPriceKind`'s nullable\n// `organization_id`), kept nullable rather than dropped for a future\n// per-organization phase.\n@Entity({ tableName: 'customer_group_terms' })\n@Index({ name: 'customer_group_terms_tenant_idx', properties: ['tenantId'] })\n@Index({\n name: 'customer_group_terms_group_unique',\n expression:\n 'create unique index \"customer_group_terms_group_unique\" on \"customer_group_terms\" (\"group_id\") where \"deleted_at\" is null',\n})\nexport class CustomerGroupTerms {\n [OptionalProps]?:\n | 'createdAt'\n | 'updatedAt'\n | 'deletedAt'\n\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId?: string | null\n\n @Property({ name: 'tenant_id', type: 'uuid' })\n tenantId!: string\n\n @Property({ name: 'group_id', type: 'uuid' })\n groupId!: string\n\n @Property({ name: 'price_kind_id', type: 'uuid', nullable: true })\n priceKindId?: string | null\n\n @Property({ name: 'payment_terms_days', type: 'int', nullable: true })\n paymentTermsDays?: number | null\n\n // Nullable like every other terms field: `null` means \"not set on this group, inherit\n // from the parent chain / tenant default\", so a group can override only some fields.\n @Property({ name: 'allow_purchase_on_account', type: 'boolean', nullable: true })\n allowPurchaseOnAccount?: boolean | null\n\n @Property({ name: 'default_credit_limit', type: 'numeric', precision: 16, scale: 2, nullable: true })\n defaultCreditLimit?: string | null\n\n @Property({ name: 'credit_currency_code', type: 'text', nullable: true })\n creditCurrencyCode?: string | null\n\n @Property({ name: 'approval_required_above', type: 'numeric', precision: 16, scale: 2, nullable: true })\n approvalRequiredAbove?: string | null\n\n @Property({ name: 'min_order_value', type: 'numeric', precision: 16, scale: 2, nullable: true })\n minOrderValue?: string | null\n\n @Property({ name: 'assortment_scope', type: 'jsonb', nullable: true })\n assortmentScope?: AssortmentScope | null\n\n @Property({ type: 'jsonb', nullable: true })\n metadata?: Record<string, unknown> | null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt?: Date | null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -111,6 +111,13 @@ const clearableNonNegativeNumberSchema = z.preprocess(
111
111
  z.coerce.number().min(0).max(NUMERIC_16_2_MAX).nullable().optional()
112
112
  );
113
113
  const currencyCodeSchema = clearableStringSchema(4);
114
+ const assortmentScopeSchema = z.object({
115
+ categoryIds: z.array(uuid()).optional(),
116
+ tagIds: z.array(uuid()).optional(),
117
+ excludeProductIds: z.array(uuid()).optional(),
118
+ excludeCategoryIds: z.array(uuid()).optional(),
119
+ excludeTagIds: z.array(uuid()).optional()
120
+ }).strict().nullable();
114
121
  const customerGroupTermsBaseShape = {
115
122
  organizationId: uuid().nullable().optional(),
116
123
  tenantId: uuid(),
@@ -122,6 +129,7 @@ const customerGroupTermsBaseShape = {
122
129
  creditCurrencyCode: currencyCodeSchema,
123
130
  approvalRequiredAbove: clearableNonNegativeNumberSchema,
124
131
  minOrderValue: clearableNonNegativeNumberSchema,
132
+ assortmentScope: assortmentScopeSchema.optional(),
125
133
  metadata: clearableMetadataSchema
126
134
  };
127
135
  const customerGroupTermsCreateSchema = z.object(customerGroupTermsBaseShape);
@@ -133,6 +141,7 @@ const customerGroupTermsDeleteSchema = z.object({
133
141
  });
134
142
  export {
135
143
  CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH,
144
+ assortmentScopeSchema,
136
145
  customerGroupCreateSchema,
137
146
  customerGroupDeleteSchema,
138
147
  customerGroupKindSchema,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_groups/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\n\nconst uuid = () => z.string().uuid()\n\nconst emptyStringToNull = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\n// Plain optional string fields that map to nullable columns: blanking a previously-set\n// value on edit must transmit null to clear it, not be silently dropped. Mirrors\n// `clearableStringSchema` in `customers/data/validators.ts`.\nconst clearableStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, z.string().trim().max(max).nullable().optional())\n\nconst clearableDateSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.date().nullable().optional(),\n)\n\nconst clearableUuidSchema = z.preprocess(emptyStringToNull, uuid().nullable().optional())\n\nconst clearableMetadataSchema = z.preprocess(\n (value) => (value === '' ? null : value),\n z.record(z.string(), z.unknown()).nullable().optional(),\n)\n\n// Slug-safe identifier used as the stable code across imports and rules (spec \u00A75.1:\n// \"stable identifier used in imports and rules\"). Mirrors the `tagCreateSchema.slug`\n// pattern in `customers/data/validators.ts`.\nconst codeSchema = z\n .string()\n .trim()\n .min(1)\n .max(80)\n .regex(/^[a-z0-9_-]+$/, 'Code must be lowercase and may contain dashes or underscores')\n\nconst nameSchema = z.string().trim().min(1).max(200)\n\n// Spec \u00A75.1: a group's ancestor chain (the group itself plus every parent up to the\n// root) is capped at 5 levels. Also the walk limit of `customerGroupsService`'s\n// ancestor resolution \u2014 anything deeper would silently lose inherited terms.\nexport const CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH = 5\n\n// Column bounds: without them an out-of-range value reaches Postgres and fails as a raw\n// numeric-overflow 500 instead of a 400. `int4` is the `int` column type of `priority`\n// and `payment_terms_days`; `numeric(16,2)` holds at most 14 integer digits.\nconst INT4_MAX = 2147483647\n// Priorities are floored well above `int4`'s minimum: reorder parks rows below the\n// tenant's lowest priority and adopt places placeholders 10 below it, and both must stay\n// inside `int4` or they fail as a raw overflow 500.\nconst PRIORITY_MIN = -1000000000\nconst NUMERIC_16_2_MAX = 99999999999999.99\n\nexport const customerGroupKindValues = ['b2c', 'b2b', 'internal', 'partner'] as const\nexport const customerGroupKindSchema = z.enum(customerGroupKindValues)\n\nexport const customerGroupMembershipSourceValues = ['manual', 'import', 'rule', 'onboarding'] as const\nexport const customerGroupMembershipSourceSchema = z.enum(customerGroupMembershipSourceValues)\n\n// Default-free field set shared by the create and update schemas. Zod v4's `.partial()`\n// keeps an inner `.default()`, so deriving the update schema from the create schema\n// injected `isDefault: false` / `isActive: true` into every partial update and silently\n// overwrote stored values. Defaults live on the create schema only.\nconst customerGroupBaseShape = {\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n code: codeSchema,\n name: nameSchema,\n description: clearableStringSchema(4000),\n kind: customerGroupKindSchema,\n parentId: clearableUuidSchema,\n // Not floored at 0: reconcile adopt places orphan placeholders below the tenant's\n // lowest priority (spec \u00A78.2: \"priority at the bottom\"), which can be negative, and the\n // edit form must still be able to save such a group.\n priority: z.number().int().min(PRIORITY_MIN).max(INT4_MAX),\n isDefault: z.boolean(),\n isActive: z.boolean(),\n metadata: clearableMetadataSchema,\n}\n\nexport const customerGroupCreateSchema = z.object({\n ...customerGroupBaseShape,\n isDefault: z.boolean().optional().default(false),\n isActive: z.boolean().optional().default(true),\n})\n\nexport type CustomerGroupCreateInput = z.infer<typeof customerGroupCreateSchema>\n\nexport const customerGroupUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(z.object(customerGroupBaseShape).partial())\n\nexport type CustomerGroupUpdateInput = z.infer<typeof customerGroupUpdateSchema>\n\nexport const customerGroupDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupDeleteInput = z.infer<typeof customerGroupDeleteSchema>\n\nconst customerGroupMembershipCreateBaseSchema = z.object({\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid(),\n customerId: uuid(),\n source: customerGroupMembershipSourceSchema.optional().default('manual'),\n validFrom: clearableDateSchema,\n validUntil: clearableDateSchema,\n assignedByUserId: clearableUuidSchema,\n notes: clearableStringSchema(2000),\n})\n\n// Server-owned fields: the tenant comes from the session, the organization from the\n// referenced customer, and `assignedByUserId` from the acting user.\nconst membershipServerOwnedFields = { organizationId: true, tenantId: true, assignedByUserId: true } as const\n\nexport const customerGroupMembershipCreateRequestSchema =\n customerGroupMembershipCreateBaseSchema.omit(membershipServerOwnedFields)\n\nexport const customerGroupMembershipCreateSchema = customerGroupMembershipCreateBaseSchema\n .refine(\n (payload) =>\n !payload.validFrom || !payload.validUntil || payload.validFrom <= payload.validUntil,\n {\n message: 'validFrom must be before or equal to validUntil',\n path: ['validUntil'],\n },\n )\n\nexport type CustomerGroupMembershipCreateInput = z.infer<typeof customerGroupMembershipCreateSchema>\n\nconst customerGroupMembershipUpdateBaseSchema = z\n .object({\n id: uuid(),\n })\n .merge(\n z.object({\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid().optional(),\n customerId: uuid().optional(),\n source: customerGroupMembershipSourceSchema.optional(),\n validFrom: clearableDateSchema,\n validUntil: clearableDateSchema,\n assignedByUserId: clearableUuidSchema,\n notes: clearableStringSchema(2000),\n }),\n )\n\nexport const customerGroupMembershipUpdateRequestSchema =\n customerGroupMembershipUpdateBaseSchema.omit(membershipServerOwnedFields)\n\nexport const customerGroupMembershipUpdateSchema = customerGroupMembershipUpdateBaseSchema.refine(\n (payload) =>\n !payload.validFrom || !payload.validUntil || payload.validFrom <= payload.validUntil,\n {\n message: 'validFrom must be before or equal to validUntil',\n path: ['validUntil'],\n },\n)\n\nexport type CustomerGroupMembershipUpdateInput = z.infer<typeof customerGroupMembershipUpdateSchema>\n\nexport const customerGroupMembershipDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupMembershipDeleteInput = z.infer<typeof customerGroupMembershipDeleteSchema>\n\n// Drag-reorder payload for the admin group list (Step 1.7): an ordered array of\n// `CustomerGroup` ids. A full ordering renumbers `priority` in gaps of 10; a partial\n// one permutes the listed groups' existing priorities \u2014 see commands/reorderGroups.ts.\nexport const customerGroupReorderSchema = z.object({\n tenantId: uuid(),\n ids: z.array(uuid()).min(1),\n})\n\nexport type CustomerGroupReorderInput = z.infer<typeof customerGroupReorderSchema>\n\n// Optional/nullable non-negative number field (payment days, credit/order thresholds):\n// blanking a previously-set value on edit must transmit null to clear it (same rationale\n// as `clearableStringSchema` above), and negative input is rejected server-side to match\n// the UI's inline validation (spec acceptance criterion: \"Numeric fields... reject\n// negative input inline\").\nconst clearableNonNegativeIntSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.number().int().min(0).max(INT4_MAX).nullable().optional(),\n)\n\nconst clearableNonNegativeNumberSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.number().min(0).max(NUMERIC_16_2_MAX).nullable().optional(),\n)\n\nconst currencyCodeSchema = clearableStringSchema(4)\n\n// Default-free, for the same reason as `customerGroupBaseShape` above.\nconst customerGroupTermsBaseShape = {\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid(),\n priceKindId: clearableUuidSchema,\n paymentTermsDays: clearableNonNegativeIntSchema,\n allowPurchaseOnAccount: z.boolean().nullable().optional(),\n defaultCreditLimit: clearableNonNegativeNumberSchema,\n creditCurrencyCode: currencyCodeSchema,\n approvalRequiredAbove: clearableNonNegativeNumberSchema,\n minOrderValue: clearableNonNegativeNumberSchema,\n metadata: clearableMetadataSchema,\n}\n\nexport const customerGroupTermsCreateSchema = z.object(customerGroupTermsBaseShape)\n\nexport type CustomerGroupTermsCreateInput = z.infer<typeof customerGroupTermsCreateSchema>\n\nexport const customerGroupTermsUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(z.object(customerGroupTermsBaseShape).partial())\n\nexport type CustomerGroupTermsUpdateInput = z.infer<typeof customerGroupTermsUpdateSchema>\n\nexport const customerGroupTermsDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupTermsDeleteInput = z.infer<typeof customerGroupTermsDeleteSchema>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\n\nconst uuid = () => z.string().uuid()\n\nconst emptyStringToNull = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\n// Plain optional string fields that map to nullable columns: blanking a previously-set\n// value on edit must transmit null to clear it, not be silently dropped. Mirrors\n// `clearableStringSchema` in `customers/data/validators.ts`.\nconst clearableStringSchema = (max: number) =>\n z.preprocess(emptyStringToNull, z.string().trim().max(max).nullable().optional())\n\nconst clearableDateSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.date().nullable().optional(),\n)\n\nconst clearableUuidSchema = z.preprocess(emptyStringToNull, uuid().nullable().optional())\n\nconst clearableMetadataSchema = z.preprocess(\n (value) => (value === '' ? null : value),\n z.record(z.string(), z.unknown()).nullable().optional(),\n)\n\n// Slug-safe identifier used as the stable code across imports and rules (spec \u00A75.1:\n// \"stable identifier used in imports and rules\"). Mirrors the `tagCreateSchema.slug`\n// pattern in `customers/data/validators.ts`.\nconst codeSchema = z\n .string()\n .trim()\n .min(1)\n .max(80)\n .regex(/^[a-z0-9_-]+$/, 'Code must be lowercase and may contain dashes or underscores')\n\nconst nameSchema = z.string().trim().min(1).max(200)\n\n// Spec \u00A75.1: a group's ancestor chain (the group itself plus every parent up to the\n// root) is capped at 5 levels. Also the walk limit of `customerGroupsService`'s\n// ancestor resolution \u2014 anything deeper would silently lose inherited terms.\nexport const CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH = 5\n\n// Column bounds: without them an out-of-range value reaches Postgres and fails as a raw\n// numeric-overflow 500 instead of a 400. `int4` is the `int` column type of `priority`\n// and `payment_terms_days`; `numeric(16,2)` holds at most 14 integer digits.\nconst INT4_MAX = 2147483647\n// Priorities are floored well above `int4`'s minimum: reorder parks rows below the\n// tenant's lowest priority and adopt places placeholders 10 below it, and both must stay\n// inside `int4` or they fail as a raw overflow 500.\nconst PRIORITY_MIN = -1000000000\nconst NUMERIC_16_2_MAX = 99999999999999.99\n\nexport const customerGroupKindValues = ['b2c', 'b2b', 'internal', 'partner'] as const\nexport const customerGroupKindSchema = z.enum(customerGroupKindValues)\n\nexport const customerGroupMembershipSourceValues = ['manual', 'import', 'rule', 'onboarding'] as const\nexport const customerGroupMembershipSourceSchema = z.enum(customerGroupMembershipSourceValues)\n\n// Default-free field set shared by the create and update schemas. Zod v4's `.partial()`\n// keeps an inner `.default()`, so deriving the update schema from the create schema\n// injected `isDefault: false` / `isActive: true` into every partial update and silently\n// overwrote stored values. Defaults live on the create schema only.\nconst customerGroupBaseShape = {\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n code: codeSchema,\n name: nameSchema,\n description: clearableStringSchema(4000),\n kind: customerGroupKindSchema,\n parentId: clearableUuidSchema,\n // Not floored at 0: reconcile adopt places orphan placeholders below the tenant's\n // lowest priority (spec \u00A78.2: \"priority at the bottom\"), which can be negative, and the\n // edit form must still be able to save such a group.\n priority: z.number().int().min(PRIORITY_MIN).max(INT4_MAX),\n isDefault: z.boolean(),\n isActive: z.boolean(),\n metadata: clearableMetadataSchema,\n}\n\nexport const customerGroupCreateSchema = z.object({\n ...customerGroupBaseShape,\n isDefault: z.boolean().optional().default(false),\n isActive: z.boolean().optional().default(true),\n})\n\nexport type CustomerGroupCreateInput = z.infer<typeof customerGroupCreateSchema>\n\nexport const customerGroupUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(z.object(customerGroupBaseShape).partial())\n\nexport type CustomerGroupUpdateInput = z.infer<typeof customerGroupUpdateSchema>\n\nexport const customerGroupDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupDeleteInput = z.infer<typeof customerGroupDeleteSchema>\n\nconst customerGroupMembershipCreateBaseSchema = z.object({\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid(),\n customerId: uuid(),\n source: customerGroupMembershipSourceSchema.optional().default('manual'),\n validFrom: clearableDateSchema,\n validUntil: clearableDateSchema,\n assignedByUserId: clearableUuidSchema,\n notes: clearableStringSchema(2000),\n})\n\n// Server-owned fields: the tenant comes from the session, the organization from the\n// referenced customer, and `assignedByUserId` from the acting user.\nconst membershipServerOwnedFields = { organizationId: true, tenantId: true, assignedByUserId: true } as const\n\nexport const customerGroupMembershipCreateRequestSchema =\n customerGroupMembershipCreateBaseSchema.omit(membershipServerOwnedFields)\n\nexport const customerGroupMembershipCreateSchema = customerGroupMembershipCreateBaseSchema\n .refine(\n (payload) =>\n !payload.validFrom || !payload.validUntil || payload.validFrom <= payload.validUntil,\n {\n message: 'validFrom must be before or equal to validUntil',\n path: ['validUntil'],\n },\n )\n\nexport type CustomerGroupMembershipCreateInput = z.infer<typeof customerGroupMembershipCreateSchema>\n\nconst customerGroupMembershipUpdateBaseSchema = z\n .object({\n id: uuid(),\n })\n .merge(\n z.object({\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid().optional(),\n customerId: uuid().optional(),\n source: customerGroupMembershipSourceSchema.optional(),\n validFrom: clearableDateSchema,\n validUntil: clearableDateSchema,\n assignedByUserId: clearableUuidSchema,\n notes: clearableStringSchema(2000),\n }),\n )\n\nexport const customerGroupMembershipUpdateRequestSchema =\n customerGroupMembershipUpdateBaseSchema.omit(membershipServerOwnedFields)\n\nexport const customerGroupMembershipUpdateSchema = customerGroupMembershipUpdateBaseSchema.refine(\n (payload) =>\n !payload.validFrom || !payload.validUntil || payload.validFrom <= payload.validUntil,\n {\n message: 'validFrom must be before or equal to validUntil',\n path: ['validUntil'],\n },\n)\n\nexport type CustomerGroupMembershipUpdateInput = z.infer<typeof customerGroupMembershipUpdateSchema>\n\nexport const customerGroupMembershipDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupMembershipDeleteInput = z.infer<typeof customerGroupMembershipDeleteSchema>\n\n// Drag-reorder payload for the admin group list (Step 1.7): an ordered array of\n// `CustomerGroup` ids. A full ordering renumbers `priority` in gaps of 10; a partial\n// one permutes the listed groups' existing priorities \u2014 see commands/reorderGroups.ts.\nexport const customerGroupReorderSchema = z.object({\n tenantId: uuid(),\n ids: z.array(uuid()).min(1),\n})\n\nexport type CustomerGroupReorderInput = z.infer<typeof customerGroupReorderSchema>\n\n// Optional/nullable non-negative number field (payment days, credit/order thresholds):\n// blanking a previously-set value on edit must transmit null to clear it (same rationale\n// as `clearableStringSchema` above), and negative input is rejected server-side to match\n// the UI's inline validation (spec acceptance criterion: \"Numeric fields... reject\n// negative input inline\").\nconst clearableNonNegativeIntSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.number().int().min(0).max(INT4_MAX).nullable().optional(),\n)\n\nconst clearableNonNegativeNumberSchema = z.preprocess(\n emptyStringToNull,\n z.coerce.number().min(0).max(NUMERIC_16_2_MAX).nullable().optional(),\n)\n\nconst currencyCodeSchema = clearableStringSchema(4)\n\nexport const assortmentScopeSchema = z\n .object({\n categoryIds: z.array(uuid()).optional(),\n tagIds: z.array(uuid()).optional(),\n excludeProductIds: z.array(uuid()).optional(),\n excludeCategoryIds: z.array(uuid()).optional(),\n excludeTagIds: z.array(uuid()).optional(),\n })\n .strict()\n .nullable()\n\nexport type AssortmentScopeInput = z.infer<typeof assortmentScopeSchema>\n\n// Default-free, for the same reason as `customerGroupBaseShape` above.\nconst customerGroupTermsBaseShape = {\n organizationId: uuid().nullable().optional(),\n tenantId: uuid(),\n groupId: uuid(),\n priceKindId: clearableUuidSchema,\n paymentTermsDays: clearableNonNegativeIntSchema,\n allowPurchaseOnAccount: z.boolean().nullable().optional(),\n defaultCreditLimit: clearableNonNegativeNumberSchema,\n creditCurrencyCode: currencyCodeSchema,\n approvalRequiredAbove: clearableNonNegativeNumberSchema,\n minOrderValue: clearableNonNegativeNumberSchema,\n assortmentScope: assortmentScopeSchema.optional(),\n metadata: clearableMetadataSchema,\n}\n\nexport const customerGroupTermsCreateSchema = z.object(customerGroupTermsBaseShape)\n\nexport type CustomerGroupTermsCreateInput = z.infer<typeof customerGroupTermsCreateSchema>\n\nexport const customerGroupTermsUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(z.object(customerGroupTermsBaseShape).partial())\n\nexport type CustomerGroupTermsUpdateInput = z.infer<typeof customerGroupTermsUpdateSchema>\n\nexport const customerGroupTermsDeleteSchema = z.object({\n id: uuid(),\n})\n\nexport type CustomerGroupTermsDeleteInput = z.infer<typeof customerGroupTermsDeleteSchema>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,17 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20261005110250_customer_groups extends Migration {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.name = "Migration20261005110250";
6
+ }
7
+ up() {
8
+ this.addSql(`alter table "customer_group_terms" add "assortment_scope" jsonb null;`);
9
+ }
10
+ down() {
11
+ this.addSql(`alter table "customer_group_terms" drop column "assortment_scope";`);
12
+ }
13
+ }
14
+ export {
15
+ Migration20261005110250_customer_groups
16
+ };
17
+ //# sourceMappingURL=Migration20261005110250_customer_groups.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20261005110250_customer_groups extends Migration {\n\n override name = 'Migration20261005110250';\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"customer_group_terms\" add \"assortment_scope\" jsonb null;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"customer_group_terms\" drop column \"assortment_scope\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,gDAAgD,UAAU;AAAA,EAAhE;AAAA;AAEL,SAAS,OAAO;AAAA;AAAA,EAEP,KAA2B;AAClC,SAAK,OAAO,uEAAuE;AAAA,EACrF;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,oEAAoE;AAAA,EAClF;AAEF;",
6
+ "names": []
7
+ }
@@ -9,8 +9,21 @@ const TERMS_FIELD_NAMES = [
9
9
  "minOrderValue"
10
10
  ];
11
11
  const MAX_ANCESTOR_DEPTH = CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH;
12
- function groupOwnAssortmentScope(_groupId) {
13
- return null;
12
+ const AUTHORED_ASSORTMENT_SCOPE_KEYS = [
13
+ "categoryIds",
14
+ "tagIds",
15
+ "excludeProductIds",
16
+ "excludeCategoryIds",
17
+ "excludeTagIds"
18
+ ];
19
+ function normalizeAuthoredAssortmentScope(scope) {
20
+ if (!scope) return null;
21
+ const normalized = {};
22
+ for (const key of AUTHORED_ASSORTMENT_SCOPE_KEYS) {
23
+ const ids = scope[key];
24
+ if (Array.isArray(ids) && ids.length > 0) normalized[key] = ids;
25
+ }
26
+ return Object.keys(normalized).length > 0 ? normalized : null;
14
27
  }
15
28
  async function loadGroupCached(em, groupId, tenantId, cache) {
16
29
  if (cache.has(groupId)) return cache.get(groupId) ?? null;
@@ -29,6 +42,19 @@ async function loadCustomerGroupAncestorChain(em, groupId, tenantId, cache = /*
29
42
  }
30
43
  return chain;
31
44
  }
45
+ function resolveEffectiveCustomerIds(input) {
46
+ const distinctIds = [];
47
+ for (const customerId of input.customerIds ?? []) {
48
+ if (typeof customerId !== "string" || !customerId.length) continue;
49
+ if (!distinctIds.includes(customerId)) distinctIds.push(customerId);
50
+ }
51
+ if (distinctIds.length) return distinctIds;
52
+ return input.customerId == null ? [] : [input.customerId];
53
+ }
54
+ function compareRankedMemberships(left, right) {
55
+ if (left.customerIndex !== right.customerIndex) return left.customerIndex - right.customerIndex;
56
+ return right.membership.createdAt.getTime() - left.membership.createdAt.getTime();
57
+ }
32
58
  function toGroupSummary(group) {
33
59
  return { id: group.id, code: group.code, name: group.name, kind: group.kind, priority: group.priority };
34
60
  }
@@ -98,22 +124,28 @@ class DefaultCustomerGroupsService {
98
124
  }
99
125
  async resolveGroups(input) {
100
126
  const at = input.at ?? /* @__PURE__ */ new Date();
101
- if (input.customerId == null) return this.resolveDefaultGroup(input.tenantId);
127
+ const customerIds = resolveEffectiveCustomerIds(input);
128
+ if (!customerIds.length) return this.resolveDefaultGroup(input.tenantId);
102
129
  const memberships = await this.em.find(CustomerGroupMembership, {
103
130
  tenantId: input.tenantId,
104
- customerId: input.customerId,
131
+ customerId: customerIds.length === 1 ? customerIds[0] : { $in: customerIds },
105
132
  deletedAt: null
106
133
  });
107
134
  const validMemberships = memberships.filter((membership) => isMembershipValidAt(membership, at));
108
135
  if (!validMemberships.length) return this.resolveDefaultGroup(input.tenantId);
109
- const latestMembershipByGroupId = /* @__PURE__ */ new Map();
136
+ const bestMembershipByGroupId = /* @__PURE__ */ new Map();
110
137
  for (const membership of validMemberships) {
111
- const existing = latestMembershipByGroupId.get(membership.groupId);
112
- if (!existing || membership.createdAt.getTime() > existing.createdAt.getTime()) {
113
- latestMembershipByGroupId.set(membership.groupId, membership);
138
+ const customerIndex = customerIds.indexOf(membership.customerId);
139
+ const candidate = {
140
+ membership,
141
+ customerIndex: customerIndex === -1 ? customerIds.length : customerIndex
142
+ };
143
+ const existing = bestMembershipByGroupId.get(membership.groupId);
144
+ if (!existing || compareRankedMemberships(candidate, existing) < 0) {
145
+ bestMembershipByGroupId.set(membership.groupId, candidate);
114
146
  }
115
147
  }
116
- const groupIds = Array.from(latestMembershipByGroupId.keys());
148
+ const groupIds = Array.from(bestMembershipByGroupId.keys());
117
149
  const groups = await this.em.find(CustomerGroup, {
118
150
  id: { $in: groupIds },
119
151
  tenantId: input.tenantId,
@@ -122,9 +154,7 @@ class DefaultCustomerGroupsService {
122
154
  });
123
155
  const sortedGroups = groups.slice().sort((a, b) => {
124
156
  if (b.priority !== a.priority) return b.priority - a.priority;
125
- const membershipA = latestMembershipByGroupId.get(a.id);
126
- const membershipB = latestMembershipByGroupId.get(b.id);
127
- return membershipB.createdAt.getTime() - membershipA.createdAt.getTime();
157
+ return compareRankedMemberships(bestMembershipByGroupId.get(a.id), bestMembershipByGroupId.get(b.id));
128
158
  });
129
159
  if (!sortedGroups.length) return this.resolveDefaultGroup(input.tenantId);
130
160
  return {
@@ -143,7 +173,12 @@ class DefaultCustomerGroupsService {
143
173
  return { groupIds: [defaultGroup.id], groups: [toGroupSummary(defaultGroup)] };
144
174
  }
145
175
  async resolveTerms(input) {
146
- const groupIds = input.groupIds ?? (await this.resolveGroups({ customerId: input.customerId, tenantId: input.tenantId, at: input.at })).groupIds;
176
+ const groupIds = input.groupIds ?? (await this.resolveGroups({
177
+ customerId: input.customerId,
178
+ customerIds: input.customerIds,
179
+ tenantId: input.tenantId,
180
+ at: input.at
181
+ })).groupIds;
147
182
  if (!groupIds.length) return tenantDefaultTerms();
148
183
  const groupCache = /* @__PURE__ */ new Map();
149
184
  const termsCache = /* @__PURE__ */ new Map();
@@ -168,7 +203,17 @@ class DefaultCustomerGroupsService {
168
203
  }
169
204
  async resolveAssortmentScope(input) {
170
205
  const { groupIds } = await this.resolveGroups(input);
171
- const scope = unionScopes(groupIds.map((groupId) => groupOwnAssortmentScope(groupId)));
206
+ const termsRows = groupIds.length ? await this.em.find(CustomerGroupTerms, {
207
+ groupId: { $in: groupIds },
208
+ tenantId: input.tenantId,
209
+ deletedAt: null
210
+ }) : [];
211
+ const ownScopeByGroupId = /* @__PURE__ */ new Map();
212
+ for (const terms of termsRows) {
213
+ ownScopeByGroupId.set(terms.groupId, normalizeAuthoredAssortmentScope(terms.assortmentScope));
214
+ }
215
+ const groupOwnScopes = groupIds.map((groupId) => ownScopeByGroupId.get(groupId) ?? null);
216
+ const scope = unionScopes(groupOwnScopes);
172
217
  return { scope, sourceGroupIds: groupIds, sourceCustomerOverrideId: null };
173
218
  }
174
219
  async loadTerms(groupId, tenantId, cache) {
@@ -185,6 +230,8 @@ export {
185
230
  DefaultCustomerGroupsService,
186
231
  createCustomerGroupsService,
187
232
  isMembershipValidAt,
188
- loadCustomerGroupAncestorChain
233
+ loadCustomerGroupAncestorChain,
234
+ normalizeAuthoredAssortmentScope,
235
+ resolveEffectiveCustomerIds
189
236
  };
190
237
  //# sourceMappingURL=customerGroupsService.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_groups/services/customerGroupsService.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { unionScopes } from '@open-mercato/shared/lib/catalog-visibility'\nimport type { AssortmentScope, EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\nimport { CustomerGroup, CustomerGroupMembership, CustomerGroupTerms } from '../data/entities'\nimport { CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH } from '../data/validators'\n\nexport type GroupResolution = {\n groupIds: string[]\n groups: Array<{ id: string; code: string; name: string; kind: string; priority: number }>\n}\n\n// The five scalar fields `resolveTerms` resolves per group, independently of one\n// another (spec \u00A76.1). Kept as a `const` tuple (not a hardcoded literal union) so the\n// resolution loop and the `sources` map share a single source of truth for the field\n// list.\nconst TERMS_FIELD_NAMES = [\n 'priceKindId',\n 'paymentTermsDays',\n 'allowPurchaseOnAccount',\n 'approvalRequiredAbove',\n 'minOrderValue',\n] as const\ntype TermsFieldName = (typeof TERMS_FIELD_NAMES)[number]\n\n// Spec \u00A76.1's illustrative type sketch shows a single shared `sourceGroupId` on\n// `ResolvedTerms`, but its own acceptance criteria (US-C2: \"each resolved scalar\n// field... shows its value and the group it came from\") require independent\n// per-field provenance \u2014 a customer can get `paymentTermsDays` from a grandparent\n// while `priceKindId` comes straight from its own group. Modeled as a `sources` map\n// keyed by field name (rather than five parallel `<field>SourceGroupId` fields) so\n// the resolution loop can write `result.sources[field] = groupId` generically instead\n// of a per-field switch, and so a future terms field only needs adding to\n// `TERMS_FIELD_NAMES` instead of a new parallel field everywhere.\nexport type ResolvedTermsSources = Record<TermsFieldName, string | null>\n\nexport type ResolvedTerms = {\n priceKindId: string | null\n paymentTermsDays: number | null\n allowPurchaseOnAccount: boolean\n approvalRequiredAbove: number | null\n minOrderValue: number | null\n sources: ResolvedTermsSources\n}\n\n// Ancestor-walk depth cap \u2014 the group hierarchy's own depth cap (spec \u00A75.1, enforced at\n// create/update time by `findParentAssignmentIssue`), shared so the two cannot drift.\n// Defensive only: normal data can never produce a chain this long, but this stops a\n// corrupt/cyclic `parentId` graph from looping forever.\nconst MAX_ANCESTOR_DEPTH = CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH\n\n// `customer_groups` entities are tenant-scoped only (see the \"Groups are\n// tenant-scoped, not organization-scoped\" note in `data/entities.ts`), and this\n// codebase has no ambient/DI-resolved tenant context \u2014 `createRequestContainer()`\n// (packages/shared/src/lib/di/container.ts) builds the container before auth is\n// resolved, and every DB-querying DI service in this repo that needs tenant scope\n// takes it as an explicit caller-supplied argument (see\n// `warranty_claims/services/entitlementResolver.ts`'s `resolveEntitlement(input, scope, em)`).\n// The illustrative `resolveGroups(input: { customerId, at })` signature in\n// `.ai/specs/2026-08-14-customer-groups-and-b2b-terms.md` \u00A76 omits `tenantId` for\n// brevity; `tenantId` is added here to the input so this service can never silently\n// query across tenants (root AGENTS.md \u00A7 Never: \"expose cross-tenant data or skip\n// tenant/organization scoping\").\nexport type ResolveGroupsInput = {\n customerId: string | null\n tenantId: string\n at?: Date\n}\n\n// `groupIds` is optional and, when supplied by a caller who already resolved the\n// customer's groups, skips the membership-resolution query inside `resolveTerms` \u2014\n// `resolveGroups`'s own DB round-trips (memberships + groups, or the tenant-default\n// lookup) are not repeated. Ancestor/terms rows are still fetched fresh regardless,\n// since `resolveGroups` never returns those.\nexport type ResolveTermsInput = {\n customerId: string | null\n tenantId: string\n at?: Date\n groupIds?: string[]\n}\n\n// Same shape as `ResolveGroupsInput` \u2014 `resolveAssortmentScope` delegates straight to\n// `resolveGroups()` and adds no extra inputs of its own. Aliased rather than reused\n// directly so a future assortment-specific input (e.g. a channel id, once Phase 2 of\n// catalog-visibility composes channel scope too) doesn't ripple through `resolveGroups`'s\n// own signature.\nexport type ResolveAssortmentScopeInput = ResolveGroupsInput\n\nexport type ResolvedAssortmentScope = {\n scope: EffectiveAssortmentScope\n sourceGroupIds: string[]\n // Always `null` in Phase 1 \u2014 `CustomerAssortmentOverride` (per-customer override table)\n // is Phase 4 scope (spec \u00A714 non-goals) and does not exist yet. Never read from anywhere;\n // this is a fixed literal until that phase lands.\n sourceCustomerOverrideId: string | null\n}\n\nexport interface CustomerGroupsService {\n resolveGroups(input: ResolveGroupsInput): Promise<GroupResolution>\n resolveTerms(input: ResolveTermsInput): Promise<ResolvedTerms>\n resolveAssortmentScope(input: ResolveAssortmentScopeInput): Promise<ResolvedAssortmentScope>\n}\n\n// Phase 1's `CustomerGroup` entity has no `assortment_scope`-shaped column yet \u2014 the spec's\n// full field list places it on `CustomerGroupTerms`, but that was deliberately withheld from\n// this PR's Step 2.1 entity (Phase 5 scope, spec \u00A714 non-goals). Every matching group\n// therefore contributes an unrestricted (`null`) own-scope for now. Once a real column\n// exists, replace this stub's body with a read of it \u2014 `resolveAssortmentScope`'s\n// `unionScopes` composition below needs no changes to start returning real restrictions.\nfunction groupOwnAssortmentScope(_groupId: string): AssortmentScope | null {\n return null\n}\n\nasync function loadGroupCached(\n em: EntityManager,\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroup | null>,\n): Promise<CustomerGroup | null> {\n if (cache.has(groupId)) return cache.get(groupId) ?? null\n const group = await em.findOne(CustomerGroup, { id: groupId, tenantId, deletedAt: null })\n cache.set(groupId, group)\n return group\n}\n\n/**\n * One group's ancestor chain \u2014 self first, then parent, grandparent, ... up to\n * `MAX_ANCESTOR_DEPTH` \u2014 as loaded, non-deleted, active, same-tenant entities. The\n * walk stops at the first id that does not resolve (soft-deleted, other tenant, or\n * dangling `parentId`) or resolves to an inactive group (spec \u00A75.1: inactive groups\n * are excluded from resolution): such a group is never part of the chain, so neither\n * its terms row nor anything above it can be inherited. Shared by `resolveTerms` and\n * the explain-terms route so the resolved value and the explained ancestor path always\n * come from the SAME chain. `cache` lets callers reuse group rows across several chains.\n */\nexport async function loadCustomerGroupAncestorChain(\n em: EntityManager,\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroup | null> = new Map(),\n): Promise<CustomerGroup[]> {\n const chain: CustomerGroup[] = []\n let currentId: string | null = groupId\n while (currentId && chain.length < MAX_ANCESTOR_DEPTH) {\n const group = await loadGroupCached(em, currentId, tenantId, cache)\n if (!group || !group.isActive) break\n chain.push(group)\n currentId = group.parentId ?? null\n }\n return chain\n}\n\nfunction toGroupSummary(group: CustomerGroup): GroupResolution['groups'][number] {\n return { id: group.id, code: group.code, name: group.name, kind: group.kind, priority: group.priority }\n}\n\nexport function isMembershipValidAt(\n membership: Pick<CustomerGroupMembership, 'validFrom' | 'validUntil'>,\n at: Date,\n): boolean {\n if (membership.validFrom && membership.validFrom.getTime() > at.getTime()) return false\n // Inclusive upper bound: a membership expiring exactly at `at` is still valid\n // AT that instant (spec-derived rule \u2014 see the module's di.ts contract task).\n if (membership.validUntil && membership.validUntil.getTime() < at.getTime()) return false\n return true\n}\n\nfunction tenantDefaultTerms(): ResolvedTerms {\n return {\n priceKindId: null,\n paymentTermsDays: null,\n allowPurchaseOnAccount: false,\n approvalRequiredAbove: null,\n minOrderValue: null,\n sources: {\n priceKindId: null,\n paymentTermsDays: null,\n allowPurchaseOnAccount: null,\n approvalRequiredAbove: null,\n minOrderValue: null,\n },\n }\n}\n\nfunction toNumberOrNull(value: string | null | undefined): number | null {\n if (value == null) return null\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Every terms field is a nullable column: `null` means \"not set on this group, keep\n// walking the ancestor chain\", so a group can override a single field and inherit the\n// rest.\nfunction termsFieldIsSet(terms: CustomerGroupTerms, field: TermsFieldName): boolean {\n switch (field) {\n case 'priceKindId':\n return terms.priceKindId != null\n case 'paymentTermsDays':\n return terms.paymentTermsDays != null\n case 'allowPurchaseOnAccount':\n return terms.allowPurchaseOnAccount != null\n case 'approvalRequiredAbove':\n return terms.approvalRequiredAbove != null\n case 'minOrderValue':\n return terms.minOrderValue != null\n }\n}\n\nfunction applyTermsField(\n result: ResolvedTerms,\n field: TermsFieldName,\n terms: CustomerGroupTerms,\n sourceGroupId: string,\n): void {\n switch (field) {\n case 'priceKindId':\n result.priceKindId = terms.priceKindId ?? null\n break\n case 'paymentTermsDays':\n result.paymentTermsDays = terms.paymentTermsDays ?? null\n break\n case 'allowPurchaseOnAccount':\n result.allowPurchaseOnAccount = terms.allowPurchaseOnAccount === true\n break\n case 'approvalRequiredAbove':\n result.approvalRequiredAbove = toNumberOrNull(terms.approvalRequiredAbove)\n break\n case 'minOrderValue':\n result.minOrderValue = toNumberOrNull(terms.minOrderValue)\n break\n }\n result.sources[field] = sourceGroupId\n}\n\nexport class DefaultCustomerGroupsService implements CustomerGroupsService {\n constructor(private readonly em: EntityManager) {}\n\n async resolveGroups(input: ResolveGroupsInput): Promise<GroupResolution> {\n const at = input.at ?? new Date()\n\n if (input.customerId == null) return this.resolveDefaultGroup(input.tenantId)\n\n const memberships = await this.em.find(CustomerGroupMembership, {\n tenantId: input.tenantId,\n customerId: input.customerId,\n deletedAt: null,\n })\n\n const validMemberships = memberships.filter((membership) => isMembershipValidAt(membership, at))\n if (!validMemberships.length) return this.resolveDefaultGroup(input.tenantId)\n\n // Precedence rule: when a customer has more than one membership row for the\n // same group (e.g. re-added after a prior removal), the most recently created\n // one is the one whose validity/attribution counts.\n const latestMembershipByGroupId = new Map<string, CustomerGroupMembership>()\n for (const membership of validMemberships) {\n const existing = latestMembershipByGroupId.get(membership.groupId)\n if (!existing || membership.createdAt.getTime() > existing.createdAt.getTime()) {\n latestMembershipByGroupId.set(membership.groupId, membership)\n }\n }\n\n const groupIds = Array.from(latestMembershipByGroupId.keys())\n const groups = await this.em.find(CustomerGroup, {\n id: { $in: groupIds },\n tenantId: input.tenantId,\n isActive: true,\n deletedAt: null,\n })\n\n const sortedGroups = groups.slice().sort((a, b) => {\n if (b.priority !== a.priority) return b.priority - a.priority\n const membershipA = latestMembershipByGroupId.get(a.id)!\n const membershipB = latestMembershipByGroupId.get(b.id)!\n return membershipB.createdAt.getTime() - membershipA.createdAt.getTime()\n })\n\n if (!sortedGroups.length) return this.resolveDefaultGroup(input.tenantId)\n\n return {\n groupIds: sortedGroups.map((group) => group.id),\n groups: sortedGroups.map(toGroupSummary),\n }\n }\n\n private async resolveDefaultGroup(tenantId: string): Promise<GroupResolution> {\n const defaultGroup = await this.em.findOne(CustomerGroup, {\n tenantId,\n isDefault: true,\n isActive: true,\n deletedAt: null,\n })\n if (!defaultGroup) return { groupIds: [], groups: [] }\n return { groupIds: [defaultGroup.id], groups: [toGroupSummary(defaultGroup)] }\n }\n\n async resolveTerms(input: ResolveTermsInput): Promise<ResolvedTerms> {\n const groupIds = input.groupIds\n ?? (\n await this.resolveGroups({ customerId: input.customerId, tenantId: input.tenantId, at: input.at })\n ).groupIds\n\n if (!groupIds.length) return tenantDefaultTerms()\n\n // One ancestor chain per matching group, in the SAME priority order `groupIds`\n // arrived in (self first, then parent, then grandparent, ... up to the depth\n // cap). Computed once up front \u2014 the field loop below walks these same chains\n // for every field, relying on `groupCache`/`termsCache` to avoid re-querying a\n // group/terms row visited by an earlier chain or an earlier field.\n const groupCache = new Map<string, CustomerGroup | null>()\n const termsCache = new Map<string, CustomerGroupTerms | null>()\n const chains: string[][] = []\n for (const groupId of groupIds) {\n const chain = await loadCustomerGroupAncestorChain(this.em, groupId, input.tenantId, groupCache)\n chains.push(chain.map((group) => group.id))\n }\n\n const result = tenantDefaultTerms()\n\n for (const field of TERMS_FIELD_NAMES) {\n // \"highest-priority group with a non-null value \u2192 its ancestors\" is ONE\n // resolution unit per priority-ordered group (spec \u00A76.1): walk THIS group's\n // full ancestor chain to completion before moving to the next-priority group,\n // not \"check every group's own row first, then every group's ancestors.\"\n chainLoop: for (const chain of chains) {\n for (const ancestorId of chain) {\n const terms = await this.loadTerms(ancestorId, input.tenantId, termsCache)\n if (!terms) continue\n if (!termsFieldIsSet(terms, field)) continue\n applyTermsField(result, field, terms, ancestorId)\n break chainLoop\n }\n }\n }\n\n return result\n }\n\n async resolveAssortmentScope(input: ResolveAssortmentScopeInput): Promise<ResolvedAssortmentScope> {\n const { groupIds } = await this.resolveGroups(input)\n const scope = unionScopes(groupIds.map((groupId) => groupOwnAssortmentScope(groupId)))\n return { scope, sourceGroupIds: groupIds, sourceCustomerOverrideId: null }\n }\n\n private async loadTerms(\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroupTerms | null>,\n ): Promise<CustomerGroupTerms | null> {\n if (cache.has(groupId)) return cache.get(groupId) ?? null\n const terms = await this.em.findOne(CustomerGroupTerms, { groupId, tenantId, deletedAt: null })\n cache.set(groupId, terms)\n return terms\n }\n}\n\nexport function createCustomerGroupsService(em: EntityManager): CustomerGroupsService {\n return new DefaultCustomerGroupsService(em)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { unionScopes } from '@open-mercato/shared/lib/catalog-visibility'\nimport type { AssortmentScope, EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\nimport { CustomerGroup, CustomerGroupMembership, CustomerGroupTerms } from '../data/entities'\nimport { CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH } from '../data/validators'\n\nexport type GroupResolution = {\n groupIds: string[]\n groups: Array<{ id: string; code: string; name: string; kind: string; priority: number }>\n}\n\n// The five scalar fields `resolveTerms` resolves per group, independently of one\n// another (spec \u00A76.1). Kept as a `const` tuple (not a hardcoded literal union) so the\n// resolution loop and the `sources` map share a single source of truth for the field\n// list.\nconst TERMS_FIELD_NAMES = [\n 'priceKindId',\n 'paymentTermsDays',\n 'allowPurchaseOnAccount',\n 'approvalRequiredAbove',\n 'minOrderValue',\n] as const\ntype TermsFieldName = (typeof TERMS_FIELD_NAMES)[number]\n\n// Spec \u00A76.1's illustrative type sketch shows a single shared `sourceGroupId` on\n// `ResolvedTerms`, but its own acceptance criteria (US-C2: \"each resolved scalar\n// field... shows its value and the group it came from\") require independent\n// per-field provenance \u2014 a customer can get `paymentTermsDays` from a grandparent\n// while `priceKindId` comes straight from its own group. Modeled as a `sources` map\n// keyed by field name (rather than five parallel `<field>SourceGroupId` fields) so\n// the resolution loop can write `result.sources[field] = groupId` generically instead\n// of a per-field switch, and so a future terms field only needs adding to\n// `TERMS_FIELD_NAMES` instead of a new parallel field everywhere.\nexport type ResolvedTermsSources = Record<TermsFieldName, string | null>\n\nexport type ResolvedTerms = {\n priceKindId: string | null\n paymentTermsDays: number | null\n allowPurchaseOnAccount: boolean\n approvalRequiredAbove: number | null\n minOrderValue: number | null\n sources: ResolvedTermsSources\n}\n\n// Ancestor-walk depth cap \u2014 the group hierarchy's own depth cap (spec \u00A75.1, enforced at\n// create/update time by `findParentAssignmentIssue`), shared so the two cannot drift.\n// Defensive only: normal data can never produce a chain this long, but this stops a\n// corrupt/cyclic `parentId` graph from looping forever.\nconst MAX_ANCESTOR_DEPTH = CUSTOMER_GROUP_MAX_ANCESTOR_DEPTH\n\n// `customer_groups` entities are tenant-scoped only (see the \"Groups are\n// tenant-scoped, not organization-scoped\" note in `data/entities.ts`), and this\n// codebase has no ambient/DI-resolved tenant context \u2014 `createRequestContainer()`\n// (packages/shared/src/lib/di/container.ts) builds the container before auth is\n// resolved, and every DB-querying DI service in this repo that needs tenant scope\n// takes it as an explicit caller-supplied argument (see\n// `warranty_claims/services/entitlementResolver.ts`'s `resolveEntitlement(input, scope, em)`).\n// The illustrative `resolveGroups(input: { customerId, at })` signature in\n// `.ai/specs/2026-08-14-customer-groups-and-b2b-terms.md` \u00A76 omits `tenantId` for\n// brevity; `tenantId` is added here to the input so this service can never silently\n// query across tenants (root AGENTS.md \u00A7 Never: \"expose cross-tenant data or skip\n// tenant/organization scoping\").\nexport type ResolveGroupsInput = {\n customerId: string | null\n customerIds?: string[]\n tenantId: string\n at?: Date\n}\n\n// `groupIds` is optional and, when supplied by a caller who already resolved the\n// customer's groups, skips the membership-resolution query inside `resolveTerms` \u2014\n// `resolveGroups`'s own DB round-trips (memberships + groups, or the tenant-default\n// lookup) are not repeated. Ancestor/terms rows are still fetched fresh regardless,\n// since `resolveGroups` never returns those.\nexport type ResolveTermsInput = {\n customerId: string | null\n customerIds?: string[]\n tenantId: string\n at?: Date\n groupIds?: string[]\n}\n\n// Same shape as `ResolveGroupsInput` \u2014 `resolveAssortmentScope` delegates straight to\n// `resolveGroups()` and adds no extra inputs of its own. Aliased rather than reused\n// directly so a future assortment-specific input (e.g. a channel id, once Phase 2 of\n// catalog-visibility composes channel scope too) doesn't ripple through `resolveGroups`'s\n// own signature.\nexport type ResolveAssortmentScopeInput = ResolveGroupsInput\n\nexport type ResolvedAssortmentScope = {\n scope: EffectiveAssortmentScope\n sourceGroupIds: string[]\n // Always `null` in Phase 1 \u2014 `CustomerAssortmentOverride` (per-customer override table)\n // is Phase 4 scope (spec \u00A714 non-goals) and does not exist yet. Never read from anywhere;\n // this is a fixed literal until that phase lands.\n sourceCustomerOverrideId: string | null\n}\n\nexport interface CustomerGroupsService {\n resolveGroups(input: ResolveGroupsInput): Promise<GroupResolution>\n resolveTerms(input: ResolveTermsInput): Promise<ResolvedTerms>\n resolveAssortmentScope(input: ResolveAssortmentScopeInput): Promise<ResolvedAssortmentScope>\n}\n\nconst AUTHORED_ASSORTMENT_SCOPE_KEYS = [\n 'categoryIds',\n 'tagIds',\n 'excludeProductIds',\n 'excludeCategoryIds',\n 'excludeTagIds',\n] as const\n\nexport function normalizeAuthoredAssortmentScope(scope: AssortmentScope | null | undefined): AssortmentScope | null {\n if (!scope) return null\n const normalized: AssortmentScope = {}\n for (const key of AUTHORED_ASSORTMENT_SCOPE_KEYS) {\n const ids = scope[key]\n if (Array.isArray(ids) && ids.length > 0) normalized[key] = ids\n }\n return Object.keys(normalized).length > 0 ? normalized : null\n}\n\nasync function loadGroupCached(\n em: EntityManager,\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroup | null>,\n): Promise<CustomerGroup | null> {\n if (cache.has(groupId)) return cache.get(groupId) ?? null\n const group = await em.findOne(CustomerGroup, { id: groupId, tenantId, deletedAt: null })\n cache.set(groupId, group)\n return group\n}\n\n/**\n * One group's ancestor chain \u2014 self first, then parent, grandparent, ... up to\n * `MAX_ANCESTOR_DEPTH` \u2014 as loaded, non-deleted, active, same-tenant entities. The\n * walk stops at the first id that does not resolve (soft-deleted, other tenant, or\n * dangling `parentId`) or resolves to an inactive group (spec \u00A75.1: inactive groups\n * are excluded from resolution): such a group is never part of the chain, so neither\n * its terms row nor anything above it can be inherited. Shared by `resolveTerms` and\n * the explain-terms route so the resolved value and the explained ancestor path always\n * come from the SAME chain. `cache` lets callers reuse group rows across several chains.\n */\nexport async function loadCustomerGroupAncestorChain(\n em: EntityManager,\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroup | null> = new Map(),\n): Promise<CustomerGroup[]> {\n const chain: CustomerGroup[] = []\n let currentId: string | null = groupId\n while (currentId && chain.length < MAX_ANCESTOR_DEPTH) {\n const group = await loadGroupCached(em, currentId, tenantId, cache)\n if (!group || !group.isActive) break\n chain.push(group)\n currentId = group.parentId ?? null\n }\n return chain\n}\n\nexport function resolveEffectiveCustomerIds(input: Pick<ResolveGroupsInput, 'customerId' | 'customerIds'>): string[] {\n const distinctIds: string[] = []\n for (const customerId of input.customerIds ?? []) {\n if (typeof customerId !== 'string' || !customerId.length) continue\n if (!distinctIds.includes(customerId)) distinctIds.push(customerId)\n }\n if (distinctIds.length) return distinctIds\n return input.customerId == null ? [] : [input.customerId]\n}\n\ntype RankedMembership = {\n membership: CustomerGroupMembership\n customerIndex: number\n}\n\nfunction compareRankedMemberships(left: RankedMembership, right: RankedMembership): number {\n if (left.customerIndex !== right.customerIndex) return left.customerIndex - right.customerIndex\n return right.membership.createdAt.getTime() - left.membership.createdAt.getTime()\n}\n\nfunction toGroupSummary(group: CustomerGroup): GroupResolution['groups'][number] {\n return { id: group.id, code: group.code, name: group.name, kind: group.kind, priority: group.priority }\n}\n\nexport function isMembershipValidAt(\n membership: Pick<CustomerGroupMembership, 'validFrom' | 'validUntil'>,\n at: Date,\n): boolean {\n if (membership.validFrom && membership.validFrom.getTime() > at.getTime()) return false\n // Inclusive upper bound: a membership expiring exactly at `at` is still valid\n // AT that instant (spec-derived rule \u2014 see the module's di.ts contract task).\n if (membership.validUntil && membership.validUntil.getTime() < at.getTime()) return false\n return true\n}\n\nfunction tenantDefaultTerms(): ResolvedTerms {\n return {\n priceKindId: null,\n paymentTermsDays: null,\n allowPurchaseOnAccount: false,\n approvalRequiredAbove: null,\n minOrderValue: null,\n sources: {\n priceKindId: null,\n paymentTermsDays: null,\n allowPurchaseOnAccount: null,\n approvalRequiredAbove: null,\n minOrderValue: null,\n },\n }\n}\n\nfunction toNumberOrNull(value: string | null | undefined): number | null {\n if (value == null) return null\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Every terms field is a nullable column: `null` means \"not set on this group, keep\n// walking the ancestor chain\", so a group can override a single field and inherit the\n// rest.\nfunction termsFieldIsSet(terms: CustomerGroupTerms, field: TermsFieldName): boolean {\n switch (field) {\n case 'priceKindId':\n return terms.priceKindId != null\n case 'paymentTermsDays':\n return terms.paymentTermsDays != null\n case 'allowPurchaseOnAccount':\n return terms.allowPurchaseOnAccount != null\n case 'approvalRequiredAbove':\n return terms.approvalRequiredAbove != null\n case 'minOrderValue':\n return terms.minOrderValue != null\n }\n}\n\nfunction applyTermsField(\n result: ResolvedTerms,\n field: TermsFieldName,\n terms: CustomerGroupTerms,\n sourceGroupId: string,\n): void {\n switch (field) {\n case 'priceKindId':\n result.priceKindId = terms.priceKindId ?? null\n break\n case 'paymentTermsDays':\n result.paymentTermsDays = terms.paymentTermsDays ?? null\n break\n case 'allowPurchaseOnAccount':\n result.allowPurchaseOnAccount = terms.allowPurchaseOnAccount === true\n break\n case 'approvalRequiredAbove':\n result.approvalRequiredAbove = toNumberOrNull(terms.approvalRequiredAbove)\n break\n case 'minOrderValue':\n result.minOrderValue = toNumberOrNull(terms.minOrderValue)\n break\n }\n result.sources[field] = sourceGroupId\n}\n\nexport class DefaultCustomerGroupsService implements CustomerGroupsService {\n constructor(private readonly em: EntityManager) {}\n\n async resolveGroups(input: ResolveGroupsInput): Promise<GroupResolution> {\n const at = input.at ?? new Date()\n const customerIds = resolveEffectiveCustomerIds(input)\n\n if (!customerIds.length) return this.resolveDefaultGroup(input.tenantId)\n\n const memberships = await this.em.find(CustomerGroupMembership, {\n tenantId: input.tenantId,\n customerId: customerIds.length === 1 ? customerIds[0] : { $in: customerIds },\n deletedAt: null,\n })\n\n const validMemberships = memberships.filter((membership) => isMembershipValidAt(membership, at))\n if (!validMemberships.length) return this.resolveDefaultGroup(input.tenantId)\n\n // Precedence rule (spec \u00A76.0): one membership represents each group \u2014 the one of\n // the earliest buyer identity (person before company), then the most recently\n // created one (e.g. re-added after a prior removal).\n const bestMembershipByGroupId = new Map<string, RankedMembership>()\n for (const membership of validMemberships) {\n const customerIndex = customerIds.indexOf(membership.customerId)\n const candidate: RankedMembership = {\n membership,\n customerIndex: customerIndex === -1 ? customerIds.length : customerIndex,\n }\n const existing = bestMembershipByGroupId.get(membership.groupId)\n if (!existing || compareRankedMemberships(candidate, existing) < 0) {\n bestMembershipByGroupId.set(membership.groupId, candidate)\n }\n }\n\n const groupIds = Array.from(bestMembershipByGroupId.keys())\n const groups = await this.em.find(CustomerGroup, {\n id: { $in: groupIds },\n tenantId: input.tenantId,\n isActive: true,\n deletedAt: null,\n })\n\n const sortedGroups = groups.slice().sort((a, b) => {\n if (b.priority !== a.priority) return b.priority - a.priority\n return compareRankedMemberships(bestMembershipByGroupId.get(a.id)!, bestMembershipByGroupId.get(b.id)!)\n })\n\n if (!sortedGroups.length) return this.resolveDefaultGroup(input.tenantId)\n\n return {\n groupIds: sortedGroups.map((group) => group.id),\n groups: sortedGroups.map(toGroupSummary),\n }\n }\n\n private async resolveDefaultGroup(tenantId: string): Promise<GroupResolution> {\n const defaultGroup = await this.em.findOne(CustomerGroup, {\n tenantId,\n isDefault: true,\n isActive: true,\n deletedAt: null,\n })\n if (!defaultGroup) return { groupIds: [], groups: [] }\n return { groupIds: [defaultGroup.id], groups: [toGroupSummary(defaultGroup)] }\n }\n\n async resolveTerms(input: ResolveTermsInput): Promise<ResolvedTerms> {\n const groupIds = input.groupIds\n ?? (\n await this.resolveGroups({\n customerId: input.customerId,\n customerIds: input.customerIds,\n tenantId: input.tenantId,\n at: input.at,\n })\n ).groupIds\n\n if (!groupIds.length) return tenantDefaultTerms()\n\n // One ancestor chain per matching group, in the SAME priority order `groupIds`\n // arrived in (self first, then parent, then grandparent, ... up to the depth\n // cap). Computed once up front \u2014 the field loop below walks these same chains\n // for every field, relying on `groupCache`/`termsCache` to avoid re-querying a\n // group/terms row visited by an earlier chain or an earlier field.\n const groupCache = new Map<string, CustomerGroup | null>()\n const termsCache = new Map<string, CustomerGroupTerms | null>()\n const chains: string[][] = []\n for (const groupId of groupIds) {\n const chain = await loadCustomerGroupAncestorChain(this.em, groupId, input.tenantId, groupCache)\n chains.push(chain.map((group) => group.id))\n }\n\n const result = tenantDefaultTerms()\n\n for (const field of TERMS_FIELD_NAMES) {\n // \"highest-priority group with a non-null value \u2192 its ancestors\" is ONE\n // resolution unit per priority-ordered group (spec \u00A76.1): walk THIS group's\n // full ancestor chain to completion before moving to the next-priority group,\n // not \"check every group's own row first, then every group's ancestors.\"\n chainLoop: for (const chain of chains) {\n for (const ancestorId of chain) {\n const terms = await this.loadTerms(ancestorId, input.tenantId, termsCache)\n if (!terms) continue\n if (!termsFieldIsSet(terms, field)) continue\n applyTermsField(result, field, terms, ancestorId)\n break chainLoop\n }\n }\n }\n\n return result\n }\n\n async resolveAssortmentScope(input: ResolveAssortmentScopeInput): Promise<ResolvedAssortmentScope> {\n const { groupIds } = await this.resolveGroups(input)\n const termsRows = groupIds.length\n ? await this.em.find(CustomerGroupTerms, {\n groupId: { $in: groupIds },\n tenantId: input.tenantId,\n deletedAt: null,\n })\n : []\n const ownScopeByGroupId = new Map<string, AssortmentScope | null>()\n for (const terms of termsRows) {\n ownScopeByGroupId.set(terms.groupId, normalizeAuthoredAssortmentScope(terms.assortmentScope))\n }\n const groupOwnScopes = groupIds.map((groupId) => ownScopeByGroupId.get(groupId) ?? null)\n const scope = unionScopes(groupOwnScopes)\n return { scope, sourceGroupIds: groupIds, sourceCustomerOverrideId: null }\n }\n\n private async loadTerms(\n groupId: string,\n tenantId: string,\n cache: Map<string, CustomerGroupTerms | null>,\n ): Promise<CustomerGroupTerms | null> {\n if (cache.has(groupId)) return cache.get(groupId) ?? null\n const terms = await this.em.findOne(CustomerGroupTerms, { groupId, tenantId, deletedAt: null })\n cache.set(groupId, terms)\n return terms\n }\n}\n\nexport function createCustomerGroupsService(em: EntityManager): CustomerGroupsService {\n return new DefaultCustomerGroupsService(em)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,13 @@
1
+ const features = [
2
+ { id: "ecommerce.stores.view", title: "View stores", module: "ecommerce" },
3
+ { id: "ecommerce.stores.manage", title: "Manage stores", module: "ecommerce", dependsOn: ["ecommerce.stores.view"] },
4
+ { id: "ecommerce.branding.manage", title: "Manage store branding", module: "ecommerce", dependsOn: ["ecommerce.stores.view"] },
5
+ { id: "ecommerce.domains.manage", title: "Manage store domain bindings", module: "ecommerce", dependsOn: ["ecommerce.stores.view"] },
6
+ { id: "ecommerce.channels.manage", title: "Manage store channel bindings", module: "ecommerce", dependsOn: ["ecommerce.stores.view"] }
7
+ ];
8
+ var acl_default = features;
9
+ export {
10
+ acl_default as default,
11
+ features
12
+ };
13
+ //# sourceMappingURL=acl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/ecommerce/acl.ts"],
4
+ "sourcesContent": ["export const features = [\n { id: 'ecommerce.stores.view', title: 'View stores', module: 'ecommerce' },\n { id: 'ecommerce.stores.manage', title: 'Manage stores', module: 'ecommerce', dependsOn: ['ecommerce.stores.view'] },\n { id: 'ecommerce.branding.manage', title: 'Manage store branding', module: 'ecommerce', dependsOn: ['ecommerce.stores.view'] },\n { id: 'ecommerce.domains.manage', title: 'Manage store domain bindings', module: 'ecommerce', dependsOn: ['ecommerce.stores.view'] },\n { id: 'ecommerce.channels.manage', title: 'Manage store channel bindings', module: 'ecommerce', dependsOn: ['ecommerce.stores.view'] },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,yBAAyB,OAAO,eAAe,QAAQ,YAAY;AAAA,EACzE,EAAE,IAAI,2BAA2B,OAAO,iBAAiB,QAAQ,aAAa,WAAW,CAAC,uBAAuB,EAAE;AAAA,EACnH,EAAE,IAAI,6BAA6B,OAAO,yBAAyB,QAAQ,aAAa,WAAW,CAAC,uBAAuB,EAAE;AAAA,EAC7H,EAAE,IAAI,4BAA4B,OAAO,gCAAgC,QAAQ,aAAa,WAAW,CAAC,uBAAuB,EAAE;AAAA,EACnI,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,aAAa,WAAW,CAAC,uBAAuB,EAAE;AACvI;AAEA,IAAO,cAAQ;",
6
+ "names": []
7
+ }