@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.
Files changed (443) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/admin/components/PackagingUnitsEditor.d.ts +12 -0
  4. package/dist/admin/components/PackagingUnitsEditor.d.ts.map +1 -0
  5. package/dist/admin/components/PackagingUnitsEditor.js +99 -0
  6. package/dist/admin/components/PackagingUnitsEditor.js.map +1 -0
  7. package/dist/admin/components/ProductAttributesTab.d.ts +13 -0
  8. package/dist/admin/components/ProductAttributesTab.d.ts.map +1 -0
  9. package/dist/admin/components/ProductAttributesTab.js +140 -0
  10. package/dist/admin/components/ProductAttributesTab.js.map +1 -0
  11. package/dist/admin/components/ProductInventoryTab.d.ts +14 -0
  12. package/dist/admin/components/ProductInventoryTab.d.ts.map +1 -0
  13. package/dist/admin/components/ProductInventoryTab.js +284 -0
  14. package/dist/admin/components/ProductInventoryTab.js.map +1 -0
  15. package/dist/admin/components/ProductScopeEditor.d.ts +94 -0
  16. package/dist/admin/components/ProductScopeEditor.d.ts.map +1 -0
  17. package/dist/admin/components/ProductScopeEditor.js +395 -0
  18. package/dist/admin/components/ProductScopeEditor.js.map +1 -0
  19. package/dist/admin/components/ProductsBulkEditDialog.d.ts +11 -0
  20. package/dist/admin/components/ProductsBulkEditDialog.d.ts.map +1 -0
  21. package/dist/admin/components/ProductsBulkEditDialog.js +304 -0
  22. package/dist/admin/components/ProductsBulkEditDialog.js.map +1 -0
  23. package/dist/admin/index.d.ts +45 -0
  24. package/dist/admin/index.d.ts.map +1 -0
  25. package/dist/admin/index.js +184 -0
  26. package/dist/admin/index.js.map +1 -0
  27. package/dist/admin/lib/resolve-product-selection.d.ts +21 -0
  28. package/dist/admin/lib/resolve-product-selection.d.ts.map +1 -0
  29. package/dist/admin/lib/resolve-product-selection.js +25 -0
  30. package/dist/admin/lib/resolve-product-selection.js.map +1 -0
  31. package/dist/admin/pages/AttachmentTypesPage.d.ts +18 -0
  32. package/dist/admin/pages/AttachmentTypesPage.d.ts.map +1 -0
  33. package/dist/admin/pages/AttachmentTypesPage.js +92 -0
  34. package/dist/admin/pages/AttachmentTypesPage.js.map +1 -0
  35. package/dist/admin/pages/AttributeSetsPage.d.ts +21 -0
  36. package/dist/admin/pages/AttributeSetsPage.d.ts.map +1 -0
  37. package/dist/admin/pages/AttributeSetsPage.js +158 -0
  38. package/dist/admin/pages/AttributeSetsPage.js.map +1 -0
  39. package/dist/admin/pages/AttributesManager.d.ts +9 -0
  40. package/dist/admin/pages/AttributesManager.d.ts.map +1 -0
  41. package/dist/admin/pages/AttributesManager.js +305 -0
  42. package/dist/admin/pages/AttributesManager.js.map +1 -0
  43. package/dist/admin/pages/BulkOperationDetailPage.d.ts +9 -0
  44. package/dist/admin/pages/BulkOperationDetailPage.d.ts.map +1 -0
  45. package/dist/admin/pages/BulkOperationDetailPage.js +187 -0
  46. package/dist/admin/pages/BulkOperationDetailPage.js.map +1 -0
  47. package/dist/admin/pages/BulkOperationsPage.d.ts +9 -0
  48. package/dist/admin/pages/BulkOperationsPage.d.ts.map +1 -0
  49. package/dist/admin/pages/BulkOperationsPage.js +98 -0
  50. package/dist/admin/pages/BulkOperationsPage.js.map +1 -0
  51. package/dist/admin/pages/CategoriesTree.d.ts +9 -0
  52. package/dist/admin/pages/CategoriesTree.d.ts.map +1 -0
  53. package/dist/admin/pages/CategoriesTree.js +176 -0
  54. package/dist/admin/pages/CategoriesTree.js.map +1 -0
  55. package/dist/admin/pages/ProductEditor.d.ts +13 -0
  56. package/dist/admin/pages/ProductEditor.d.ts.map +1 -0
  57. package/dist/admin/pages/ProductEditor.js +1050 -0
  58. package/dist/admin/pages/ProductEditor.js.map +1 -0
  59. package/dist/admin/pages/ProductsList.d.ts +9 -0
  60. package/dist/admin/pages/ProductsList.d.ts.map +1 -0
  61. package/dist/admin/pages/ProductsList.js +399 -0
  62. package/dist/admin/pages/ProductsList.js.map +1 -0
  63. package/dist/backend/commands/attribute-commands.d.ts +111 -0
  64. package/dist/backend/commands/attribute-commands.d.ts.map +1 -0
  65. package/dist/backend/commands/attribute-commands.js +482 -0
  66. package/dist/backend/commands/attribute-commands.js.map +1 -0
  67. package/dist/backend/demo/reset.d.ts +23 -0
  68. package/dist/backend/demo/reset.d.ts.map +1 -0
  69. package/dist/backend/demo/reset.js +41 -0
  70. package/dist/backend/demo/reset.js.map +1 -0
  71. package/dist/backend/demo/rows.d.ts +103 -0
  72. package/dist/backend/demo/rows.d.ts.map +1 -0
  73. package/dist/backend/demo/rows.js +162 -0
  74. package/dist/backend/demo/rows.js.map +1 -0
  75. package/dist/backend/demo/seed.d.ts +35 -0
  76. package/dist/backend/demo/seed.d.ts.map +1 -0
  77. package/dist/backend/demo/seed.js +177 -0
  78. package/dist/backend/demo/seed.js.map +1 -0
  79. package/dist/backend/entities/attachment-type.entity.d.ts +16 -0
  80. package/dist/backend/entities/attachment-type.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/attachment-type.entity.js +57 -0
  82. package/dist/backend/entities/attachment-type.entity.js.map +1 -0
  83. package/dist/backend/entities/attribute-set-attribute.entity.d.ts +26 -0
  84. package/dist/backend/entities/attribute-set-attribute.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/attribute-set-attribute.entity.js +55 -0
  86. package/dist/backend/entities/attribute-set-attribute.entity.js.map +1 -0
  87. package/dist/backend/entities/attribute-set.entity.d.ts +22 -0
  88. package/dist/backend/entities/attribute-set.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/attribute-set.entity.js +67 -0
  90. package/dist/backend/entities/attribute-set.entity.js.map +1 -0
  91. package/dist/backend/entities/bulk-operation.entity.d.ts +78 -0
  92. package/dist/backend/entities/bulk-operation.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/bulk-operation.entity.js +138 -0
  94. package/dist/backend/entities/bulk-operation.entity.js.map +1 -0
  95. package/dist/backend/entities/bundle-slot-option.entity.d.ts +23 -0
  96. package/dist/backend/entities/bundle-slot-option.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/bundle-slot-option.entity.js +68 -0
  98. package/dist/backend/entities/bundle-slot-option.entity.js.map +1 -0
  99. package/dist/backend/entities/bundle-slot.entity.d.ts +25 -0
  100. package/dist/backend/entities/bundle-slot.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/bundle-slot.entity.js +74 -0
  102. package/dist/backend/entities/bundle-slot.entity.js.map +1 -0
  103. package/dist/backend/entities/category.entity.d.ts +26 -0
  104. package/dist/backend/entities/category.entity.d.ts.map +1 -0
  105. package/dist/backend/entities/category.entity.js +119 -0
  106. package/dist/backend/entities/category.entity.js.map +1 -0
  107. package/dist/backend/entities/gallery-item-label.entity.d.ts +22 -0
  108. package/dist/backend/entities/gallery-item-label.entity.d.ts.map +1 -0
  109. package/dist/backend/entities/gallery-item-label.entity.js +50 -0
  110. package/dist/backend/entities/gallery-item-label.entity.js.map +1 -0
  111. package/dist/backend/entities/gallery-item.entity.d.ts +17 -0
  112. package/dist/backend/entities/gallery-item.entity.d.ts.map +1 -0
  113. package/dist/backend/entities/gallery-item.entity.js +58 -0
  114. package/dist/backend/entities/gallery-item.entity.js.map +1 -0
  115. package/dist/backend/entities/grouped-item.entity.d.ts +23 -0
  116. package/dist/backend/entities/grouped-item.entity.d.ts.map +1 -0
  117. package/dist/backend/entities/grouped-item.entity.js +68 -0
  118. package/dist/backend/entities/grouped-item.entity.js.map +1 -0
  119. package/dist/backend/entities/product-attachment.entity.d.ts +23 -0
  120. package/dist/backend/entities/product-attachment.entity.d.ts.map +1 -0
  121. package/dist/backend/entities/product-attachment.entity.js +77 -0
  122. package/dist/backend/entities/product-attachment.entity.js.map +1 -0
  123. package/dist/backend/entities/product-attribute.entity.d.ts +72 -0
  124. package/dist/backend/entities/product-attribute.entity.d.ts.map +1 -0
  125. package/dist/backend/entities/product-attribute.entity.js +161 -0
  126. package/dist/backend/entities/product-attribute.entity.js.map +1 -0
  127. package/dist/backend/entities/product-editor-preference.entity.d.ts +28 -0
  128. package/dist/backend/entities/product-editor-preference.entity.d.ts.map +1 -0
  129. package/dist/backend/entities/product-editor-preference.entity.js +63 -0
  130. package/dist/backend/entities/product-editor-preference.entity.js.map +1 -0
  131. package/dist/backend/entities/product-link.entity.d.ts +25 -0
  132. package/dist/backend/entities/product-link.entity.d.ts.map +1 -0
  133. package/dist/backend/entities/product-link.entity.js +70 -0
  134. package/dist/backend/entities/product-link.entity.js.map +1 -0
  135. package/dist/backend/entities/product-packaging-unit.entity.d.ts +26 -0
  136. package/dist/backend/entities/product-packaging-unit.entity.d.ts.map +1 -0
  137. package/dist/backend/entities/product-packaging-unit.entity.js +76 -0
  138. package/dist/backend/entities/product-packaging-unit.entity.js.map +1 -0
  139. package/dist/backend/entities/product-value-override.entity.d.ts +43 -0
  140. package/dist/backend/entities/product-value-override.entity.d.ts.map +1 -0
  141. package/dist/backend/entities/product-value-override.entity.js +90 -0
  142. package/dist/backend/entities/product-value-override.entity.js.map +1 -0
  143. package/dist/backend/entities/product-variant.entity.d.ts +18 -0
  144. package/dist/backend/entities/product-variant.entity.d.ts.map +1 -0
  145. package/dist/backend/entities/product-variant.entity.js +68 -0
  146. package/dist/backend/entities/product-variant.entity.js.map +1 -0
  147. package/dist/backend/entities/product.entity.d.ts +108 -0
  148. package/dist/backend/entities/product.entity.d.ts.map +1 -0
  149. package/dist/backend/entities/product.entity.js +230 -0
  150. package/dist/backend/entities/product.entity.js.map +1 -0
  151. package/dist/backend/index.d.ts +262 -0
  152. package/dist/backend/index.d.ts.map +1 -0
  153. package/dist/backend/index.js +642 -0
  154. package/dist/backend/index.js.map +1 -0
  155. package/dist/backend/plugin.d.ts +216 -0
  156. package/dist/backend/plugin.d.ts.map +1 -0
  157. package/dist/backend/plugin.js +223 -0
  158. package/dist/backend/plugin.js.map +1 -0
  159. package/dist/backend/prompt-tools.d.ts +47 -0
  160. package/dist/backend/prompt-tools.d.ts.map +1 -0
  161. package/dist/backend/prompt-tools.js +284 -0
  162. package/dist/backend/prompt-tools.js.map +1 -0
  163. package/dist/backend/routes.admin.d.ts +105 -0
  164. package/dist/backend/routes.admin.d.ts.map +1 -0
  165. package/dist/backend/routes.admin.js +1162 -0
  166. package/dist/backend/routes.admin.js.map +1 -0
  167. package/dist/backend/routes.api-key.d.ts +33 -0
  168. package/dist/backend/routes.api-key.d.ts.map +1 -0
  169. package/dist/backend/routes.api-key.js +43 -0
  170. package/dist/backend/routes.api-key.js.map +1 -0
  171. package/dist/backend/routes.external.d.ts +32 -0
  172. package/dist/backend/routes.external.d.ts.map +1 -0
  173. package/dist/backend/routes.external.js +115 -0
  174. package/dist/backend/routes.external.js.map +1 -0
  175. package/dist/backend/routes.public.d.ts +46 -0
  176. package/dist/backend/routes.public.d.ts.map +1 -0
  177. package/dist/backend/routes.public.js +318 -0
  178. package/dist/backend/routes.public.js.map +1 -0
  179. package/dist/backend/services/asset-references.d.ts +4 -0
  180. package/dist/backend/services/asset-references.d.ts.map +1 -0
  181. package/dist/backend/services/asset-references.js +104 -0
  182. package/dist/backend/services/asset-references.js.map +1 -0
  183. package/dist/backend/services/attachment.service.d.ts +45 -0
  184. package/dist/backend/services/attachment.service.d.ts.map +1 -0
  185. package/dist/backend/services/attachment.service.js +268 -0
  186. package/dist/backend/services/attachment.service.js.map +1 -0
  187. package/dist/backend/services/attribute-option-validator.d.ts +47 -0
  188. package/dist/backend/services/attribute-option-validator.d.ts.map +1 -0
  189. package/dist/backend/services/attribute-option-validator.js +69 -0
  190. package/dist/backend/services/attribute-option-validator.js.map +1 -0
  191. package/dist/backend/services/attribute-set-validations.d.ts +65 -0
  192. package/dist/backend/services/attribute-set-validations.d.ts.map +1 -0
  193. package/dist/backend/services/attribute-set-validations.js +89 -0
  194. package/dist/backend/services/attribute-set-validations.js.map +1 -0
  195. package/dist/backend/services/attribute-set.service.d.ts +50 -0
  196. package/dist/backend/services/attribute-set.service.d.ts.map +1 -0
  197. package/dist/backend/services/attribute-set.service.js +401 -0
  198. package/dist/backend/services/attribute-set.service.js.map +1 -0
  199. package/dist/backend/services/attribute-type-mapping.d.ts +66 -0
  200. package/dist/backend/services/attribute-type-mapping.d.ts.map +1 -0
  201. package/dist/backend/services/attribute-type-mapping.js +121 -0
  202. package/dist/backend/services/attribute-type-mapping.js.map +1 -0
  203. package/dist/backend/services/attribute-value-key.service.d.ts +48 -0
  204. package/dist/backend/services/attribute-value-key.service.d.ts.map +1 -0
  205. package/dist/backend/services/attribute-value-key.service.js +118 -0
  206. package/dist/backend/services/attribute-value-key.service.js.map +1 -0
  207. package/dist/backend/services/audit-references.d.ts +13 -0
  208. package/dist/backend/services/audit-references.d.ts.map +1 -0
  209. package/dist/backend/services/audit-references.js +51 -0
  210. package/dist/backend/services/audit-references.js.map +1 -0
  211. package/dist/backend/services/bulk-operation-queue.d.ts +26 -0
  212. package/dist/backend/services/bulk-operation-queue.d.ts.map +1 -0
  213. package/dist/backend/services/bulk-operation-queue.js +34 -0
  214. package/dist/backend/services/bulk-operation-queue.js.map +1 -0
  215. package/dist/backend/services/bulk-operation.service.d.ts +195 -0
  216. package/dist/backend/services/bulk-operation.service.d.ts.map +1 -0
  217. package/dist/backend/services/bulk-operation.service.js +510 -0
  218. package/dist/backend/services/bulk-operation.service.js.map +1 -0
  219. package/dist/backend/services/bundle.service.d.ts +87 -0
  220. package/dist/backend/services/bundle.service.d.ts.map +1 -0
  221. package/dist/backend/services/bundle.service.js +286 -0
  222. package/dist/backend/services/bundle.service.js.map +1 -0
  223. package/dist/backend/services/catalog-admin.service.d.ts +398 -0
  224. package/dist/backend/services/catalog-admin.service.d.ts.map +1 -0
  225. package/dist/backend/services/catalog-admin.service.js +1516 -0
  226. package/dist/backend/services/catalog-admin.service.js.map +1 -0
  227. package/dist/backend/services/catalog-attribute-read.service.d.ts +78 -0
  228. package/dist/backend/services/catalog-attribute-read.service.d.ts.map +1 -0
  229. package/dist/backend/services/catalog-attribute-read.service.js +216 -0
  230. package/dist/backend/services/catalog-attribute-read.service.js.map +1 -0
  231. package/dist/backend/services/catalog-bulk-import.service.d.ts +30 -0
  232. package/dist/backend/services/catalog-bulk-import.service.d.ts.map +1 -0
  233. package/dist/backend/services/catalog-bulk-import.service.js +136 -0
  234. package/dist/backend/services/catalog-bulk-import.service.js.map +1 -0
  235. package/dist/backend/services/catalog-bulk-update.service.d.ts +91 -0
  236. package/dist/backend/services/catalog-bulk-update.service.d.ts.map +1 -0
  237. package/dist/backend/services/catalog-bulk-update.service.js +330 -0
  238. package/dist/backend/services/catalog-bulk-update.service.js.map +1 -0
  239. package/dist/backend/services/catalog-category-read.service.d.ts +66 -0
  240. package/dist/backend/services/catalog-category-read.service.d.ts.map +1 -0
  241. package/dist/backend/services/catalog-category-read.service.js +248 -0
  242. package/dist/backend/services/catalog-category-read.service.js.map +1 -0
  243. package/dist/backend/services/catalog-org-price-decorator.d.ts +90 -0
  244. package/dist/backend/services/catalog-org-price-decorator.d.ts.map +1 -0
  245. package/dist/backend/services/catalog-org-price-decorator.js +184 -0
  246. package/dist/backend/services/catalog-org-price-decorator.js.map +1 -0
  247. package/dist/backend/services/catalog-product-filter.service.d.ts +82 -0
  248. package/dist/backend/services/catalog-product-filter.service.d.ts.map +1 -0
  249. package/dist/backend/services/catalog-product-filter.service.js +275 -0
  250. package/dist/backend/services/catalog-product-filter.service.js.map +1 -0
  251. package/dist/backend/services/catalog-product-read.service.d.ts +46 -0
  252. package/dist/backend/services/catalog-product-read.service.d.ts.map +1 -0
  253. package/dist/backend/services/catalog-product-read.service.js +197 -0
  254. package/dist/backend/services/catalog-product-read.service.js.map +1 -0
  255. package/dist/backend/services/catalog-query.service.d.ts +444 -0
  256. package/dist/backend/services/catalog-query.service.d.ts.map +1 -0
  257. package/dist/backend/services/catalog-query.service.js +1633 -0
  258. package/dist/backend/services/catalog-query.service.js.map +1 -0
  259. package/dist/backend/services/catalog-quick-search.service.d.ts +78 -0
  260. package/dist/backend/services/catalog-quick-search.service.d.ts.map +1 -0
  261. package/dist/backend/services/catalog-quick-search.service.js +159 -0
  262. package/dist/backend/services/catalog-quick-search.service.js.map +1 -0
  263. package/dist/backend/services/catalog-write-ports.d.ts +25 -0
  264. package/dist/backend/services/catalog-write-ports.d.ts.map +1 -0
  265. package/dist/backend/services/catalog-write-ports.js +63 -0
  266. package/dist/backend/services/catalog-write-ports.js.map +1 -0
  267. package/dist/backend/services/category-admin.service.d.ts +102 -0
  268. package/dist/backend/services/category-admin.service.d.ts.map +1 -0
  269. package/dist/backend/services/category-admin.service.js +271 -0
  270. package/dist/backend/services/category-admin.service.js.map +1 -0
  271. package/dist/backend/services/gallery.service.d.ts +85 -0
  272. package/dist/backend/services/gallery.service.d.ts.map +1 -0
  273. package/dist/backend/services/gallery.service.js +326 -0
  274. package/dist/backend/services/gallery.service.js.map +1 -0
  275. package/dist/backend/services/grouped.service.d.ts +39 -0
  276. package/dist/backend/services/grouped.service.d.ts.map +1 -0
  277. package/dist/backend/services/grouped.service.js +136 -0
  278. package/dist/backend/services/grouped.service.js.map +1 -0
  279. package/dist/backend/services/label-resolver.d.ts +16 -0
  280. package/dist/backend/services/label-resolver.d.ts.map +1 -0
  281. package/dist/backend/services/label-resolver.js +43 -0
  282. package/dist/backend/services/label-resolver.js.map +1 -0
  283. package/dist/backend/services/packaging-unit.service.d.ts +28 -0
  284. package/dist/backend/services/packaging-unit.service.d.ts.map +1 -0
  285. package/dist/backend/services/packaging-unit.service.js +197 -0
  286. package/dist/backend/services/packaging-unit.service.js.map +1 -0
  287. package/dist/backend/services/primary-asset-url.d.ts +4 -0
  288. package/dist/backend/services/primary-asset-url.d.ts.map +1 -0
  289. package/dist/backend/services/primary-asset-url.js +55 -0
  290. package/dist/backend/services/primary-asset-url.js.map +1 -0
  291. package/dist/backend/services/product-editor-preferences.service.d.ts +20 -0
  292. package/dist/backend/services/product-editor-preferences.service.d.ts.map +1 -0
  293. package/dist/backend/services/product-editor-preferences.service.js +44 -0
  294. package/dist/backend/services/product-editor-preferences.service.js.map +1 -0
  295. package/dist/backend/services/product-link.service.d.ts +169 -0
  296. package/dist/backend/services/product-link.service.d.ts.map +1 -0
  297. package/dist/backend/services/product-link.service.js +297 -0
  298. package/dist/backend/services/product-link.service.js.map +1 -0
  299. package/dist/backend/services/product-overrides.service.d.ts +77 -0
  300. package/dist/backend/services/product-overrides.service.d.ts.map +1 -0
  301. package/dist/backend/services/product-overrides.service.js +204 -0
  302. package/dist/backend/services/product-overrides.service.js.map +1 -0
  303. package/dist/backend/services/product-scope-context.service.d.ts +36 -0
  304. package/dist/backend/services/product-scope-context.service.d.ts.map +1 -0
  305. package/dist/backend/services/product-scope-context.service.js +106 -0
  306. package/dist/backend/services/product-scope-context.service.js.map +1 -0
  307. package/dist/backend/services/product-type-validations.d.ts +42 -0
  308. package/dist/backend/services/product-type-validations.d.ts.map +1 -0
  309. package/dist/backend/services/product-type-validations.js +55 -0
  310. package/dist/backend/services/product-type-validations.js.map +1 -0
  311. package/dist/backend/services/product-value-resolver.service.d.ts +59 -0
  312. package/dist/backend/services/product-value-resolver.service.d.ts.map +1 -0
  313. package/dist/backend/services/product-value-resolver.service.js +111 -0
  314. package/dist/backend/services/product-value-resolver.service.js.map +1 -0
  315. package/dist/backend/services/system-attribute-scopes.d.ts +18 -0
  316. package/dist/backend/services/system-attribute-scopes.d.ts.map +1 -0
  317. package/dist/backend/services/system-attribute-scopes.js +18 -0
  318. package/dist/backend/services/system-attribute-scopes.js.map +1 -0
  319. package/dist/backend/services/viewer-organization.d.ts +25 -0
  320. package/dist/backend/services/viewer-organization.d.ts.map +1 -0
  321. package/dist/backend/services/viewer-organization.js +31 -0
  322. package/dist/backend/services/viewer-organization.js.map +1 -0
  323. package/dist/manifest.d.ts +204 -0
  324. package/dist/manifest.d.ts.map +1 -0
  325. package/dist/manifest.js +686 -0
  326. package/dist/manifest.js.map +1 -0
  327. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts +33 -0
  328. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.d.ts.map +1 -0
  329. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js +118 -0
  330. package/dist/migrations/20260429T064146_catalog_attribute_sets_init.js.map +1 -0
  331. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts +24 -0
  332. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.d.ts.map +1 -0
  333. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js +31 -0
  334. package/dist/migrations/20260429T070004_catalog_product_attribute_extensions.js.map +1 -0
  335. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts +32 -0
  336. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.d.ts.map +1 -0
  337. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js +58 -0
  338. package/dist/migrations/20260429T102322_catalog_product_type_and_virtual_fields.js.map +1 -0
  339. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts +22 -0
  340. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.d.ts.map +1 -0
  341. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js +66 -0
  342. package/dist/migrations/20260429T111839_catalog_gallery_items_and_labels.js.map +1 -0
  343. package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts +18 -0
  344. package/dist/migrations/20260429T112543_catalog_product_attachments.d.ts.map +1 -0
  345. package/dist/migrations/20260429T112543_catalog_product_attachments.js +73 -0
  346. package/dist/migrations/20260429T112543_catalog_product_attachments.js.map +1 -0
  347. package/dist/migrations/20260429T123726_catalog_product_links.d.ts +21 -0
  348. package/dist/migrations/20260429T123726_catalog_product_links.d.ts.map +1 -0
  349. package/dist/migrations/20260429T123726_catalog_product_links.js +48 -0
  350. package/dist/migrations/20260429T123726_catalog_product_links.js.map +1 -0
  351. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts +23 -0
  352. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.d.ts.map +1 -0
  353. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js +94 -0
  354. package/dist/migrations/20260429T130803_catalog_grouped_and_bundle.js.map +1 -0
  355. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts +24 -0
  356. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.d.ts.map +1 -0
  357. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js +31 -0
  358. package/dist/migrations/20260501T185835_catalog_product_attribute_is_comparable.js.map +1 -0
  359. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts +31 -0
  360. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.d.ts.map +1 -0
  361. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js +133 -0
  362. package/dist/migrations/20260505T060113_catalog_attribute_options_and_flags.js.map +1 -0
  363. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts +16 -0
  364. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.d.ts.map +1 -0
  365. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js +23 -0
  366. package/dist/migrations/20260515T082629_catalog_attribute_mass_editable.js.map +1 -0
  367. package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts +10 -0
  368. package/dist/migrations/20260526T124736_catalog_product_status_inactive.d.ts.map +1 -0
  369. package/dist/migrations/20260526T124736_catalog_product_status_inactive.js +22 -0
  370. package/dist/migrations/20260526T124736_catalog_product_status_inactive.js.map +1 -0
  371. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts +38 -0
  372. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.d.ts.map +1 -0
  373. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js +98 -0
  374. package/dist/migrations/20260611T140346_catalog_product_value_overrides_init.js.map +1 -0
  375. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts +13 -0
  376. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.d.ts.map +1 -0
  377. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js +17 -0
  378. package/dist/migrations/20260611T140400_catalog_attribute_quick_searchable.js.map +1 -0
  379. package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts +16 -0
  380. package/dist/migrations/20260611T140407_catalog_bulk_operations.d.ts.map +1 -0
  381. package/dist/migrations/20260611T140407_catalog_bulk_operations.js +42 -0
  382. package/dist/migrations/20260611T140407_catalog_bulk_operations.js.map +1 -0
  383. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts +14 -0
  384. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.d.ts.map +1 -0
  385. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js +18 -0
  386. package/dist/migrations/20260611T140408_catalog_bulk_operation_logs.js.map +1 -0
  387. package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts +11 -0
  388. package/dist/migrations/20260611T140412_catalog_product_packaging_units.d.ts.map +1 -0
  389. package/dist/migrations/20260611T140412_catalog_product_packaging_units.js +29 -0
  390. package/dist/migrations/20260611T140412_catalog_product_packaging_units.js.map +1 -0
  391. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts +23 -0
  392. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.d.ts.map +1 -0
  393. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js +35 -0
  394. package/dist/migrations/20260718T060659_catalog_bulk_operation_revert_state.js.map +1 -0
  395. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts +12 -0
  396. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.d.ts.map +1 -0
  397. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js +16 -0
  398. package/dist/migrations/20260718T200343_catalog_category_custom_field_values.js.map +1 -0
  399. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts +32 -0
  400. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.d.ts.map +1 -0
  401. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js +286 -0
  402. package/dist/migrations/20260723T230401_catalog_attributes_on_custom_fields.js.map +1 -0
  403. package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts +36 -0
  404. package/dist/migrations/20260804T152604_catalog_widen_product_sku.d.ts.map +1 -0
  405. package/dist/migrations/20260804T152604_catalog_widen_product_sku.js +63 -0
  406. package/dist/migrations/20260804T152604_catalog_widen_product_sku.js.map +1 -0
  407. package/dist/migrations/20260804T160244_catalog_category_activation.d.ts +18 -0
  408. package/dist/migrations/20260804T160244_catalog_category_activation.d.ts.map +1 -0
  409. package/dist/migrations/20260804T160244_catalog_category_activation.js +22 -0
  410. package/dist/migrations/20260804T160244_catalog_category_activation.js.map +1 -0
  411. package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts +27 -0
  412. package/dist/migrations/20260912T094557_catalog_sales_channel_products.d.ts.map +1 -0
  413. package/dist/migrations/20260912T094557_catalog_sales_channel_products.js +44 -0
  414. package/dist/migrations/20260912T094557_catalog_sales_channel_products.js.map +1 -0
  415. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts +27 -0
  416. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.d.ts.map +1 -0
  417. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js +44 -0
  418. package/dist/migrations/20260912T094623_catalog_sales_channel_categories.js.map +1 -0
  419. package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts +36 -0
  420. package/dist/migrations/20260925T125527_catalog_inventory_columns.d.ts.map +1 -0
  421. package/dist/migrations/20260925T125527_catalog_inventory_columns.js +87 -0
  422. package/dist/migrations/20260925T125527_catalog_inventory_columns.js.map +1 -0
  423. package/dist/migrations/index.d.ts +55 -0
  424. package/dist/migrations/index.d.ts.map +1 -0
  425. package/dist/migrations/index.js +80 -0
  426. package/dist/migrations/index.js.map +1 -0
  427. package/dist/ports/index.d.ts +96 -0
  428. package/dist/ports/index.d.ts.map +1 -0
  429. package/dist/ports/index.js +2 -0
  430. package/dist/ports/index.js.map +1 -0
  431. package/docs/catalog/attachments.md +69 -0
  432. package/docs/catalog/attribute-sets.md +63 -0
  433. package/docs/catalog/attributes.md +244 -0
  434. package/docs/catalog/composite-products.md +115 -0
  435. package/docs/catalog/gallery-and-labels.md +73 -0
  436. package/docs/catalog/packaging-units.md +67 -0
  437. package/docs/catalog/per-channel-per-language-overrides.md +234 -0
  438. package/docs/catalog/product-links.md +79 -0
  439. package/docs/catalog.md +207 -0
  440. package/i18n/en.json +649 -0
  441. package/i18n/pl.json +649 -0
  442. package/package.json +109 -0
  443. package/tailwind.css +14 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * GalleryService — admin-only CRUD for Product Gallery Items + their
3
+ * Base/Small/Thumbnail label assignments (feature 002 US3,
4
+ * data-model.md §2.3, §2.4, research.md R-2).
5
+ *
6
+ * The per-product UNIQUE constraint on `gallery_item_labels(product_id,
7
+ * label)` is the load-bearing invariant. The atomic-swap operation
8
+ * (`?replace=true`) wraps the swap in a single transaction:
9
+ * DELETE conflicting labels FROM other items → INSERT/UPDATE the new
10
+ * ones. Reading "current owner" + writing "new owner" is one atomic
11
+ * step so concurrent admins don't crash into each other's swaps.
12
+ */
13
+ import { randomUUID } from 'node:crypto';
14
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
15
+ import { HttpError } from '@endora-commerce/platform/http';
16
+ import { Product } from '../entities/product.entity.js';
17
+ import { GalleryItem } from '../entities/gallery-item.entity.js';
18
+ import { GalleryItemLabel } from '../entities/gallery-item-label.entity.js';
19
+ const GALLERY_LABELS_VALID = new Set([
20
+ 'base_image',
21
+ 'small_image',
22
+ 'thumbnail',
23
+ ]);
24
+ const ASSET_KINDS_GALLERY = new Set(['image', 'video']);
25
+ export class GalleryService {
26
+ emFactory;
27
+ commandBus;
28
+ assets;
29
+ constructor(emFactory,
30
+ /** Feature 054 — audits gallery-item writes co-transactionally when provided. */
31
+ commandBus,
32
+ /**
33
+ * Feature 075 — `assets_library`'s read port, where `em.findOne(Asset, …)`
34
+ * used to be. It backs the kind check below, so with `assets_library` off
35
+ * the port fails closed and a gallery item pointing at an unverifiable
36
+ * asset is refused rather than stored.
37
+ */
38
+ assets) {
39
+ this.emFactory = emFactory;
40
+ this.commandBus = commandBus;
41
+ this.assets = assets;
42
+ }
43
+ #requireAssets() {
44
+ if (!this.assets) {
45
+ throw new Error('GalleryService: the asset read port is not wired — a gallery item cannot be verified.');
46
+ }
47
+ return this.assets;
48
+ }
49
+ /**
50
+ * Feature 054 — run a gallery-item write through the Command Bus. The item +
51
+ * audit are co-transactional; the raw-SQL LABEL writes stay AFTER the command
52
+ * (a gallery item with no labels is a valid state, so nothing is orphaned).
53
+ */
54
+ async #audited(action, objectId, write) {
55
+ if (this.commandBus) {
56
+ return this.commandBus.run({ action, objectType: 'gallery_item', objectId, run: ({ em }) => write(em) });
57
+ }
58
+ const em = this.emFactory();
59
+ const w = await write(em);
60
+ await em.flush();
61
+ return w.result;
62
+ }
63
+ async list(productId) {
64
+ const em = this.emFactory();
65
+ await this.#assertProductExists(em, productId);
66
+ return this.#fetchAllForProduct(em, productId);
67
+ }
68
+ /**
69
+ * Feature 075 / D-87 — the batch half of {@link list}, for the callers that
70
+ * walk a page or a whole sellable catalogue.
71
+ *
72
+ * `product_feeds` used to run this statement itself, inside its own hydration
73
+ * batch, against a table `catalog` owns. It named no import specifier, so the
74
+ * boundary compiled and returned rows; `list` could not replace it because
75
+ * three round-trips per product is 1500 for a 500-product batch.
76
+ *
77
+ * Two things are deliberately absent. There is **no `#assertProductExists`**:
78
+ * a caller asks about the ids it holds, one of which may have been removed
79
+ * since, and answering 404 for the batch would be answering about a different
80
+ * question. And there are **no labels**: neither consumer reads them here, and
81
+ * fetching them costs a second statement per batch.
82
+ */
83
+ async listForProducts(productIds) {
84
+ if (productIds.length === 0)
85
+ return [];
86
+ const ids = [...new Set(productIds)];
87
+ // `em.execute`, not `em.getConnection().execute`, so a caller that opens a
88
+ // transaction around this read sees its own uncommitted writes (issue #200).
89
+ const rows = await this.emFactory().execute(`select gi.product_id::text as product_id,
90
+ gi.asset_id::text as asset_id,
91
+ gi.position
92
+ from gallery_items gi
93
+ where gi.product_id in (${ids.map(() => '?').join(',')})
94
+ order by gi.product_id, gi.position asc, gi.id asc`, ids);
95
+ return rows.map((row) => ({
96
+ productId: row.product_id,
97
+ assetId: row.asset_id,
98
+ position: Number(row.position),
99
+ }));
100
+ }
101
+ /**
102
+ * Feature 075 / D-87 — the `base_image` url of each of a batch of products.
103
+ *
104
+ * `comparisons` used to answer this with one statement joining this module's
105
+ * `gallery_item_labels` and `gallery_items` to `assets_library`'s `assets` —
106
+ * three cross-module reaches in a single join, which is why they retire
107
+ * together or not at all. Here the join stops at this module's own two tables
108
+ * and the asset row is asked of its owner through the port this service
109
+ * already holds, in one call for the whole batch.
110
+ *
111
+ * `base_image` only. This is not {@link resolvePrimaryAssetUrls}, which walks
112
+ * thumbnail → base_image → first item and answers "which image represents
113
+ * this product"; the comparison column names the base image explicitly
114
+ * (feature 007, FR-006), and a fallback here would quietly put a thumbnail in
115
+ * a column an operator set deliberately.
116
+ *
117
+ * The map holds one entry per requested id. `null` covers three cases the
118
+ * callers render identically — no label, no product, no asset row — and the
119
+ * inner join it replaces collapsed all three the same way.
120
+ */
121
+ async baseImageUrls(productIds) {
122
+ const urlByProduct = new Map();
123
+ for (const id of productIds)
124
+ urlByProduct.set(id, null);
125
+ if (urlByProduct.size === 0)
126
+ return urlByProduct;
127
+ const ids = [...urlByProduct.keys()];
128
+ const rows = await this.emFactory().execute(`select gil.product_id::text as product_id, gi.asset_id::text as asset_id
129
+ from gallery_item_labels gil
130
+ join gallery_items gi on gi.id = gil.gallery_item_id
131
+ where gil.label = 'base_image'
132
+ and gil.product_id in (${ids.map(() => '?').join(',')})`, ids);
133
+ if (rows.length === 0)
134
+ return urlByProduct;
135
+ // One call to the owner for every base image in the batch. `assets_library`
136
+ // is a binding dependency of this module, so an absent owner fails closed
137
+ // here rather than quietly answering "no product has an image".
138
+ const assetIds = [...new Set(rows.map((row) => row.asset_id))];
139
+ const urlByAsset = new Map((await this.#requireAssets().findByIds(assetIds)).map((asset) => [
140
+ asset.id,
141
+ asset.storageUrl,
142
+ ]));
143
+ for (const row of rows) {
144
+ const url = urlByAsset.get(row.asset_id);
145
+ if (url !== undefined)
146
+ urlByProduct.set(row.product_id, url);
147
+ }
148
+ return urlByProduct;
149
+ }
150
+ async create(productId, req, options) {
151
+ const em0 = this.emFactory();
152
+ await this.#assertProductExists(em0, productId);
153
+ const asset = await this.#requireAssets().findById(req.assetId);
154
+ if (!asset) {
155
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Asset ${req.assetId} not found.`);
156
+ }
157
+ if (!ASSET_KINDS_GALLERY.has(asset.kind)) {
158
+ throw new HttpError(400, ERROR_CODES.ASSET_KIND_NOT_SUPPORTED, `Gallery items require asset.kind ∈ {image, video}; got "${asset.kind}".`);
159
+ }
160
+ const labels = this.#normalizeAndValidateLabels(req.labels);
161
+ const position = req.position ?? (await this.#nextPosition(em0, productId));
162
+ const id = randomUUID();
163
+ await this.#audited('gallery_item.create', id, async (em) => {
164
+ const item = em.create(GalleryItem, { id, productId, assetId: req.assetId, position });
165
+ await em.flush();
166
+ return {
167
+ result: item,
168
+ before: null,
169
+ after: { productId, assetId: req.assetId, position, labels },
170
+ };
171
+ });
172
+ // Labels run AFTER the item commits (bridge-after-command) — matches the
173
+ // pre-054 two-step; an item with no labels is a valid state.
174
+ const em = this.emFactory();
175
+ if (labels.length > 0) {
176
+ await this.#applyLabels(em, productId, id, labels, options);
177
+ }
178
+ return (await this.#fetchOne(em, id));
179
+ }
180
+ async update(productId, itemId, req, options) {
181
+ await this.#audited('gallery_item.update', itemId, async (em) => {
182
+ const item = await em.findOne(GalleryItem, { id: itemId, productId });
183
+ if (!item) {
184
+ throw new HttpError(404, ERROR_CODES.GALLERY_ITEM_NOT_FOUND, `Gallery item ${itemId} not found under Product ${productId}.`);
185
+ }
186
+ const before = { position: item.position };
187
+ if (req.position !== undefined)
188
+ item.position = req.position;
189
+ return { result: undefined, before, after: { position: item.position } };
190
+ });
191
+ // Label replacement runs AFTER the item update (bridge-after-command).
192
+ if (req.labels !== undefined) {
193
+ const em = this.emFactory();
194
+ const labels = this.#normalizeAndValidateLabels(req.labels);
195
+ await em.execute(`delete from gallery_item_labels where gallery_item_id = ?`, [itemId]);
196
+ if (labels.length > 0) {
197
+ await this.#applyLabels(em, productId, itemId, labels, options);
198
+ }
199
+ }
200
+ return (await this.#fetchOne(this.emFactory(), itemId));
201
+ }
202
+ async delete(productId, itemId) {
203
+ await this.#audited('gallery_item.delete', itemId, async (em) => {
204
+ const item = await em.findOne(GalleryItem, { id: itemId, productId });
205
+ if (!item) {
206
+ throw new HttpError(404, ERROR_CODES.GALLERY_ITEM_NOT_FOUND, `Gallery item ${itemId} not found under Product ${productId}.`);
207
+ }
208
+ const before = { productId, assetId: item.assetId };
209
+ em.remove(item); // CASCADE on gallery_item_labels picks up the children.
210
+ return { result: undefined, before, after: null };
211
+ });
212
+ }
213
+ async reorder(productId, orderedIds) {
214
+ const em = this.emFactory();
215
+ await this.#assertProductExists(em, productId);
216
+ // Verify all ids belong to this product.
217
+ const existing = await em.find(GalleryItem, { productId });
218
+ const existingIds = new Set(existing.map((i) => i.id));
219
+ if (orderedIds.length !== existing.length ||
220
+ !orderedIds.every((id) => existingIds.has(id))) {
221
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'orderedGalleryItemIds must cover every existing gallery item exactly once.');
222
+ }
223
+ // One UPDATE per row. `em.execute`, not `em.getConnection().execute`, so a
224
+ // caller that ever opens a transaction around this gets the whole reorder
225
+ // in it (issue #207). This comment used to claim the loop was transactional
226
+ // and it is not: `em` here is a bare fork, so a failure part-way through
227
+ // leaves the gallery in the order the successful updates put it.
228
+ for (let i = 0; i < orderedIds.length; i++) {
229
+ await em.execute(`update gallery_items set position = ? where id = ?`, [i, orderedIds[i]]);
230
+ }
231
+ }
232
+ // -- INTERNALS -------------------------------------------------------------
233
+ async #assertProductExists(em, productId) {
234
+ const product = await em.findOne(Product, { id: productId });
235
+ if (!product) {
236
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, `Product ${productId} not found.`);
237
+ }
238
+ }
239
+ #normalizeAndValidateLabels(labels) {
240
+ if (!labels)
241
+ return [];
242
+ const seen = new Set();
243
+ for (const l of labels) {
244
+ if (!GALLERY_LABELS_VALID.has(l)) {
245
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, `Unknown gallery label "${l}".`);
246
+ }
247
+ seen.add(l);
248
+ }
249
+ if (seen.size !== labels.length) {
250
+ throw new HttpError(400, ERROR_CODES.GALLERY_LABEL_LIMIT_EXCEEDED, 'A gallery item cannot carry the same label twice (1-3 distinct labels).');
251
+ }
252
+ if (seen.size > 3) {
253
+ throw new HttpError(400, ERROR_CODES.GALLERY_LABEL_LIMIT_EXCEEDED, 'A gallery item can have at most 3 labels.');
254
+ }
255
+ return Array.from(seen);
256
+ }
257
+ async #nextPosition(em, productId) {
258
+ const rows = (await em.execute(`select coalesce(max(position), -1) + 1 as next_position from gallery_items where product_id = ?`, [productId]));
259
+ return rows[0]?.next_position ?? 0;
260
+ }
261
+ /**
262
+ * Apply a set of labels to a gallery item. If `replaceConflictingLabels`
263
+ * is true, any existing assignment of the same label to ANOTHER item
264
+ * under the same product is removed first (atomic swap). If false and
265
+ * a conflict exists, throws 409 GALLERY_LABEL_ALREADY_TAKEN.
266
+ */
267
+ async #applyLabels(em, productId, itemId, labels, options) {
268
+ if (options.replaceConflictingLabels) {
269
+ // Remove any other assignment of these labels under the product.
270
+ const placeholders = labels.map(() => '?').join(', ');
271
+ await em.execute(`delete from gallery_item_labels
272
+ where product_id = ? and label in (${placeholders}) and gallery_item_id <> ?`, [productId, ...labels, itemId]);
273
+ }
274
+ else {
275
+ // Fail-fast on any conflict.
276
+ const placeholders = labels.map(() => '?').join(', ');
277
+ const conflicts = (await em.execute(`select label, gallery_item_id from gallery_item_labels
278
+ where product_id = ? and label in (${placeholders}) and gallery_item_id <> ?`, [productId, ...labels, itemId]));
279
+ if (conflicts.length > 0) {
280
+ const c = conflicts[0];
281
+ throw new HttpError(409, ERROR_CODES.GALLERY_LABEL_ALREADY_TAKEN, `Label "${c.label}" is already assigned to gallery item ${c.gallery_item_id}.`, [{ path: 'labels', issue: `currently on ${c.gallery_item_id}` }]);
282
+ }
283
+ }
284
+ // Insert the new assignments. ON CONFLICT (gallery_item_id, label)
285
+ // DO NOTHING handles the case where the same label is already on
286
+ // this item (idempotent re-application).
287
+ const values = labels.map(() => '(?, ?, ?)').join(', ');
288
+ const params = [];
289
+ for (const l of labels)
290
+ params.push(itemId, productId, l);
291
+ await em.execute(`insert into gallery_item_labels (gallery_item_id, product_id, label) values ${values}
292
+ on conflict (gallery_item_id, label) do nothing`, params);
293
+ }
294
+ async #fetchAllForProduct(em, productId) {
295
+ const items = await em.find(GalleryItem, { productId }, { orderBy: { position: 'asc', id: 'asc' } });
296
+ if (items.length === 0)
297
+ return [];
298
+ const labels = await em.find(GalleryItemLabel, { productId });
299
+ const labelsByItem = new Map();
300
+ for (const l of labels) {
301
+ const existing = labelsByItem.get(l.galleryItemId) ?? [];
302
+ existing.push(l.label);
303
+ labelsByItem.set(l.galleryItemId, existing);
304
+ }
305
+ return items.map((i) => this.#toDto(i, labelsByItem.get(i.id) ?? []));
306
+ }
307
+ async #fetchOne(em, itemId) {
308
+ const item = await em.findOne(GalleryItem, { id: itemId });
309
+ if (!item)
310
+ return null;
311
+ const labels = await em.find(GalleryItemLabel, { galleryItemId: itemId });
312
+ return this.#toDto(item, labels.map((l) => l.label));
313
+ }
314
+ #toDto(item, labels) {
315
+ return {
316
+ id: item.id,
317
+ productId: item.productId,
318
+ assetId: item.assetId,
319
+ position: item.position,
320
+ labels: [...labels].sort(),
321
+ createdAt: item.createdAt.toISOString(),
322
+ updatedAt: item.updatedAt.toISOString(),
323
+ };
324
+ }
325
+ }
326
+ //# sourceMappingURL=gallery.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery.service.js","sourceRoot":"","sources":["../../../src/backend/services/gallery.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { CommandBus } from '@endora-commerce/platform/commands';
3
+ /**
4
+ * GroupedService — admin CRUD for grouped product children (feature 002
5
+ * US5, T128). The parent product MUST be `type='grouped'`; children MUST
6
+ * NOT be `type='grouped'` or `type='bundle'` (research R-8 — no nested
7
+ * composites, enforced at the service layer because PG CHECK can't JOIN
8
+ * `products`).
9
+ */
10
+ export interface GroupedItemRow {
11
+ id: string;
12
+ parentProductId: string;
13
+ childProductId: string;
14
+ quantity: number;
15
+ position: number;
16
+ }
17
+ export declare class GroupedService {
18
+ #private;
19
+ private readonly emFactory;
20
+ /** Feature 054 — audits grouped-item writes co-transactionally when provided. */
21
+ private readonly commandBus?;
22
+ constructor(emFactory: () => EntityManager,
23
+ /** Feature 054 — audits grouped-item writes co-transactionally when provided. */
24
+ commandBus?: CommandBus | undefined);
25
+ list(parentProductId: string): Promise<GroupedItemRow[]>;
26
+ addItem(parentProductId: string, input: {
27
+ childProductId: string;
28
+ quantity: number;
29
+ position?: number | undefined;
30
+ }): Promise<GroupedItemRow>;
31
+ updateItem(parentProductId: string, itemId: string, input: {
32
+ quantity?: number | undefined;
33
+ position?: number | undefined;
34
+ }): Promise<GroupedItemRow>;
35
+ removeItem(parentProductId: string, itemId: string): Promise<void>;
36
+ private assertGroupedParent;
37
+ private toRow;
38
+ }
39
+ //# sourceMappingURL=grouped.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouped.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/grouped.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAIrE;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAc;;IAEvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAFX,SAAS,EAAE,MAAM,aAAa;IAC/C,iFAAiF;IAChE,UAAU,CAAC,EAAE,UAAU,YAAA;IAsBpC,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAWxD,OAAO,CACX,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GACjF,OAAO,CAAC,cAAc,CAAC;IAyEpB,UAAU,CACd,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GACtE,OAAO,CAAC,cAAc,CAAC;IAwBpB,UAAU,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAiB1D,mBAAmB;IAsBjC,OAAO,CAAC,KAAK;CASd"}
@@ -0,0 +1,136 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ERROR_CODES } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { Product } from '../entities/product.entity.js';
5
+ import { GroupedItem } from '../entities/grouped-item.entity.js';
6
+ export class GroupedService {
7
+ emFactory;
8
+ commandBus;
9
+ constructor(emFactory,
10
+ /** Feature 054 — audits grouped-item writes co-transactionally when provided. */
11
+ commandBus) {
12
+ this.emFactory = emFactory;
13
+ this.commandBus = commandBus;
14
+ }
15
+ /** Feature 054 — run a grouped-item write through the Command Bus. */
16
+ async #audited(action, objectId, write) {
17
+ if (this.commandBus) {
18
+ return this.commandBus.run({ action, objectType: 'grouped_item', objectId, run: ({ em }) => write(em) });
19
+ }
20
+ const em = this.emFactory();
21
+ const w = await write(em);
22
+ await em.flush();
23
+ return w.result;
24
+ }
25
+ async list(parentProductId) {
26
+ const em = this.emFactory();
27
+ await this.assertGroupedParent(em, parentProductId);
28
+ const rows = await em.find(GroupedItem, { parentProductId }, { orderBy: { position: 'asc', id: 'asc' } });
29
+ return rows.map((r) => this.toRow(r));
30
+ }
31
+ async addItem(parentProductId, input) {
32
+ const em = this.emFactory();
33
+ await this.assertGroupedParent(em, parentProductId);
34
+ if (input.childProductId === parentProductId) {
35
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'A grouped product cannot include itself as a child.');
36
+ }
37
+ const child = await em.findOne(Product, { id: input.childProductId });
38
+ if (!child || child.deletedAt) {
39
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, `Child product "${input.childProductId}" not found.`);
40
+ }
41
+ if (child.type === 'grouped' || child.type === 'bundle') {
42
+ throw new HttpError(400, ERROR_CODES.NESTED_COMPOSITE_NOT_ALLOWED, `Grouped products cannot include another ${child.type} product as a child.`);
43
+ }
44
+ if (input.quantity <= 0) {
45
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'quantity must be positive.');
46
+ }
47
+ // Append by default; admin can override.
48
+ let position = input.position;
49
+ if (position === undefined) {
50
+ const tally = await em.find(GroupedItem, { parentProductId });
51
+ position = tally.reduce((max, r) => Math.max(max, r.position + 1), 0);
52
+ }
53
+ const id = randomUUID();
54
+ const row = await this.#audited('grouped_item.add', id, async (cem) => {
55
+ const r = cem.create(GroupedItem, {
56
+ id,
57
+ parentProductId,
58
+ childProductId: input.childProductId,
59
+ quantity: input.quantity,
60
+ position,
61
+ });
62
+ try {
63
+ await cem.flush();
64
+ }
65
+ catch (err) {
66
+ if (err instanceof Error && /uniq_grouped_items_parent_child/.test(err.message)) {
67
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `Child product "${input.childProductId}" is already in this group.`);
68
+ }
69
+ throw err;
70
+ }
71
+ return {
72
+ result: r,
73
+ before: null,
74
+ after: {
75
+ parentProductId,
76
+ childProductId: input.childProductId,
77
+ quantity: input.quantity,
78
+ position,
79
+ },
80
+ };
81
+ });
82
+ return this.toRow(row);
83
+ }
84
+ async updateItem(parentProductId, itemId, input) {
85
+ const row = await this.#audited('grouped_item.update', itemId, async (em) => {
86
+ await this.assertGroupedParent(em, parentProductId);
87
+ const r = await em.findOne(GroupedItem, { id: itemId, parentProductId });
88
+ if (!r) {
89
+ throw new HttpError(404, ERROR_CODES.GROUPED_ITEM_NOT_FOUND, `Grouped item "${itemId}" not found on parent ${parentProductId}.`);
90
+ }
91
+ const before = { quantity: r.quantity, position: r.position };
92
+ if (input.quantity !== undefined) {
93
+ if (input.quantity <= 0) {
94
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'quantity must be positive.');
95
+ }
96
+ r.quantity = input.quantity;
97
+ }
98
+ if (input.position !== undefined)
99
+ r.position = input.position;
100
+ return { result: r, before, after: { quantity: r.quantity, position: r.position } };
101
+ });
102
+ return this.toRow(row);
103
+ }
104
+ async removeItem(parentProductId, itemId) {
105
+ await this.#audited('grouped_item.delete', itemId, async (em) => {
106
+ await this.assertGroupedParent(em, parentProductId);
107
+ const row = await em.findOne(GroupedItem, { id: itemId, parentProductId });
108
+ if (!row) {
109
+ throw new HttpError(404, ERROR_CODES.GROUPED_ITEM_NOT_FOUND, `Grouped item "${itemId}" not found on parent ${parentProductId}.`);
110
+ }
111
+ const before = { parentProductId, childProductId: row.childProductId };
112
+ em.remove(row);
113
+ return { result: undefined, before, after: null };
114
+ });
115
+ }
116
+ async assertGroupedParent(em, parentProductId) {
117
+ const parent = await em.findOne(Product, { id: parentProductId });
118
+ if (!parent || parent.deletedAt) {
119
+ throw new HttpError(404, ERROR_CODES.PRODUCT_NOT_FOUND, `Product "${parentProductId}" not found.`);
120
+ }
121
+ if (parent.type !== 'grouped') {
122
+ throw new HttpError(400, ERROR_CODES.PRODUCT_TYPE_MISMATCH, `Product type is "${parent.type}", expected "grouped".`);
123
+ }
124
+ return parent;
125
+ }
126
+ toRow(r) {
127
+ return {
128
+ id: r.id,
129
+ parentProductId: r.parentProductId,
130
+ childProductId: r.childProductId,
131
+ quantity: r.quantity,
132
+ position: r.position,
133
+ };
134
+ }
135
+ }
136
+ //# sourceMappingURL=grouped.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grouped.service.js","sourceRoot":"","sources":["../../../src/backend/services/grouped.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Per-locale label resolver (feature 012 / R-3).
3
+ *
4
+ * Reads `label[locale]` from the per-locale JSONB map; falls back to
5
+ * `labelDefault` when the active locale is missing or the per-locale
6
+ * value is empty. Locale tags are normalised so `pl_PL` matches a map
7
+ * keyed `pl-PL` (and vice-versa) and the region tag is matched
8
+ * case-insensitively.
9
+ *
10
+ * The resolver is pure — given the same inputs it returns the same
11
+ * output. No I/O, no clock, no globals. Used by every storefront +
12
+ * admin reader that needs the rendered label of an attribute or
13
+ * option.
14
+ */
15
+ export declare function resolveLabel(label: Record<string, string> | null | undefined, labelDefault: string, locale: string): string;
16
+ //# sourceMappingURL=label-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/label-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,EAChD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CASR"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-locale label resolver (feature 012 / R-3).
3
+ *
4
+ * Reads `label[locale]` from the per-locale JSONB map; falls back to
5
+ * `labelDefault` when the active locale is missing or the per-locale
6
+ * value is empty. Locale tags are normalised so `pl_PL` matches a map
7
+ * keyed `pl-PL` (and vice-versa) and the region tag is matched
8
+ * case-insensitively.
9
+ *
10
+ * The resolver is pure — given the same inputs it returns the same
11
+ * output. No I/O, no clock, no globals. Used by every storefront +
12
+ * admin reader that needs the rendered label of an attribute or
13
+ * option.
14
+ */
15
+ export function resolveLabel(label, labelDefault, locale) {
16
+ if (!label)
17
+ return labelDefault;
18
+ const wantedKeys = normaliseLocaleVariants(locale);
19
+ const lookup = buildKeyLookup(label);
20
+ for (const key of wantedKeys) {
21
+ const hit = lookup.get(key);
22
+ if (hit !== undefined && hit !== '')
23
+ return hit;
24
+ }
25
+ return labelDefault;
26
+ }
27
+ function normaliseLocaleVariants(locale) {
28
+ if (!locale)
29
+ return [];
30
+ const lower = locale.toLowerCase();
31
+ // Generate the four common spellings: as-is, lower-case, dash↔underscore variants.
32
+ const variants = new Set([locale, lower, lower.replace('_', '-'), lower.replace('-', '_')]);
33
+ return [...variants];
34
+ }
35
+ function buildKeyLookup(label) {
36
+ const out = new Map();
37
+ for (const [key, value] of Object.entries(label)) {
38
+ out.set(key, value);
39
+ out.set(key.toLowerCase(), value);
40
+ }
41
+ return out;
42
+ }
43
+ //# sourceMappingURL=label-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/label-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAgD,EAChD,YAAoB,EACpB,MAAc;IAEd,IAAI,CAAC,KAAK;QAAE,OAAO,YAAY,CAAC;IAChC,MAAM,UAAU,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACrC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;YAAE,OAAO,GAAG,CAAC;IAClD,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAc;IAC7C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACnC,mFAAmF;IACnF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACpG,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,cAAc,CAAC,KAA6B;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACpB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * PackagingUnitService — admin CRUD for product packaging units (feature 043).
3
+ *
4
+ * A packaging unit is a named ordering unit on a product (e.g. "Paleta" = 480
5
+ * pieces). Units apply only to physical, purchasable product types
6
+ * (simple / configurable). At most one unit per product is the default
7
+ * presented on the storefront; setting a new default clears the previous one
8
+ * (last write wins). Names are unique within a product. Hard delete is safe
9
+ * because cart/order/RFQ lines snapshot their own copy.
10
+ */
11
+ import type { EntityManager } from '@mikro-orm/postgresql';
12
+ import { type CreatePackagingUnitRequest, type PackagingUnitDto, type ReorderPackagingUnitsRequest, type UpdatePackagingUnitRequest } from '@endora-commerce/contracts';
13
+ import type { CommandBus } from '@endora-commerce/platform/commands';
14
+ export declare class PackagingUnitService {
15
+ #private;
16
+ private readonly emFactory;
17
+ /** Feature 054 — audits packaging-unit writes co-transactionally when provided. */
18
+ private readonly commandBus?;
19
+ constructor(emFactory: () => EntityManager,
20
+ /** Feature 054 — audits packaging-unit writes co-transactionally when provided. */
21
+ commandBus?: CommandBus | undefined);
22
+ list(productId: string): Promise<PackagingUnitDto[]>;
23
+ create(productId: string, req: CreatePackagingUnitRequest): Promise<PackagingUnitDto>;
24
+ update(productId: string, unitId: string, req: UpdatePackagingUnitRequest): Promise<PackagingUnitDto>;
25
+ delete(productId: string, unitId: string): Promise<void>;
26
+ reorder(productId: string, req: ReorderPackagingUnitsRequest): Promise<PackagingUnitDto[]>;
27
+ }
28
+ //# sourceMappingURL=packaging-unit.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packaging-unit.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/packaging-unit.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAChC,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAYrE,qBAAa,oBAAoB;;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAFX,SAAS,EAAE,MAAM,aAAa;IAC/C,mFAAmF;IAClE,UAAU,CAAC,EAAE,UAAU,YAAA;IA0BpC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAWpD,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,0BAA0B,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IA4BtB,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,0BAA0B,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IAuBtB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxD,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,4BAA4B,GAChC,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAqH/B"}