@open-mercato/core 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6331.1.a33b8e99b7

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 (304) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +3 -0
  3. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  4. package/dist/modules/auth/api/roles/acl/route.js +2 -2
  5. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  6. package/dist/modules/auth/api/users/acl/route.js +2 -2
  7. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  8. package/dist/modules/auth/backend/roles/[id]/edit/page.js +12 -0
  9. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  10. package/dist/modules/auth/backend/users/[id]/edit/page.js +12 -0
  11. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  12. package/dist/modules/auth/data/entities.js +2 -2
  13. package/dist/modules/auth/data/entities.js.map +2 -2
  14. package/dist/modules/business_rules/api/rules/route.js +3 -3
  15. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  16. package/dist/modules/business_rules/api/sets/route.js +3 -3
  17. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  18. package/dist/modules/business_rules/backend/rules/[id]/page.js +12 -1
  19. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +2 -2
  20. package/dist/modules/business_rules/backend/sets/[id]/page.js +12 -1
  21. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  22. package/dist/modules/catalog/api/product-media/route.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +12 -0
  24. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  25. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
  26. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  27. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +12 -1
  28. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  29. package/dist/modules/catalog/components/PriceKindSettings.js +13 -3
  30. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  31. package/dist/modules/catalog/lib/bulkDelete.js.map +2 -2
  32. package/dist/modules/currencies/backend/currencies/[id]/page.js +15 -3
  33. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  34. package/dist/modules/currencies/backend/currencies/page.js +2 -1
  35. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  36. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +12 -1
  37. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  38. package/dist/modules/currencies/backend/exchange-rates/page.js +3 -2
  39. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -3
  41. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  42. package/dist/modules/customer_accounts/api/admin/users/[id].js +3 -3
  43. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  44. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
  45. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  46. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
  47. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  48. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
  49. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  50. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
  51. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
  52. package/dist/modules/customers/api/addresses/route.js +4 -2
  53. package/dist/modules/customers/api/addresses/route.js.map +2 -2
  54. package/dist/modules/customers/api/tags/route.js +8 -3
  55. package/dist/modules/customers/api/tags/route.js.map +2 -2
  56. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +13 -2
  57. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  58. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +24 -9
  59. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  60. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +53 -23
  61. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
  62. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js +13 -3
  63. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.js.map +2 -2
  64. package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -2
  65. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  66. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +13 -2
  67. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  68. package/dist/modules/customers/commands/interactions.js +5 -5
  69. package/dist/modules/customers/commands/interactions.js.map +2 -2
  70. package/dist/modules/customers/commands/pipeline-stages.js +24 -2
  71. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  72. package/dist/modules/customers/commands/pipelines.js +24 -2
  73. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  74. package/dist/modules/customers/components/detail/AddressesSection.js +46 -29
  75. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  76. package/dist/modules/customers/components/detail/DealForm.js +5 -1
  77. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  78. package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
  79. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
  80. package/dist/modules/customers/components/detail/notesAdapter.js +25 -18
  81. package/dist/modules/customers/components/detail/notesAdapter.js.map +2 -2
  82. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
  83. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  84. package/dist/modules/data_sync/api/runs.js +1 -0
  85. package/dist/modules/data_sync/api/runs.js.map +2 -2
  86. package/dist/modules/data_sync/api/schedules/[id]/route.js +38 -19
  87. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  88. package/dist/modules/data_sync/api/schedules/route.js +1 -1
  89. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  90. package/dist/modules/data_sync/backend/data-sync/page.js +35 -16
  91. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  92. package/dist/modules/data_sync/data/validators.js +1 -0
  93. package/dist/modules/data_sync/data/validators.js.map +2 -2
  94. package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
  95. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  96. package/dist/modules/data_sync/lib/sync-schedule-service.js +19 -5
  97. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  98. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +3 -3
  99. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  100. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +3 -3
  101. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  102. package/dist/modules/dictionaries/components/DictionariesManager.js +12 -1
  103. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  104. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  105. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +12 -0
  106. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  107. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +12 -0
  108. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  109. package/dist/modules/entities/api/entities.js +2 -2
  110. package/dist/modules/entities/api/entities.js.map +2 -2
  111. package/dist/modules/entities/api/records.js +18 -6
  112. package/dist/modules/entities/api/records.js.map +2 -2
  113. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
  114. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  115. package/dist/modules/feature_toggles/api/overrides/route.js +2 -2
  116. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  117. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  118. package/dist/modules/feature_toggles/data/entities.js +1 -1
  119. package/dist/modules/feature_toggles/data/entities.js.map +2 -2
  120. package/dist/modules/feature_toggles/data/validators.js +2 -1
  121. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  122. package/dist/modules/feature_toggles/lib/queries.js +2 -1
  123. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  124. package/dist/modules/inbox_ops/api/settings/route.js +2 -2
  125. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  126. package/dist/modules/messages/api/unread-count/route.js +32 -0
  127. package/dist/modules/messages/api/unread-count/route.js.map +2 -2
  128. package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
  129. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  130. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +12 -1
  131. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  132. package/dist/modules/resources/backend/resources/resources/[id]/page.js +12 -1
  133. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  134. package/dist/modules/resources/components/detail/notesAdapter.js +25 -18
  135. package/dist/modules/resources/components/detail/notesAdapter.js.map +2 -2
  136. package/dist/modules/sales/backend/sales/documents/[id]/page.js +14 -1
  137. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  138. package/dist/modules/sales/commands/documents.js +11 -10
  139. package/dist/modules/sales/commands/documents.js.map +2 -2
  140. package/dist/modules/sales/commands/payments.js +6 -1
  141. package/dist/modules/sales/commands/payments.js.map +2 -2
  142. package/dist/modules/sales/commands/returns.js +3 -3
  143. package/dist/modules/sales/commands/returns.js.map +2 -2
  144. package/dist/modules/sales/commands/shared.js +3 -3
  145. package/dist/modules/sales/commands/shared.js.map +2 -2
  146. package/dist/modules/sales/commands/shipments.js +6 -1
  147. package/dist/modules/sales/commands/shipments.js.map +2 -2
  148. package/dist/modules/sales/components/documents/PaymentDialog.js +6 -3
  149. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  150. package/dist/modules/sales/components/documents/PaymentsSection.js +7 -3
  151. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  152. package/dist/modules/sales/components/documents/ShipmentDialog.js +6 -3
  153. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  154. package/dist/modules/sales/components/documents/ShipmentsSection.js +7 -3
  155. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  156. package/dist/modules/sales/di.js +39 -3
  157. package/dist/modules/sales/di.js.map +2 -2
  158. package/dist/modules/sales/setup.js +2 -0
  159. package/dist/modules/sales/setup.js.map +2 -2
  160. package/dist/modules/shipping_carriers/i18n/en.js +11 -1
  161. package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
  162. package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
  163. package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
  164. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
  165. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
  166. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
  167. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
  168. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +12 -1
  169. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  170. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +12 -1
  171. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  172. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +12 -1
  173. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  174. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -1
  175. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  176. package/dist/modules/staff/commands/job-histories.js +3 -3
  177. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  178. package/dist/modules/staff/components/detail/addressesAdapter.js +33 -24
  179. package/dist/modules/staff/components/detail/addressesAdapter.js.map +2 -2
  180. package/dist/modules/staff/components/detail/notesAdapter.js +25 -18
  181. package/dist/modules/staff/components/detail/notesAdapter.js.map +2 -2
  182. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +37 -0
  183. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  184. package/dist/modules/translations/components/TranslationManager.js +7 -1
  185. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  186. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
  187. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  188. package/dist/modules/workflows/api/definitions/[id]/route.js +3 -3
  189. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  190. package/dist/modules/workflows/backend/definitions/[id]/page.js +12 -1
  191. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  192. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +11 -1
  193. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  194. package/package.json +7 -7
  195. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +3 -0
  196. package/src/modules/auth/api/roles/acl/route.ts +2 -2
  197. package/src/modules/auth/api/users/acl/route.ts +2 -2
  198. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +17 -0
  199. package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -0
  200. package/src/modules/auth/data/entities.ts +2 -2
  201. package/src/modules/business_rules/api/rules/route.ts +3 -3
  202. package/src/modules/business_rules/api/sets/route.ts +3 -3
  203. package/src/modules/business_rules/backend/rules/[id]/page.tsx +17 -1
  204. package/src/modules/business_rules/backend/sets/[id]/page.tsx +18 -1
  205. package/src/modules/catalog/api/product-media/route.ts +4 -0
  206. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +17 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +16 -1
  209. package/src/modules/catalog/components/PriceKindSettings.tsx +15 -7
  210. package/src/modules/catalog/lib/bulkDelete.ts +6 -0
  211. package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -3
  212. package/src/modules/currencies/backend/currencies/page.tsx +2 -1
  213. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +17 -1
  214. package/src/modules/currencies/backend/exchange-rates/page.tsx +3 -2
  215. package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -3
  216. package/src/modules/customer_accounts/api/admin/users/[id].ts +3 -3
  217. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
  218. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
  219. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
  220. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
  221. package/src/modules/customers/api/addresses/route.ts +2 -0
  222. package/src/modules/customers/api/tags/route.ts +6 -1
  223. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +19 -2
  224. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +29 -8
  225. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +47 -20
  226. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealPipeline.ts +13 -3
  227. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
  228. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -2
  229. package/src/modules/customers/commands/interactions.ts +5 -5
  230. package/src/modules/customers/commands/pipeline-stages.ts +26 -2
  231. package/src/modules/customers/commands/pipelines.ts +26 -2
  232. package/src/modules/customers/components/detail/AddressesSection.tsx +50 -28
  233. package/src/modules/customers/components/detail/DealForm.tsx +12 -0
  234. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
  235. package/src/modules/customers/components/detail/notesAdapter.ts +29 -18
  236. package/src/modules/customers/i18n/de.json +2 -0
  237. package/src/modules/customers/i18n/en.json +2 -0
  238. package/src/modules/customers/i18n/es.json +2 -0
  239. package/src/modules/customers/i18n/pl.json +2 -0
  240. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
  241. package/src/modules/data_sync/api/runs.ts +1 -0
  242. package/src/modules/data_sync/api/schedules/[id]/route.ts +38 -20
  243. package/src/modules/data_sync/api/schedules/route.ts +1 -1
  244. package/src/modules/data_sync/backend/data-sync/page.tsx +35 -16
  245. package/src/modules/data_sync/data/validators.ts +1 -0
  246. package/src/modules/data_sync/i18n/de.json +1 -0
  247. package/src/modules/data_sync/i18n/en.json +1 -0
  248. package/src/modules/data_sync/i18n/es.json +1 -0
  249. package/src/modules/data_sync/i18n/pl.json +1 -0
  250. package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
  251. package/src/modules/data_sync/lib/sync-schedule-service.ts +30 -5
  252. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +3 -3
  253. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +3 -3
  254. package/src/modules/dictionaries/components/DictionariesManager.tsx +21 -1
  255. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +2 -0
  256. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +17 -0
  257. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +17 -0
  258. package/src/modules/entities/api/entities.ts +2 -2
  259. package/src/modules/entities/api/records.ts +24 -6
  260. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
  261. package/src/modules/feature_toggles/api/overrides/route.ts +2 -2
  262. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +12 -0
  263. package/src/modules/feature_toggles/data/entities.ts +1 -1
  264. package/src/modules/feature_toggles/data/validators.ts +1 -0
  265. package/src/modules/feature_toggles/lib/queries.ts +1 -0
  266. package/src/modules/inbox_ops/api/settings/route.ts +2 -2
  267. package/src/modules/messages/api/unread-count/route.ts +40 -0
  268. package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
  269. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +17 -1
  270. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +17 -1
  271. package/src/modules/resources/components/detail/notesAdapter.ts +29 -18
  272. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +17 -1
  273. package/src/modules/sales/commands/documents.ts +11 -10
  274. package/src/modules/sales/commands/payments.ts +11 -0
  275. package/src/modules/sales/commands/returns.ts +3 -3
  276. package/src/modules/sales/commands/shared.ts +10 -4
  277. package/src/modules/sales/commands/shipments.ts +14 -0
  278. package/src/modules/sales/components/documents/PaymentDialog.tsx +7 -3
  279. package/src/modules/sales/components/documents/PaymentsSection.tsx +8 -3
  280. package/src/modules/sales/components/documents/ShipmentDialog.tsx +7 -3
  281. package/src/modules/sales/components/documents/ShipmentsSection.tsx +8 -3
  282. package/src/modules/sales/di.ts +68 -9
  283. package/src/modules/sales/setup.ts +2 -0
  284. package/src/modules/shipping_carriers/i18n/de.json +11 -1
  285. package/src/modules/shipping_carriers/i18n/en.json +11 -1
  286. package/src/modules/shipping_carriers/i18n/en.ts +10 -0
  287. package/src/modules/shipping_carriers/i18n/es.json +11 -1
  288. package/src/modules/shipping_carriers/i18n/pl.json +11 -1
  289. package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
  290. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
  291. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
  292. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +17 -1
  293. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +17 -1
  294. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +17 -1
  295. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -1
  296. package/src/modules/staff/commands/job-histories.ts +3 -3
  297. package/src/modules/staff/components/detail/addressesAdapter.ts +40 -23
  298. package/src/modules/staff/components/detail/notesAdapter.ts +28 -18
  299. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +74 -0
  300. package/src/modules/translations/components/TranslationManager.tsx +14 -1
  301. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
  302. package/src/modules/workflows/api/definitions/[id]/route.ts +3 -3
  303. package/src/modules/workflows/backend/definitions/[id]/page.tsx +18 -1
  304. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +19 -1
@@ -3,6 +3,7 @@
3
3
  import { useQuery } from '@tanstack/react-query'
4
4
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
6
7
 
7
8
  interface CompanyUser {
8
9
  id: string
@@ -59,9 +60,9 @@ export default function CompanyUsersWidget({ context }: CompanyUsersProps) {
59
60
  <div className="text-xs text-muted-foreground">{user.email}</div>
60
61
  </div>
61
62
  <div className="flex items-center gap-2">
62
- <span className={`text-xs ${user.isActive ? 'text-green-600' : 'text-red-600'}`}>
63
+ <StatusBadge variant={user.isActive ? 'success' : 'error'} dot>
63
64
  {user.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}
64
- </span>
65
+ </StatusBadge>
65
66
  <a
66
67
  href={`/backend/customer_accounts/users/${user.id}`}
67
68
  className="text-xs text-primary hover:underline"
@@ -68,6 +68,7 @@ const crud = makeCrudRoute({
68
68
  'is_primary',
69
69
  'organization_id',
70
70
  'tenant_id',
71
+ 'updated_at',
71
72
  ],
72
73
  sortFieldMap: {
73
74
  createdAt: 'created_at',
@@ -142,6 +143,7 @@ const addressListItemSchema = z.object({
142
143
  is_primary: z.boolean().nullable().optional(),
143
144
  organization_id: z.string().uuid().nullable().optional(),
144
145
  tenant_id: z.string().uuid().nullable().optional(),
146
+ updated_at: z.string().nullable().optional(),
145
147
  })
146
148
 
147
149
  const addressCreateResponseSchema = z.object({
@@ -42,12 +42,16 @@ const crud = makeCrudRoute({
42
42
  idField: 'id',
43
43
  orgField: 'organizationId',
44
44
  tenantField: 'tenantId',
45
+ // CustomerTag has no `deleted_at` column. Without this, the auto-registered
46
+ // generic optimistic-lock reader would filter on `deletedAt: null` against a
47
+ // non-existent column, throw, and fail open — silently disabling the lock.
48
+ softDeleteField: null,
45
49
  },
46
50
  indexer: { entityType: E.customers.customer_tag },
47
51
  list: {
48
52
  schema: listSchema,
49
53
  entityId: E.customers.customer_tag,
50
- fields: ['id', 'slug', 'label', 'color', 'description', 'organization_id', 'tenant_id'],
54
+ fields: ['id', 'slug', 'label', 'color', 'description', 'organization_id', 'tenant_id', 'updated_at'],
51
55
  buildFilters: async (query: any) => {
52
56
  const filters: Record<string, any> = {}
53
57
  if (query.search) {
@@ -107,6 +111,7 @@ const tagListItemSchema = z.object({
107
111
  description: z.string().nullable().optional(),
108
112
  organization_id: z.string().uuid().nullable().optional(),
109
113
  tenant_id: z.string().uuid().nullable().optional(),
114
+ updated_at: z.string().nullable().optional(),
110
115
  })
111
116
 
112
117
  const tagCreateResponseSchema = z.object({
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
- import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation'
6
6
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
7
  import { Building2, Hash, Users, BarChart3, StickyNote } from 'lucide-react'
8
8
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
@@ -16,6 +16,7 @@ import { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState,
16
16
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
17
17
  import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
18
18
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
19
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
19
20
  import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
20
21
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
21
22
  import { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
@@ -49,6 +50,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
49
50
  const t = useT()
50
51
  const router = useRouter()
51
52
  const searchParams = useSearchParams()
53
+ const pathname = usePathname()
52
54
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
53
55
 
54
56
  const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
@@ -193,6 +195,21 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
193
195
  logContext: 'customers.companies-v2',
194
196
  })
195
197
 
198
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
199
+ // mount so the enterprise record_locks widget resolves `customers.company` + id
200
+ // explicitly (the hardcoded path allowlist misses the `companies-v2` route).
201
+ useSetCurrentRecordInjectionContext(
202
+ buildRecordInjectionContext({
203
+ resourceKind: 'customers.company',
204
+ resourceId: currentCompanyId,
205
+ updatedAt: (data?.company as { updatedAt?: string | null; updated_at?: string | null } | undefined)?.updatedAt
206
+ ?? (data?.company as { updated_at?: string | null } | undefined)?.updated_at
207
+ ?? null,
208
+ data: data as Record<string, unknown> | null,
209
+ path: pathname,
210
+ }),
211
+ )
212
+
196
213
  const handleEditActivity = React.useCallback((activity: { id: string; interactionType?: string; title?: string | null; body?: string | null; scheduledAt?: string | null; occurredAt?: string | null; [key: string]: unknown }) => {
197
214
  const raw = activity as Record<string, unknown>
198
215
  const durationValue = typeof raw.duration === 'number'
@@ -470,7 +487,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
470
487
  <CrudForm<CompanyEditFormValues>
471
488
  embedded
472
489
  trackDirtyWhenEmbedded
473
- injectionSpotId="customers.company"
490
+ injectionSpotId="crud-form:customers.company"
474
491
  entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}
475
492
  schema={formSchema}
476
493
  fields={formFields}
@@ -1,7 +1,9 @@
1
1
  import * as React from 'react'
2
2
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
3
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
3
4
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
4
- import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
6
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
5
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
8
  import type {
7
9
  CompanyAssociationApiRecord,
@@ -95,6 +97,8 @@ type UseDealAssociationsOptions = {
95
97
  data: DealDetailPayload | null
96
98
  setData: React.Dispatch<React.SetStateAction<DealDetailPayload | null>>
97
99
  runMutationWithContext: GuardedMutationRunner
100
+ /** Re-fetch the deal detail; wired into the conflict bar's refresh action on a 409. */
101
+ onRefresh?: (() => void) | null
98
102
  }
99
103
 
100
104
  type UseDealAssociationsResult = {
@@ -113,6 +117,7 @@ export function useDealAssociations({
113
117
  data,
114
118
  setData,
115
119
  runMutationWithContext,
120
+ onRefresh,
116
121
  }: UseDealAssociationsOptions): UseDealAssociationsResult {
117
122
  const t = useT()
118
123
  const [peopleEditorIds, setPeopleEditorIds] = React.useState<string[]>([])
@@ -263,7 +268,10 @@ export function useDealAssociations({
263
268
  setPeopleSaving(true)
264
269
  try {
265
270
  await runMutationWithContext(
266
- () => updateCrud('customers/deals', { id: currentDealId, personIds: nextIds }),
271
+ () => withScopedApiRequestHeaders(
272
+ buildOptimisticLockHeader(data?.deal.updatedAt),
273
+ () => updateCrud('customers/deals', { id: currentDealId, personIds: nextIds }),
274
+ ),
267
275
  { id: currentDealId, personIds: nextIds, operation: 'updateDealPeople' },
268
276
  )
269
277
  const nextPeople = await loadPeopleAssociations(nextIds.slice(0, 3))
@@ -277,7 +285,7 @@ export function useDealAssociations({
277
285
  }
278
286
  : prev,
279
287
  )
280
- } catch {
288
+ } catch (error) {
281
289
  setPeopleEditorIds(previousIds)
282
290
  setData((prev) =>
283
291
  prev
@@ -289,12 +297,16 @@ export function useDealAssociations({
289
297
  }
290
298
  : prev,
291
299
  )
292
- flash(t('customers.deals.detail.peopleUpdateError', 'Failed to update linked people.'), 'error')
300
+ // runMutationWithContext already surfaces the conflict bar on a 409; only
301
+ // fall back to the generic flash when this is not a record conflict.
302
+ if (!surfaceRecordConflict(error, t, { onRefresh: onRefresh ?? null })) {
303
+ flash(t('customers.deals.detail.peopleUpdateError', 'Failed to update linked people.'), 'error')
304
+ }
293
305
  } finally {
294
306
  setPeopleSaving(false)
295
307
  }
296
308
  },
297
- [currentDealId, data?.people, loadPeopleAssociations, peopleEditorIds, runMutationWithContext, setData, t],
309
+ [currentDealId, data?.deal.updatedAt, data?.people, loadPeopleAssociations, onRefresh, peopleEditorIds, runMutationWithContext, setData, t],
298
310
  )
299
311
 
300
312
  const handleCompaniesAssociationsChange = React.useCallback(
@@ -307,7 +319,10 @@ export function useDealAssociations({
307
319
  setCompaniesSaving(true)
308
320
  try {
309
321
  await runMutationWithContext(
310
- () => updateCrud('customers/deals', { id: currentDealId, companyIds: nextIds }),
322
+ () => withScopedApiRequestHeaders(
323
+ buildOptimisticLockHeader(data?.deal.updatedAt),
324
+ () => updateCrud('customers/deals', { id: currentDealId, companyIds: nextIds }),
325
+ ),
311
326
  { id: currentDealId, companyIds: nextIds, operation: 'updateDealCompanies' },
312
327
  )
313
328
  const nextCompanies = await loadCompanyAssociations(nextIds.slice(0, 3))
@@ -321,7 +336,7 @@ export function useDealAssociations({
321
336
  }
322
337
  : prev,
323
338
  )
324
- } catch {
339
+ } catch (error) {
325
340
  setCompaniesEditorIds(previousIds)
326
341
  setData((prev) =>
327
342
  prev
@@ -333,7 +348,11 @@ export function useDealAssociations({
333
348
  }
334
349
  : prev,
335
350
  )
336
- flash(t('customers.deals.detail.companiesUpdateError', 'Failed to update linked companies.'), 'error')
351
+ // runMutationWithContext already surfaces the conflict bar on a 409; only
352
+ // fall back to the generic flash when this is not a record conflict.
353
+ if (!surfaceRecordConflict(error, t, { onRefresh: onRefresh ?? null })) {
354
+ flash(t('customers.deals.detail.companiesUpdateError', 'Failed to update linked companies.'), 'error')
355
+ }
337
356
  } finally {
338
357
  setCompaniesSaving(false)
339
358
  }
@@ -342,7 +361,9 @@ export function useDealAssociations({
342
361
  companiesEditorIds,
343
362
  currentDealId,
344
363
  data?.companies,
364
+ data?.deal.updatedAt,
345
365
  loadCompanyAssociations,
366
+ onRefresh,
346
367
  runMutationWithContext,
347
368
  setData,
348
369
  t,
@@ -1,10 +1,15 @@
1
1
  import * as React from 'react'
2
2
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
3
- import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
3
+ import { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
4
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
5
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
6
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
7
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
4
8
  import type { DealStatsPayload, GuardedMutationRunner } from './types'
5
9
 
6
10
  type UseDealClosureOptions = {
7
11
  currentDealId: string | null
12
+ dealUpdatedAt: string | null
8
13
  runMutationWithContext: GuardedMutationRunner
9
14
  confirmDiscardIfDirty: () => Promise<boolean>
10
15
  onClosed: () => Promise<void>
@@ -26,10 +31,12 @@ type UseDealClosureResult = {
26
31
 
27
32
  export function useDealClosure({
28
33
  currentDealId,
34
+ dealUpdatedAt,
29
35
  runMutationWithContext,
30
36
  confirmDiscardIfDirty,
31
37
  onClosed,
32
38
  }: UseDealClosureOptions): UseDealClosureResult {
39
+ const t = useT()
33
40
  const [lostDialogOpen, setLostDialogOpen] = React.useState(false)
34
41
  const [wonPopupOpen, setWonPopupOpen] = React.useState(false)
35
42
  const [lostPopupOpen, setLostPopupOpen] = React.useState(false)
@@ -51,45 +58,65 @@ export function useDealClosure({
51
58
  const handleWon = React.useCallback(async () => {
52
59
  if (!currentDealId) return
53
60
  if (!(await confirmDiscardIfDirty())) return
54
- await runMutationWithContext(
55
- () => updateCrud('customers/deals', { id: currentDealId, closureOutcome: 'won', status: 'win' }),
56
- { id: currentDealId, closureOutcome: 'won', status: 'win', operation: 'closeWon' },
57
- )
61
+ try {
62
+ await runMutationWithContext(
63
+ () => withScopedApiRequestHeaders(
64
+ buildOptimisticLockHeader(dealUpdatedAt),
65
+ () => updateCrud('customers/deals', { id: currentDealId, closureOutcome: 'won', status: 'win' }),
66
+ ),
67
+ { id: currentDealId, closureOutcome: 'won', status: 'win', operation: 'closeWon' },
68
+ )
69
+ } catch (err) {
70
+ if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onClosed() } })) {
71
+ flash(t('customers.deals.detail.closeWonError', 'Failed to mark deal as won.'), 'error')
72
+ }
73
+ return
74
+ }
58
75
  const stats = await fetchDealStats()
59
76
  setWonStats(stats)
60
77
  setWonPopupOpen(true)
61
78
  await onClosed()
62
- }, [confirmDiscardIfDirty, currentDealId, fetchDealStats, onClosed, runMutationWithContext])
79
+ }, [confirmDiscardIfDirty, currentDealId, dealUpdatedAt, fetchDealStats, onClosed, runMutationWithContext, t])
63
80
 
64
81
  const handleLostConfirm = React.useCallback(
65
82
  async (input: { lossReasonId: string; lossNotes?: string }) => {
66
83
  if (!currentDealId) return
67
84
  if (!(await confirmDiscardIfDirty())) return
68
- await runMutationWithContext(
69
- () =>
70
- updateCrud('customers/deals', {
85
+ try {
86
+ await runMutationWithContext(
87
+ () => withScopedApiRequestHeaders(
88
+ buildOptimisticLockHeader(dealUpdatedAt),
89
+ () =>
90
+ updateCrud('customers/deals', {
91
+ id: currentDealId,
92
+ closureOutcome: 'lost',
93
+ status: 'loose',
94
+ lossReasonId: input.lossReasonId,
95
+ lossNotes: input.lossNotes ?? null,
96
+ }),
97
+ ),
98
+ {
71
99
  id: currentDealId,
72
100
  closureOutcome: 'lost',
73
101
  status: 'loose',
74
102
  lossReasonId: input.lossReasonId,
75
103
  lossNotes: input.lossNotes ?? null,
76
- }),
77
- {
78
- id: currentDealId,
79
- closureOutcome: 'lost',
80
- status: 'loose',
81
- lossReasonId: input.lossReasonId,
82
- lossNotes: input.lossNotes ?? null,
83
- operation: 'closeLost',
84
- },
85
- )
104
+ operation: 'closeLost',
105
+ },
106
+ )
107
+ } catch (err) {
108
+ if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onClosed() } })) {
109
+ flash(t('customers.deals.detail.closeLostError', 'Failed to mark deal as lost.'), 'error')
110
+ }
111
+ return
112
+ }
86
113
  setLostDialogOpen(false)
87
114
  const stats = await fetchDealStats()
88
115
  setLostStats(stats)
89
116
  setLostPopupOpen(true)
90
117
  await onClosed()
91
118
  },
92
- [confirmDiscardIfDirty, currentDealId, fetchDealStats, onClosed, runMutationWithContext],
119
+ [confirmDiscardIfDirty, currentDealId, dealUpdatedAt, fetchDealStats, onClosed, runMutationWithContext, t],
93
120
  )
94
121
 
95
122
  const openLostDialog = React.useCallback(() => setLostDialogOpen(true), [])
@@ -1,6 +1,9 @@
1
1
  import * as React from 'react'
2
2
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
3
3
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
4
+ import { withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
5
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
6
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
4
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
8
  import type { DealDetailPayload, GuardedMutationRunner } from './types'
6
9
 
@@ -35,13 +38,20 @@ export function useDealPipeline({
35
38
  setIsStageSaving(true)
36
39
  try {
37
40
  await runMutationWithContext(
38
- () => updateCrud('customers/deals', { id: currentDealId, pipelineStageId: nextStageId }),
41
+ () => withScopedApiRequestHeaders(
42
+ buildOptimisticLockHeader(data.deal.updatedAt),
43
+ () => updateCrud('customers/deals', { id: currentDealId, pipelineStageId: nextStageId }),
44
+ ),
39
45
  { id: currentDealId, pipelineStageId: nextStageId, operation: 'updateDealStage' },
40
46
  )
41
47
  flash(t('customers.deals.detail.stageUpdateSuccess', 'Deal stage updated.'), 'success')
42
48
  await onStageChanged()
43
- } catch {
44
- flash(t('customers.deals.detail.stageUpdateError', 'Failed to update deal stage.'), 'error')
49
+ } catch (err) {
50
+ // A concurrent edit / pessimistic lock 409 routes to the unified conflict
51
+ // bar or merge dialog (S3); surface any other error as a flash.
52
+ if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onStageChanged() } })) {
53
+ flash(t('customers.deals.detail.stageUpdateError', 'Failed to update deal stage.'), 'error')
54
+ }
45
55
  } finally {
46
56
  setIsStageSaving(false)
47
57
  }
@@ -4,11 +4,12 @@ import * as React from 'react'
4
4
  import Link from 'next/link'
5
5
  import { Building2, UserSearch, Users } from 'lucide-react'
6
6
  import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
7
- import { useRouter, useSearchParams } from 'next/navigation'
7
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation'
8
8
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
9
9
  import { Button } from '@open-mercato/ui/primitives/button'
10
10
  import { AttachmentsSection, ErrorMessage, LoadingMessage, NotesSection, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
11
11
  import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
12
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
12
13
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
13
14
  import { CollapsibleZoneLayout } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'
14
15
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -52,6 +53,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
52
53
  const t = useT()
53
54
  const router = useRouter()
54
55
  const searchParams = useSearchParams()
56
+ const pathname = usePathname()
55
57
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
56
58
  const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
57
59
 
@@ -80,6 +82,20 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
80
82
  data,
81
83
  })
82
84
 
85
+ // Publish page-load record context with explicit `updatedAt`/`data` to the
86
+ // AppShell-owned `backend:record:current` mount. The deal path is already in the
87
+ // widget's allowlist (presence works) but supplied no version/record payload —
88
+ // this gives the merge dialog its action-log base and field-diff source.
89
+ useSetCurrentRecordInjectionContext(
90
+ buildRecordInjectionContext({
91
+ resourceKind: 'customers.deal',
92
+ resourceId: data?.deal.id ?? null,
93
+ updatedAt: data?.deal.updatedAt ?? null,
94
+ data: data as Record<string, unknown> | null,
95
+ path: pathname,
96
+ }),
97
+ )
98
+
83
99
  const notesAdapter = React.useMemo(
84
100
  () => createCustomerNotesAdapter(detailTranslator, { runMutation: runMutationWithContext }),
85
101
  [detailTranslator, runMutationWithContext],
@@ -171,6 +187,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
171
187
  data,
172
188
  setData,
173
189
  runMutationWithContext,
190
+ onRefresh: () => { void loadData() },
174
191
  })
175
192
 
176
193
  const { isStageSaving, handleStageChange } = useDealPipeline({
@@ -195,6 +212,7 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
195
212
  handleLostConfirm,
196
213
  } = useDealClosure({
197
214
  currentDealId,
215
+ dealUpdatedAt: data?.deal.updatedAt ?? null,
198
216
  runMutationWithContext,
199
217
  confirmDiscardIfDirty,
200
218
  onClosed: loadData,
@@ -365,6 +383,8 @@ export default function DealDetailPage({ params }: { params?: { id?: string } })
365
383
  showAssociationsGroup={false}
366
384
  showVersionHistory={false}
367
385
  showCancelAction={false}
386
+ injectionSpotId="crud-form:customers.deal"
387
+ optimisticLockUpdatedAt={data.deal.updatedAt}
368
388
  onDirtyChange={setIsDirty}
369
389
  initialPipelineOptions={formPipelineOptions}
370
390
  initialPipelineStageOptions={data.pipelineStages}
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
- import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation'
6
6
  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'
@@ -24,6 +24,7 @@ import { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState,
24
24
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
25
25
  import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
26
26
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
27
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
27
28
  import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
28
29
 
29
30
  import { ActivitiesSection } from '../../../../components/detail/ActivitiesSection'
@@ -57,6 +58,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
57
58
  const t = useT()
58
59
  const router = useRouter()
59
60
  const searchParams = useSearchParams()
61
+ const pathname = usePathname()
60
62
  const { organizationId } = useOrganizationScopeDetail()
61
63
  const isMobile = useIsMobile()
62
64
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
@@ -219,6 +221,20 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
219
221
  [injectionContext, runMutation],
220
222
  )
221
223
 
224
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
225
+ // mount so the enterprise record_locks widget resolves `customers.person` + id
226
+ // explicitly (the hardcoded path allowlist misses the `people-v2` route).
227
+ // Presence/acquire/heartbeat run on load; the hook clears on unmount/record switch.
228
+ useSetCurrentRecordInjectionContext(
229
+ buildRecordInjectionContext({
230
+ resourceKind: 'customers.person',
231
+ resourceId: currentPersonId,
232
+ updatedAt: data?.person?.updatedAt ?? data?.person?.updated_at ?? null,
233
+ data: data as Record<string, unknown> | null,
234
+ path: pathname,
235
+ }),
236
+ )
237
+
222
238
  const handleAddActivity = React.useCallback((kind: ActivityKind) => {
223
239
  setScheduleEditData({
224
240
  id: '',
@@ -518,7 +534,7 @@ export default function PersonDetailV2Page({ params }: { params?: { id?: string
518
534
  <CrudForm<PersonEditFormValues>
519
535
  embedded
520
536
  trackDirtyWhenEmbedded
521
- injectionSpotId="customers.person"
537
+ injectionSpotId="crud-form:customers.person"
522
538
  entityIds={[E.customers.customer_entity, E.customers.customer_person_profile]}
523
539
  schema={formSchema}
524
540
  fields={fields}
@@ -39,7 +39,7 @@ import {
39
39
  buildCustomFieldResetMap,
40
40
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
41
41
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
42
- import { enforceRecordGoneIsConflict, enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
42
+ import { enforceRecordGoneIsConflict, enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
43
43
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
44
44
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
45
45
  import { recomputeNextInteraction } from '../lib/interactionProjection'
@@ -644,7 +644,7 @@ const updateInteractionCommand: CommandHandler<InteractionUpdateInput, { interac
644
644
  // when the client opted into optimistic locking, a stale edit fails with the
645
645
  // unified 409 instead of silently overwriting (#2055). Strictly additive —
646
646
  // no-op when no expected-version header is present.
647
- enforceCommandOptimisticLock({
647
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
648
648
  resourceKind: 'customers.interaction',
649
649
  resourceId: interaction.id,
650
650
  current: interaction.updatedAt,
@@ -912,7 +912,7 @@ const completeInteractionCommand: CommandHandler<InteractionCompleteInput, { int
912
912
  ensureTenantScope(ctx, interaction.tenantId)
913
913
  ensureOrganizationScope(ctx, interaction.organizationId)
914
914
 
915
- enforceCommandOptimisticLock({
915
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
916
916
  resourceKind: 'customers.interaction',
917
917
  resourceId: interaction.id,
918
918
  current: interaction.updatedAt,
@@ -1052,7 +1052,7 @@ const cancelInteractionCommand: CommandHandler<InteractionCancelInput, { interac
1052
1052
  ensureTenantScope(ctx, interaction.tenantId)
1053
1053
  ensureOrganizationScope(ctx, interaction.organizationId)
1054
1054
 
1055
- enforceCommandOptimisticLock({
1055
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
1056
1056
  resourceKind: 'customers.interaction',
1057
1057
  resourceId: interaction.id,
1058
1058
  current: interaction.updatedAt,
@@ -1190,7 +1190,7 @@ const deleteInteractionCommand: CommandHandler<{ body?: Record<string, unknown>;
1190
1190
  ensureTenantScope(ctx, interaction.tenantId)
1191
1191
  ensureOrganizationScope(ctx, interaction.organizationId)
1192
1192
 
1193
- enforceCommandOptimisticLock({
1193
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
1194
1194
  resourceKind: 'customers.interaction',
1195
1195
  resourceId: interaction.id,
1196
1196
  current: interaction.updatedAt,
@@ -16,6 +16,10 @@ import {
16
16
  import { ensureOrganizationScope, ensureTenantScope, ensureDictionaryEntry } from './shared'
17
17
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
18
18
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
19
+ import {
20
+ enforceCommandOptimisticLockWithGuards,
21
+ enforceRecordGoneIsConflict,
22
+ } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
19
23
 
20
24
  const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { stageId: string }> = {
21
25
  id: 'customers.pipeline-stages.create',
@@ -106,11 +110,21 @@ const updatePipelineStageCommand: CommandHandler<PipelineStageUpdateInput, void>
106
110
  ...(callerTenantId ? { tenantId: callerTenantId } : {}),
107
111
  ...(callerOrganizationId ? { organizationId: callerOrganizationId } : {}),
108
112
  })
109
- if (!stage) throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
113
+ if (!stage) {
114
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.pipelineStage', resourceId: parsed.id, request: ctx.request ?? null })
115
+ throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
116
+ }
110
117
 
111
118
  ensureTenantScope(ctx, stage.tenantId)
112
119
  ensureOrganizationScope(ctx, stage.organizationId)
113
120
 
121
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
122
+ resourceKind: 'customers.pipelineStage',
123
+ resourceId: stage.id,
124
+ current: stage.updatedAt,
125
+ request: ctx.request ?? null,
126
+ })
127
+
114
128
  await withAtomicFlush(em, [
115
129
  () => {
116
130
  if (parsed.label !== undefined) stage.label = parsed.label
@@ -148,11 +162,21 @@ const deletePipelineStageCommand: CommandHandler<PipelineStageDeleteInput, void>
148
162
  ...(callerTenantId ? { tenantId: callerTenantId } : {}),
149
163
  ...(callerOrganizationId ? { organizationId: callerOrganizationId } : {}),
150
164
  })
151
- if (!stage) throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
165
+ if (!stage) {
166
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.pipelineStage', resourceId: parsed.id, request: ctx.request ?? null })
167
+ throw new CrudHttpError(404, { error: 'Pipeline stage not found' })
168
+ }
152
169
 
153
170
  ensureTenantScope(ctx, stage.tenantId)
154
171
  ensureOrganizationScope(ctx, stage.organizationId)
155
172
 
173
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
174
+ resourceKind: 'customers.pipelineStage',
175
+ resourceId: stage.id,
176
+ current: stage.updatedAt,
177
+ request: ctx.request ?? null,
178
+ })
179
+
156
180
  const activeDealsCount = await em.count(CustomerDeal, {
157
181
  pipelineStageId: parsed.id,
158
182
  deletedAt: null,
@@ -12,6 +12,10 @@ import {
12
12
  } from '../data/validators'
13
13
  import { ensureOrganizationScope, ensureTenantScope } from './shared'
14
14
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
15
+ import {
16
+ enforceCommandOptimisticLockWithGuards,
17
+ enforceRecordGoneIsConflict,
18
+ } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
15
19
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
16
20
 
17
21
  const createPipelineCommand: CommandHandler<PipelineCreateInput, { pipelineId: string }> = {
@@ -56,11 +60,21 @@ const updatePipelineCommand: CommandHandler<PipelineUpdateInput, void> = {
56
60
 
57
61
  const em = (ctx.container.resolve('em') as EntityManager).fork()
58
62
  const pipeline = await em.findOne(CustomerPipeline, { id: parsed.id })
59
- if (!pipeline) throw new CrudHttpError(404, { error: 'Pipeline not found' })
63
+ if (!pipeline) {
64
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.pipeline', resourceId: parsed.id, request: ctx.request ?? null })
65
+ throw new CrudHttpError(404, { error: 'Pipeline not found' })
66
+ }
60
67
 
61
68
  ensureTenantScope(ctx, pipeline.tenantId)
62
69
  ensureOrganizationScope(ctx, pipeline.organizationId)
63
70
 
71
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
72
+ resourceKind: 'customers.pipeline',
73
+ resourceId: pipeline.id,
74
+ current: pipeline.updatedAt,
75
+ request: ctx.request ?? null,
76
+ })
77
+
64
78
  await withAtomicFlush(em, [
65
79
  async () => {
66
80
  if (parsed.isDefault && !pipeline.isDefault) {
@@ -86,11 +100,21 @@ const deletePipelineCommand: CommandHandler<PipelineDeleteInput, void> = {
86
100
 
87
101
  const em = (ctx.container.resolve('em') as EntityManager).fork()
88
102
  const pipeline = await em.findOne(CustomerPipeline, { id: parsed.id })
89
- if (!pipeline) throw new CrudHttpError(404, { error: 'Pipeline not found' })
103
+ if (!pipeline) {
104
+ enforceRecordGoneIsConflict({ resourceKind: 'customers.pipeline', resourceId: parsed.id, request: ctx.request ?? null })
105
+ throw new CrudHttpError(404, { error: 'Pipeline not found' })
106
+ }
90
107
 
91
108
  ensureTenantScope(ctx, pipeline.tenantId)
92
109
  ensureOrganizationScope(ctx, pipeline.organizationId)
93
110
 
111
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
112
+ resourceKind: 'customers.pipeline',
113
+ resourceId: pipeline.id,
114
+ current: pipeline.updatedAt,
115
+ request: ctx.request ?? null,
116
+ })
117
+
94
118
  const activeDealsCount = await em.count(CustomerDeal, {
95
119
  pipelineId: parsed.id,
96
120
  deletedAt: null,