@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
- package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
- package/dist/generated/entities/customer_group_terms/index.js +2 -0
- package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
- package/dist/generated/entities/ecommerce_store/index.js +33 -0
- package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +8 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
- package/dist/modules/catalog/acl.js +8 -1
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
- package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/history/route.js +131 -0
- package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +54 -245
- package/dist/modules/catalog/api/products/route.js.map +3 -3
- package/dist/modules/catalog/api/tags/route.js +6 -0
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
- package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
- package/dist/modules/catalog/cli.js +80 -2
- package/dist/modules/catalog/cli.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +51 -2
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +43 -1
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +71 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +7 -0
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
- package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
- package/dist/modules/catalog/components/lookupSources.js +31 -0
- package/dist/modules/catalog/components/lookupSources.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +122 -1
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +78 -0
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/di.js +18 -0
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/catalog/lib/omnibus.js +285 -0
- package/dist/modules/catalog/lib/omnibus.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusCache.js +89 -0
- package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
- package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
- package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
- package/dist/modules/catalog/lib/pricing.js +26 -2
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/lib/productFilters.js +273 -0
- package/dist/modules/catalog/lib/productFilters.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
- package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
- package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
- package/dist/modules/catalog/setup.js +3 -1
- package/dist/modules/catalog/setup.js.map +2 -2
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
- package/dist/modules/catalog/translations.js +10 -0
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +28 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
- package/dist/modules/customer_groups/data/entities.js +3 -0
- package/dist/modules/customer_groups/data/entities.js.map +2 -2
- package/dist/modules/customer_groups/data/validators.js +9 -0
- package/dist/modules/customer_groups/data/validators.js.map +2 -2
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
- package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
- package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
- package/dist/modules/ecommerce/acl.js +13 -0
- package/dist/modules/ecommerce/acl.js.map +7 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/openapi.js +27 -0
- package/dist/modules/ecommerce/api/openapi.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/crud.js +401 -0
- package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/route.js +64 -0
- package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/commands/stores.js +110 -0
- package/dist/modules/ecommerce/commands/stores.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
- package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
- package/dist/modules/ecommerce/components/channelLookups.js +28 -0
- package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
- package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
- package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
- package/dist/modules/ecommerce/components/storeChannels.js +140 -0
- package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
- package/dist/modules/ecommerce/components/storeDomains.js +167 -0
- package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
- package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
- package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
- package/dist/modules/ecommerce/components/storeSeo.js +69 -0
- package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
- package/dist/modules/ecommerce/data/enrichers.js +157 -0
- package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
- package/dist/modules/ecommerce/data/entities.js +197 -0
- package/dist/modules/ecommerce/data/entities.js.map +7 -0
- package/dist/modules/ecommerce/data/validators.js +406 -0
- package/dist/modules/ecommerce/data/validators.js.map +7 -0
- package/dist/modules/ecommerce/di.js +11 -0
- package/dist/modules/ecommerce/di.js.map +7 -0
- package/dist/modules/ecommerce/events.js +27 -0
- package/dist/modules/ecommerce/events.js.map +7 -0
- package/dist/modules/ecommerce/index.js +16 -0
- package/dist/modules/ecommerce/index.js.map +7 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
- package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
- package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
- package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
- package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
- package/dist/modules/ecommerce/lib/references.js +70 -0
- package/dist/modules/ecommerce/lib/references.js.map +7 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
- package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContext.js +393 -0
- package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
- package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
- package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/types.js +1 -0
- package/dist/modules/ecommerce/lib/types.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/notifications.client.js +47 -0
- package/dist/modules/ecommerce/notifications.client.js.map +7 -0
- package/dist/modules/ecommerce/notifications.js +48 -0
- package/dist/modules/ecommerce/notifications.js.map +7 -0
- package/dist/modules/ecommerce/search.js +83 -0
- package/dist/modules/ecommerce/search.js.map +7 -0
- package/dist/modules/ecommerce/setup.js +17 -0
- package/dist/modules/ecommerce/setup.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
- package/dist/modules/query_index/lib/batch.js +23 -9
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
- package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
- package/dist/modules/query_index/lib/document.js +2 -0
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +68 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +7 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +2 -0
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/data/validators.js +1 -1
- package/dist/modules/translations/data/validators.js.map +1 -1
- package/dist/modules/translations/lib/batch.js +21 -1
- package/dist/modules/translations/lib/batch.js.map +2 -2
- package/dist/modules/translations/lib/helpers.js +3 -0
- package/dist/modules/translations/lib/helpers.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +14 -2
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/generated/entities/catalog_price_history_entry/index.ts +25 -0
- package/generated/entities/customer_group_terms/index.ts +1 -0
- package/generated/entities/ecommerce_store/index.ts +15 -0
- package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
- package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
- package/generated/entities.ids.generated.ts +8 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
- package/src/modules/auth/i18n/de.json +8 -0
- package/src/modules/auth/i18n/en.json +8 -0
- package/src/modules/auth/i18n/es.json +8 -0
- package/src/modules/auth/i18n/ko.json +8 -0
- package/src/modules/auth/i18n/pl.json +8 -0
- package/src/modules/catalog/AGENTS.md +5 -2
- package/src/modules/catalog/acl.ts +7 -0
- package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
- package/src/modules/catalog/api/prices/history/route.ts +139 -0
- package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
- package/src/modules/catalog/api/products/route.ts +53 -308
- package/src/modules/catalog/api/tags/route.ts +6 -0
- package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
- package/src/modules/catalog/cli.ts +84 -1
- package/src/modules/catalog/commands/categories.ts +54 -2
- package/src/modules/catalog/commands/prices.ts +43 -1
- package/src/modules/catalog/commands/products.ts +84 -1
- package/src/modules/catalog/commands/variants.ts +7 -0
- package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
- package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
- package/src/modules/catalog/components/lookupSources.ts +29 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
- package/src/modules/catalog/data/entities.ts +123 -1
- package/src/modules/catalog/data/validators.ts +118 -0
- package/src/modules/catalog/di.ts +23 -0
- package/src/modules/catalog/i18n/de.json +77 -0
- package/src/modules/catalog/i18n/en.json +77 -0
- package/src/modules/catalog/i18n/es.json +77 -0
- package/src/modules/catalog/i18n/ko.json +77 -0
- package/src/modules/catalog/i18n/pl.json +77 -0
- package/src/modules/catalog/lib/omnibus.ts +406 -0
- package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
- package/src/modules/catalog/lib/omnibusCache.ts +111 -0
- package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
- package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
- package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
- package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
- package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
- package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
- package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
- package/src/modules/catalog/lib/pricing.ts +42 -4
- package/src/modules/catalog/lib/productFilters.ts +349 -0
- package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
- package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
- package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
- package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
- package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
- package/src/modules/catalog/setup.ts +2 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
- package/src/modules/catalog/translations.ts +12 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +35 -0
- package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
- package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
- package/src/modules/customer_groups/data/entities.ts +4 -0
- package/src/modules/customer_groups/data/validators.ts +14 -0
- package/src/modules/customer_groups/i18n/de.json +12 -0
- package/src/modules/customer_groups/i18n/en.json +12 -0
- package/src/modules/customer_groups/i18n/es.json +12 -0
- package/src/modules/customer_groups/i18n/ko.json +12 -0
- package/src/modules/customer_groups/i18n/pl.json +12 -0
- package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
- package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
- package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
- package/src/modules/ecommerce/acl.ts +9 -0
- package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
- package/src/modules/ecommerce/api/openapi.ts +28 -0
- package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
- package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
- package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
- package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
- package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
- package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
- package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
- package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
- package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
- package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
- package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
- package/src/modules/ecommerce/api/stores/crud.ts +457 -0
- package/src/modules/ecommerce/api/stores/route.ts +66 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
- package/src/modules/ecommerce/commands/stores.ts +154 -0
- package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
- package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
- package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
- package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
- package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
- package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
- package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
- package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
- package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
- package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
- package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
- package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
- package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
- package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
- package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
- package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
- package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
- package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
- package/src/modules/ecommerce/components/channelLookups.ts +24 -0
- package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
- package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
- package/src/modules/ecommerce/components/storeChannels.ts +210 -0
- package/src/modules/ecommerce/components/storeDomains.ts +221 -0
- package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
- package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
- package/src/modules/ecommerce/components/storeSeo.ts +78 -0
- package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
- package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
- package/src/modules/ecommerce/data/enrichers.ts +213 -0
- package/src/modules/ecommerce/data/entities.ts +168 -0
- package/src/modules/ecommerce/data/validators.ts +587 -0
- package/src/modules/ecommerce/di.ts +9 -0
- package/src/modules/ecommerce/events.ts +27 -0
- package/src/modules/ecommerce/i18n/de.json +341 -0
- package/src/modules/ecommerce/i18n/en.json +341 -0
- package/src/modules/ecommerce/i18n/es.json +341 -0
- package/src/modules/ecommerce/i18n/ko.json +341 -0
- package/src/modules/ecommerce/i18n/pl.json +341 -0
- package/src/modules/ecommerce/index.ts +14 -0
- package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
- package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
- package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
- package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
- package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
- package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
- package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
- package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
- package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
- package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
- package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
- package/src/modules/ecommerce/lib/references.ts +93 -0
- package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
- package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
- package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
- package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
- package/src/modules/ecommerce/lib/storeContext.ts +669 -0
- package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
- package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
- package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
- package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
- package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
- package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
- package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
- package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
- package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
- package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
- package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
- package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
- package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
- package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
- package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
- package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
- package/src/modules/ecommerce/lib/types.ts +69 -0
- package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
- package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
- package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
- package/src/modules/ecommerce/notifications.client.ts +46 -0
- package/src/modules/ecommerce/notifications.ts +46 -0
- package/src/modules/ecommerce/search.ts +93 -0
- package/src/modules/ecommerce/setup.ts +15 -0
- package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
- package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
- package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
- package/src/modules/query_index/lib/batch.ts +25 -9
- package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
- package/src/modules/query_index/lib/document.ts +2 -0
- package/src/modules/query_index/lib/engine.ts +58 -1
- package/src/modules/query_index/lib/indexer.ts +8 -0
- package/src/modules/query_index/lib/search-tokens.ts +2 -0
- package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/data/validators.ts +1 -1
- package/src/modules/translations/lib/batch.ts +47 -0
- package/src/modules/translations/lib/helpers.ts +4 -0
- package/src/modules/translations/lib/resolve-field-list.ts +16 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/customerGroupsFixtures.ts"],
|
|
4
|
-
"sourcesContent": ["import { randomInt } from 'node:crypto';\nimport { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { expectId, readJsonSafe } from './generalFixtures';\n\nconst GROUPS_PATH = '/api/customer_groups/customer-groups';\nconst MEMBERSHIPS_PATH = '/api/customer_groups/customer-groups/memberships';\n\n/**\n * `CustomerGroup.priority` is unique per tenant among live rows (partial\n * unique index `customer_groups_tenant_priority_unique`), and integration\n * specs share one admin tenant. A fixed default would collide with any live\n * group already holding that value, so callers that do not care about the\n * exact priority get a random value from a band no product flow writes\n * (reorder writes 10, 20, ...; the TC-CGRP `fixturePriority` band is\n * 2,000,000-2,999,999).\n */\nexport function uniqueCustomerGroupPriority(): number {\n return 3_000_000 + randomInt(0, 1_000_000_000);\n}\n\nexport type CustomerGroupFixtureInput = {\n code: string;\n name: string;\n kind?: 'b2c' | 'b2b' | 'internal' | 'partner';\n priority?: number;\n isDefault?: boolean;\n isActive?: boolean;\n parentId?: string;\n};\n\nexport async function createCustomerGroupFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', GROUPS_PATH, {\n token,\n data: {\n code: input.code,\n name: input.name,\n kind: input.kind ?? 'b2c',\n priority: input.priority ?? uniqueCustomerGroupPriority(),\n isDefault: input.isDefault ?? false,\n isActive: input.isActive ?? true,\n ...(input.parentId ? { parentId: input.parentId } : {}),\n },\n });\n expect(response.status(), `create customer group fixture failed: ${response.status()}`).toBe(201);\n return expectId(\n (await readJsonSafe<{ id?: string }>(response))?.id,\n 'customer group fixture should return an id',\n );\n}\n\nexport async function deleteCustomerGroupIfExists(\n request: APIRequestContext,\n token: string | null,\n groupId: string | null,\n): Promise<void> {\n if (!token || !groupId) return;\n await apiRequest(request, 'DELETE', `${GROUPS_PATH}?id=${encodeURIComponent(groupId)}`, { token }).catch(\n () => undefined,\n );\n}\n\nexport type CustomerGroupMembershipFixtureInput = {\n groupId: string;\n customerId: string;\n source?: 'manual' | 'import' | 'rule' | 'onboarding';\n validFrom?: string;\n validUntil?: string;\n};\n\nexport async function createCustomerGroupMembershipFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupMembershipFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', MEMBERSHIPS_PATH, {\n token,\n data: {\n groupId: input.groupId,\n customerId: input.customerId,\n source: input.source ?? 'manual',\n ...(input.validFrom ? { validFrom: input.validFrom } : {}),\n ...(input.validUntil ? { validUntil: input.validUntil } : {}),\n },\n });\n expect(response.status(), `create customer group membership fixture failed: ${response.status()}`).toBe(201);\n return expectId(\n (await readJsonSafe<{ id?: string }>(response))?.id,\n 'customer group membership fixture should return an id',\n );\n}\n\nexport async function deleteCustomerGroupMembershipIfExists(\n request: APIRequestContext,\n token: string | null,\n membershipId: string | null,\n): Promise<void> {\n if (!token || !membershipId) return;\n await apiRequest(request, 'DELETE', `${MEMBERSHIPS_PATH}?id=${encodeURIComponent(membershipId)}`, {\n token,\n }).catch(() => undefined);\n}\n\nexport type CustomerGroupTermsFixtureInput = {\n groupId: string;\n priceKindId?: string | null;\n paymentTermsDays?: number | null;\n allowPurchaseOnAccount?: boolean;\n defaultCreditLimit?: number | null;\n creditCurrencyCode?: string | null;\n approvalRequiredAbove?: number | null;\n minOrderValue?: number | null;\n metadata?: Record<string, unknown> | null;\n};\n\nexport type CustomerGroupTermsFixtureResult = {\n id: string;\n updatedAt: string;\n};\n\n/**\n * Upserts (`PUT /api/customer_groups/customer-groups/:id/terms`) the target group's commercial\n * terms row. Mirrors `createCustomerGroupFixture`'s shape but returns\n * `{ id, updatedAt }` instead of a bare id \u2014 callers exercising optimistic\n * locking need the row's `updatedAt` immediately after create/update without\n * an extra GET round-trip.\n */\nexport async function createCustomerGroupTermsFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupTermsFixtureInput,\n headers: Record<string, string> = {},\n): Promise<CustomerGroupTermsFixtureResult> {\n const { groupId, ...fields } = input;\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(fields)) {\n if (value !== undefined) data[key] = value;\n }\n const response = await apiRequest(request, 'PUT', `${GROUPS_PATH}/${groupId}/terms`, {\n token,\n data,\n headers,\n });\n expect(response.status(), `create/update customer group terms fixture failed: ${response.status()}`).toBe(200);\n const body = await readJsonSafe<{ terms?: { id?: string; updatedAt?: string } }>(response);\n return {\n id: expectId(body?.terms?.id, 'customer group terms fixture should return an id'),\n updatedAt: expectId(body?.terms?.updatedAt, 'customer group terms fixture should return updatedAt'),\n };\n}\n\n/**\n * `CustomerGroupTerms` has no standalone DELETE endpoint \u2014 `GET/PUT\n * /api/customer_groups/customer-groups/:id/terms` only (see the doc comment on that route:\n * terms are a strict 1:1 sub-resource of a group with no independent\n * list/create/delete semantics). Deleting the PARENT group via\n * `deleteCustomerGroupIfExists` is what actually reclaims a terms row in\n * tests. This no-op exists only for naming symmetry with the other\n * `delete*IfExists` helpers so a spec's `finally` block reads consistently.\n */\nexport async function deleteCustomerGroupTermsIfExists(\n _request: APIRequestContext,\n _token: string | null,\n _groupId: string | null,\n): Promise<void> {\n return undefined;\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,cAAsC;
|
|
4
|
+
"sourcesContent": ["import { randomInt } from 'node:crypto';\nimport { expect, type APIRequestContext } from '@playwright/test';\nimport type { AssortmentScope } from '@open-mercato/shared/lib/catalog-visibility';\nimport { apiRequest } from './api';\nimport { expectId, readJsonSafe } from './generalFixtures';\n\nconst GROUPS_PATH = '/api/customer_groups/customer-groups';\nconst MEMBERSHIPS_PATH = '/api/customer_groups/customer-groups/memberships';\n\n/**\n * `CustomerGroup.priority` is unique per tenant among live rows (partial\n * unique index `customer_groups_tenant_priority_unique`), and integration\n * specs share one admin tenant. A fixed default would collide with any live\n * group already holding that value, so callers that do not care about the\n * exact priority get a random value from a band no product flow writes\n * (reorder writes 10, 20, ...; the TC-CGRP `fixturePriority` band is\n * 2,000,000-2,999,999).\n */\nexport function uniqueCustomerGroupPriority(): number {\n return 3_000_000 + randomInt(0, 1_000_000_000);\n}\n\nexport type CustomerGroupFixtureInput = {\n code: string;\n name: string;\n kind?: 'b2c' | 'b2b' | 'internal' | 'partner';\n priority?: number;\n isDefault?: boolean;\n isActive?: boolean;\n parentId?: string;\n};\n\nexport async function createCustomerGroupFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', GROUPS_PATH, {\n token,\n data: {\n code: input.code,\n name: input.name,\n kind: input.kind ?? 'b2c',\n priority: input.priority ?? uniqueCustomerGroupPriority(),\n isDefault: input.isDefault ?? false,\n isActive: input.isActive ?? true,\n ...(input.parentId ? { parentId: input.parentId } : {}),\n },\n });\n expect(response.status(), `create customer group fixture failed: ${response.status()}`).toBe(201);\n return expectId(\n (await readJsonSafe<{ id?: string }>(response))?.id,\n 'customer group fixture should return an id',\n );\n}\n\nexport async function deleteCustomerGroupIfExists(\n request: APIRequestContext,\n token: string | null,\n groupId: string | null,\n): Promise<void> {\n if (!token || !groupId) return;\n await apiRequest(request, 'DELETE', `${GROUPS_PATH}?id=${encodeURIComponent(groupId)}`, { token }).catch(\n () => undefined,\n );\n}\n\nexport type CustomerGroupMembershipFixtureInput = {\n groupId: string;\n customerId: string;\n source?: 'manual' | 'import' | 'rule' | 'onboarding';\n validFrom?: string;\n validUntil?: string;\n};\n\nexport async function createCustomerGroupMembershipFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupMembershipFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', MEMBERSHIPS_PATH, {\n token,\n data: {\n groupId: input.groupId,\n customerId: input.customerId,\n source: input.source ?? 'manual',\n ...(input.validFrom ? { validFrom: input.validFrom } : {}),\n ...(input.validUntil ? { validUntil: input.validUntil } : {}),\n },\n });\n expect(response.status(), `create customer group membership fixture failed: ${response.status()}`).toBe(201);\n return expectId(\n (await readJsonSafe<{ id?: string }>(response))?.id,\n 'customer group membership fixture should return an id',\n );\n}\n\nexport async function deleteCustomerGroupMembershipIfExists(\n request: APIRequestContext,\n token: string | null,\n membershipId: string | null,\n): Promise<void> {\n if (!token || !membershipId) return;\n await apiRequest(request, 'DELETE', `${MEMBERSHIPS_PATH}?id=${encodeURIComponent(membershipId)}`, {\n token,\n }).catch(() => undefined);\n}\n\nexport type CustomerGroupTermsFixtureInput = {\n groupId: string;\n priceKindId?: string | null;\n paymentTermsDays?: number | null;\n allowPurchaseOnAccount?: boolean;\n defaultCreditLimit?: number | null;\n creditCurrencyCode?: string | null;\n approvalRequiredAbove?: number | null;\n minOrderValue?: number | null;\n assortmentScope?: AssortmentScope | null;\n metadata?: Record<string, unknown> | null;\n};\n\nexport type CustomerGroupTermsFixtureResult = {\n id: string;\n updatedAt: string;\n};\n\n/**\n * Upserts (`PUT /api/customer_groups/customer-groups/:id/terms`) the target group's commercial\n * terms row. Mirrors `createCustomerGroupFixture`'s shape but returns\n * `{ id, updatedAt }` instead of a bare id \u2014 callers exercising optimistic\n * locking need the row's `updatedAt` immediately after create/update without\n * an extra GET round-trip.\n */\nexport async function createCustomerGroupTermsFixture(\n request: APIRequestContext,\n token: string,\n input: CustomerGroupTermsFixtureInput,\n headers: Record<string, string> = {},\n): Promise<CustomerGroupTermsFixtureResult> {\n const { groupId, ...fields } = input;\n const data: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(fields)) {\n if (value !== undefined) data[key] = value;\n }\n const response = await apiRequest(request, 'PUT', `${GROUPS_PATH}/${groupId}/terms`, {\n token,\n data,\n headers,\n });\n expect(response.status(), `create/update customer group terms fixture failed: ${response.status()}`).toBe(200);\n const body = await readJsonSafe<{ terms?: { id?: string; updatedAt?: string } }>(response);\n return {\n id: expectId(body?.terms?.id, 'customer group terms fixture should return an id'),\n updatedAt: expectId(body?.terms?.updatedAt, 'customer group terms fixture should return updatedAt'),\n };\n}\n\n/**\n * `CustomerGroupTerms` has no standalone DELETE endpoint \u2014 `GET/PUT\n * /api/customer_groups/customer-groups/:id/terms` only (see the doc comment on that route:\n * terms are a strict 1:1 sub-resource of a group with no independent\n * list/create/delete semantics). Deleting the PARENT group via\n * `deleteCustomerGroupIfExists` is what actually reclaims a terms row in\n * tests. This no-op exists only for naming symmetry with the other\n * `delete*IfExists` helpers so a spec's `finally` block reads consistently.\n */\nexport async function deleteCustomerGroupTermsIfExists(\n _request: APIRequestContext,\n _token: string | null,\n _groupId: string | null,\n): Promise<void> {\n return undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -30,7 +30,14 @@ const features = [
|
|
|
30
30
|
module: "catalog",
|
|
31
31
|
dependsOn: ["catalog.products.view", "currencies.view"]
|
|
32
32
|
},
|
|
33
|
-
{ id: "catalog.settings.
|
|
33
|
+
{ id: "catalog.settings.view", title: "View catalog settings", module: "catalog" },
|
|
34
|
+
{ id: "catalog.settings.manage", title: "Manage catalog settings", module: "catalog" },
|
|
35
|
+
{
|
|
36
|
+
id: "catalog.price_history.view",
|
|
37
|
+
title: "View catalog price history",
|
|
38
|
+
module: "catalog",
|
|
39
|
+
dependsOn: ["catalog.products.view"]
|
|
40
|
+
}
|
|
34
41
|
];
|
|
35
42
|
var acl_default = features;
|
|
36
43
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/catalog/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n {\n id: 'catalog.products.view',\n title: 'View catalog products',\n module: 'catalog',\n dependsOn: ['currencies.view', 'dictionaries.view'],\n },\n {\n id: 'catalog.products.manage',\n title: 'Manage catalog products',\n module: 'catalog',\n dependsOn: ['catalog.products.view'],\n },\n { id: 'catalog.categories.view', title: 'View catalog categories', module: 'catalog' },\n {\n id: 'catalog.categories.manage',\n title: 'Manage catalog categories',\n module: 'catalog',\n dependsOn: ['catalog.categories.view'],\n },\n {\n id: 'catalog.variants.manage',\n title: 'Manage catalog variants',\n module: 'catalog',\n dependsOn: ['catalog.products.view'],\n },\n {\n id: 'catalog.pricing.manage',\n title: 'Manage catalog pricing',\n module: 'catalog',\n dependsOn: ['catalog.products.view', 'currencies.view'],\n },\n { id: 'catalog.settings.manage', title: 'Manage catalog settings', module: 'catalog' },\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB,mBAAmB;AAAA,EACpD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,UAAU;AAAA,EACrF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB,iBAAiB;AAAA,EACxD;AAAA,EACA,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,UAAU;
|
|
4
|
+
"sourcesContent": ["export const features = [\n {\n id: 'catalog.products.view',\n title: 'View catalog products',\n module: 'catalog',\n dependsOn: ['currencies.view', 'dictionaries.view'],\n },\n {\n id: 'catalog.products.manage',\n title: 'Manage catalog products',\n module: 'catalog',\n dependsOn: ['catalog.products.view'],\n },\n { id: 'catalog.categories.view', title: 'View catalog categories', module: 'catalog' },\n {\n id: 'catalog.categories.manage',\n title: 'Manage catalog categories',\n module: 'catalog',\n dependsOn: ['catalog.categories.view'],\n },\n {\n id: 'catalog.variants.manage',\n title: 'Manage catalog variants',\n module: 'catalog',\n dependsOn: ['catalog.products.view'],\n },\n {\n id: 'catalog.pricing.manage',\n title: 'Manage catalog pricing',\n module: 'catalog',\n dependsOn: ['catalog.products.view', 'currencies.view'],\n },\n { id: 'catalog.settings.view', title: 'View catalog settings', module: 'catalog' },\n { id: 'catalog.settings.manage', title: 'Manage catalog settings', module: 'catalog' },\n {\n id: 'catalog.price_history.view',\n title: 'View catalog price history',\n module: 'catalog',\n dependsOn: ['catalog.products.view'],\n },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB,mBAAmB;AAAA,EACpD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,UAAU;AAAA,EACrF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB,iBAAiB;AAAA,EACxD;AAAA,EACA,EAAE,IAAI,yBAAyB,OAAO,yBAAyB,QAAQ,UAAU;AAAA,EACjF,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,UAAU;AAAA,EACrF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AACF;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
+
import {
|
|
7
|
+
bridgeLegacyGuard,
|
|
8
|
+
runMutationGuards
|
|
9
|
+
} from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
10
|
+
import { getAllMutationGuardInstances } from "@open-mercato/shared/lib/crud/mutation-guard-store";
|
|
11
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
12
|
+
import { omnibusConfigPatchSchema } from "../../../data/validators.js";
|
|
13
|
+
import { OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, omnibusConfigSchema } from "../../../lib/omnibusTypes.js";
|
|
14
|
+
import {
|
|
15
|
+
buildOmnibusFieldErrors,
|
|
16
|
+
evaluateOmnibusConfig,
|
|
17
|
+
invalidOmnibusConfig,
|
|
18
|
+
mergeOmnibusConfig,
|
|
19
|
+
parseStoredOmnibusConfig
|
|
20
|
+
} from "../../../lib/omnibusConfig.js";
|
|
21
|
+
import { invalidateOmnibusTenantCache, resolveOmnibusCache } from "../../../lib/omnibusCache.js";
|
|
22
|
+
const logger = createLogger("catalog");
|
|
23
|
+
const OMNIBUS_CONFIG_RESOURCE_KIND = "catalog.settings";
|
|
24
|
+
const OMNIBUS_CONFIG_READ_FEATURES = ["catalog.settings.view", "catalog.settings.manage"];
|
|
25
|
+
const metadata = {
|
|
26
|
+
GET: { requireAuth: true },
|
|
27
|
+
PATCH: { requireAuth: true, requireFeatures: ["catalog.settings.manage"] }
|
|
28
|
+
};
|
|
29
|
+
function resolveUserFeatures(auth) {
|
|
30
|
+
const features = auth?.features;
|
|
31
|
+
if (!Array.isArray(features)) return [];
|
|
32
|
+
return features.filter((value) => typeof value === "string");
|
|
33
|
+
}
|
|
34
|
+
async function readStoredConfig(service, tenantId) {
|
|
35
|
+
const raw = await service.getValue(OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, { scope: { tenantId } });
|
|
36
|
+
const config = parseStoredOmnibusConfig(raw);
|
|
37
|
+
if (raw !== null && raw !== void 0 && !config) {
|
|
38
|
+
logger.warn("[internal] catalog omnibus stored config is invalid; treating it as unset", { tenantId });
|
|
39
|
+
}
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
function evaluatePatch(existing, payload) {
|
|
43
|
+
const parsed = omnibusConfigPatchSchema.safeParse(payload);
|
|
44
|
+
if (!parsed.success) return invalidOmnibusConfig(buildOmnibusFieldErrors(parsed.error.issues));
|
|
45
|
+
return { ...evaluateOmnibusConfig(mergeOmnibusConfig(existing, parsed.data)), patch: parsed.data };
|
|
46
|
+
}
|
|
47
|
+
async function GET(req) {
|
|
48
|
+
try {
|
|
49
|
+
const auth = await getAuthFromRequest(req);
|
|
50
|
+
if (!auth || !auth.tenantId) {
|
|
51
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
52
|
+
}
|
|
53
|
+
const container = await createRequestContainer();
|
|
54
|
+
const rbacService = container.resolve("rbacService");
|
|
55
|
+
const scope = { tenantId: auth.tenantId, organizationId: auth.orgId ?? null };
|
|
56
|
+
let canRead = false;
|
|
57
|
+
for (const feature of OMNIBUS_CONFIG_READ_FEATURES) {
|
|
58
|
+
if (await rbacService.userHasAllFeatures(auth.sub, [feature], scope)) {
|
|
59
|
+
canRead = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!canRead) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
64
|
+
const service = container.resolve("moduleConfigService");
|
|
65
|
+
const config = await readStoredConfig(service, auth.tenantId);
|
|
66
|
+
return NextResponse.json(config ?? {});
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
69
|
+
logger.error("catalog.config.omnibus.GET failed", { err });
|
|
70
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function PATCH(req) {
|
|
74
|
+
try {
|
|
75
|
+
const auth = await getAuthFromRequest(req);
|
|
76
|
+
if (!auth || !auth.tenantId) {
|
|
77
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
78
|
+
}
|
|
79
|
+
const tenantId = auth.tenantId;
|
|
80
|
+
const organizationId = auth.orgId ?? null;
|
|
81
|
+
let body;
|
|
82
|
+
try {
|
|
83
|
+
body = await req.json();
|
|
84
|
+
} catch {
|
|
85
|
+
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
const container = await createRequestContainer();
|
|
88
|
+
const service = container.resolve("moduleConfigService");
|
|
89
|
+
const existing = await readStoredConfig(service, tenantId);
|
|
90
|
+
let evaluation = evaluatePatch(existing, body);
|
|
91
|
+
if (!evaluation.ok) return NextResponse.json(evaluation.body, { status: evaluation.status });
|
|
92
|
+
const legacyGuard = bridgeLegacyGuard(container);
|
|
93
|
+
const guards = [...getAllMutationGuardInstances()];
|
|
94
|
+
if (legacyGuard) guards.push(legacyGuard);
|
|
95
|
+
const guardInput = {
|
|
96
|
+
tenantId,
|
|
97
|
+
organizationId,
|
|
98
|
+
userId: auth.sub ?? "",
|
|
99
|
+
resourceKind: OMNIBUS_CONFIG_RESOURCE_KIND,
|
|
100
|
+
resourceId: OMNIBUS_CONFIG_NAME,
|
|
101
|
+
operation: "update",
|
|
102
|
+
requestMethod: req.method,
|
|
103
|
+
requestHeaders: req.headers
|
|
104
|
+
};
|
|
105
|
+
const guardResult = await runMutationGuards(
|
|
106
|
+
guards,
|
|
107
|
+
{ ...guardInput, mutationPayload: evaluation.patch ?? null },
|
|
108
|
+
{ userFeatures: resolveUserFeatures(auth) }
|
|
109
|
+
);
|
|
110
|
+
if (!guardResult.ok) {
|
|
111
|
+
return NextResponse.json(guardResult.errorBody ?? { error: "Operation blocked by guard" }, {
|
|
112
|
+
status: guardResult.errorStatus ?? 422
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (guardResult.modifiedPayload) {
|
|
116
|
+
evaluation = evaluatePatch(existing, { ...evaluation.patch, ...guardResult.modifiedPayload });
|
|
117
|
+
if (!evaluation.ok) return NextResponse.json(evaluation.body, { status: evaluation.status });
|
|
118
|
+
}
|
|
119
|
+
const nextConfig = evaluation.config;
|
|
120
|
+
await service.setValue(OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, nextConfig, { tenantId });
|
|
121
|
+
await invalidateOmnibusTenantCache(resolveOmnibusCache(container), tenantId);
|
|
122
|
+
for (const callback of guardResult.afterSuccessCallbacks) {
|
|
123
|
+
if (!callback.guard.afterSuccess) continue;
|
|
124
|
+
try {
|
|
125
|
+
await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null });
|
|
126
|
+
} catch (callbackError) {
|
|
127
|
+
logger.error("Mutation guard afterSuccess callback failed", {
|
|
128
|
+
component: "catalog.config.omnibus",
|
|
129
|
+
guardId: callback.guard.id,
|
|
130
|
+
err: callbackError
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return NextResponse.json(nextConfig);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
|
|
137
|
+
logger.error("catalog.config.omnibus.PATCH failed", { err });
|
|
138
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const errorSchema = z.object({ error: z.string() }).passthrough();
|
|
142
|
+
const invalidConfigSchema = z.object({
|
|
143
|
+
error: z.literal("Invalid config"),
|
|
144
|
+
details: z.object({ fieldErrors: z.record(z.string(), z.array(z.string())) })
|
|
145
|
+
});
|
|
146
|
+
const backfillRequiredSchema = z.object({
|
|
147
|
+
field: z.literal("enabled"),
|
|
148
|
+
error: z.literal("backfill_required_before_enable"),
|
|
149
|
+
channels: z.array(z.string())
|
|
150
|
+
});
|
|
151
|
+
const openApi = {
|
|
152
|
+
tag: "Catalog",
|
|
153
|
+
summary: "Omnibus price-reference configuration",
|
|
154
|
+
methods: {
|
|
155
|
+
GET: {
|
|
156
|
+
summary: "Read the tenant Omnibus configuration",
|
|
157
|
+
description: "Returns the tenant-scoped Omnibus configuration with defaults applied, or an empty object when it has never been configured.",
|
|
158
|
+
tags: ["Catalog"],
|
|
159
|
+
responses: [
|
|
160
|
+
{ status: 200, description: "Tenant Omnibus configuration ({} when unset)", schema: omnibusConfigSchema.partial() }
|
|
161
|
+
],
|
|
162
|
+
errors: [{ status: 401, description: "Unauthorized", schema: errorSchema }]
|
|
163
|
+
},
|
|
164
|
+
PATCH: {
|
|
165
|
+
summary: "Update the tenant Omnibus configuration",
|
|
166
|
+
description: "Shallow-merges the provided top-level fields into the stored tenant configuration (a provided `channels` map replaces the stored one; `null` clears `defaultPresentedPriceKindId`). Member-state derogation fields and `backfillCoverage` are rejected. Enabling requires a resolvable presented price kind and completed backfill coverage for every in-scope EU channel.",
|
|
167
|
+
tags: ["Catalog"],
|
|
168
|
+
requestBody: { schema: omnibusConfigPatchSchema },
|
|
169
|
+
responses: [{ status: 200, description: "Merged Omnibus configuration", schema: omnibusConfigSchema }],
|
|
170
|
+
errors: [
|
|
171
|
+
{ status: 400, description: "Invalid JSON body or invalid config", schema: invalidConfigSchema },
|
|
172
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
173
|
+
{ status: 422, description: "Backfill required before enabling, or blocked by a mutation guard", schema: backfillRequiredSchema }
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
export {
|
|
179
|
+
GET,
|
|
180
|
+
PATCH,
|
|
181
|
+
metadata,
|
|
182
|
+
openApi
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/catalog/api/config/omnibus/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { omnibusConfigPatchSchema, type OmnibusConfigPatch } from '../../../data/validators'\nimport { OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, omnibusConfigSchema, type OmnibusConfig } from '../../../lib/omnibusTypes'\nimport {\n buildOmnibusFieldErrors,\n evaluateOmnibusConfig,\n invalidOmnibusConfig,\n mergeOmnibusConfig,\n parseStoredOmnibusConfig,\n type OmnibusConfigEvaluation,\n} from '../../../lib/omnibusConfig'\nimport { invalidateOmnibusTenantCache, resolveOmnibusCache } from '../../../lib/omnibusCache'\n\nconst logger = createLogger('catalog')\n\nconst OMNIBUS_CONFIG_RESOURCE_KIND = 'catalog.settings'\n\nconst OMNIBUS_CONFIG_READ_FEATURES = ['catalog.settings.view', 'catalog.settings.manage'] as const\n\nexport const metadata = {\n GET: { requireAuth: true },\n PATCH: { requireAuth: true, requireFeatures: ['catalog.settings.manage'] },\n}\n\nfunction resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nasync function readStoredConfig(service: ModuleConfigService, tenantId: string): Promise<OmnibusConfig | null> {\n const raw = await service.getValue<unknown>(OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, { scope: { tenantId } })\n const config = parseStoredOmnibusConfig(raw)\n if (raw !== null && raw !== undefined && !config) {\n logger.warn('[internal] catalog omnibus stored config is invalid; treating it as unset', { tenantId })\n }\n return config\n}\n\nfunction evaluatePatch(existing: OmnibusConfig | null, payload: unknown): OmnibusConfigEvaluation & { patch?: OmnibusConfigPatch } {\n const parsed = omnibusConfigPatchSchema.safeParse(payload)\n if (!parsed.success) return invalidOmnibusConfig(buildOmnibusFieldErrors(parsed.error.issues))\n return { ...evaluateOmnibusConfig(mergeOmnibusConfig(existing, parsed.data)), patch: parsed.data }\n}\n\nexport async function GET(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const container = await createRequestContainer()\n const rbacService = container.resolve('rbacService') as RbacService\n const scope = { tenantId: auth.tenantId, organizationId: auth.orgId ?? null }\n let canRead = false\n for (const feature of OMNIBUS_CONFIG_READ_FEATURES) {\n if (await rbacService.userHasAllFeatures(auth.sub, [feature], scope)) {\n canRead = true\n break\n }\n }\n if (!canRead) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n const service = container.resolve('moduleConfigService') as ModuleConfigService\n const config = await readStoredConfig(service, auth.tenantId)\n return NextResponse.json(config ?? {})\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n logger.error('catalog.config.omnibus.GET failed', { err })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const tenantId = auth.tenantId\n const organizationId = auth.orgId ?? null\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const service = container.resolve('moduleConfigService') as ModuleConfigService\n const existing = await readStoredConfig(service, tenantId)\n\n let evaluation = evaluatePatch(existing, body)\n if (!evaluation.ok) return NextResponse.json(evaluation.body, { status: evaluation.status })\n\n const legacyGuard = bridgeLegacyGuard(container)\n const guards: MutationGuard[] = [...getAllMutationGuardInstances()]\n if (legacyGuard) guards.push(legacyGuard)\n const guardInput = {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: OMNIBUS_CONFIG_RESOURCE_KIND,\n resourceId: OMNIBUS_CONFIG_NAME,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const guardResult = await runMutationGuards(\n guards,\n { ...guardInput, mutationPayload: evaluation.patch ?? null },\n { userFeatures: resolveUserFeatures(auth) },\n )\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, {\n status: guardResult.errorStatus ?? 422,\n })\n }\n if (guardResult.modifiedPayload) {\n evaluation = evaluatePatch(existing, { ...evaluation.patch, ...guardResult.modifiedPayload })\n if (!evaluation.ok) return NextResponse.json(evaluation.body, { status: evaluation.status })\n }\n\n const nextConfig = evaluation.config\n // optimistic-lock-exempt: single tenant-scoped config blob merged server-side; no per-record version to compare\n await service.setValue(OMNIBUS_CONFIG_MODULE_ID, OMNIBUS_CONFIG_NAME, nextConfig, { tenantId })\n await invalidateOmnibusTenantCache(resolveOmnibusCache(container), tenantId)\n\n for (const callback of guardResult.afterSuccessCallbacks) {\n if (!callback.guard.afterSuccess) continue\n try {\n await callback.guard.afterSuccess({ ...guardInput, metadata: callback.metadata ?? null })\n } catch (callbackError) {\n logger.error('Mutation guard afterSuccess callback failed', {\n component: 'catalog.config.omnibus',\n guardId: callback.guard.id,\n err: callbackError,\n })\n }\n }\n\n return NextResponse.json(nextConfig)\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n logger.error('catalog.config.omnibus.PATCH failed', { err })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst errorSchema = z.object({ error: z.string() }).passthrough()\n\nconst invalidConfigSchema = z.object({\n error: z.literal('Invalid config'),\n details: z.object({ fieldErrors: z.record(z.string(), z.array(z.string())) }),\n})\n\nconst backfillRequiredSchema = z.object({\n field: z.literal('enabled'),\n error: z.literal('backfill_required_before_enable'),\n channels: z.array(z.string()),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Catalog',\n summary: 'Omnibus price-reference configuration',\n methods: {\n GET: {\n summary: 'Read the tenant Omnibus configuration',\n description: 'Returns the tenant-scoped Omnibus configuration with defaults applied, or an empty object when it has never been configured.',\n tags: ['Catalog'],\n responses: [\n { status: 200, description: 'Tenant Omnibus configuration ({} when unset)', schema: omnibusConfigSchema.partial() },\n ],\n errors: [{ status: 401, description: 'Unauthorized', schema: errorSchema }],\n },\n PATCH: {\n summary: 'Update the tenant Omnibus configuration',\n description:\n 'Shallow-merges the provided top-level fields into the stored tenant configuration (a provided `channels` map replaces the stored one; `null` clears `defaultPresentedPriceKindId`). Member-state derogation fields and `backfillCoverage` are rejected. Enabling requires a resolvable presented price kind and completed backfill coverage for every in-scope EU channel.',\n tags: ['Catalog'],\n requestBody: { schema: omnibusConfigPatchSchema },\n responses: [{ status: 200, description: 'Merged Omnibus configuration', schema: omnibusConfigSchema }],\n errors: [\n { status: 400, description: 'Invalid JSON body or invalid config', schema: invalidConfigSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 422, description: 'Backfill required before enabling, or blocked by a mutation guard', schema: backfillRequiredSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
7
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
8
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
9
|
+
import { CatalogPriceHistoryEntry } from "../../../data/entities.js";
|
|
10
|
+
import { priceHistoryQuerySchema } from "../../../data/validators.js";
|
|
11
|
+
import { PRICE_HISTORY_CHANGE_TYPES, PRICE_HISTORY_SOURCES } from "../../../lib/omnibusTypes.js";
|
|
12
|
+
import {
|
|
13
|
+
applyPriceHistoryCursor,
|
|
14
|
+
buildPriceHistoryWhere,
|
|
15
|
+
decodePriceHistoryCursor,
|
|
16
|
+
encodePriceHistoryCursor,
|
|
17
|
+
resolvePriceHistoryOrganizationIds,
|
|
18
|
+
serializePriceHistoryEntry
|
|
19
|
+
} from "../../../lib/priceHistoryQuery.js";
|
|
20
|
+
const logger = createLogger("catalog");
|
|
21
|
+
const metadata = {
|
|
22
|
+
GET: { requireAuth: true, requireFeatures: ["catalog.price_history.view"] }
|
|
23
|
+
};
|
|
24
|
+
async function GET(req) {
|
|
25
|
+
try {
|
|
26
|
+
const auth = await getAuthFromRequest(req);
|
|
27
|
+
if (!auth || !auth.tenantId) {
|
|
28
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
29
|
+
}
|
|
30
|
+
const url = new URL(req.url);
|
|
31
|
+
const parsed = priceHistoryQuerySchema.safeParse(Object.fromEntries(url.searchParams));
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: "Invalid query", details: z.flattenError(parsed.error) },
|
|
35
|
+
{ status: 400 }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const query = parsed.data;
|
|
39
|
+
const container = await createRequestContainer();
|
|
40
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
41
|
+
const tenantId = scope.tenantId ?? auth.tenantId;
|
|
42
|
+
const organizationIds = resolvePriceHistoryOrganizationIds(scope, auth.orgId);
|
|
43
|
+
if (organizationIds !== null && organizationIds.length === 0) {
|
|
44
|
+
return NextResponse.json({ items: [], nextCursor: null, ...query.includeTotal === "true" ? { total: 0 } : {} });
|
|
45
|
+
}
|
|
46
|
+
const em = container.resolve("em").fork();
|
|
47
|
+
const baseWhere = buildPriceHistoryWhere(
|
|
48
|
+
{ tenantId, organizationIds },
|
|
49
|
+
query
|
|
50
|
+
);
|
|
51
|
+
const cursor = decodePriceHistoryCursor(query.cursor);
|
|
52
|
+
const rows = await findWithDecryption(
|
|
53
|
+
em,
|
|
54
|
+
CatalogPriceHistoryEntry,
|
|
55
|
+
applyPriceHistoryCursor(baseWhere, cursor),
|
|
56
|
+
{ orderBy: { recordedAt: "desc", id: "desc" }, limit: query.pageSize + 1 },
|
|
57
|
+
{ tenantId, organizationId: scope.selectedId ?? auth.orgId ?? null }
|
|
58
|
+
);
|
|
59
|
+
const hasMore = rows.length > query.pageSize;
|
|
60
|
+
const pageRows = hasMore ? rows.slice(0, query.pageSize) : rows;
|
|
61
|
+
const items = pageRows.map(serializePriceHistoryEntry);
|
|
62
|
+
const last = items[items.length - 1];
|
|
63
|
+
const nextCursor = hasMore && last ? encodePriceHistoryCursor({ recordedAt: last.recordedAt, id: last.id }) : null;
|
|
64
|
+
if (query.includeTotal === "true") {
|
|
65
|
+
const total = await em.count(CatalogPriceHistoryEntry, baseWhere);
|
|
66
|
+
return NextResponse.json({ items, nextCursor, total });
|
|
67
|
+
}
|
|
68
|
+
return NextResponse.json({ items, nextCursor });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.error("catalog.prices.history.GET Unexpected error", { err });
|
|
71
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
72
|
+
module: "catalog",
|
|
73
|
+
code: "catalog.price_history_read_failed"
|
|
74
|
+
});
|
|
75
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const priceHistoryItemSchema = z.object({
|
|
79
|
+
id: z.string().uuid(),
|
|
80
|
+
priceId: z.string().uuid(),
|
|
81
|
+
productId: z.string().uuid(),
|
|
82
|
+
variantId: z.string().uuid().nullable(),
|
|
83
|
+
offerId: z.string().uuid().nullable(),
|
|
84
|
+
channelId: z.string().uuid().nullable(),
|
|
85
|
+
priceKindId: z.string().uuid(),
|
|
86
|
+
priceKindCode: z.string(),
|
|
87
|
+
currencyCode: z.string(),
|
|
88
|
+
unitPriceNet: z.string().nullable(),
|
|
89
|
+
unitPriceGross: z.string().nullable(),
|
|
90
|
+
taxRate: z.string().nullable(),
|
|
91
|
+
taxAmount: z.string().nullable(),
|
|
92
|
+
minQuantity: z.number().int().nullable(),
|
|
93
|
+
maxQuantity: z.number().int().nullable(),
|
|
94
|
+
startsAt: z.string().nullable(),
|
|
95
|
+
endsAt: z.string().nullable(),
|
|
96
|
+
recordedAt: z.string(),
|
|
97
|
+
changeType: z.enum(PRICE_HISTORY_CHANGE_TYPES),
|
|
98
|
+
source: z.enum(PRICE_HISTORY_SOURCES),
|
|
99
|
+
isAnnounced: z.boolean()
|
|
100
|
+
});
|
|
101
|
+
const priceHistoryResponseSchema = z.object({
|
|
102
|
+
items: z.array(priceHistoryItemSchema),
|
|
103
|
+
nextCursor: z.string().nullable(),
|
|
104
|
+
total: z.number().int().optional()
|
|
105
|
+
});
|
|
106
|
+
const errorSchema = z.object({ error: z.string() });
|
|
107
|
+
const openApi = {
|
|
108
|
+
tag: "Catalog",
|
|
109
|
+
summary: "Catalog price history",
|
|
110
|
+
methods: {
|
|
111
|
+
GET: {
|
|
112
|
+
summary: "List recorded price history entries",
|
|
113
|
+
description: "Returns immutable price history snapshots for one product and/or variant, ordered by recordedAt DESC, id DESC with keyset pagination. At least one of productId or variantId is required (400 otherwise) so every read stays on a scoped index. Monetary values are fixed 4-decimal strings. An invalid cursor returns the first page.",
|
|
114
|
+
query: priceHistoryQuerySchema,
|
|
115
|
+
responses: [
|
|
116
|
+
{ status: 200, description: "Price history page", schema: priceHistoryResponseSchema }
|
|
117
|
+
],
|
|
118
|
+
errors: [
|
|
119
|
+
{ status: 400, description: "Invalid query, or neither productId nor variantId provided", schema: errorSchema },
|
|
120
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
121
|
+
{ status: 403, description: "Missing catalog.price_history.view", schema: errorSchema }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
export {
|
|
127
|
+
GET,
|
|
128
|
+
metadata,
|
|
129
|
+
openApi
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/catalog/api/prices/history/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CatalogPriceHistoryEntry } from '../../../data/entities'\nimport { priceHistoryQuerySchema } from '../../../data/validators'\nimport { PRICE_HISTORY_CHANGE_TYPES, PRICE_HISTORY_SOURCES } from '../../../lib/omnibusTypes'\nimport {\n applyPriceHistoryCursor,\n buildPriceHistoryWhere,\n decodePriceHistoryCursor,\n encodePriceHistoryCursor,\n resolvePriceHistoryOrganizationIds,\n serializePriceHistoryEntry,\n} from '../../../lib/priceHistoryQuery'\n\nconst logger = createLogger('catalog')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['catalog.price_history.view'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = priceHistoryQuerySchema.safeParse(Object.fromEntries(url.searchParams))\n if (!parsed.success) {\n return NextResponse.json(\n { error: 'Invalid query', details: z.flattenError(parsed.error) },\n { status: 400 },\n )\n }\n const query = parsed.data\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope.tenantId ?? auth.tenantId\n const organizationIds = resolvePriceHistoryOrganizationIds(scope, auth.orgId)\n if (organizationIds !== null && organizationIds.length === 0) {\n return NextResponse.json({ items: [], nextCursor: null, ...(query.includeTotal === 'true' ? { total: 0 } : {}) })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const baseWhere = buildPriceHistoryWhere(\n { tenantId, organizationIds },\n query,\n )\n const cursor = decodePriceHistoryCursor(query.cursor)\n const rows = await findWithDecryption(\n em,\n CatalogPriceHistoryEntry,\n applyPriceHistoryCursor(baseWhere, cursor),\n { orderBy: { recordedAt: 'desc', id: 'desc' }, limit: query.pageSize + 1 },\n { tenantId, organizationId: scope.selectedId ?? auth.orgId ?? null },\n )\n const hasMore = rows.length > query.pageSize\n const pageRows = hasMore ? rows.slice(0, query.pageSize) : rows\n const items = pageRows.map(serializePriceHistoryEntry)\n const last = items[items.length - 1]\n const nextCursor = hasMore && last ? encodePriceHistoryCursor({ recordedAt: last.recordedAt, id: last.id }) : null\n\n if (query.includeTotal === 'true') {\n const total = await em.count(CatalogPriceHistoryEntry, baseWhere)\n return NextResponse.json({ items, nextCursor, total })\n }\n return NextResponse.json({ items, nextCursor })\n } catch (err) {\n logger.error('catalog.prices.history.GET Unexpected error', { err })\n getTelemetryRuntime()?.reportError(err, {\n module: 'catalog',\n code: 'catalog.price_history_read_failed',\n })\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst priceHistoryItemSchema = z.object({\n id: z.string().uuid(),\n priceId: z.string().uuid(),\n productId: z.string().uuid(),\n variantId: z.string().uuid().nullable(),\n offerId: z.string().uuid().nullable(),\n channelId: z.string().uuid().nullable(),\n priceKindId: z.string().uuid(),\n priceKindCode: z.string(),\n currencyCode: z.string(),\n unitPriceNet: z.string().nullable(),\n unitPriceGross: z.string().nullable(),\n taxRate: z.string().nullable(),\n taxAmount: z.string().nullable(),\n minQuantity: z.number().int().nullable(),\n maxQuantity: z.number().int().nullable(),\n startsAt: z.string().nullable(),\n endsAt: z.string().nullable(),\n recordedAt: z.string(),\n changeType: z.enum(PRICE_HISTORY_CHANGE_TYPES),\n source: z.enum(PRICE_HISTORY_SOURCES),\n isAnnounced: z.boolean(),\n})\n\nconst priceHistoryResponseSchema = z.object({\n items: z.array(priceHistoryItemSchema),\n nextCursor: z.string().nullable(),\n total: z.number().int().optional(),\n})\n\nconst errorSchema = z.object({ error: z.string() })\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Catalog',\n summary: 'Catalog price history',\n methods: {\n GET: {\n summary: 'List recorded price history entries',\n description:\n 'Returns immutable price history snapshots for one product and/or variant, ordered by recordedAt DESC, id DESC with keyset pagination. At least one of productId or variantId is required (400 otherwise) so every read stays on a scoped index. Monetary values are fixed 4-decimal strings. An invalid cursor returns the first page.',\n query: priceHistoryQuerySchema,\n responses: [\n { status: 200, description: 'Price history page', schema: priceHistoryResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query, or neither productId nor variantId provided', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Missing catalog.price_history.view', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
7
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
8
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
9
|
+
import {
|
|
10
|
+
CatalogOffer,
|
|
11
|
+
CatalogPriceKind,
|
|
12
|
+
CatalogProduct,
|
|
13
|
+
CatalogProductPrice,
|
|
14
|
+
CatalogProductVariant
|
|
15
|
+
} from "../../../data/entities.js";
|
|
16
|
+
import { omnibusPreviewQuerySchema } from "../../../data/validators.js";
|
|
17
|
+
import { omnibusBlockSchema } from "../../../lib/omnibusTypes.js";
|
|
18
|
+
import { resolveOmnibusPresentedEntries } from "../../../lib/omnibusPresentedEntry.js";
|
|
19
|
+
import { resolvePriceHistoryOrganizationIds } from "../../../lib/priceHistoryQuery.js";
|
|
20
|
+
import { resolvePriceVariantId, selectBestPrice } from "../../../lib/pricing.js";
|
|
21
|
+
const logger = createLogger("catalog");
|
|
22
|
+
const metadata = {
|
|
23
|
+
GET: { requireAuth: true, requireFeatures: ["catalog.price_history.view"] }
|
|
24
|
+
};
|
|
25
|
+
const NOT_FOUND = { error: "Not found" };
|
|
26
|
+
function relationId(value) {
|
|
27
|
+
if (!value) return null;
|
|
28
|
+
return typeof value === "string" ? value : value.id;
|
|
29
|
+
}
|
|
30
|
+
async function GET(req) {
|
|
31
|
+
try {
|
|
32
|
+
const auth = await getAuthFromRequest(req);
|
|
33
|
+
if (!auth || !auth.tenantId) {
|
|
34
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
35
|
+
}
|
|
36
|
+
const url = new URL(req.url);
|
|
37
|
+
const parsed = omnibusPreviewQuerySchema.safeParse(Object.fromEntries(url.searchParams));
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
{ error: "Invalid query", details: z.flattenError(parsed.error) },
|
|
41
|
+
{ status: 400 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const query = parsed.data;
|
|
45
|
+
const container = await createRequestContainer();
|
|
46
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
47
|
+
const tenantId = scope.tenantId ?? auth.tenantId;
|
|
48
|
+
const organizationIds = resolvePriceHistoryOrganizationIds(scope, auth.orgId);
|
|
49
|
+
if (organizationIds !== null && organizationIds.length === 0) {
|
|
50
|
+
return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
51
|
+
}
|
|
52
|
+
const organizationFilter = organizationIds === null ? {} : { organizationId: { $in: organizationIds } };
|
|
53
|
+
const decryptionScope = { tenantId, organizationId: scope.selectedId ?? auth.orgId ?? null };
|
|
54
|
+
const em = container.resolve("em").fork();
|
|
55
|
+
const offer = query.offerId ? await findOneWithDecryption(
|
|
56
|
+
em,
|
|
57
|
+
CatalogOffer,
|
|
58
|
+
{ id: query.offerId, tenantId, deletedAt: null, ...organizationFilter },
|
|
59
|
+
void 0,
|
|
60
|
+
decryptionScope
|
|
61
|
+
) : null;
|
|
62
|
+
if (query.offerId && !offer) return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
63
|
+
const variant = query.variantId ? await findOneWithDecryption(
|
|
64
|
+
em,
|
|
65
|
+
CatalogProductVariant,
|
|
66
|
+
{ id: query.variantId, tenantId, deletedAt: null, ...organizationFilter },
|
|
67
|
+
void 0,
|
|
68
|
+
decryptionScope
|
|
69
|
+
) : null;
|
|
70
|
+
if (query.variantId && !variant) return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
71
|
+
const productIds = new Set(
|
|
72
|
+
[query.productId ?? null, relationId(offer?.product), relationId(variant?.product)].filter(
|
|
73
|
+
(id) => typeof id === "string"
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
if (productIds.size !== 1) return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
77
|
+
const [productId] = Array.from(productIds);
|
|
78
|
+
const product = await findOneWithDecryption(
|
|
79
|
+
em,
|
|
80
|
+
CatalogProduct,
|
|
81
|
+
{ id: productId, tenantId, deletedAt: null, ...organizationFilter },
|
|
82
|
+
void 0,
|
|
83
|
+
decryptionScope
|
|
84
|
+
);
|
|
85
|
+
if (!product) return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
86
|
+
const organizationId = product.organizationId;
|
|
87
|
+
const productScope = { tenantId, organizationId };
|
|
88
|
+
const priceKind = await findOneWithDecryption(
|
|
89
|
+
em,
|
|
90
|
+
CatalogPriceKind,
|
|
91
|
+
{
|
|
92
|
+
id: query.priceKindId,
|
|
93
|
+
tenantId,
|
|
94
|
+
deletedAt: null,
|
|
95
|
+
$or: [{ organizationId }, { organizationId: null }]
|
|
96
|
+
},
|
|
97
|
+
void 0,
|
|
98
|
+
productScope
|
|
99
|
+
);
|
|
100
|
+
if (!priceKind) return NextResponse.json(NOT_FOUND, { status: 404 });
|
|
101
|
+
const priceRows = await findWithDecryption(
|
|
102
|
+
em,
|
|
103
|
+
CatalogProductPrice,
|
|
104
|
+
{
|
|
105
|
+
tenantId,
|
|
106
|
+
organizationId,
|
|
107
|
+
priceKind: query.priceKindId,
|
|
108
|
+
currencyCode: query.currencyCode,
|
|
109
|
+
$or: [{ product: productId }, ...query.variantId ? [{ variant: query.variantId }] : []]
|
|
110
|
+
},
|
|
111
|
+
{ populate: ["offer", "variant", "product", "priceKind"] },
|
|
112
|
+
productScope
|
|
113
|
+
);
|
|
114
|
+
const scopedRows = priceRows.filter((row) => {
|
|
115
|
+
const rowVariantId = resolvePriceVariantId(row);
|
|
116
|
+
return !query.variantId || !rowVariantId || rowVariantId === query.variantId;
|
|
117
|
+
});
|
|
118
|
+
const presentedPrice = selectBestPrice(scopedRows, {
|
|
119
|
+
channelId: query.channelId ?? null,
|
|
120
|
+
offerId: query.offerId ?? null,
|
|
121
|
+
priceKindId: query.priceKindId,
|
|
122
|
+
currencyCode: query.currencyCode,
|
|
123
|
+
quantity: 1,
|
|
124
|
+
date: /* @__PURE__ */ new Date()
|
|
125
|
+
});
|
|
126
|
+
const presentedEntries = presentedPrice ? await resolveOmnibusPresentedEntries(em, [presentedPrice]) : /* @__PURE__ */ new Map();
|
|
127
|
+
const service = container.resolve("catalogOmnibusService");
|
|
128
|
+
const block = await service.resolveOmnibusBlock(
|
|
129
|
+
em,
|
|
130
|
+
{
|
|
131
|
+
tenantId,
|
|
132
|
+
organizationId,
|
|
133
|
+
productId,
|
|
134
|
+
variantId: query.variantId ?? null,
|
|
135
|
+
offerId: query.offerId ?? null,
|
|
136
|
+
channelId: query.channelId ?? null,
|
|
137
|
+
priceKindId: query.priceKindId,
|
|
138
|
+
currencyCode: query.currencyCode,
|
|
139
|
+
isStorefront: false
|
|
140
|
+
},
|
|
141
|
+
presentedPrice ? presentedEntries.get(presentedPrice.id) ?? null : null,
|
|
142
|
+
priceKind.isPromotion === true
|
|
143
|
+
);
|
|
144
|
+
return NextResponse.json(block);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.error("catalog.prices.omnibus-preview.GET Unexpected error", { err });
|
|
147
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
148
|
+
module: "catalog",
|
|
149
|
+
code: "catalog.omnibus_preview_failed"
|
|
150
|
+
});
|
|
151
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const errorSchema = z.object({ error: z.string() });
|
|
155
|
+
const openApi = {
|
|
156
|
+
tag: "Catalog",
|
|
157
|
+
summary: "Catalog Omnibus reference-price preview",
|
|
158
|
+
methods: {
|
|
159
|
+
GET: {
|
|
160
|
+
summary: "Preview the Omnibus reference-price block",
|
|
161
|
+
description: "Resolves the EU Omnibus reference-price block for one product, variant or offer and price kind, using the current active price of that kind as the presented entry. Returns null when Omnibus is disabled for the tenant. Monetary values are fixed 4-decimal strings.",
|
|
162
|
+
query: omnibusPreviewQuerySchema,
|
|
163
|
+
responses: [
|
|
164
|
+
{
|
|
165
|
+
status: 200,
|
|
166
|
+
description: "Omnibus block, or null when Omnibus is disabled",
|
|
167
|
+
schema: omnibusBlockSchema.nullable()
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
errors: [
|
|
171
|
+
{ status: 400, description: "Invalid query", schema: errorSchema },
|
|
172
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
173
|
+
{ status: 403, description: "Missing catalog.price_history.view", schema: errorSchema },
|
|
174
|
+
{ status: 404, description: "Product, variant, offer or price kind not found in scope", schema: errorSchema }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
export {
|
|
180
|
+
GET,
|
|
181
|
+
metadata,
|
|
182
|
+
openApi
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=route.js.map
|