@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 type { CommandBus } from '@open-mercato/shared/lib/commands'
5
5
  import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
6
6
  import { getOverrides } from '../../lib/queries'
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 {
10
10
  runCrudMutationGuardAfterSuccess,
11
11
  validateCrudMutationGuard,
@@ -100,7 +100,7 @@ export async function PUT(req: Request) {
100
100
  toggle: { id: parsed.data.toggleId },
101
101
  })
102
102
  if (existingOverride) {
103
- enforceCommandOptimisticLock({
103
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
104
104
  resourceKind: 'feature_toggles.feature_toggle_override',
105
105
  resourceId: existingOverride.id,
106
106
  current: existingOverride.updatedAt ?? null,
@@ -57,6 +57,18 @@ export default function EditFeatureTogglePage({ params }: { params?: { id?: stri
57
57
  )
58
58
  }
59
59
 
60
+ // record_locks scope decision (Phase 6): the global feature toggle is a
61
+ // NON-TENANT, superadmin-only entity. record_locks enrichment (presence,
62
+ // acquire/heartbeat, conflict upgrade) is tenant-scoped — its decorator skips
63
+ // enrichment when no tenant/resource resolves — so a global (null-tenant) record
64
+ // cannot participate in record_locks. This surface therefore stays OSS-floor
65
+ // guarded ONLY: the CrudForm auto-derives the optimistic-lock header from
66
+ // `optimisticLockUpdatedAt`, the route enforces it via the OSS floor, and a stale
67
+ // save surfaces the unified conflict bar. No `backend:record:current` presence is
68
+ // mounted: it would never resolve a tenant-scoped lock, and this is a
69
+ // single-superadmin surface with no concurrent-editor expectation. Per-tenant
70
+ // override values (`api/global/[id]/override`) ARE tenant-scoped and are enabled
71
+ // in Phase 6b.
60
72
  return (
61
73
  <Page>
62
74
  <PageBody>
@@ -67,7 +67,7 @@ export class FeatureToggleOverride {
67
67
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
68
68
  createdAt: Date = new Date()
69
69
 
70
- @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
70
+ @Property({ name: 'updated_at', type: Date, onCreate: () => new Date(), onUpdate: () => new Date() })
71
71
  updatedAt: Date = new Date()
72
72
 
73
73
  @Property({ name: 'value', type: 'jsonb' })
@@ -104,6 +104,7 @@ export const overrideListResponseSchema = z.object({
104
104
  name: z.string(),
105
105
  category: z.string(),
106
106
  isOverride: z.boolean(),
107
+ updatedAt: z.string().nullable().optional(),
107
108
  });
108
109
 
109
110
  export type OverrideListResponse = z.infer<typeof overrideListResponseSchema>
@@ -100,6 +100,7 @@ export async function getOverrides(
100
100
  name: toggle.name,
101
101
  category: toggle.category ?? '',
102
102
  isOverride: Boolean(override),
103
+ updatedAt: override?.updatedAt instanceof Date ? override.updatedAt.toISOString() : null,
103
104
  }
104
105
  })
105
106
 
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
3
  import { runWithCacheTenant } from '@open-mercato/cache'
4
4
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
5
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
6
6
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
7
  import { InboxSettings } from '../../data/entities'
8
8
  import { updateSettingsSchema } from '../../data/validators'
@@ -104,7 +104,7 @@ export async function PATCH(req: Request) {
104
104
  // Optimistic lock: refuse a stale overwrite when two tabs edit the same inbox
105
105
  // settings record. Strictly additive — a no-op without the expected-version header.
106
106
  try {
107
- enforceCommandOptimisticLock({
107
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
108
108
  resourceKind: 'inbox_ops.settings',
109
109
  resourceId: settings.id,
110
110
  current: settings.updatedAt ?? null,
@@ -1,5 +1,11 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { type Kysely, sql } from 'kysely'
3
+ import { runWithCacheTenant } from '@open-mercato/cache'
4
+ import {
5
+ buildCollectionTags,
6
+ isCrudCacheEnabled,
7
+ resolveCrudCache,
8
+ } from '@open-mercato/shared/lib/crud/cache'
3
9
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'
4
10
  import { resolveMessageContext } from '../../lib/routeHelpers'
5
11
  import { unreadCountResponseSchema } from '../openapi'
@@ -8,6 +14,16 @@ export const metadata = {
8
14
  GET: { requireAuth: true },
9
15
  }
10
16
 
17
+ const UNREAD_COUNT_RESOURCE = 'messages.message'
18
+ const UNREAD_COUNT_TTL_MS = 10_000
19
+
20
+ function buildUnreadCountCacheKey(params: {
21
+ userId: string
22
+ orgId: string | null
23
+ }): string {
24
+ return `messages:unread-count:u=${params.userId}:org=${params.orgId ?? 'null'}`
25
+ }
26
+
11
27
  function getDb(em: EntityManager): Kysely<any> {
12
28
  return em.getKysely<any>()
13
29
  }
@@ -17,6 +33,19 @@ export async function GET(req: Request) {
17
33
  const em = ctx.container.resolve('em') as EntityManager
18
34
  const db = getDb(em) as any
19
35
 
36
+ const orgId = scope.organizationId ?? null
37
+ const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null
38
+ const cacheKey = cache
39
+ ? buildUnreadCountCacheKey({ userId: scope.userId, orgId })
40
+ : null
41
+
42
+ if (cache && cacheKey) {
43
+ const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))
44
+ if (typeof cached === 'number') {
45
+ return Response.json({ unreadCount: cached })
46
+ }
47
+ }
48
+
20
49
  let query = db
21
50
  .selectFrom('message_recipients as r')
22
51
  .innerJoin('messages as m', 'm.id', 'r.message_id')
@@ -38,6 +67,17 @@ export async function GET(req: Request) {
38
67
  .executeTakeFirst() as { count: string | number } | undefined
39
68
  const count = Number(row?.count ?? 0)
40
69
 
70
+ if (cache && cacheKey) {
71
+ try {
72
+ await runWithCacheTenant(scope.tenantId, () =>
73
+ cache.set(cacheKey, count, {
74
+ ttl: UNREAD_COUNT_TTL_MS,
75
+ tags: buildCollectionTags(UNREAD_COUNT_RESOURCE, scope.tenantId, [orgId]),
76
+ }),
77
+ )
78
+ } catch {}
79
+ }
80
+
41
81
  return Response.json({ unreadCount: count })
42
82
  }
43
83
 
@@ -11,6 +11,7 @@ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/u
11
11
  import { useT } from '@open-mercato/shared/lib/i18n/context'
12
12
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
13
13
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
14
+ import { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'
14
15
 
15
16
  const MUTATION_CONTEXT_ID = 'query_index.status.list:actions'
16
17
 
@@ -127,7 +128,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
127
128
  if (!record.vectorEnabled) return <span>—</span>
128
129
  const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount
129
130
  const display = formatCount(record.vectorCount)
130
- const className = ok ? 'text-green-600' : 'text-orange-600'
131
+ const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
131
132
  return <span className={className}>{display}</span>
132
133
  },
133
134
  meta: { priority: 2 },
@@ -141,7 +142,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
141
142
  if (!record.fulltextEnabled) return <span>—</span>
142
143
  const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount
143
144
  const display = formatCount(record.fulltextCount)
144
- const className = ok ? 'text-green-600' : 'text-orange-600'
145
+ const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
145
146
  return <span className={className}>{display}</span>
146
147
  },
147
148
  meta: { priority: 2 },
@@ -159,17 +160,14 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
159
160
  const label = jobProgress
160
161
  ? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })
161
162
  : statusText
162
- const className = job
163
- ? job.status === 'stalled'
164
- ? 'text-red-600'
165
- : job.status === 'reindexing' || job.status === 'purging'
166
- ? 'text-orange-600'
167
- : ok
168
- ? 'text-green-600'
169
- : 'text-muted-foreground'
170
- : ok
171
- ? 'text-green-600'
172
- : 'text-muted-foreground'
163
+ let variant: StatusBadgeVariant = 'neutral'
164
+ if (job) {
165
+ if (job.status === 'stalled') variant = 'error'
166
+ else if (job.status === 'reindexing' || job.status === 'purging') variant = 'warning'
167
+ else variant = ok ? 'success' : 'neutral'
168
+ } else {
169
+ variant = ok ? 'success' : 'neutral'
170
+ }
173
171
 
174
172
  const lines: string[] = []
175
173
 
@@ -203,8 +201,12 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
203
201
  }
204
202
 
205
203
  return (
206
- <div className="space-y-1">
207
- <span className={className}>{label}</span>
204
+ <div className="space-y-1.5">
205
+ <div>
206
+ <StatusBadge variant={variant} dot>
207
+ {label}
208
+ </StatusBadge>
209
+ </div>
208
210
  {lines.length > 0 && (
209
211
  <div className="text-xs text-muted-foreground">
210
212
  {lines.map((line, idx) => (
@@ -1,9 +1,10 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { useRouter } from 'next/navigation'
4
+ import { usePathname, useRouter } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
7
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
7
8
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
8
9
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
9
10
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
@@ -19,6 +20,7 @@ export default function ResourcesResourceTypeEditPage({ params }: { params?: { i
19
20
  const resourceTypeId = params?.id ?? ''
20
21
  const t = useT()
21
22
  const router = useRouter()
23
+ const pathname = usePathname()
22
24
  const [initialValues, setInitialValues] = React.useState<ResourceTypeFormValues | null>(null)
23
25
  const [loading, setLoading] = React.useState(true)
24
26
  const [error, setError] = React.useState<string | null>(null)
@@ -108,6 +110,20 @@ export default function ResourcesResourceTypeEditPage({ params }: { params?: { i
108
110
  router.push('/backend/resources/resource-types')
109
111
  }, [resourceCount, resourceTypeId, router, t])
110
112
 
113
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
114
+ // mount so the enterprise record_locks widget resolves `resources.resourceType` + id
115
+ // explicitly. The resourceKind mirrors the ResourceTypeCrudForm `versionHistory` so the
116
+ // held lock matches the save-time conflict surface for the same resource type.
117
+ useSetCurrentRecordInjectionContext(
118
+ buildRecordInjectionContext({
119
+ resourceKind: 'resources.resourceType',
120
+ resourceId: resourceTypeId || null,
121
+ updatedAt: initialValues?.updatedAt ?? null,
122
+ data: initialValues as Record<string, unknown> | null,
123
+ path: pathname,
124
+ }),
125
+ )
126
+
111
127
  if (isNotFound) {
112
128
  return (
113
129
  <Page>
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { useRouter, useSearchParams } from 'next/navigation'
4
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { FormHeader } from '@open-mercato/ui/backend/forms'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
@@ -12,6 +12,7 @@ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors
12
12
  import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
13
13
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
14
14
  import { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
15
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
15
16
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
16
17
  import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
17
18
  import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
@@ -91,6 +92,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
91
92
  const t = useT()
92
93
  const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])
93
94
  const router = useRouter()
95
+ const pathname = usePathname()
94
96
  const searchParams = useSearchParams()
95
97
  const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)
96
98
  const [isNotFound, setIsNotFound] = React.useState(false)
@@ -602,6 +604,20 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
602
604
  ? initialValues.name.trim()
603
605
  : t('resources.resources.detail.untitled', 'Unnamed resource')
604
606
 
607
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
608
+ // mount so the enterprise record_locks widget resolves `resources.resource` + id
609
+ // explicitly. The resourceKind mirrors the VersionHistoryAction config / ResourceCrudForm
610
+ // `versionHistory` so the held lock matches the save-time conflict surface for the resource.
611
+ useSetCurrentRecordInjectionContext(
612
+ buildRecordInjectionContext({
613
+ resourceKind: 'resources.resource',
614
+ resourceId: resourceId || null,
615
+ updatedAt: typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
616
+ data: initialValues,
617
+ path: pathname,
618
+ }),
619
+ )
620
+
605
621
  if (isNotFound) {
606
622
  return (
607
623
  <Page>
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
- import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
3
+ import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
4
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
4
5
  import { mapCommentSummary, type NotesDataAdapter } from '@open-mercato/ui/backend/detail/NotesSection'
5
6
 
6
7
  type Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string
@@ -61,33 +62,43 @@ export function createResourceNotesAdapter(
61
62
  )
62
63
  return response.result ?? {}
63
64
  },
64
- update: async ({ id, patch }) => {
65
+ update: async ({ id, patch, updatedAt }) => {
65
66
  const payload: Record<string, unknown> = { id }
66
67
  if (patch.body !== undefined) payload.body = patch.body
67
68
  if (patch.appearanceIcon !== undefined) payload.appearanceIcon = patch.appearanceIcon
68
69
  if (patch.appearanceColor !== undefined) payload.appearanceColor = patch.appearanceColor
70
+ // Run through the guarded-mutation runner AND send the optimistic-lock header
71
+ // (note's loaded updatedAt) so a stale edit fails with a 409; the shared
72
+ // NotesSection host surfaces it through surfaceRecordConflict instead of
73
+ // silently overwriting.
69
74
  await runWrite(
70
- () => apiCallOrThrow(
71
- '/api/resources/comments',
72
- {
73
- method: 'PUT',
74
- headers: { 'content-type': 'application/json' },
75
- body: JSON.stringify(payload),
76
- },
77
- { errorMessage: translator('resources.resources.detail.notes.updateError', 'Failed to update note.') },
75
+ () => withScopedApiRequestHeaders(
76
+ buildOptimisticLockHeader(updatedAt ?? null),
77
+ () => apiCallOrThrow(
78
+ '/api/resources/comments',
79
+ {
80
+ method: 'PUT',
81
+ headers: { 'content-type': 'application/json' },
82
+ body: JSON.stringify(payload),
83
+ },
84
+ { errorMessage: translator('resources.resources.detail.notes.updateError', 'Failed to update note.') },
85
+ ),
78
86
  ),
79
87
  { operation: 'updateNote', id },
80
88
  )
81
89
  },
82
- delete: async ({ id }) => {
90
+ delete: async ({ id, updatedAt }) => {
83
91
  await runWrite(
84
- () => apiCallOrThrow(
85
- `/api/resources/comments?id=${encodeURIComponent(id)}`,
86
- {
87
- method: 'DELETE',
88
- headers: { 'content-type': 'application/json' },
89
- },
90
- { errorMessage: translator('resources.resources.detail.notes.deleteError', 'Failed to delete note') },
92
+ () => withScopedApiRequestHeaders(
93
+ buildOptimisticLockHeader(updatedAt ?? null),
94
+ () => apiCallOrThrow(
95
+ `/api/resources/comments?id=${encodeURIComponent(id)}`,
96
+ {
97
+ method: 'DELETE',
98
+ headers: { 'content-type': 'application/json' },
99
+ },
100
+ { errorMessage: translator('resources.resources.detail.notes.deleteError', 'Failed to delete note') },
101
+ ),
91
102
  ),
92
103
  { operation: 'deleteNote', id },
93
104
  )
@@ -3,7 +3,7 @@
3
3
  "use client"
4
4
 
5
5
  import * as React from 'react'
6
- import { useSearchParams, useRouter } from 'next/navigation'
6
+ import { useSearchParams, useRouter, usePathname } from 'next/navigation'
7
7
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
8
8
  import {
9
9
  CustomDataSection,
@@ -68,6 +68,7 @@ import { ICON_SUGGESTIONS } from '@open-mercato/core/modules/customers/lib/dicti
68
68
  import { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '@open-mercato/core/modules/customers/lib/markdownPreference'
69
69
  import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
70
70
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
71
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
71
72
 
72
73
  function formatMessageAmount(amount: number | null | undefined, currency: string | null | undefined): string | null {
73
74
  if (typeof amount !== 'number' || !Number.isFinite(amount)) return null
@@ -1892,6 +1893,7 @@ export default function SalesDocumentDetailPage({
1892
1893
  const t = useT()
1893
1894
  const router = useRouter()
1894
1895
  const searchParams = useSearchParams()
1896
+ const pathname = usePathname()
1895
1897
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
1896
1898
  const [loading, setLoading] = React.useState(true)
1897
1899
  const [isNotFound, setIsNotFound] = React.useState(false)
@@ -1972,6 +1974,18 @@ export default function SalesDocumentDetailPage({
1972
1974
  },
1973
1975
  [detailInjectionContext, runMutation],
1974
1976
  )
1977
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
1978
+ // mount so record_locks gets presence + the action-log base (updatedAt/data) for
1979
+ // this order/quote. Sub-resource sections inherit this context — no second mount.
1980
+ useSetCurrentRecordInjectionContext(
1981
+ buildRecordInjectionContext({
1982
+ resourceKind: `sales.${kind}`,
1983
+ resourceId: record?.id ?? null,
1984
+ updatedAt: record?.updatedAt ?? null,
1985
+ data: record as Record<string, unknown> | null,
1986
+ path: pathname,
1987
+ }),
1988
+ )
1975
1989
  const clearCustomerError = React.useCallback(() => setCustomerError(null), [])
1976
1990
  const { data: currencyDictionary } = useCurrencyDictionary()
1977
1991
  const scopeVersion = useOrganizationScopeVersion()
@@ -4244,6 +4258,7 @@ export default function SalesDocumentDetailPage({
4244
4258
  currencyCode={record.currencyCode ?? null}
4245
4259
  organizationId={(record as any)?.organizationId ?? (record as any)?.organization_id ?? null}
4246
4260
  tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
4261
+ documentUpdatedAt={record.updatedAt ?? null}
4247
4262
  shippingAddressSnapshot={shippingSnapshot ?? null}
4248
4263
  onActionChange={handleSectionActionChange}
4249
4264
  onAddComment={appendShipmentComment}
@@ -4300,6 +4315,7 @@ export default function SalesDocumentDetailPage({
4300
4315
  currencyCode={record.currencyCode ?? null}
4301
4316
  organizationId={(record as any)?.organizationId ?? (record as any)?.organization_id ?? null}
4302
4317
  tenantId={(record as any)?.tenantId ?? (record as any)?.tenant_id ?? null}
4318
+ documentUpdatedAt={record.updatedAt ?? null}
4303
4319
  onActionChange={handleSectionActionChange}
4304
4320
  onPaymentsChange={(payments) => setHasPayments(payments.length > 0)}
4305
4321
  onTotalsChange={() => {
@@ -4981,7 +4981,7 @@ const updateQuoteCommand: CommandHandler<
4981
4981
  if (!quote)
4982
4982
  throw new CrudHttpError(404, { error: "Sales quote not found" });
4983
4983
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
4984
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
4984
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
4985
4985
  const shouldInvalidateSentToken = (quote.status ?? null) === "sent";
4986
4986
  if (shouldInvalidateSentToken) {
4987
4987
  quote.acceptanceToken = null;
@@ -5213,6 +5213,7 @@ const updateOrderCommand: CommandHandler<
5213
5213
  if (!order)
5214
5214
  throw new CrudHttpError(404, { error: "Sales order not found" });
5215
5215
  ensureOrderScope(ctx, order.organizationId, order.tenantId);
5216
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
5216
5217
  const previousStatus = normalizeStatusValue(order.status);
5217
5218
  let statusChangeNote: SalesNote | null = null;
5218
5219
  const shouldRecalculateTotals =
@@ -6055,7 +6056,7 @@ const convertQuoteToOrderCommand: CommandHandler<
6055
6056
  ),
6056
6057
  });
6057
6058
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
6058
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
6059
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
6059
6060
  const snapshot = await loadQuoteSnapshot(em, payload.quoteId);
6060
6061
  if (!snapshot)
6061
6062
  throw new CrudHttpError(404, {
@@ -6545,7 +6546,7 @@ const orderLineUpsertCommand: CommandHandler<
6545
6546
  if (!order)
6546
6547
  throw new CrudHttpError(404, { error: "Sales order not found" });
6547
6548
  ensureOrderScope(ctx, order.organizationId, order.tenantId);
6548
- enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
6549
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
6549
6550
 
6550
6551
  const [existingLines, adjustments] = await Promise.all([
6551
6552
  em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
@@ -6866,7 +6867,7 @@ const orderLineDeleteCommand: CommandHandler<
6866
6867
  ),
6867
6868
  });
6868
6869
  ensureOrderScope(ctx, order.organizationId, order.tenantId);
6869
- enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
6870
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
6870
6871
  const shipmentCount = await em.count(SalesShipmentItem, {
6871
6872
  orderLine: parsed.id,
6872
6873
  shipment: { deletedAt: null },
@@ -7037,7 +7038,7 @@ const quoteLineUpsertCommand: CommandHandler<
7037
7038
  if (!quote)
7038
7039
  throw new CrudHttpError(404, { error: "Sales quote not found" });
7039
7040
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
7040
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7041
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7041
7042
  const [existingLines, adjustments] = await Promise.all([
7042
7043
  em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
7043
7044
  em.find(
@@ -7349,7 +7350,7 @@ const quoteLineDeleteCommand: CommandHandler<
7349
7350
  if (!quote)
7350
7351
  throw new CrudHttpError(404, { error: "Sales quote not found" });
7351
7352
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
7352
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7353
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7353
7354
  const existingLines = await em.find(
7354
7355
  SalesQuoteLine,
7355
7356
  { quote },
@@ -7503,7 +7504,7 @@ const orderAdjustmentUpsertCommand: CommandHandler<
7503
7504
  if (!order)
7504
7505
  throw new CrudHttpError(404, { error: "Sales order not found" });
7505
7506
  ensureOrderScope(ctx, order.organizationId, order.tenantId);
7506
- enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
7507
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
7507
7508
  if (parsed.scope === "line") {
7508
7509
  throw new CrudHttpError(400, {
7509
7510
  error: "Line-scoped adjustments are not supported yet.",
@@ -7797,7 +7798,7 @@ const orderAdjustmentDeleteCommand: CommandHandler<
7797
7798
  if (!order)
7798
7799
  throw new CrudHttpError(404, { error: "Sales order not found" });
7799
7800
  ensureOrderScope(ctx, order.organizationId, order.tenantId);
7800
- enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
7801
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER);
7801
7802
 
7802
7803
  const [existingLines, adjustments] = await Promise.all([
7803
7804
  em.find(SalesOrderLine, { order }, { orderBy: { lineNumber: "asc" } }),
@@ -7962,7 +7963,7 @@ const quoteAdjustmentUpsertCommand: CommandHandler<
7962
7963
  if (!quote)
7963
7964
  throw new CrudHttpError(404, { error: "Sales quote not found" });
7964
7965
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
7965
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7966
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
7966
7967
  if (parsed.scope === "line") {
7967
7968
  throw new CrudHttpError(400, {
7968
7969
  error: "Line-scoped adjustments are not supported yet.",
@@ -8254,7 +8255,7 @@ const quoteAdjustmentDeleteCommand: CommandHandler<
8254
8255
  if (!quote)
8255
8256
  throw new CrudHttpError(404, { error: "Sales quote not found" });
8256
8257
  ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
8257
- enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
8258
+ await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
8258
8259
 
8259
8260
  const [existingLines, adjustments] = await Promise.all([
8260
8261
  em.find(SalesQuoteLine, { quote }, { orderBy: { lineNumber: "asc" } }),
@@ -30,6 +30,8 @@ import {
30
30
  ensureTenantScope,
31
31
  extractUndoPayload,
32
32
  toNumericString,
33
+ enforceSalesDocumentOptimisticLock,
34
+ SALES_RESOURCE_KIND_ORDER,
33
35
  } from './shared'
34
36
  import { resolveDictionaryEntryValue } from '../lib/dictionaries'
35
37
  import { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'
@@ -343,6 +345,9 @@ const createPaymentCommand: CommandHandler<
343
345
  if (order.deletedAt) {
344
346
  throw new CrudHttpError(404, { error: 'sales.payments.order_not_found' })
345
347
  }
348
+ // Guard the parent order's aggregate version (Gap A): a payment mutation
349
+ // recalculates the order totals, so a stale parent must 409 before we touch it.
350
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
346
351
  if (
347
352
  order.currencyCode &&
348
353
  input.currencyCode &&
@@ -730,6 +735,9 @@ const updatePaymentCommand: CommandHandler<
730
735
  )
731
736
  ensureSameScope(payment, resolvedOrganizationId, resolvedTenantId)
732
737
  const previousOrder = payment.order as SalesOrder | null
738
+ // Guard the parent order's aggregate version (Gap A): updating a payment
739
+ // recalculates the order totals, so a stale parent must 409 before mutating.
740
+ await enforceSalesDocumentOptimisticLock(ctx, previousOrder, SALES_RESOURCE_KIND_ORDER)
733
741
  // Apply payment scalar fields, order/line status changes and the
734
742
  // allocations rebuild in one transaction so a mid-write failure cannot
735
743
  // leave the payment and its allocations partially committed (#2336).
@@ -1064,6 +1072,9 @@ const deletePaymentCommand: CommandHandler<
1064
1072
  )
1065
1073
  ensureSameScope(payment, input.organizationId, input.tenantId)
1066
1074
  const order = payment.order as SalesOrder | null
1075
+ // Guard the parent order's aggregate version (Gap A): deleting a payment
1076
+ // recalculates the order totals, so a stale parent must 409 before mutating.
1077
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
1067
1078
  const allocations = await findWithDecryption(em, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
1068
1079
  const allocationOrders = allocations
1069
1080
  .map((allocation) =>
@@ -615,7 +615,7 @@ const createReturnCommand: CommandHandler<ReturnCreateInput, { returnId: string
615
615
  throw new CrudHttpError(404, { error: translate('sales.returns.orderMissing', 'Order not found.') })
616
616
  }
617
617
  ensureSameScope(order, input.organizationId, input.tenantId)
618
- enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
618
+ await enforceSalesDocumentOptimisticLock(ctx, order, SALES_RESOURCE_KIND_ORDER)
619
619
 
620
620
  const orderLines = await findWithDecryption(
621
621
  tx,
@@ -890,7 +890,7 @@ const updateReturnCommand: CommandHandler<ReturnUpdateInput, { returnId: string
890
890
  }
891
891
  // Lock on the return's own version — editing header fields (reason / notes /
892
892
  // returnedAt) only touches the return, not the order totals.
893
- enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)
893
+ await enforceSalesDocumentOptimisticLock(ctx, entity, SALES_RESOURCE_KIND_RETURN)
894
894
 
895
895
  if (input.reason !== undefined) entity.reason = input.reason.length ? input.reason : null
896
896
  if (input.notes !== undefined) entity.notes = input.notes.length ? input.notes : null
@@ -1020,7 +1020,7 @@ const deleteReturnCommand: CommandHandler<ReturnDeleteInput, { returnId: string
1020
1020
  }
1021
1021
  ensureSameScope(header, input.organizationId, input.tenantId)
1022
1022
  // Lock on the return's own version, captured before any mutation.
1023
- enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)
1023
+ await enforceSalesDocumentOptimisticLock(ctx, header, SALES_RESOURCE_KIND_RETURN)
1024
1024
 
1025
1025
  await reverseReturnEffects(em, salesCalculationService, snapshot)
1026
1026
 
@@ -2,7 +2,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
3
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
4
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
5
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
5
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
6
6
  export { assertFound } from '@open-mercato/shared/lib/crud/errors'
7
7
  export { ensureOrganizationScope, ensureSameScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
8
8
  export { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
@@ -28,14 +28,20 @@ export const SALES_RESOURCE_KIND_RETURN = 'sales.return'
28
28
  *
29
29
  * Strictly additive: when the client sends no header the check is a no-op, so
30
30
  * existing API consumers are unaffected. Respects `OM_OPTIMISTIC_LOCK`.
31
+ *
32
+ * Routes through the async DI-aware seam `enforceCommandOptimisticLockWithGuards`
33
+ * (Phase 0 / S1): the OSS `updated_at` floor runs first (identical behavior when
34
+ * `record_locks` is disabled — floor only), then the optional enterprise
35
+ * `record_locks` enrichment is awaited so the aggregate check observes the
36
+ * action-log diff when the resource is enabled. Callers MUST `await` it.
31
37
  */
32
- export function enforceSalesDocumentOptimisticLock(
38
+ export async function enforceSalesDocumentOptimisticLock(
33
39
  ctx: CommandRuntimeContext,
34
40
  document: { id: string; updatedAt?: Date | string | null } | null | undefined,
35
41
  resourceKind: string,
36
- ): void {
42
+ ): Promise<void> {
37
43
  if (!document) return
38
- enforceCommandOptimisticLock({
44
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
39
45
  resourceKind,
40
46
  resourceId: document.id,
41
47
  current: document.updatedAt ?? null,