@endora-commerce/mod-catalog 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/admin/components/PackagingUnitsEditor.d.ts +12 -0
- package/dist/admin/components/PackagingUnitsEditor.d.ts.map +1 -0
- package/dist/admin/components/PackagingUnitsEditor.js +99 -0
- package/dist/admin/components/PackagingUnitsEditor.js.map +1 -0
- package/dist/admin/components/ProductAttributesTab.d.ts +13 -0
- package/dist/admin/components/ProductAttributesTab.d.ts.map +1 -0
- package/dist/admin/components/ProductAttributesTab.js +140 -0
- package/dist/admin/components/ProductAttributesTab.js.map +1 -0
- package/dist/admin/components/ProductInventoryTab.d.ts +14 -0
- package/dist/admin/components/ProductInventoryTab.d.ts.map +1 -0
- package/dist/admin/components/ProductInventoryTab.js +284 -0
- package/dist/admin/components/ProductInventoryTab.js.map +1 -0
- package/dist/admin/components/ProductScopeEditor.d.ts +94 -0
- package/dist/admin/components/ProductScopeEditor.d.ts.map +1 -0
- package/dist/admin/components/ProductScopeEditor.js +395 -0
- package/dist/admin/components/ProductScopeEditor.js.map +1 -0
- package/dist/admin/components/ProductsBulkEditDialog.d.ts +11 -0
- package/dist/admin/components/ProductsBulkEditDialog.d.ts.map +1 -0
- package/dist/admin/components/ProductsBulkEditDialog.js +304 -0
- package/dist/admin/components/ProductsBulkEditDialog.js.map +1 -0
- package/dist/admin/index.d.ts +45 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +184 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/lib/resolve-product-selection.d.ts +21 -0
- package/dist/admin/lib/resolve-product-selection.d.ts.map +1 -0
- package/dist/admin/lib/resolve-product-selection.js +25 -0
- package/dist/admin/lib/resolve-product-selection.js.map +1 -0
- package/dist/admin/pages/AttachmentTypesPage.d.ts +18 -0
- package/dist/admin/pages/AttachmentTypesPage.d.ts.map +1 -0
- package/dist/admin/pages/AttachmentTypesPage.js +92 -0
- package/dist/admin/pages/AttachmentTypesPage.js.map +1 -0
- package/dist/admin/pages/AttributeSetsPage.d.ts +21 -0
- package/dist/admin/pages/AttributeSetsPage.d.ts.map +1 -0
- package/dist/admin/pages/AttributeSetsPage.js +158 -0
- package/dist/admin/pages/AttributeSetsPage.js.map +1 -0
- package/dist/admin/pages/AttributesManager.d.ts +9 -0
- package/dist/admin/pages/AttributesManager.d.ts.map +1 -0
- package/dist/admin/pages/AttributesManager.js +305 -0
- package/dist/admin/pages/AttributesManager.js.map +1 -0
- package/dist/admin/pages/BulkOperationDetailPage.d.ts +9 -0
- package/dist/admin/pages/BulkOperationDetailPage.d.ts.map +1 -0
- package/dist/admin/pages/BulkOperationDetailPage.js +187 -0
- package/dist/admin/pages/BulkOperationDetailPage.js.map +1 -0
- package/dist/admin/pages/BulkOperationsPage.d.ts +9 -0
- package/dist/admin/pages/BulkOperationsPage.d.ts.map +1 -0
- package/dist/admin/pages/BulkOperationsPage.js +98 -0
- package/dist/admin/pages/BulkOperationsPage.js.map +1 -0
- package/dist/admin/pages/CategoriesTree.d.ts +9 -0
- package/dist/admin/pages/CategoriesTree.d.ts.map +1 -0
- package/dist/admin/pages/CategoriesTree.js +176 -0
- package/dist/admin/pages/CategoriesTree.js.map +1 -0
- package/dist/admin/pages/ProductEditor.d.ts +13 -0
- package/dist/admin/pages/ProductEditor.d.ts.map +1 -0
- package/dist/admin/pages/ProductEditor.js +1050 -0
- package/dist/admin/pages/ProductEditor.js.map +1 -0
- package/dist/admin/pages/ProductsList.d.ts +9 -0
- package/dist/admin/pages/ProductsList.d.ts.map +1 -0
- package/dist/admin/pages/ProductsList.js +399 -0
- package/dist/admin/pages/ProductsList.js.map +1 -0
- package/dist/backend/commands/attribute-commands.d.ts +111 -0
- package/dist/backend/commands/attribute-commands.d.ts.map +1 -0
- package/dist/backend/commands/attribute-commands.js +482 -0
- package/dist/backend/commands/attribute-commands.js.map +1 -0
- package/dist/backend/demo/reset.d.ts +23 -0
- package/dist/backend/demo/reset.d.ts.map +1 -0
- package/dist/backend/demo/reset.js +41 -0
- package/dist/backend/demo/reset.js.map +1 -0
- package/dist/backend/demo/rows.d.ts +103 -0
- package/dist/backend/demo/rows.d.ts.map +1 -0
- package/dist/backend/demo/rows.js +162 -0
- package/dist/backend/demo/rows.js.map +1 -0
- package/dist/backend/demo/seed.d.ts +35 -0
- package/dist/backend/demo/seed.d.ts.map +1 -0
- package/dist/backend/demo/seed.js +177 -0
- package/dist/backend/demo/seed.js.map +1 -0
- package/dist/backend/entities/attachment-type.entity.d.ts +16 -0
- package/dist/backend/entities/attachment-type.entity.d.ts.map +1 -0
- package/dist/backend/entities/attachment-type.entity.js +57 -0
- package/dist/backend/entities/attachment-type.entity.js.map +1 -0
- package/dist/backend/entities/attribute-set-attribute.entity.d.ts +26 -0
- package/dist/backend/entities/attribute-set-attribute.entity.d.ts.map +1 -0
- package/dist/backend/entities/attribute-set-attribute.entity.js +55 -0
- package/dist/backend/entities/attribute-set-attribute.entity.js.map +1 -0
- package/dist/backend/entities/attribute-set.entity.d.ts +22 -0
- package/dist/backend/entities/attribute-set.entity.d.ts.map +1 -0
- package/dist/backend/entities/attribute-set.entity.js +67 -0
- package/dist/backend/entities/attribute-set.entity.js.map +1 -0
- package/dist/backend/entities/bulk-operation.entity.d.ts +78 -0
- package/dist/backend/entities/bulk-operation.entity.d.ts.map +1 -0
- package/dist/backend/entities/bulk-operation.entity.js +138 -0
- package/dist/backend/entities/bulk-operation.entity.js.map +1 -0
- package/dist/backend/entities/bundle-slot-option.entity.d.ts +23 -0
- package/dist/backend/entities/bundle-slot-option.entity.d.ts.map +1 -0
- package/dist/backend/entities/bundle-slot-option.entity.js +68 -0
- package/dist/backend/entities/bundle-slot-option.entity.js.map +1 -0
- package/dist/backend/entities/bundle-slot.entity.d.ts +25 -0
- package/dist/backend/entities/bundle-slot.entity.d.ts.map +1 -0
- package/dist/backend/entities/bundle-slot.entity.js +74 -0
- package/dist/backend/entities/bundle-slot.entity.js.map +1 -0
- package/dist/backend/entities/category.entity.d.ts +26 -0
- package/dist/backend/entities/category.entity.d.ts.map +1 -0
- package/dist/backend/entities/category.entity.js +119 -0
- package/dist/backend/entities/category.entity.js.map +1 -0
- package/dist/backend/entities/gallery-item-label.entity.d.ts +22 -0
- package/dist/backend/entities/gallery-item-label.entity.d.ts.map +1 -0
- package/dist/backend/entities/gallery-item-label.entity.js +50 -0
- package/dist/backend/entities/gallery-item-label.entity.js.map +1 -0
- package/dist/backend/entities/gallery-item.entity.d.ts +17 -0
- package/dist/backend/entities/gallery-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/gallery-item.entity.js +58 -0
- package/dist/backend/entities/gallery-item.entity.js.map +1 -0
- package/dist/backend/entities/grouped-item.entity.d.ts +23 -0
- package/dist/backend/entities/grouped-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/grouped-item.entity.js +68 -0
- package/dist/backend/entities/grouped-item.entity.js.map +1 -0
- package/dist/backend/entities/product-attachment.entity.d.ts +23 -0
- package/dist/backend/entities/product-attachment.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-attachment.entity.js +77 -0
- package/dist/backend/entities/product-attachment.entity.js.map +1 -0
- package/dist/backend/entities/product-attribute.entity.d.ts +72 -0
- package/dist/backend/entities/product-attribute.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-attribute.entity.js +161 -0
- package/dist/backend/entities/product-attribute.entity.js.map +1 -0
- package/dist/backend/entities/product-editor-preference.entity.d.ts +28 -0
- package/dist/backend/entities/product-editor-preference.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-editor-preference.entity.js +63 -0
- package/dist/backend/entities/product-editor-preference.entity.js.map +1 -0
- package/dist/backend/entities/product-link.entity.d.ts +25 -0
- package/dist/backend/entities/product-link.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-link.entity.js +70 -0
- package/dist/backend/entities/product-link.entity.js.map +1 -0
- package/dist/backend/entities/product-packaging-unit.entity.d.ts +26 -0
- package/dist/backend/entities/product-packaging-unit.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-packaging-unit.entity.js +76 -0
- package/dist/backend/entities/product-packaging-unit.entity.js.map +1 -0
- package/dist/backend/entities/product-value-override.entity.d.ts +43 -0
- package/dist/backend/entities/product-value-override.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-value-override.entity.js +90 -0
- package/dist/backend/entities/product-value-override.entity.js.map +1 -0
- package/dist/backend/entities/product-variant.entity.d.ts +18 -0
- package/dist/backend/entities/product-variant.entity.d.ts.map +1 -0
- package/dist/backend/entities/product-variant.entity.js +68 -0
- package/dist/backend/entities/product-variant.entity.js.map +1 -0
- package/dist/backend/entities/product.entity.d.ts +108 -0
- package/dist/backend/entities/product.entity.d.ts.map +1 -0
- package/dist/backend/entities/product.entity.js +230 -0
- package/dist/backend/entities/product.entity.js.map +1 -0
- package/dist/backend/index.d.ts +262 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +642 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +216 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +223 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/prompt-tools.d.ts +47 -0
- package/dist/backend/prompt-tools.d.ts.map +1 -0
- package/dist/backend/prompt-tools.js +284 -0
- package/dist/backend/prompt-tools.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +105 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +1162 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.api-key.d.ts +33 -0
- package/dist/backend/routes.api-key.d.ts.map +1 -0
- package/dist/backend/routes.api-key.js +43 -0
- package/dist/backend/routes.api-key.js.map +1 -0
- package/dist/backend/routes.external.d.ts +32 -0
- package/dist/backend/routes.external.d.ts.map +1 -0
- package/dist/backend/routes.external.js +115 -0
- package/dist/backend/routes.external.js.map +1 -0
- package/dist/backend/routes.public.d.ts +46 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +318 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/services/asset-references.d.ts +4 -0
- package/dist/backend/services/asset-references.d.ts.map +1 -0
- package/dist/backend/services/asset-references.js +104 -0
- package/dist/backend/services/asset-references.js.map +1 -0
- package/dist/backend/services/attachment.service.d.ts +45 -0
- package/dist/backend/services/attachment.service.d.ts.map +1 -0
- package/dist/backend/services/attachment.service.js +268 -0
- package/dist/backend/services/attachment.service.js.map +1 -0
- package/dist/backend/services/attribute-option-validator.d.ts +47 -0
- package/dist/backend/services/attribute-option-validator.d.ts.map +1 -0
- package/dist/backend/services/attribute-option-validator.js +69 -0
- package/dist/backend/services/attribute-option-validator.js.map +1 -0
- package/dist/backend/services/attribute-set-validations.d.ts +65 -0
- package/dist/backend/services/attribute-set-validations.d.ts.map +1 -0
- package/dist/backend/services/attribute-set-validations.js +89 -0
- package/dist/backend/services/attribute-set-validations.js.map +1 -0
- package/dist/backend/services/attribute-set.service.d.ts +50 -0
- package/dist/backend/services/attribute-set.service.d.ts.map +1 -0
- package/dist/backend/services/attribute-set.service.js +401 -0
- package/dist/backend/services/attribute-set.service.js.map +1 -0
- package/dist/backend/services/attribute-type-mapping.d.ts +66 -0
- package/dist/backend/services/attribute-type-mapping.d.ts.map +1 -0
- package/dist/backend/services/attribute-type-mapping.js +121 -0
- package/dist/backend/services/attribute-type-mapping.js.map +1 -0
- package/dist/backend/services/attribute-value-key.service.d.ts +48 -0
- package/dist/backend/services/attribute-value-key.service.d.ts.map +1 -0
- package/dist/backend/services/attribute-value-key.service.js +118 -0
- package/dist/backend/services/attribute-value-key.service.js.map +1 -0
- package/dist/backend/services/audit-references.d.ts +13 -0
- package/dist/backend/services/audit-references.d.ts.map +1 -0
- package/dist/backend/services/audit-references.js +51 -0
- package/dist/backend/services/audit-references.js.map +1 -0
- package/dist/backend/services/bulk-operation-queue.d.ts +26 -0
- package/dist/backend/services/bulk-operation-queue.d.ts.map +1 -0
- package/dist/backend/services/bulk-operation-queue.js +34 -0
- package/dist/backend/services/bulk-operation-queue.js.map +1 -0
- package/dist/backend/services/bulk-operation.service.d.ts +195 -0
- package/dist/backend/services/bulk-operation.service.d.ts.map +1 -0
- package/dist/backend/services/bulk-operation.service.js +510 -0
- package/dist/backend/services/bulk-operation.service.js.map +1 -0
- package/dist/backend/services/bundle.service.d.ts +87 -0
- package/dist/backend/services/bundle.service.d.ts.map +1 -0
- package/dist/backend/services/bundle.service.js +286 -0
- package/dist/backend/services/bundle.service.js.map +1 -0
- package/dist/backend/services/catalog-admin.service.d.ts +398 -0
- package/dist/backend/services/catalog-admin.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-admin.service.js +1516 -0
- package/dist/backend/services/catalog-admin.service.js.map +1 -0
- package/dist/backend/services/catalog-attribute-read.service.d.ts +78 -0
- package/dist/backend/services/catalog-attribute-read.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-attribute-read.service.js +216 -0
- package/dist/backend/services/catalog-attribute-read.service.js.map +1 -0
- package/dist/backend/services/catalog-bulk-import.service.d.ts +30 -0
- package/dist/backend/services/catalog-bulk-import.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-bulk-import.service.js +136 -0
- package/dist/backend/services/catalog-bulk-import.service.js.map +1 -0
- package/dist/backend/services/catalog-bulk-update.service.d.ts +91 -0
- package/dist/backend/services/catalog-bulk-update.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-bulk-update.service.js +330 -0
- package/dist/backend/services/catalog-bulk-update.service.js.map +1 -0
- package/dist/backend/services/catalog-category-read.service.d.ts +66 -0
- package/dist/backend/services/catalog-category-read.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-category-read.service.js +248 -0
- package/dist/backend/services/catalog-category-read.service.js.map +1 -0
- package/dist/backend/services/catalog-org-price-decorator.d.ts +90 -0
- package/dist/backend/services/catalog-org-price-decorator.d.ts.map +1 -0
- package/dist/backend/services/catalog-org-price-decorator.js +184 -0
- package/dist/backend/services/catalog-org-price-decorator.js.map +1 -0
- package/dist/backend/services/catalog-product-filter.service.d.ts +82 -0
- package/dist/backend/services/catalog-product-filter.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-product-filter.service.js +275 -0
- package/dist/backend/services/catalog-product-filter.service.js.map +1 -0
- package/dist/backend/services/catalog-product-read.service.d.ts +46 -0
- package/dist/backend/services/catalog-product-read.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-product-read.service.js +197 -0
- package/dist/backend/services/catalog-product-read.service.js.map +1 -0
- package/dist/backend/services/catalog-query.service.d.ts +444 -0
- package/dist/backend/services/catalog-query.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-query.service.js +1633 -0
- package/dist/backend/services/catalog-query.service.js.map +1 -0
- package/dist/backend/services/catalog-quick-search.service.d.ts +78 -0
- package/dist/backend/services/catalog-quick-search.service.d.ts.map +1 -0
- package/dist/backend/services/catalog-quick-search.service.js +159 -0
- package/dist/backend/services/catalog-quick-search.service.js.map +1 -0
- package/dist/backend/services/catalog-write-ports.d.ts +25 -0
- package/dist/backend/services/catalog-write-ports.d.ts.map +1 -0
- package/dist/backend/services/catalog-write-ports.js +63 -0
- package/dist/backend/services/catalog-write-ports.js.map +1 -0
- package/dist/backend/services/category-admin.service.d.ts +102 -0
- package/dist/backend/services/category-admin.service.d.ts.map +1 -0
- package/dist/backend/services/category-admin.service.js +271 -0
- package/dist/backend/services/category-admin.service.js.map +1 -0
- package/dist/backend/services/gallery.service.d.ts +85 -0
- package/dist/backend/services/gallery.service.d.ts.map +1 -0
- package/dist/backend/services/gallery.service.js +326 -0
- package/dist/backend/services/gallery.service.js.map +1 -0
- package/dist/backend/services/grouped.service.d.ts +39 -0
- package/dist/backend/services/grouped.service.d.ts.map +1 -0
- package/dist/backend/services/grouped.service.js +136 -0
- package/dist/backend/services/grouped.service.js.map +1 -0
- package/dist/backend/services/label-resolver.d.ts +16 -0
- package/dist/backend/services/label-resolver.d.ts.map +1 -0
- package/dist/backend/services/label-resolver.js +43 -0
- package/dist/backend/services/label-resolver.js.map +1 -0
- package/dist/backend/services/packaging-unit.service.d.ts +28 -0
- package/dist/backend/services/packaging-unit.service.d.ts.map +1 -0
- package/dist/backend/services/packaging-unit.service.js +197 -0
- package/dist/backend/services/packaging-unit.service.js.map +1 -0
- package/dist/backend/services/primary-asset-url.d.ts +4 -0
- package/dist/backend/services/primary-asset-url.d.ts.map +1 -0
- package/dist/backend/services/primary-asset-url.js +55 -0
- package/dist/backend/services/primary-asset-url.js.map +1 -0
- package/dist/backend/services/product-editor-preferences.service.d.ts +20 -0
- package/dist/backend/services/product-editor-preferences.service.d.ts.map +1 -0
- package/dist/backend/services/product-editor-preferences.service.js +44 -0
- package/dist/backend/services/product-editor-preferences.service.js.map +1 -0
- package/dist/backend/services/product-link.service.d.ts +169 -0
- package/dist/backend/services/product-link.service.d.ts.map +1 -0
- package/dist/backend/services/product-link.service.js +297 -0
- package/dist/backend/services/product-link.service.js.map +1 -0
- package/dist/backend/services/product-overrides.service.d.ts +77 -0
- package/dist/backend/services/product-overrides.service.d.ts.map +1 -0
- package/dist/backend/services/product-overrides.service.js +204 -0
- package/dist/backend/services/product-overrides.service.js.map +1 -0
- package/dist/backend/services/product-scope-context.service.d.ts +36 -0
- package/dist/backend/services/product-scope-context.service.d.ts.map +1 -0
- package/dist/backend/services/product-scope-context.service.js +106 -0
- package/dist/backend/services/product-scope-context.service.js.map +1 -0
- package/dist/backend/services/product-type-validations.d.ts +42 -0
- package/dist/backend/services/product-type-validations.d.ts.map +1 -0
- package/dist/backend/services/product-type-validations.js +55 -0
- package/dist/backend/services/product-type-validations.js.map +1 -0
- package/dist/backend/services/product-value-resolver.service.d.ts +59 -0
- package/dist/backend/services/product-value-resolver.service.d.ts.map +1 -0
- package/dist/backend/services/product-value-resolver.service.js +111 -0
- package/dist/backend/services/product-value-resolver.service.js.map +1 -0
- package/dist/backend/services/system-attribute-scopes.d.ts +18 -0
- package/dist/backend/services/system-attribute-scopes.d.ts.map +1 -0
- package/dist/backend/services/system-attribute-scopes.js +18 -0
- package/dist/backend/services/system-attribute-scopes.js.map +1 -0
- package/dist/backend/services/viewer-organization.d.ts +25 -0
- package/dist/backend/services/viewer-organization.d.ts.map +1 -0
- package/dist/backend/services/viewer-organization.js +31 -0
- package/dist/backend/services/viewer-organization.js.map +1 -0
- package/dist/manifest.d.ts +204 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +686 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts +33 -0
- package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts.map +1 -0
- package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js +118 -0
- package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js.map +1 -0
- package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts +24 -0
- package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts.map +1 -0
- package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js +31 -0
- package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js.map +1 -0
- package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts +32 -0
- package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts.map +1 -0
- package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js +58 -0
- package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js.map +1 -0
- package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts +22 -0
- package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts.map +1 -0
- package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js +66 -0
- package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js.map +1 -0
- package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts +18 -0
- package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts.map +1 -0
- package/dist/migrations/20260429T112543_catalog_product_attachments.js +73 -0
- package/dist/migrations/20260429T112543_catalog_product_attachments.js.map +1 -0
- package/dist/migrations/20260429T123726_catalog_product_links.d.ts +21 -0
- package/dist/migrations/20260429T123726_catalog_product_links.d.ts.map +1 -0
- package/dist/migrations/20260429T123726_catalog_product_links.js +48 -0
- package/dist/migrations/20260429T123726_catalog_product_links.js.map +1 -0
- package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts +23 -0
- package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts.map +1 -0
- package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js +94 -0
- package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js.map +1 -0
- package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts +24 -0
- package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts.map +1 -0
- package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js +31 -0
- package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js.map +1 -0
- package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts +31 -0
- package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts.map +1 -0
- package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js +133 -0
- package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js.map +1 -0
- package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts +16 -0
- package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts.map +1 -0
- package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js +23 -0
- package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js.map +1 -0
- package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts +10 -0
- package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts.map +1 -0
- package/dist/migrations/20260526T124736_catalog_product_status_inactive.js +22 -0
- package/dist/migrations/20260526T124736_catalog_product_status_inactive.js.map +1 -0
- package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts +38 -0
- package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts.map +1 -0
- package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js +98 -0
- package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js.map +1 -0
- package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts +13 -0
- package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts.map +1 -0
- package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js +17 -0
- package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js.map +1 -0
- package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts +16 -0
- package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts.map +1 -0
- package/dist/migrations/20260611T140407_catalog_bulk_operations.js +42 -0
- package/dist/migrations/20260611T140407_catalog_bulk_operations.js.map +1 -0
- package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts +14 -0
- package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts.map +1 -0
- package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js +18 -0
- package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js.map +1 -0
- package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts +11 -0
- package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts.map +1 -0
- package/dist/migrations/20260611T140412_catalog_product_packaging_units.js +29 -0
- package/dist/migrations/20260611T140412_catalog_product_packaging_units.js.map +1 -0
- package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts +23 -0
- package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts.map +1 -0
- package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js +35 -0
- package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js.map +1 -0
- package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts +12 -0
- package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts.map +1 -0
- package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js +16 -0
- package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js.map +1 -0
- package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts +32 -0
- package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts.map +1 -0
- package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js +286 -0
- package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js.map +1 -0
- package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts +36 -0
- package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts.map +1 -0
- package/dist/migrations/20260804T152604_catalog_widen_product_sku.js +63 -0
- package/dist/migrations/20260804T152604_catalog_widen_product_sku.js.map +1 -0
- package/dist/migrations/20260804T160244_catalog_category_activation.d.ts +18 -0
- package/dist/migrations/20260804T160244_catalog_category_activation.d.ts.map +1 -0
- package/dist/migrations/20260804T160244_catalog_category_activation.js +22 -0
- package/dist/migrations/20260804T160244_catalog_category_activation.js.map +1 -0
- package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts +27 -0
- package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts.map +1 -0
- package/dist/migrations/20260912T094557_catalog_sales_channel_products.js +44 -0
- package/dist/migrations/20260912T094557_catalog_sales_channel_products.js.map +1 -0
- package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts +27 -0
- package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts.map +1 -0
- package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js +44 -0
- package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js.map +1 -0
- package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts +36 -0
- package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts.map +1 -0
- package/dist/migrations/20260925T125527_catalog_inventory_columns.js +87 -0
- package/dist/migrations/20260925T125527_catalog_inventory_columns.js.map +1 -0
- package/dist/migrations/index.d.ts +55 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +80 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +96 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/catalog/attachments.md +69 -0
- package/docs/catalog/attribute-sets.md +63 -0
- package/docs/catalog/attributes.md +244 -0
- package/docs/catalog/composite-products.md +115 -0
- package/docs/catalog/gallery-and-labels.md +73 -0
- package/docs/catalog/packaging-units.md +67 -0
- package/docs/catalog/per-channel-per-language-overrides.md +234 -0
- package/docs/catalog/product-links.md +79 -0
- package/docs/catalog.md +207 -0
- package/i18n/en.json +649 -0
- package/i18n/pl.json +649 -0
- package/package.json +109 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Packaging Units
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Packaging Units
|
|
6
|
+
|
|
7
|
+
Named ordering units attached to a product — for example a **Paleta** worth
|
|
8
|
+
480 pieces. They let B2B buyers order in bulk units without typing the exact
|
|
9
|
+
piece count, and carry that context all the way into the order and quote
|
|
10
|
+
request.
|
|
11
|
+
|
|
12
|
+
## For operators
|
|
13
|
+
|
|
14
|
+
Manage packaging units in the **Inventory** section of the product card
|
|
15
|
+
(simple and configurable products only). Each unit has:
|
|
16
|
+
|
|
17
|
+
- a **name** (operator text, e.g. `Paleta`, `Karton`) — unique within the product,
|
|
18
|
+
- a **base quantity** (whole number ≥ 1) — how many base pieces the unit holds,
|
|
19
|
+
- a **default** flag — the unit pre-selected on the storefront,
|
|
20
|
+
- a **position** — the order units appear in.
|
|
21
|
+
|
|
22
|
+
## For buyers
|
|
23
|
+
|
|
24
|
+
On the product page a selector offers the available units plus a *single piece*
|
|
25
|
+
option. Ordering a unit adds `baseQuantity × units` pieces to the cart as one
|
|
26
|
+
line, whose displayed name gets the unit appended — e.g.
|
|
27
|
+
`Łożysko 6205-2RS (Paleta)`. A packaging-unit line and a plain single-piece
|
|
28
|
+
line of the same product stay separate.
|
|
29
|
+
|
|
30
|
+
## How the label travels
|
|
31
|
+
|
|
32
|
+
The unit name and base quantity are **snapshotted** on the line when it is
|
|
33
|
+
created, so later edits to (or deletion of) a product's packaging units never
|
|
34
|
+
change historical carts, orders, or quote requests.
|
|
35
|
+
|
|
36
|
+
- **Cart** — `cart_items` snapshot the unit; the cart serializer composes a
|
|
37
|
+
`displayName` with the suffix.
|
|
38
|
+
- **Order** — `order_items` get a `packaging_unit_snapshot`, and the unit name
|
|
39
|
+
is appended to `product_snapshot.name`, so every order document (detail,
|
|
40
|
+
invoice, CSV, email) shows it.
|
|
41
|
+
- **Quote request** — `quote_request_items` snapshot the unit and append it to
|
|
42
|
+
`product_name`; cart → quote-request conversion carries the context through.
|
|
43
|
+
|
|
44
|
+
## Pricing & availability
|
|
45
|
+
|
|
46
|
+
Pricing reuses the existing engine on the resulting base-piece quantity
|
|
47
|
+
(including price-list quantity tiers) — there is no separate per-unit price.
|
|
48
|
+
Stock availability and per-line limits are evaluated on the resulting piece
|
|
49
|
+
count.
|
|
50
|
+
|
|
51
|
+
## API surface
|
|
52
|
+
|
|
53
|
+
- Admin CRUD: `GET/POST /api/v1/admin/catalog/products/:id/packaging-units`,
|
|
54
|
+
`PATCH/DELETE …/:unitId`, `PATCH …/packaging-units/reorder`
|
|
55
|
+
(gated by `catalog:read` / `catalog:write`).
|
|
56
|
+
- Public: the product detail (`GET /api/v1/catalog/products/:idOrSlug`)
|
|
57
|
+
includes an optional `packagingUnits` array.
|
|
58
|
+
- Cart: `POST /api/v1/cart/items` accepts an optional `packagingUnitId`; the
|
|
59
|
+
resulting line quantity is `baseQuantity × quantity`.
|
|
60
|
+
|
|
61
|
+
## Schema
|
|
62
|
+
|
|
63
|
+
Table `product_packaging_units` (migration 068): `id`, `product_id`
|
|
64
|
+
(FK → `products`, cascade delete), `name`, `base_quantity`
|
|
65
|
+
(`CHECK >= 1`), `position`, `is_default`, timestamps; `UNIQUE (product_id,
|
|
66
|
+
name)`. Additive snapshot columns: `cart_items` (069), `order_items` (070),
|
|
67
|
+
`quote_request_items` (071).
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Per-Channel + Per-Language Overrides
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Per-Channel + Per-Language Overrides
|
|
6
|
+
|
|
7
|
+
The Catalog uses a **four-scope value model** for product attribute
|
|
8
|
+
values. The same attribute key can hold up to four addressable slots
|
|
9
|
+
per product:
|
|
10
|
+
|
|
11
|
+
- `global` — one value across every Sales Channel and every language;
|
|
12
|
+
- `language` — one value per language (e.g. system Name, Description);
|
|
13
|
+
- `channel` — one value per Sales Channel the product is assigned to;
|
|
14
|
+
- `channel+language` — one value per `(channel, language)` pair.
|
|
15
|
+
|
|
16
|
+
Which slots are addressable for a given attribute is governed by two
|
|
17
|
+
flags on the attribute definition, `channelScoped` and `languageScoped`.
|
|
18
|
+
System attributes `name` and `description` are pinned channel-scoped +
|
|
19
|
+
language-scoped via a backend constant
|
|
20
|
+
(`SYSTEM_ATTRIBUTE_SCOPES`). User-defined attributes default to
|
|
21
|
+
global-only and opt in via the attribute editor.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
A B2B catalog routinely needs to overlay channel-specific marketing
|
|
26
|
+
copy (wholesale vs. retail) and language-specific translation on the
|
|
27
|
+
same product. The legacy storage — JSONB on `products.name` /
|
|
28
|
+
`products.description` + a flat `products.attribute_values` JSONB —
|
|
29
|
+
could express only the language dimension. The four-scope model lifts
|
|
30
|
+
that ceiling without rewriting baseline storage; it adds a sibling
|
|
31
|
+
overrides table that holds the channel-aware slots, and a single
|
|
32
|
+
resolver function fuses the two on read.
|
|
33
|
+
|
|
34
|
+
## Storage shape
|
|
35
|
+
|
|
36
|
+
The global baseline keeps living where it has always lived:
|
|
37
|
+
|
|
38
|
+
- `products.name` (`Record<lang, string>` JSONB) — language-scoped.
|
|
39
|
+
- `products.description` (`Record<lang, string>` JSONB) — language-scoped.
|
|
40
|
+
- `products.attribute_values` (`Record<key, value>` JSONB) — global by
|
|
41
|
+
default. When the attribute's `languageScoped` flag is `true`, the
|
|
42
|
+
inner value is itself keyed by language: `Record<key, Record<lang,
|
|
43
|
+
value>>`.
|
|
44
|
+
|
|
45
|
+
Channel-aware slots land in a new table:
|
|
46
|
+
|
|
47
|
+
```sql
|
|
48
|
+
create table "product_value_overrides" (
|
|
49
|
+
"id" uuid primary key default gen_random_uuid(),
|
|
50
|
+
"product_id" uuid not null references "products"("id") on delete cascade,
|
|
51
|
+
"attribute_key" varchar(64) not null,
|
|
52
|
+
"channel_id" uuid not null references "sales_channels"("id") on delete cascade,
|
|
53
|
+
"language_code" varchar(16) null,
|
|
54
|
+
"value" jsonb not null, -- always wrapped { "v": <scalar | object> }
|
|
55
|
+
"created_at" timestamptz not null default now(),
|
|
56
|
+
"updated_at" timestamptz not null default now()
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The (product, attribute, channel, language) tuple is unique under
|
|
61
|
+
**two partial indexes**:
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
create unique index "product_value_overrides_channel_only_uniq"
|
|
65
|
+
on "product_value_overrides" ("product_id", "attribute_key", "channel_id")
|
|
66
|
+
where "language_code" is null;
|
|
67
|
+
|
|
68
|
+
create unique index "product_value_overrides_channel_lang_uniq"
|
|
69
|
+
on "product_value_overrides" ("product_id", "attribute_key", "channel_id", "language_code")
|
|
70
|
+
where "language_code" is not null;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
PostgreSQL treats `NULL` as distinct in a regular UNIQUE constraint, so
|
|
74
|
+
the only correct way to model "(channel, NULL) and (channel, NULL) are
|
|
75
|
+
the same row" is two partial indexes — one for each NULL semantic.
|
|
76
|
+
|
|
77
|
+
The lookup path used by the resolver in tight loops is a plain btree:
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
create index "product_value_overrides_product_attr_idx"
|
|
81
|
+
on "product_value_overrides" ("product_id", "attribute_key");
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A second table — `product_editor_preferences` — stores the per-(admin
|
|
85
|
+
user, product) last-selected `(channelId, languageCode)` so the
|
|
86
|
+
product edit page seeds the switchers from the editor's prior visit.
|
|
87
|
+
It carries no FK to `sales_channels` so a deleted / un-assigned
|
|
88
|
+
channel is silently tolerated on read.
|
|
89
|
+
|
|
90
|
+
## The resolver
|
|
91
|
+
|
|
92
|
+
The single source of truth for "what value should I show?" lives in
|
|
93
|
+
`packages/contracts/src/product-value-resolver.ts`. The same TypeScript
|
|
94
|
+
file is imported by the backend (admin endpoints, storefront read
|
|
95
|
+
path, search indexer) and the admin SPA. By construction, the admin's
|
|
96
|
+
effective-value preview cannot drift from what the storefront renders.
|
|
97
|
+
|
|
98
|
+
The algorithm — `resolveAttribute({ baseline, overrides, scope, ctx })`
|
|
99
|
+
— walks four steps:
|
|
100
|
+
|
|
101
|
+
1. `(channel + language)` slot — when the attribute is channel-scoped
|
|
102
|
+
AND language-scoped AND the context carries both;
|
|
103
|
+
2. `(channel-only)` slot — when channel-scoped AND a channel is in the
|
|
104
|
+
context;
|
|
105
|
+
3. `(global + language)` baseline — for language-scoped attributes,
|
|
106
|
+
pick the requested language out of the JSONB;
|
|
107
|
+
4. `(global)` baseline — single value (or primary-language pick for
|
|
108
|
+
language-scoped attrs).
|
|
109
|
+
|
|
110
|
+
The first non-empty slot wins. Empty string, `null`, empty object,
|
|
111
|
+
empty array are all treated as "absent" and fall through. The function
|
|
112
|
+
returns both the resolved value AND a `source` tag indicating which
|
|
113
|
+
slot produced the answer — the admin UI consumes this to render the
|
|
114
|
+
"channel + language override" / "global baseline" badge per field.
|
|
115
|
+
|
|
116
|
+
### Orphan tolerance
|
|
117
|
+
|
|
118
|
+
An administrator can flip an attribute's `channelScoped` flag back to
|
|
119
|
+
`false` after channel overrides have already been written. The
|
|
120
|
+
resolver simply skips override rows whose attribute is no longer
|
|
121
|
+
channel-scoped, so the storefront falls back to the baseline without
|
|
122
|
+
any data-loss event. A future maintenance pass can drop orphan rows;
|
|
123
|
+
this feature does not require it.
|
|
124
|
+
|
|
125
|
+
## Write path
|
|
126
|
+
|
|
127
|
+
Channel-aware overrides land through one admin endpoint:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
PATCH /api/v1/admin/catalog/products/:id/value-overrides
|
|
131
|
+
{
|
|
132
|
+
"upserts": [
|
|
133
|
+
{ "attributeKey": "name", "channelId": "<vip-uuid>", "languageCode": "en-US", "value": { "v": "VIP wholesale name (EN)" } },
|
|
134
|
+
{ "attributeKey": "description", "channelId": "<vip-uuid>", "languageCode": "pl-PL", "value": { "v": "Wholesale-only Polish copy" } }
|
|
135
|
+
],
|
|
136
|
+
"deletes": [
|
|
137
|
+
{ "attributeKey": "description", "channelId": "<retail-uuid>", "languageCode": "en-US" }
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
All requested operations run in a single `em.transactional` pass.
|
|
143
|
+
The handler validates every entry up front against six rules and
|
|
144
|
+
rolls everything back on the first failure:
|
|
145
|
+
|
|
146
|
+
| 422 code | Triggered when |
|
|
147
|
+
| ----------------------------------- | -------------- |
|
|
148
|
+
| `attribute_unknown` | `attributeKey` is neither a system attr (`name`, `description`) nor a row in `product_attributes`. |
|
|
149
|
+
| `attribute_not_channel_scoped` | the attribute's `channelScoped=false`, so it has no channel slot. |
|
|
150
|
+
| `attribute_missing_language` | the attribute's `languageScoped=true`, but the slot has `languageCode=null`. |
|
|
151
|
+
| `channel_not_assigned_to_product` | `channelId` is not in `sales_channel_products` for the product. |
|
|
152
|
+
| `language_not_in_channel` | `languageCode` is non-null and not in the channel's `SalesChannel.languages` array. |
|
|
153
|
+
| `value_invalid` | the wrapped `value.v` does not match the attribute's `valueType`. |
|
|
154
|
+
|
|
155
|
+
The baseline write path (Name / Description per language; global
|
|
156
|
+
`attribute_values`) is unchanged — editors still use the existing
|
|
157
|
+
locale-side-by-side inputs on the Details tab. Channel overrides are
|
|
158
|
+
additive.
|
|
159
|
+
|
|
160
|
+
## Read paths
|
|
161
|
+
|
|
162
|
+
Three consumers go through the resolver:
|
|
163
|
+
|
|
164
|
+
- **Admin endpoint** — `GET /api/v1/admin/catalog/products/:id?channelId=&languageCode=&includeOverridesMap=true`
|
|
165
|
+
returns the baseline product PLUS, when context params are passed, a
|
|
166
|
+
`resolved` block with `name`, `description`, `attributeValues`,
|
|
167
|
+
`sources`. When `includeOverridesMap=true`, the full override list
|
|
168
|
+
is attached for client-side switcher previews.
|
|
169
|
+
- **Storefront public read** — pulls the channel from the
|
|
170
|
+
`x-sales-channel` header (the existing convention) and
|
|
171
|
+
the language from `Accept-Language`. The override layer is invisible
|
|
172
|
+
to the public client.
|
|
173
|
+
- **Search indexer (Meilisearch)** — builds one document per
|
|
174
|
+
`(product, channel)` pair. Each document's `name` and `description`
|
|
175
|
+
fields are run through the resolver with `channel.defaultLanguage`
|
|
176
|
+
as the active language, so per-channel overrides flow into
|
|
177
|
+
storefront search ranking.
|
|
178
|
+
|
|
179
|
+
## Admin UI
|
|
180
|
+
|
|
181
|
+
The product edit page's **Details** tab gains a
|
|
182
|
+
`<ProductScopeEditor>` panel above the existing locale inputs. It:
|
|
183
|
+
|
|
184
|
+
1. Loads `GET /scope-context` (channels assigned to the product + each
|
|
185
|
+
channel's languages + the platform primary admin language + the
|
|
186
|
+
editor's remembered preference) and `GET /value-overrides` in
|
|
187
|
+
parallel.
|
|
188
|
+
2. Renders a Sales Channel switcher (Global + each assigned channel)
|
|
189
|
+
and a Language switcher narrowed to the active channel's languages
|
|
190
|
+
(or the union of all channel languages when Global).
|
|
191
|
+
3. Shows the resolved Name + Description for the active context with a
|
|
192
|
+
source badge per field — `channel + language override` / `channel
|
|
193
|
+
override` / `global baseline (language)` / `global baseline
|
|
194
|
+
(fallback)` / `no value`.
|
|
195
|
+
4. Offers "Add override" / "Edit override" / "Reset to Global"
|
|
196
|
+
affordances per field. Edits go through `PATCH /value-overrides`
|
|
197
|
+
with optimistic refresh.
|
|
198
|
+
5. Persists the editor's `(channel, language)` selection back via
|
|
199
|
+
`PUT /editor-preference` (debounced, best-effort) so the next visit
|
|
200
|
+
seeds the same context.
|
|
201
|
+
|
|
202
|
+
Edit affordances are gated on a specific channel being selected.
|
|
203
|
+
Under Global / no channel, the panel is read-only and a help string
|
|
204
|
+
explains that overrides only apply per-channel.
|
|
205
|
+
|
|
206
|
+
## Migration
|
|
207
|
+
|
|
208
|
+
One migration `043_product_value_overrides_init.ts` ships everything:
|
|
209
|
+
|
|
210
|
+
- adds `channel_scoped` + `language_scoped` boolean columns to
|
|
211
|
+
`product_attributes` (default `false`);
|
|
212
|
+
- creates `product_value_overrides` with the two partial UNIQUE
|
|
213
|
+
indexes and the `(product_id, attribute_key)` btree;
|
|
214
|
+
- creates `product_editor_preferences` (composite PK
|
|
215
|
+
`(admin_user_id, product_id)`).
|
|
216
|
+
|
|
217
|
+
No baseline data migration. No data backfill. The `down()` is the
|
|
218
|
+
exact inverse — drop the two tables and the two columns.
|
|
219
|
+
|
|
220
|
+
## What is not in scope
|
|
221
|
+
|
|
222
|
+
- **User-defined attribute overrides in the admin UI** — the panel
|
|
223
|
+
surfaces only the system Name + Description today. User attribute
|
|
224
|
+
overrides are accepted by the PATCH endpoint and resolved on read,
|
|
225
|
+
but the admin SPA does not yet expose a write affordance for them.
|
|
226
|
+
- **Storefront public read shape change** — `products.name` and
|
|
227
|
+
`products.description` still ship as `Record<lang, string>` JSONB
|
|
228
|
+
on the public catalog endpoint. Switching to a resolved scalar is
|
|
229
|
+
a breaking change for storefront consumers and is scoped as a
|
|
230
|
+
follow-up.
|
|
231
|
+
- **Channel-aware reindex enqueue** — the PATCH endpoint does not
|
|
232
|
+
yet enqueue a Meilisearch reindex job per touched channel; the
|
|
233
|
+
next per-product upsert (driven by any catalog mutation) picks it
|
|
234
|
+
up. A dedicated reindex on override write is a small follow-up.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Product Links
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Product Links
|
|
6
|
+
|
|
7
|
+
Directed links between Products that drive cross-merchandising on the
|
|
8
|
+
PDP and cart. Three kinds:
|
|
9
|
+
|
|
10
|
+
- **Related** — "Related products" section on the PDP
|
|
11
|
+
- **Up-sell** — "You might also like" on the PDP
|
|
12
|
+
- **Cross-sell** — "You may also need" on the cart page
|
|
13
|
+
|
|
14
|
+
## DB-level guarantees
|
|
15
|
+
|
|
16
|
+
Migration 022 ships three constraints that make incorrect link data
|
|
17
|
+
impossible:
|
|
18
|
+
|
|
19
|
+
- `UNIQUE (source_product_id, target_product_id, kind)` — same pair can
|
|
20
|
+
appear once per kind, never twice for the same kind
|
|
21
|
+
- `CHECK source_product_id <> target_product_id` — defence-in-depth
|
|
22
|
+
against self-links beyond the API-layer guard
|
|
23
|
+
- `CHECK kind IN ('related','up_sell','cross_sell')`
|
|
24
|
+
|
|
25
|
+
Both FK columns cascade on product delete: link rows have no value when
|
|
26
|
+
either side is gone.
|
|
27
|
+
|
|
28
|
+
## Public surface
|
|
29
|
+
|
|
30
|
+
| Verb + Path | Audience | Purpose |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `GET /api/v1/admin/catalog/products/:id/links?kind=...` | admin | List links optionally filtered by kind |
|
|
33
|
+
| `POST /api/v1/admin/catalog/products/:id/links` | admin | Bulk create — all-or-nothing transaction |
|
|
34
|
+
| `DELETE /api/v1/admin/catalog/products/:id/links/:linkId` | admin | Remove a single link |
|
|
35
|
+
| `PUT /api/v1/admin/catalog/products/:id/links/:kind/order` | admin | Reorder by id list |
|
|
36
|
+
| `GET /api/v1/catalog/products/:idOrSlug/links?kind=...` | storefront | Storefront read; archived/channel-restricted targets filtered server-side |
|
|
37
|
+
|
|
38
|
+
## Bulk create semantics
|
|
39
|
+
|
|
40
|
+
`POST .../links` accepts a `links[]` array where each entry has
|
|
41
|
+
`{targetProductId, kind, position?}`. The service validates the whole
|
|
42
|
+
batch before inserting any row:
|
|
43
|
+
|
|
44
|
+
1. Source product exists and is not archived (404 otherwise)
|
|
45
|
+
2. No entry has `targetProductId === sourceProductId` (400)
|
|
46
|
+
3. All targets exist (404 with the offending id)
|
|
47
|
+
4. No `(source, target, kind)` already exists (409)
|
|
48
|
+
|
|
49
|
+
If any check fails, **no rows are inserted**. This matches the admin UX
|
|
50
|
+
of submitting a curated batch — partial inserts would surprise an
|
|
51
|
+
admin who's still finishing their picks.
|
|
52
|
+
|
|
53
|
+
## Errors
|
|
54
|
+
|
|
55
|
+
| Code | Status | When |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `SELF_LINK_NOT_ALLOWED` | 400 | source = target |
|
|
58
|
+
| `LINK_ALREADY_EXISTS` | 409 | Duplicate `(source, target, kind)` |
|
|
59
|
+
| `TARGET_PRODUCT_NOT_FOUND` | 404 | Any target id missing |
|
|
60
|
+
| `PRODUCT_LINK_NOT_FOUND` | 404 | `:linkId` missing |
|
|
61
|
+
|
|
62
|
+
## Storefront integration
|
|
63
|
+
|
|
64
|
+
`productDetail.links` carries pre-grouped arrays sliced to default page
|
|
65
|
+
sizes: `related[8]`, `upSell[4]`, `crossSell[4]`. Each entry is a
|
|
66
|
+
storefront-shape link
|
|
67
|
+
summary `{id, kind, position, product{id, sku, slug, name,
|
|
68
|
+
primaryAssetUrl, price}}` so the listing card renders without a
|
|
69
|
+
follow-up fetch.
|
|
70
|
+
|
|
71
|
+
The `<ProductLinksSections>` PDP component renders Related and Up-sell
|
|
72
|
+
under their own headings. The `<CrossSellSection>` cart component
|
|
73
|
+
fetches cross-sell links per cart item and dedupes by product id so the
|
|
74
|
+
same target shows up once even when reached via multiple cart entries.
|
|
75
|
+
|
|
76
|
+
## Storage
|
|
77
|
+
|
|
78
|
+
Single `product_links` table with the constraints above plus indexes on
|
|
79
|
+
`(source_product_id, kind)` and `(target_product_id)`.
|
package/docs/catalog.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: catalog
|
|
3
|
+
sidebar_label: Catalog
|
|
4
|
+
description: Products, variants, categories, attributes, sales channels
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# `catalog`
|
|
8
|
+
|
|
9
|
+
The product catalog: Products, ProductVariants, Categories,
|
|
10
|
+
ProductAttributes, and SalesChannels. Owns all read paths the storefront
|
|
11
|
+
depends on and the admin-side authoring surface.
|
|
12
|
+
|
|
13
|
+
## Public surface
|
|
14
|
+
|
|
15
|
+
Admin routes are gated by `catalog:read` (list / get) /
|
|
16
|
+
`catalog:write` (mutations).
|
|
17
|
+
|
|
18
|
+
| Verb + Path | Audience | Purpose |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `GET /api/v1/catalog/products` | storefront / API key | List/search/filter products in the active Sales Channel |
|
|
21
|
+
| `GET /api/v1/catalog/products/:idOrSlug` | storefront | Product detail (price omitted on non-public Sales Channels) |
|
|
22
|
+
| `GET /api/v1/catalog/categories` | storefront | Nested category tree |
|
|
23
|
+
| `GET /api/v1/catalog/filters` | storefront | Filterable attributes for the active Sales Channel |
|
|
24
|
+
| `GET /api/v1/catalog/sitemap.xml` | crawlers | SEO sitemap |
|
|
25
|
+
| `GET /api/v1/admin/catalog/products?includeArchived` | admin | Admin product list (includes drafts; archived rows opt-in) |
|
|
26
|
+
| `GET /api/v1/admin/catalog/products/:id` | admin | Product detail |
|
|
27
|
+
| `POST /api/v1/admin/catalog/products` | admin | Create product (`type` immutable post-create; `sku` is editable) |
|
|
28
|
+
| `PATCH /api/v1/admin/catalog/products/:id` | admin | Update (incl. `sku`); writes an audit row with stateBefore / stateAfter; refuses with `409 sku_in_use` if the new SKU already belongs to another product |
|
|
29
|
+
| `DELETE /api/v1/admin/catalog/products/:id` | admin | Archive (soft) |
|
|
30
|
+
| `GET /api/v1/admin/catalog/attributes` | admin | List attributes |
|
|
31
|
+
| `GET /api/v1/admin/catalog/attributes/by-flag?flag=isPromoRule\|isComparable\|...` | admin | Picker payload — every attribute carrying the requested flag |
|
|
32
|
+
| `GET /api/v1/admin/catalog/attributes/:idOrKey` | admin | Single attribute read |
|
|
33
|
+
| `POST /api/v1/admin/catalog/attributes` | admin | Create attribute (accepts the new flags + inline `options[]` for select-style types) |
|
|
34
|
+
| `PATCH /api/v1/admin/catalog/attributes/:key` | admin | Hot-toggle `isFilterable` / `isSearchable` / `isVariantAxis` / `isPromoRule` / `isComparable` / `isVisibleOnProductPage` / `isRequired` / `filterPosition` (re-emits `attribute.updated.v1`) |
|
|
35
|
+
| `DELETE /api/v1/admin/catalog/attributes/:idOrKey` | admin | Delete; refused with `409 attribute_in_use_by_set` while any Attribute Set still references it |
|
|
36
|
+
| `GET /api/v1/admin/catalog/attributes/:idOrKey/options` | admin | List option-list rows for select/enum/multiselect attributes |
|
|
37
|
+
| `POST /api/v1/admin/catalog/attributes/:idOrKey/options` | admin | Append an option |
|
|
38
|
+
| `PATCH /api/v1/admin/catalog/attribute-options/:optionId` | admin | Patch label / labelDefault / isDefault / sortOrder (option `value` is immutable) |
|
|
39
|
+
| `DELETE /api/v1/admin/catalog/attribute-options/:optionId` | admin | Remove; refused with `409 option_in_use` while any product still carries the value |
|
|
40
|
+
| `POST /api/v1/admin/catalog/attribute-set-preview` | admin | Preview which Set's attributes will be edited / hidden when an operator switches a product's Attribute Set |
|
|
41
|
+
| `GET /api/v1/admin/catalog/categories` | admin | Flat list, the UI folds into a tree |
|
|
42
|
+
| `POST /api/v1/admin/catalog/categories` | admin | Create (parent must exist) |
|
|
43
|
+
| `PATCH /api/v1/admin/catalog/categories/:id` | admin | Update; reparenting walks the new parent's chain to refuse cycles (409) |
|
|
44
|
+
| `DELETE /api/v1/admin/catalog/categories/:id` | admin | Soft-delete; rejects with 409 if any active child still references the row |
|
|
45
|
+
| `PUT /api/v1/catalog/products/by-sku/:sku` | API key | Idempotent upsert (PIM sync) |
|
|
46
|
+
|
|
47
|
+
## Entities
|
|
48
|
+
|
|
49
|
+
`Product`, `ProductVariant`, `Category`, `ProductAttribute`,
|
|
50
|
+
`SalesChannel`, plus the M:N bridges
|
|
51
|
+
`product_categories`, `sales_channel_products`, `product_assets`.
|
|
52
|
+
|
|
53
|
+
## Events emitted
|
|
54
|
+
|
|
55
|
+
`product.created.v1`, `product.updated.v1`, `product.archived.v1`,
|
|
56
|
+
`attribute.updated.v1`. Picked up by the search indexer and bridged to
|
|
57
|
+
webhook subscribers.
|
|
58
|
+
|
|
59
|
+
## Extension points
|
|
60
|
+
|
|
61
|
+
- **Per-Sales-Channel pricing** — query service receives a SalesChannel
|
|
62
|
+
context; new gating (e.g. customer-segment-specific catalogs) is added by
|
|
63
|
+
composing into `catalog-query.service.ts`.
|
|
64
|
+
- **Slug uniqueness** — the slug is unique across all Sales Channels by
|
|
65
|
+
default; override the slugifier in `catalog-admin.service.ts` if locale
|
|
66
|
+
collisions become a concern.
|
|
67
|
+
|
|
68
|
+
## Product structure and composition surfaces
|
|
69
|
+
|
|
70
|
+
The catalog grew several capability surfaces, each with its own page:
|
|
71
|
+
|
|
72
|
+
- [Attribute Sets](./catalog/attribute-sets.md) — reusable attribute
|
|
73
|
+
schemas pinned to Products, with a system Default
|
|
74
|
+
- [Product Gallery](./catalog/gallery-and-labels.md) — image / video
|
|
75
|
+
gallery with Base / Small / Thumbnail label invariants enforced at
|
|
76
|
+
the database level
|
|
77
|
+
- [Attachments](./catalog/attachments.md) — downloadable files
|
|
78
|
+
(certificates, tech specs, ...) with a typed dictionary
|
|
79
|
+
- [Product Links](./catalog/product-links.md) — Related, Up-sell,
|
|
80
|
+
Cross-sell pairings driving cross-merchandising on the PDP and cart
|
|
81
|
+
- [Composite Products](./catalog/composite-products.md) — `grouped`
|
|
82
|
+
(fixed children), `bundle` (configurable slots), `virtual` (digital
|
|
83
|
+
delivery)
|
|
84
|
+
|
|
85
|
+
Five product types are now supported: `simple`, `configurable`,
|
|
86
|
+
`grouped`, `bundle`, `virtual`. `simple` and `configurable` are the
|
|
87
|
+
original pair; the other three were added later.
|
|
88
|
+
|
|
89
|
+
## Attribute extensions on the Catalog read paths
|
|
90
|
+
|
|
91
|
+
The Attributes work added the operational surface the storefront
|
|
92
|
+
needs to render rich product information and the search / promotions
|
|
93
|
+
modules need to resolve customer queries. The dedicated
|
|
94
|
+
[Attributes](./catalog/attributes.md) page covers the attribute
|
|
95
|
+
authoring surface in full — this section only summarises what changed
|
|
96
|
+
on the Catalog read paths.
|
|
97
|
+
|
|
98
|
+
### New attribute flags
|
|
99
|
+
|
|
100
|
+
`ProductAttribute` gains four behavioural flags + a numeric position +
|
|
101
|
+
a per-locale label fallback:
|
|
102
|
+
|
|
103
|
+
- `isPromoRule` (boolean) — picker eligibility for the Promotion Rule
|
|
104
|
+
editor's `attribute` criterion variant
|
|
105
|
+
- `isVisibleOnProductPage` (boolean) — surface the attribute on the
|
|
106
|
+
storefront PDP "Parametry produktu" tab when the product carries a
|
|
107
|
+
value
|
|
108
|
+
- `isRequired` (boolean) — enforced at product save time when the
|
|
109
|
+
attribute is part of the product's Attribute Set
|
|
110
|
+
- `filterPosition` (number) — sort key for the storefront filter sidebar
|
|
111
|
+
(lower comes first; ties broken by label)
|
|
112
|
+
- `labelDefault` (string) — fallback used when the active locale has no
|
|
113
|
+
matching key in the per-locale `label` JSONB
|
|
114
|
+
|
|
115
|
+
### Option lists
|
|
116
|
+
|
|
117
|
+
Select-style attribute types (`select`, `enum`, `multiselect`) carry an
|
|
118
|
+
ordered option list — each row keyed by `(definition, value)` with
|
|
119
|
+
per-locale label + fallback + sort order + default flag. The legacy
|
|
120
|
+
`enum_values: string[]` JSONB column on `product_attributes` was
|
|
121
|
+
decommissioned by migration 032 (into the catalog-owned
|
|
122
|
+
`attribute_options` table), and migration 102 moved the
|
|
123
|
+
rows into the generic `custom_field_options` table. Existing readers
|
|
124
|
+
project the option list back into the legacy form for backward
|
|
125
|
+
compatibility at the API boundary.
|
|
126
|
+
|
|
127
|
+
### Editable SKU
|
|
128
|
+
|
|
129
|
+
Product `sku` is mutable. The internal canonical reference for every
|
|
130
|
+
cross-module link (assets, links, RFQ items, ...) is the `Product.id`
|
|
131
|
+
UUID, which never changes. Updating the SKU writes an audit row and
|
|
132
|
+
refuses with `409 sku_in_use` if the new value already belongs to
|
|
133
|
+
another product.
|
|
134
|
+
|
|
135
|
+
### Attribute Set swap
|
|
136
|
+
|
|
137
|
+
When an operator assigns a different Attribute Set to a Product, the
|
|
138
|
+
admin form re-renders to show only the new Set's attributes. Values
|
|
139
|
+
for attributes outside the new Set stay in the JSONB column server-side
|
|
140
|
+
— switching back surfaces them again. The
|
|
141
|
+
`attribute-set-preview` endpoint lets the editor warn the operator
|
|
142
|
+
which fields will be hidden vs. retained before they confirm.
|
|
143
|
+
|
|
144
|
+
### Cross-module read surface
|
|
145
|
+
|
|
146
|
+
Two methods on `CatalogQueryService` cross module boundaries (the
|
|
147
|
+
documented service ports):
|
|
148
|
+
|
|
149
|
+
- `comparableAttributeKeys(): string[]` — Compare
|
|
150
|
+
- `promoRuleAttributeKeys(): string[]` + `getAttributeWithOptions(key)`
|
|
151
|
+
— Promotions
|
|
152
|
+
- `buildVisibleAttributesProjection()` — internal, used by the PDP
|
|
153
|
+
detail response to assemble the `visibleAttributes[]` payload
|
|
154
|
+
|
|
155
|
+
## Attributes as Custom Field extensions
|
|
156
|
+
|
|
157
|
+
The attribute definition store has converged onto the generic Custom
|
|
158
|
+
Fields layer that the `custom_fields` module owns, adapter-shaped rather
|
|
159
|
+
than rewritten. Nothing changed on the
|
|
160
|
+
HTTP surface — every endpoint above keeps its shape — but the storage
|
|
161
|
+
and ownership model is different:
|
|
162
|
+
|
|
163
|
+
- **A product attribute is a catalog extension of a product-host Custom
|
|
164
|
+
Field definition.** The generic identity (`key`, per-locale `label` +
|
|
165
|
+
`labelDefault`, `valueType`, `required`) lives on a
|
|
166
|
+
`custom_field_definitions` row with `entity_type = 'product'`. The
|
|
167
|
+
`product_attributes` table remains, rebuilt as a thin 1:1 extension
|
|
168
|
+
row (`custom_field_definition_id` UNIQUE FK) carrying only the
|
|
169
|
+
catalog behaviour flags (`isSearchable`, `isFilterable`,
|
|
170
|
+
`isVariantAxis`, `displayAsSlider`, `isComparable`,
|
|
171
|
+
`quickSearchable`, `isPromoRule`, `filterPosition`,
|
|
172
|
+
`isVisibleOnProductPage`, `channelScoped`, `languageScoped`,
|
|
173
|
+
`massEditable`) plus two presentation refinements (`selectDisplay`,
|
|
174
|
+
`numericKind`) that keep the legacy `enum`/`select` and
|
|
175
|
+
`number`/`price` distinctions lossless. **Flags stay catalog-owned**
|
|
176
|
+
— the generic core never interprets them.
|
|
177
|
+
- **Options live in `custom_field_options`.** The catalog-owned
|
|
178
|
+
`attribute_options` table is gone; option lists are ordinary Custom
|
|
179
|
+
Field option rows on the product-host definition.
|
|
180
|
+
- **Single write surface: `/catalog/attributes`.** Attribute and
|
|
181
|
+
option mutations are catalog Commands that create/update/delete the
|
|
182
|
+
definition and the extension together in one transaction (one audit
|
|
183
|
+
row), using the transactional apply seam exported by
|
|
184
|
+
`custom_fields`. The generic Custom Fields admin surface lists
|
|
185
|
+
product definitions read-only and refuses mutations with
|
|
186
|
+
`409 host_managed`.
|
|
187
|
+
- **Migration `102_attributes_on_custom_fields.ts`** performed the
|
|
188
|
+
one-time convergence in a single transaction: backfilled one
|
|
189
|
+
definition per legacy attribute (key, labels, mapped value type,
|
|
190
|
+
required, deterministic sort order), moved `attribute_options` rows
|
|
191
|
+
into `custom_field_options`, re-keyed `attribute_set_attributes` to
|
|
192
|
+
definition ids, added `custom_field_definition_id` /
|
|
193
|
+
`select_display` / `numeric_kind` to `product_attributes`, dropped
|
|
194
|
+
the duplicated columns (`key`, `label`, `label_default`,
|
|
195
|
+
`value_type`, `is_required`), and dropped `attribute_options`. The
|
|
196
|
+
migration is reversible (`down()` restores the legacy shape) and
|
|
197
|
+
aborts loudly on a reserved-key collision.
|
|
198
|
+
- **Attribute values did not move** — `products.attribute_values`,
|
|
199
|
+
`product_variants.variant_attribute_values`, and
|
|
200
|
+
`product_value_overrides` keep their shape and catalog ownership
|
|
201
|
+
(the host owns its data).
|
|
202
|
+
|
|
203
|
+
Internal consumers (search, quick order, comparisons, bulk edit, the
|
|
204
|
+
promotions port, the scope editor) read attributes through the
|
|
205
|
+
catalog-exported `CatalogAttributeReadService`, which composes the
|
|
206
|
+
definition and the extension into the legacy-shaped
|
|
207
|
+
`CatalogAttributeView`.
|