@open-mercato/core 0.6.5-develop.4534.1.b459babe6d → 0.6.5-develop.4559.1.839e136509

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 (644) 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/communicationChannelsFixtures.js.map +2 -2
  10. package/dist/helpers/integration/dbFixtures.js +2 -1
  11. package/dist/helpers/integration/dbFixtures.js.map +2 -2
  12. package/dist/helpers/integration/optimisticLockUi.js +104 -0
  13. package/dist/helpers/integration/optimisticLockUi.js.map +7 -0
  14. package/dist/helpers/integration/salesFixtures.js +17 -0
  15. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  16. package/dist/modules/api_keys/backend/api-keys/page.js +9 -5
  17. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  18. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +17 -9
  19. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  20. package/dist/modules/auth/api/roles/acl/route.js +32 -13
  21. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  22. package/dist/modules/auth/api/roles/route.js +3 -1
  23. package/dist/modules/auth/api/roles/route.js.map +2 -2
  24. package/dist/modules/auth/api/sidebar/preferences/route.js +71 -3
  25. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  26. package/dist/modules/auth/api/users/acl/route.js +42 -19
  27. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  28. package/dist/modules/auth/api/users/route.js +3 -1
  29. package/dist/modules/auth/api/users/route.js.map +2 -2
  30. package/dist/modules/auth/backend/roles/[id]/edit/page.js +24 -4
  31. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  32. package/dist/modules/auth/backend/roles/page.js +8 -4
  33. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  34. package/dist/modules/auth/backend/users/[id]/edit/page.js +27 -5
  35. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  36. package/dist/modules/auth/backend/users/page.js +6 -2
  37. package/dist/modules/auth/backend/users/page.js.map +2 -2
  38. package/dist/modules/auth/components/AclEditor.js +3 -1
  39. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  40. package/dist/modules/auth/data/entities.js +6 -0
  41. package/dist/modules/auth/data/entities.js.map +2 -2
  42. package/dist/modules/auth/services/sidebarPreferencesService.js +32 -4
  43. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  44. package/dist/modules/business_rules/api/rules/route.js +28 -0
  45. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  46. package/dist/modules/business_rules/api/sets/route.js +28 -0
  47. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  48. package/dist/modules/business_rules/backend/rules/[id]/page.js +11 -4
  49. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +3 -3
  50. package/dist/modules/business_rules/backend/rules/page.js +20 -11
  51. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  52. package/dist/modules/business_rules/backend/sets/[id]/page.js +11 -4
  53. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  54. package/dist/modules/business_rules/backend/sets/page.js +20 -11
  55. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  56. package/dist/modules/catalog/api/categories/route.js +2 -0
  57. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  58. package/dist/modules/catalog/api/products/route.js +2 -1
  59. package/dist/modules/catalog/api/products/route.js.map +2 -2
  60. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -0
  61. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  62. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +94 -40
  63. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  64. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +37 -8
  65. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  66. package/dist/modules/catalog/backend/catalog/products/optionSchemaClient.js.map +2 -2
  67. package/dist/modules/catalog/commands/variants.js +32 -31
  68. package/dist/modules/catalog/commands/variants.js.map +2 -2
  69. package/dist/modules/catalog/components/PriceKindSettings.js +12 -5
  70. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  71. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  72. package/dist/modules/catalog/components/products/ProductMediaManager.js.map +2 -2
  73. package/dist/modules/catalog/components/products/ProductsDataTable.js +5 -3
  74. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  75. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  76. package/dist/modules/catalog/components/products/variantForm.js +2 -1
  77. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  78. package/dist/modules/communication_channels/api/post/test-seed/route.js +23 -2
  79. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  80. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +5 -0
  81. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  82. package/dist/modules/communication_channels/commands/set-primary-channel.js +2 -1
  83. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +2 -2
  84. package/dist/modules/currencies/backend/currencies/[id]/page.js +6 -3
  85. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  86. package/dist/modules/currencies/backend/currencies/page.js +18 -11
  87. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  88. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +1 -0
  89. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  90. package/dist/modules/currencies/backend/exchange-rates/page.js +10 -6
  91. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  92. package/dist/modules/currencies/commands/currencies.js +7 -5
  93. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  94. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +26 -19
  95. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  96. package/dist/modules/customer_accounts/api/admin/roles/[id].js +28 -5
  97. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  98. package/dist/modules/customer_accounts/api/admin/roles.js +4 -2
  99. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  100. package/dist/modules/customer_accounts/api/admin/users/[id].js +28 -5
  101. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  102. package/dist/modules/customer_accounts/api/admin/users.js +2 -0
  103. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  104. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +16 -8
  105. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  106. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +8 -4
  107. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  108. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +8 -4
  109. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +2 -2
  110. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +29 -18
  111. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  112. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +18 -11
  113. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  114. package/dist/modules/customers/api/companies/route.js +13 -2
  115. package/dist/modules/customers/api/companies/route.js.map +2 -2
  116. package/dist/modules/customers/api/deals/route.js +2 -0
  117. package/dist/modules/customers/api/deals/route.js.map +2 -2
  118. package/dist/modules/customers/api/people/route.js +11 -2
  119. package/dist/modules/customers/api/people/route.js.map +2 -2
  120. package/dist/modules/customers/api/todos/route.js +1 -0
  121. package/dist/modules/customers/api/todos/route.js.map +2 -2
  122. package/dist/modules/customers/backend/config/customers/deals/page.js.map +2 -2
  123. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +34 -21
  124. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  125. package/dist/modules/customers/backend/customers/companies/[id]/page.js +45 -27
  126. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  127. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  128. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +22 -5
  129. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  130. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +30 -8
  131. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +2 -2
  132. package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -0
  133. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/deals/page.js +16 -6
  135. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  136. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +62 -39
  137. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  138. package/dist/modules/customers/backend/customers/people/[id]/page.js +41 -26
  139. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  140. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  141. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +50 -23
  142. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  143. package/dist/modules/customers/commands/addresses.js +16 -14
  144. package/dist/modules/customers/commands/addresses.js.map +2 -2
  145. package/dist/modules/customers/commands/companies.js +1 -1
  146. package/dist/modules/customers/commands/companies.js.map +2 -2
  147. package/dist/modules/customers/commands/interactions.js +41 -4
  148. package/dist/modules/customers/commands/interactions.js.map +2 -2
  149. package/dist/modules/customers/commands/people.js +1 -1
  150. package/dist/modules/customers/commands/people.js.map +2 -2
  151. package/dist/modules/customers/commands/personCompanyLinks.js +8 -5
  152. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  153. package/dist/modules/customers/commands/pipeline-stages.js +13 -11
  154. package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
  155. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  156. package/dist/modules/customers/components/DictionarySettings.js +20 -13
  157. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  158. package/dist/modules/customers/components/DictionarySortSettings.js +4 -0
  159. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  160. package/dist/modules/customers/components/PipelineSettings.js +38 -23
  161. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  162. package/dist/modules/customers/components/detail/ActivityTimeline.js +1 -1
  163. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  164. package/dist/modules/customers/components/detail/AddressesSection.js +4 -0
  165. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  166. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +28 -22
  167. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  168. package/dist/modules/customers/components/detail/DealsSection.js +36 -24
  169. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  170. package/dist/modules/customers/components/detail/EmailCardActions.js +5 -0
  171. package/dist/modules/customers/components/detail/EmailCardActions.js.map +2 -2
  172. package/dist/modules/customers/components/detail/EntityTagsDialog.js +7 -0
  173. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  174. package/dist/modules/customers/components/detail/ManageTagsDialog.js +34 -22
  175. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
  176. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +41 -29
  177. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  178. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +14 -8
  179. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  180. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +14 -6
  181. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  182. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +29 -13
  183. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +2 -2
  184. package/dist/modules/customers/components/detail/hooks/useInteractions.js +77 -35
  185. package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
  186. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +25 -17
  187. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  188. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  189. package/dist/modules/customers/components/formConfig.js.map +2 -2
  190. package/dist/modules/customers/data/guards.js +66 -0
  191. package/dist/modules/customers/data/guards.js.map +7 -0
  192. package/dist/modules/customers/di.js +37 -0
  193. package/dist/modules/customers/di.js.map +2 -2
  194. package/dist/modules/customers/lib/todoCompatibility.js +11 -0
  195. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  196. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  197. package/dist/modules/data_sync/api/options.js +4 -4
  198. package/dist/modules/data_sync/api/options.js.map +2 -2
  199. package/dist/modules/data_sync/api/schedules/route.js +9 -1
  200. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  201. package/dist/modules/data_sync/backend/data-sync/page.js +17 -8
  202. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  203. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +43 -22
  204. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  205. package/dist/modules/data_sync/lib/sync-schedule-service.js +9 -0
  206. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  207. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +8 -1
  208. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  209. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +17 -1
  210. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  211. package/dist/modules/dictionaries/components/DictionariesManager.js +31 -10
  212. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  213. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +28 -15
  214. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  215. package/dist/modules/directory/api/organizations/route.js +3 -0
  216. package/dist/modules/directory/api/organizations/route.js.map +2 -2
  217. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -0
  218. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  219. package/dist/modules/directory/backend/directory/organizations/page.js +9 -5
  220. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  221. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +7 -3
  222. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  223. package/dist/modules/directory/backend/directory/tenants/page.js +8 -4
  224. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  225. package/dist/modules/directory/commands/organizations.js +7 -2
  226. package/dist/modules/directory/commands/organizations.js.map +2 -2
  227. package/dist/modules/entities/api/records.js +66 -0
  228. package/dist/modules/entities/api/records.js.map +2 -2
  229. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +1 -0
  230. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  231. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +8 -4
  232. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  233. package/dist/modules/entities/lib/helpers.js +17 -0
  234. package/dist/modules/entities/lib/helpers.js.map +2 -2
  235. package/dist/modules/feature_toggles/api/global/[id]/override/route.js +2 -1
  236. package/dist/modules/feature_toggles/api/global/[id]/override/route.js.map +2 -2
  237. package/dist/modules/feature_toggles/api/overrides/route.js +15 -0
  238. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  239. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js +15 -14
  240. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  241. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +20 -12
  242. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
  243. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +6 -2
  244. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  245. package/dist/modules/feature_toggles/components/formConfig.js +2 -1
  246. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  247. package/dist/modules/feature_toggles/components/overrideFormConfig.js +5 -1
  248. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  249. package/dist/modules/feature_toggles/data/validators.js +7 -4
  250. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  251. package/dist/modules/inbox_ops/api/settings/route.js +17 -2
  252. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  253. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +13 -8
  254. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  255. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +9 -4
  256. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  257. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -11
  258. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  259. package/dist/modules/integrations/backend/integrations/page.js +12 -8
  260. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  261. package/dist/modules/messages/commands/messages.js +13 -10
  262. package/dist/modules/messages/commands/messages.js.map +2 -2
  263. package/dist/modules/perspectives/api/[tableId]/route.js +39 -30
  264. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  265. package/dist/modules/perspectives/services/perspectiveService.js +7 -0
  266. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  267. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +6 -14
  268. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
  269. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +4 -2
  270. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  271. package/dist/modules/planner/components/AvailabilityRuleSetForm.js +2 -0
  272. package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +2 -2
  273. package/dist/modules/planner/components/AvailabilityRulesEditor.js +36 -11
  274. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  275. package/dist/modules/planner/components/AvailabilitySchedule.js +9 -5
  276. package/dist/modules/planner/components/AvailabilitySchedule.js.map +2 -2
  277. package/dist/modules/query_index/lib/engine.js +19 -0
  278. package/dist/modules/query_index/lib/engine.js.map +2 -2
  279. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -0
  280. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  281. package/dist/modules/resources/backend/resources/resource-types/page.js +4 -2
  282. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  283. package/dist/modules/resources/backend/resources/resources/[id]/page.js +14 -3
  284. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  285. package/dist/modules/resources/backend/resources/resources/page.js +8 -4
  286. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  287. package/dist/modules/resources/components/ResourceCrudForm.js +2 -0
  288. package/dist/modules/resources/components/ResourceCrudForm.js.map +2 -2
  289. package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -0
  290. package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
  291. package/dist/modules/sales/api/documents/factory.js +7 -2
  292. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  293. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +3 -1
  294. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  295. package/dist/modules/sales/backend/sales/channels/offers/page.js +13 -4
  296. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  297. package/dist/modules/sales/backend/sales/channels/page.js +16 -4
  298. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  299. package/dist/modules/sales/backend/sales/documents/[id]/page.js +68 -22
  300. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  301. package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
  302. package/dist/modules/sales/commands/documentAddresses.js +181 -2
  303. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  304. package/dist/modules/sales/commands/documents.js +29 -1
  305. package/dist/modules/sales/commands/documents.js.map +2 -2
  306. package/dist/modules/sales/commands/returns.js +12 -2
  307. package/dist/modules/sales/commands/returns.js.map +2 -2
  308. package/dist/modules/sales/commands/shared.js +15 -0
  309. package/dist/modules/sales/commands/shared.js.map +2 -2
  310. package/dist/modules/sales/commands/shipments.js +4 -1
  311. package/dist/modules/sales/commands/shipments.js.map +2 -2
  312. package/dist/modules/sales/components/AdjustmentKindSettings.js +19 -11
  313. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  314. package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
  315. package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
  316. package/dist/modules/sales/components/PaymentMethodsSettings.js +12 -4
  317. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  318. package/dist/modules/sales/components/ShippingMethodsSettings.js +12 -4
  319. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  320. package/dist/modules/sales/components/StatusSettings.js +18 -11
  321. package/dist/modules/sales/components/StatusSettings.js.map +2 -2
  322. package/dist/modules/sales/components/TaxRatesSettings.js +12 -4
  323. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  324. package/dist/modules/sales/components/channels/ChannelOfferForm.js +47 -16
  325. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  326. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +8 -4
  327. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  328. package/dist/modules/sales/components/documents/AddressesSection.js +44 -25
  329. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  330. package/dist/modules/sales/components/documents/AdjustmentsSection.js +43 -23
  331. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  332. package/dist/modules/sales/components/documents/ItemsSection.js +22 -13
  333. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  334. package/dist/modules/sales/components/documents/LineItemDialog.js +23 -10
  335. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  336. package/dist/modules/sales/components/documents/PaymentDialog.js +29 -14
  337. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  338. package/dist/modules/sales/components/documents/PaymentsSection.js +20 -10
  339. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  340. package/dist/modules/sales/components/documents/ReturnDialog.js +26 -17
  341. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  342. package/dist/modules/sales/components/documents/ReturnsSection.js +3 -1
  343. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  344. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -5
  345. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  346. package/dist/modules/sales/components/documents/ShipmentDialog.js +21 -7
  347. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  348. package/dist/modules/sales/components/documents/ShipmentsSection.js +19 -10
  349. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  350. package/dist/modules/sales/components/documents/optimisticLock.js +27 -0
  351. package/dist/modules/sales/components/documents/optimisticLock.js.map +7 -0
  352. package/dist/modules/sales/di.js +18 -0
  353. package/dist/modules/sales/di.js.map +2 -2
  354. package/dist/modules/staff/api/job-histories.js +11 -2
  355. package/dist/modules/staff/api/job-histories.js.map +2 -2
  356. package/dist/modules/staff/api/timesheets/time-entries/route.js +11 -4
  357. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  358. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +13 -8
  359. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  360. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +2 -1
  361. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  362. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +7 -4
  363. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  364. package/dist/modules/staff/backend/staff/team-members/page.js +4 -2
  365. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  366. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +1 -0
  367. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  368. package/dist/modules/staff/backend/staff/team-roles/page.js +4 -2
  369. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  370. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +5 -2
  371. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  372. package/dist/modules/staff/backend/staff/teams/page.js +12 -3
  373. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  374. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -1
  375. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  376. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +2 -2
  377. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +12 -3
  378. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  379. package/dist/modules/staff/commands/job-histories.js +40 -3
  380. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  381. package/dist/modules/staff/components/LeaveRequestForm.js +1 -0
  382. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  383. package/dist/modules/staff/components/TeamForm.js +1 -0
  384. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  385. package/dist/modules/staff/components/TeamMemberForm.js +1 -0
  386. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  387. package/dist/modules/staff/components/TeamRoleForm.js +1 -0
  388. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  389. package/dist/modules/staff/components/detail/JobHistorySection.js +20 -7
  390. package/dist/modules/staff/components/detail/JobHistorySection.js.map +2 -2
  391. package/dist/modules/staff/data/validators.js +7 -1
  392. package/dist/modules/staff/data/validators.js.map +2 -2
  393. package/dist/modules/staff/lib/leaveRequestHelpers.js +2 -1
  394. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +2 -2
  395. package/dist/modules/translations/components/TranslationManager.js +12 -8
  396. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  397. package/dist/modules/workflows/api/definitions/[id]/route.js +106 -0
  398. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  399. package/dist/modules/workflows/backend/definitions/[id]/page.js +11 -3
  400. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  401. package/dist/modules/workflows/backend/definitions/page.js +19 -8
  402. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  403. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +29 -16
  404. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  405. package/dist/modules/workflows/components/formConfig.js +4 -1
  406. package/dist/modules/workflows/components/formConfig.js.map +2 -2
  407. package/dist/modules/workflows/di.js +12 -0
  408. package/dist/modules/workflows/di.js.map +2 -2
  409. package/generated/entities/role/index.ts +1 -0
  410. package/generated/entities/user/index.ts +1 -0
  411. package/generated/entity-fields-registry.ts +2 -0
  412. package/jest.setup.ts +17 -0
  413. package/package.json +8 -7
  414. package/src/helpers/integration/communicationChannelsFixtures.ts +6 -0
  415. package/src/helpers/integration/dbFixtures.ts +1 -1
  416. package/src/helpers/integration/optimisticLockUi.ts +172 -0
  417. package/src/helpers/integration/salesFixtures.ts +29 -0
  418. package/src/modules/api_keys/backend/api-keys/page.tsx +10 -5
  419. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +19 -9
  420. package/src/modules/auth/api/roles/acl/route.ts +37 -11
  421. package/src/modules/auth/api/roles/route.ts +2 -0
  422. package/src/modules/auth/api/sidebar/preferences/route.ts +73 -0
  423. package/src/modules/auth/api/users/acl/route.ts +46 -18
  424. package/src/modules/auth/api/users/route.ts +2 -0
  425. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +29 -4
  426. package/src/modules/auth/backend/roles/page.tsx +9 -4
  427. package/src/modules/auth/backend/users/[id]/edit/page.tsx +37 -4
  428. package/src/modules/auth/backend/users/page.tsx +7 -2
  429. package/src/modules/auth/components/AclEditor.tsx +10 -1
  430. package/src/modules/auth/data/entities.ts +7 -1
  431. package/src/modules/auth/services/sidebarPreferencesService.ts +38 -4
  432. package/src/modules/business_rules/api/rules/route.ts +30 -0
  433. package/src/modules/business_rules/api/sets/route.ts +30 -0
  434. package/src/modules/business_rules/backend/rules/[id]/page.tsx +16 -4
  435. package/src/modules/business_rules/backend/rules/page.tsx +20 -11
  436. package/src/modules/business_rules/backend/sets/[id]/page.tsx +16 -4
  437. package/src/modules/business_rules/backend/sets/page.tsx +20 -11
  438. package/src/modules/catalog/api/categories/route.ts +3 -0
  439. package/src/modules/catalog/api/products/route.ts +4 -0
  440. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +5 -0
  441. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +112 -35
  442. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +56 -7
  443. package/src/modules/catalog/backend/catalog/products/optionSchemaClient.ts +2 -0
  444. package/src/modules/catalog/commands/variants.ts +32 -32
  445. package/src/modules/catalog/components/PriceKindSettings.tsx +20 -7
  446. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -0
  447. package/src/modules/catalog/components/products/ProductMediaManager.tsx +2 -0
  448. package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -4
  449. package/src/modules/catalog/components/products/productForm.ts +3 -0
  450. package/src/modules/catalog/components/products/variantForm.ts +9 -0
  451. package/src/modules/communication_channels/api/post/test-seed/route.ts +28 -1
  452. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +5 -0
  453. package/src/modules/communication_channels/commands/set-primary-channel.ts +10 -7
  454. package/src/modules/currencies/backend/currencies/[id]/page.tsx +13 -6
  455. package/src/modules/currencies/backend/currencies/page.tsx +18 -11
  456. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +3 -0
  457. package/src/modules/currencies/backend/exchange-rates/page.tsx +10 -6
  458. package/src/modules/currencies/commands/currencies.ts +10 -5
  459. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +31 -21
  460. package/src/modules/customer_accounts/api/admin/roles/[id].ts +35 -5
  461. package/src/modules/customer_accounts/api/admin/roles.ts +2 -0
  462. package/src/modules/customer_accounts/api/admin/users/[id].ts +38 -5
  463. package/src/modules/customer_accounts/api/admin/users.ts +2 -0
  464. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -20
  465. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +9 -4
  466. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +11 -4
  467. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +28 -17
  468. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +19 -11
  469. package/src/modules/customers/AGENTS.md +2 -2
  470. package/src/modules/customers/api/companies/route.ts +14 -1
  471. package/src/modules/customers/api/deals/route.ts +3 -0
  472. package/src/modules/customers/api/people/route.ts +12 -1
  473. package/src/modules/customers/api/todos/route.ts +1 -0
  474. package/src/modules/customers/backend/config/customers/deals/page.tsx +1 -0
  475. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +36 -21
  476. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +52 -27
  477. package/src/modules/customers/backend/customers/companies/page.tsx +2 -0
  478. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -5
  479. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +39 -7
  480. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -0
  481. package/src/modules/customers/backend/customers/deals/page.tsx +18 -6
  482. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +64 -39
  483. package/src/modules/customers/backend/customers/people/[id]/page.tsx +46 -26
  484. package/src/modules/customers/backend/customers/people/page.tsx +2 -0
  485. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +84 -24
  486. package/src/modules/customers/commands/addresses.ts +16 -14
  487. package/src/modules/customers/commands/companies.ts +3 -1
  488. package/src/modules/customers/commands/interactions.ts +50 -4
  489. package/src/modules/customers/commands/people.ts +2 -1
  490. package/src/modules/customers/commands/personCompanyLinks.ts +8 -5
  491. package/src/modules/customers/commands/pipeline-stages.ts +16 -16
  492. package/src/modules/customers/components/AddressFormatSettings.tsx +1 -0
  493. package/src/modules/customers/components/DictionarySettings.tsx +18 -13
  494. package/src/modules/customers/components/DictionarySortSettings.tsx +4 -0
  495. package/src/modules/customers/components/PipelineSettings.tsx +42 -21
  496. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -3
  497. package/src/modules/customers/components/detail/AddressesSection.tsx +4 -0
  498. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +2 -0
  499. package/src/modules/customers/components/detail/DealsSection.tsx +4 -0
  500. package/src/modules/customers/components/detail/EmailCardActions.tsx +5 -0
  501. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +7 -0
  502. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +4 -0
  503. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +4 -0
  504. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +2 -0
  505. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +23 -7
  506. package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +25 -15
  507. package/src/modules/customers/components/detail/hooks/useInteractions.ts +76 -35
  508. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +30 -17
  509. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +2 -0
  510. package/src/modules/customers/components/detail/types.ts +1 -0
  511. package/src/modules/customers/components/formConfig.tsx +2 -0
  512. package/src/modules/customers/data/guards.ts +67 -0
  513. package/src/modules/customers/di.ts +66 -0
  514. package/src/modules/customers/i18n/de.json +2 -0
  515. package/src/modules/customers/i18n/en.json +2 -0
  516. package/src/modules/customers/i18n/es.json +2 -0
  517. package/src/modules/customers/i18n/pl.json +2 -0
  518. package/src/modules/customers/lib/todoCompatibility.ts +14 -0
  519. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +2 -0
  520. package/src/modules/data_sync/api/options.ts +7 -4
  521. package/src/modules/data_sync/api/schedules/route.ts +9 -1
  522. package/src/modules/data_sync/backend/data-sync/page.tsx +18 -5
  523. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +46 -19
  524. package/src/modules/data_sync/lib/sync-schedule-service.ts +11 -0
  525. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +8 -1
  526. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +23 -0
  527. package/src/modules/dictionaries/components/DictionariesManager.tsx +32 -9
  528. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +30 -14
  529. package/src/modules/dictionaries/i18n/de.json +1 -0
  530. package/src/modules/dictionaries/i18n/en.json +1 -0
  531. package/src/modules/dictionaries/i18n/es.json +1 -0
  532. package/src/modules/dictionaries/i18n/pl.json +1 -0
  533. package/src/modules/directory/api/organizations/route.ts +3 -0
  534. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +8 -0
  535. package/src/modules/directory/backend/directory/organizations/page.tsx +10 -5
  536. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +16 -5
  537. package/src/modules/directory/backend/directory/tenants/page.tsx +8 -4
  538. package/src/modules/directory/commands/organizations.ts +7 -4
  539. package/src/modules/entities/api/records.ts +99 -0
  540. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +7 -0
  541. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +8 -4
  542. package/src/modules/entities/lib/helpers.ts +17 -0
  543. package/src/modules/feature_toggles/api/global/[id]/override/route.ts +1 -0
  544. package/src/modules/feature_toggles/api/overrides/route.ts +19 -0
  545. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +19 -13
  546. package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +22 -12
  547. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +7 -2
  548. package/src/modules/feature_toggles/components/formConfig.tsx +2 -1
  549. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +10 -1
  550. package/src/modules/feature_toggles/data/validators.ts +11 -3
  551. package/src/modules/inbox_ops/api/settings/route.ts +18 -0
  552. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +15 -10
  553. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +9 -4
  554. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +20 -11
  555. package/src/modules/integrations/backend/integrations/page.tsx +13 -8
  556. package/src/modules/messages/commands/messages.ts +27 -15
  557. package/src/modules/perspectives/api/[tableId]/route.ts +11 -2
  558. package/src/modules/perspectives/services/perspectiveService.ts +13 -1
  559. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +16 -14
  560. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +6 -3
  561. package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +3 -0
  562. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +58 -15
  563. package/src/modules/planner/components/AvailabilitySchedule.tsx +22 -7
  564. package/src/modules/query_index/lib/engine.ts +34 -0
  565. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +7 -1
  566. package/src/modules/resources/backend/resources/resource-types/page.tsx +6 -3
  567. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +23 -3
  568. package/src/modules/resources/backend/resources/resources/page.tsx +15 -4
  569. package/src/modules/resources/components/ResourceCrudForm.tsx +3 -0
  570. package/src/modules/resources/components/ResourceTypeCrudForm.tsx +2 -0
  571. package/src/modules/sales/api/documents/factory.ts +13 -1
  572. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +6 -0
  573. package/src/modules/sales/backend/sales/channels/offers/page.tsx +10 -4
  574. package/src/modules/sales/backend/sales/channels/page.tsx +19 -4
  575. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +73 -20
  576. package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -0
  577. package/src/modules/sales/commands/documentAddresses.ts +226 -4
  578. package/src/modules/sales/commands/documents.ts +28 -0
  579. package/src/modules/sales/commands/returns.ts +12 -3
  580. package/src/modules/sales/commands/shared.ts +36 -0
  581. package/src/modules/sales/commands/shipments.ts +17 -1
  582. package/src/modules/sales/components/AdjustmentKindSettings.tsx +20 -11
  583. package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -0
  584. package/src/modules/sales/components/OrderEditingSettings.tsx +1 -0
  585. package/src/modules/sales/components/PaymentMethodsSettings.tsx +12 -4
  586. package/src/modules/sales/components/ShippingMethodsSettings.tsx +12 -4
  587. package/src/modules/sales/components/StatusSettings.tsx +20 -11
  588. package/src/modules/sales/components/TaxRatesSettings.tsx +12 -5
  589. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +67 -14
  590. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +7 -4
  591. package/src/modules/sales/components/documents/AddressesSection.tsx +35 -25
  592. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +50 -25
  593. package/src/modules/sales/components/documents/ItemsSection.tsx +24 -13
  594. package/src/modules/sales/components/documents/LineItemDialog.tsx +26 -9
  595. package/src/modules/sales/components/documents/PaymentDialog.tsx +33 -14
  596. package/src/modules/sales/components/documents/PaymentsSection.tsx +22 -10
  597. package/src/modules/sales/components/documents/ReturnDialog.tsx +28 -17
  598. package/src/modules/sales/components/documents/ReturnsSection.tsx +4 -1
  599. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +11 -4
  600. package/src/modules/sales/components/documents/ShipmentDialog.tsx +23 -8
  601. package/src/modules/sales/components/documents/ShipmentsSection.tsx +20 -10
  602. package/src/modules/sales/components/documents/optimisticLock.ts +34 -0
  603. package/src/modules/sales/components/documents/shipmentTypes.ts +1 -0
  604. package/src/modules/sales/di.ts +35 -0
  605. package/src/modules/sales/i18n/de.json +3 -0
  606. package/src/modules/sales/i18n/en.json +3 -0
  607. package/src/modules/sales/i18n/es.json +3 -0
  608. package/src/modules/sales/i18n/pl.json +3 -0
  609. package/src/modules/staff/api/job-histories.ts +12 -2
  610. package/src/modules/staff/api/timesheets/time-entries/route.ts +16 -4
  611. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +12 -7
  612. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +2 -0
  613. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +16 -5
  614. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -2
  615. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +8 -0
  616. package/src/modules/staff/backend/staff/team-roles/page.tsx +6 -2
  617. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +13 -3
  618. package/src/modules/staff/backend/staff/teams/page.tsx +9 -3
  619. package/src/modules/staff/backend/staff/timesheets/page.tsx +10 -1
  620. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -0
  621. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +9 -3
  622. package/src/modules/staff/commands/job-histories.ts +42 -3
  623. package/src/modules/staff/components/LeaveRequestForm.tsx +2 -0
  624. package/src/modules/staff/components/TeamForm.tsx +2 -0
  625. package/src/modules/staff/components/TeamMemberForm.tsx +2 -0
  626. package/src/modules/staff/components/TeamRoleForm.tsx +2 -0
  627. package/src/modules/staff/components/detail/JobHistorySection.tsx +28 -6
  628. package/src/modules/staff/data/validators.ts +6 -0
  629. package/src/modules/staff/i18n/de.json +1 -0
  630. package/src/modules/staff/i18n/en.json +1 -0
  631. package/src/modules/staff/i18n/es.json +1 -0
  632. package/src/modules/staff/i18n/pl.json +1 -0
  633. package/src/modules/staff/lib/leaveRequestHelpers.ts +4 -0
  634. package/src/modules/translations/components/TranslationManager.tsx +13 -8
  635. package/src/modules/workflows/api/definitions/[id]/route.ts +112 -0
  636. package/src/modules/workflows/backend/definitions/[id]/page.tsx +20 -4
  637. package/src/modules/workflows/backend/definitions/page.tsx +20 -9
  638. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +29 -16
  639. package/src/modules/workflows/components/formConfig.tsx +5 -0
  640. package/src/modules/workflows/di.ts +20 -0
  641. package/src/modules/workflows/i18n/de.json +1 -0
  642. package/src/modules/workflows/i18n/en.json +1 -0
  643. package/src/modules/workflows/i18n/es.json +1 -0
  644. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -7,7 +7,8 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
- import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
10
+ import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
11
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
11
12
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
12
13
  import { mapCrudServerErrorToFormErrors } from '@open-mercato/ui/backend/utils/serverErrors'
13
14
  import { E } from '#generated/entities.ids.generated'
@@ -68,6 +69,7 @@ type PersonOverview = {
68
69
  nextInteractionIcon?: string | null
69
70
  nextInteractionColor?: string | null
70
71
  organizationId?: string | null
72
+ updatedAt?: string | null
71
73
  }
72
74
  profile: {
73
75
  id: string
@@ -317,19 +319,31 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
317
319
  async (patch: Record<string, unknown>, apply: (prev: PersonOverview) => PersonOverview) => {
318
320
  if (!data) return
319
321
  const payload = { id: data.person.id, ...patch }
320
- await runMutationWithContext(
321
- () => apiCallOrThrow(
322
- '/api/customers/people',
323
- {
324
- method: 'PUT',
325
- headers: { 'content-type': 'application/json' },
326
- body: JSON.stringify(payload),
327
- },
328
- { errorMessage: t('customers.people.detail.inline.error') },
322
+ const result = await runMutationWithContext(
323
+ () => withScopedApiRequestHeaders(
324
+ buildOptimisticLockHeader((data?.person as { updatedAt?: string } | undefined)?.updatedAt),
325
+ () => apiCallOrThrow<{ ok?: boolean; updatedAt?: string | null }>(
326
+ '/api/customers/people',
327
+ {
328
+ method: 'PUT',
329
+ headers: { 'content-type': 'application/json' },
330
+ body: JSON.stringify(payload),
331
+ },
332
+ { errorMessage: t('customers.people.detail.inline.error') },
333
+ ),
329
334
  ),
330
335
  payload,
331
336
  )
332
- setData((prev) => (prev ? apply(prev) : prev))
337
+ // Refresh the optimistic-lock token so the next sequential inline edit does not
338
+ // send a stale updatedAt and falsely 409 (#2055, default-ON locking).
339
+ const nextUpdatedAt = result?.result?.updatedAt ?? null
340
+ setData((prev) => {
341
+ if (!prev) return prev
342
+ const applied = apply(prev)
343
+ return nextUpdatedAt
344
+ ? { ...applied, person: { ...applied.person, updatedAt: nextUpdatedAt } }
345
+ : applied
346
+ })
333
347
  },
334
348
  [data, runMutationWithContext, t]
335
349
  )
@@ -382,13 +396,16 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
382
396
  setIsDeleting(true)
383
397
  try {
384
398
  await runMutationWithContext(
385
- () => apiCallOrThrow(
386
- `/api/customers/people?id=${encodeURIComponent(personId)}`,
387
- {
388
- method: 'DELETE',
389
- headers: { 'content-type': 'application/json' },
390
- },
391
- { errorMessage: t('customers.people.list.deleteError') },
399
+ () => withScopedApiRequestHeaders(
400
+ buildOptimisticLockHeader((data?.person as { updatedAt?: string } | undefined)?.updatedAt),
401
+ () => apiCallOrThrow(
402
+ `/api/customers/people?id=${encodeURIComponent(personId)}`,
403
+ {
404
+ method: 'DELETE',
405
+ headers: { 'content-type': 'application/json' },
406
+ },
407
+ { errorMessage: t('customers.people.list.deleteError') },
408
+ ),
392
409
  ),
393
410
  { id: personId },
394
411
  )
@@ -428,14 +445,17 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
428
445
  customFields: customPayload,
429
446
  }
430
447
  await runMutationWithContext(
431
- () => apiCallOrThrow(
432
- '/api/customers/people',
433
- {
434
- method: 'PUT',
435
- headers: { 'content-type': 'application/json' },
436
- body: JSON.stringify(payload),
437
- },
438
- { errorMessage: t('customers.people.detail.inline.error') },
448
+ () => withScopedApiRequestHeaders(
449
+ buildOptimisticLockHeader((data?.person as { updatedAt?: string } | undefined)?.updatedAt),
450
+ () => apiCallOrThrow(
451
+ '/api/customers/people',
452
+ {
453
+ method: 'PUT',
454
+ headers: { 'content-type': 'application/json' },
455
+ body: JSON.stringify(payload),
456
+ },
457
+ { errorMessage: t('customers.people.detail.inline.error') },
458
+ ),
439
459
  ),
440
460
  payload,
441
461
  )
@@ -461,6 +461,7 @@ export default function CustomersPeoplePage() {
461
461
  try {
462
462
  await runSingleMutation({
463
463
  operation: async () => {
464
+ // optimistic-lock-exempt: delete-only mutation — no field-level lost-update
464
465
  await apiCallOrThrow(
465
466
  `/api/customers/people?id=${encodeURIComponent(person.id)}`,
466
467
  {
@@ -500,6 +501,7 @@ export default function CustomersPeoplePage() {
500
501
  runBulkDelete(
501
502
  selectedRows,
502
503
  async (row) => {
504
+ // optimistic-lock-exempt: bulk delete-only mutation — no field-level lost-update
503
505
  await apiCallOrThrow(`/api/customers/people?id=${encodeURIComponent(row.id)}`, {
504
506
  method: 'DELETE',
505
507
  headers: { 'content-type': 'application/json' },
@@ -7,12 +7,16 @@ import { User, Hash, Users, Building2 } from 'lucide-react'
7
7
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
8
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
9
9
  import { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
10
+ import { useIsMobile } from '@open-mercato/ui/hooks/useIsMobile'
10
11
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
11
12
  import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
12
13
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
14
+ import { withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
15
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
13
16
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
14
17
  import { E } from '#generated/entities.ids.generated'
15
18
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
19
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
16
20
  import { useT } from '@open-mercato/shared/lib/i18n/context'
17
21
  import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
18
22
  import { Button } from '@open-mercato/ui/primitives/button'
@@ -53,6 +57,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
53
57
  const router = useRouter()
54
58
  const searchParams = useSearchParams()
55
59
  const { organizationId } = useOrganizationScopeDetail()
60
+ const isMobile = useIsMobile()
56
61
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
57
62
 
58
63
  const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
@@ -62,6 +67,15 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
62
67
  const fields = React.useMemo(() => createPersonEditFields(t), [t])
63
68
 
64
69
  const [data, setData] = React.useState<PersonOverview | null>(null)
70
+ // Mirror the latest `data` into a ref so save handlers always read the current
71
+ // optimistic-lock token (`person.updatedAt`) instead of the value captured in
72
+ // their `useCallback` closure. Without this, a header-field save issued after a
73
+ // prior in-page reload would send a stale token (or none), letting a concurrent
74
+ // two-tab overwrite slip through without the 409 + conflict bar (#2055, Alina A7).
75
+ const dataRef = React.useRef<PersonOverview | null>(null)
76
+ React.useEffect(() => {
77
+ dataRef.current = data
78
+ }, [data])
65
79
  const [isLoading, setIsLoading] = React.useState(true)
66
80
  const [error, setError] = React.useState<string | null>(null)
67
81
  const [isNotFound, setIsNotFound] = React.useState(false)
@@ -124,7 +138,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
124
138
 
125
139
  // Data loading
126
140
  const initialLoadDoneRef = React.useRef(false)
127
- const loadData = React.useCallback(async () => {
141
+ const loadData = React.useCallback(async (lockTokenOverride?: string | null) => {
128
142
  if (!id) {
129
143
  setIsNotFound(true)
130
144
  setIsLoading(false)
@@ -141,7 +155,15 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
141
155
  undefined,
142
156
  { errorMessage: t('customers.people.detail.error.load', 'Failed to load person.') },
143
157
  )
144
- setData(payload as PersonOverview)
158
+ // When the caller is the save handler, pin the optimistic-lock token to the
159
+ // value the write itself returned rather than the one this GET observed — a
160
+ // concurrent third-party bump between save and reload must stay stale so the
161
+ // next in-page save 409s (#2055, Alina A7). Applied in the same state update
162
+ // as the refresh to avoid a redundant second re-render.
163
+ const next = lockTokenOverride && payload?.person
164
+ ? { ...payload, person: { ...payload.person, updatedAt: lockTokenOverride, updated_at: lockTokenOverride } }
165
+ : payload
166
+ setData(next as PersonOverview)
145
167
  } catch (err) {
146
168
  if ((err as { status?: number }).status === 404) {
147
169
  setIsNotFound(true)
@@ -230,6 +252,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
230
252
  // moment instead of "today" (#1807 prefill).
231
253
  const editPayload = {
232
254
  id: activity.id,
255
+ updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt as string : typeof raw.updated_at === 'string' ? raw.updated_at as string : null,
233
256
  interactionType: typeof activity.interactionType === 'string' ? activity.interactionType : undefined,
234
257
  title: typeof activity.title === 'string' ? activity.title : null,
235
258
  body: typeof activity.body === 'string' ? activity.body : null,
@@ -329,9 +352,27 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
329
352
  throw err
330
353
  }
331
354
 
332
- await updateCrud('customers/people', payload)
355
+ // Attach the current optimistic-lock token directly on this write path so
356
+ // every header-field edit (displayName/status/…) carries `updatedAt`, not
357
+ // just the fields the embedded CrudForm intercepts. Read from `dataRef` so
358
+ // the token reflects the latest in-page reload rather than a stale closure
359
+ // capture, and let the 409 propagate to CrudForm's surfaceRecordConflict so
360
+ // the unified conflict bar renders (#2055, Alina A7).
361
+ const lockedUpdatedAt = dataRef.current?.person?.updatedAt
362
+ ?? dataRef.current?.person?.updated_at
363
+ ?? null
364
+ const updateResponse = await withScopedApiRequestHeaders(
365
+ buildOptimisticLockHeader(lockedUpdatedAt),
366
+ () => updateCrud<{ updatedAt?: string | null }>('customers/people', payload),
367
+ )
333
368
  flash(t('customers.people.form.updateSuccess', 'Person updated.'), 'success')
334
- await loadData()
369
+ // Refresh the view and pin the optimistic-lock token to the write's OWN
370
+ // authoritative `updatedAt` in a single reload (see loadData) so a
371
+ // concurrent third-party bump stays stale on the next save (#2055, Alina A7).
372
+ const savedUpdatedAt = typeof updateResponse.result?.updatedAt === 'string'
373
+ ? updateResponse.result.updatedAt
374
+ : null
375
+ await loadData(savedUpdatedAt)
335
376
  } finally {
336
377
  setIsSaving(false)
337
378
  }
@@ -351,10 +392,28 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
351
392
  variant: 'destructive',
352
393
  })
353
394
  if (!approved) return
354
- await runMutationWithContext(
355
- () => deleteCrud('customers/people', { id: personId }),
356
- { id: personId, operation: 'deletePerson' },
357
- )
395
+ try {
396
+ await runMutationWithContext(
397
+ () => withScopedApiRequestHeaders(
398
+ buildOptimisticLockHeader(data?.person?.updatedAt ?? data?.person?.updated_at ?? null),
399
+ () => deleteCrud('customers/people', { id: personId }),
400
+ ),
401
+ { id: personId, operation: 'deletePerson' },
402
+ )
403
+ } catch (err) {
404
+ // The guarded mutation routes a 409 to the unified conflict bar; surface
405
+ // any other server error (e.g. a linked-records delete guard) as a flash
406
+ // instead of letting it crash the page.
407
+ if (!surfaceRecordConflict(err, t)) {
408
+ flash(
409
+ err instanceof Error && err.message.trim().length > 0
410
+ ? err.message
411
+ : t('customers.people.detail.deleteError', 'Failed to delete person.'),
412
+ 'error',
413
+ )
414
+ }
415
+ return
416
+ }
358
417
  flash(t('customers.people.list.deleteSuccess', 'Person deleted.'), 'success')
359
418
  router.push('/backend/customers/people')
360
419
  },
@@ -464,6 +523,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
464
523
  fields={fields}
465
524
  groups={groups}
466
525
  initialValues={initialValues}
526
+ optimisticLockUpdatedAt={data.person.updatedAt ?? data.person.updated_at ?? null}
467
527
  onSubmit={handleFormSubmit}
468
528
  onDelete={handleFormDelete}
469
529
  hideFooterActions
@@ -602,22 +662,22 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
602
662
  </div>
603
663
  </PersonDetailTabs>
604
664
  )
605
- return (
606
- <>
607
- <div className="md:hidden">
608
- <MobilePersonDetail zone1={zone1Content} zone2={zone2Content} />
609
- </div>
610
- <div className="hidden md:block">
611
- <CollapsibleZoneLayout
612
- pageType="person-v2"
613
- entityName={personName}
614
- isDirty={isDirty}
615
- sections={zoneSections}
616
- zone1={zone1Content}
617
- zone2={zone2Content}
618
- />
619
- </div>
620
- </>
665
+ // Render only the layout variant that matches the viewport. Mounting
666
+ // both the mobile and desktop layouts at once would create two
667
+ // CrudForm instances bound to one `formWrapperRef`, so header Save
668
+ // could submit the hidden instance's stale values and silently
669
+ // discard the edit (#2453). `useIsMobile` is SSR-safe.
670
+ return isMobile ? (
671
+ <MobilePersonDetail zone1={zone1Content} zone2={zone2Content} />
672
+ ) : (
673
+ <CollapsibleZoneLayout
674
+ pageType="person-v2"
675
+ entityName={personName}
676
+ isDirty={isDirty}
677
+ sections={zoneSections}
678
+ zone1={zone1Content}
679
+ zone2={zone2Content}
680
+ />
621
681
  )
622
682
  })()}
623
683
 
@@ -213,22 +213,24 @@ const updateAddressCommand: CommandHandler<AddressUpdateInput, { addressId: stri
213
213
  ensureSameScope(entity, address.organizationId, address.tenantId)
214
214
  address.entity = entity
215
215
  }
216
- if (parsed.name !== undefined) address.name = parsed.name ?? null
217
- if (parsed.purpose !== undefined) address.purpose = parsed.purpose ?? null
218
- if (parsed.companyName !== undefined) address.companyName = parsed.companyName ?? null
219
- if (parsed.addressLine1 !== undefined) address.addressLine1 = parsed.addressLine1
220
- if (parsed.addressLine2 !== undefined) address.addressLine2 = parsed.addressLine2 ?? null
221
- if (parsed.buildingNumber !== undefined) address.buildingNumber = parsed.buildingNumber ?? null
222
- if (parsed.flatNumber !== undefined) address.flatNumber = parsed.flatNumber ?? null
223
- if (parsed.city !== undefined) address.city = parsed.city ?? null
224
- if (parsed.region !== undefined) address.region = parsed.region ?? null
225
- if (parsed.postalCode !== undefined) address.postalCode = parsed.postalCode ?? null
226
- if (parsed.country !== undefined) address.country = parsed.country ?? null
227
- if (parsed.latitude !== undefined) address.latitude = parsed.latitude ?? null
228
- if (parsed.longitude !== undefined) address.longitude = parsed.longitude ?? null
229
- if (parsed.isPrimary !== undefined) address.isPrimary = parsed.isPrimary
230
216
 
231
217
  await withAtomicFlush(em, [
218
+ () => {
219
+ if (parsed.name !== undefined) address.name = parsed.name ?? null
220
+ if (parsed.purpose !== undefined) address.purpose = parsed.purpose ?? null
221
+ if (parsed.companyName !== undefined) address.companyName = parsed.companyName ?? null
222
+ if (parsed.addressLine1 !== undefined) address.addressLine1 = parsed.addressLine1
223
+ if (parsed.addressLine2 !== undefined) address.addressLine2 = parsed.addressLine2 ?? null
224
+ if (parsed.buildingNumber !== undefined) address.buildingNumber = parsed.buildingNumber ?? null
225
+ if (parsed.flatNumber !== undefined) address.flatNumber = parsed.flatNumber ?? null
226
+ if (parsed.city !== undefined) address.city = parsed.city ?? null
227
+ if (parsed.region !== undefined) address.region = parsed.region ?? null
228
+ if (parsed.postalCode !== undefined) address.postalCode = parsed.postalCode ?? null
229
+ if (parsed.country !== undefined) address.country = parsed.country ?? null
230
+ if (parsed.latitude !== undefined) address.latitude = parsed.latitude ?? null
231
+ if (parsed.longitude !== undefined) address.longitude = parsed.longitude ?? null
232
+ if (parsed.isPrimary !== undefined) address.isPrimary = parsed.isPrimary
233
+ },
232
234
  async () => {
233
235
  if (address.isPrimary) {
234
236
  await enforcePrimaryAddress(em, typeof address.entity === 'string' ? address.entity : address.entity.id, address.id)
@@ -677,7 +677,9 @@ const updateCompanyCommand: CommandHandler<CompanyUpdateInput, { entityId: strin
677
677
  })
678
678
  await emitQueryIndexUpsertEvents(ctx, [companyEntityIndexEntry(record)])
679
679
 
680
- return { entityId: record.id }
680
+ // Expose the freshly-bumped updatedAt so the CRUD update response can hand it to
681
+ // inline-edit detail pages for sequential-save lock-token refresh (#2055).
682
+ return { entityId: record.id, updatedAt: record.updatedAt }
681
683
  },
682
684
  captureAfter: async (_input, result, ctx) => {
683
685
  const em = (ctx.container.resolve('em') as EntityManager).fork()
@@ -38,6 +38,7 @@ import {
38
38
  buildCustomFieldResetMap,
39
39
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
40
40
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
41
+ import { enforceRecordGoneIsConflict, enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
41
42
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
42
43
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
43
44
  import { recomputeNextInteraction } from '../lib/interactionProjection'
@@ -451,10 +452,25 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
451
452
  const em = (ctx.container.resolve('em') as EntityManager).fork()
452
453
  const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
453
454
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
454
- if (!interaction) throw new CrudHttpError(404, { error: 'Interaction not found' })
455
+ if (!interaction) {
456
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
457
+ throw new CrudHttpError(404, { error: 'Interaction not found' })
458
+ }
455
459
  ensureTenantScope(ctx, interaction.tenantId)
456
460
  ensureOrganizationScope(ctx, interaction.organizationId)
457
461
 
462
+ // Concurrent-edit guard for command-driven callers (e.g. the legacy
463
+ // /api/customers/todos route, which bypasses the makeCrudRoute lock guard):
464
+ // when the client opted into optimistic locking, a stale edit fails with the
465
+ // unified 409 instead of silently overwriting (#2055). Strictly additive —
466
+ // no-op when no expected-version header is present.
467
+ enforceCommandOptimisticLock({
468
+ resourceKind: 'customers.interaction',
469
+ resourceId: interaction.id,
470
+ current: interaction.updatedAt,
471
+ request: ctx.request ?? null,
472
+ })
473
+
458
474
  // Email visibility is an access-controlled field: only the interaction's
459
475
  // author may change a
460
476
  // private email's visibility (mirrors the dedicated PATCH .../visibility
@@ -709,10 +725,20 @@ const completeInteractionCommand: CommandHandler<InteractionCompleteInput, { int
709
725
  const em = (ctx.container.resolve('em') as EntityManager).fork()
710
726
  const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
711
727
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
712
- if (!interaction) throw new CrudHttpError(404, { error: 'Interaction not found' })
728
+ if (!interaction) {
729
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
730
+ throw new CrudHttpError(404, { error: 'Interaction not found' })
731
+ }
713
732
  ensureTenantScope(ctx, interaction.tenantId)
714
733
  ensureOrganizationScope(ctx, interaction.organizationId)
715
734
 
735
+ enforceCommandOptimisticLock({
736
+ resourceKind: 'customers.interaction',
737
+ resourceId: interaction.id,
738
+ current: interaction.updatedAt,
739
+ request: ctx.request ?? null,
740
+ })
741
+
716
742
  interaction.status = 'done'
717
743
  interaction.occurredAt = parsed.occurredAt ?? new Date()
718
744
  await trx.flush()
@@ -839,10 +865,20 @@ const cancelInteractionCommand: CommandHandler<InteractionCancelInput, { interac
839
865
  const em = (ctx.container.resolve('em') as EntityManager).fork()
840
866
  const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
841
867
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null })
842
- if (!interaction) throw new CrudHttpError(404, { error: 'Interaction not found' })
868
+ if (!interaction) {
869
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: parsed.id, request: ctx.request ?? null })
870
+ throw new CrudHttpError(404, { error: 'Interaction not found' })
871
+ }
843
872
  ensureTenantScope(ctx, interaction.tenantId)
844
873
  ensureOrganizationScope(ctx, interaction.organizationId)
845
874
 
875
+ enforceCommandOptimisticLock({
876
+ resourceKind: 'customers.interaction',
877
+ resourceId: interaction.id,
878
+ current: interaction.updatedAt,
879
+ request: ctx.request ?? null,
880
+ })
881
+
846
882
  interaction.status = 'canceled'
847
883
  await trx.flush()
848
884
 
@@ -967,10 +1003,20 @@ const deleteInteractionCommand: CommandHandler<{ body?: Record<string, unknown>;
967
1003
  const em = (ctx.container.resolve('em') as EntityManager).fork()
968
1004
  const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
969
1005
  const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id, deletedAt: null })
970
- if (!interaction) throw new CrudHttpError(404, { error: 'Interaction not found' })
1006
+ if (!interaction) {
1007
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.interaction', resourceId: id, request: ctx.request ?? null })
1008
+ throw new CrudHttpError(404, { error: 'Interaction not found' })
1009
+ }
971
1010
  ensureTenantScope(ctx, interaction.tenantId)
972
1011
  ensureOrganizationScope(ctx, interaction.organizationId)
973
1012
 
1013
+ enforceCommandOptimisticLock({
1014
+ resourceKind: 'customers.interaction',
1015
+ resourceId: interaction.id,
1016
+ current: interaction.updatedAt,
1017
+ request: ctx.request ?? null,
1018
+ })
1019
+
974
1020
  const entityId = typeof interaction.entity === 'string' ? interaction.entity : interaction.entity.id
975
1021
  interaction.deletedAt = new Date()
976
1022
  await trx.flush()
@@ -866,7 +866,8 @@ const updatePersonCommand: CommandHandler<PersonUpdateInput, { entityId: string
866
866
  })
867
867
  await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(record)])
868
868
 
869
- return { entityId: record.id }
869
+ // Expose the freshly-bumped updatedAt for inline-edit sequential-save lock refresh (#2055).
870
+ return { entityId: record.id, updatedAt: record.updatedAt }
870
871
  },
871
872
  captureAfter: async (_input, result, ctx) => {
872
873
  const em = (ctx.container.resolve('em') as EntityManager).fork()
@@ -382,6 +382,7 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
382
382
  const profile = await requirePersonProfile(em, person)
383
383
  const linkedCompany = await requireCompanyEntity(em, companyId, parsed.tenantId, parsed.organizationId)
384
384
 
385
+ const linkWasPrimary = link.isPrimary
385
386
  await withAtomicFlush(em, [
386
387
  async () => {
387
388
  if (parsed.isPrimary) {
@@ -389,16 +390,18 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
389
390
  link.isPrimary = true
390
391
  profile.company = linkedCompany
391
392
  } else if (!parsed.isPrimary) {
392
- const linkWasPrimary = link.isPrimary
393
393
  link.isPrimary = false
394
- if (linkWasPrimary) {
395
- const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)
396
- await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)
397
- } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {
394
+ if (!linkWasPrimary && profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {
398
395
  profile.company = null
399
396
  }
400
397
  }
401
398
  },
399
+ async () => {
400
+ if (!parsed.isPrimary && linkWasPrimary) {
401
+ const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)
402
+ await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)
403
+ }
404
+ },
402
405
  ], { transaction: true })
403
406
 
404
407
  const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
@@ -49,18 +49,6 @@ const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { sta
49
49
  ? existingStages.length
50
50
  : Math.max(0, Math.min(requestedOrder, existingStages.length))
51
51
 
52
- // Shift the order of every stage at or after the insert position. Skipping
53
- // this step would either duplicate `order` values (silently corrupting kanban
54
- // ordering) or push the new stage to the wrong spot when re-sorting.
55
- if (requestedOrder !== undefined) {
56
- for (const stage of existingStages) {
57
- if (stage.order >= insertOrder) {
58
- stage.order += 1
59
- stage.updatedAt = new Date()
60
- }
61
- }
62
- }
63
-
64
52
  const stage = em.create(CustomerPipelineStage, {
65
53
  organizationId: parsed.organizationId,
66
54
  tenantId: parsed.tenantId,
@@ -73,6 +61,17 @@ const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { sta
73
61
 
74
62
  await withAtomicFlush(em, [
75
63
  () => {
64
+ // Shift the order of every stage at or after the insert position. Skipping
65
+ // this step would either duplicate `order` values (silently corrupting kanban
66
+ // ordering) or push the new stage to the wrong spot when re-sorting.
67
+ if (requestedOrder !== undefined) {
68
+ for (const existing of existingStages) {
69
+ if (existing.order >= insertOrder) {
70
+ existing.order += 1
71
+ existing.updatedAt = new Date()
72
+ }
73
+ }
74
+ }
76
75
  em.persist(stage)
77
76
  },
78
77
  async () => {
@@ -112,11 +111,12 @@ const updatePipelineStageCommand: CommandHandler<PipelineStageUpdateInput, void>
112
111
  ensureTenantScope(ctx, stage.tenantId)
113
112
  ensureOrganizationScope(ctx, stage.organizationId)
114
113
 
115
- if (parsed.label !== undefined) stage.label = parsed.label
116
- if (parsed.order !== undefined) stage.order = parsed.order
117
- stage.updatedAt = new Date()
118
-
119
114
  await withAtomicFlush(em, [
115
+ () => {
116
+ if (parsed.label !== undefined) stage.label = parsed.label
117
+ if (parsed.order !== undefined) stage.order = parsed.order
118
+ stage.updatedAt = new Date()
119
+ },
120
120
  async () => {
121
121
  if (parsed.label !== undefined || parsed.color !== undefined || parsed.icon !== undefined) {
122
122
  await ensureDictionaryEntry(em, {
@@ -87,6 +87,7 @@ export function AddressFormatSettings() {
87
87
  setPending(next)
88
88
  setError(null)
89
89
  try {
90
+ // optimistic-lock-exempt: single-row tenant address-format preference toggle — no per-record version / concurrent record edit
90
91
  const call = await apiCall<Record<string, unknown>>(
91
92
  '/api/customers/settings/address-format',
92
93
  {
@@ -8,7 +8,8 @@ import {
8
8
  DialogTitle,
9
9
  } from '@open-mercato/ui/primitives/dialog'
10
10
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
11
- import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
11
+ import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
12
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
12
13
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
13
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
15
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
@@ -220,10 +221,12 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
220
221
  })
221
222
  if (!confirmed) return
222
223
  try {
223
- await apiCallOrThrow(
224
- `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,
225
- { method: 'DELETE' },
226
- { errorMessage: errorDelete },
224
+ await withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>
225
+ apiCallOrThrow(
226
+ `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,
227
+ { method: 'DELETE' },
228
+ { errorMessage: errorDelete },
229
+ ),
227
230
  )
228
231
  flash(successDelete, 'success')
229
232
  await loadEntries()
@@ -281,14 +284,16 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
281
284
  closeDialog()
282
285
  return
283
286
  }
284
- await apiCallOrThrow(
285
- `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,
286
- {
287
- method: 'PATCH',
288
- headers: { 'content-type': 'application/json' },
289
- body: JSON.stringify(body),
290
- },
291
- { errorMessage: errorSave },
287
+ await withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>
288
+ apiCallOrThrow(
289
+ `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,
290
+ {
291
+ method: 'PATCH',
292
+ headers: { 'content-type': 'application/json' },
293
+ body: JSON.stringify(body),
294
+ },
295
+ { errorMessage: errorSave },
296
+ ),
292
297
  )
293
298
  flash(successSave, 'success')
294
299
  }
@@ -170,6 +170,10 @@ export function DictionarySortSettings() {
170
170
  setError(null)
171
171
  try {
172
172
  await runMutation({
173
+ // optimistic-lock-exempt: tenant-scoped settings blob (dictionary sort
174
+ // modes), not a versioned per-record entity — there is no `updatedAt`
175
+ // round-trip to lock against, and concurrent writes converge on the
176
+ // last-selected preference. Mirrors other singleton settings PATCHes.
173
177
  operation: async () => {
174
178
  await apiCallOrThrow(
175
179
  '/api/customers/settings/dictionary-sort-modes',