@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
@@ -5,7 +5,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
7
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
8
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
9
9
  import { BusinessRule } from '../../data/entities'
10
10
  import type { EntityManager } from '@mikro-orm/postgresql'
11
11
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
@@ -280,7 +280,7 @@ export async function PUT(req: Request) {
280
280
  }
281
281
 
282
282
  try {
283
- enforceCommandOptimisticLock({
283
+ await enforceCommandOptimisticLockWithGuards(container, {
284
284
  resourceKind: 'business_rules.rule',
285
285
  resourceId: rule.id,
286
286
  current: rule.updatedAt ?? null,
@@ -338,7 +338,7 @@ export async function DELETE(req: Request) {
338
338
  }
339
339
 
340
340
  try {
341
- enforceCommandOptimisticLock({
341
+ await enforceCommandOptimisticLockWithGuards(container, {
342
342
  resourceKind: 'business_rules.rule',
343
343
  resourceId: rule.id,
344
344
  current: rule.updatedAt ?? null,
@@ -4,7 +4,7 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
4
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
7
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
8
8
  import { RuleSet } from '../../data/entities'
9
9
  import type { EntityManager } from '@mikro-orm/postgresql'
10
10
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
@@ -224,7 +224,7 @@ export async function PUT(req: Request) {
224
224
  }
225
225
 
226
226
  try {
227
- enforceCommandOptimisticLock({
227
+ await enforceCommandOptimisticLockWithGuards(container, {
228
228
  resourceKind: 'business_rules.ruleSet',
229
229
  resourceId: ruleSet.id,
230
230
  current: ruleSet.updatedAt ?? null,
@@ -271,7 +271,7 @@ export async function DELETE(req: Request) {
271
271
  }
272
272
 
273
273
  try {
274
- enforceCommandOptimisticLock({
274
+ await enforceCommandOptimisticLockWithGuards(container, {
275
275
  resourceKind: 'business_rules.ruleSet',
276
276
  resourceId: ruleSet.id,
277
277
  current: ruleSet.updatedAt ?? null,
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
- import { useRouter, useParams } from 'next/navigation'
5
+ import { useRouter, useParams, usePathname } from 'next/navigation'
6
6
  import { useQuery } from '@tanstack/react-query'
7
7
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
8
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
@@ -10,6 +10,7 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
10
10
  import { Button } from '@open-mercato/ui/primitives/button'
11
11
  import { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'
12
12
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
13
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
13
14
  import { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'
14
15
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
15
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -27,6 +28,7 @@ import { buildRulePayload, parseRuleToFormValues } from '../../../components/uti
27
28
  export default function EditBusinessRulePage() {
28
29
  const router = useRouter()
29
30
  const params = useParams()
31
+ const pathname = usePathname()
30
32
 
31
33
  // Handle catch-all route: params.slug = ['rules', 'uuid']
32
34
  let ruleId: string | undefined
@@ -103,6 +105,20 @@ export default function EditBusinessRulePage() {
103
105
  [t]
104
106
  )
105
107
 
108
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
109
+ // mount so the enterprise record_locks widget resolves `business_rules.rule` + id
110
+ // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`
111
+ // call so the held lock matches the save-time conflict surface for the same rule.
112
+ useSetCurrentRecordInjectionContext(
113
+ buildRecordInjectionContext({
114
+ resourceKind: 'business_rules.rule',
115
+ resourceId: ruleId ?? null,
116
+ updatedAt: rule?.updatedAt ?? rule?.updated_at ?? null,
117
+ data: (rule ?? null) as Record<string, unknown> | null,
118
+ path: pathname,
119
+ }),
120
+ )
121
+
106
122
  if (isLoading) {
107
123
  return (
108
124
  <Page>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from 'react'
4
4
  import Link from 'next/link'
5
- import { useRouter, useParams } from 'next/navigation'
5
+ import { useRouter, useParams, usePathname } from 'next/navigation'
6
6
  import { useQuery, useQueryClient } from '@tanstack/react-query'
7
7
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
8
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
@@ -12,6 +12,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
12
12
  import { apiFetch, withScopedApiHeaders } from '@open-mercato/ui/backend/utils/api'
13
13
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
14
14
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
15
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
15
16
  import { readJsonSafe } from '@open-mercato/ui/backend/utils/serverErrors'
16
17
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
17
18
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
@@ -55,6 +56,7 @@ type RuleSetDetail = {
55
56
  export default function EditRuleSetPage() {
56
57
  const router = useRouter()
57
58
  const params = useParams()
59
+ const pathname = usePathname()
58
60
 
59
61
  // Handle catch-all route: params.slug = ['sets', 'uuid']
60
62
  let setId: string | undefined
@@ -237,6 +239,21 @@ export default function EditRuleSetPage() {
237
239
  ]
238
240
  }, [t, ruleSet, handleAddMember, handleUpdateMember, handleRemoveMember])
239
241
 
242
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
243
+ // mount so the enterprise record_locks widget resolves the rule set + id
244
+ // explicitly. The resourceKind mirrors the route's `enforceCommandOptimisticLock`
245
+ // call (`business_rules.ruleSet`) so the held lock matches the save-time conflict
246
+ // surface for the same rule set.
247
+ useSetCurrentRecordInjectionContext(
248
+ buildRecordInjectionContext({
249
+ resourceKind: 'business_rules.ruleSet',
250
+ resourceId: setId ?? null,
251
+ updatedAt: ruleSet?.updatedAt ?? null,
252
+ data: (ruleSet ?? null) as Record<string, unknown> | null,
253
+ path: pathname,
254
+ }),
255
+ )
256
+
240
257
  if (isLoading) {
241
258
  return (
242
259
  <Page>
@@ -10,6 +10,10 @@ import { CatalogProduct } from '../../data/entities'
10
10
  import { E } from '#generated/entities.ids.generated'
11
11
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
12
12
 
13
+ // record_locks decision: this route is read-only (GET only) and projects the
14
+ // attachment side-table for a product. It is EXEMPT from the optimistic-lock /
15
+ // record-lock guard (Phase 4) — there is no mutating method here, and media
16
+ // uploads/deletes are owned by the attachments module's own write paths.
13
17
  export const metadata = {
14
18
  GET: { requireAuth: true, requireFeatures: ['catalog.products.view'] },
15
19
  }
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { usePathname } from 'next/navigation'
4
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
5
6
  import { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
6
7
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
@@ -8,6 +9,7 @@ import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
8
9
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
9
10
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
10
11
  import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
12
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
11
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
14
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
13
15
  import { E } from '#generated/entities.ids.generated'
@@ -81,6 +83,7 @@ async function submitCategoryUpdate(
81
83
  export default function EditCatalogCategoryPage({ params }: { params?: { id?: string } }) {
82
84
  const categoryId = params?.id ?? ''
83
85
  const t = useT()
86
+ const pathname = usePathname()
84
87
  const [initialValues, setInitialValues] = React.useState<CategoryFormValues | null>(null)
85
88
  const [pathLabel, setPathLabel] = React.useState<string>('')
86
89
  const [loading, setLoading] = React.useState<boolean>(true)
@@ -195,6 +198,20 @@ export default function EditCatalogCategoryPage({ params }: { params?: { id?: st
195
198
  },
196
199
  ], [t])
197
200
 
201
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
202
+ // mount so the enterprise record_locks widget resolves `catalog.category` + id
203
+ // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
204
+ // lock matches the save-time conflict surface for the same category.
205
+ useSetCurrentRecordInjectionContext(
206
+ buildRecordInjectionContext({
207
+ resourceKind: 'catalog.category',
208
+ resourceId: categoryId || null,
209
+ updatedAt: initialValues?.updatedAt ?? null,
210
+ data: initialValues as Record<string, unknown> | null,
211
+ path: pathname,
212
+ }),
213
+ )
214
+
198
215
  if (!categoryId) {
199
216
  return (
200
217
  <Page>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import Link from "next/link";
5
+ import { usePathname } from "next/navigation";
5
6
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
6
7
  import { ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
7
8
  import {
@@ -41,6 +42,10 @@ import {
41
42
  } from "@open-mercato/ui/backend/utils/apiCall";
42
43
  import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
43
44
  import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
45
+ import {
46
+ buildRecordInjectionContext,
47
+ useSetCurrentRecordInjectionContext,
48
+ } from "@open-mercato/ui/backend/injection/recordContext";
44
49
  import { useT } from "@open-mercato/shared/lib/i18n/context";
45
50
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
46
51
  import { E } from "#generated/entities.ids.generated";
@@ -314,6 +319,7 @@ export default function EditCatalogProductPage({
314
319
  }) {
315
320
  const productId = params?.id ? String(params.id) : null;
316
321
  const t = useT();
322
+ const pathname = usePathname();
317
323
  const productSubpathPrefix = productId
318
324
  ? `/backend/catalog/products/${productId}/`
319
325
  : null;
@@ -835,6 +841,21 @@ export default function EditCatalogProductPage({
835
841
  el.scrollIntoView({ behavior: 'smooth', block: 'start' })
836
842
  })
837
843
 
844
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
845
+ // mount so the enterprise record_locks widget resolves `catalog.product` + id
846
+ // explicitly (presence/acquire/heartbeat on load; cleared on unmount). The
847
+ // resourceKind mirrors the CrudForm `versionHistory` so the held lock matches
848
+ // the save-time conflict surface for the same product.
849
+ useSetCurrentRecordInjectionContext(
850
+ buildRecordInjectionContext({
851
+ resourceKind: "catalog.product",
852
+ resourceId: productId,
853
+ updatedAt: initialValues?.updatedAt ?? null,
854
+ data: initialValues as Record<string, unknown> | null,
855
+ path: pathname,
856
+ }),
857
+ );
858
+
838
859
  const handleVariantDeleted = React.useCallback((variantId: string) => {
839
860
  setVariants((prev) => prev.filter((variant) => variant.id !== variantId));
840
861
  }, []);
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { useRouter } from 'next/navigation'
4
+ import { useRouter, usePathname } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
7
7
  import { createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
@@ -10,6 +10,7 @@ import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customF
10
10
  import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
11
11
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
12
12
  import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
13
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
13
14
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
14
15
  import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
15
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -100,6 +101,7 @@ function resolveVariantPriceLabel(prices: Record<string, VariantPriceDraft> | un
100
101
  export default function EditVariantPage({ params }: { params?: { productId?: string; variantId?: string } }) {
101
102
  const router = useRouter()
102
103
  const t = useT()
104
+ const pathname = usePathname()
103
105
  const productId = params?.productId ? String(params.productId) : null
104
106
  const variantId = params?.variantId ? String(params.variantId) : null
105
107
  const isCreateSentinel = variantId === 'create'
@@ -476,6 +478,19 @@ export default function EditVariantPage({ params }: { params?: { productId?: str
476
478
  return list
477
479
  }, [optionDefinitions, priceKinds, t, taxRates])
478
480
 
481
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
482
+ // mount so the enterprise record_locks widget resolves `catalog.variant` + id
483
+ // explicitly. Skipped on the create-delegation path (no record to lock).
484
+ useSetCurrentRecordInjectionContext(
485
+ buildRecordInjectionContext({
486
+ resourceKind: 'catalog.variant',
487
+ resourceId: isCreateSentinel ? null : variantId,
488
+ updatedAt: initialValues?.updatedAt ?? null,
489
+ data: initialValues as Record<string, unknown> | null,
490
+ path: pathname,
491
+ }),
492
+ )
493
+
479
494
  if (isCreateSentinel) {
480
495
  if (!productId) {
481
496
  return (
@@ -18,7 +18,8 @@ import {
18
18
  } from '@open-mercato/ui/primitives/dialog'
19
19
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
20
20
  import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
21
- import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
21
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
22
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
22
23
  import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
23
24
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
24
25
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -236,9 +237,15 @@ export function PriceKindSettings() {
236
237
  await loadItems()
237
238
  } catch (err) {
238
239
  console.error('catalog.price-kinds.save failed', err)
239
- const message = extractOptimisticLockConflict(err)
240
- ? t('ui.forms.flash.recordModified', 'This record was modified by someone else. Refresh and try again.')
241
- : err instanceof Error ? err.message : t('catalog.priceKinds.errors.save', 'Failed to save price kind.')
240
+ // Route a concurrent-edit 409 through the single conflict surface (unified
241
+ // conflict bar, or the enterprise merge dialog when its handler is mounted)
242
+ // and close the editor so that surface owns the resolution. Other errors
243
+ // stay inline in the dialog so the user can correct and resubmit.
244
+ if (surfaceRecordConflict(err, t, { onRefresh: () => { void loadItems() } })) {
245
+ closeDialog()
246
+ return
247
+ }
248
+ const message = err instanceof Error ? err.message : t('catalog.priceKinds.errors.save', 'Failed to save price kind.')
242
249
  setError(message)
243
250
  } finally {
244
251
  setSubmitting(false)
@@ -270,9 +277,10 @@ export function PriceKindSettings() {
270
277
  await loadItems()
271
278
  } catch (err) {
272
279
  console.error('catalog.price-kinds.delete failed', err)
273
- const message = extractOptimisticLockConflict(err)
274
- ? t('ui.forms.flash.recordModified', 'This record was modified by someone else. Refresh and try again.')
275
- : err instanceof Error ? err.message : t('catalog.priceKinds.errors.delete', 'Failed to delete price kind.')
280
+ // Route a concurrent-edit 409 through the single conflict surface; fall
281
+ // back to a flash for any other delete failure.
282
+ if (surfaceRecordConflict(err, t, { onRefresh: () => { void loadItems() } })) return
283
+ const message = err instanceof Error ? err.message : t('catalog.priceKinds.errors.delete', 'Failed to delete price kind.')
276
284
  flash(message, 'error')
277
285
  }
278
286
  },
@@ -82,6 +82,12 @@ export async function deleteCatalogProductsWithProgress(params: {
82
82
  let affectedCount = 0
83
83
  const deletedIds = new Set<string>()
84
84
 
85
+ // record_locks decision: bulk product delete is EXEMPT from the optimistic-lock
86
+ // / record-lock guard (Phase 4). It is an explicit destructive operation over a
87
+ // multi-selection, not a collaborative field edit, and the queued job has no
88
+ // per-row expected `updated_at` to compare against — mirroring the customers
89
+ // bulk pattern from Phase 2. Single-product delete on the detail screen still
90
+ // rides the CRUD mutation-guard decorator with the version header.
85
91
  for (const [index, id] of ids.entries()) {
86
92
  await commandBus.execute<{ body?: Record<string, unknown> }, { productId: string }>('catalog.products.delete', {
87
93
  input: { body: { id } },
@@ -1,14 +1,16 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import { useRouter, useParams } from 'next/navigation'
4
+ import { useRouter, useParams, usePathname } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { CrudForm, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
7
7
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
8
8
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
10
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
11
- import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
11
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
12
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
13
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
12
14
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
13
15
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
16
  import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
@@ -35,6 +37,7 @@ type CurrencyData = {
35
37
  export default function EditCurrencyPage({ params }: { params?: { id?: string } }) {
36
38
  const t = useT()
37
39
  const router = useRouter()
40
+ const pathname = usePathname()
38
41
  const { confirm: confirmDialog, ConfirmDialogElement } = useConfirmDialog()
39
42
  const mutationContextId = 'currencies-edit:delete'
40
43
  const { runMutation, retryLastMutation } = useGuardedMutation<{
@@ -185,11 +188,25 @@ export default function EditCurrencyPage({ params }: { params?: { id?: string }
185
188
  flash(t('currencies.flash.deleted'), 'success')
186
189
  router.push('/backend/currencies')
187
190
  } catch (error) {
188
- if (extractOptimisticLockConflict(error)) return
191
+ if (surfaceRecordConflict(error, t)) return
189
192
  flash(t('currencies.flash.deleteError'), 'error')
190
193
  }
191
194
  }, [currency, t, router, confirmDialog, mutationContextId, retryLastMutation, runMutation])
192
195
 
196
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
197
+ // mount so the enterprise record_locks widget resolves `currencies.currency` + id
198
+ // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
199
+ // lock matches the save-time conflict surface for the same currency.
200
+ useSetCurrentRecordInjectionContext(
201
+ buildRecordInjectionContext({
202
+ resourceKind: 'currencies.currency',
203
+ resourceId: currency?.id ?? null,
204
+ updatedAt: currency?.updatedAt ?? currency?.updated_at ?? null,
205
+ data: currency as Record<string, unknown> | null,
206
+ path: pathname,
207
+ }),
208
+ )
209
+
193
210
  if (loading) {
194
211
  return (
195
212
  <Page>
@@ -14,6 +14,7 @@ import { Plus, Star } from 'lucide-react'
14
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
15
15
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
16
16
  import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
17
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
17
18
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
18
19
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
19
20
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
@@ -188,7 +189,7 @@ export default function CurrenciesPage() {
188
189
  flash(t('currencies.flash.deleted'), 'success')
189
190
  setReloadToken((token) => token + 1)
190
191
  } catch (error) {
191
- if (extractOptimisticLockConflict(error)) return
192
+ if (surfaceRecordConflict(error, t, { onRefresh: () => setReloadToken((token) => token + 1) })) return
192
193
  flash(t('currencies.flash.deleteError'), 'error')
193
194
  }
194
195
  },
@@ -1,13 +1,14 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
- import { useRouter } from 'next/navigation'
4
+ import { useRouter, usePathname } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
7
7
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
8
8
  import { updateCrud } from '@open-mercato/ui/backend/utils/crud'
9
9
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
10
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
11
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
11
12
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
13
  import {
13
14
  loadCurrencyOptions,
@@ -47,6 +48,7 @@ type ExchangeRateData = {
47
48
  export default function EditExchangeRatePage({ params }: { params?: { id?: string } }) {
48
49
  const t = useT()
49
50
  const router = useRouter()
51
+ const pathname = usePathname()
50
52
 
51
53
  const [exchangeRate, setExchangeRate] = React.useState<ExchangeRateData | null>(null)
52
54
  const [loading, setLoading] = React.useState(true)
@@ -84,6 +86,20 @@ export default function EditExchangeRatePage({ params }: { params?: { id?: strin
84
86
  [t, loadOptions]
85
87
  )
86
88
 
89
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
90
+ // mount so the enterprise record_locks widget resolves `currencies.exchange_rate`
91
+ // + id explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the
92
+ // held lock matches the save-time conflict surface for the same exchange rate.
93
+ useSetCurrentRecordInjectionContext(
94
+ buildRecordInjectionContext({
95
+ resourceKind: 'currencies.exchange_rate',
96
+ resourceId: exchangeRate?.id ?? null,
97
+ updatedAt: exchangeRate?.updatedAt ?? exchangeRate?.updated_at ?? null,
98
+ data: exchangeRate as Record<string, unknown> | null,
99
+ path: pathname,
100
+ }),
101
+ )
102
+
87
103
  if (loading) {
88
104
  return (
89
105
  <Page>
@@ -13,7 +13,8 @@ import { BooleanIcon } from '@open-mercato/ui/backend/ValueIcons'
13
13
  import { Plus } from 'lucide-react'
14
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
15
15
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
16
- import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
16
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
17
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
17
18
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
18
19
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
19
20
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
@@ -152,7 +153,7 @@ export default function ExchangeRatesPage() {
152
153
  flash(t('exchangeRates.flash.deleted'), 'success')
153
154
  setReloadToken((token) => token + 1)
154
155
  } catch (error) {
155
- if (extractOptimisticLockConflict(error)) return
156
+ if (surfaceRecordConflict(error, t, { onRefresh: () => setReloadToken((token) => token + 1) })) return
156
157
  flash(t('exchangeRates.flash.deleteError'), 'error')
157
158
  }
158
159
  },
@@ -7,7 +7,7 @@ import { RbacService } from '@open-mercato/core/modules/auth/services/rbacServic
7
7
  import { CustomerRole, CustomerRoleAcl, CustomerUserRole } from '@open-mercato/core/modules/customer_accounts/data/entities'
8
8
  import { updateRoleSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
9
9
  import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
10
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
10
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
11
11
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
12
12
 
13
13
  export const metadata = {}
@@ -107,7 +107,7 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
107
107
  // Optimistic lock: refuse a stale overwrite so two admins editing the same role
108
108
  // in parallel cannot silently clobber each other (#2055). Strictly additive.
109
109
  try {
110
- enforceCommandOptimisticLock({
110
+ await enforceCommandOptimisticLockWithGuards(container, {
111
111
  resourceKind: 'customer_accounts.role',
112
112
  resourceId: role.id,
113
113
  current: role.updatedAt ?? null,
@@ -169,7 +169,7 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
169
169
 
170
170
  // Optimistic lock: refuse a stale delete. Strictly additive.
171
171
  try {
172
- enforceCommandOptimisticLock({
172
+ await enforceCommandOptimisticLockWithGuards(container, {
173
173
  resourceKind: 'customer_accounts.role',
174
174
  resourceId: role.id,
175
175
  current: role.updatedAt ?? null,
@@ -12,7 +12,7 @@ import { adminUpdateUserSchema } from '@open-mercato/core/modules/customer_accou
12
12
  import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
13
13
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
14
14
  import { isOwnedCompanyEntity } from '@open-mercato/core/modules/customer_accounts/lib/customerEntityOwnership'
15
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
15
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
16
16
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
17
17
 
18
18
  export const metadata = {}
@@ -143,7 +143,7 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
143
143
  // customer user in parallel cannot silently clobber each other (#2055). The
144
144
  // check is strictly additive — a no-op when the client sends no expected-version header.
145
145
  try {
146
- enforceCommandOptimisticLock({
146
+ await enforceCommandOptimisticLockWithGuards(container, {
147
147
  resourceKind: 'customer_accounts.user',
148
148
  resourceId: user.id,
149
149
  current: user.updatedAt ?? null,
@@ -267,7 +267,7 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
267
267
  // Optimistic lock: refuse a stale delete (e.g. deleting a record another admin
268
268
  // already modified). Strictly additive — a no-op without the expected-version header.
269
269
  try {
270
- enforceCommandOptimisticLock({
270
+ await enforceCommandOptimisticLockWithGuards(container, {
271
271
  resourceKind: 'customer_accounts.user',
272
272
  resourceId: user.id,
273
273
  current: user.updatedAt ?? null,
@@ -7,6 +7,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
7
  import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
8
8
  import type { CrudField, CrudFormGroup, CrudFormGroupComponentProps } from '@open-mercato/ui/backend/CrudForm'
9
9
  import { Button } from '@open-mercato/ui/primitives/button'
10
+ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
10
11
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
11
12
  import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
12
13
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
@@ -106,12 +107,9 @@ function PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentPro
106
107
  <div className="flex items-center justify-between border-b px-4 py-3">
107
108
  <span className="text-sm font-semibold">{t(group.labelKey, group.fallback)}</span>
108
109
  <label className="flex items-center gap-2 text-xs text-muted-foreground">
109
- <input
110
- type="checkbox"
111
- checked={allSelected}
112
- ref={(el) => { if (el) el.indeterminate = someSelected && !allSelected }}
113
- onChange={() => handleGroupToggle(groupFeatures)}
114
- className="rounded border-border"
110
+ <Checkbox
111
+ checked={allSelected ? true : (someSelected ? 'indeterminate' : false)}
112
+ onCheckedChange={() => handleGroupToggle(groupFeatures)}
115
113
  />
116
114
  {t('customer_accounts.admin.roleDetail.selectAll', 'Select all')}
117
115
  </label>
@@ -121,11 +119,10 @@ function PortalPermissionsEditor({ values, setValue }: CrudFormGroupComponentPro
121
119
  const feature = PORTAL_FEATURES.find((portalFeature) => portalFeature.id === featureId)
122
120
  return (
123
121
  <label key={featureId} className="flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors">
124
- <input
125
- type="checkbox"
122
+ <Checkbox
126
123
  checked={features.includes(featureId)}
127
- onChange={() => handleFeatureToggle(featureId)}
128
- className="mt-0.5 rounded border-border"
124
+ onCheckedChange={() => handleFeatureToggle(featureId)}
125
+ className="mt-0.5"
129
126
  />
130
127
  <div className="space-y-0.5">
131
128
  <div className="text-sm font-medium">{feature ? t(feature.labelKey, feature.fallback) : featureId}</div>
@@ -191,20 +191,19 @@ function CreateUserDialog({
191
191
  {roleOptions.map((role) => {
192
192
  const isSelected = selectedRoleIds.includes(role.id)
193
193
  return (
194
- <button
194
+ <Button
195
195
  key={role.id}
196
196
  type="button"
197
+ size="2xs"
198
+ variant={isSelected ? 'secondary' : 'outline'}
199
+ aria-pressed={isSelected}
197
200
  onClick={() => setSelectedRoleIds((prev) =>
198
201
  prev.includes(role.id) ? prev.filter((rid) => rid !== role.id) : [...prev, role.id],
199
202
  )}
200
- className={`rounded-full border px-3 py-1 text-xs font-medium transition-colors ${
201
- isSelected
202
- ? 'border-primary bg-primary/10 text-primary'
203
- : 'border-border bg-background text-muted-foreground hover:bg-muted'
204
- }`}
203
+ className="rounded-full"
205
204
  >
206
205
  {role.label}
207
- </button>
206
+ </Button>
208
207
  )
209
208
  })}
210
209
  </div>
@@ -8,6 +8,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
8
8
  import { Button } from '@open-mercato/ui/primitives/button'
9
9
  import { Input } from '@open-mercato/ui/primitives/input'
10
10
  import { EmailInput } from '@open-mercato/ui/primitives/email-input'
11
+ import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
11
12
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
13
 
13
14
  interface AccountStatusData {
@@ -305,9 +306,9 @@ export default function AccountStatusWidget({ context }: AccountStatusProps) {
305
306
  <div className="space-y-1 text-sm">
306
307
  <div className="flex justify-between">
307
308
  <span className="text-muted-foreground">{t('common.status', 'Status')}</span>
308
- <span className={data.isActive ? 'text-green-600' : 'text-red-600'}>
309
+ <StatusBadge variant={data.isActive ? 'success' : 'error'} dot>
309
310
  {data.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}
310
- </span>
311
+ </StatusBadge>
311
312
  </div>
312
313
  <div className="flex justify-between">
313
314
  <span className="text-muted-foreground">{t('common.email', 'Email')}</span>