@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\nimport { dictionaryKeySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n// System dictionaries use namespaced keys (e.g. `sales.deal_loss_reason`,\n// `resources.activity-types`) that the strict create-key regex rejects. The\n// manager edit dialog disables the key field but still resubmits the existing\n// key, so the update parse must accept any stored key verbatim. The strict\n// user-key regex is only enforced below when the key actually changes.\nconst updateKeySchema = z.string().trim().min(1).max(100)\nconst updateSchema = upsertDictionarySchema\n .partial()\n .extend({ key: updateKeySchema.optional() })\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n current: dictionary.updatedAt ?? null,\n request: req,\n })\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const strictKey = dictionaryKeySchema.safeParse(key)\n if (!strictKey.success) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.invalid_key', 'Use lowercase letters, numbers, hyphen, or underscore.') })\n }\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: err.issues[0]?.message ?? 'Validation failed' }, { status: 400 })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\nimport { dictionaryKeySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n// System dictionaries use namespaced keys (e.g. `sales.deal_loss_reason`,\n// `resources.activity-types`) that the strict create-key regex rejects. The\n// manager edit dialog disables the key field but still resubmits the existing\n// key, so the update parse must accept any stored key verbatim. The strict\n// user-key regex is only enforced below when the key actually changes.\nconst updateKeySchema = z.string().trim().min(1).max(100)\nconst updateSchema = upsertDictionarySchema\n .partial()\n .extend({ key: updateKeySchema.optional() })\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n current: dictionary.updatedAt ?? null,\n request: req,\n })\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const strictKey = dictionaryKeySchema.safeParse(key)\n if (!strictKey.success) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.invalid_key', 'Use lowercase letters, numbers, hyphen, or underscore.') })\n }\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: err.issues[0]?.message ?? 'Validation failed' }, { status: 400 })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -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 {
6
10
  DEFAULT_DICTIONARY_ENTRY_SORT_MODE,
7
11
  resolveDictionaryEntrySortMode
@@ -89,8 +93,36 @@ async function POST(req) {
89
93
  createdAt: /* @__PURE__ */ new Date(),
90
94
  updatedAt: /* @__PURE__ */ new Date()
91
95
  });
96
+ const guardUserId = resolveDictionaryActorId(context.auth);
97
+ const guardResult = await validateCrudMutationGuard(context.container, {
98
+ tenantId: context.tenantId,
99
+ organizationId: context.organizationId,
100
+ userId: guardUserId,
101
+ resourceKind: "dictionaries.dictionary",
102
+ resourceId: dictionary.id,
103
+ operation: "create",
104
+ requestMethod: req.method,
105
+ requestHeaders: req.headers,
106
+ mutationPayload: payload
107
+ });
108
+ if (guardResult && !guardResult.ok) {
109
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
110
+ }
92
111
  context.em.persist(dictionary);
93
112
  await context.em.flush();
113
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
114
+ await runCrudMutationGuardAfterSuccess(context.container, {
115
+ tenantId: context.tenantId,
116
+ organizationId: context.organizationId,
117
+ userId: guardUserId,
118
+ resourceKind: "dictionaries.dictionary",
119
+ resourceId: dictionary.id,
120
+ operation: "create",
121
+ requestMethod: req.method,
122
+ requestHeaders: req.headers,
123
+ metadata: guardResult.metadata ?? null
124
+ });
125
+ }
94
126
  return NextResponse.json({
95
127
  id: dictionary.id,
96
128
  key: dictionary.key,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dictionaries/api/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryListQuerySchema,\n dictionaryListResponseSchema,\n upsertDictionarySchema,\n} from './openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const url = new URL(req.url)\n const includeInactive = parseBooleanToken(url.searchParams.get('includeInactive')) === true\n\n const organizationFilter = context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}\n\n const items = await context.em.find(\n Dictionary,\n {\n ...organizationFilter,\n tenantId: context.tenantId,\n deletedAt: null,\n ...(includeInactive ? {} : { isActive: true }),\n },\n { orderBy: { name: 'asc' } },\n )\n\n return NextResponse.json({\n items: items.map((dictionary) => ({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionaries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const payload = upsertDictionarySchema.parse(await req.json().catch(() => ({})))\n const key = payload.key.trim().toLowerCase()\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n\n const existing = await context.em.findOne(Dictionary, {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n key,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n\n const dictionary = context.em.create(Dictionary, {\n key,\n name: payload.name.trim(),\n description: payload.description?.trim() || null,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n isSystem: payload.isSystem ?? false,\n isActive: payload.isActive ?? true,\n managerVisibility: 'default',\n entrySortMode: payload.entrySortMode ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n context.em.persist(dictionary)\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.POST] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to create dictionary' }, { status: 500 })\n }\n}\n\nconst dictionariesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionaries',\n description: 'Returns dictionaries accessible to the current organization, optionally including inactive records.',\n tags: [dictionariesTag],\n query: dictionaryListQuerySchema,\n responses: [\n { status: 200, description: 'Dictionary collection.', schema: dictionaryListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionaries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionariesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary',\n description: 'Registers a dictionary scoped to the current organization.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: upsertDictionarySchema,\n description: 'Dictionary definition including unique key and display name.',\n },\n responses: [\n { status: 201, description: 'Dictionary created.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionaries collection',\n methods: {\n GET: dictionariesGetDoc,\n POST: dictionariesPostDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryListQuerySchema,\n dictionaryListResponseSchema,\n upsertDictionarySchema,\n} from './openapi'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n POST: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function GET(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const url = new URL(req.url)\n const includeInactive = parseBooleanToken(url.searchParams.get('includeInactive')) === true\n\n const organizationFilter = context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}\n\n const items = await context.em.find(\n Dictionary,\n {\n ...organizationFilter,\n tenantId: context.tenantId,\n deletedAt: null,\n ...(includeInactive ? {} : { isActive: true }),\n },\n { orderBy: { name: 'asc' } },\n )\n\n return NextResponse.json({\n items: items.map((dictionary) => ({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })),\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionaries' }, { status: 500 })\n }\n}\n\nexport async function POST(req: Request) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const payload = upsertDictionarySchema.parse(await req.json().catch(() => ({})))\n const key = payload.key.trim().toLowerCase()\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n\n const existing = await context.em.findOne(Dictionary, {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n key,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n\n const dictionary = context.em.create(Dictionary, {\n key,\n name: payload.name.trim(),\n description: payload.description?.trim() || null,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n isSystem: payload.isSystem ?? false,\n isActive: payload.isActive ?? true,\n managerVisibility: 'default',\n entrySortMode: payload.entrySortMode ?? DEFAULT_DICTIONARY_ENTRY_SORT_MODE,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n const guardUserId = resolveDictionaryActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: payload,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n context.em.persist(dictionary)\n await context.em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries.POST] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to create dictionary' }, { status: 500 })\n }\n}\n\nconst dictionariesGetDoc: OpenApiMethodDoc = {\n summary: 'List dictionaries',\n description: 'Returns dictionaries accessible to the current organization, optionally including inactive records.',\n tags: [dictionariesTag],\n query: dictionaryListQuerySchema,\n responses: [\n { status: 200, description: 'Dictionary collection.', schema: dictionaryListResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionaries', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionariesPostDoc: OpenApiMethodDoc = {\n summary: 'Create dictionary',\n description: 'Registers a dictionary scoped to the current organization.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: upsertDictionarySchema,\n description: 'Dictionary definition including unique key and display name.',\n },\n responses: [\n { status: 201, description: 'Dictionary created.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to create dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionaries collection',\n methods: {\n GET: dictionariesGetDoc,\n POST: dictionariesPostDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,7 @@
1
+ import { QueryOrder } from "@mikro-orm/postgresql";
1
2
  import { FeatureToggle, FeatureToggleOverride } from "../data/entities.js";
2
3
  import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
4
+ const SORTABLE_FIELDS = /* @__PURE__ */ new Set(["identifier", "name", "category"]);
3
5
  async function getOverrides(em, tenant, query) {
4
6
  const filters = { deletedAt: null };
5
7
  if (query.category) {
@@ -11,63 +13,45 @@ async function getOverrides(em, tenant, query) {
11
13
  if (query.identifier) {
12
14
  filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` };
13
15
  }
14
- const globalToggles = await em.find(FeatureToggle, filters);
15
- const overrides = await em.find(FeatureToggleOverride, {
16
+ const page = query.page ?? 1;
17
+ const pageSize = query.pageSize ?? 25;
18
+ const offset = (page - 1) * pageSize;
19
+ const direction = query.sortDir === "desc" ? QueryOrder.DESC : QueryOrder.ASC;
20
+ const orderBy = {};
21
+ if (query.sortField && SORTABLE_FIELDS.has(query.sortField)) {
22
+ orderBy[query.sortField] = direction;
23
+ }
24
+ orderBy.id = QueryOrder.ASC;
25
+ const totalItems = await em.count(FeatureToggle, filters);
26
+ const globalToggles = await em.find(FeatureToggle, filters, {
27
+ orderBy,
28
+ limit: pageSize,
29
+ offset
30
+ });
31
+ const overrides = globalToggles.length ? await em.find(FeatureToggleOverride, {
16
32
  tenantId: tenant.id,
17
33
  toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } }
18
- });
19
- const response = [];
20
- globalToggles.forEach((toggle) => {
21
- const override = overrides.find((o) => o.toggle.id === toggle.id);
22
- if (override) {
23
- response.push({
24
- id: override?.id ?? "",
25
- toggleId: toggle.id,
26
- tenantName: tenant.name,
27
- tenantId: tenant.id,
28
- identifier: toggle.identifier,
29
- name: toggle.name,
30
- category: toggle.category ?? "",
31
- isOverride: true
32
- });
33
- } else {
34
- response.push({
35
- id: "",
36
- toggleId: toggle.id,
37
- tenantName: tenant.name,
38
- tenantId: tenant.id,
39
- identifier: toggle.identifier,
40
- name: toggle.name,
41
- category: toggle.category ?? "",
42
- isOverride: false
43
- });
44
- }
45
- });
46
- if (query.sortField && query.sortDir) {
47
- const sortField = query.sortField;
48
- const sortDir = query.sortDir;
49
- response.sort((a, b) => {
50
- let aValue = a[sortField];
51
- let bValue = b[sortField];
52
- if (typeof aValue === "string" && typeof bValue === "string") {
53
- const comparison = aValue.localeCompare(bValue);
54
- return sortDir === "asc" ? comparison : -comparison;
55
- }
56
- if (typeof aValue === "number" && typeof bValue === "number") {
57
- return sortDir === "asc" ? aValue - bValue : bValue - aValue;
58
- }
59
- return 0;
60
- });
34
+ }) : [];
35
+ const overridesByToggleId = /* @__PURE__ */ new Map();
36
+ for (const override of overrides) {
37
+ overridesByToggleId.set(override.toggle.id, override);
61
38
  }
62
- const totalItems = response.length;
63
- const page = query.page ?? 1;
64
- const pageSize = query.pageSize ?? 25;
39
+ const items = globalToggles.map((toggle) => {
40
+ const override = overridesByToggleId.get(toggle.id);
41
+ return {
42
+ id: override?.id ?? "",
43
+ toggleId: toggle.id,
44
+ tenantName: tenant.name,
45
+ tenantId: tenant.id,
46
+ identifier: toggle.identifier,
47
+ name: toggle.name,
48
+ category: toggle.category ?? "",
49
+ isOverride: Boolean(override)
50
+ };
51
+ });
65
52
  const totalPages = Math.ceil(totalItems / pageSize);
66
- const startIndex = (page - 1) * pageSize;
67
- const endIndex = startIndex + pageSize;
68
- const paginatedItems = response.slice(startIndex, endIndex);
69
53
  return {
70
- items: paginatedItems,
54
+ items,
71
55
  total: totalItems,
72
56
  totalPages,
73
57
  page,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/feature_toggles/lib/queries.ts"],
4
- "sourcesContent": ["import { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const globalToggles = await em.find(FeatureToggle, filters)\n\n const overrides = await em.find(FeatureToggleOverride, {\n tenantId: tenant.id,\n toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },\n })\n\n const response: OverrideListResponse[] = []\n\n globalToggles.forEach((toggle) => {\n const override = overrides.find((o) => o.toggle.id === toggle.id)\n if (override) {\n response.push({\n id: override?.id ?? '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: true,\n })\n } else {\n response.push({\n id: '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: false,\n })\n }\n })\n\n if (query.sortField && query.sortDir) {\n const sortField = query.sortField\n const sortDir = query.sortDir\n\n response.sort((a, b) => {\n let aValue: any = a[sortField as keyof typeof a]\n let bValue: any = b[sortField as keyof typeof b]\n\n\n\n if (typeof aValue === 'string' && typeof bValue === 'string') {\n const comparison = aValue.localeCompare(bValue)\n return sortDir === 'asc' ? comparison : -comparison\n }\n\n if (typeof aValue === 'number' && typeof bValue === 'number') {\n return sortDir === 'asc' ? aValue - bValue : bValue - aValue\n }\n\n return 0\n })\n }\n\n const totalItems = response.length\n const page = query.page ?? 1\n const pageSize = query.pageSize ?? 25\n const totalPages = Math.ceil(totalItems / pageSize)\n const startIndex = (page - 1) * pageSize\n const endIndex = startIndex + pageSize\n\n const paginatedItems = response.slice(startIndex, endIndex)\n\n return {\n items: paginatedItems,\n total: totalItems,\n totalPages,\n page,\n pageSize,\n raw: {\n toggles: globalToggles,\n overrides,\n },\n }\n}\n"],
5
- "mappings": "AACA,SAAS,eAAe,6BAA6B;AAErD,SAAS,yBAAyB;AAuClC,eAAsB,aAClB,IACA,QACA,OAC2B;AAC3B,QAAM,UAAsC,EAAE,WAAW,KAAK;AAC9D,MAAI,MAAM,UAAU;AAChB,YAAQ,WAAW,EAAE,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,CAAC,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,MAAM;AACZ,YAAQ,OAAO,EAAE,QAAQ,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI;AAAA,EAClE;AACA,MAAI,MAAM,YAAY;AAClB,YAAQ,aAAa,EAAE,QAAQ,IAAI,kBAAkB,MAAM,UAAU,CAAC,IAAI;AAAA,EAC9E;AAEA,QAAM,gBAAgB,MAAM,GAAG,KAAK,eAAe,OAAO;AAE1D,QAAM,YAAY,MAAM,GAAG,KAAK,uBAAuB;AAAA,IACnD,UAAU,OAAO;AAAA,IACjB,QAAQ,EAAE,IAAI,EAAE,KAAK,cAAc,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE;AAAA,EACpE,CAAC;AAED,QAAM,WAAmC,CAAC;AAE1C,gBAAc,QAAQ,CAAC,WAAW;AAC9B,UAAM,WAAW,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,OAAO,EAAE;AAChE,QAAI,UAAU;AACV,eAAS,KAAK;AAAA,QACV,IAAI,UAAU,MAAM;AAAA,QACpB,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,QACnB,MAAM,OAAO;AAAA,QACb,UAAU,OAAO,YAAY;AAAA,QAC7B,YAAY;AAAA,MAChB,CAAC;AAAA,IACL,OAAO;AACH,eAAS,KAAK;AAAA,QACV,IAAI;AAAA,QACJ,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,YAAY,OAAO;AAAA,QACnB,MAAM,OAAO;AAAA,QACb,UAAU,OAAO,YAAY;AAAA,QAC7B,YAAY;AAAA,MAChB,CAAC;AAAA,IACL;AAAA,EACJ,CAAC;AAED,MAAI,MAAM,aAAa,MAAM,SAAS;AAClC,UAAM,YAAY,MAAM;AACxB,UAAM,UAAU,MAAM;AAEtB,aAAS,KAAK,CAAC,GAAG,MAAM;AACpB,UAAI,SAAc,EAAE,SAA2B;AAC/C,UAAI,SAAc,EAAE,SAA2B;AAI/C,UAAI,OAAO,WAAW,YAAY,OAAO,WAAW,UAAU;AAC1D,cAAM,aAAa,OAAO,cAAc,MAAM;AAC9C,eAAO,YAAY,QAAQ,aAAa,CAAC;AAAA,MAC7C;AAEA,UAAI,OAAO,WAAW,YAAY,OAAO,WAAW,UAAU;AAC1D,eAAO,YAAY,QAAQ,SAAS,SAAS,SAAS;AAAA,MAC1D;AAEA,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAEA,QAAM,aAAa,SAAS;AAC5B,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ;AAClD,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,WAAW,aAAa;AAE9B,QAAM,iBAAiB,SAAS,MAAM,YAAY,QAAQ;AAE1D,SAAO;AAAA,IACH,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,MACD,SAAS;AAAA,MACT;AAAA,IACJ;AAAA,EACJ;AACJ;",
4
+ "sourcesContent": ["import { EntityManager, FilterQuery, QueryOrder } from '@mikro-orm/postgresql'\nimport { FeatureToggle, FeatureToggleOverride } from '../data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { OverrideListResponse } from '../data/validators'\n\nexport type FeatureToggleOverrideResponse = {\n id: string\n toggleId: string\n overrideState: 'enabled' | 'disabled' | 'inherit'\n tenantName: string\n tenantId: string\n identifier: string\n name: string\n category: string\n defaultState: boolean\n}\n\nexport type GetOverridesQuery = {\n category?: string\n name?: string\n identifier?: string\n defaultState?: 'enabled' | 'disabled'\n overrideState?: 'enabled' | 'disabled' | 'inherit'\n sortField?: 'identifier' | 'name' | 'category' | 'defaultState' | 'overrideState'\n sortDir?: 'asc' | 'desc'\n page?: number\n pageSize?: number\n}\n\nconst SORTABLE_FIELDS = new Set<keyof FeatureToggle>(['identifier', 'name', 'category'])\n\ntype GetOverridesResult = {\n items: OverrideListResponse[]\n total: number\n totalPages: number\n page: number\n pageSize: number\n raw: {\n toggles: FeatureToggle[]\n overrides: FeatureToggleOverride[]\n }\n}\n\nexport async function getOverrides(\n em: EntityManager,\n tenant: Tenant,\n query: GetOverridesQuery\n): Promise<GetOverridesResult> {\n const filters: FilterQuery<FeatureToggle> = { deletedAt: null }\n if (query.category) {\n filters.category = { $ilike: `%${escapeLikePattern(query.category)}%` }\n }\n if (query.name) {\n filters.name = { $ilike: `%${escapeLikePattern(query.name)}%` }\n }\n if (query.identifier) {\n filters.identifier = { $ilike: `%${escapeLikePattern(query.identifier)}%` }\n }\n\n const page = query.page ?? 1\n const pageSize = query.pageSize ?? 25\n const offset = (page - 1) * pageSize\n\n const direction = query.sortDir === 'desc' ? QueryOrder.DESC : QueryOrder.ASC\n const orderBy: Partial<Record<keyof FeatureToggle, QueryOrder>> = {}\n if (query.sortField && SORTABLE_FIELDS.has(query.sortField as keyof FeatureToggle)) {\n orderBy[query.sortField as keyof FeatureToggle] = direction\n }\n orderBy.id = QueryOrder.ASC\n\n const totalItems = await em.count(FeatureToggle, filters)\n\n const globalToggles = await em.find(FeatureToggle, filters, {\n orderBy,\n limit: pageSize,\n offset,\n })\n\n const overrides = globalToggles.length\n ? await em.find(FeatureToggleOverride, {\n tenantId: tenant.id,\n toggle: { id: { $in: globalToggles.map((toggle) => toggle.id) } },\n })\n : []\n\n const overridesByToggleId = new Map<string, FeatureToggleOverride>()\n for (const override of overrides) {\n overridesByToggleId.set(override.toggle.id, override)\n }\n\n const items: OverrideListResponse[] = globalToggles.map((toggle) => {\n const override = overridesByToggleId.get(toggle.id)\n return {\n id: override?.id ?? '',\n toggleId: toggle.id,\n tenantName: tenant.name,\n tenantId: tenant.id,\n identifier: toggle.identifier,\n name: toggle.name,\n category: toggle.category ?? '',\n isOverride: Boolean(override),\n }\n })\n\n const totalPages = Math.ceil(totalItems / pageSize)\n\n return {\n items,\n total: totalItems,\n totalPages,\n page,\n pageSize,\n raw: {\n toggles: globalToggles,\n overrides,\n },\n }\n}\n"],
5
+ "mappings": "AAAA,SAAqC,kBAAkB;AACvD,SAAS,eAAe,6BAA6B;AAErD,SAAS,yBAAyB;AA2BlC,MAAM,kBAAkB,oBAAI,IAAyB,CAAC,cAAc,QAAQ,UAAU,CAAC;AAcvF,eAAsB,aAClB,IACA,QACA,OAC2B;AAC3B,QAAM,UAAsC,EAAE,WAAW,KAAK;AAC9D,MAAI,MAAM,UAAU;AAChB,YAAQ,WAAW,EAAE,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,CAAC,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,MAAM;AACZ,YAAQ,OAAO,EAAE,QAAQ,IAAI,kBAAkB,MAAM,IAAI,CAAC,IAAI;AAAA,EAClE;AACA,MAAI,MAAM,YAAY;AAClB,YAAQ,aAAa,EAAE,QAAQ,IAAI,kBAAkB,MAAM,UAAU,CAAC,IAAI;AAAA,EAC9E;AAEA,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,UAAU,OAAO,KAAK;AAE5B,QAAM,YAAY,MAAM,YAAY,SAAS,WAAW,OAAO,WAAW;AAC1E,QAAM,UAA4D,CAAC;AACnE,MAAI,MAAM,aAAa,gBAAgB,IAAI,MAAM,SAAgC,GAAG;AAChF,YAAQ,MAAM,SAAgC,IAAI;AAAA,EACtD;AACA,UAAQ,KAAK,WAAW;AAExB,QAAM,aAAa,MAAM,GAAG,MAAM,eAAe,OAAO;AAExD,QAAM,gBAAgB,MAAM,GAAG,KAAK,eAAe,SAAS;AAAA,IACxD;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACJ,CAAC;AAED,QAAM,YAAY,cAAc,SAC1B,MAAM,GAAG,KAAK,uBAAuB;AAAA,IACjC,UAAU,OAAO;AAAA,IACjB,QAAQ,EAAE,IAAI,EAAE,KAAK,cAAc,IAAI,CAAC,WAAW,OAAO,EAAE,EAAE,EAAE;AAAA,EACpE,CAAC,IACD,CAAC;AAEP,QAAM,sBAAsB,oBAAI,IAAmC;AACnE,aAAW,YAAY,WAAW;AAC9B,wBAAoB,IAAI,SAAS,OAAO,IAAI,QAAQ;AAAA,EACxD;AAEA,QAAM,QAAgC,cAAc,IAAI,CAAC,WAAW;AAChE,UAAM,WAAW,oBAAoB,IAAI,OAAO,EAAE;AAClD,WAAO;AAAA,MACH,IAAI,UAAU,MAAM;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,MACb,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,QAAQ,QAAQ;AAAA,IAChC;AAAA,EACJ,CAAC;AAED,QAAM,aAAa,KAAK,KAAK,aAAa,QAAQ;AAElD,SAAO;AAAA,IACH;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK;AAAA,MACD,SAAS;AAAA,MACT;AAAA,IACJ;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -13,13 +13,23 @@ import {
13
13
  } from "../lib/actions.js";
14
14
  import { getMessageType } from "../lib/message-types-registry.js";
15
15
  import { assertOrganizationAccess } from "./shared.js";
16
+ const actionStateSnapshotSchema = z.object({
17
+ actionTaken: z.string().nullable(),
18
+ actionTakenByUserId: z.string().nullable(),
19
+ actionTakenAt: z.string().nullable(),
20
+ actionResult: z.record(z.string(), z.unknown()).nullable()
21
+ });
16
22
  const recordTerminalActionSchema = z.object({
17
23
  messageId: z.string().uuid(),
18
24
  actionId: z.string().min(1),
19
25
  result: z.record(z.string(), z.unknown()),
20
26
  tenantId: z.string().uuid(),
21
27
  organizationId: z.string().uuid().nullable(),
22
- userId: z.string().uuid()
28
+ userId: z.string().uuid(),
29
+ // Optional pre-claim snapshot supplied by `messages.actions.execute` so the
30
+ // undo log records the true (un-taken) state even though the terminal action
31
+ // was atomically claimed before this finalizer runs.
32
+ previousState: actionStateSnapshotSchema.optional()
23
33
  });
24
34
  const executeActionSchema = z.object({
25
35
  messageId: z.string().uuid(),
@@ -100,12 +110,29 @@ const recordTerminalActionCommand = {
100
110
  const input = recordTerminalActionSchema.parse(rawInput);
101
111
  const em = ctx.container.resolve("em").fork();
102
112
  const message = await requireActionTarget(em, input);
103
- if (message.actionTaken) {
104
- throw new Error("Action already taken");
113
+ const claimedByCaller = message.actionTaken === input.actionId && message.actionTakenByUserId === input.userId;
114
+ if (!claimedByCaller) {
115
+ if (message.actionTaken) {
116
+ throw new Error("Action already taken");
117
+ }
118
+ const claimedRows = await em.nativeUpdate(
119
+ Message,
120
+ { id: input.messageId, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
121
+ {
122
+ actionTaken: input.actionId,
123
+ actionTakenByUserId: input.userId,
124
+ actionTakenAt: /* @__PURE__ */ new Date()
125
+ }
126
+ );
127
+ if (claimedRows === 0) {
128
+ throw new Error("Action already taken");
129
+ }
130
+ message.actionTaken = input.actionId;
131
+ message.actionTakenByUserId = input.userId;
132
+ }
133
+ if (!message.actionTakenAt) {
134
+ message.actionTakenAt = /* @__PURE__ */ new Date();
105
135
  }
106
- message.actionTaken = input.actionId;
107
- message.actionTakenByUserId = input.userId;
108
- message.actionTakenAt = /* @__PURE__ */ new Date();
109
136
  message.actionResult = input.result;
110
137
  await em.flush();
111
138
  return { ok: true };
@@ -118,6 +145,7 @@ const recordTerminalActionCommand = {
118
145
  },
119
146
  buildLog: async ({ input, snapshots }) => {
120
147
  const parsed = recordTerminalActionSchema.parse(input);
148
+ const before = parsed.previousState ?? snapshots.before ?? null;
121
149
  return {
122
150
  actionLabel: "Execute message terminal action",
123
151
  resourceKind: "messages.message",
@@ -126,11 +154,11 @@ const recordTerminalActionCommand = {
126
154
  organizationId: parsed.organizationId,
127
155
  payload: {
128
156
  undo: {
129
- before: snapshots.before ?? null,
157
+ before,
130
158
  after: snapshots.after ?? null
131
159
  }
132
160
  },
133
- snapshotBefore: snapshots.before ?? null,
161
+ snapshotBefore: before,
134
162
  snapshotAfter: snapshots.after ?? null
135
163
  };
136
164
  },
@@ -179,6 +207,46 @@ const executeActionCommand = {
179
207
  }
180
208
  }
181
209
  }
210
+ const previousState = snapshotActionState(message);
211
+ let claimedTerminal = false;
212
+ const releaseTerminalClaim = async () => {
213
+ if (!claimedTerminal) return;
214
+ claimedTerminal = false;
215
+ await em.nativeUpdate(
216
+ Message,
217
+ {
218
+ id: message.id,
219
+ tenantId: input.tenantId,
220
+ actionTaken: action.id,
221
+ actionTakenByUserId: input.userId
222
+ },
223
+ { actionTaken: null, actionTakenByUserId: null, actionTakenAt: null }
224
+ );
225
+ };
226
+ if (shouldRecordActionTaken) {
227
+ const claimedRows = await em.nativeUpdate(
228
+ Message,
229
+ { id: message.id, tenantId: input.tenantId, actionTaken: null, deletedAt: null },
230
+ {
231
+ actionTaken: action.id,
232
+ actionTakenByUserId: input.userId,
233
+ actionTakenAt: /* @__PURE__ */ new Date()
234
+ }
235
+ );
236
+ if (claimedRows === 0) {
237
+ const current = await findOneWithDecryption(
238
+ em,
239
+ Message,
240
+ { id: message.id, tenantId: input.tenantId, deletedAt: null },
241
+ void 0,
242
+ { tenantId: input.tenantId, organizationId: input.organizationId }
243
+ );
244
+ throw Object.assign(new Error("Action already taken"), {
245
+ actionTaken: current?.actionTaken ?? message.actionTaken ?? action.id
246
+ });
247
+ }
248
+ claimedTerminal = true;
249
+ }
182
250
  const commandBus = ctx.container.resolve("commandBus");
183
251
  let result = {};
184
252
  let operationLogEntry = null;
@@ -223,6 +291,7 @@ const executeActionCommand = {
223
291
  operationLogEntry = commandResult.logEntry ?? null;
224
292
  } catch (err) {
225
293
  console.error("[messages] executeActionCommand sub-command failed", err);
294
+ await releaseTerminalClaim();
226
295
  throw new Error("Action failed");
227
296
  }
228
297
  } else if (action.href) {
@@ -232,10 +301,12 @@ const executeActionCommand = {
232
301
  userId: input.userId
233
302
  });
234
303
  if (!safeRedirect) {
304
+ await releaseTerminalClaim();
235
305
  throw new Error("Action has an unsafe redirect target");
236
306
  }
237
307
  result = { redirect: safeRedirect };
238
308
  } else {
309
+ await releaseTerminalClaim();
239
310
  throw new Error("Action has no executable target");
240
311
  }
241
312
  if (shouldRecordActionTaken) {
@@ -246,7 +317,8 @@ const executeActionCommand = {
246
317
  result,
247
318
  tenantId: input.tenantId,
248
319
  organizationId: input.organizationId,
249
- userId: input.userId
320
+ userId: input.userId,
321
+ previousState
250
322
  },
251
323
  ctx: {
252
324
  container: ctx.container,