@open-mercato/core 0.6.5-develop.4534.1.b459babe6d → 0.6.5-develop.4544.1.71c003c861

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 (633) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +5 -0
  3. package/dist/generated/entities/role/index.js +3 -1
  4. package/dist/generated/entities/role/index.js.map +2 -2
  5. package/dist/generated/entities/user/index.js +3 -1
  6. package/dist/generated/entities/user/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +2 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/optimisticLockUi.js +104 -0
  10. package/dist/helpers/integration/optimisticLockUi.js.map +7 -0
  11. package/dist/helpers/integration/salesFixtures.js +17 -0
  12. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  13. package/dist/modules/api_keys/backend/api-keys/page.js +9 -5
  14. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +17 -9
  16. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  17. package/dist/modules/auth/api/roles/acl/route.js +32 -13
  18. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  19. package/dist/modules/auth/api/roles/route.js +3 -1
  20. package/dist/modules/auth/api/roles/route.js.map +2 -2
  21. package/dist/modules/auth/api/sidebar/preferences/route.js +71 -3
  22. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  23. package/dist/modules/auth/api/users/acl/route.js +42 -19
  24. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  25. package/dist/modules/auth/api/users/route.js +3 -1
  26. package/dist/modules/auth/api/users/route.js.map +2 -2
  27. package/dist/modules/auth/backend/roles/[id]/edit/page.js +24 -4
  28. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  29. package/dist/modules/auth/backend/roles/page.js +8 -4
  30. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  31. package/dist/modules/auth/backend/users/[id]/edit/page.js +27 -5
  32. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  33. package/dist/modules/auth/backend/users/page.js +6 -2
  34. package/dist/modules/auth/backend/users/page.js.map +2 -2
  35. package/dist/modules/auth/components/AclEditor.js +3 -1
  36. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  37. package/dist/modules/auth/data/entities.js +6 -0
  38. package/dist/modules/auth/data/entities.js.map +2 -2
  39. package/dist/modules/auth/services/sidebarPreferencesService.js +32 -4
  40. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  41. package/dist/modules/business_rules/api/rules/route.js +28 -0
  42. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  43. package/dist/modules/business_rules/api/sets/route.js +28 -0
  44. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  45. package/dist/modules/business_rules/backend/rules/[id]/page.js +11 -4
  46. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +3 -3
  47. package/dist/modules/business_rules/backend/rules/page.js +20 -11
  48. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  49. package/dist/modules/business_rules/backend/sets/[id]/page.js +11 -4
  50. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  51. package/dist/modules/business_rules/backend/sets/page.js +20 -11
  52. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  53. package/dist/modules/catalog/api/categories/route.js +2 -0
  54. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  55. package/dist/modules/catalog/api/products/route.js +2 -1
  56. package/dist/modules/catalog/api/products/route.js.map +2 -2
  57. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -0
  58. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  59. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +94 -40
  60. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  61. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +37 -8
  62. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  63. package/dist/modules/catalog/backend/catalog/products/optionSchemaClient.js.map +2 -2
  64. package/dist/modules/catalog/commands/variants.js +32 -31
  65. package/dist/modules/catalog/commands/variants.js.map +2 -2
  66. package/dist/modules/catalog/components/PriceKindSettings.js +12 -5
  67. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  68. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  69. package/dist/modules/catalog/components/products/ProductMediaManager.js.map +2 -2
  70. package/dist/modules/catalog/components/products/ProductsDataTable.js +5 -3
  71. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  72. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  73. package/dist/modules/catalog/components/products/variantForm.js +2 -1
  74. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  75. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +5 -0
  76. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  77. package/dist/modules/currencies/backend/currencies/[id]/page.js +6 -3
  78. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  79. package/dist/modules/currencies/backend/currencies/page.js +18 -11
  80. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  81. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +1 -0
  82. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  83. package/dist/modules/currencies/backend/exchange-rates/page.js +10 -6
  84. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  85. package/dist/modules/currencies/commands/currencies.js +7 -5
  86. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  87. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +26 -19
  88. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  89. package/dist/modules/customer_accounts/api/admin/roles/[id].js +28 -5
  90. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  91. package/dist/modules/customer_accounts/api/admin/roles.js +4 -2
  92. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  93. package/dist/modules/customer_accounts/api/admin/users/[id].js +28 -5
  94. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  95. package/dist/modules/customer_accounts/api/admin/users.js +2 -0
  96. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  97. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +16 -8
  98. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  99. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +8 -4
  100. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  101. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +8 -4
  102. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +2 -2
  103. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +29 -18
  104. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  105. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +18 -11
  106. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  107. package/dist/modules/customers/api/companies/route.js +13 -2
  108. package/dist/modules/customers/api/companies/route.js.map +2 -2
  109. package/dist/modules/customers/api/deals/route.js +2 -0
  110. package/dist/modules/customers/api/deals/route.js.map +2 -2
  111. package/dist/modules/customers/api/people/route.js +11 -2
  112. package/dist/modules/customers/api/people/route.js.map +2 -2
  113. package/dist/modules/customers/api/todos/route.js +1 -0
  114. package/dist/modules/customers/api/todos/route.js.map +2 -2
  115. package/dist/modules/customers/backend/config/customers/deals/page.js.map +2 -2
  116. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +34 -21
  117. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  118. package/dist/modules/customers/backend/customers/companies/[id]/page.js +45 -27
  119. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  120. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  121. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +22 -5
  122. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  123. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +30 -8
  124. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +2 -2
  125. package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -0
  126. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  127. package/dist/modules/customers/backend/customers/deals/page.js +16 -6
  128. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  129. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +62 -39
  130. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  131. package/dist/modules/customers/backend/customers/people/[id]/page.js +41 -26
  132. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  133. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +50 -23
  135. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  136. package/dist/modules/customers/commands/addresses.js +16 -14
  137. package/dist/modules/customers/commands/addresses.js.map +2 -2
  138. package/dist/modules/customers/commands/companies.js +1 -1
  139. package/dist/modules/customers/commands/companies.js.map +2 -2
  140. package/dist/modules/customers/commands/interactions.js +41 -4
  141. package/dist/modules/customers/commands/interactions.js.map +2 -2
  142. package/dist/modules/customers/commands/people.js +1 -1
  143. package/dist/modules/customers/commands/people.js.map +2 -2
  144. package/dist/modules/customers/commands/personCompanyLinks.js +8 -5
  145. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  146. package/dist/modules/customers/commands/pipeline-stages.js +13 -11
  147. package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
  148. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  149. package/dist/modules/customers/components/DictionarySettings.js +20 -13
  150. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  151. package/dist/modules/customers/components/DictionarySortSettings.js +4 -0
  152. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  153. package/dist/modules/customers/components/PipelineSettings.js +38 -23
  154. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  155. package/dist/modules/customers/components/detail/ActivityTimeline.js +1 -1
  156. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  157. package/dist/modules/customers/components/detail/AddressesSection.js +4 -0
  158. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  159. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +28 -22
  160. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  161. package/dist/modules/customers/components/detail/DealsSection.js +36 -24
  162. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  163. package/dist/modules/customers/components/detail/EmailCardActions.js +5 -0
  164. package/dist/modules/customers/components/detail/EmailCardActions.js.map +2 -2
  165. package/dist/modules/customers/components/detail/EntityTagsDialog.js +7 -0
  166. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  167. package/dist/modules/customers/components/detail/ManageTagsDialog.js +34 -22
  168. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
  169. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +41 -29
  170. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  171. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +14 -8
  172. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  173. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +14 -6
  174. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  175. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +29 -13
  176. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +2 -2
  177. package/dist/modules/customers/components/detail/hooks/useInteractions.js +77 -35
  178. package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
  179. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +25 -17
  180. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  181. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  182. package/dist/modules/customers/components/formConfig.js.map +2 -2
  183. package/dist/modules/customers/data/guards.js +66 -0
  184. package/dist/modules/customers/data/guards.js.map +7 -0
  185. package/dist/modules/customers/di.js +37 -0
  186. package/dist/modules/customers/di.js.map +2 -2
  187. package/dist/modules/customers/lib/todoCompatibility.js +11 -0
  188. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  189. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  190. package/dist/modules/data_sync/api/options.js +4 -4
  191. package/dist/modules/data_sync/api/options.js.map +2 -2
  192. package/dist/modules/data_sync/api/schedules/route.js +9 -1
  193. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  194. package/dist/modules/data_sync/backend/data-sync/page.js +17 -8
  195. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  196. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +43 -22
  197. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  198. package/dist/modules/data_sync/lib/sync-schedule-service.js +9 -0
  199. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  200. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +8 -1
  201. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  202. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +17 -1
  203. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  204. package/dist/modules/dictionaries/components/DictionariesManager.js +31 -10
  205. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  206. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +28 -15
  207. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  208. package/dist/modules/directory/api/organizations/route.js +3 -0
  209. package/dist/modules/directory/api/organizations/route.js.map +2 -2
  210. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -0
  211. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  212. package/dist/modules/directory/backend/directory/organizations/page.js +9 -5
  213. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  214. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +7 -3
  215. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  216. package/dist/modules/directory/backend/directory/tenants/page.js +8 -4
  217. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  218. package/dist/modules/directory/commands/organizations.js +7 -2
  219. package/dist/modules/directory/commands/organizations.js.map +2 -2
  220. package/dist/modules/entities/api/records.js +66 -0
  221. package/dist/modules/entities/api/records.js.map +2 -2
  222. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +1 -0
  223. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  224. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +8 -4
  225. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  226. package/dist/modules/entities/lib/helpers.js +17 -0
  227. package/dist/modules/entities/lib/helpers.js.map +2 -2
  228. package/dist/modules/feature_toggles/api/global/[id]/override/route.js +2 -1
  229. package/dist/modules/feature_toggles/api/global/[id]/override/route.js.map +2 -2
  230. package/dist/modules/feature_toggles/api/overrides/route.js +15 -0
  231. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  232. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js +15 -14
  233. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  234. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +20 -12
  235. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
  236. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +6 -2
  237. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  238. package/dist/modules/feature_toggles/components/formConfig.js +2 -1
  239. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  240. package/dist/modules/feature_toggles/components/overrideFormConfig.js +5 -1
  241. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  242. package/dist/modules/feature_toggles/data/validators.js +7 -4
  243. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  244. package/dist/modules/inbox_ops/api/settings/route.js +17 -2
  245. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  246. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +13 -8
  247. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  248. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +9 -4
  249. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  250. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -11
  251. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  252. package/dist/modules/integrations/backend/integrations/page.js +12 -8
  253. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  254. package/dist/modules/messages/commands/messages.js +13 -10
  255. package/dist/modules/messages/commands/messages.js.map +2 -2
  256. package/dist/modules/perspectives/api/[tableId]/route.js +39 -30
  257. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  258. package/dist/modules/perspectives/services/perspectiveService.js +7 -0
  259. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  260. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +6 -14
  261. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
  262. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +4 -2
  263. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  264. package/dist/modules/planner/components/AvailabilityRuleSetForm.js +2 -0
  265. package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +2 -2
  266. package/dist/modules/planner/components/AvailabilityRulesEditor.js +36 -11
  267. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  268. package/dist/modules/planner/components/AvailabilitySchedule.js +9 -5
  269. package/dist/modules/planner/components/AvailabilitySchedule.js.map +2 -2
  270. package/dist/modules/query_index/lib/engine.js +19 -0
  271. package/dist/modules/query_index/lib/engine.js.map +2 -2
  272. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -0
  273. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  274. package/dist/modules/resources/backend/resources/resource-types/page.js +4 -2
  275. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  276. package/dist/modules/resources/backend/resources/resources/[id]/page.js +14 -3
  277. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  278. package/dist/modules/resources/backend/resources/resources/page.js +8 -4
  279. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  280. package/dist/modules/resources/components/ResourceCrudForm.js +2 -0
  281. package/dist/modules/resources/components/ResourceCrudForm.js.map +2 -2
  282. package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -0
  283. package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
  284. package/dist/modules/sales/api/documents/factory.js +7 -2
  285. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  286. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +3 -1
  287. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  288. package/dist/modules/sales/backend/sales/channels/offers/page.js +13 -4
  289. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  290. package/dist/modules/sales/backend/sales/channels/page.js +16 -4
  291. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  292. package/dist/modules/sales/backend/sales/documents/[id]/page.js +68 -22
  293. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  294. package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
  295. package/dist/modules/sales/commands/documentAddresses.js +181 -2
  296. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  297. package/dist/modules/sales/commands/documents.js +29 -1
  298. package/dist/modules/sales/commands/documents.js.map +2 -2
  299. package/dist/modules/sales/commands/returns.js +12 -2
  300. package/dist/modules/sales/commands/returns.js.map +2 -2
  301. package/dist/modules/sales/commands/shared.js +15 -0
  302. package/dist/modules/sales/commands/shared.js.map +2 -2
  303. package/dist/modules/sales/commands/shipments.js +4 -1
  304. package/dist/modules/sales/commands/shipments.js.map +2 -2
  305. package/dist/modules/sales/components/AdjustmentKindSettings.js +19 -11
  306. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  307. package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
  308. package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
  309. package/dist/modules/sales/components/PaymentMethodsSettings.js +12 -4
  310. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  311. package/dist/modules/sales/components/ShippingMethodsSettings.js +12 -4
  312. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  313. package/dist/modules/sales/components/StatusSettings.js +18 -11
  314. package/dist/modules/sales/components/StatusSettings.js.map +2 -2
  315. package/dist/modules/sales/components/TaxRatesSettings.js +12 -4
  316. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  317. package/dist/modules/sales/components/channels/ChannelOfferForm.js +47 -16
  318. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  319. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +8 -4
  320. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  321. package/dist/modules/sales/components/documents/AddressesSection.js +44 -25
  322. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  323. package/dist/modules/sales/components/documents/AdjustmentsSection.js +43 -23
  324. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  325. package/dist/modules/sales/components/documents/ItemsSection.js +22 -13
  326. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  327. package/dist/modules/sales/components/documents/LineItemDialog.js +23 -10
  328. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  329. package/dist/modules/sales/components/documents/PaymentDialog.js +29 -14
  330. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  331. package/dist/modules/sales/components/documents/PaymentsSection.js +20 -10
  332. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  333. package/dist/modules/sales/components/documents/ReturnDialog.js +26 -17
  334. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  335. package/dist/modules/sales/components/documents/ReturnsSection.js +3 -1
  336. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  337. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -5
  338. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  339. package/dist/modules/sales/components/documents/ShipmentDialog.js +21 -7
  340. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  341. package/dist/modules/sales/components/documents/ShipmentsSection.js +19 -10
  342. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  343. package/dist/modules/sales/components/documents/optimisticLock.js +27 -0
  344. package/dist/modules/sales/components/documents/optimisticLock.js.map +7 -0
  345. package/dist/modules/sales/di.js +18 -0
  346. package/dist/modules/sales/di.js.map +2 -2
  347. package/dist/modules/staff/api/job-histories.js +11 -2
  348. package/dist/modules/staff/api/job-histories.js.map +2 -2
  349. package/dist/modules/staff/api/timesheets/time-entries/route.js +11 -4
  350. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  351. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +13 -8
  352. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  353. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +2 -1
  354. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  355. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +7 -4
  356. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  357. package/dist/modules/staff/backend/staff/team-members/page.js +4 -2
  358. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  359. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +1 -0
  360. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  361. package/dist/modules/staff/backend/staff/team-roles/page.js +4 -2
  362. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  363. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +5 -2
  364. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  365. package/dist/modules/staff/backend/staff/teams/page.js +12 -3
  366. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  367. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -1
  368. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  369. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +2 -2
  370. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +12 -3
  371. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  372. package/dist/modules/staff/commands/job-histories.js +40 -3
  373. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  374. package/dist/modules/staff/components/LeaveRequestForm.js +1 -0
  375. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  376. package/dist/modules/staff/components/TeamForm.js +1 -0
  377. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  378. package/dist/modules/staff/components/TeamMemberForm.js +1 -0
  379. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  380. package/dist/modules/staff/components/TeamRoleForm.js +1 -0
  381. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  382. package/dist/modules/staff/components/detail/JobHistorySection.js +20 -7
  383. package/dist/modules/staff/components/detail/JobHistorySection.js.map +2 -2
  384. package/dist/modules/staff/data/validators.js +7 -1
  385. package/dist/modules/staff/data/validators.js.map +2 -2
  386. package/dist/modules/staff/lib/leaveRequestHelpers.js +2 -1
  387. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +2 -2
  388. package/dist/modules/translations/components/TranslationManager.js +12 -8
  389. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  390. package/dist/modules/workflows/api/definitions/[id]/route.js +106 -0
  391. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  392. package/dist/modules/workflows/backend/definitions/[id]/page.js +11 -3
  393. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  394. package/dist/modules/workflows/backend/definitions/page.js +19 -8
  395. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  396. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +29 -16
  397. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  398. package/dist/modules/workflows/components/formConfig.js +4 -1
  399. package/dist/modules/workflows/components/formConfig.js.map +2 -2
  400. package/dist/modules/workflows/di.js +12 -0
  401. package/dist/modules/workflows/di.js.map +2 -2
  402. package/generated/entities/role/index.ts +1 -0
  403. package/generated/entities/user/index.ts +1 -0
  404. package/generated/entity-fields-registry.ts +2 -0
  405. package/jest.setup.ts +17 -0
  406. package/package.json +8 -7
  407. package/src/helpers/integration/optimisticLockUi.ts +172 -0
  408. package/src/helpers/integration/salesFixtures.ts +29 -0
  409. package/src/modules/api_keys/backend/api-keys/page.tsx +10 -5
  410. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +19 -9
  411. package/src/modules/auth/api/roles/acl/route.ts +37 -11
  412. package/src/modules/auth/api/roles/route.ts +2 -0
  413. package/src/modules/auth/api/sidebar/preferences/route.ts +73 -0
  414. package/src/modules/auth/api/users/acl/route.ts +46 -18
  415. package/src/modules/auth/api/users/route.ts +2 -0
  416. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +29 -4
  417. package/src/modules/auth/backend/roles/page.tsx +9 -4
  418. package/src/modules/auth/backend/users/[id]/edit/page.tsx +37 -4
  419. package/src/modules/auth/backend/users/page.tsx +7 -2
  420. package/src/modules/auth/components/AclEditor.tsx +10 -1
  421. package/src/modules/auth/data/entities.ts +7 -1
  422. package/src/modules/auth/services/sidebarPreferencesService.ts +38 -4
  423. package/src/modules/business_rules/api/rules/route.ts +30 -0
  424. package/src/modules/business_rules/api/sets/route.ts +30 -0
  425. package/src/modules/business_rules/backend/rules/[id]/page.tsx +16 -4
  426. package/src/modules/business_rules/backend/rules/page.tsx +20 -11
  427. package/src/modules/business_rules/backend/sets/[id]/page.tsx +16 -4
  428. package/src/modules/business_rules/backend/sets/page.tsx +20 -11
  429. package/src/modules/catalog/api/categories/route.ts +3 -0
  430. package/src/modules/catalog/api/products/route.ts +4 -0
  431. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +5 -0
  432. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +112 -35
  433. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +56 -7
  434. package/src/modules/catalog/backend/catalog/products/optionSchemaClient.ts +2 -0
  435. package/src/modules/catalog/commands/variants.ts +32 -32
  436. package/src/modules/catalog/components/PriceKindSettings.tsx +20 -7
  437. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -0
  438. package/src/modules/catalog/components/products/ProductMediaManager.tsx +2 -0
  439. package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -4
  440. package/src/modules/catalog/components/products/productForm.ts +3 -0
  441. package/src/modules/catalog/components/products/variantForm.ts +9 -0
  442. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +5 -0
  443. package/src/modules/currencies/backend/currencies/[id]/page.tsx +13 -6
  444. package/src/modules/currencies/backend/currencies/page.tsx +18 -11
  445. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +3 -0
  446. package/src/modules/currencies/backend/exchange-rates/page.tsx +10 -6
  447. package/src/modules/currencies/commands/currencies.ts +10 -5
  448. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +31 -21
  449. package/src/modules/customer_accounts/api/admin/roles/[id].ts +35 -5
  450. package/src/modules/customer_accounts/api/admin/roles.ts +2 -0
  451. package/src/modules/customer_accounts/api/admin/users/[id].ts +38 -5
  452. package/src/modules/customer_accounts/api/admin/users.ts +2 -0
  453. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -20
  454. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +9 -4
  455. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +11 -4
  456. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +28 -17
  457. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +19 -11
  458. package/src/modules/customers/AGENTS.md +2 -2
  459. package/src/modules/customers/api/companies/route.ts +14 -1
  460. package/src/modules/customers/api/deals/route.ts +3 -0
  461. package/src/modules/customers/api/people/route.ts +12 -1
  462. package/src/modules/customers/api/todos/route.ts +1 -0
  463. package/src/modules/customers/backend/config/customers/deals/page.tsx +1 -0
  464. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +36 -21
  465. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +52 -27
  466. package/src/modules/customers/backend/customers/companies/page.tsx +2 -0
  467. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -5
  468. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +39 -7
  469. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -0
  470. package/src/modules/customers/backend/customers/deals/page.tsx +18 -6
  471. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +64 -39
  472. package/src/modules/customers/backend/customers/people/[id]/page.tsx +46 -26
  473. package/src/modules/customers/backend/customers/people/page.tsx +2 -0
  474. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +84 -24
  475. package/src/modules/customers/commands/addresses.ts +16 -14
  476. package/src/modules/customers/commands/companies.ts +3 -1
  477. package/src/modules/customers/commands/interactions.ts +50 -4
  478. package/src/modules/customers/commands/people.ts +2 -1
  479. package/src/modules/customers/commands/personCompanyLinks.ts +8 -5
  480. package/src/modules/customers/commands/pipeline-stages.ts +16 -16
  481. package/src/modules/customers/components/AddressFormatSettings.tsx +1 -0
  482. package/src/modules/customers/components/DictionarySettings.tsx +18 -13
  483. package/src/modules/customers/components/DictionarySortSettings.tsx +4 -0
  484. package/src/modules/customers/components/PipelineSettings.tsx +42 -21
  485. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -3
  486. package/src/modules/customers/components/detail/AddressesSection.tsx +4 -0
  487. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +2 -0
  488. package/src/modules/customers/components/detail/DealsSection.tsx +4 -0
  489. package/src/modules/customers/components/detail/EmailCardActions.tsx +5 -0
  490. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +7 -0
  491. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +4 -0
  492. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +4 -0
  493. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +2 -0
  494. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +23 -7
  495. package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +25 -15
  496. package/src/modules/customers/components/detail/hooks/useInteractions.ts +76 -35
  497. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +30 -17
  498. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +2 -0
  499. package/src/modules/customers/components/detail/types.ts +1 -0
  500. package/src/modules/customers/components/formConfig.tsx +2 -0
  501. package/src/modules/customers/data/guards.ts +67 -0
  502. package/src/modules/customers/di.ts +66 -0
  503. package/src/modules/customers/i18n/de.json +2 -0
  504. package/src/modules/customers/i18n/en.json +2 -0
  505. package/src/modules/customers/i18n/es.json +2 -0
  506. package/src/modules/customers/i18n/pl.json +2 -0
  507. package/src/modules/customers/lib/todoCompatibility.ts +14 -0
  508. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +2 -0
  509. package/src/modules/data_sync/api/options.ts +7 -4
  510. package/src/modules/data_sync/api/schedules/route.ts +9 -1
  511. package/src/modules/data_sync/backend/data-sync/page.tsx +18 -5
  512. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +46 -19
  513. package/src/modules/data_sync/lib/sync-schedule-service.ts +11 -0
  514. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +8 -1
  515. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +23 -0
  516. package/src/modules/dictionaries/components/DictionariesManager.tsx +32 -9
  517. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +30 -14
  518. package/src/modules/dictionaries/i18n/de.json +1 -0
  519. package/src/modules/dictionaries/i18n/en.json +1 -0
  520. package/src/modules/dictionaries/i18n/es.json +1 -0
  521. package/src/modules/dictionaries/i18n/pl.json +1 -0
  522. package/src/modules/directory/api/organizations/route.ts +3 -0
  523. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +8 -0
  524. package/src/modules/directory/backend/directory/organizations/page.tsx +10 -5
  525. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +16 -5
  526. package/src/modules/directory/backend/directory/tenants/page.tsx +8 -4
  527. package/src/modules/directory/commands/organizations.ts +7 -4
  528. package/src/modules/entities/api/records.ts +99 -0
  529. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +7 -0
  530. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +8 -4
  531. package/src/modules/entities/lib/helpers.ts +17 -0
  532. package/src/modules/feature_toggles/api/global/[id]/override/route.ts +1 -0
  533. package/src/modules/feature_toggles/api/overrides/route.ts +19 -0
  534. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +19 -13
  535. package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +22 -12
  536. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +7 -2
  537. package/src/modules/feature_toggles/components/formConfig.tsx +2 -1
  538. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +10 -1
  539. package/src/modules/feature_toggles/data/validators.ts +11 -3
  540. package/src/modules/inbox_ops/api/settings/route.ts +18 -0
  541. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +15 -10
  542. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +9 -4
  543. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +20 -11
  544. package/src/modules/integrations/backend/integrations/page.tsx +13 -8
  545. package/src/modules/messages/commands/messages.ts +27 -15
  546. package/src/modules/perspectives/api/[tableId]/route.ts +11 -2
  547. package/src/modules/perspectives/services/perspectiveService.ts +13 -1
  548. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +16 -14
  549. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +6 -3
  550. package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +3 -0
  551. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +58 -15
  552. package/src/modules/planner/components/AvailabilitySchedule.tsx +22 -7
  553. package/src/modules/query_index/lib/engine.ts +34 -0
  554. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +7 -1
  555. package/src/modules/resources/backend/resources/resource-types/page.tsx +6 -3
  556. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +23 -3
  557. package/src/modules/resources/backend/resources/resources/page.tsx +15 -4
  558. package/src/modules/resources/components/ResourceCrudForm.tsx +3 -0
  559. package/src/modules/resources/components/ResourceTypeCrudForm.tsx +2 -0
  560. package/src/modules/sales/api/documents/factory.ts +13 -1
  561. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +6 -0
  562. package/src/modules/sales/backend/sales/channels/offers/page.tsx +10 -4
  563. package/src/modules/sales/backend/sales/channels/page.tsx +19 -4
  564. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +73 -20
  565. package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -0
  566. package/src/modules/sales/commands/documentAddresses.ts +226 -4
  567. package/src/modules/sales/commands/documents.ts +28 -0
  568. package/src/modules/sales/commands/returns.ts +12 -3
  569. package/src/modules/sales/commands/shared.ts +36 -0
  570. package/src/modules/sales/commands/shipments.ts +17 -1
  571. package/src/modules/sales/components/AdjustmentKindSettings.tsx +20 -11
  572. package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -0
  573. package/src/modules/sales/components/OrderEditingSettings.tsx +1 -0
  574. package/src/modules/sales/components/PaymentMethodsSettings.tsx +12 -4
  575. package/src/modules/sales/components/ShippingMethodsSettings.tsx +12 -4
  576. package/src/modules/sales/components/StatusSettings.tsx +20 -11
  577. package/src/modules/sales/components/TaxRatesSettings.tsx +12 -5
  578. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +67 -14
  579. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +7 -4
  580. package/src/modules/sales/components/documents/AddressesSection.tsx +35 -25
  581. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +50 -25
  582. package/src/modules/sales/components/documents/ItemsSection.tsx +24 -13
  583. package/src/modules/sales/components/documents/LineItemDialog.tsx +26 -9
  584. package/src/modules/sales/components/documents/PaymentDialog.tsx +33 -14
  585. package/src/modules/sales/components/documents/PaymentsSection.tsx +22 -10
  586. package/src/modules/sales/components/documents/ReturnDialog.tsx +28 -17
  587. package/src/modules/sales/components/documents/ReturnsSection.tsx +4 -1
  588. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +11 -4
  589. package/src/modules/sales/components/documents/ShipmentDialog.tsx +23 -8
  590. package/src/modules/sales/components/documents/ShipmentsSection.tsx +20 -10
  591. package/src/modules/sales/components/documents/optimisticLock.ts +34 -0
  592. package/src/modules/sales/components/documents/shipmentTypes.ts +1 -0
  593. package/src/modules/sales/di.ts +35 -0
  594. package/src/modules/sales/i18n/de.json +3 -0
  595. package/src/modules/sales/i18n/en.json +3 -0
  596. package/src/modules/sales/i18n/es.json +3 -0
  597. package/src/modules/sales/i18n/pl.json +3 -0
  598. package/src/modules/staff/api/job-histories.ts +12 -2
  599. package/src/modules/staff/api/timesheets/time-entries/route.ts +16 -4
  600. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +12 -7
  601. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +2 -0
  602. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +16 -5
  603. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -2
  604. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +8 -0
  605. package/src/modules/staff/backend/staff/team-roles/page.tsx +6 -2
  606. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +13 -3
  607. package/src/modules/staff/backend/staff/teams/page.tsx +9 -3
  608. package/src/modules/staff/backend/staff/timesheets/page.tsx +10 -1
  609. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -0
  610. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +9 -3
  611. package/src/modules/staff/commands/job-histories.ts +42 -3
  612. package/src/modules/staff/components/LeaveRequestForm.tsx +2 -0
  613. package/src/modules/staff/components/TeamForm.tsx +2 -0
  614. package/src/modules/staff/components/TeamMemberForm.tsx +2 -0
  615. package/src/modules/staff/components/TeamRoleForm.tsx +2 -0
  616. package/src/modules/staff/components/detail/JobHistorySection.tsx +28 -6
  617. package/src/modules/staff/data/validators.ts +6 -0
  618. package/src/modules/staff/i18n/de.json +1 -0
  619. package/src/modules/staff/i18n/en.json +1 -0
  620. package/src/modules/staff/i18n/es.json +1 -0
  621. package/src/modules/staff/i18n/pl.json +1 -0
  622. package/src/modules/staff/lib/leaveRequestHelpers.ts +4 -0
  623. package/src/modules/translations/components/TranslationManager.tsx +13 -8
  624. package/src/modules/workflows/api/definitions/[id]/route.ts +112 -0
  625. package/src/modules/workflows/backend/definitions/[id]/page.tsx +20 -4
  626. package/src/modules/workflows/backend/definitions/page.tsx +20 -9
  627. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +29 -16
  628. package/src/modules/workflows/components/formConfig.tsx +5 -0
  629. package/src/modules/workflows/di.ts +20 -0
  630. package/src/modules/workflows/i18n/de.json +1 -0
  631. package/src/modules/workflows/i18n/en.json +1 -0
  632. package/src/modules/workflows/i18n/es.json +1 -0
  633. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -4,9 +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 { logCrudAccess } from "@open-mercato/shared/lib/crud/factory";
6
6
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
8
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
7
9
  import { RoleAcl, Role } from "@open-mercato/core/modules/auth/data/entities";
8
10
  import { resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
9
- import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
10
11
  import {
11
12
  assertActorCanGrantAcl,
12
13
  assertActorCanModifySuperAdminRoleTarget,
@@ -30,7 +31,8 @@ const metadata = {
30
31
  const roleAclResponseSchema = z.object({
31
32
  isSuperAdmin: z.boolean(),
32
33
  features: z.array(z.string()),
33
- organizations: z.array(z.string()).nullable()
34
+ organizations: z.array(z.string()).nullable(),
35
+ updatedAt: z.string().nullable()
34
36
  });
35
37
  const roleAclUpdateResponseSchema = z.object({
36
38
  ok: z.literal(true),
@@ -83,8 +85,9 @@ async function GET(req) {
83
85
  const response = acl ? {
84
86
  isSuperAdmin: !!acl.isSuperAdmin,
85
87
  features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],
86
- organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null
87
- } : { isSuperAdmin: false, features: [], organizations: null };
88
+ organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,
89
+ updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null
90
+ } : { isSuperAdmin: false, features: [], organizations: null, updatedAt: null };
88
91
  await logCrudAccess({
89
92
  container,
90
93
  auth,
@@ -147,7 +150,19 @@ async function PUT(req) {
147
150
  }
148
151
  }
149
152
  let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId });
150
- if (!acl) {
153
+ if (acl) {
154
+ try {
155
+ enforceCommandOptimisticLock({
156
+ resourceKind: "auth.role_acl",
157
+ resourceId: acl.id,
158
+ current: acl.updatedAt ?? null,
159
+ request: req
160
+ });
161
+ } catch (err) {
162
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
163
+ throw err;
164
+ }
165
+ } else {
151
166
  acl = em.create(RoleAcl, {
152
167
  role,
153
168
  tenantId: targetTenantId,
@@ -177,14 +192,18 @@ async function PUT(req) {
177
192
  throw err;
178
193
  }
179
194
  const aclToPersist = acl;
180
- await withAtomicFlush(em, [
181
- () => {
182
- aclToPersist.organizationsJson = requestedOrganizations;
183
- aclToPersist.isSuperAdmin = requestedIsSuperAdmin;
184
- aclToPersist.featuresJson = requestedFeatures;
185
- em.persist(aclToPersist);
186
- }
187
- ], { transaction: true });
195
+ await withAtomicFlush(
196
+ em,
197
+ [
198
+ () => {
199
+ aclToPersist.organizationsJson = requestedOrganizations;
200
+ aclToPersist.isSuperAdmin = requestedIsSuperAdmin;
201
+ aclToPersist.featuresJson = requestedFeatures;
202
+ em.persist(aclToPersist);
203
+ }
204
+ ],
205
+ { transaction: true }
206
+ );
188
207
  if (targetTenantId) {
189
208
  await rbacService.invalidateTenantCache(targetTenantId);
190
209
  try {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/auth/api/roles/acl/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport {\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n normalizeGrantFeatureList,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\nconst getSchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\nconst putSchema = z.object({\n roleId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\nconst roleAclResponseSchema = z.object({\n isSuperAdmin: z.boolean(),\n features: z.array(z.string()),\n organizations: z.array(z.string()).nullable(),\n})\n\nconst roleAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst roleAclErrorSchema = z.object({ error: z.string() })\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsed = getSchema.safeParse({\n roleId: url.searchParams.get('roleId'),\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = container.resolve('em') as EntityManager\n const authTenantId = auth.tenantId ?? null\n const roleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n roleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, roleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let tenantScope = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== tenantScope) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) tenantScope = parsed.data.tenantId\n else return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (!tenantScope && !isSuperAdmin) tenantScope = authTenantId ?? null\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: tenantScope,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n const acl = tenantScope\n ? await em.findOne(RoleAcl, { role, tenantId: tenantScope })\n : null\n const response = acl\n ? {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],\n organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,\n }\n : { isSuperAdmin: false, features: [], organizations: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.roleId, ...response }],\n idField: 'id',\n resourceKind: 'auth.role_acl',\n organizationId: auth.orgId ?? null,\n tenantId: tenantScope,\n query: { roleId: parsed.data.roleId, tenantId: tenantScope },\n accessType: 'read:item',\n })\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({}))\n const parsed = putSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const rbacService = container.resolve('rbacService') as RbacService\n const authTenantId = auth.tenantId ?? null\n const putRoleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n putRoleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, putRoleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let targetTenantId = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== targetTenantId) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) {\n targetTenantId = parsed.data.tenantId\n } else {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n }\n if (!targetTenantId && !isSuperAdmin) targetTenantId = authTenantId ?? null\n if (!targetTenantId) return NextResponse.json({ error: 'Tenant required' }, { status: 400 })\n\n if (!isSuperAdmin && targetTenantId !== authTenantId) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId })\n if (!acl) {\n acl = em.create(RoleAcl, {\n role,\n tenantId: targetTenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n }\n\n const existingIsSuperAdmin = !!acl.isSuperAdmin\n const existingFeatures = normalizeGrantFeatureList(acl.featuresJson)\n const existingOrganizations = normalizeOrganizations(acl.organizationsJson)\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? existingIsSuperAdmin\n const requestedFeatures = parsed.data.features === undefined\n ? existingFeatures\n : normalizeGrantFeatureList(parsed.data.features)\n const requestedOrganizations = parsed.data.organizations === undefined\n ? existingOrganizations\n : normalizeOrganizations(parsed.data.organizations)\n\n try {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations: requestedOrganizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n const aclToPersist = acl\n await withAtomicFlush(em, [\n () => {\n aclToPersist.organizationsJson = requestedOrganizations\n aclToPersist.isSuperAdmin = requestedIsSuperAdmin\n aclToPersist.featuresJson = requestedFeatures\n em.persist(aclToPersist)\n },\n ], { transaction: true })\n\n // Invalidate cache for all users in this tenant since role ACL changed\n if (targetTenantId) {\n await rbacService.invalidateTenantCache(targetTenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n try {\n const cache = container.resolve('cache') as TaggableCache | undefined\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:tenant:${targetTenantId}`])\n } catch {}\n }\n \n return NextResponse.json({\n ok: true,\n sanitized: false,\n })\n}\n\nfunction normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role ACL management',\n methods: {\n GET: {\n summary: 'Fetch role ACL',\n description: 'Returns the feature and organization assignments associated with a role within the current tenant.',\n query: getSchema,\n responses: [\n { status: 200, description: 'Role ACL entry', schema: roleAclResponseSchema },\n { status: 400, description: 'Invalid role id', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update role ACL',\n description: 'Replaces the feature list, super admin flag, and optional organization assignments for a role.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'Role ACL updated', schema: roleAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n normalizeGrantFeatureList,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\n\ntype TaggableCache = { deleteByTags?: (tags: string[]) => Promise<void> | void }\n\nconst getSchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n})\nconst putSchema = z.object({\n roleId: z.string().uuid(),\n isSuperAdmin: z.boolean().optional(),\n features: z.array(z.string()).optional(),\n organizations: z.array(z.string()).nullable().optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\nconst roleAclResponseSchema = z.object({\n isSuperAdmin: z.boolean(),\n features: z.array(z.string()),\n organizations: z.array(z.string()).nullable(),\n updatedAt: z.string().nullable(),\n})\n\nconst roleAclUpdateResponseSchema = z.object({\n ok: z.literal(true),\n sanitized: z.boolean(),\n})\n\nconst roleAclErrorSchema = z.object({ error: z.string() })\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsed = getSchema.safeParse({\n roleId: url.searchParams.get('roleId'),\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const em = container.resolve('em') as EntityManager\n const authTenantId = auth.tenantId ?? null\n const roleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n roleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, roleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let tenantScope = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== tenantScope) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) tenantScope = parsed.data.tenantId\n else return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (!tenantScope && !isSuperAdmin) tenantScope = authTenantId ?? null\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: tenantScope,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n const acl = tenantScope\n ? await em.findOne(RoleAcl, { role, tenantId: tenantScope })\n : null\n const response = acl\n ? {\n isSuperAdmin: !!acl.isSuperAdmin,\n features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],\n organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,\n updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null,\n }\n : { isSuperAdmin: false, features: [], organizations: null, updatedAt: null }\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items: [{ id: parsed.data.roleId, ...response }],\n idField: 'id',\n resourceKind: 'auth.role_acl',\n organizationId: auth.orgId ?? null,\n tenantId: tenantScope,\n query: { roleId: parsed.data.roleId, tenantId: tenantScope },\n accessType: 'read:item',\n })\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const body = await req.json().catch(() => ({}))\n const parsed = putSchema.safeParse(body)\n if (!parsed.success) return NextResponse.json({ error: 'Invalid input' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const isSuperAdmin = await resolveIsSuperAdmin({ auth, container })\n const rbacService = container.resolve('rbacService') as RbacService\n const authTenantId = auth.tenantId ?? null\n const putRoleFilter: Record<string, unknown> = { id: parsed.data.roleId }\n if (!isSuperAdmin && authTenantId) {\n putRoleFilter.$or = [{ tenantId: authTenantId }, { tenantId: null }]\n }\n const role = await em.findOne(Role, putRoleFilter)\n if (!role) return NextResponse.json({ error: 'Not found' }, { status: 404 })\n\n const roleTenantId = role?.tenantId ? String(role.tenantId) : null\n\n let targetTenantId = parsed.data.tenantId ?? roleTenantId ?? authTenantId ?? null\n if (parsed.data.tenantId && parsed.data.tenantId !== targetTenantId) {\n if (isSuperAdmin || parsed.data.tenantId === authTenantId) {\n targetTenantId = parsed.data.tenantId\n } else {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n }\n if (!targetTenantId && !isSuperAdmin) targetTenantId = authTenantId ?? null\n if (!targetTenantId) return NextResponse.json({ error: 'Tenant required' }, { status: 400 })\n\n if (!isSuperAdmin && targetTenantId !== authTenantId) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n if (!isSuperAdmin && auth.sub) {\n try {\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n targetRoleId: parsed.data.roleId,\n actorIsSuperAdmin: false,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n }\n\n let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId })\n // Optimistic lock: refuse a stale ACL overwrite so two admins editing the same\n // role's features in parallel cannot silently clobber each other (#2055). The\n // check is strictly additive \u2014 when the client sends no expected-version header\n // it is a no-op. Skipped when the ACL row does not exist yet (first grant has\n // no prior version to conflict with).\n if (acl) {\n try {\n enforceCommandOptimisticLock({\n resourceKind: 'auth.role_acl',\n resourceId: acl.id,\n current: acl.updatedAt ?? null,\n request: req,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n } else {\n acl = em.create(RoleAcl, {\n role,\n tenantId: targetTenantId,\n createdAt: new Date(),\n isSuperAdmin: false,\n })\n }\n\n const existingIsSuperAdmin = !!acl.isSuperAdmin\n const existingFeatures = normalizeGrantFeatureList(acl.featuresJson)\n const existingOrganizations = normalizeOrganizations(acl.organizationsJson)\n const requestedIsSuperAdmin = parsed.data.isSuperAdmin ?? existingIsSuperAdmin\n const requestedFeatures = parsed.data.features === undefined\n ? existingFeatures\n : normalizeGrantFeatureList(parsed.data.features)\n const requestedOrganizations = parsed.data.organizations === undefined\n ? existingOrganizations\n : normalizeOrganizations(parsed.data.organizations)\n\n try {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n actorUserId: auth.sub,\n tenantId: targetTenantId,\n organizationId: auth.orgId ?? null,\n isSuperAdmin: requestedIsSuperAdmin,\n features: requestedFeatures,\n organizations: requestedOrganizations,\n })\n } catch (err) {\n if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })\n throw err\n }\n\n // Persist the ACL mutation inside a transaction so the role-permission write\n // commits atomically (proper ACL-edit transaction handling).\n const aclToPersist = acl\n await withAtomicFlush(\n em,\n [\n () => {\n aclToPersist.organizationsJson = requestedOrganizations\n aclToPersist.isSuperAdmin = requestedIsSuperAdmin\n aclToPersist.featuresJson = requestedFeatures\n em.persist(aclToPersist)\n },\n ],\n { transaction: true },\n )\n\n // Invalidate cache for all users in this tenant since role ACL changed\n if (targetTenantId) {\n await rbacService.invalidateTenantCache(targetTenantId)\n // Sidebar nav caches depend on RBAC; invalidate tenant scope nav caches\n try {\n const cache = container.resolve('cache') as TaggableCache | undefined\n if (cache?.deleteByTags) await cache.deleteByTags([`rbac:tenant:${targetTenantId}`])\n } catch {}\n }\n \n return NextResponse.json({\n ok: true,\n sanitized: false,\n })\n}\n\nfunction normalizeOrganizations(organizations: unknown): string[] | null {\n if (!Array.isArray(organizations)) return null\n return normalizeGrantFeatureList(organizations)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role ACL management',\n methods: {\n GET: {\n summary: 'Fetch role ACL',\n description: 'Returns the feature and organization assignments associated with a role within the current tenant.',\n query: getSchema,\n responses: [\n { status: 200, description: 'Role ACL entry', schema: roleAclResponseSchema },\n { status: 400, description: 'Invalid role id', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update role ACL',\n description: 'Replaces the feature list, super admin flag, and optional organization assignments for a role.',\n requestBody: {\n contentType: 'application/json',\n schema: putSchema,\n },\n responses: [\n { status: 200, description: 'Role ACL updated', schema: roleAclUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: roleAclErrorSchema },\n { status: 401, description: 'Unauthorized', schema: roleAclErrorSchema },\n { status: 403, description: 'Insufficient privileges to modify ACL', schema: roleAclErrorSchema },\n { status: 404, description: 'Role not found', schema: roleAclErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -34,7 +34,8 @@ const roleListItemSchema = z.object({
34
34
  usersCount: z.number().int().nonnegative(),
35
35
  tenantId: z.string().uuid().nullable(),
36
36
  tenantIds: z.array(z.string().uuid()).optional(),
37
- tenantName: z.string().nullable()
37
+ tenantName: z.string().nullable(),
38
+ updatedAt: z.string().nullable().optional()
38
39
  });
39
40
  const roleListResponseSchema = z.object({
40
41
  items: z.array(roleListItemSchema),
@@ -200,6 +201,7 @@ async function GET(req) {
200
201
  tenantId: tenantId ?? null,
201
202
  tenantIds: exposeTenant && tenantId ? [tenantId] : [],
202
203
  tenantName: exposeTenant ? tenantName : null,
204
+ updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : null,
203
205
  ...cfByRole[idStr] || {}
204
206
  };
205
207
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/auth/api/roles/route.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { roleCrudEvents, roleCrudIndexer } from '@open-mercato/core/modules/auth/commands/roles'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { assertActorCanModifySuperAdminRoleTarget } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n}).passthrough()\n\nconst roleCreateSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst roleUpdateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nconst roleListItemSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n usersCount: z.number().int().nonnegative(),\n tenantId: z.string().uuid().nullable(),\n tenantIds: z.array(z.string().uuid()).optional(),\n tenantName: z.string().nullable(),\n})\n\nconst roleListResponseSchema = z.object({\n items: z.array(roleListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.roles.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Role,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.roles.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'auth.roles.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request && typeof parsed.id === 'string' && parsed.id.length) {\n await assertCanModifySuperAdminRole(ctx.request, parsed.id)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.roles.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = false\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = !!acl?.isSuperAdmin\n }\n } catch (err) {\n console.error('roles: failed to resolve rbac', err)\n }\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n if (!isSuperAdmin && !actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n let superAdminRoleIds: Set<string> | null = null\n if (!isSuperAdmin && actorTenantId) {\n const superAdminAcls = await findWithDecryption(em, RoleAcl, { tenantId: actorTenantId, isSuperAdmin: true }, {}, { tenantId: actorTenantId, organizationId: null })\n if (superAdminAcls.length) {\n superAdminRoleIds = new Set(\n superAdminAcls\n .map((acl) => {\n const roleRef = acl.role\n const idValue = roleRef?.id\n return idValue ? String(idValue) : null\n })\n .filter((id): id is string => !!id),\n )\n } else {\n superAdminRoleIds = new Set()\n }\n }\n const { id, page, pageSize, search, tenantId: requestedTenantId } = parsed.data\n const tenantFilter = isSuperAdmin && requestedTenantId ? String(requestedTenantId) : null\n const filters: any[] = [{ deletedAt: null }]\n if (id) filters.push({ id })\n if (search) filters.push({ name: { $ilike: `%${escapeLikePattern(search)}%` } })\n if (!isSuperAdmin && actorTenantId) {\n filters.push({ tenantId: actorTenantId })\n filters.push({ name: { $ne: 'superadmin' } })\n if (superAdminRoleIds && superAdminRoleIds.size) {\n filters.push({ id: { $nin: Array.from(superAdminRoleIds) } })\n }\n } else if (tenantFilter) {\n filters.push({ tenantId: tenantFilter })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(Role, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const roleIds = rows.map((r: any) => String(r.id))\n const counts: Record<string, number> = {}\n if (roleIds.length) {\n const userRoleFilter: FilterQuery<UserRole> = { role: { $in: roleIds }, deletedAt: null }\n const links = await findWithDecryption(em, UserRole, userRoleFilter, {}, { tenantId: null, organizationId: null })\n for (const l of links) {\n const rid = String((l as any).role?.id || (l as any).role)\n counts[rid] = (counts[rid] || 0) + 1\n }\n }\n const roleTenantIds = rows\n .map((role: any) => (role.tenantId ? String(role.tenantId) : null))\n .filter((tenantId): tenantId is string => typeof tenantId === 'string' && tenantId.length > 0)\n const uniqueTenantIds = Array.from(new Set(roleTenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await findWithDecryption(em, Tenant, { id: { $in: uniqueTenantIds as any }, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = tenant?.id ? String(tenant.id) : null\n if (!tid) return acc\n const rawName = (tenant as any)?.name\n const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : tid\n acc[tid] = name\n return acc\n }, {})\n }\n const tenantByRole: Record<string, string | null> = {}\n for (const role of rows) {\n const rid = String(role.id)\n tenantByRole[rid] = role.tenantId ? String(role.tenantId) : null\n }\n const tenantFallbacks = Array.from(new Set<string | null>([\n auth.tenantId ?? null,\n tenantFilter ?? null,\n ...Object.values(tenantByRole),\n ]))\n const cfByRole = roleIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.role,\n recordIds: roleIds,\n tenantIdByRecord: tenantByRole,\n tenantFallbacks,\n })\n : {}\n const items = rows.map((r: any) => {\n const idStr = String(r.id)\n const tenantId = tenantByRole[idStr]\n const tenantName = tenantId ? tenantMap[tenantId] ?? tenantId : null\n const exposeTenant = isSuperAdmin || (tenantId && auth.tenantId && tenantId === auth.tenantId)\n return {\n id: idStr,\n name: String(r.name),\n usersCount: counts[idStr] || 0,\n tenantId: tenantId ?? null,\n tenantIds: exposeTenant && tenantId ? [tenantId] : [],\n tenantName: exposeTenant ? tenantName : null,\n ...(cfByRole[idStr] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.role',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = async (req: Request) => {\n const targetId = new URL(req.url).searchParams.get('id')\n if (targetId) {\n try {\n await assertCanModifySuperAdminRole(req, targetId)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n }\n return crud.DELETE(req)\n}\n\nasync function assertCanModifySuperAdminRole(req: Request, targetRoleId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetRoleId,\n })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role management',\n methods: {\n GET: {\n summary: 'List roles',\n description:\n 'Returns available roles within the current tenant. Super administrators receive visibility across tenants.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Role collection', schema: roleListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create role',\n description: 'Creates a new role for the current tenant or globally when `tenantId` is omitted.',\n requestBody: {\n contentType: 'application/json',\n schema: roleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Role created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update role',\n description: 'Updates mutable fields on an existing role.',\n requestBody: {\n contentType: 'application/json',\n schema: roleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Role updated',\n schema: okResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Role not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete role',\n description: 'Deletes a role by identifier. Fails when users remain assigned.',\n query: z.object({ id: z.string().uuid().describe('Role identifier') }),\n responses: [\n { status: 200, description: 'Role deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Role cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Role not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { roleCrudEvents, roleCrudIndexer } from '@open-mercato/core/modules/auth/commands/roles'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { assertActorCanModifySuperAdminRoleTarget } from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n}).passthrough()\n\nconst roleCreateSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst roleUpdateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nconst roleListItemSchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n usersCount: z.number().int().nonnegative(),\n tenantId: z.string().uuid().nullable(),\n tenantIds: z.array(z.string().uuid()).optional(),\n tenantName: z.string().nullable(),\n updatedAt: z.string().nullable().optional(),\n})\n\nconst roleListResponseSchema = z.object({\n items: z.array(roleListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.roles.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.roles.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Role,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.roles.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'auth.roles.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request && typeof parsed.id === 'string' && parsed.id.length) {\n await assertCanModifySuperAdminRole(ctx.request, parsed.id)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.roles.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n tenantId: url.searchParams.get('tenantId') || undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = false\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = !!acl?.isSuperAdmin\n }\n } catch (err) {\n console.error('roles: failed to resolve rbac', err)\n }\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n if (!isSuperAdmin && !actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n let superAdminRoleIds: Set<string> | null = null\n if (!isSuperAdmin && actorTenantId) {\n const superAdminAcls = await findWithDecryption(em, RoleAcl, { tenantId: actorTenantId, isSuperAdmin: true }, {}, { tenantId: actorTenantId, organizationId: null })\n if (superAdminAcls.length) {\n superAdminRoleIds = new Set(\n superAdminAcls\n .map((acl) => {\n const roleRef = acl.role\n const idValue = roleRef?.id\n return idValue ? String(idValue) : null\n })\n .filter((id): id is string => !!id),\n )\n } else {\n superAdminRoleIds = new Set()\n }\n }\n const { id, page, pageSize, search, tenantId: requestedTenantId } = parsed.data\n const tenantFilter = isSuperAdmin && requestedTenantId ? String(requestedTenantId) : null\n const filters: any[] = [{ deletedAt: null }]\n if (id) filters.push({ id })\n if (search) filters.push({ name: { $ilike: `%${escapeLikePattern(search)}%` } })\n if (!isSuperAdmin && actorTenantId) {\n filters.push({ tenantId: actorTenantId })\n filters.push({ name: { $ne: 'superadmin' } })\n if (superAdminRoleIds && superAdminRoleIds.size) {\n filters.push({ id: { $nin: Array.from(superAdminRoleIds) } })\n }\n } else if (tenantFilter) {\n filters.push({ tenantId: tenantFilter })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(Role, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const roleIds = rows.map((r: any) => String(r.id))\n const counts: Record<string, number> = {}\n if (roleIds.length) {\n const userRoleFilter: FilterQuery<UserRole> = { role: { $in: roleIds }, deletedAt: null }\n const links = await findWithDecryption(em, UserRole, userRoleFilter, {}, { tenantId: null, organizationId: null })\n for (const l of links) {\n const rid = String((l as any).role?.id || (l as any).role)\n counts[rid] = (counts[rid] || 0) + 1\n }\n }\n const roleTenantIds = rows\n .map((role: any) => (role.tenantId ? String(role.tenantId) : null))\n .filter((tenantId): tenantId is string => typeof tenantId === 'string' && tenantId.length > 0)\n const uniqueTenantIds = Array.from(new Set(roleTenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await findWithDecryption(em, Tenant, { id: { $in: uniqueTenantIds as any }, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tid = tenant?.id ? String(tenant.id) : null\n if (!tid) return acc\n const rawName = (tenant as any)?.name\n const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : tid\n acc[tid] = name\n return acc\n }, {})\n }\n const tenantByRole: Record<string, string | null> = {}\n for (const role of rows) {\n const rid = String(role.id)\n tenantByRole[rid] = role.tenantId ? String(role.tenantId) : null\n }\n const tenantFallbacks = Array.from(new Set<string | null>([\n auth.tenantId ?? null,\n tenantFilter ?? null,\n ...Object.values(tenantByRole),\n ]))\n const cfByRole = roleIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.role,\n recordIds: roleIds,\n tenantIdByRecord: tenantByRole,\n tenantFallbacks,\n })\n : {}\n const items = rows.map((r: any) => {\n const idStr = String(r.id)\n const tenantId = tenantByRole[idStr]\n const tenantName = tenantId ? tenantMap[tenantId] ?? tenantId : null\n const exposeTenant = isSuperAdmin || (tenantId && auth.tenantId && tenantId === auth.tenantId)\n return {\n id: idStr,\n name: String(r.name),\n usersCount: counts[idStr] || 0,\n tenantId: tenantId ?? null,\n tenantIds: exposeTenant && tenantId ? [tenantId] : [],\n tenantName: exposeTenant ? tenantName : null,\n updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : null,\n ...(cfByRole[idStr] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.role',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = async (req: Request) => {\n const targetId = new URL(req.url).searchParams.get('id')\n if (targetId) {\n try {\n await assertCanModifySuperAdminRole(req, targetId)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n }\n return crud.DELETE(req)\n}\n\nasync function assertCanModifySuperAdminRole(req: Request, targetRoleId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetRoleId,\n })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Role management',\n methods: {\n GET: {\n summary: 'List roles',\n description:\n 'Returns available roles within the current tenant. Super administrators receive visibility across tenants.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Role collection', schema: roleListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create role',\n description: 'Creates a new role for the current tenant or globally when `tenantId` is omitted.',\n requestBody: {\n contentType: 'application/json',\n schema: roleCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'Role created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update role',\n description: 'Updates mutable fields on an existing role.',\n requestBody: {\n contentType: 'application/json',\n schema: roleUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Role updated',\n schema: okResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Role not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete role',\n description: 'Deletes a role by identifier. Fails when users remain assigned.',\n query: z.object({ id: z.string().uuid().describe('Role identifier') }),\n responses: [\n { status: 200, description: 'Role deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Role cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'Role not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["id"]
7
7
  }
@@ -8,13 +8,17 @@ import {
8
8
  sidebarPreferencesScopeSchema
9
9
  } from "../../../data/validators.js";
10
10
  import {
11
+ loadRoleSidebarPreferenceUpdatedAt,
11
12
  loadRoleSidebarPreferences,
12
13
  loadSidebarPreference,
14
+ loadSidebarPreferenceUpdatedAt,
13
15
  saveRoleSidebarPreference,
14
16
  saveSidebarPreference
15
17
  } from "../../../services/sidebarPreferencesService.js";
16
18
  import { SIDEBAR_PREFERENCES_VERSION } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
17
19
  import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
20
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
21
+ import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
18
22
  import { Role, RoleSidebarPreference } from "../../../data/entities.js";
19
23
  import { z } from "zod";
20
24
  const metadata = {
@@ -40,7 +44,8 @@ const sidebarPreferencesResponseSchema = z.object({
40
44
  settings: sidebarSettingsSchema,
41
45
  canApplyToRoles: z.boolean(),
42
46
  roles: z.array(sidebarRoleEntrySchema),
43
- scope: sidebarPreferencesScopeSchema
47
+ scope: sidebarPreferencesScopeSchema,
48
+ updatedAt: z.string().datetime().nullable()
44
49
  });
45
50
  const sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({
46
51
  appliedRoles: z.array(z.string().uuid()),
@@ -127,6 +132,11 @@ async function GET(req) {
127
132
  });
128
133
  const pref = rolePrefs.get(role.id) ?? null;
129
134
  const rolesPayload2 = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale });
135
+ const roleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
136
+ roleId: role.id,
137
+ tenantId: auth.tenantId ?? null,
138
+ locale
139
+ });
130
140
  return NextResponse.json({
131
141
  locale,
132
142
  settings: pref ? {
@@ -139,7 +149,8 @@ async function GET(req) {
139
149
  } : emptySettings(),
140
150
  canApplyToRoles,
141
151
  roles: rolesPayload2,
142
- scope: { type: "role", roleId: role.id }
152
+ scope: { type: "role", roleId: role.id },
153
+ updatedAt: roleVersion?.updatedAt ? roleVersion.updatedAt.toISOString() : null
143
154
  });
144
155
  }
145
156
  const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub;
@@ -150,6 +161,12 @@ async function GET(req) {
150
161
  locale
151
162
  }) : null;
152
163
  const rolesPayload = canApplyToRoles ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale }) : [];
164
+ const userVersion = effectiveUserId ? await loadSidebarPreferenceUpdatedAt(em, {
165
+ userId: effectiveUserId,
166
+ tenantId: auth.tenantId ?? null,
167
+ organizationId: auth.orgId ?? null,
168
+ locale
169
+ }) : null;
153
170
  return NextResponse.json({
154
171
  locale,
155
172
  settings: {
@@ -162,7 +179,8 @@ async function GET(req) {
162
179
  },
163
180
  canApplyToRoles,
164
181
  roles: rolesPayload,
165
- scope: { type: "user" }
182
+ scope: { type: "user" },
183
+ updatedAt: userVersion?.updatedAt ? userVersion.updatedAt.toISOString() : null
166
184
  });
167
185
  }
168
186
  async function PUT(req) {
@@ -257,11 +275,34 @@ async function PUT(req) {
257
275
  if (!role) {
258
276
  return NextResponse.json({ error: "Role not found" }, { status: 404 });
259
277
  }
278
+ const existingRolePref = await loadRoleSidebarPreferenceUpdatedAt(em, {
279
+ roleId: role.id,
280
+ tenantId: auth.tenantId ?? null,
281
+ locale
282
+ });
283
+ if (existingRolePref) {
284
+ try {
285
+ enforceCommandOptimisticLock({
286
+ resourceKind: "auth.role_sidebar_preference",
287
+ resourceId: existingRolePref.id,
288
+ current: existingRolePref.updatedAt ?? null,
289
+ request: req
290
+ });
291
+ } catch (err) {
292
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
293
+ throw err;
294
+ }
295
+ }
260
296
  const saved = await saveRoleSidebarPreference(em, {
261
297
  roleId: role.id,
262
298
  tenantId: auth.tenantId ?? null,
263
299
  locale
264
300
  }, payload);
301
+ const savedRoleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
302
+ roleId: role.id,
303
+ tenantId: auth.tenantId ?? null,
304
+ locale
305
+ });
265
306
  if (cache?.deleteByTags) {
266
307
  try {
267
308
  await cache.deleteByTags([`nav:sidebar:role:${role.id}`]);
@@ -282,6 +323,7 @@ async function PUT(req) {
282
323
  canApplyToRoles,
283
324
  roles: rolesPayload2,
284
325
  scope: { type: "role", roleId: role.id },
326
+ updatedAt: savedRoleVersion?.updatedAt ? savedRoleVersion.updatedAt.toISOString() : null,
285
327
  appliedRoles: [],
286
328
  clearedRoles: []
287
329
  });
@@ -293,6 +335,25 @@ async function PUT(req) {
293
335
  if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {
294
336
  return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
295
337
  }
338
+ const existingUserPref = await loadSidebarPreferenceUpdatedAt(em, {
339
+ userId: effectiveUserId,
340
+ tenantId: auth.tenantId ?? null,
341
+ organizationId: auth.orgId ?? null,
342
+ locale
343
+ });
344
+ if (existingUserPref) {
345
+ try {
346
+ enforceCommandOptimisticLock({
347
+ resourceKind: "auth.sidebar_preference",
348
+ resourceId: existingUserPref.id,
349
+ current: existingUserPref.updatedAt ?? null,
350
+ request: req
351
+ });
352
+ } catch (err) {
353
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status });
354
+ throw err;
355
+ }
356
+ }
296
357
  const settings = await saveSidebarPreference(em, {
297
358
  userId: effectiveUserId,
298
359
  tenantId: auth.tenantId ?? null,
@@ -367,12 +428,19 @@ async function PUT(req) {
367
428
  hasPreference: rolePrefs.has(role.id)
368
429
  }));
369
430
  }
431
+ const savedUserVersion = await loadSidebarPreferenceUpdatedAt(em, {
432
+ userId: effectiveUserId,
433
+ tenantId: auth.tenantId ?? null,
434
+ organizationId: auth.orgId ?? null,
435
+ locale
436
+ });
370
437
  return NextResponse.json({
371
438
  locale,
372
439
  settings,
373
440
  canApplyToRoles,
374
441
  roles: rolesPayload,
375
442
  scope: { type: "user" },
443
+ updatedAt: savedUserVersion?.updatedAt ? savedUserVersion.updatedAt.toISOString() : null,
376
444
  appliedRoles: updatedRoleIds,
377
445
  clearedRoles: filteredClearRoleIds
378
446
  });