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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  147. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  151. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  153. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  154. package/dist/modules/planner/api/access.js +9 -0
  155. package/dist/modules/planner/api/access.js.map +2 -2
  156. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  157. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  158. package/dist/modules/planner/api/availability-weekly.js +22 -1
  159. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  160. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  161. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  164. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  165. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  167. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  168. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  169. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  172. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  173. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  175. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  178. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  179. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  180. package/dist/modules/translations/api/context.js +7 -0
  181. package/dist/modules/translations/api/context.js.map +2 -2
  182. package/dist/modules/translations/api/put/locales.js +33 -1
  183. package/dist/modules/translations/api/put/locales.js.map +2 -2
  184. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  185. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  186. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  187. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  188. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  189. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  192. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  193. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  194. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  195. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  196. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  197. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  198. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  199. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  200. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  201. package/generated/entities/catalog_product/index.ts +26 -0
  202. package/generated/entities/catalog_product_variant/index.ts +2 -0
  203. package/generated/entity-fields-registry.ts +28 -0
  204. package/package.json +7 -7
  205. package/src/modules/catalog/api/products/route.ts +123 -39
  206. package/src/modules/catalog/api/variants/route.ts +4 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  209. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  210. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  211. package/src/modules/catalog/commands/products.ts +307 -2
  212. package/src/modules/catalog/commands/variants.ts +90 -1
  213. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  214. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  215. package/src/modules/catalog/components/products/productForm.ts +262 -0
  216. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  217. package/src/modules/catalog/data/entities.ts +97 -0
  218. package/src/modules/catalog/data/types.ts +36 -0
  219. package/src/modules/catalog/data/validators.ts +159 -3
  220. package/src/modules/catalog/i18n/de.json +64 -0
  221. package/src/modules/catalog/i18n/en.json +64 -0
  222. package/src/modules/catalog/i18n/es.json +64 -0
  223. package/src/modules/catalog/i18n/pl.json +64 -0
  224. package/src/modules/catalog/lib/gtin.ts +66 -0
  225. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  226. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  227. package/src/modules/catalog/translations.ts +1 -1
  228. package/src/modules/configs/api/cache/route.ts +72 -10
  229. package/src/modules/configs/components/CachePanel.tsx +59 -29
  230. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  231. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  232. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  233. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  234. package/src/modules/customer_accounts/i18n/de.json +1 -1
  235. package/src/modules/customer_accounts/i18n/en.json +1 -1
  236. package/src/modules/customer_accounts/i18n/es.json +1 -1
  237. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  238. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  239. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  240. package/src/modules/customers/cli.ts +1 -0
  241. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  242. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  243. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  244. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  245. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  246. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  247. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  248. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  249. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  250. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  251. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  252. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  253. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  254. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  255. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  256. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  257. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  258. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  259. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  260. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  261. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  262. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  263. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  264. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  265. package/src/modules/customers/components/calendar/types.ts +163 -0
  266. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  267. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  268. package/src/modules/customers/i18n/de.json +195 -0
  269. package/src/modules/customers/i18n/en.json +195 -0
  270. package/src/modules/customers/i18n/es.json +195 -0
  271. package/src/modules/customers/i18n/pl.json +195 -0
  272. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  273. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  274. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  275. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  276. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  277. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  278. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  279. package/src/modules/customers/lib/calendar/range.ts +45 -0
  280. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  281. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  282. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  283. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  284. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  285. package/src/modules/dictionaries/api/route.ts +36 -1
  286. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  287. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  288. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  289. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  290. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  291. package/src/modules/planner/api/access.ts +9 -0
  292. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  293. package/src/modules/planner/api/availability-weekly.ts +22 -1
  294. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  295. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  296. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  297. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  298. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  299. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  300. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  301. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  302. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  303. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  304. package/src/modules/translations/api/context.ts +7 -0
  305. package/src/modules/translations/api/put/locales.ts +35 -1
  306. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  307. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  308. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  309. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  310. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  311. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  312. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  313. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  314. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  315. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  316. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  317. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard,
8
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
5
9
  import { deleteUserPerspective } from '@open-mercato/core/modules/perspectives/services/perspectiveService'
6
10
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
7
11
  import { perspectivesTag, perspectivesErrorSchema, perspectivesSuccessSchema } from '../../openapi'
@@ -40,7 +44,22 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; per
40
44
  }
41
45
  })()
42
46
 
43
- await deleteUserPerspective(em, cache, {
47
+ const guardResult = await validateCrudMutationGuard(container, {
48
+ tenantId: auth.tenantId ?? '',
49
+ organizationId: auth.orgId ?? null,
50
+ userId: auth.sub,
51
+ resourceKind: 'perspectives.perspective',
52
+ resourceId: perspectiveId,
53
+ operation: 'delete',
54
+ requestMethod: req.method,
55
+ requestHeaders: req.headers,
56
+ mutationPayload: { tableId, perspectiveId },
57
+ })
58
+ if (guardResult && !guardResult.ok) {
59
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
60
+ }
61
+
62
+ const deleted = await deleteUserPerspective(em, cache, {
44
63
  scope: {
45
64
  userId: auth.sub,
46
65
  tenantId: auth.tenantId ?? null,
@@ -50,6 +69,20 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; per
50
69
  perspectiveId,
51
70
  })
52
71
 
72
+ if (deleted && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
73
+ await runCrudMutationGuardAfterSuccess(container, {
74
+ tenantId: auth.tenantId ?? '',
75
+ organizationId: auth.orgId ?? null,
76
+ userId: auth.sub,
77
+ resourceKind: 'perspectives.perspective',
78
+ resourceId: perspectiveId,
79
+ operation: 'delete',
80
+ requestMethod: req.method,
81
+ requestHeaders: req.headers,
82
+ metadata: guardResult.metadata ?? null,
83
+ })
84
+ }
85
+
53
86
  return NextResponse.json({ success: true })
54
87
  }
55
88
 
@@ -2,6 +2,10 @@ import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard,
8
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
5
9
  import { clearRolePerspectives } from '@open-mercato/core/modules/perspectives/services/perspectiveService'
6
10
  import { Role } from '@open-mercato/core/modules/auth/data/entities'
7
11
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
@@ -48,13 +52,42 @@ export async function DELETE(req: Request, ctx: { params: { tableId: string; rol
48
52
  const role = await em.findOne(Role, { id: roleId, deletedAt: null, ...(scope as any) } as any)
49
53
  if (!role) return NextResponse.json({ error: 'Role not found' }, { status: 404 })
50
54
 
51
- await clearRolePerspectives(em, cache, {
55
+ const guardResult = await validateCrudMutationGuard(container, {
56
+ tenantId: auth.tenantId ?? '',
57
+ organizationId: auth.orgId ?? null,
58
+ userId: auth.sub,
59
+ resourceKind: 'perspectives.role_perspective',
60
+ resourceId: roleId,
61
+ operation: 'delete',
62
+ requestMethod: req.method,
63
+ requestHeaders: req.headers,
64
+ mutationPayload: { tableId, roleId },
65
+ })
66
+ if (guardResult && !guardResult.ok) {
67
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
68
+ }
69
+
70
+ const clearedCount = await clearRolePerspectives(em, cache, {
52
71
  tableId,
53
72
  tenantId: auth.tenantId ?? null,
54
73
  organizationId: auth.orgId ?? null,
55
74
  roleIds: [roleId],
56
75
  })
57
76
 
77
+ if (clearedCount > 0 && guardResult?.ok && guardResult.shouldRunAfterSuccess) {
78
+ await runCrudMutationGuardAfterSuccess(container, {
79
+ tenantId: auth.tenantId ?? '',
80
+ organizationId: auth.orgId ?? null,
81
+ userId: auth.sub,
82
+ resourceKind: 'perspectives.role_perspective',
83
+ resourceId: roleId,
84
+ operation: 'delete',
85
+ requestMethod: req.method,
86
+ requestHeaders: req.headers,
87
+ metadata: guardResult.metadata ?? null,
88
+ })
89
+ }
90
+
58
91
  return NextResponse.json({ success: true })
59
92
  }
60
93
 
@@ -4,6 +4,10 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
6
6
  import { 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 { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'
8
12
  import {
9
13
  loadPerspectivesState,
@@ -168,6 +172,7 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
168
172
  const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)
169
173
  const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)
170
174
  const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0
175
+ const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))
171
176
 
172
177
  if (hasRoleOps) {
173
178
  const canApplyToRoles = await rbac.userHasAllFeatures?.(
@@ -183,7 +188,6 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
183
188
  const roleScope = auth.tenantId
184
189
  ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }
185
190
  : { tenantId: null }
186
- const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))
187
191
  const roles = await em.find(Role, {
188
192
  id: { $in: targetRoleIds as any },
189
193
  ...(roleScope as any),
@@ -197,44 +201,86 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
197
201
  }
198
202
  }
199
203
 
204
+ const guardResourceId = parsed.data.perspectiveId ?? tableId
205
+ const guardResult = await validateCrudMutationGuard(container, {
206
+ tenantId: auth.tenantId ?? '',
207
+ organizationId: auth.orgId ?? null,
208
+ userId: auth.sub,
209
+ resourceKind: 'perspectives.perspective',
210
+ resourceId: guardResourceId,
211
+ operation: 'custom',
212
+ requestMethod: req.method,
213
+ requestHeaders: req.headers,
214
+ mutationPayload: { ...parsed.data, tableId },
215
+ })
216
+ if (guardResult && !guardResult.ok) {
217
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
218
+ }
219
+
220
+ let roleGuardResult: Awaited<ReturnType<typeof validateCrudMutationGuard>> | null = null
221
+ if (hasRoleOps) {
222
+ roleGuardResult = await validateCrudMutationGuard(container, {
223
+ tenantId: auth.tenantId ?? '',
224
+ organizationId: auth.orgId ?? null,
225
+ userId: auth.sub,
226
+ resourceKind: 'perspectives.role_perspective',
227
+ resourceId: targetRoleIds.join(','),
228
+ operation: 'custom',
229
+ requestMethod: req.method,
230
+ requestHeaders: req.headers,
231
+ mutationPayload: {
232
+ tableId,
233
+ applyToRoles,
234
+ clearRoleIds,
235
+ name: parsed.data.name,
236
+ settings: parsed.data.settings,
237
+ setRoleDefault: parsed.data.setRoleDefault ?? false,
238
+ },
239
+ })
240
+ }
241
+ if (roleGuardResult && !roleGuardResult.ok) {
242
+ return NextResponse.json(roleGuardResult.body, { status: roleGuardResult.status })
243
+ }
244
+
200
245
  let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null
201
246
  let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null
247
+ let clearedRolePerspectiveCount = 0
202
248
 
203
249
  try {
204
250
  await withAtomicFlush(em, [
205
- async () => {
206
- saved = await saveUserPerspective(em, cache, {
207
- scope,
208
- tableId,
209
- input: parsed.data,
210
- request: req,
211
- })
212
- },
213
- async () => {
214
- if (applyToRoles.length) {
215
- updatedRolePerspectives = await saveRolePerspectives(em, cache, {
216
- tableId,
217
- tenantId: auth.tenantId ?? null,
218
- organizationId: auth.orgId ?? null,
219
- input: {
220
- roleIds: applyToRoles,
221
- name: parsed.data.name,
222
- settings: parsed.data.settings,
223
- setDefault: parsed.data.setRoleDefault ?? false,
224
- },
225
- })
226
- }
227
- },
228
- async () => {
229
- if (clearRoleIds.length) {
230
- await clearRolePerspectives(em, cache, {
251
+ async () => {
252
+ saved = await saveUserPerspective(em, cache, {
253
+ scope,
231
254
  tableId,
232
- tenantId: auth.tenantId ?? null,
233
- organizationId: auth.orgId ?? null,
234
- roleIds: clearRoleIds,
255
+ input: parsed.data,
256
+ request: req,
235
257
  })
236
- }
237
- },
258
+ },
259
+ async () => {
260
+ if (applyToRoles.length) {
261
+ updatedRolePerspectives = await saveRolePerspectives(em, cache, {
262
+ tableId,
263
+ tenantId: auth.tenantId ?? null,
264
+ organizationId: auth.orgId ?? null,
265
+ input: {
266
+ roleIds: applyToRoles,
267
+ name: parsed.data.name,
268
+ settings: parsed.data.settings,
269
+ setDefault: parsed.data.setRoleDefault ?? false,
270
+ },
271
+ })
272
+ }
273
+ },
274
+ async () => {
275
+ if (clearRoleIds.length) {
276
+ clearedRolePerspectiveCount = await clearRolePerspectives(em, cache, {
277
+ tableId,
278
+ tenantId: auth.tenantId ?? null,
279
+ organizationId: auth.orgId ?? null,
280
+ roleIds: clearRoleIds,
281
+ })
282
+ }
283
+ },
238
284
  ], { transaction: true })
239
285
  } catch (err) {
240
286
  if (isCrudHttpError(err)) {
@@ -243,6 +289,35 @@ export async function POST(req: Request, ctx: { params: { tableId: string } }) {
243
289
  throw err
244
290
  }
245
291
 
292
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
293
+ await runCrudMutationGuardAfterSuccess(container, {
294
+ tenantId: auth.tenantId ?? '',
295
+ organizationId: auth.orgId ?? null,
296
+ userId: auth.sub,
297
+ resourceKind: 'perspectives.perspective',
298
+ resourceId: guardResourceId,
299
+ operation: 'custom',
300
+ requestMethod: req.method,
301
+ requestHeaders: req.headers,
302
+ metadata: guardResult.metadata ?? null,
303
+ })
304
+ }
305
+
306
+ const didWriteRolePerspectives = applyToRoles.length > 0 || clearedRolePerspectiveCount > 0
307
+ if (didWriteRolePerspectives && roleGuardResult?.ok && roleGuardResult.shouldRunAfterSuccess) {
308
+ await runCrudMutationGuardAfterSuccess(container, {
309
+ tenantId: auth.tenantId ?? '',
310
+ organizationId: auth.orgId ?? null,
311
+ userId: auth.sub,
312
+ resourceKind: 'perspectives.role_perspective',
313
+ resourceId: targetRoleIds.join(','),
314
+ operation: 'custom',
315
+ requestMethod: req.method,
316
+ requestHeaders: req.headers,
317
+ metadata: roleGuardResult.metadata ?? null,
318
+ })
319
+ }
320
+
246
321
  return NextResponse.json({
247
322
  perspective: saved,
248
323
  rolePerspectives: updatedRolePerspectives ?? [],
@@ -309,7 +309,7 @@ export async function deleteUserPerspective(
309
309
  em: EntityManager,
310
310
  cache: CacheStrategy | null | undefined,
311
311
  options: { scope: PerspectiveScope; tableId: string; perspectiveId: string },
312
- ): Promise<void> {
312
+ ): Promise<boolean> {
313
313
  const { scope, tableId, perspectiveId } = options
314
314
  const tenantId = scope.tenantId ?? null
315
315
  const organizationId = scope.organizationId ?? null
@@ -322,7 +322,7 @@ export async function deleteUserPerspective(
322
322
  tableId,
323
323
  deletedAt: null,
324
324
  })
325
- if (!existing) return
325
+ if (!existing) return false
326
326
 
327
327
  existing.deletedAt = new Date()
328
328
  existing.isDefault = false
@@ -331,6 +331,8 @@ export async function deleteUserPerspective(
331
331
  if (cache?.deleteByTags) {
332
332
  await cache.deleteByTags([userTag(scope, tableId)])
333
333
  }
334
+
335
+ return true
334
336
  }
335
337
 
336
338
  export async function saveRolePerspectives(
@@ -430,13 +432,13 @@ export async function clearRolePerspectives(
430
432
  organizationId?: string | null
431
433
  roleIds: string[]
432
434
  },
433
- ): Promise<void> {
435
+ ): Promise<number> {
434
436
  const { tableId, roleIds } = options
435
437
  const tenantId = options.tenantId ?? null
436
438
  const organizationId = options.organizationId ?? null
437
- if (!roleIds.length) return
439
+ if (!roleIds.length) return 0
438
440
 
439
- await em.nativeUpdate(
441
+ const affected = await em.nativeUpdate(
440
442
  RolePerspective,
441
443
  {
442
444
  roleId: { $in: roleIds as any },
@@ -452,4 +454,6 @@ export async function clearRolePerspectives(
452
454
  const tags = roleIds.map((roleId) => roleTag(roleId, tableId, tenantId))
453
455
  await cache.deleteByTags(tags)
454
456
  }
457
+
458
+ return affected
455
459
  }
@@ -79,3 +79,12 @@ export async function assertAvailabilityWriteAccess(
79
79
  }
80
80
  return access
81
81
  }
82
+
83
+ export function resolveAvailabilityActorId(auth: AuthContext): string {
84
+ if (auth) {
85
+ if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub
86
+ if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId
87
+ if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId
88
+ }
89
+ return 'system'
90
+ }
@@ -11,9 +11,13 @@ import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
11
11
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
12
12
  import { plannerAvailabilityDateSpecificReplaceSchema } from '../data/validators'
13
13
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
14
+ import {
15
+ runCrudMutationGuardAfterSuccess,
16
+ validateCrudMutationGuard,
17
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
14
18
  import { PlannerAvailabilityRule } from '../data/entities'
15
19
  import { parseAvailabilityRuleWindow } from '../lib/availabilitySchedule'
16
- import { assertAvailabilityWriteAccess } from './access'
20
+ import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'
17
21
 
18
22
  export const metadata = {
19
23
  POST: { requireAuth: true },
@@ -93,8 +97,25 @@ export async function POST(req: Request) {
93
97
  }
94
98
  }
95
99
  }
100
+ const guardInput = {
101
+ tenantId: input.tenantId,
102
+ organizationId: input.organizationId,
103
+ userId: resolveAvailabilityActorId(ctx.auth),
104
+ resourceKind: 'planner.availability',
105
+ resourceId: input.subjectId,
106
+ operation: 'custom' as const,
107
+ requestMethod: req.method,
108
+ requestHeaders: req.headers,
109
+ }
110
+ const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })
111
+ if (guardResult && !guardResult.ok) {
112
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
113
+ }
96
114
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
97
115
  const { logEntry } = await commandBus.execute('planner.availability.date-specific.replace', { input, ctx })
116
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
117
+ await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })
118
+ }
98
119
  const response = NextResponse.json({ ok: true })
99
120
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
100
121
  response.headers.set(
@@ -9,7 +9,11 @@ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/er
9
9
  import { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
10
10
  import { plannerAvailabilityWeeklyReplaceSchema } from '../data/validators'
11
11
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
12
- import { assertAvailabilityWriteAccess } from './access'
12
+ import {
13
+ runCrudMutationGuardAfterSuccess,
14
+ validateCrudMutationGuard,
15
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
16
+ import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'
13
17
 
14
18
  export const metadata = {
15
19
  POST: { requireAuth: true },
@@ -55,8 +59,25 @@ export async function POST(req: Request) {
55
59
  const payload = await req.json().catch(() => ({}))
56
60
  const input = parseScopedCommandInput(plannerAvailabilityWeeklyReplaceSchema, payload, ctx, translate)
57
61
  await assertAvailabilityWriteAccess(ctx, { subjectType: input.subjectType, subjectId: input.subjectId }, translate)
62
+ const guardInput = {
63
+ tenantId: input.tenantId,
64
+ organizationId: input.organizationId,
65
+ userId: resolveAvailabilityActorId(ctx.auth),
66
+ resourceKind: 'planner.availability',
67
+ resourceId: input.subjectId,
68
+ operation: 'custom' as const,
69
+ requestMethod: req.method,
70
+ requestHeaders: req.headers,
71
+ }
72
+ const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })
73
+ if (guardResult && !guardResult.ok) {
74
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
75
+ }
58
76
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
59
77
  const { logEntry } = await commandBus.execute('planner.availability.weekly.replace', { input, ctx })
78
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
79
+ await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })
80
+ }
60
81
  const response = NextResponse.json({ ok: true })
61
82
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
62
83
  response.headers.set(
@@ -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 }>(
47
75
  'resources.resourceTags.assign',
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 }, { status: 201 })
51
93
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
52
94
  response.headers.set(
@@ -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({