@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.5-develop.4534.1.b459babe6d",
3
+ "version": "0.6.5-develop.4544.1.71c003c861",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -237,6 +237,7 @@
237
237
  "html-to-text": "^10.0.0",
238
238
  "mammoth": "^1.9.0",
239
239
  "pdfjs-dist": "^6.0.227",
240
+ "resend": "^6.12.3",
240
241
  "sanitize-html": "^2.13.0",
241
242
  "semver": "^7.8.1",
242
243
  "svix": "^1.95.1",
@@ -244,16 +245,16 @@
244
245
  "zod": "^4.4.3"
245
246
  },
246
247
  "peerDependencies": {
247
- "@open-mercato/ai-assistant": "0.6.5-develop.4534.1.b459babe6d",
248
- "@open-mercato/shared": "0.6.5-develop.4534.1.b459babe6d",
249
- "@open-mercato/ui": "0.6.5-develop.4534.1.b459babe6d",
248
+ "@open-mercato/ai-assistant": "0.6.5-develop.4544.1.71c003c861",
249
+ "@open-mercato/shared": "0.6.5-develop.4544.1.71c003c861",
250
+ "@open-mercato/ui": "0.6.5-develop.4544.1.71c003c861",
250
251
  "react": "^19.0.0",
251
252
  "react-dom": "^19.0.0"
252
253
  },
253
254
  "devDependencies": {
254
- "@open-mercato/ai-assistant": "0.6.5-develop.4534.1.b459babe6d",
255
- "@open-mercato/shared": "0.6.5-develop.4534.1.b459babe6d",
256
- "@open-mercato/ui": "0.6.5-develop.4534.1.b459babe6d",
255
+ "@open-mercato/ai-assistant": "0.6.5-develop.4544.1.71c003c861",
256
+ "@open-mercato/shared": "0.6.5-develop.4544.1.71c003c861",
257
+ "@open-mercato/ui": "0.6.5-develop.4544.1.71c003c861",
257
258
  "@testing-library/dom": "^10.4.1",
258
259
  "@testing-library/jest-dom": "^6.9.1",
259
260
  "@testing-library/react": "^16.3.1",
@@ -0,0 +1,172 @@
1
+ import { expect, type APIRequestContext, type Page } from '@playwright/test'
2
+ import {
3
+ OPTIMISTIC_LOCK_HEADER_NAME,
4
+ OPTIMISTIC_LOCK_CONFLICT_CODE,
5
+ } from '@open-mercato/shared/lib/crud/optimistic-lock-headers'
6
+
7
+ /**
8
+ * Shared helpers for the browser-driven optimistic-lock specs
9
+ * (`TC-LOCK-OSS-014..046`). They make the conflict deterministic without two
10
+ * real tabs or sleeps: the spec loads an edit page in the browser (the form
11
+ * captures the record's `updated_at`), then advances `updated_at` out-of-band
12
+ * via a header-less API PUT (additive path, always succeeds), and finally edits
13
+ * + saves in the browser so the now-stale header triggers the 409 → conflict bar.
14
+ *
15
+ * See `packages/core/src/modules/sales/__integration__/__concurrent_edit_pattern.md`
16
+ * and the conflict bar component
17
+ * `packages/ui/src/backend/conflicts/RecordConflictBanner.tsx`
18
+ * (`data-testid="record-conflict-banner"`).
19
+ */
20
+
21
+ const BASE_URL = process.env.BASE_URL?.trim() || ''
22
+
23
+ export function resolveApiUrl(path: string): string {
24
+ return BASE_URL ? `${BASE_URL}${path}` : path
25
+ }
26
+
27
+ export const CONFLICT_BANNER_TESTID = 'record-conflict-banner'
28
+
29
+ /**
30
+ * The enterprise `record_locks` module (enabled in CI via
31
+ * `OM_ENABLE_ENTERPRISE_MODULES=true`) supersedes the OSS banner with a richer
32
+ * "Conflict detected" resolution dialog. Both are valid surfaces for "the stale
33
+ * write was refused": on a CrudForm edit page either one can win depending on
34
+ * whether the record_locks incoming-changes SSE (fired by the out-of-band bump)
35
+ * is processed before the browser's stale save reaches the server. Asserting one
36
+ * fixed surface is therefore racy; we wait for whichever conflict surface appears.
37
+ * (List-delete / non-form flows have no record_locks lock and only ever surface
38
+ * the OSS banner, so matching either surface is safe there too.)
39
+ */
40
+ export const CONFLICT_DIALOG_TESTID = 'record-lock-conflict-dialog'
41
+
42
+ function authHeaders(token: string, lockValue?: string): Record<string, string> {
43
+ const headers: Record<string, string> = {
44
+ Authorization: `Bearer ${token}`,
45
+ 'Content-Type': 'application/json',
46
+ }
47
+ if (lockValue !== undefined) headers[OPTIMISTIC_LOCK_HEADER_NAME] = lockValue
48
+ return headers
49
+ }
50
+
51
+ /**
52
+ * Read a record's current `updated_at` from a list-shaped CRUD GET
53
+ * (`GET <basePath>?id=<id>` → `items[0].updated_at`), normalized to ISO.
54
+ * Works for the `makeCrudRoute` list responses (snake or camel case).
55
+ */
56
+ export async function readUpdatedAt(
57
+ request: APIRequestContext,
58
+ token: string,
59
+ basePath: string,
60
+ id: string,
61
+ idParam = 'id',
62
+ ): Promise<string> {
63
+ const response = await request.fetch(
64
+ resolveApiUrl(`${basePath}?${idParam}=${encodeURIComponent(id)}`),
65
+ { method: 'GET', headers: authHeaders(token) },
66
+ )
67
+ expect(response.status(), `GET ${basePath}?${idParam}=... should be 200`).toBe(200)
68
+ const body = (await response.json()) as
69
+ | { items?: Array<Record<string, unknown>> }
70
+ | Record<string, unknown>
71
+ const item = Array.isArray((body as { items?: unknown[] }).items)
72
+ ? (body as { items: Array<Record<string, unknown>> }).items[0]
73
+ : (body as Record<string, unknown>)
74
+ expect(item, `response should include the record for id=${id}`).toBeTruthy()
75
+ const raw = (item?.updated_at ?? item?.updatedAt) as string | undefined
76
+ expect(typeof raw, `record should expose updated_at, got ${String(raw)}`).toBe('string')
77
+ const ms = Date.parse(raw as string)
78
+ expect(Number.isFinite(ms), `updated_at should parse, got ${String(raw)}`).toBe(true)
79
+ return new Date(ms).toISOString()
80
+ }
81
+
82
+ /**
83
+ * Advance a record's `updated_at` out-of-band so the browser's loaded form now
84
+ * holds a stale token. Uses a **header-less** PUT (the strictly-additive path
85
+ * always succeeds and bumps `updated_at`). Returns the new ISO `updated_at`.
86
+ */
87
+ export async function bumpRecordViaApi(
88
+ request: APIRequestContext,
89
+ token: string,
90
+ basePath: string,
91
+ putBody: Record<string, unknown>,
92
+ opts: { idParam?: string; method?: 'PUT' | 'PATCH' } = {},
93
+ ): Promise<string | null> {
94
+ const response = await request.fetch(resolveApiUrl(basePath), {
95
+ method: opts.method ?? 'PUT',
96
+ headers: authHeaders(token),
97
+ data: putBody,
98
+ })
99
+ expect(
100
+ response.status(),
101
+ `out-of-band ${opts.method ?? 'PUT'} ${basePath} should succeed (additive path), got ${response.status()}`,
102
+ ).toBeLessThan(300)
103
+ const id = putBody[opts.idParam ?? 'id']
104
+ if (typeof id === 'string') {
105
+ try {
106
+ return await readUpdatedAt(request, token, basePath, id, opts.idParam)
107
+ } catch {
108
+ return null
109
+ }
110
+ }
111
+ return null
112
+ }
113
+
114
+ /** Direct API helpers to assert the 409 contract body (used by the negative/UX specs). */
115
+ export async function putWithLock(
116
+ request: APIRequestContext,
117
+ token: string,
118
+ basePath: string,
119
+ body: Record<string, unknown>,
120
+ lockValue: string,
121
+ ) {
122
+ return request.fetch(resolveApiUrl(basePath), {
123
+ method: 'PUT',
124
+ headers: authHeaders(token, lockValue),
125
+ data: body,
126
+ })
127
+ }
128
+
129
+ export async function expectConflictBody(response: { status(): number; json(): Promise<unknown> }) {
130
+ expect(response.status(), 'stale write should be 409').toBe(409)
131
+ const body = (await response.json()) as { code?: string; currentUpdatedAt?: string; expectedUpdatedAt?: string }
132
+ expect(body.code, 'body.code should be the optimistic-lock conflict code').toBe(OPTIMISTIC_LOCK_CONFLICT_CODE)
133
+ return body
134
+ }
135
+
136
+ /**
137
+ * Assert a stale save was refused and surfaced as a conflict. Matches EITHER the
138
+ * OSS `record-conflict-banner` OR the enterprise record_locks "Conflict detected"
139
+ * dialog — see `CONFLICT_DIALOG_TESTID` for why both are valid and why pinning one
140
+ * is racy when enterprise modules are enabled.
141
+ */
142
+ export async function expectConflictBanner(page: Page): Promise<void> {
143
+ const conflictSurface = page
144
+ .getByTestId(CONFLICT_BANNER_TESTID)
145
+ .or(page.getByTestId(CONFLICT_DIALOG_TESTID))
146
+ await expect(
147
+ conflictSurface.first(),
148
+ 'a conflict surface (OSS bar or record_locks dialog) should appear after a stale save',
149
+ ).toBeVisible({ timeout: 10_000 })
150
+ }
151
+
152
+ /** Assert no conflict surface is present (a clean single-tab save must not 409). */
153
+ export async function expectNoConflictBanner(page: Page): Promise<void> {
154
+ await expect(
155
+ page.getByTestId(CONFLICT_BANNER_TESTID),
156
+ 'a clean save must not surface a false-positive conflict bar',
157
+ ).toHaveCount(0)
158
+ await expect(
159
+ page.getByTestId(CONFLICT_DIALOG_TESTID),
160
+ 'a clean save must not surface a false-positive conflict dialog',
161
+ ).toHaveCount(0)
162
+ }
163
+
164
+ /** Click the conflict bar's Refresh button. */
165
+ export async function clickConflictRefresh(page: Page): Promise<void> {
166
+ await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole('button', { name: /refresh/i }).click()
167
+ }
168
+
169
+ /** Dismiss the conflict bar via its close (X) button. */
170
+ export async function dismissConflictBanner(page: Page): Promise<void> {
171
+ await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole('button', { name: /dismiss/i }).click()
172
+ }
@@ -73,6 +73,35 @@ export async function createOrderLineFixture(
73
73
  );
74
74
  }
75
75
 
76
+ /**
77
+ * Probe whether the authenticated principal can create a sales order on the
78
+ * current tenant (i.e. holds `sales.orders.manage`). Sales-write integration
79
+ * specs use this to self-skip on dev databases whose role ACLs were never
80
+ * synced (`yarn mercato auth sync-role-acls`) rather than fail spuriously —
81
+ * CI bootstraps a fully-synced tenant so the probe passes there. The probed
82
+ * order is deleted immediately so the check leaves no residue.
83
+ */
84
+ export async function canManageSalesOrders(
85
+ request: APIRequestContext,
86
+ token: string,
87
+ ): Promise<boolean> {
88
+ const response = await apiRequest(request, 'POST', '/api/sales/orders', {
89
+ token,
90
+ data: { currencyCode: 'USD' },
91
+ });
92
+ if (response.status() === 403) return false;
93
+ if (!response.ok()) return false;
94
+ const id = readId((await response.json()) as unknown, ['id', 'orderId']);
95
+ if (id) {
96
+ try {
97
+ await apiRequest(request, 'DELETE', '/api/sales/orders', { token, data: { id } });
98
+ } catch {
99
+ // best-effort cleanup
100
+ }
101
+ }
102
+ return true;
103
+ }
104
+
76
105
  export async function deleteSalesEntityIfExists(
77
106
  request: APIRequestContext,
78
107
  token: string | null,
@@ -6,7 +6,8 @@ import { DataTable } from '@open-mercato/ui/backend/DataTable'
6
6
  import type { ColumnDef } from '@tanstack/react-table'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
9
- import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
10
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
10
11
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
11
12
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
12
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -25,6 +26,7 @@ type Row = {
25
26
  lastUsedAt: string | null
26
27
  expiresAt: string | null
27
28
  roles: RoleSummary[]
29
+ updatedAt?: string | null
28
30
  }
29
31
 
30
32
  type ResponsePayload = {
@@ -104,10 +106,13 @@ export default function ApiKeysListPage() {
104
106
  })
105
107
  if (!confirmed) return
106
108
  try {
107
- const call = await apiCall<{ error?: string }>(
108
- `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,
109
- { method: 'DELETE' },
110
- { fallback: null },
109
+ const call = await withScopedApiRequestHeaders(
110
+ buildOptimisticLockHeader(row.updatedAt),
111
+ () => apiCall<{ error?: string }>(
112
+ `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,
113
+ { method: 'DELETE' },
114
+ { fallback: null },
115
+ ),
111
116
  )
112
117
  if (!call.ok) {
113
118
  const errorPayload = call.result as { error?: string } | undefined
@@ -22,7 +22,8 @@ import {
22
22
  DialogHeader,
23
23
  DialogTitle,
24
24
  } from '@open-mercato/ui/primitives/dialog'
25
- import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
25
+ import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
26
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
26
27
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
27
28
  import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
28
29
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -41,6 +42,7 @@ type Partition = {
41
42
  configJson: Record<string, unknown> | null
42
43
  envKey: string
43
44
  createdAt: string | null
45
+ updatedAt?: string | null
44
46
  }
45
47
 
46
48
  type DialogState =
@@ -230,11 +232,15 @@ export function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSe
230
232
  dialog.mode === 'edit'
231
233
  ? JSON.stringify({ id: dialog.entry.id, ...payload })
232
234
  : JSON.stringify(payload)
233
- const call = await apiCall('/api/attachments/partitions', {
234
- method,
235
- headers: { 'content-type': 'application/json' },
236
- body,
237
- })
235
+ const lockHeader =
236
+ dialog.mode === 'edit' ? buildOptimisticLockHeader(dialog.entry.updatedAt) : {}
237
+ const call = await withScopedApiRequestHeaders(lockHeader, () =>
238
+ apiCall('/api/attachments/partitions', {
239
+ method,
240
+ headers: { 'content-type': 'application/json' },
241
+ body,
242
+ }),
243
+ )
238
244
  if (!call.ok) {
239
245
  await raiseCrudError(
240
246
  call.response,
@@ -271,9 +277,13 @@ export function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSe
271
277
  })
272
278
  if (!confirmed) return
273
279
  try {
274
- const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {
275
- method: 'DELETE',
276
- })
280
+ const call = await withScopedApiRequestHeaders(
281
+ buildOptimisticLockHeader(entry.updatedAt),
282
+ () =>
283
+ apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {
284
+ method: 'DELETE',
285
+ }),
286
+ )
277
287
  if (!call.ok) {
278
288
  await raiseCrudError(
279
289
  call.response,
@@ -5,11 +5,12 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'
7
7
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
9
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
8
10
  import { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'
9
11
  import type { EntityManager } from '@mikro-orm/postgresql'
10
12
  import { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
11
13
  import { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
12
- import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
13
14
  import {
14
15
  assertActorCanGrantAcl,
15
16
  assertActorCanModifySuperAdminRoleTarget,
@@ -39,6 +40,7 @@ const roleAclResponseSchema = z.object({
39
40
  isSuperAdmin: z.boolean(),
40
41
  features: z.array(z.string()),
41
42
  organizations: z.array(z.string()).nullable(),
43
+ updatedAt: z.string().nullable(),
42
44
  })
43
45
 
44
46
  const roleAclUpdateResponseSchema = z.object({
@@ -101,8 +103,9 @@ export async function GET(req: Request) {
101
103
  isSuperAdmin: !!acl.isSuperAdmin,
102
104
  features: Array.isArray(acl.featuresJson) ? acl.featuresJson : [],
103
105
  organizations: Array.isArray(acl.organizationsJson) ? acl.organizationsJson : null,
106
+ updatedAt: acl.updatedAt instanceof Date ? acl.updatedAt.toISOString() : null,
104
107
  }
105
- : { isSuperAdmin: false, features: [], organizations: null }
108
+ : { isSuperAdmin: false, features: [], organizations: null, updatedAt: null }
106
109
 
107
110
  await logCrudAccess({
108
111
  container,
@@ -173,7 +176,24 @@ export async function PUT(req: Request) {
173
176
  }
174
177
 
175
178
  let acl = await em.findOne(RoleAcl, { role, tenantId: targetTenantId })
176
- if (!acl) {
179
+ // Optimistic lock: refuse a stale ACL overwrite so two admins editing the same
180
+ // role's features in parallel cannot silently clobber each other (#2055). The
181
+ // check is strictly additive — when the client sends no expected-version header
182
+ // it is a no-op. Skipped when the ACL row does not exist yet (first grant has
183
+ // no prior version to conflict with).
184
+ if (acl) {
185
+ try {
186
+ enforceCommandOptimisticLock({
187
+ resourceKind: 'auth.role_acl',
188
+ resourceId: acl.id,
189
+ current: acl.updatedAt ?? null,
190
+ request: req,
191
+ })
192
+ } catch (err) {
193
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
194
+ throw err
195
+ }
196
+ } else {
177
197
  acl = em.create(RoleAcl, {
178
198
  role,
179
199
  tenantId: targetTenantId,
@@ -209,15 +229,21 @@ export async function PUT(req: Request) {
209
229
  throw err
210
230
  }
211
231
 
232
+ // Persist the ACL mutation inside a transaction so the role-permission write
233
+ // commits atomically (proper ACL-edit transaction handling).
212
234
  const aclToPersist = acl
213
- await withAtomicFlush(em, [
214
- () => {
215
- aclToPersist.organizationsJson = requestedOrganizations
216
- aclToPersist.isSuperAdmin = requestedIsSuperAdmin
217
- aclToPersist.featuresJson = requestedFeatures
218
- em.persist(aclToPersist)
219
- },
220
- ], { transaction: true })
235
+ await withAtomicFlush(
236
+ em,
237
+ [
238
+ () => {
239
+ aclToPersist.organizationsJson = requestedOrganizations
240
+ aclToPersist.isSuperAdmin = requestedIsSuperAdmin
241
+ aclToPersist.featuresJson = requestedFeatures
242
+ em.persist(aclToPersist)
243
+ },
244
+ ],
245
+ { transaction: true },
246
+ )
221
247
 
222
248
  // Invalidate cache for all users in this tenant since role ACL changed
223
249
  if (targetTenantId) {
@@ -43,6 +43,7 @@ const roleListItemSchema = z.object({
43
43
  tenantId: z.string().uuid().nullable(),
44
44
  tenantIds: z.array(z.string().uuid()).optional(),
45
45
  tenantName: z.string().nullable(),
46
+ updatedAt: z.string().nullable().optional(),
46
47
  })
47
48
 
48
49
  const roleListResponseSchema = z.object({
@@ -223,6 +224,7 @@ export async function GET(req: Request) {
223
224
  tenantId: tenantId ?? null,
224
225
  tenantIds: exposeTenant && tenantId ? [tenantId] : [],
225
226
  tenantName: exposeTenant ? tenantName : null,
227
+ updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : null,
226
228
  ...(cfByRole[idStr] || {}),
227
229
  }
228
230
  })
@@ -9,13 +9,17 @@ import {
9
9
  sidebarPreferencesScopeSchema,
10
10
  } from '../../../data/validators'
11
11
  import {
12
+ loadRoleSidebarPreferenceUpdatedAt,
12
13
  loadRoleSidebarPreferences,
13
14
  loadSidebarPreference,
15
+ loadSidebarPreferenceUpdatedAt,
14
16
  saveRoleSidebarPreference,
15
17
  saveSidebarPreference,
16
18
  } from '../../../services/sidebarPreferencesService'
17
19
  import { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'
18
20
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
21
+ import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
22
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
19
23
  import { Role, RoleSidebarPreference } from '../../../data/entities'
20
24
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
21
25
  import { z } from 'zod'
@@ -47,6 +51,7 @@ const sidebarPreferencesResponseSchema = z.object({
47
51
  canApplyToRoles: z.boolean(),
48
52
  roles: z.array(sidebarRoleEntrySchema),
49
53
  scope: sidebarPreferencesScopeSchema,
54
+ updatedAt: z.string().datetime().nullable(),
50
55
  })
51
56
 
52
57
  const sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({
@@ -165,6 +170,11 @@ export async function GET(req: Request) {
165
170
  })
166
171
  const pref = rolePrefs.get(role.id) ?? null
167
172
  const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })
173
+ const roleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
174
+ roleId: role.id,
175
+ tenantId: auth.tenantId ?? null,
176
+ locale,
177
+ })
168
178
  return NextResponse.json({
169
179
  locale,
170
180
  settings: pref
@@ -180,6 +190,7 @@ export async function GET(req: Request) {
180
190
  canApplyToRoles,
181
191
  roles: rolesPayload,
182
192
  scope: { type: 'role', roleId: role.id },
193
+ updatedAt: roleVersion?.updatedAt ? roleVersion.updatedAt.toISOString() : null,
183
194
  })
184
195
  }
185
196
 
@@ -198,6 +209,15 @@ export async function GET(req: Request) {
198
209
  ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })
199
210
  : []
200
211
 
212
+ const userVersion = effectiveUserId
213
+ ? await loadSidebarPreferenceUpdatedAt(em, {
214
+ userId: effectiveUserId,
215
+ tenantId: auth.tenantId ?? null,
216
+ organizationId: auth.orgId ?? null,
217
+ locale,
218
+ })
219
+ : null
220
+
201
221
  return NextResponse.json({
202
222
  locale,
203
223
  settings: {
@@ -211,6 +231,7 @@ export async function GET(req: Request) {
211
231
  canApplyToRoles,
212
232
  roles: rolesPayload,
213
233
  scope: { type: 'user' },
234
+ updatedAt: userVersion?.updatedAt ? userVersion.updatedAt.toISOString() : null,
214
235
  })
215
236
  }
216
237
 
@@ -318,11 +339,34 @@ export async function PUT(req: Request) {
318
339
  if (!role) {
319
340
  return NextResponse.json({ error: 'Role not found' }, { status: 404 })
320
341
  }
342
+ const existingRolePref = await loadRoleSidebarPreferenceUpdatedAt(em, {
343
+ roleId: role.id,
344
+ tenantId: auth.tenantId ?? null,
345
+ locale,
346
+ })
347
+ if (existingRolePref) {
348
+ try {
349
+ enforceCommandOptimisticLock({
350
+ resourceKind: 'auth.role_sidebar_preference',
351
+ resourceId: existingRolePref.id,
352
+ current: existingRolePref.updatedAt ?? null,
353
+ request: req,
354
+ })
355
+ } catch (err) {
356
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
357
+ throw err
358
+ }
359
+ }
321
360
  const saved = await saveRoleSidebarPreference(em, {
322
361
  roleId: role.id,
323
362
  tenantId: auth.tenantId ?? null,
324
363
  locale,
325
364
  }, payload)
365
+ const savedRoleVersion = await loadRoleSidebarPreferenceUpdatedAt(em, {
366
+ roleId: role.id,
367
+ tenantId: auth.tenantId ?? null,
368
+ locale,
369
+ })
326
370
  if (cache?.deleteByTags) {
327
371
  try {
328
372
  await cache.deleteByTags([`nav:sidebar:role:${role.id}`])
@@ -342,6 +386,7 @@ export async function PUT(req: Request) {
342
386
  canApplyToRoles,
343
387
  roles: rolesPayload,
344
388
  scope: { type: 'role', roleId: role.id },
389
+ updatedAt: savedRoleVersion?.updatedAt ? savedRoleVersion.updatedAt.toISOString() : null,
345
390
  appliedRoles: [],
346
391
  clearedRoles: [],
347
392
  })
@@ -356,6 +401,26 @@ export async function PUT(req: Request) {
356
401
  return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })
357
402
  }
358
403
 
404
+ const existingUserPref = await loadSidebarPreferenceUpdatedAt(em, {
405
+ userId: effectiveUserId,
406
+ tenantId: auth.tenantId ?? null,
407
+ organizationId: auth.orgId ?? null,
408
+ locale,
409
+ })
410
+ if (existingUserPref) {
411
+ try {
412
+ enforceCommandOptimisticLock({
413
+ resourceKind: 'auth.sidebar_preference',
414
+ resourceId: existingUserPref.id,
415
+ current: existingUserPref.updatedAt ?? null,
416
+ request: req,
417
+ })
418
+ } catch (err) {
419
+ if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
420
+ throw err
421
+ }
422
+ }
423
+
359
424
  const settings = await saveSidebarPreference(em, {
360
425
  userId: effectiveUserId,
361
426
  tenantId: auth.tenantId ?? null,
@@ -444,12 +509,20 @@ export async function PUT(req: Request) {
444
509
  }))
445
510
  }
446
511
 
512
+ const savedUserVersion = await loadSidebarPreferenceUpdatedAt(em, {
513
+ userId: effectiveUserId,
514
+ tenantId: auth.tenantId ?? null,
515
+ organizationId: auth.orgId ?? null,
516
+ locale,
517
+ })
518
+
447
519
  return NextResponse.json({
448
520
  locale,
449
521
  settings,
450
522
  canApplyToRoles,
451
523
  roles: rolesPayload,
452
524
  scope: { type: 'user' },
525
+ updatedAt: savedUserVersion?.updatedAt ? savedUserVersion.updatedAt.toISOString() : null,
453
526
  appliedRoles: updatedRoleIds,
454
527
  clearedRoles: filteredClearRoleIds,
455
528
  })