@open-mercato/core 0.6.6-develop.6089.1.a7ed560528 → 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 (320) 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/messages/commands/actions.js +81 -9
  147. package/dist/modules/messages/commands/actions.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  151. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  153. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  154. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  155. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  156. package/dist/modules/planner/api/access.js +9 -0
  157. package/dist/modules/planner/api/access.js.map +2 -2
  158. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  159. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  160. package/dist/modules/planner/api/availability-weekly.js +22 -1
  161. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  164. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  165. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  167. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  168. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  169. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  172. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  173. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  175. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  178. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  179. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  180. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  181. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  182. package/dist/modules/translations/api/context.js +7 -0
  183. package/dist/modules/translations/api/context.js.map +2 -2
  184. package/dist/modules/translations/api/put/locales.js +33 -1
  185. package/dist/modules/translations/api/put/locales.js.map +2 -2
  186. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  187. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  188. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  189. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  192. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  193. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  194. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  195. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  196. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  197. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  198. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  199. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  200. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  201. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  202. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  203. package/generated/entities/catalog_product/index.ts +26 -0
  204. package/generated/entities/catalog_product_variant/index.ts +2 -0
  205. package/generated/entity-fields-registry.ts +28 -0
  206. package/package.json +7 -7
  207. package/src/modules/catalog/api/products/route.ts +123 -39
  208. package/src/modules/catalog/api/variants/route.ts +4 -0
  209. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  210. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  211. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  212. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  213. package/src/modules/catalog/commands/products.ts +307 -2
  214. package/src/modules/catalog/commands/variants.ts +90 -1
  215. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  216. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  217. package/src/modules/catalog/components/products/productForm.ts +262 -0
  218. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  219. package/src/modules/catalog/data/entities.ts +97 -0
  220. package/src/modules/catalog/data/types.ts +36 -0
  221. package/src/modules/catalog/data/validators.ts +159 -3
  222. package/src/modules/catalog/i18n/de.json +64 -0
  223. package/src/modules/catalog/i18n/en.json +64 -0
  224. package/src/modules/catalog/i18n/es.json +64 -0
  225. package/src/modules/catalog/i18n/pl.json +64 -0
  226. package/src/modules/catalog/lib/gtin.ts +66 -0
  227. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  228. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  229. package/src/modules/catalog/translations.ts +1 -1
  230. package/src/modules/configs/api/cache/route.ts +72 -10
  231. package/src/modules/configs/components/CachePanel.tsx +59 -29
  232. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  233. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  234. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  235. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  236. package/src/modules/customer_accounts/i18n/de.json +1 -1
  237. package/src/modules/customer_accounts/i18n/en.json +1 -1
  238. package/src/modules/customer_accounts/i18n/es.json +1 -1
  239. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  240. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  241. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  242. package/src/modules/customers/cli.ts +1 -0
  243. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  244. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  245. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  246. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  247. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  248. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  249. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  250. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  251. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  252. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  253. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  254. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  255. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  256. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  257. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  258. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  259. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  260. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  261. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  262. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  263. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  264. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  265. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  266. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  267. package/src/modules/customers/components/calendar/types.ts +163 -0
  268. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  269. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  270. package/src/modules/customers/i18n/de.json +195 -0
  271. package/src/modules/customers/i18n/en.json +195 -0
  272. package/src/modules/customers/i18n/es.json +195 -0
  273. package/src/modules/customers/i18n/pl.json +195 -0
  274. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  275. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  276. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  277. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  278. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  279. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  280. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  281. package/src/modules/customers/lib/calendar/range.ts +45 -0
  282. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  283. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  284. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  285. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  286. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  287. package/src/modules/dictionaries/api/route.ts +36 -1
  288. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  289. package/src/modules/messages/commands/actions.ts +100 -7
  290. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  291. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  292. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  293. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  294. package/src/modules/planner/api/access.ts +9 -0
  295. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  296. package/src/modules/planner/api/availability-weekly.ts +22 -1
  297. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  298. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  299. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  300. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  301. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  302. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  303. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  304. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  305. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  306. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  307. package/src/modules/translations/api/context.ts +7 -0
  308. package/src/modules/translations/api/put/locales.ts +35 -1
  309. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  310. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  311. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  312. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  313. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  314. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  315. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  316. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  317. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  318. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  319. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  320. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -157,6 +157,32 @@ export type ProductFormValues = {
157
157
  tags: string[];
158
158
  optionSchemaId?: string | null;
159
159
  updatedAt?: string | null;
160
+ countryOfOriginCode: string;
161
+ pkwiuCode: string;
162
+ cnCode: string;
163
+ hsCode: string;
164
+ taxClassificationCode: string;
165
+ gtuCodes: string[];
166
+ ageMin: string;
167
+ isExciseGood: boolean;
168
+ exciseCategory: string | null;
169
+ requiresPrescription: boolean;
170
+ hazmatClass: string;
171
+ unNumber: string;
172
+ hazmatPackingGroup: string | null;
173
+ containsLithiumBattery: boolean;
174
+ launchAt: string;
175
+ endOfLifeAt: string;
176
+ availableFrom: string;
177
+ availableUntil: string;
178
+ minOrderQty: string;
179
+ maxOrderQty: string;
180
+ orderQtyIncrement: string;
181
+ requiresShipping: boolean;
182
+ isQuoteOnly: boolean;
183
+ seoTitle: string;
184
+ seoDescription: string;
185
+ canonicalUrl: string;
160
186
  };
161
187
 
162
188
  const optionalPositiveNumberInput = z.preprocess((value) => {
@@ -251,6 +277,45 @@ export const productFormSchema = z
251
277
  channelIds: z.array(z.string().uuid()).optional(),
252
278
  tags: z.array(z.string().trim().min(1).max(100)).optional(),
253
279
  optionSchemaId: z.string().uuid().nullable().optional(),
280
+ countryOfOriginCode: z
281
+ .string()
282
+ .trim()
283
+ .regex(/^([A-Za-z]{2})?$/, 'catalog.products.validation.countryCodeInvalid')
284
+ .optional(),
285
+ pkwiuCode: z.string().trim().max(32).optional(),
286
+ cnCode: z.string().trim().max(32).optional(),
287
+ hsCode: z.string().trim().max(32).optional(),
288
+ taxClassificationCode: z.string().trim().max(64).optional(),
289
+ gtuCodes: z.array(z.string()).optional(),
290
+ ageMin: optionalBoundedIntegerInput(0, 120),
291
+ isExciseGood: z.boolean().optional(),
292
+ exciseCategory: z.string().nullable().optional(),
293
+ requiresPrescription: z.boolean().optional(),
294
+ hazmatClass: z.string().trim().max(32).optional(),
295
+ unNumber: z
296
+ .string()
297
+ .trim()
298
+ .regex(/^((?:UN)?[0-9]{4})?$/i, 'catalog.products.validation.unNumberInvalid')
299
+ .optional(),
300
+ hazmatPackingGroup: z.string().nullable().optional(),
301
+ containsLithiumBattery: z.boolean().optional(),
302
+ launchAt: z.string().optional(),
303
+ endOfLifeAt: z.string().optional(),
304
+ availableFrom: z.string().optional(),
305
+ availableUntil: z.string().optional(),
306
+ minOrderQty: optionalBoundedIntegerInput(1, 100000000),
307
+ maxOrderQty: optionalBoundedIntegerInput(1, 100000000),
308
+ orderQtyIncrement: optionalBoundedIntegerInput(1, 100000000),
309
+ requiresShipping: z.boolean().optional(),
310
+ isQuoteOnly: z.boolean().optional(),
311
+ seoTitle: z.string().trim().max(255).optional(),
312
+ seoDescription: z.string().trim().max(1000).optional(),
313
+ canonicalUrl: z
314
+ .string()
315
+ .trim()
316
+ .max(500)
317
+ .regex(/^(https?:\/\/\S+)?$/, 'catalog.products.validation.canonicalUrlInvalid')
318
+ .optional(),
254
319
  })
255
320
  .passthrough()
256
321
  .refine(
@@ -260,12 +325,34 @@ export const productFormSchema = z
260
325
  .refine(
261
326
  (data) => !data.defaultSalesUnit || (data.defaultUnit != null && data.defaultUnit.length > 0),
262
327
  { message: 'catalog.products.validation.baseUnitRequired', path: ['defaultUnit'] }
328
+ )
329
+ .refine(
330
+ (data) =>
331
+ data.minOrderQty === undefined ||
332
+ data.maxOrderQty === undefined ||
333
+ data.maxOrderQty >= data.minOrderQty,
334
+ { message: 'catalog.products.validation.orderQtyRange', path: ['maxOrderQty'] }
335
+ )
336
+ .refine(
337
+ (data) =>
338
+ !data.launchAt ||
339
+ !data.endOfLifeAt ||
340
+ new Date(data.endOfLifeAt) >= new Date(data.launchAt),
341
+ { message: 'catalog.products.validation.lifecycleDateRange', path: ['endOfLifeAt'] }
342
+ )
343
+ .refine(
344
+ (data) =>
345
+ !data.availableFrom ||
346
+ !data.availableUntil ||
347
+ new Date(data.availableUntil) >= new Date(data.availableFrom),
348
+ { message: 'catalog.products.validation.availabilityDateRange', path: ['availableUntil'] }
263
349
  );
264
350
 
265
351
  export const PRODUCT_FORM_STEPS = [
266
352
  "general",
267
353
  "organize",
268
354
  "uom",
355
+ "compliance",
269
356
  "variants",
270
357
  ] as const;
271
358
 
@@ -302,11 +389,186 @@ export const BASE_INITIAL_VALUES: ProductFormValues = {
302
389
  channelIds: [],
303
390
  tags: [],
304
391
  optionSchemaId: null,
392
+ countryOfOriginCode: "",
393
+ pkwiuCode: "",
394
+ cnCode: "",
395
+ hsCode: "",
396
+ taxClassificationCode: "",
397
+ gtuCodes: [],
398
+ ageMin: "",
399
+ isExciseGood: false,
400
+ exciseCategory: null,
401
+ requiresPrescription: false,
402
+ hazmatClass: "",
403
+ unNumber: "",
404
+ hazmatPackingGroup: null,
405
+ containsLithiumBattery: false,
406
+ launchAt: "",
407
+ endOfLifeAt: "",
408
+ availableFrom: "",
409
+ availableUntil: "",
410
+ minOrderQty: "",
411
+ maxOrderQty: "",
412
+ orderQtyIncrement: "",
413
+ requiresShipping: true,
414
+ isQuoteOnly: false,
415
+ seoTitle: "",
416
+ seoDescription: "",
417
+ canonicalUrl: "",
305
418
  };
306
419
 
307
420
  export const isConfigurableProductType = (type: string): boolean =>
308
421
  (CATALOG_CONFIGURABLE_PRODUCT_TYPES as readonly string[]).includes(type);
309
422
 
423
+ const complianceTrimOrNull = (value: string | null | undefined): string | null => {
424
+ const trimmed = typeof value === "string" ? value.trim() : "";
425
+ return trimmed.length ? trimmed : null;
426
+ };
427
+
428
+ const compliancePositiveIntOrNull = (
429
+ value: string | number | null | undefined,
430
+ ): number | null => {
431
+ if (typeof value === "number") {
432
+ return Number.isFinite(value) && Number.isInteger(value) ? value : null;
433
+ }
434
+ const trimmed = typeof value === "string" ? value.trim() : "";
435
+ if (!trimmed.length) return null;
436
+ const numeric = Number(trimmed);
437
+ return Number.isFinite(numeric) && Number.isInteger(numeric) ? numeric : null;
438
+ };
439
+
440
+ const complianceDateOrNull = (value: string | null | undefined): string | null => {
441
+ const trimmed = typeof value === "string" ? value.trim() : "";
442
+ if (!trimmed.length) return null;
443
+ const parsed = new Date(trimmed);
444
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
445
+ };
446
+
447
+ export const buildComplianceProductPayload = (
448
+ values: ProductFormValues,
449
+ ): Record<string, unknown> => ({
450
+ countryOfOriginCode: complianceTrimOrNull(values.countryOfOriginCode),
451
+ pkwiuCode: complianceTrimOrNull(values.pkwiuCode),
452
+ cnCode: complianceTrimOrNull(values.cnCode),
453
+ hsCode: complianceTrimOrNull(values.hsCode),
454
+ taxClassificationCode: complianceTrimOrNull(values.taxClassificationCode),
455
+ gtuCodes:
456
+ Array.isArray(values.gtuCodes) && values.gtuCodes.length
457
+ ? values.gtuCodes
458
+ : null,
459
+ ageMin: compliancePositiveIntOrNull(values.ageMin),
460
+ isExciseGood: values.isExciseGood === true,
461
+ exciseCategory: complianceTrimOrNull(values.exciseCategory),
462
+ requiresPrescription: values.requiresPrescription === true,
463
+ hazmatClass: complianceTrimOrNull(values.hazmatClass),
464
+ unNumber: complianceTrimOrNull(values.unNumber),
465
+ hazmatPackingGroup: complianceTrimOrNull(values.hazmatPackingGroup),
466
+ containsLithiumBattery: values.containsLithiumBattery === true,
467
+ launchAt: complianceDateOrNull(values.launchAt),
468
+ endOfLifeAt: complianceDateOrNull(values.endOfLifeAt),
469
+ availableFrom: complianceDateOrNull(values.availableFrom),
470
+ availableUntil: complianceDateOrNull(values.availableUntil),
471
+ minOrderQty: compliancePositiveIntOrNull(values.minOrderQty),
472
+ maxOrderQty: compliancePositiveIntOrNull(values.maxOrderQty),
473
+ orderQtyIncrement: compliancePositiveIntOrNull(values.orderQtyIncrement),
474
+ requiresShipping: values.requiresShipping !== false,
475
+ isQuoteOnly: values.isQuoteOnly === true,
476
+ seoTitle: complianceTrimOrNull(values.seoTitle),
477
+ seoDescription: complianceTrimOrNull(values.seoDescription),
478
+ canonicalUrl: complianceTrimOrNull(values.canonicalUrl),
479
+ });
480
+
481
+ export type ComplianceFormValues = Pick<
482
+ ProductFormValues,
483
+ | "countryOfOriginCode"
484
+ | "pkwiuCode"
485
+ | "cnCode"
486
+ | "hsCode"
487
+ | "taxClassificationCode"
488
+ | "gtuCodes"
489
+ | "ageMin"
490
+ | "isExciseGood"
491
+ | "exciseCategory"
492
+ | "requiresPrescription"
493
+ | "hazmatClass"
494
+ | "unNumber"
495
+ | "hazmatPackingGroup"
496
+ | "containsLithiumBattery"
497
+ | "launchAt"
498
+ | "endOfLifeAt"
499
+ | "availableFrom"
500
+ | "availableUntil"
501
+ | "minOrderQty"
502
+ | "maxOrderQty"
503
+ | "orderQtyIncrement"
504
+ | "requiresShipping"
505
+ | "isQuoteOnly"
506
+ | "seoTitle"
507
+ | "seoDescription"
508
+ | "canonicalUrl"
509
+ >;
510
+
511
+ export const complianceFormValuesFromApiRecord = (
512
+ record: Record<string, unknown>,
513
+ ): ComplianceFormValues => {
514
+ const pick = (...keys: string[]): unknown => {
515
+ for (const key of keys) {
516
+ if (record[key] !== undefined && record[key] !== null) return record[key];
517
+ }
518
+ return null;
519
+ };
520
+ const str = (value: unknown): string => (typeof value === "string" ? value : "");
521
+ const strOrNull = (value: unknown): string | null =>
522
+ typeof value === "string" && value.length ? value : null;
523
+ const numStr = (value: unknown): string =>
524
+ typeof value === "number" && Number.isFinite(value) ? String(value) : "";
525
+ const boolWithDefault = (value: unknown, fallback: boolean): boolean =>
526
+ typeof value === "boolean" ? value : fallback;
527
+ const dateInput = (value: unknown): string =>
528
+ typeof value === "string" && value.length >= 10 ? value.slice(0, 10) : "";
529
+ const stringArray = (value: unknown): string[] =>
530
+ Array.isArray(value)
531
+ ? value.filter((entry): entry is string => typeof entry === "string")
532
+ : [];
533
+ return {
534
+ countryOfOriginCode: str(pick("country_of_origin_code", "countryOfOriginCode")),
535
+ pkwiuCode: str(pick("pkwiu_code", "pkwiuCode")),
536
+ cnCode: str(pick("cn_code", "cnCode")),
537
+ hsCode: str(pick("hs_code", "hsCode")),
538
+ taxClassificationCode: str(pick("tax_classification_code", "taxClassificationCode")),
539
+ gtuCodes: stringArray(pick("gtu_codes", "gtuCodes")),
540
+ ageMin: numStr(pick("age_min", "ageMin")),
541
+ isExciseGood: boolWithDefault(pick("is_excise_good", "isExciseGood"), false),
542
+ exciseCategory: strOrNull(pick("excise_category", "exciseCategory")),
543
+ requiresPrescription: boolWithDefault(
544
+ pick("requires_prescription", "requiresPrescription"),
545
+ false,
546
+ ),
547
+ hazmatClass: str(pick("hazmat_class", "hazmatClass")),
548
+ unNumber: str(pick("un_number", "unNumber")),
549
+ hazmatPackingGroup: strOrNull(pick("hazmat_packing_group", "hazmatPackingGroup")),
550
+ containsLithiumBattery: boolWithDefault(
551
+ pick("contains_lithium_battery", "containsLithiumBattery"),
552
+ false,
553
+ ),
554
+ launchAt: dateInput(pick("launch_at", "launchAt")),
555
+ endOfLifeAt: dateInput(pick("end_of_life_at", "endOfLifeAt")),
556
+ availableFrom: dateInput(pick("available_from", "availableFrom")),
557
+ availableUntil: dateInput(pick("available_until", "availableUntil")),
558
+ minOrderQty: numStr(pick("min_order_qty", "minOrderQty")),
559
+ maxOrderQty: numStr(pick("max_order_qty", "maxOrderQty")),
560
+ orderQtyIncrement: numStr(pick("order_qty_increment", "orderQtyIncrement")),
561
+ requiresShipping: boolWithDefault(
562
+ pick("requires_shipping", "requiresShipping"),
563
+ true,
564
+ ),
565
+ isQuoteOnly: boolWithDefault(pick("is_quote_only", "isQuoteOnly"), false),
566
+ seoTitle: str(pick("seo_title", "seoTitle")),
567
+ seoDescription: str(pick("seo_description", "seoDescription")),
568
+ canonicalUrl: str(pick("canonical_url", "canonicalUrl")),
569
+ };
570
+ };
571
+
310
572
  export const createInitialProductFormValues = (): ProductFormValues => ({
311
573
  ...BASE_INITIAL_VALUES,
312
574
  mediaDraftId: createLocalId(),
@@ -26,6 +26,8 @@ export type VariantFormValues = {
26
26
  name: string
27
27
  sku: string
28
28
  barcode: string
29
+ gtinType: string | null
30
+ hsCode: string
29
31
  isDefault: boolean
30
32
  isActive: boolean
31
33
  optionValues: Record<string, string>
@@ -44,6 +46,8 @@ export const VARIANT_BASE_VALUES: VariantFormValues = {
44
46
  name: '',
45
47
  sku: '',
46
48
  barcode: '',
49
+ gtinType: null,
50
+ hsCode: '',
47
51
  isDefault: false,
48
52
  isActive: true,
49
53
  optionValues: {},
@@ -1,6 +1,9 @@
1
1
  import { Collection, OptionalProps } from '@mikro-orm/core'
2
2
  import { Entity, Index, ManyToOne, OneToMany, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy'
3
3
  import type {
4
+ CatalogExciseCategory,
5
+ CatalogGtinType,
6
+ CatalogHazmatPackingGroup,
4
7
  CatalogPriceDisplayMode,
5
8
  CatalogProductOptionSchema,
6
9
  CatalogProductRelationType,
@@ -70,6 +73,11 @@ export class CatalogProduct {
70
73
  | 'uomRoundingScale'
71
74
  | 'uomRoundingMode'
72
75
  | 'unitPriceEnabled'
76
+ | 'isExciseGood'
77
+ | 'requiresPrescription'
78
+ | 'containsLithiumBattery'
79
+ | 'requiresShipping'
80
+ | 'isQuoteOnly'
73
81
 
74
82
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
75
83
  id!: string
@@ -154,6 +162,84 @@ export class CatalogProduct {
154
162
  unit?: string | null
155
163
  } | null
156
164
 
165
+ @Property({ name: 'country_of_origin_code', type: 'text', nullable: true })
166
+ countryOfOriginCode?: string | null
167
+
168
+ @Property({ name: 'pkwiu_code', type: 'text', nullable: true })
169
+ pkwiuCode?: string | null
170
+
171
+ @Property({ name: 'cn_code', type: 'text', nullable: true })
172
+ cnCode?: string | null
173
+
174
+ @Property({ name: 'hs_code', type: 'text', nullable: true })
175
+ hsCode?: string | null
176
+
177
+ @Property({ name: 'tax_classification_code', type: 'text', nullable: true })
178
+ taxClassificationCode?: string | null
179
+
180
+ @Property({ name: 'gtu_codes', type: 'text[]', nullable: true })
181
+ gtuCodes?: string[] | null
182
+
183
+ @Property({ name: 'age_min', type: 'smallint', nullable: true })
184
+ ageMin?: number | null
185
+
186
+ @Property({ name: 'is_excise_good', type: 'boolean', default: false })
187
+ isExciseGood: boolean = false
188
+
189
+ @Property({ name: 'excise_category', type: 'text', nullable: true })
190
+ exciseCategory?: CatalogExciseCategory | null
191
+
192
+ @Property({ name: 'requires_prescription', type: 'boolean', default: false })
193
+ requiresPrescription: boolean = false
194
+
195
+ @Property({ name: 'hazmat_class', type: 'text', nullable: true })
196
+ hazmatClass?: string | null
197
+
198
+ @Property({ name: 'un_number', type: 'text', nullable: true })
199
+ unNumber?: string | null
200
+
201
+ @Property({ name: 'hazmat_packing_group', type: 'text', nullable: true })
202
+ hazmatPackingGroup?: CatalogHazmatPackingGroup | null
203
+
204
+ @Property({ name: 'contains_lithium_battery', type: 'boolean', default: false })
205
+ containsLithiumBattery: boolean = false
206
+
207
+ @Property({ name: 'launch_at', type: Date, nullable: true })
208
+ launchAt?: Date | null
209
+
210
+ @Property({ name: 'end_of_life_at', type: Date, nullable: true })
211
+ endOfLifeAt?: Date | null
212
+
213
+ @Property({ name: 'available_from', type: Date, nullable: true })
214
+ availableFrom?: Date | null
215
+
216
+ @Property({ name: 'available_until', type: Date, nullable: true })
217
+ availableUntil?: Date | null
218
+
219
+ @Property({ name: 'min_order_qty', type: 'integer', nullable: true })
220
+ minOrderQty?: number | null
221
+
222
+ @Property({ name: 'max_order_qty', type: 'integer', nullable: true })
223
+ maxOrderQty?: number | null
224
+
225
+ @Property({ name: 'order_qty_increment', type: 'integer', nullable: true })
226
+ orderQtyIncrement?: number | null
227
+
228
+ @Property({ name: 'requires_shipping', type: 'boolean', default: true })
229
+ requiresShipping: boolean = true
230
+
231
+ @Property({ name: 'is_quote_only', type: 'boolean', default: false })
232
+ isQuoteOnly: boolean = false
233
+
234
+ @Property({ name: 'seo_title', type: 'text', nullable: true })
235
+ seoTitle?: string | null
236
+
237
+ @Property({ name: 'seo_description', type: 'text', nullable: true })
238
+ seoDescription?: string | null
239
+
240
+ @Property({ name: 'canonical_url', type: 'text', nullable: true })
241
+ canonicalUrl?: string | null
242
+
157
243
  @Property({ name: 'metadata', type: 'jsonb', nullable: true })
158
244
  metadata?: Record<string, unknown> | null
159
245
 
@@ -468,6 +554,11 @@ export class CatalogOffer {
468
554
  name: 'catalog_product_variants_sku_unique',
469
555
  properties: ['organizationId', 'tenantId', 'sku'],
470
556
  })
557
+ @Index({
558
+ name: 'catalog_product_variants_gtin_scope_unique',
559
+ expression:
560
+ `create unique index "catalog_product_variants_gtin_scope_unique" on "catalog_product_variants" ("tenant_id", "organization_id", "gtin_type", "barcode") where deleted_at is null and gtin_type is not null and barcode is not null`,
561
+ })
471
562
  export class CatalogProductVariant {
472
563
  [OptionalProps]?: 'createdAt' | 'updatedAt' | 'deletedAt'
473
564
 
@@ -492,6 +583,12 @@ export class CatalogProductVariant {
492
583
  @Property({ type: 'text', nullable: true })
493
584
  barcode?: string | null
494
585
 
586
+ @Property({ name: 'gtin_type', type: 'text', nullable: true })
587
+ gtinType?: CatalogGtinType | null
588
+
589
+ @Property({ name: 'hs_code', type: 'text', nullable: true })
590
+ hsCode?: string | null
591
+
495
592
  @Property({ type: 'text', nullable: true })
496
593
  statusEntryId?: string | null
497
594
 
@@ -19,6 +19,42 @@ export const CATALOG_PRODUCT_RELATION_TYPES = ['bundle', 'grouped'] as const
19
19
 
20
20
  export type CatalogProductRelationType = (typeof CATALOG_PRODUCT_RELATION_TYPES)[number]
21
21
 
22
+ export const CATALOG_GTU_CODES = [
23
+ 'GTU_01',
24
+ 'GTU_02',
25
+ 'GTU_03',
26
+ 'GTU_04',
27
+ 'GTU_05',
28
+ 'GTU_06',
29
+ 'GTU_07',
30
+ 'GTU_08',
31
+ 'GTU_09',
32
+ 'GTU_10',
33
+ 'GTU_11',
34
+ 'GTU_12',
35
+ 'GTU_13',
36
+ ] as const
37
+
38
+ export type CatalogGtuCode = (typeof CATALOG_GTU_CODES)[number]
39
+
40
+ export const CATALOG_EXCISE_CATEGORIES = [
41
+ 'alcohol',
42
+ 'tobacco',
43
+ 'energy_drink',
44
+ 'fuel',
45
+ 'other',
46
+ ] as const
47
+
48
+ export type CatalogExciseCategory = (typeof CATALOG_EXCISE_CATEGORIES)[number]
49
+
50
+ export const CATALOG_HAZMAT_PACKING_GROUPS = ['I', 'II', 'III'] as const
51
+
52
+ export type CatalogHazmatPackingGroup = (typeof CATALOG_HAZMAT_PACKING_GROUPS)[number]
53
+
54
+ export const CATALOG_GTIN_TYPES = ['ean13', 'ean8', 'upc', 'isbn', 'asin', 'mpn'] as const
55
+
56
+ export type CatalogGtinType = (typeof CATALOG_GTIN_TYPES)[number]
57
+
22
58
  export type CatalogProductOptionChoice = {
23
59
  code: string
24
60
  label?: string | null
@@ -1,5 +1,13 @@
1
1
  import { z } from 'zod'
2
- import { CATALOG_PRICE_DISPLAY_MODES, CATALOG_PRODUCT_TYPES } from './types'
2
+ import {
3
+ CATALOG_EXCISE_CATEGORIES,
4
+ CATALOG_GTIN_TYPES,
5
+ CATALOG_GTU_CODES,
6
+ CATALOG_HAZMAT_PACKING_GROUPS,
7
+ CATALOG_PRICE_DISPLAY_MODES,
8
+ CATALOG_PRODUCT_TYPES,
9
+ } from './types'
10
+ import { isValidGtin, normalizeGtinValue } from '../lib/gtin'
3
11
  import { REFERENCE_UNIT_CODES } from '../lib/unitCodes'
4
12
  import {
5
13
  getCatalogPriceAmountValidationMessage,
@@ -214,14 +222,111 @@ const productBaseSchema = scoped.extend({
214
222
  customFieldsetCode: slugSchema.nullable().optional(),
215
223
  isConfigurable: z.boolean().optional(),
216
224
  isActive: z.boolean().optional(),
225
+ countryOfOriginCode: z
226
+ .string()
227
+ .trim()
228
+ .toUpperCase()
229
+ .regex(/^[A-Z]{2}$/, 'catalog.products.validation.countryCodeInvalid')
230
+ .nullable()
231
+ .optional(),
232
+ pkwiuCode: z.string().trim().min(1).max(32).nullable().optional(),
233
+ cnCode: z.string().trim().min(1).max(32).nullable().optional(),
234
+ hsCode: z.string().trim().min(1).max(32).nullable().optional(),
235
+ taxClassificationCode: z.string().trim().min(1).max(64).nullable().optional(),
236
+ gtuCodes: z
237
+ .array(z.enum(CATALOG_GTU_CODES))
238
+ .max(13)
239
+ .transform((codes) => Array.from(new Set(codes)).sort())
240
+ .nullable()
241
+ .optional(),
242
+ ageMin: z.coerce.number().int().min(0).max(120).nullable().optional(),
243
+ isExciseGood: z.boolean().optional(),
244
+ exciseCategory: z.enum(CATALOG_EXCISE_CATEGORIES).nullable().optional(),
245
+ requiresPrescription: z.boolean().optional(),
246
+ hazmatClass: z.string().trim().min(1).max(32).nullable().optional(),
247
+ unNumber: z
248
+ .string()
249
+ .trim()
250
+ .regex(/^(?:UN)?[0-9]{4}$/i, 'catalog.products.validation.unNumberInvalid')
251
+ .transform((value) => {
252
+ const upper = value.toUpperCase()
253
+ return upper.startsWith('UN') ? upper : `UN${upper}`
254
+ })
255
+ .nullable()
256
+ .optional(),
257
+ hazmatPackingGroup: z.enum(CATALOG_HAZMAT_PACKING_GROUPS).nullable().optional(),
258
+ containsLithiumBattery: z.boolean().optional(),
259
+ launchAt: z.coerce.date().nullable().optional(),
260
+ endOfLifeAt: z.coerce.date().nullable().optional(),
261
+ availableFrom: z.coerce.date().nullable().optional(),
262
+ availableUntil: z.coerce.date().nullable().optional(),
263
+ minOrderQty: z.coerce.number().int().min(1).nullable().optional(),
264
+ maxOrderQty: z.coerce.number().int().min(1).nullable().optional(),
265
+ orderQtyIncrement: z.coerce.number().int().min(1).nullable().optional(),
266
+ requiresShipping: z.boolean().optional(),
267
+ isQuoteOnly: z.boolean().optional(),
268
+ seoTitle: z.string().trim().min(1).max(255).nullable().optional(),
269
+ seoDescription: z.string().trim().min(1).max(1000).nullable().optional(),
270
+ canonicalUrl: z
271
+ .string()
272
+ .trim()
273
+ .max(500)
274
+ .regex(/^https?:\/\/\S+$/, 'catalog.products.validation.canonicalUrlInvalid')
275
+ .nullable()
276
+ .optional(),
217
277
  metadata: metadataSchema,
218
278
  offers: z.array(offerInputSchema.omit({ id: true })).optional(),
219
279
  categoryIds: z.array(uuid()).max(100).optional(),
220
280
  tags: z.array(tagLabelSchema).max(100).optional(),
221
281
  })
222
282
 
283
+ // Cross-field checks chained on the exported schemas only — refining the base
284
+ // schema itself would break `.partial()` in the update schema.
285
+ const productComplianceCrossFieldRefinement = (
286
+ input: {
287
+ minOrderQty?: number | null
288
+ maxOrderQty?: number | null
289
+ launchAt?: Date | null
290
+ endOfLifeAt?: Date | null
291
+ availableFrom?: Date | null
292
+ availableUntil?: Date | null
293
+ },
294
+ ctx: z.RefinementCtx,
295
+ ) => {
296
+ if (
297
+ input.minOrderQty != null &&
298
+ input.maxOrderQty != null &&
299
+ input.maxOrderQty < input.minOrderQty
300
+ ) {
301
+ ctx.addIssue({
302
+ code: z.ZodIssueCode.custom,
303
+ path: ['maxOrderQty'],
304
+ message: 'catalog.products.validation.orderQtyRange',
305
+ })
306
+ }
307
+ if (input.launchAt && input.endOfLifeAt && input.endOfLifeAt < input.launchAt) {
308
+ ctx.addIssue({
309
+ code: z.ZodIssueCode.custom,
310
+ path: ['endOfLifeAt'],
311
+ message: 'catalog.products.validation.lifecycleDateRange',
312
+ })
313
+ }
314
+ if (
315
+ input.availableFrom &&
316
+ input.availableUntil &&
317
+ input.availableUntil < input.availableFrom
318
+ ) {
319
+ ctx.addIssue({
320
+ code: z.ZodIssueCode.custom,
321
+ path: ['availableUntil'],
322
+ message: 'catalog.products.validation.availabilityDateRange',
323
+ })
324
+ }
325
+ }
326
+
223
327
  export const productCreateSchema = productBaseSchema
224
328
  .superRefine(productUomCrossFieldRefinement)
329
+ .superRefine(productComplianceCrossFieldRefinement)
225
330
 
226
331
  export const productUpdateSchema = z
227
332
  .object({
@@ -232,8 +337,10 @@ export const productUpdateSchema = z
232
337
  productType: productTypeSchema.optional(),
233
338
  })
234
339
  .superRefine(productUomCrossFieldRefinement)
340
+ .superRefine(productComplianceCrossFieldRefinement)
235
341
 
236
- export const variantCreateSchema = scoped.extend({
342
+ // Base schema without refinements (used for .partial() in update schema)
343
+ const variantBaseSchema = scoped.extend({
237
344
  productId: uuid(),
238
345
  name: z.string().trim().max(255).optional(),
239
346
  sku: z
@@ -243,6 +350,8 @@ export const variantCreateSchema = scoped.extend({
243
350
  .max(191)
244
351
  .optional(),
245
352
  barcode: z.string().trim().max(191).optional(),
353
+ gtinType: z.enum(CATALOG_GTIN_TYPES).nullable().optional(),
354
+ hsCode: z.string().trim().min(1).max(32).nullable().optional(),
246
355
  statusEntryId: uuid().optional(),
247
356
  isDefault: z.boolean().optional(),
248
357
  isActive: z.boolean().optional(),
@@ -265,11 +374,58 @@ export const variantCreateSchema = scoped.extend({
265
374
  customFieldsetCode: slugSchema.nullable().optional(),
266
375
  })
267
376
 
377
+ // Payload-level GTIN check. Partial updates carrying only one of
378
+ // (gtinType, barcode) are re-validated against the merged record state in the
379
+ // catalog.variants.update command — zod cannot see the stored half here.
380
+ const variantGtinRefinement =
381
+ (mode: 'create' | 'update') =>
382
+ (
383
+ input: {
384
+ gtinType?: (typeof CATALOG_GTIN_TYPES)[number] | null
385
+ barcode?: string
386
+ },
387
+ ctx: z.RefinementCtx,
388
+ ) => {
389
+ const gtinType = input.gtinType
390
+ if (!gtinType) return
391
+ const barcode = input.barcode
392
+ if (barcode === undefined) {
393
+ if (mode === 'create') {
394
+ ctx.addIssue({
395
+ code: z.ZodIssueCode.custom,
396
+ path: ['barcode'],
397
+ message: 'catalog.variants.validation.gtinBarcodeRequired',
398
+ })
399
+ }
400
+ return
401
+ }
402
+ if (!barcode.trim().length) {
403
+ ctx.addIssue({
404
+ code: z.ZodIssueCode.custom,
405
+ path: ['barcode'],
406
+ message: 'catalog.variants.validation.gtinBarcodeRequired',
407
+ })
408
+ return
409
+ }
410
+ if (!isValidGtin(gtinType, normalizeGtinValue(gtinType, barcode))) {
411
+ ctx.addIssue({
412
+ code: z.ZodIssueCode.custom,
413
+ path: ['barcode'],
414
+ message: 'catalog.variants.validation.gtinChecksum',
415
+ })
416
+ }
417
+ }
418
+
419
+ export const variantCreateSchema = variantBaseSchema.superRefine(
420
+ variantGtinRefinement('create'),
421
+ )
422
+
268
423
  export const variantUpdateSchema = z
269
424
  .object({
270
425
  id: uuid(),
271
426
  })
272
- .merge(variantCreateSchema.partial())
427
+ .merge(variantBaseSchema.partial())
428
+ .superRefine(variantGtinRefinement('update'))
273
429
 
274
430
  export const optionSchemaTemplateCreateSchema = scoped.extend({
275
431
  name: z.string().trim().min(1).max(255),