@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
@@ -9,6 +9,10 @@ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/er
9
9
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
+ import {
13
+ runCrudMutationGuardAfterSuccess,
14
+ validateCrudMutationGuard,
15
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
12
16
  import {
13
17
  resourcesResourceTagAssignmentSchema,
14
18
  type ResourcesResourceTagAssignmentInput,
@@ -37,16 +41,54 @@ async function buildContext(
37
41
  return { ctx, translate }
38
42
  }
39
43
 
44
+ function resolveActorId(ctx: CommandRuntimeContext): string {
45
+ const auth = ctx.auth
46
+ if (auth && typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
47
+ if (auth && typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
48
+ if (auth && typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
49
+ return 'system'
50
+ }
51
+
40
52
  export async function POST(req: Request) {
41
53
  try {
42
54
  const { ctx, translate } = await buildContext(req)
43
55
  const body = await req.json().catch(() => ({}))
44
56
  const input = parseScopedCommandInput(resourcesResourceTagAssignmentSchema, body, ctx, translate)
57
+ const actorId = resolveActorId(ctx)
58
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
59
+ tenantId: input.tenantId,
60
+ organizationId: input.organizationId,
61
+ userId: actorId,
62
+ resourceKind: 'resources.resourceTagAssignment',
63
+ resourceId: input.resourceId,
64
+ operation: 'custom',
65
+ requestMethod: req.method,
66
+ requestHeaders: req.headers,
67
+ mutationPayload: input,
68
+ })
69
+ if (guardResult && !guardResult.ok) {
70
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
71
+ }
72
+
45
73
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
46
74
  const { result, logEntry } = await commandBus.execute<ResourcesResourceTagAssignmentInput, { assignmentId: string | null }>(
47
75
  'resources.resourceTags.unassign',
48
76
  { input, ctx },
49
77
  )
78
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
79
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
80
+ tenantId: input.tenantId,
81
+ organizationId: input.organizationId,
82
+ userId: actorId,
83
+ resourceKind: 'resources.resourceTagAssignment',
84
+ resourceId: input.resourceId,
85
+ operation: 'custom',
86
+ requestMethod: req.method,
87
+ requestHeaders: req.headers,
88
+ metadata: guardResult.metadata ?? null,
89
+ })
90
+ }
91
+
50
92
  const response = NextResponse.json({ id: result?.assignmentId ?? null })
51
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
94
  response.headers.set(
@@ -13,6 +13,11 @@ import { loadSalesSettings } from '../../../commands/settings'
13
13
  import { SalesDocumentNumberGenerator } from '../../../services/salesDocumentNumberGenerator'
14
14
  import { DOCUMENT_NUMBER_TOKENS, DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'
15
15
  import { withScopedPayload } from '../../utils'
16
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
17
+ import {
18
+ runCrudMutationGuardAfterSuccess,
19
+ validateCrudMutationGuard,
20
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
16
21
 
17
22
  export const metadata = {
18
23
  GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
@@ -94,10 +99,25 @@ export async function GET(req: Request) {
94
99
  export async function PUT(req: Request) {
95
100
  try {
96
101
  const { ctx, translate, generator, organizationId, tenantId } = await resolveSettingsContext(req)
97
- const payload = await req.json().catch(() => ({}))
102
+ const payload = await readJsonSafe(req, {})
98
103
  const scoped = withScopedPayload(payload, ctx, translate)
99
104
  const input = salesSettingsUpsertSchema.parse(scoped)
100
105
 
106
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
107
+ tenantId,
108
+ organizationId,
109
+ userId: ctx.auth!.sub,
110
+ resourceKind: 'sales.settings',
111
+ resourceId: organizationId,
112
+ operation: 'update',
113
+ requestMethod: req.method,
114
+ requestHeaders: req.headers,
115
+ mutationPayload: input,
116
+ })
117
+ if (guardResult && !guardResult.ok) {
118
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
119
+ }
120
+
101
121
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
102
122
  const { result } = await commandBus.execute<
103
123
  SalesSettingsUpsertInput,
@@ -110,6 +130,20 @@ export async function PUT(req: Request) {
110
130
  }
111
131
  >('sales.settings.save', { input, ctx })
112
132
 
133
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
134
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
135
+ tenantId,
136
+ organizationId,
137
+ userId: ctx.auth!.sub,
138
+ resourceKind: 'sales.settings',
139
+ resourceId: organizationId,
140
+ operation: 'update',
141
+ requestMethod: req.method,
142
+ requestHeaders: req.headers,
143
+ metadata: guardResult.metadata ?? null,
144
+ })
145
+ }
146
+
113
147
  const sequences = await generator.peekSequences({ organizationId, tenantId })
114
148
 
115
149
  return NextResponse.json({
@@ -12,6 +12,11 @@ import { salesEditingSettingsSchema, salesSettingsUpsertSchema } from '../../../
12
12
  import { loadSalesSettings } from '../../../commands/settings'
13
13
  import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from '../../../lib/documentNumberTokens'
14
14
  import { withScopedPayload } from '../../utils'
15
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
16
+ import {
17
+ runCrudMutationGuardAfterSuccess,
18
+ validateCrudMutationGuard,
19
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
15
20
  import { ensureSalesDictionary } from '../../../lib/dictionaries'
16
21
  import { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
17
22
 
@@ -114,23 +119,52 @@ export async function GET(req: Request) {
114
119
  export async function PUT(req: Request) {
115
120
  try {
116
121
  const { ctx, translate, organizationId, tenantId, em } = await resolveSettingsContext(req)
117
- const payload = await req.json().catch(() => ({}))
122
+ const payload = (await readJsonSafe<Record<string, unknown>>(req, {})) ?? {}
118
123
  const scoped = withScopedPayload(payload, ctx, translate)
119
124
  const parsed = salesEditingSettingsSchema.parse(scoped)
120
125
 
126
+ const guardResult = await validateCrudMutationGuard(ctx.container, {
127
+ tenantId,
128
+ organizationId,
129
+ userId: ctx.auth!.sub,
130
+ resourceKind: 'sales.settings',
131
+ resourceId: organizationId,
132
+ operation: 'update',
133
+ requestMethod: req.method,
134
+ requestHeaders: req.headers,
135
+ mutationPayload: parsed,
136
+ })
137
+ if (guardResult && !guardResult.ok) {
138
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
139
+ }
140
+
121
141
  const current = await loadSalesSettings(em, { tenantId, organizationId })
122
142
  const commandInput = salesSettingsUpsertSchema.parse({
123
143
  ...parsed,
124
144
  orderNumberFormat: parsed.orderNumberFormat ?? current?.orderNumberFormat ?? DEFAULT_ORDER_NUMBER_FORMAT,
125
145
  quoteNumberFormat: parsed.quoteNumberFormat ?? current?.quoteNumberFormat ?? DEFAULT_QUOTE_NUMBER_FORMAT,
126
- orderNextNumber: payload?.orderNextNumber,
127
- quoteNextNumber: payload?.quoteNextNumber,
146
+ orderNextNumber: payload.orderNextNumber,
147
+ quoteNextNumber: payload.quoteNextNumber,
128
148
  })
129
149
 
130
150
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
131
151
  const response = await commandBus.execute('sales.settings.save', { input: commandInput, ctx })
132
152
  const result = (response as { result?: { orderCustomerEditableStatuses?: string[] | null; orderAddressEditableStatuses?: string[] | null } }).result
133
153
 
154
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
155
+ await runCrudMutationGuardAfterSuccess(ctx.container, {
156
+ tenantId,
157
+ organizationId,
158
+ userId: ctx.auth!.sub,
159
+ resourceKind: 'sales.settings',
160
+ resourceId: organizationId,
161
+ operation: 'update',
162
+ requestMethod: req.method,
163
+ requestHeaders: req.headers,
164
+ metadata: guardResult.metadata ?? null,
165
+ })
166
+ }
167
+
134
168
  const orderStatuses = await loadStatusOptions(em, tenantId, organizationId)
135
169
 
136
170
  return NextResponse.json({
@@ -10,6 +10,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
12
  import { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards,
17
+ } from '../../guards'
13
18
 
14
19
  export const metadata = {
15
20
  POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },
@@ -39,11 +44,51 @@ export async function POST(req: Request) {
39
44
  const { ctx, translate } = await buildContext(req)
40
45
  const body = await req.json().catch(() => ({}))
41
46
  const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)
47
+
48
+ const auth = ctx.auth
49
+ const tenantId = auth?.tenantId ?? ''
50
+ const organizationId = ctx.selectedOrganizationId ?? null
51
+ const guardResult = await runStaffMutationGuards(
52
+ ctx.container,
53
+ {
54
+ tenantId,
55
+ organizationId,
56
+ userId: auth?.sub ?? '',
57
+ resourceKind: 'staff.leave_request',
58
+ resourceId: input.id,
59
+ operation: 'update',
60
+ requestMethod: req.method,
61
+ requestHeaders: req.headers,
62
+ mutationPayload: input,
63
+ },
64
+ resolveUserFeatures(auth),
65
+ )
66
+ if (!guardResult.ok) {
67
+ return NextResponse.json(
68
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
69
+ { status: guardResult.errorStatus ?? 422 },
70
+ )
71
+ }
72
+
42
73
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
43
74
  const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(
44
75
  'staff.leave-requests.accept',
45
76
  { input, ctx },
46
77
  )
78
+
79
+ if (guardResult.afterSuccessCallbacks.length) {
80
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
81
+ tenantId,
82
+ organizationId,
83
+ userId: auth?.sub ?? '',
84
+ resourceKind: 'staff.leave_request',
85
+ resourceId: result?.requestId ?? input.id,
86
+ operation: 'update',
87
+ requestMethod: req.method,
88
+ requestHeaders: req.headers,
89
+ })
90
+ }
91
+
47
92
  const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })
48
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
49
94
  response.headers.set(
@@ -10,6 +10,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
10
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
12
  import { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards,
17
+ } from '../../guards'
13
18
 
14
19
  export const metadata = {
15
20
  POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },
@@ -39,11 +44,51 @@ export async function POST(req: Request) {
39
44
  const { ctx, translate } = await buildContext(req)
40
45
  const body = await req.json().catch(() => ({}))
41
46
  const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)
47
+
48
+ const auth = ctx.auth
49
+ const tenantId = auth?.tenantId ?? ''
50
+ const organizationId = ctx.selectedOrganizationId ?? null
51
+ const guardResult = await runStaffMutationGuards(
52
+ ctx.container,
53
+ {
54
+ tenantId,
55
+ organizationId,
56
+ userId: auth?.sub ?? '',
57
+ resourceKind: 'staff.leave_request',
58
+ resourceId: input.id,
59
+ operation: 'update',
60
+ requestMethod: req.method,
61
+ requestHeaders: req.headers,
62
+ mutationPayload: input,
63
+ },
64
+ resolveUserFeatures(auth),
65
+ )
66
+ if (!guardResult.ok) {
67
+ return NextResponse.json(
68
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
69
+ { status: guardResult.errorStatus ?? 422 },
70
+ )
71
+ }
72
+
42
73
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
43
74
  const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(
44
75
  'staff.leave-requests.reject',
45
76
  { input, ctx },
46
77
  )
78
+
79
+ if (guardResult.afterSuccessCallbacks.length) {
80
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
81
+ tenantId,
82
+ organizationId,
83
+ userId: auth?.sub ?? '',
84
+ resourceKind: 'staff.leave_request',
85
+ resourceId: result?.requestId ?? input.id,
86
+ operation: 'update',
87
+ requestMethod: req.method,
88
+ requestHeaders: req.headers,
89
+ })
90
+ }
91
+
47
92
  const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })
48
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
49
94
  response.headers.set(
@@ -16,6 +16,11 @@ import {
16
16
  type StaffTeamMemberSelfCreateInput,
17
17
  type StaffTeamMemberCreateInput,
18
18
  } from '../../../data/validators'
19
+ import {
20
+ resolveUserFeatures,
21
+ runStaffMutationGuardAfterSuccess,
22
+ runStaffMutationGuards,
23
+ } from '../../guards'
19
24
 
20
25
  export const metadata = {
21
26
  GET: { requireAuth: true, requireFeatures: ['staff.leave_requests.send'] },
@@ -102,6 +107,30 @@ export async function POST(req: Request) {
102
107
  throw new CrudHttpError(409, { error: translate('staff.teamMembers.self.exists', 'Team member profile already exists.') })
103
108
  }
104
109
 
110
+ const tenantId = auth.tenantId ?? ''
111
+ const organizationId = ctx.selectedOrganizationId ?? null
112
+ const guardResult = await runStaffMutationGuards(
113
+ ctx.container,
114
+ {
115
+ tenantId,
116
+ organizationId,
117
+ userId: auth.sub,
118
+ resourceKind: 'staff.team_member',
119
+ resourceId: auth.sub,
120
+ operation: 'create',
121
+ requestMethod: req.method,
122
+ requestHeaders: req.headers,
123
+ mutationPayload: parsed,
124
+ },
125
+ resolveUserFeatures(auth),
126
+ )
127
+ if (!guardResult.ok) {
128
+ return NextResponse.json(
129
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
130
+ { status: guardResult.errorStatus ?? 422 },
131
+ )
132
+ }
133
+
105
134
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
106
135
  const selfInput: StaffTeamMemberCreateInput = {
107
136
  ...parsed,
@@ -119,6 +148,20 @@ export async function POST(req: Request) {
119
148
  ctx,
120
149
  },
121
150
  )
151
+
152
+ if (guardResult.afterSuccessCallbacks.length) {
153
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
154
+ tenantId,
155
+ organizationId,
156
+ userId: auth.sub,
157
+ resourceKind: 'staff.team_member',
158
+ resourceId: result?.memberId ?? auth.sub,
159
+ operation: 'create',
160
+ requestMethod: req.method,
161
+ requestHeaders: req.headers,
162
+ })
163
+ }
164
+
122
165
  const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })
123
166
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
124
167
  response.headers.set(
@@ -13,6 +13,11 @@ import {
13
13
  staffTeamMemberTagAssignmentSchema,
14
14
  type StaffTeamMemberTagAssignmentInput,
15
15
  } from '../../../../data/validators'
16
+ import {
17
+ resolveUserFeatures,
18
+ runStaffMutationGuardAfterSuccess,
19
+ runStaffMutationGuards,
20
+ } from '../../../guards'
16
21
 
17
22
  export const metadata = {
18
23
  POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },
@@ -42,11 +47,51 @@ export async function POST(req: Request) {
42
47
  const { ctx, translate } = await buildContext(req)
43
48
  const body = await req.json().catch(() => ({}))
44
49
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)
50
+
51
+ const auth = ctx.auth
52
+ const tenantId = auth?.tenantId ?? ''
53
+ const organizationId = ctx.selectedOrganizationId ?? null
54
+ const guardResult = await runStaffMutationGuards(
55
+ ctx.container,
56
+ {
57
+ tenantId,
58
+ organizationId,
59
+ userId: auth?.sub ?? '',
60
+ resourceKind: 'staff.team_member',
61
+ resourceId: input.memberId,
62
+ operation: 'create',
63
+ requestMethod: req.method,
64
+ requestHeaders: req.headers,
65
+ mutationPayload: input,
66
+ },
67
+ resolveUserFeatures(auth),
68
+ )
69
+ if (!guardResult.ok) {
70
+ return NextResponse.json(
71
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
72
+ { status: guardResult.errorStatus ?? 422 },
73
+ )
74
+ }
75
+
45
76
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
46
77
  const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(
47
78
  'staff.team-members.tags.assign',
48
79
  { input, ctx },
49
80
  )
81
+
82
+ if (guardResult.afterSuccessCallbacks.length) {
83
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
84
+ tenantId,
85
+ organizationId,
86
+ userId: auth?.sub ?? '',
87
+ resourceKind: 'staff.team_member',
88
+ resourceId: result?.memberId ?? input.memberId,
89
+ operation: 'create',
90
+ requestMethod: req.method,
91
+ requestHeaders: req.headers,
92
+ })
93
+ }
94
+
50
95
  const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })
51
96
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
97
  response.headers.set(
@@ -13,6 +13,11 @@ import {
13
13
  staffTeamMemberTagAssignmentSchema,
14
14
  type StaffTeamMemberTagAssignmentInput,
15
15
  } from '../../../../data/validators'
16
+ import {
17
+ resolveUserFeatures,
18
+ runStaffMutationGuardAfterSuccess,
19
+ runStaffMutationGuards,
20
+ } from '../../../guards'
16
21
 
17
22
  export const metadata = {
18
23
  POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },
@@ -42,11 +47,51 @@ export async function POST(req: Request) {
42
47
  const { ctx, translate } = await buildContext(req)
43
48
  const body = await req.json().catch(() => ({}))
44
49
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)
50
+
51
+ const auth = ctx.auth
52
+ const tenantId = auth?.tenantId ?? ''
53
+ const organizationId = ctx.selectedOrganizationId ?? null
54
+ const guardResult = await runStaffMutationGuards(
55
+ ctx.container,
56
+ {
57
+ tenantId,
58
+ organizationId,
59
+ userId: auth?.sub ?? '',
60
+ resourceKind: 'staff.team_member',
61
+ resourceId: input.memberId,
62
+ operation: 'delete',
63
+ requestMethod: req.method,
64
+ requestHeaders: req.headers,
65
+ mutationPayload: input,
66
+ },
67
+ resolveUserFeatures(auth),
68
+ )
69
+ if (!guardResult.ok) {
70
+ return NextResponse.json(
71
+ guardResult.errorBody ?? { error: 'Operation blocked by guard' },
72
+ { status: guardResult.errorStatus ?? 422 },
73
+ )
74
+ }
75
+
45
76
  const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
46
77
  const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(
47
78
  'staff.team-members.tags.unassign',
48
79
  { input, ctx },
49
80
  )
81
+
82
+ if (guardResult.afterSuccessCallbacks.length) {
83
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
84
+ tenantId,
85
+ organizationId,
86
+ userId: auth?.sub ?? '',
87
+ resourceKind: 'staff.team_member',
88
+ resourceId: result?.memberId ?? input.memberId,
89
+ operation: 'delete',
90
+ requestMethod: req.method,
91
+ requestHeaders: req.headers,
92
+ })
93
+ }
94
+
50
95
  const response = NextResponse.json({ id: result?.memberId ?? null })
51
96
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
97
  response.headers.set(
@@ -1,9 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { sql } from 'kysely'
4
- import { resolveTranslationsRouteContext, requireTranslationFeatures } from '@open-mercato/core/modules/translations/api/context'
4
+ import { resolveTranslationsRouteContext, requireTranslationFeatures, resolveTranslationsActorId } from '@open-mercato/core/modules/translations/api/context'
5
5
  import { translationBodySchema, entityTypeParamSchema, entityIdParamSchema } from '@open-mercato/core/modules/translations/data/validators'
6
6
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard,
10
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
7
11
  import { CommandBus } from '@open-mercato/shared/lib/commands'
8
12
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
9
13
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -80,6 +84,22 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
80
84
  }
81
85
  const translations = translationBodySchema.parse(rawBody)
82
86
 
87
+ const guardUserId = resolveTranslationsActorId(context.auth)
88
+ const guardResult = await validateCrudMutationGuard(context.container, {
89
+ tenantId: context.tenantId,
90
+ organizationId: context.organizationId,
91
+ userId: guardUserId,
92
+ resourceKind: 'translations.translation',
93
+ resourceId: `${entityType}:${entityId}`,
94
+ operation: 'update',
95
+ requestMethod: req.method,
96
+ requestHeaders: req.headers,
97
+ mutationPayload: { entityType, entityId, translations },
98
+ })
99
+ if (guardResult && !guardResult.ok) {
100
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
101
+ }
102
+
83
103
  const commandBus = context.container.resolve('commandBus') as CommandBus
84
104
  const { result, logEntry } = await commandBus.execute<
85
105
  { entityType: string; entityId: string; translations: typeof translations; organizationId: string | null; tenantId: string },
@@ -95,6 +115,20 @@ export async function PUT(req: Request, ctx: { params?: { entityType?: string; e
95
115
  ctx: context.commandCtx,
96
116
  })
97
117
 
118
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
119
+ await runCrudMutationGuardAfterSuccess(context.container, {
120
+ tenantId: context.tenantId,
121
+ organizationId: context.organizationId,
122
+ userId: guardUserId,
123
+ resourceKind: 'translations.translation',
124
+ resourceId: `${entityType}:${entityId}`,
125
+ operation: 'update',
126
+ requestMethod: req.method,
127
+ requestHeaders: req.headers,
128
+ metadata: guardResult.metadata ?? null,
129
+ })
130
+ }
131
+
98
132
  const row = await (context.db as any)
99
133
  .selectFrom('entity_translations')
100
134
  .selectAll()
@@ -150,6 +184,22 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
150
184
  entityId: ctx.params?.entityId,
151
185
  })
152
186
 
187
+ const guardUserId = resolveTranslationsActorId(context.auth)
188
+ const guardResult = await validateCrudMutationGuard(context.container, {
189
+ tenantId: context.tenantId,
190
+ organizationId: context.organizationId,
191
+ userId: guardUserId,
192
+ resourceKind: 'translations.translation',
193
+ resourceId: `${entityType}:${entityId}`,
194
+ operation: 'delete',
195
+ requestMethod: req.method,
196
+ requestHeaders: req.headers,
197
+ mutationPayload: null,
198
+ })
199
+ if (guardResult && !guardResult.ok) {
200
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
201
+ }
202
+
153
203
  const commandBus = context.container.resolve('commandBus') as CommandBus
154
204
  const { logEntry } = await commandBus.execute<
155
205
  { entityType: string; entityId: string; organizationId: string | null; tenantId: string },
@@ -164,6 +214,20 @@ export async function DELETE(req: Request, ctx: { params?: { entityType?: string
164
214
  ctx: context.commandCtx,
165
215
  })
166
216
 
217
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
218
+ await runCrudMutationGuardAfterSuccess(context.container, {
219
+ tenantId: context.tenantId,
220
+ organizationId: context.organizationId,
221
+ userId: guardUserId,
222
+ resourceKind: 'translations.translation',
223
+ resourceId: `${entityType}:${entityId}`,
224
+ operation: 'delete',
225
+ requestMethod: req.method,
226
+ requestHeaders: req.headers,
227
+ metadata: guardResult.metadata ?? null,
228
+ })
229
+ }
230
+
167
231
  const response = new NextResponse(null, { status: 204 })
168
232
 
169
233
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
@@ -17,6 +17,13 @@ export type TranslationsRouteContext = {
17
17
  commandCtx: CommandRuntimeContext
18
18
  }
19
19
 
20
+ export function resolveTranslationsActorId(auth: TranslationsRouteContext['auth']): string {
21
+ if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
22
+ if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
23
+ if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
24
+ return 'system'
25
+ }
26
+
20
27
  export async function resolveTranslationsRouteContext(req: Request): Promise<TranslationsRouteContext> {
21
28
  const container = await createRequestContainer()
22
29
  const auth = await getAuthFromRequest(req)