@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6121.1.5797a901b3

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 (317) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  147. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  151. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  153. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  154. package/dist/modules/planner/api/access.js +9 -0
  155. package/dist/modules/planner/api/access.js.map +2 -2
  156. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  157. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  158. package/dist/modules/planner/api/availability-weekly.js +22 -1
  159. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  160. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  161. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  164. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  165. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  167. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  168. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  169. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  172. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  173. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  175. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  178. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  179. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  180. package/dist/modules/translations/api/context.js +7 -0
  181. package/dist/modules/translations/api/context.js.map +2 -2
  182. package/dist/modules/translations/api/put/locales.js +33 -1
  183. package/dist/modules/translations/api/put/locales.js.map +2 -2
  184. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  185. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  186. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  187. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  188. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  189. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  192. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  193. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  194. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  195. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  196. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  197. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  198. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  199. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  200. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  201. package/generated/entities/catalog_product/index.ts +26 -0
  202. package/generated/entities/catalog_product_variant/index.ts +2 -0
  203. package/generated/entity-fields-registry.ts +28 -0
  204. package/package.json +7 -7
  205. package/src/modules/catalog/api/products/route.ts +123 -39
  206. package/src/modules/catalog/api/variants/route.ts +4 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  209. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  210. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  211. package/src/modules/catalog/commands/products.ts +307 -2
  212. package/src/modules/catalog/commands/variants.ts +90 -1
  213. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  214. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  215. package/src/modules/catalog/components/products/productForm.ts +262 -0
  216. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  217. package/src/modules/catalog/data/entities.ts +97 -0
  218. package/src/modules/catalog/data/types.ts +36 -0
  219. package/src/modules/catalog/data/validators.ts +159 -3
  220. package/src/modules/catalog/i18n/de.json +64 -0
  221. package/src/modules/catalog/i18n/en.json +64 -0
  222. package/src/modules/catalog/i18n/es.json +64 -0
  223. package/src/modules/catalog/i18n/pl.json +64 -0
  224. package/src/modules/catalog/lib/gtin.ts +66 -0
  225. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  226. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  227. package/src/modules/catalog/translations.ts +1 -1
  228. package/src/modules/configs/api/cache/route.ts +72 -10
  229. package/src/modules/configs/components/CachePanel.tsx +59 -29
  230. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  231. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  232. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  233. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  234. package/src/modules/customer_accounts/i18n/de.json +1 -1
  235. package/src/modules/customer_accounts/i18n/en.json +1 -1
  236. package/src/modules/customer_accounts/i18n/es.json +1 -1
  237. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  238. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  239. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  240. package/src/modules/customers/cli.ts +1 -0
  241. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  242. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  243. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  244. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  245. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  246. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  247. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  248. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  249. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  250. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  251. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  252. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  253. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  254. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  255. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  256. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  257. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  258. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  259. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  260. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  261. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  262. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  263. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  264. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  265. package/src/modules/customers/components/calendar/types.ts +163 -0
  266. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  267. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  268. package/src/modules/customers/i18n/de.json +195 -0
  269. package/src/modules/customers/i18n/en.json +195 -0
  270. package/src/modules/customers/i18n/es.json +195 -0
  271. package/src/modules/customers/i18n/pl.json +195 -0
  272. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  273. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  274. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  275. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  276. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  277. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  278. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  279. package/src/modules/customers/lib/calendar/range.ts +45 -0
  280. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  281. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  282. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  283. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  284. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  285. package/src/modules/dictionaries/api/route.ts +36 -1
  286. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  287. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  288. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  289. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  290. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  291. package/src/modules/planner/api/access.ts +9 -0
  292. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  293. package/src/modules/planner/api/availability-weekly.ts +22 -1
  294. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  295. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  296. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  297. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  298. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  299. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  300. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  301. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  302. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  303. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  304. package/src/modules/translations/api/context.ts +7 -0
  305. package/src/modules/translations/api/put/locales.ts +35 -1
  306. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  307. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  308. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  309. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  310. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  311. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  312. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  313. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  314. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  315. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  316. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  317. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -345,6 +345,18 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
345
345
  name: typeof record.name === 'string' ? record.name : '',
346
346
  sku: typeof record.sku === 'string' ? record.sku : '',
347
347
  barcode: typeof record.barcode === 'string' ? record.barcode : '',
348
+ gtinType:
349
+ typeof record.gtin_type === 'string'
350
+ ? record.gtin_type
351
+ : typeof record.gtinType === 'string'
352
+ ? record.gtinType
353
+ : null,
354
+ hsCode:
355
+ typeof record.hs_code === 'string'
356
+ ? record.hs_code
357
+ : typeof record.hsCode === 'string'
358
+ ? record.hsCode
359
+ : '',
348
360
  isDefault: record.is_default === true || record.isDefault === true,
349
361
  isActive: record.is_active !== false && record.isActive !== false,
350
362
  optionValues: normalizedOptionValues,
@@ -600,6 +612,8 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
600
612
  name,
601
613
  sku: values.sku?.trim() || undefined,
602
614
  barcode: values.barcode?.trim() || undefined,
615
+ gtinType: values.gtinType ?? null,
616
+ hsCode: values.hsCode?.trim() || null,
603
617
  isDefault: Boolean(values.isDefault),
604
618
  isActive: values.isActive !== false,
605
619
  optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,
@@ -367,6 +367,8 @@ export default function CreateVariantPage({ params }: { params?: { productId?: s
367
367
  name,
368
368
  sku: values.sku?.trim() || undefined,
369
369
  barcode: values.barcode?.trim() || undefined,
370
+ gtinType: values.gtinType ?? null,
371
+ hsCode: values.hsCode?.trim() || null,
370
372
  isDefault: Boolean(values.isDefault),
371
373
  isActive: values.isActive !== false,
372
374
  optionValues: Object.keys(values.optionValues ?? {}).length ? values.optionValues : undefined,
@@ -77,6 +77,7 @@ import {
77
77
  updateDimensionValue,
78
78
  updateWeightValue,
79
79
  isConfigurableProductType,
80
+ buildComplianceProductPayload,
80
81
  } from "@open-mercato/core/modules/catalog/components/products/productForm";
81
82
  import { CATALOG_PRODUCT_TYPES } from "@open-mercato/core/modules/catalog/data/types";
82
83
  import {
@@ -84,6 +85,7 @@ import {
84
85
  slugifyAttachmentFileName,
85
86
  } from "@open-mercato/core/modules/attachments/lib/imageUrls";
86
87
  import { ProductUomSection } from "@open-mercato/core/modules/catalog/components/products/ProductUomSection";
88
+ import { ProductComplianceSection } from "@open-mercato/core/modules/catalog/components/products/ProductComplianceSection";
87
89
  import { canonicalizeUnitCode } from "@open-mercato/core/modules/catalog/lib/unitCodes";
88
90
  import {
89
91
  UNIT_PRICE_REFERENCE_UNITS,
@@ -150,6 +152,33 @@ const STEP_FIELD_MATCHERS: Record<
150
152
  matchField("unitPriceBaseQuantity"),
151
153
  matchPrefix("unitConversions"),
152
154
  ],
155
+ compliance: [
156
+ matchField("countryOfOriginCode"),
157
+ matchField("pkwiuCode"),
158
+ matchField("cnCode"),
159
+ matchField("hsCode"),
160
+ matchField("taxClassificationCode"),
161
+ matchField("gtuCodes"),
162
+ matchField("ageMin"),
163
+ matchField("isExciseGood"),
164
+ matchField("exciseCategory"),
165
+ matchField("requiresPrescription"),
166
+ matchPrefix("hazmat"),
167
+ matchField("unNumber"),
168
+ matchField("containsLithiumBattery"),
169
+ matchField("launchAt"),
170
+ matchField("endOfLifeAt"),
171
+ matchField("availableFrom"),
172
+ matchField("availableUntil"),
173
+ matchField("minOrderQty"),
174
+ matchField("maxOrderQty"),
175
+ matchField("orderQtyIncrement"),
176
+ matchField("requiresShipping"),
177
+ matchField("isQuoteOnly"),
178
+ matchField("seoTitle"),
179
+ matchField("seoDescription"),
180
+ matchField("canonicalUrl"),
181
+ ],
153
182
  variants: [
154
183
  matchField("hasVariants"),
155
184
  matchPrefix("options"),
@@ -542,6 +571,7 @@ export default function CreateCatalogProductPage() {
542
571
  unitPriceBaseQuantity: unitPriceEnabled
543
572
  ? unitPriceBaseQuantity
544
573
  : undefined,
574
+ ...buildComplianceProductPayload(formValues),
545
575
  };
546
576
  if (optionSchemaDefinition) {
547
577
  productPayload.optionSchema = optionSchemaDefinition;
@@ -1312,6 +1342,8 @@ function ProductBuilder({
1312
1342
  t("catalog.products.create.steps.organize", "Organize")}
1313
1343
  {step === "uom" &&
1314
1344
  t("catalog.products.uom.title", "Units of measure")}
1345
+ {step === "compliance" &&
1346
+ t("catalog.products.compliance.title", "Compliance & commerce")}
1315
1347
  {step === "variants" &&
1316
1348
  t("catalog.products.create.steps.variants", "Variants")}
1317
1349
  {(stepErrors[step]?.length ?? 0) > 0 ? (
@@ -1434,6 +1466,15 @@ function ProductBuilder({
1434
1466
  />
1435
1467
  ) : null}
1436
1468
 
1469
+ {currentStepKey === "compliance" ? (
1470
+ <ProductComplianceSection
1471
+ values={values as ProductFormValues}
1472
+ setValue={setValue}
1473
+ errors={errors}
1474
+ embedded
1475
+ />
1476
+ ) : null}
1477
+
1437
1478
  {currentStepKey === "variants" ? (
1438
1479
  <div className="space-y-6">
1439
1480
  <label className="flex items-center gap-2 text-sm font-medium">
@@ -52,6 +52,8 @@ import {
52
52
  type ProductUpdateInput,
53
53
  } from "../data/validators";
54
54
  import type {
55
+ CatalogExciseCategory,
56
+ CatalogHazmatPackingGroup,
55
57
  CatalogProductOptionSchema,
56
58
  CatalogProductType,
57
59
  } from "../data/types";
@@ -105,6 +107,32 @@ type ProductSnapshot = {
105
107
  weightValue: string | null;
106
108
  weightUnit: string | null;
107
109
  dimensions: Record<string, unknown> | null;
110
+ countryOfOriginCode: string | null;
111
+ pkwiuCode: string | null;
112
+ cnCode: string | null;
113
+ hsCode: string | null;
114
+ taxClassificationCode: string | null;
115
+ gtuCodes: string[] | null;
116
+ ageMin: number | null;
117
+ isExciseGood: boolean;
118
+ exciseCategory: CatalogExciseCategory | null;
119
+ requiresPrescription: boolean;
120
+ hazmatClass: string | null;
121
+ unNumber: string | null;
122
+ hazmatPackingGroup: CatalogHazmatPackingGroup | null;
123
+ containsLithiumBattery: boolean;
124
+ launchAt: string | null;
125
+ endOfLifeAt: string | null;
126
+ availableFrom: string | null;
127
+ availableUntil: string | null;
128
+ minOrderQty: number | null;
129
+ maxOrderQty: number | null;
130
+ orderQtyIncrement: number | null;
131
+ requiresShipping: boolean;
132
+ isQuoteOnly: boolean;
133
+ seoTitle: string | null;
134
+ seoDescription: string | null;
135
+ canonicalUrl: string | null;
108
136
  optionSchemaId: string | null;
109
137
  customFieldsetCode: string | null;
110
138
  metadata: Record<string, unknown> | null;
@@ -1154,6 +1182,36 @@ async function loadProductSnapshot(
1154
1182
  weightValue,
1155
1183
  weightUnit,
1156
1184
  dimensions,
1185
+ countryOfOriginCode: record.countryOfOriginCode ?? null,
1186
+ pkwiuCode: record.pkwiuCode ?? null,
1187
+ cnCode: record.cnCode ?? null,
1188
+ hsCode: record.hsCode ?? null,
1189
+ taxClassificationCode: record.taxClassificationCode ?? null,
1190
+ gtuCodes: record.gtuCodes?.length ? [...record.gtuCodes] : null,
1191
+ ageMin: record.ageMin ?? null,
1192
+ isExciseGood: record.isExciseGood ?? false,
1193
+ exciseCategory: record.exciseCategory ?? null,
1194
+ requiresPrescription: record.requiresPrescription ?? false,
1195
+ hazmatClass: record.hazmatClass ?? null,
1196
+ unNumber: record.unNumber ?? null,
1197
+ hazmatPackingGroup: record.hazmatPackingGroup ?? null,
1198
+ containsLithiumBattery: record.containsLithiumBattery ?? false,
1199
+ launchAt: record.launchAt ? record.launchAt.toISOString() : null,
1200
+ endOfLifeAt: record.endOfLifeAt ? record.endOfLifeAt.toISOString() : null,
1201
+ availableFrom: record.availableFrom
1202
+ ? record.availableFrom.toISOString()
1203
+ : null,
1204
+ availableUntil: record.availableUntil
1205
+ ? record.availableUntil.toISOString()
1206
+ : null,
1207
+ minOrderQty: record.minOrderQty ?? null,
1208
+ maxOrderQty: record.maxOrderQty ?? null,
1209
+ orderQtyIncrement: record.orderQtyIncrement ?? null,
1210
+ requiresShipping: record.requiresShipping ?? true,
1211
+ isQuoteOnly: record.isQuoteOnly ?? false,
1212
+ seoTitle: record.seoTitle ?? null,
1213
+ seoDescription: record.seoDescription ?? null,
1214
+ canonicalUrl: record.canonicalUrl ?? null,
1157
1215
  customFieldsetCode: record.customFieldsetCode ?? null,
1158
1216
  metadata,
1159
1217
  isConfigurable: record.isConfigurable,
@@ -1198,6 +1256,36 @@ function productSeedFromSnapshot(
1198
1256
  weightValue: snapshot.weightValue ?? null,
1199
1257
  weightUnit: snapshot.weightUnit ?? null,
1200
1258
  dimensions: snapshot.dimensions ? cloneJson(snapshot.dimensions) : null,
1259
+ countryOfOriginCode: snapshot.countryOfOriginCode ?? null,
1260
+ pkwiuCode: snapshot.pkwiuCode ?? null,
1261
+ cnCode: snapshot.cnCode ?? null,
1262
+ hsCode: snapshot.hsCode ?? null,
1263
+ taxClassificationCode: snapshot.taxClassificationCode ?? null,
1264
+ gtuCodes: snapshot.gtuCodes ? [...snapshot.gtuCodes] : null,
1265
+ ageMin: snapshot.ageMin ?? null,
1266
+ isExciseGood: snapshot.isExciseGood ?? false,
1267
+ exciseCategory: snapshot.exciseCategory ?? null,
1268
+ requiresPrescription: snapshot.requiresPrescription ?? false,
1269
+ hazmatClass: snapshot.hazmatClass ?? null,
1270
+ unNumber: snapshot.unNumber ?? null,
1271
+ hazmatPackingGroup: snapshot.hazmatPackingGroup ?? null,
1272
+ containsLithiumBattery: snapshot.containsLithiumBattery ?? false,
1273
+ launchAt: snapshot.launchAt ? new Date(snapshot.launchAt) : null,
1274
+ endOfLifeAt: snapshot.endOfLifeAt ? new Date(snapshot.endOfLifeAt) : null,
1275
+ availableFrom: snapshot.availableFrom
1276
+ ? new Date(snapshot.availableFrom)
1277
+ : null,
1278
+ availableUntil: snapshot.availableUntil
1279
+ ? new Date(snapshot.availableUntil)
1280
+ : null,
1281
+ minOrderQty: snapshot.minOrderQty ?? null,
1282
+ maxOrderQty: snapshot.maxOrderQty ?? null,
1283
+ orderQtyIncrement: snapshot.orderQtyIncrement ?? null,
1284
+ requiresShipping: snapshot.requiresShipping ?? true,
1285
+ isQuoteOnly: snapshot.isQuoteOnly ?? false,
1286
+ seoTitle: snapshot.seoTitle ?? null,
1287
+ seoDescription: snapshot.seoDescription ?? null,
1288
+ canonicalUrl: snapshot.canonicalUrl ?? null,
1201
1289
  metadata: snapshot.metadata ? cloneJson(snapshot.metadata) : null,
1202
1290
  customFieldsetCode: snapshot.customFieldsetCode ?? null,
1203
1291
  isConfigurable: snapshot.isConfigurable,
@@ -1239,6 +1327,38 @@ function applyProductSnapshot(
1239
1327
  record.dimensions = snapshot.dimensions
1240
1328
  ? cloneJson(snapshot.dimensions)
1241
1329
  : null;
1330
+ record.countryOfOriginCode = snapshot.countryOfOriginCode ?? null;
1331
+ record.pkwiuCode = snapshot.pkwiuCode ?? null;
1332
+ record.cnCode = snapshot.cnCode ?? null;
1333
+ record.hsCode = snapshot.hsCode ?? null;
1334
+ record.taxClassificationCode = snapshot.taxClassificationCode ?? null;
1335
+ record.gtuCodes = snapshot.gtuCodes ? [...snapshot.gtuCodes] : null;
1336
+ record.ageMin = snapshot.ageMin ?? null;
1337
+ record.isExciseGood = snapshot.isExciseGood ?? false;
1338
+ record.exciseCategory = snapshot.exciseCategory ?? null;
1339
+ record.requiresPrescription = snapshot.requiresPrescription ?? false;
1340
+ record.hazmatClass = snapshot.hazmatClass ?? null;
1341
+ record.unNumber = snapshot.unNumber ?? null;
1342
+ record.hazmatPackingGroup = snapshot.hazmatPackingGroup ?? null;
1343
+ record.containsLithiumBattery = snapshot.containsLithiumBattery ?? false;
1344
+ record.launchAt = snapshot.launchAt ? new Date(snapshot.launchAt) : null;
1345
+ record.endOfLifeAt = snapshot.endOfLifeAt
1346
+ ? new Date(snapshot.endOfLifeAt)
1347
+ : null;
1348
+ record.availableFrom = snapshot.availableFrom
1349
+ ? new Date(snapshot.availableFrom)
1350
+ : null;
1351
+ record.availableUntil = snapshot.availableUntil
1352
+ ? new Date(snapshot.availableUntil)
1353
+ : null;
1354
+ record.minOrderQty = snapshot.minOrderQty ?? null;
1355
+ record.maxOrderQty = snapshot.maxOrderQty ?? null;
1356
+ record.orderQtyIncrement = snapshot.orderQtyIncrement ?? null;
1357
+ record.requiresShipping = snapshot.requiresShipping ?? true;
1358
+ record.isQuoteOnly = snapshot.isQuoteOnly ?? false;
1359
+ record.seoTitle = snapshot.seoTitle ?? null;
1360
+ record.seoDescription = snapshot.seoDescription ?? null;
1361
+ record.canonicalUrl = snapshot.canonicalUrl ?? null;
1242
1362
  record.metadata = snapshot.metadata ? cloneJson(snapshot.metadata) : null;
1243
1363
  record.customFieldsetCode = snapshot.customFieldsetCode ?? null;
1244
1364
  record.optionSchemaTemplate = snapshot.optionSchemaId
@@ -1332,6 +1452,32 @@ const createProductCommand: CommandHandler<
1332
1452
  weightValue,
1333
1453
  weightUnit,
1334
1454
  dimensions,
1455
+ countryOfOriginCode: parsed.countryOfOriginCode ?? null,
1456
+ pkwiuCode: parsed.pkwiuCode ?? null,
1457
+ cnCode: parsed.cnCode ?? null,
1458
+ hsCode: parsed.hsCode ?? null,
1459
+ taxClassificationCode: parsed.taxClassificationCode ?? null,
1460
+ gtuCodes: parsed.gtuCodes ?? null,
1461
+ ageMin: parsed.ageMin ?? null,
1462
+ isExciseGood: parsed.isExciseGood ?? false,
1463
+ exciseCategory: parsed.exciseCategory ?? null,
1464
+ requiresPrescription: parsed.requiresPrescription ?? false,
1465
+ hazmatClass: parsed.hazmatClass ?? null,
1466
+ unNumber: parsed.unNumber ?? null,
1467
+ hazmatPackingGroup: parsed.hazmatPackingGroup ?? null,
1468
+ containsLithiumBattery: parsed.containsLithiumBattery ?? false,
1469
+ launchAt: parsed.launchAt ?? null,
1470
+ endOfLifeAt: parsed.endOfLifeAt ?? null,
1471
+ availableFrom: parsed.availableFrom ?? null,
1472
+ availableUntil: parsed.availableUntil ?? null,
1473
+ minOrderQty: parsed.minOrderQty ?? null,
1474
+ maxOrderQty: parsed.maxOrderQty ?? null,
1475
+ orderQtyIncrement: parsed.orderQtyIncrement ?? null,
1476
+ requiresShipping: parsed.requiresShipping ?? true,
1477
+ isQuoteOnly: parsed.isQuoteOnly ?? false,
1478
+ seoTitle: parsed.seoTitle ?? null,
1479
+ seoDescription: parsed.seoDescription ?? null,
1480
+ canonicalUrl: parsed.canonicalUrl ?? null,
1335
1481
  metadata,
1336
1482
  customFieldsetCode: parsed.customFieldsetCode ?? null,
1337
1483
  isConfigurable: parsed.isConfigurable ?? false,
@@ -1551,6 +1697,75 @@ const updateProductCommand: CommandHandler<
1551
1697
  ensureTenantScope(ctx, tenantId);
1552
1698
  ensureOrganizationScope(ctx, organizationId);
1553
1699
  ensureSameScope(record, organizationId, tenantId);
1700
+ // Re-validate range constraints against the merged record state: a partial
1701
+ // update may carry only one half of a (min, max) or (from, until) pair
1702
+ // while the other half is stored — payload-level zod cannot see it.
1703
+ const mergedRangeViolation = (() => {
1704
+ const minOrderQty =
1705
+ parsed.minOrderQty !== undefined
1706
+ ? (parsed.minOrderQty ?? null)
1707
+ : (record.minOrderQty ?? null);
1708
+ const maxOrderQty =
1709
+ parsed.maxOrderQty !== undefined
1710
+ ? (parsed.maxOrderQty ?? null)
1711
+ : (record.maxOrderQty ?? null);
1712
+ if (minOrderQty != null && maxOrderQty != null && maxOrderQty < minOrderQty) {
1713
+ return {
1714
+ field: "maxOrderQty",
1715
+ key: "catalog.products.validation.orderQtyRange",
1716
+ fallback: "Maximum order quantity cannot be lower than the minimum.",
1717
+ };
1718
+ }
1719
+ const launchAt =
1720
+ parsed.launchAt !== undefined
1721
+ ? (parsed.launchAt ?? null)
1722
+ : (record.launchAt ?? null);
1723
+ const endOfLifeAt =
1724
+ parsed.endOfLifeAt !== undefined
1725
+ ? (parsed.endOfLifeAt ?? null)
1726
+ : (record.endOfLifeAt ?? null);
1727
+ if (launchAt && endOfLifeAt && endOfLifeAt < launchAt) {
1728
+ return {
1729
+ field: "endOfLifeAt",
1730
+ key: "catalog.products.validation.lifecycleDateRange",
1731
+ fallback: "End of life cannot be before the launch date.",
1732
+ };
1733
+ }
1734
+ const availableFrom =
1735
+ parsed.availableFrom !== undefined
1736
+ ? (parsed.availableFrom ?? null)
1737
+ : (record.availableFrom ?? null);
1738
+ const availableUntil =
1739
+ parsed.availableUntil !== undefined
1740
+ ? (parsed.availableUntil ?? null)
1741
+ : (record.availableUntil ?? null);
1742
+ if (availableFrom && availableUntil && availableUntil < availableFrom) {
1743
+ return {
1744
+ field: "availableUntil",
1745
+ key: "catalog.products.validation.availabilityDateRange",
1746
+ fallback: "Available until cannot be before available from.",
1747
+ };
1748
+ }
1749
+ return null;
1750
+ })();
1751
+ if (mergedRangeViolation) {
1752
+ const message = translate(
1753
+ mergedRangeViolation.key,
1754
+ mergedRangeViolation.fallback,
1755
+ );
1756
+ throw new CrudHttpError(400, {
1757
+ error: message,
1758
+ fieldErrors: { [mergedRangeViolation.field]: message },
1759
+ details: [
1760
+ {
1761
+ path: [mergedRangeViolation.field],
1762
+ message,
1763
+ code: "invalid",
1764
+ origin: "validation",
1765
+ },
1766
+ ],
1767
+ });
1768
+ }
1554
1769
  const dataEngine = ctx.container.resolve("dataEngine") as DataEngine;
1555
1770
  const lookupEm = em.fork();
1556
1771
  const taxRateProvided =
@@ -1717,6 +1932,55 @@ const updateProductCommand: CommandHandler<
1717
1932
  if (parsed.isConfigurable !== undefined)
1718
1933
  record.isConfigurable = parsed.isConfigurable;
1719
1934
  if (parsed.isActive !== undefined) record.isActive = parsed.isActive;
1935
+ if (parsed.countryOfOriginCode !== undefined)
1936
+ record.countryOfOriginCode = parsed.countryOfOriginCode ?? null;
1937
+ if (parsed.pkwiuCode !== undefined)
1938
+ record.pkwiuCode = parsed.pkwiuCode ?? null;
1939
+ if (parsed.cnCode !== undefined) record.cnCode = parsed.cnCode ?? null;
1940
+ if (parsed.hsCode !== undefined) record.hsCode = parsed.hsCode ?? null;
1941
+ if (parsed.taxClassificationCode !== undefined)
1942
+ record.taxClassificationCode = parsed.taxClassificationCode ?? null;
1943
+ if (parsed.gtuCodes !== undefined)
1944
+ record.gtuCodes = parsed.gtuCodes ?? null;
1945
+ if (parsed.ageMin !== undefined) record.ageMin = parsed.ageMin ?? null;
1946
+ if (parsed.isExciseGood !== undefined)
1947
+ record.isExciseGood = parsed.isExciseGood;
1948
+ if (parsed.exciseCategory !== undefined)
1949
+ record.exciseCategory = parsed.exciseCategory ?? null;
1950
+ if (parsed.requiresPrescription !== undefined)
1951
+ record.requiresPrescription = parsed.requiresPrescription;
1952
+ if (parsed.hazmatClass !== undefined)
1953
+ record.hazmatClass = parsed.hazmatClass ?? null;
1954
+ if (parsed.unNumber !== undefined)
1955
+ record.unNumber = parsed.unNumber ?? null;
1956
+ if (parsed.hazmatPackingGroup !== undefined)
1957
+ record.hazmatPackingGroup = parsed.hazmatPackingGroup ?? null;
1958
+ if (parsed.containsLithiumBattery !== undefined)
1959
+ record.containsLithiumBattery = parsed.containsLithiumBattery;
1960
+ if (parsed.launchAt !== undefined)
1961
+ record.launchAt = parsed.launchAt ?? null;
1962
+ if (parsed.endOfLifeAt !== undefined)
1963
+ record.endOfLifeAt = parsed.endOfLifeAt ?? null;
1964
+ if (parsed.availableFrom !== undefined)
1965
+ record.availableFrom = parsed.availableFrom ?? null;
1966
+ if (parsed.availableUntil !== undefined)
1967
+ record.availableUntil = parsed.availableUntil ?? null;
1968
+ if (parsed.minOrderQty !== undefined)
1969
+ record.minOrderQty = parsed.minOrderQty ?? null;
1970
+ if (parsed.maxOrderQty !== undefined)
1971
+ record.maxOrderQty = parsed.maxOrderQty ?? null;
1972
+ if (parsed.orderQtyIncrement !== undefined)
1973
+ record.orderQtyIncrement = parsed.orderQtyIncrement ?? null;
1974
+ if (parsed.requiresShipping !== undefined)
1975
+ record.requiresShipping = parsed.requiresShipping;
1976
+ if (parsed.isQuoteOnly !== undefined)
1977
+ record.isQuoteOnly = parsed.isQuoteOnly;
1978
+ if (parsed.seoTitle !== undefined)
1979
+ record.seoTitle = parsed.seoTitle ?? null;
1980
+ if (parsed.seoDescription !== undefined)
1981
+ record.seoDescription = parsed.seoDescription ?? null;
1982
+ if (parsed.canonicalUrl !== undefined)
1983
+ record.canonicalUrl = parsed.canonicalUrl ?? null;
1720
1984
  try {
1721
1985
  await withAtomicFlush(
1722
1986
  em,
@@ -1757,6 +2021,19 @@ const updateProductCommand: CommandHandler<
1757
2021
  const after = snapshots.after as ProductSnapshot | undefined;
1758
2022
  if (!before || !after) return null;
1759
2023
  const { translate } = await resolveTranslations();
2024
+ // buildChanges compares with strict !== — array fields need a serialized
2025
+ // comparison or every update would emit a phantom gtuCodes diff.
2026
+ const beforeGtuCodes = (before.gtuCodes ?? []).join(",");
2027
+ const afterGtuCodes = (after.gtuCodes ?? []).join(",");
2028
+ const gtuCodesChange =
2029
+ beforeGtuCodes !== afterGtuCodes
2030
+ ? {
2031
+ gtuCodes: {
2032
+ from: before.gtuCodes ?? null,
2033
+ to: after.gtuCodes ?? null,
2034
+ },
2035
+ }
2036
+ : null;
1760
2037
  return {
1761
2038
  actionLabel: translate(
1762
2039
  "catalog.audit.products.update",
@@ -1766,7 +2043,8 @@ const updateProductCommand: CommandHandler<
1766
2043
  resourceId: before.id,
1767
2044
  tenantId: before.tenantId,
1768
2045
  organizationId: before.organizationId,
1769
- changes: buildChanges(before, after, [
2046
+ changes: {
2047
+ ...buildChanges(before, after, [
1770
2048
  "title",
1771
2049
  "sku",
1772
2050
  "productType",
@@ -1779,7 +2057,34 @@ const updateProductCommand: CommandHandler<
1779
2057
  "unitPriceReferenceUnit",
1780
2058
  "unitPriceBaseQuantity",
1781
2059
  "isActive",
1782
- ]),
2060
+ "countryOfOriginCode",
2061
+ "pkwiuCode",
2062
+ "cnCode",
2063
+ "hsCode",
2064
+ "taxClassificationCode",
2065
+ "ageMin",
2066
+ "isExciseGood",
2067
+ "exciseCategory",
2068
+ "requiresPrescription",
2069
+ "hazmatClass",
2070
+ "unNumber",
2071
+ "hazmatPackingGroup",
2072
+ "containsLithiumBattery",
2073
+ "launchAt",
2074
+ "endOfLifeAt",
2075
+ "availableFrom",
2076
+ "availableUntil",
2077
+ "minOrderQty",
2078
+ "maxOrderQty",
2079
+ "orderQtyIncrement",
2080
+ "requiresShipping",
2081
+ "isQuoteOnly",
2082
+ "seoTitle",
2083
+ "seoDescription",
2084
+ "canonicalUrl",
2085
+ ]),
2086
+ ...gtuCodesChange,
2087
+ },
1783
2088
  snapshotBefore: before,
1784
2089
  snapshotAfter: after,
1785
2090
  payload: {
@@ -25,6 +25,8 @@ import {
25
25
  type VariantCreateInput,
26
26
  type VariantUpdateInput,
27
27
  } from '../data/validators'
28
+ import { isValidGtin, normalizeGtinValue } from '../lib/gtin'
29
+ import type { CatalogGtinType } from '../data/types'
28
30
  import {
29
31
  cloneJson,
30
32
  commandActorScope,
@@ -59,6 +61,8 @@ type VariantSnapshot = {
59
61
  name: string | null
60
62
  sku: string | null
61
63
  barcode: string | null
64
+ gtinType: CatalogGtinType | null
65
+ hsCode: string | null
62
66
  statusEntryId: string | null
63
67
  isDefault: boolean
64
68
  isActive: boolean
@@ -86,6 +90,8 @@ const VARIANT_CHANGE_KEYS = [
86
90
  'name',
87
91
  'sku',
88
92
  'barcode',
93
+ 'gtinType',
94
+ 'hsCode',
89
95
  'statusEntryId',
90
96
  'isDefault',
91
97
  'isActive',
@@ -127,6 +133,8 @@ async function loadVariantSnapshot(
127
133
  name: record.name ?? null,
128
134
  sku: record.sku ?? null,
129
135
  barcode: record.barcode ?? null,
136
+ gtinType: record.gtinType ?? null,
137
+ hsCode: record.hsCode ?? null,
130
138
  statusEntryId: record.statusEntryId ?? null,
131
139
  isDefault: record.isDefault,
132
140
  isActive: record.isActive,
@@ -154,6 +162,8 @@ function variantSeedFromSnapshot(snapshot: VariantSnapshot): Record<string, unkn
154
162
  name: snapshot.name ?? null,
155
163
  sku: snapshot.sku ?? null,
156
164
  barcode: snapshot.barcode ?? null,
165
+ gtinType: snapshot.gtinType ?? null,
166
+ hsCode: snapshot.hsCode ?? null,
157
167
  statusEntryId: snapshot.statusEntryId ?? null,
158
168
  isDefault: snapshot.isDefault,
159
169
  isActive: snapshot.isActive,
@@ -176,6 +186,8 @@ function applyVariantSnapshot(record: CatalogProductVariant, snapshot: VariantSn
176
186
  record.name = snapshot.name ?? null
177
187
  record.sku = snapshot.sku ?? null
178
188
  record.barcode = snapshot.barcode ?? null
189
+ record.gtinType = snapshot.gtinType ?? null
190
+ record.hsCode = snapshot.hsCode ?? null
179
191
  record.statusEntryId = snapshot.statusEntryId ?? null
180
192
  record.isDefault = snapshot.isDefault
181
193
  record.isActive = snapshot.isActive
@@ -592,6 +604,12 @@ const createVariantCommand: CommandHandler<VariantCreateInput, { variantId: stri
592
604
  const resolvedOptionValues =
593
605
  parsed.optionValues ?? (metadataSplit.hadOptionValues ? metadataSplit.optionValues : null)
594
606
 
607
+ const gtinType = parsed.gtinType ?? null
608
+ const barcode =
609
+ gtinType && parsed.barcode
610
+ ? normalizeGtinValue(gtinType, parsed.barcode)
611
+ : (parsed.barcode ?? null)
612
+
595
613
  const now = new Date()
596
614
  const record = em.create(CatalogProductVariant, {
597
615
  organizationId: product.organizationId,
@@ -599,7 +617,9 @@ const createVariantCommand: CommandHandler<VariantCreateInput, { variantId: stri
599
617
  product,
600
618
  name: parsed.name ?? null,
601
619
  sku: parsed.sku ?? null,
602
- barcode: parsed.barcode ?? null,
620
+ barcode,
621
+ gtinType,
622
+ hsCode: parsed.hsCode ?? null,
603
623
  statusEntryId: parsed.statusEntryId ?? null,
604
624
  isDefault: parsed.isDefault ?? false,
605
625
  isActive: parsed.isActive ?? true,
@@ -812,6 +832,27 @@ const updateVariantCommand: CommandHandler<VariantUpdateInput, { variantId: stri
812
832
  ? await resolveVariantTaxRate(em, product, parsed.taxRateId ?? null, parsed.taxRate)
813
833
  : null
814
834
 
835
+ // Re-validate GTIN against the merged record state: a partial update may
836
+ // carry only one of (gtinType, barcode) while the other half is stored.
837
+ let normalizedGtinBarcode: string | null = null
838
+ if (parsed.gtinType !== undefined || parsed.barcode !== undefined) {
839
+ const effectiveGtinType =
840
+ parsed.gtinType !== undefined ? (parsed.gtinType ?? null) : (record.gtinType ?? null)
841
+ if (effectiveGtinType) {
842
+ const effectiveBarcode =
843
+ parsed.barcode !== undefined ? (parsed.barcode ?? null) : (record.barcode ?? null)
844
+ if (!effectiveBarcode || !effectiveBarcode.trim().length) {
845
+ await throwGtinBarcodeRequiredError()
846
+ } else {
847
+ const normalized = normalizeGtinValue(effectiveGtinType, effectiveBarcode)
848
+ if (!isValidGtin(effectiveGtinType, normalized)) {
849
+ await throwInvalidGtinError()
850
+ }
851
+ normalizedGtinBarcode = normalized
852
+ }
853
+ }
854
+ }
855
+
815
856
  let previousDefaultVariantId: string | null = null
816
857
  try {
817
858
  await withAtomicFlush(
@@ -821,6 +862,9 @@ const updateVariantCommand: CommandHandler<VariantUpdateInput, { variantId: stri
821
862
  if (parsed.name !== undefined) record.name = parsed.name ?? null
822
863
  if (parsed.sku !== undefined) record.sku = parsed.sku ?? null
823
864
  if (parsed.barcode !== undefined) record.barcode = parsed.barcode ?? null
865
+ if (parsed.gtinType !== undefined) record.gtinType = parsed.gtinType ?? null
866
+ if (parsed.hsCode !== undefined) record.hsCode = parsed.hsCode ?? null
867
+ if (normalizedGtinBarcode !== null) record.barcode = normalizedGtinBarcode
824
868
  if (parsed.statusEntryId !== undefined) record.statusEntryId = parsed.statusEntryId ?? null
825
869
  if (parsed.isDefault !== undefined) record.isDefault = parsed.isDefault
826
870
  if (parsed.isActive !== undefined) record.isActive = parsed.isActive
@@ -1159,6 +1203,45 @@ async function throwDuplicateVariantSkuError(): Promise<never> {
1159
1203
  })
1160
1204
  }
1161
1205
 
1206
+ async function throwDuplicateVariantGtinError(): Promise<never> {
1207
+ const { translate } = await resolveTranslations()
1208
+ const message = translate(
1209
+ 'catalog.variants.errors.gtinExists',
1210
+ 'Another variant already uses this identifier.',
1211
+ )
1212
+ throw new CrudHttpError(400, {
1213
+ error: message,
1214
+ fieldErrors: { barcode: message },
1215
+ details: [{ path: ['barcode'], message, code: 'duplicate', origin: 'validation' }],
1216
+ })
1217
+ }
1218
+
1219
+ async function throwGtinBarcodeRequiredError(): Promise<never> {
1220
+ const { translate } = await resolveTranslations()
1221
+ const message = translate(
1222
+ 'catalog.variants.validation.gtinBarcodeRequired',
1223
+ 'A barcode value is required when an identifier type is set.',
1224
+ )
1225
+ throw new CrudHttpError(400, {
1226
+ error: message,
1227
+ fieldErrors: { barcode: message },
1228
+ details: [{ path: ['barcode'], message, code: 'invalid', origin: 'validation' }],
1229
+ })
1230
+ }
1231
+
1232
+ async function throwInvalidGtinError(): Promise<never> {
1233
+ const { translate } = await resolveTranslations()
1234
+ const message = translate(
1235
+ 'catalog.variants.validation.gtinChecksum',
1236
+ 'The barcode does not match the selected identifier type.',
1237
+ )
1238
+ throw new CrudHttpError(400, {
1239
+ error: message,
1240
+ fieldErrors: { barcode: message },
1241
+ details: [{ path: ['barcode'], message, code: 'invalid', origin: 'validation' }],
1242
+ })
1243
+ }
1244
+
1162
1245
  async function rethrowVariantUniqueConstraint(error: unknown): Promise<never> {
1163
1246
  if (error instanceof UniqueConstraintViolationException) {
1164
1247
  const constraint = getErrorConstraint(error)
@@ -1169,6 +1252,12 @@ async function rethrowVariantUniqueConstraint(error: unknown): Promise<never> {
1169
1252
  ) {
1170
1253
  await throwDuplicateVariantSkuError()
1171
1254
  }
1255
+ if (
1256
+ constraint === 'catalog_product_variants_gtin_scope_unique' ||
1257
+ message.includes('catalog_product_variants_gtin_scope_unique')
1258
+ ) {
1259
+ await throwDuplicateVariantGtinError()
1260
+ }
1172
1261
  }
1173
1262
  throw error
1174
1263
  }