@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6126.1.0b9392fdae

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
@@ -0,0 +1,45 @@
1
+ import { addDays } from 'date-fns/addDays'
2
+ import { addMonths } from 'date-fns/addMonths'
3
+ import { addWeeks } from 'date-fns/addWeeks'
4
+ import { endOfDay } from 'date-fns/endOfDay'
5
+ import { endOfMonth } from 'date-fns/endOfMonth'
6
+ import { endOfWeek } from 'date-fns/endOfWeek'
7
+ import { startOfDay } from 'date-fns/startOfDay'
8
+ import { startOfMonth } from 'date-fns/startOfMonth'
9
+ import { startOfWeek } from 'date-fns/startOfWeek'
10
+ import type { CalendarRange, CalendarView } from '../../components/calendar/types'
11
+
12
+ const MONDAY_WEEK = { weekStartsOn: 1 as const }
13
+
14
+ export function getVisibleRange(view: CalendarView, anchor: Date, agendaHorizonDays: number): CalendarRange {
15
+ switch (view) {
16
+ case 'day':
17
+ return { from: startOfDay(anchor), to: endOfDay(anchor) }
18
+ case 'week':
19
+ return { from: startOfWeek(anchor, MONDAY_WEEK), to: endOfWeek(anchor, MONDAY_WEEK) }
20
+ case 'month':
21
+ return {
22
+ from: startOfWeek(startOfMonth(anchor), MONDAY_WEEK),
23
+ to: endOfWeek(endOfMonth(anchor), MONDAY_WEEK),
24
+ }
25
+ case 'agenda':
26
+ return { from: startOfDay(anchor), to: endOfDay(addDays(anchor, agendaHorizonDays)) }
27
+ }
28
+ }
29
+
30
+ export function getFetchWindow(range: CalendarRange): CalendarRange {
31
+ return { from: addDays(range.from, -1), to: range.to }
32
+ }
33
+
34
+ export function shiftAnchor(view: CalendarView, anchor: Date, direction: 1 | -1): Date {
35
+ switch (view) {
36
+ case 'day':
37
+ return addDays(anchor, direction)
38
+ case 'week':
39
+ return addWeeks(anchor, direction)
40
+ case 'month':
41
+ return addMonths(anchor, direction)
42
+ case 'agenda':
43
+ return addDays(anchor, direction * 7)
44
+ }
45
+ }
@@ -0,0 +1,137 @@
1
+ import { addDays } from 'date-fns/addDays'
2
+ import type { CalendarItem, CalendarRange } from '../../components/calendar/types'
3
+
4
+ const MAX_OCCURRENCES_PER_WINDOW = 100
5
+
6
+ const WEEKDAY_TOKENS = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'] as const
7
+
8
+ const UNTIL_PATTERN = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/
9
+
10
+ export type ParsedRecurrenceRule = {
11
+ freq: 'DAILY' | 'WEEKLY'
12
+ byDay: number[] | null
13
+ count: number | null
14
+ until: Date | null
15
+ }
16
+
17
+ function parseUntil(value: string): Date | null {
18
+ const match = UNTIL_PATTERN.exec(value)
19
+ if (!match) return null
20
+ const [, year, month, day, hours, minutes, seconds] = match
21
+ const parsed = new Date(
22
+ Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes), Number(seconds)),
23
+ )
24
+ return Number.isNaN(parsed.getTime()) ? null : parsed
25
+ }
26
+
27
+ export function parseRecurrenceRule(rule: string): ParsedRecurrenceRule | null {
28
+ const parts = rule
29
+ .split(';')
30
+ .map((part) => part.trim())
31
+ .filter((part) => part.length > 0)
32
+ if (parts.length === 0) return null
33
+
34
+ let freq: ParsedRecurrenceRule['freq'] | null = null
35
+ let byDay: number[] | null = null
36
+ let count: number | null = null
37
+ let until: Date | null = null
38
+
39
+ for (const part of parts) {
40
+ const separatorIndex = part.indexOf('=')
41
+ if (separatorIndex <= 0) return null
42
+ const key = part.slice(0, separatorIndex).toUpperCase()
43
+ const value = part.slice(separatorIndex + 1)
44
+ if (key === 'FREQ') {
45
+ if (value !== 'DAILY' && value !== 'WEEKLY') return null
46
+ freq = value
47
+ } else if (key === 'BYDAY') {
48
+ const tokens = value
49
+ .split(',')
50
+ .map((token) => token.trim().toUpperCase())
51
+ .filter((token) => token.length > 0)
52
+ if (tokens.length === 0) return null
53
+ const weekdays: number[] = []
54
+ for (const token of tokens) {
55
+ const weekday = WEEKDAY_TOKENS.indexOf(token as (typeof WEEKDAY_TOKENS)[number])
56
+ if (weekday === -1) return null
57
+ weekdays.push(weekday)
58
+ }
59
+ byDay = weekdays
60
+ } else if (key === 'COUNT') {
61
+ const parsedCount = Number(value)
62
+ if (!Number.isInteger(parsedCount) || parsedCount < 1) return null
63
+ count = parsedCount
64
+ } else if (key === 'UNTIL') {
65
+ const parsedUntil = parseUntil(value)
66
+ if (!parsedUntil) return null
67
+ until = parsedUntil
68
+ } else {
69
+ return null
70
+ }
71
+ }
72
+
73
+ if (!freq) return null
74
+ return { freq, byDay, count, until }
75
+ }
76
+
77
+ function parseRecurrenceEndTime(rawRecurrenceEnd: string | null | undefined): number | null {
78
+ if (typeof rawRecurrenceEnd !== 'string' || rawRecurrenceEnd.length === 0) return null
79
+ const parsed = new Date(rawRecurrenceEnd)
80
+ if (Number.isNaN(parsed.getTime())) return null
81
+ const endOfDayLocal = new Date(
82
+ parsed.getUTCFullYear(),
83
+ parsed.getUTCMonth(),
84
+ parsed.getUTCDate(),
85
+ 23,
86
+ 59,
87
+ 59,
88
+ 999,
89
+ )
90
+ return endOfDayLocal.getTime()
91
+ }
92
+
93
+ function occursOn(date: Date, rule: ParsedRecurrenceRule, seriesStartWeekday: number): boolean {
94
+ if (rule.freq === 'DAILY') return true
95
+ const allowedWeekdays = rule.byDay ?? [seriesStartWeekday]
96
+ return allowedWeekdays.includes(date.getDay())
97
+ }
98
+
99
+ export function expandOccurrences(item: CalendarItem, range: CalendarRange): CalendarItem[] {
100
+ const rawRule = item.raw.recurrenceRule
101
+ if (typeof rawRule !== 'string' || rawRule.trim().length === 0) return [item]
102
+ const rule = parseRecurrenceRule(rawRule)
103
+ if (!rule) return [item]
104
+
105
+ const durationMs = item.end.getTime() - item.start.getTime()
106
+ const recurrenceEndTime = parseRecurrenceEndTime(item.raw.recurrenceEnd)
107
+ const untilTime = rule.until ? rule.until.getTime() : null
108
+ const seriesStartWeekday = item.start.getDay()
109
+
110
+ const occurrences: CalendarItem[] = []
111
+ let occurrenceIndex = 0
112
+ let cursor = new Date(item.start)
113
+
114
+ while (cursor.getTime() <= range.to.getTime()) {
115
+ if (untilTime !== null && cursor.getTime() > untilTime) break
116
+ if (recurrenceEndTime !== null && cursor.getTime() > recurrenceEndTime) break
117
+ if (occursOn(cursor, rule, seriesStartWeekday)) {
118
+ if (rule.count !== null && occurrenceIndex >= rule.count) break
119
+ const occurrenceStart = new Date(cursor)
120
+ const occurrenceEnd = new Date(occurrenceStart.getTime() + durationMs)
121
+ if (occurrenceStart.getTime() <= range.to.getTime() && occurrenceEnd.getTime() > range.from.getTime()) {
122
+ occurrences.push({
123
+ ...item,
124
+ id: `${item.id}:${occurrenceIndex}`,
125
+ start: occurrenceStart,
126
+ end: occurrenceEnd,
127
+ isRecurringOccurrence: true,
128
+ })
129
+ if (occurrences.length >= MAX_OCCURRENCES_PER_WINDOW) break
130
+ }
131
+ occurrenceIndex += 1
132
+ }
133
+ cursor = addDays(cursor, 1)
134
+ }
135
+
136
+ return occurrences
137
+ }
@@ -145,12 +145,14 @@ export class WidgetDataService {
145
145
  }
146
146
  }
147
147
 
148
- const mainResult = await this.executeQuery(request, dateRangeResolved)
149
-
150
- let comparisonResult: { value: number | null; data: WidgetDataItem[] } | undefined
151
- if (comparisonRange && request.dateRange) {
152
- comparisonResult = await this.executeQuery(request, comparisonRange)
153
- }
148
+ const shouldFetchComparison = Boolean(comparisonRange && request.dateRange)
149
+
150
+ const [mainResult, comparisonResult] = await Promise.all([
151
+ this.executeQuery(request, dateRangeResolved),
152
+ shouldFetchComparison && comparisonRange
153
+ ? this.executeQuery(request, comparisonRange)
154
+ : Promise.resolve<{ value: number | null; data: WidgetDataItem[] } | undefined>(undefined),
155
+ ])
154
156
 
155
157
  const response: WidgetDataResponse = {
156
158
  value: mainResult.value,
@@ -1,10 +1,14 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
4
- import { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'
4
+ import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
5
5
  import { updateDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'
6
6
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
7
  import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
8
+ import {
9
+ runCrudMutationGuardAfterSuccess,
10
+ validateCrudMutationGuard,
11
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
8
12
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
9
13
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
10
14
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -80,6 +84,21 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
80
84
  })
81
85
  const rawBody = await req.json().catch(() => ({}))
82
86
  const payload = updateDictionaryEntrySchema.parse(rawBody)
87
+ const guardUserId = resolveDictionaryActorId(context.auth)
88
+ const guardResult = await validateCrudMutationGuard(context.container, {
89
+ tenantId: context.tenantId,
90
+ organizationId: context.organizationId,
91
+ userId: guardUserId,
92
+ resourceKind: 'dictionaries.entry',
93
+ resourceId: entry.id,
94
+ operation: 'update',
95
+ requestMethod: req.method,
96
+ requestHeaders: req.headers,
97
+ mutationPayload: payload,
98
+ })
99
+ if (guardResult && !guardResult.ok) {
100
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
101
+ }
83
102
  // These nested routes don't use the CRUD factory, so invoke the command bus explicitly.
84
103
  const commandBus = (context.container.resolve('commandBus') as CommandBus)
85
104
  const input = { ...(payload as Record<string, unknown>), id: entryId }
@@ -92,6 +111,19 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
92
111
  if (!updatedEntryId) {
93
112
  throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_update_failed', 'Failed to update dictionary entry') })
94
113
  }
114
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
115
+ await runCrudMutationGuardAfterSuccess(context.container, {
116
+ tenantId: context.tenantId,
117
+ organizationId: context.organizationId,
118
+ userId: guardUserId,
119
+ resourceKind: 'dictionaries.entry',
120
+ resourceId: updatedEntryId,
121
+ operation: 'update',
122
+ requestMethod: req.method,
123
+ requestHeaders: req.headers,
124
+ metadata: guardResult.metadata ?? null,
125
+ })
126
+ }
95
127
  const updated = await findOneWithDecryption(
96
128
  context.em.fork(),
97
129
  DictionaryEntry,
@@ -146,11 +178,39 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
146
178
  })
147
179
  const dictionary = await loadDictionary(context, dictionaryId)
148
180
  const entry = await loadEntry(context, dictionary, entryId)
181
+ const guardUserId = resolveDictionaryActorId(context.auth)
182
+ const guardResult = await validateCrudMutationGuard(context.container, {
183
+ tenantId: context.tenantId,
184
+ organizationId: context.organizationId,
185
+ userId: guardUserId,
186
+ resourceKind: 'dictionaries.entry',
187
+ resourceId: entry.id,
188
+ operation: 'delete',
189
+ requestMethod: req.method,
190
+ requestHeaders: req.headers,
191
+ mutationPayload: null,
192
+ })
193
+ if (guardResult && !guardResult.ok) {
194
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
195
+ }
149
196
  const commandBus = (context.container.resolve('commandBus') as CommandBus)
150
197
  const { logEntry } = await commandBus.execute('dictionaries.entries.delete', {
151
198
  input: { body: { id: entry.id } },
152
199
  ctx: context.ctx,
153
200
  })
201
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
202
+ await runCrudMutationGuardAfterSuccess(context.container, {
203
+ tenantId: context.tenantId,
204
+ organizationId: context.organizationId,
205
+ userId: guardUserId,
206
+ resourceKind: 'dictionaries.entry',
207
+ resourceId: entry.id,
208
+ operation: 'delete',
209
+ requestMethod: req.method,
210
+ requestHeaders: req.headers,
211
+ metadata: guardResult.metadata ?? null,
212
+ })
213
+ }
154
214
  const response = NextResponse.json({ ok: true })
155
215
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
156
216
  response.headers.set(
@@ -2,9 +2,13 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { runWithCacheTenant } from '@open-mercato/cache'
4
4
  import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
5
- import { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'
5
+ import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
6
6
  import { createDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'
7
7
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import {
9
+ runCrudMutationGuardAfterSuccess,
10
+ validateCrudMutationGuard,
11
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
8
12
  import type { CommandBus } from '@open-mercato/shared/lib/commands'
9
13
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
10
14
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -166,6 +170,21 @@ export async function POST(req: Request, ctx: { params?: { dictionaryId?: string
166
170
  }
167
171
  const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })
168
172
  const payload = createDictionaryEntrySchema.parse(await req.json().catch(() => ({})))
173
+ const guardUserId = resolveDictionaryActorId(context.auth)
174
+ const guardResult = await validateCrudMutationGuard(context.container, {
175
+ tenantId: context.tenantId,
176
+ organizationId: context.organizationId,
177
+ userId: guardUserId,
178
+ resourceKind: DICTIONARY_ENTRY_RESOURCE,
179
+ resourceId: dictionaryId,
180
+ operation: 'create',
181
+ requestMethod: req.method,
182
+ requestHeaders: req.headers,
183
+ mutationPayload: payload,
184
+ })
185
+ if (guardResult && !guardResult.ok) {
186
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
187
+ }
169
188
  // These nested routes do not use makeCrudRoute, so we invoke the command bus directly.
170
189
  const commandBus = (context.container.resolve('commandBus') as CommandBus)
171
190
  const { result, logEntry } = await commandBus.execute('dictionaries.entries.create', {
@@ -177,6 +196,19 @@ export async function POST(req: Request, ctx: { params?: { dictionaryId?: string
177
196
  if (!createdEntryId) {
178
197
  throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_create_failed', 'Failed to create dictionary entry') })
179
198
  }
199
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
200
+ await runCrudMutationGuardAfterSuccess(context.container, {
201
+ tenantId: context.tenantId,
202
+ organizationId: context.organizationId,
203
+ userId: guardUserId,
204
+ resourceKind: DICTIONARY_ENTRY_RESOURCE,
205
+ resourceId: createdEntryId,
206
+ operation: 'create',
207
+ requestMethod: req.method,
208
+ requestHeaders: req.headers,
209
+ metadata: guardResult.metadata ?? null,
210
+ })
211
+ }
180
212
  const entry = await findOneWithDecryption(
181
213
  context.em.fork(),
182
214
  DictionaryEntry,
@@ -1,9 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'
4
- import { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'
4
+ import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
5
5
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard,
10
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
7
11
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
8
12
  import {
9
13
  resolveDictionaryEntrySortMode,
@@ -118,6 +122,22 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
118
122
  request: req,
119
123
  })
120
124
 
125
+ const guardUserId = resolveDictionaryActorId(context.auth)
126
+ const guardResult = await validateCrudMutationGuard(context.container, {
127
+ tenantId: context.tenantId,
128
+ organizationId: context.organizationId,
129
+ userId: guardUserId,
130
+ resourceKind: 'dictionaries.dictionary',
131
+ resourceId: dictionary.id,
132
+ operation: 'update',
133
+ requestMethod: req.method,
134
+ requestHeaders: req.headers,
135
+ mutationPayload: payload,
136
+ })
137
+ if (guardResult && !guardResult.ok) {
138
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
139
+ }
140
+
121
141
  if (isProtectedCurrencyDictionary(dictionary)) {
122
142
  if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {
123
143
  throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })
@@ -172,6 +192,20 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
172
192
  dictionary.updatedAt = new Date()
173
193
  await context.em.flush()
174
194
 
195
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
196
+ await runCrudMutationGuardAfterSuccess(context.container, {
197
+ tenantId: context.tenantId,
198
+ organizationId: context.organizationId,
199
+ userId: guardUserId,
200
+ resourceKind: 'dictionaries.dictionary',
201
+ resourceId: dictionary.id,
202
+ operation: 'update',
203
+ requestMethod: req.method,
204
+ requestHeaders: req.headers,
205
+ metadata: guardResult.metadata ?? null,
206
+ })
207
+ }
208
+
175
209
  return NextResponse.json({
176
210
  id: dictionary.id,
177
211
  key: dictionary.key,
@@ -202,6 +236,22 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
202
236
  const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })
203
237
  const dictionary = await loadDictionary(context, dictionaryId)
204
238
 
239
+ const guardUserId = resolveDictionaryActorId(context.auth)
240
+ const guardResult = await validateCrudMutationGuard(context.container, {
241
+ tenantId: context.tenantId,
242
+ organizationId: context.organizationId,
243
+ userId: guardUserId,
244
+ resourceKind: 'dictionaries.dictionary',
245
+ resourceId: dictionary.id,
246
+ operation: 'delete',
247
+ requestMethod: req.method,
248
+ requestHeaders: req.headers,
249
+ mutationPayload: null,
250
+ })
251
+ if (guardResult && !guardResult.ok) {
252
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
253
+ }
254
+
205
255
  if (isProtectedCurrencyDictionary(dictionary)) {
206
256
  throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })
207
257
  }
@@ -210,6 +260,20 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
210
260
  dictionary.deletedAt = dictionary.deletedAt ?? new Date()
211
261
  await context.em.flush()
212
262
 
263
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
264
+ await runCrudMutationGuardAfterSuccess(context.container, {
265
+ tenantId: context.tenantId,
266
+ organizationId: context.organizationId,
267
+ userId: guardUserId,
268
+ resourceKind: 'dictionaries.dictionary',
269
+ resourceId: dictionary.id,
270
+ operation: 'delete',
271
+ requestMethod: req.method,
272
+ requestHeaders: req.headers,
273
+ metadata: guardResult.metadata ?? null,
274
+ })
275
+ }
276
+
213
277
  return NextResponse.json({ ok: true })
214
278
  } catch (err) {
215
279
  if (isCrudHttpError(err)) {
@@ -1,7 +1,11 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'
3
- import { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'
3
+ import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
4
4
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard,
8
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
5
9
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
6
10
  import {
7
11
  DEFAULT_DICTIONARY_ENTRY_SORT_MODE,
@@ -100,9 +104,40 @@ export async function POST(req: Request) {
100
104
  createdAt: new Date(),
101
105
  updatedAt: new Date(),
102
106
  })
107
+
108
+ const guardUserId = resolveDictionaryActorId(context.auth)
109
+ const guardResult = await validateCrudMutationGuard(context.container, {
110
+ tenantId: context.tenantId,
111
+ organizationId: context.organizationId,
112
+ userId: guardUserId,
113
+ resourceKind: 'dictionaries.dictionary',
114
+ resourceId: dictionary.id,
115
+ operation: 'create',
116
+ requestMethod: req.method,
117
+ requestHeaders: req.headers,
118
+ mutationPayload: payload,
119
+ })
120
+ if (guardResult && !guardResult.ok) {
121
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
122
+ }
123
+
103
124
  context.em.persist(dictionary)
104
125
  await context.em.flush()
105
126
 
127
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
128
+ await runCrudMutationGuardAfterSuccess(context.container, {
129
+ tenantId: context.tenantId,
130
+ organizationId: context.organizationId,
131
+ userId: guardUserId,
132
+ resourceKind: 'dictionaries.dictionary',
133
+ resourceId: dictionary.id,
134
+ operation: 'create',
135
+ requestMethod: req.method,
136
+ requestHeaders: req.headers,
137
+ metadata: guardResult.metadata ?? null,
138
+ })
139
+ }
140
+
106
141
  return NextResponse.json({
107
142
  id: dictionary.id,
108
143
  key: dictionary.key,
@@ -1,4 +1,4 @@
1
- import { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
1
+ import { EntityManager, FilterQuery, QueryOrder } from '@mikro-orm/postgresql'
2
2
  import { FeatureToggle, FeatureToggleOverride } from '../data/entities'
3
3
  import { Tenant } from '@open-mercato/core/modules/directory/data/entities'
4
4
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
@@ -28,6 +28,8 @@ export type GetOverridesQuery = {
28
28
  pageSize?: number
29
29
  }
30
30
 
31
+ const SORTABLE_FIELDS = new Set<keyof FeatureToggle>(['identifier', 'name', 'category'])
32
+
31
33
  type GetOverridesResult = {
32
34
  items: OverrideListResponse[]
33
35
  total: number
@@ -56,76 +58,55 @@ export async function getOverrides(
56
58
  filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }
57
59
  }
58
60
 
59
- const globalToggles = await em.find(FeatureToggle, filters)
61
+ const page = query.page ?? 1
62
+ const pageSize = query.pageSize ?? 25
63
+ const offset = (page - 1) * pageSize
60
64
 
61
- const overrides = await em.find(FeatureToggleOverride, {
62
- tenantId: tenant.id,
63
- toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },
64
- })
65
+ const direction = query.sortDir === 'desc' ? QueryOrder.DESC : QueryOrder.ASC
66
+ const orderBy: Partial<Record<keyof FeatureToggle, QueryOrder>> = {}
67
+ if (query.sortField && SORTABLE_FIELDS.has(query.sortField as keyof FeatureToggle)) {
68
+ orderBy[query.sortField as keyof FeatureToggle] = direction
69
+ }
70
+ orderBy.id = QueryOrder.ASC
65
71
 
66
- const response: OverrideListResponse[] = []
72
+ const totalItems = await em.count(FeatureToggle, filters)
67
73
 
68
- globalToggles.forEach((toggle) => {
69
- const override = overrides.find((o) => o.toggle.id === toggle.id)
70
- if (override) {
71
- response.push({
72
- id: override?.id ?? '',
73
- toggleId: toggle.id,
74
- tenantName: tenant.name,
75
- tenantId: tenant.id,
76
- identifier: toggle.identifier,
77
- name: toggle.name,
78
- category: toggle.category ?? '',
79
- isOverride: true,
80
- })
81
- } else {
82
- response.push({
83
- id: '',
84
- toggleId: toggle.id,
85
- tenantName: tenant.name,
86
- tenantId: tenant.id,
87
- identifier: toggle.identifier,
88
- name: toggle.name,
89
- category: toggle.category ?? '',
90
- isOverride: false,
91
- })
92
- }
74
+ const globalToggles = await em.find(FeatureToggle, filters, {
75
+ orderBy,
76
+ limit: pageSize,
77
+ offset,
93
78
  })
94
79
 
95
- if (query.sortField && query.sortDir) {
96
- const sortField = query.sortField
97
- const sortDir = query.sortDir
98
-
99
- response.sort((a, b) => {
100
- let aValue: any = a[sortField as keyof typeof a]
101
- let bValue: any = b[sortField as keyof typeof b]
102
-
103
-
80
+ const overrides = globalToggles.length
81
+ ? await em.find(FeatureToggleOverride, {
82
+ tenantId: tenant.id,
83
+ toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },
84
+ })
85
+ : []
104
86
 
105
- if (typeof aValue === 'string' && typeof bValue === 'string') {
106
- const comparison = aValue.localeCompare(bValue)
107
- return sortDir === 'asc' ? comparison : -comparison
108
- }
109
-
110
- if (typeof aValue === 'number' && typeof bValue === 'number') {
111
- return sortDir === 'asc' ? aValue - bValue : bValue - aValue
112
- }
113
-
114
- return 0
115
- })
87
+ const overridesByToggleId = new Map<string, FeatureToggleOverride>()
88
+ for (const override of overrides) {
89
+ overridesByToggleId.set(override.toggle.id, override)
116
90
  }
117
91
 
118
- const totalItems = response.length
119
- const page = query.page ?? 1
120
- const pageSize = query.pageSize ?? 25
121
- const totalPages = Math.ceil(totalItems / pageSize)
122
- const startIndex = (page - 1) * pageSize
123
- const endIndex = startIndex + pageSize
92
+ const items: OverrideListResponse[] = globalToggles.map((toggle) => {
93
+ const override = overridesByToggleId.get(toggle.id)
94
+ return {
95
+ id: override?.id ?? '',
96
+ toggleId: toggle.id,
97
+ tenantName: tenant.name,
98
+ tenantId: tenant.id,
99
+ identifier: toggle.identifier,
100
+ name: toggle.name,
101
+ category: toggle.category ?? '',
102
+ isOverride: Boolean(override),
103
+ }
104
+ })
124
105
 
125
- const paginatedItems = response.slice(startIndex, endIndex)
106
+ const totalPages = Math.ceil(totalItems / pageSize)
126
107
 
127
108
  return {
128
- items: paginatedItems,
109
+ items,
129
110
  total: totalItems,
130
111
  totalPages,
131
112
  page,