@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
@@ -26,6 +26,7 @@ import { Switch } from '@open-mercato/ui/primitives/switch'
26
26
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
27
27
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
28
28
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
29
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
29
30
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
30
31
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
31
32
  import { useT } from '@open-mercato/shared/lib/i18n/context'
@@ -167,6 +168,7 @@ export default function SyncRunsDashboardPage() {
167
168
  params.set('pageSize', '20')
168
169
  if (filterValues.status) params.set('status', filterValues.status as string)
169
170
  if (filterValues.direction) params.set('direction', filterValues.direction as string)
171
+ if (search.trim()) params.set('search', search.trim())
170
172
  const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }
171
173
  const call = await apiCall<ResponsePayload>(
172
174
  `/api/data_sync/runs?${params.toString()}`,
@@ -188,7 +190,7 @@ export default function SyncRunsDashboardPage() {
188
190
  }
189
191
  load()
190
192
  return () => { cancelled = true }
191
- }, [page, filterValues, reloadToken, scopeVersion, t])
193
+ }, [page, filterValues, search, reloadToken, scopeVersion, t])
192
194
 
193
195
  React.useEffect(() => {
194
196
  let cancelled = false
@@ -401,21 +403,27 @@ export default function SyncRunsDashboardPage() {
401
403
  setIsSavingSchedule(true)
402
404
  try {
403
405
  const call = await runMutation({
404
- // optimistic-lock-exempt: keyed upsert (POST, no record id/version in body) guard targets id-addressed PUT/PATCH/DELETE
405
- operation: () => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {
406
- method: 'POST',
407
- headers: { 'Content-Type': 'application/json' },
408
- body: JSON.stringify({
409
- integrationId: selectedIntegration.integrationId,
410
- entityType: selectedEntityType,
411
- direction: selectedDirection,
412
- scheduleType: scheduleEditor.scheduleType,
413
- scheduleValue: scheduleEditor.scheduleValue.trim(),
414
- timezone: scheduleEditor.timezone.trim() || DEFAULT_TIMEZONE,
415
- fullSync: scheduleEditor.fullSync,
416
- isEnabled: scheduleEditor.isEnabled,
417
- }),
418
- }, { fallback: null }),
406
+ // Keyed upsert (POST). When the editor holds an existing schedule's
407
+ // `updatedAt`, the lock header version-checks the resolved row on the
408
+ // server; a brand-new schedule has a null `updatedAt`, so the header is
409
+ // empty and the create path stays unaffected.
410
+ operation: () => withScopedApiRequestHeaders(
411
+ buildOptimisticLockHeader(scheduleEditor.updatedAt),
412
+ () => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {
413
+ method: 'POST',
414
+ headers: { 'Content-Type': 'application/json' },
415
+ body: JSON.stringify({
416
+ integrationId: selectedIntegration.integrationId,
417
+ entityType: selectedEntityType,
418
+ direction: selectedDirection,
419
+ scheduleType: scheduleEditor.scheduleType,
420
+ scheduleValue: scheduleEditor.scheduleValue.trim(),
421
+ timezone: scheduleEditor.timezone.trim() || DEFAULT_TIMEZONE,
422
+ fullSync: scheduleEditor.fullSync,
423
+ isEnabled: scheduleEditor.isEnabled,
424
+ }),
425
+ }, { fallback: null }),
426
+ ),
419
427
  mutationPayload: {
420
428
  integrationId: selectedIntegration.integrationId,
421
429
  entityType: selectedEntityType,
@@ -434,6 +442,16 @@ export default function SyncRunsDashboardPage() {
434
442
  })
435
443
 
436
444
  if (!call.ok || !call.result) {
445
+ const conflictError = Object.assign(
446
+ new Error((call.result as { error?: string } | null)?.error ?? t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')),
447
+ {
448
+ status: call.status,
449
+ ...(call.result && typeof call.result === 'object' ? call.result : {}),
450
+ },
451
+ )
452
+ if (surfaceRecordConflict(conflictError, t)) {
453
+ return
454
+ }
437
455
  flash((call.result as { error?: string } | null)?.error ?? t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule'), 'error')
438
456
  return
439
457
  }
@@ -965,6 +983,7 @@ export default function SyncRunsDashboardPage() {
965
983
  onFiltersClear={handleFiltersClear}
966
984
  searchValue={search}
967
985
  onSearchChange={(value) => { setSearch(value); setPage(1) }}
986
+ searchPlaceholder={t('data_sync.dashboard.searchPlaceholder')}
968
987
  perspective={{ tableId: 'data_sync.runs' }}
969
988
  onRowClick={(row) => {
970
989
  router.push(`/backend/data-sync/runs/${encodeURIComponent(row.id)}`)
@@ -28,6 +28,7 @@ export const listSyncRunsQuerySchema = z.object({
28
28
  entityType: z.string().optional(),
29
29
  direction: z.enum(['import', 'export']).optional(),
30
30
  status: z.enum(['pending', 'running', 'completed', 'failed', 'cancelled', 'paused']).optional(),
31
+ search: z.string().trim().min(1).max(200).optional(),
31
32
  page: z.coerce.number().int().min(1).default(1),
32
33
  pageSize: z.coerce.number().int().min(1).max(100).default(20),
33
34
  })
@@ -47,6 +47,7 @@
47
47
  "data_sync.dashboard.schedule.timezone": "Timezone",
48
48
  "data_sync.dashboard.schedule.title": "Recurring schedule",
49
49
  "data_sync.dashboard.schedule.type": "Schedule type",
50
+ "data_sync.dashboard.searchPlaceholder": "Nach Integration, Entitätstyp, Status oder Lauf-ID suchen",
50
51
  "data_sync.dashboard.start.batchSize": "Batch Size",
51
52
  "data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
52
53
  "data_sync.dashboard.start.error": "Failed to start sync run",
@@ -47,6 +47,7 @@
47
47
  "data_sync.dashboard.schedule.timezone": "Timezone",
48
48
  "data_sync.dashboard.schedule.title": "Recurring schedule",
49
49
  "data_sync.dashboard.schedule.type": "Schedule type",
50
+ "data_sync.dashboard.searchPlaceholder": "Search by integration, entity type, status, or run ID",
50
51
  "data_sync.dashboard.start.batchSize": "Batch Size",
51
52
  "data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
52
53
  "data_sync.dashboard.start.error": "Failed to start sync run",
@@ -47,6 +47,7 @@
47
47
  "data_sync.dashboard.schedule.timezone": "Timezone",
48
48
  "data_sync.dashboard.schedule.title": "Recurring schedule",
49
49
  "data_sync.dashboard.schedule.type": "Schedule type",
50
+ "data_sync.dashboard.searchPlaceholder": "Buscar por integración, tipo de entidad, estado o ID de ejecución",
50
51
  "data_sync.dashboard.start.batchSize": "Batch Size",
51
52
  "data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
52
53
  "data_sync.dashboard.start.error": "Failed to start sync run",
@@ -47,6 +47,7 @@
47
47
  "data_sync.dashboard.schedule.timezone": "Timezone",
48
48
  "data_sync.dashboard.schedule.title": "Recurring schedule",
49
49
  "data_sync.dashboard.schedule.type": "Schedule type",
50
+ "data_sync.dashboard.searchPlaceholder": "Szukaj po integracji, typie encji, statusie lub ID przebiegu",
50
51
  "data_sync.dashboard.start.batchSize": "Batch Size",
51
52
  "data_sync.dashboard.start.description": "Pick a sync target, launch an ad-hoc run, or save a recurring schedule for the same entity and direction from this page.",
52
53
  "data_sync.dashboard.start.error": "Failed to start sync run",
@@ -1,8 +1,26 @@
1
1
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
2
2
  import { findAndCountWithDecryption, findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
3
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
4
+ import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
4
5
  import { SyncCursor, SyncRun } from '../data/entities'
5
6
 
7
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
8
+
9
+ function buildRunSearchFilter(search: string): FilterQuery<SyncRun>[] | null {
10
+ const trimmed = search.trim()
11
+ if (!trimmed) return null
12
+ const pattern = `%${escapeLikePattern(trimmed)}%`
13
+ const conditions: FilterQuery<SyncRun>[] = [
14
+ { integrationId: { $ilike: pattern } },
15
+ { entityType: { $ilike: pattern } },
16
+ { status: { $ilike: pattern } },
17
+ ]
18
+ if (UUID_PATTERN.test(trimmed)) {
19
+ conditions.push({ id: trimmed })
20
+ }
21
+ return conditions
22
+ }
23
+
6
24
  type SyncScope = {
7
25
  organizationId: string
8
26
  tenantId: string
@@ -89,6 +107,7 @@ export function createSyncRunService(em: EntityManager) {
89
107
  entityType?: string
90
108
  direction?: 'import' | 'export'
91
109
  status?: string
110
+ search?: string
92
111
  page: number
93
112
  pageSize: number
94
113
  }, scope: SyncScope): Promise<{ items: SyncRun[]; total: number }> {
@@ -102,6 +121,10 @@ export function createSyncRunService(em: EntityManager) {
102
121
  if (query.entityType) where.entityType = query.entityType
103
122
  if (query.direction) where.direction = query.direction
104
123
  if (query.status) where.status = query.status as SyncRun['status']
124
+ if (query.search) {
125
+ const searchConditions = buildRunSearchFilter(query.search)
126
+ if (searchConditions) where.$or = searchConditions
127
+ }
105
128
 
106
129
  const [items, total] = await findAndCountWithDecryption(
107
130
  em,
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto'
2
+ import type { AwilixContainer } from 'awilix'
2
3
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
3
4
  import { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
5
+ import { enforceCommandOptimisticLockWithGuards, enforceRecordGoneIsConflict } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
5
6
  import { SyncSchedule } from '../data/entities'
6
7
 
7
8
  type SyncScope = {
@@ -131,18 +132,28 @@ export function createSyncScheduleService(em: EntityManager, schedulerService?:
131
132
  fullSync: boolean
132
133
  isEnabled: boolean
133
134
  expectedUpdatedAt?: string | null
134
- }, scope: SyncScope): Promise<SyncSchedule> {
135
+ }, scope: SyncScope, container?: AwilixContainer): Promise<SyncSchedule> {
135
136
  const existing = input.id
136
137
  ? await getById(input.id, scope)
137
138
  : await getByKey(input.integrationId, input.entityType, input.direction, scope)
138
139
 
139
- if (existing) {
140
- enforceCommandOptimisticLock({
140
+ if (existing && container) {
141
+ await enforceCommandOptimisticLockWithGuards(container, {
141
142
  resourceKind: 'data_sync.schedule',
142
143
  resourceId: existing.id,
143
144
  current: existing.updatedAt ?? null,
144
145
  expected: input.expectedUpdatedAt ?? null,
145
146
  })
147
+ } else if (!existing && input.expectedUpdatedAt) {
148
+ // Concurrent delete-then-edit race: the client edited a schedule that was
149
+ // removed before this keyed upsert ran. Surface the unified conflict
150
+ // instead of silently re-creating it. No-op when no expected version was
151
+ // sent (a genuine create) or when OM_OPTIMISTIC_LOCK is off.
152
+ enforceRecordGoneIsConflict({
153
+ resourceKind: 'data_sync.schedule',
154
+ resourceId: input.id ?? `${input.integrationId}:${input.entityType}:${input.direction}`,
155
+ expected: input.expectedUpdatedAt,
156
+ })
146
157
  }
147
158
 
148
159
  const row = existing ?? em.create(SyncSchedule, {
@@ -200,10 +211,24 @@ export function createSyncScheduleService(em: EntityManager, schedulerService?:
200
211
  return row
201
212
  },
202
213
 
203
- async deleteSchedule(id: string, scope: SyncScope): Promise<boolean> {
214
+ async deleteSchedule(
215
+ id: string,
216
+ scope: SyncScope,
217
+ container?: AwilixContainer,
218
+ expectedUpdatedAt?: string | null,
219
+ ): Promise<boolean> {
204
220
  const row = await getById(id, scope)
205
221
  if (!row) return false
206
222
 
223
+ if (container) {
224
+ await enforceCommandOptimisticLockWithGuards(container, {
225
+ resourceKind: 'data_sync.schedule',
226
+ resourceId: row.id,
227
+ current: row.updatedAt ?? null,
228
+ expected: expectedUpdatedAt ?? null,
229
+ })
230
+ }
231
+
207
232
  const scheduledJobId = row.scheduledJobId ?? row.id
208
233
  await requireScheduler().unregister(scheduledJobId)
209
234
 
@@ -4,7 +4,7 @@ import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictiona
4
4
  import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
5
5
  import { updateDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'
6
6
  import { CrudHttpError, 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 {
9
9
  runCrudMutationGuardAfterSuccess,
10
10
  validateCrudMutationGuard,
@@ -76,7 +76,7 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
76
76
  })
77
77
  const dictionary = await loadDictionary(context, dictionaryId)
78
78
  const entry = await loadEntry(context, dictionary, entryId)
79
- enforceCommandOptimisticLock({
79
+ await enforceCommandOptimisticLockWithGuards(context.container, {
80
80
  resourceKind: 'dictionaries.entry',
81
81
  resourceId: entry.id,
82
82
  current: entry.updatedAt ?? null,
@@ -178,7 +178,7 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
178
178
  })
179
179
  const dictionary = await loadDictionary(context, dictionaryId)
180
180
  const entry = await loadEntry(context, dictionary, entryId)
181
- enforceCommandOptimisticLock({
181
+ await enforceCommandOptimisticLockWithGuards(context.container, {
182
182
  resourceKind: 'dictionaries.entry',
183
183
  resourceId: entry.id,
184
184
  current: entry.updatedAt ?? null,
@@ -3,7 +3,7 @@ import { z } from 'zod'
3
3
  import { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'
4
4
  import { resolveDictionariesRouteContext, resolveDictionaryActorId } from '@open-mercato/core/modules/dictionaries/api/context'
5
5
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
6
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
7
7
  import {
8
8
  runCrudMutationGuardAfterSuccess,
9
9
  validateCrudMutationGuard,
@@ -115,7 +115,7 @@ export async function PATCH(req: Request, ctx: { params?: { dictionaryId?: strin
115
115
  const payload = updateSchema.parse(await req.json().catch(() => ({})))
116
116
  const dictionary = await loadDictionary(context, dictionaryId)
117
117
 
118
- enforceCommandOptimisticLock({
118
+ await enforceCommandOptimisticLockWithGuards(context.container, {
119
119
  resourceKind: 'dictionaries.dictionary',
120
120
  resourceId: dictionary.id,
121
121
  current: dictionary.updatedAt ?? null,
@@ -236,7 +236,7 @@ export async function DELETE(req: Request, ctx: { params?: { dictionaryId?: stri
236
236
  const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })
237
237
  const dictionary = await loadDictionary(context, dictionaryId)
238
238
 
239
- enforceCommandOptimisticLock({
239
+ await enforceCommandOptimisticLockWithGuards(context.container, {
240
240
  resourceKind: 'dictionaries.dictionary',
241
241
  resourceId: dictionary.id,
242
242
  current: dictionary.updatedAt ?? null,
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { useSearchParams } from 'next/navigation'
4
+ import { useSearchParams, usePathname } from 'next/navigation'
5
5
  import { Book, Plus, Pencil, Trash2 } from 'lucide-react'
6
6
  import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
@@ -18,6 +18,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
18
18
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
19
19
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
20
20
  import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
21
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
21
22
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
22
23
  import { useT } from '@open-mercato/shared/lib/i18n/context'
23
24
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
@@ -58,6 +59,7 @@ export function DictionariesManager() {
58
59
  const t = useT()
59
60
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
60
61
  const searchParams = useSearchParams()
62
+ const pathname = usePathname()
61
63
  const [items, setItems] = React.useState<DictionarySummary[]>([])
62
64
  const [selectedId, setSelectedId] = React.useState<string | null>(null)
63
65
  const [loading, setLoading] = React.useState(true)
@@ -366,6 +368,24 @@ export function DictionariesManager() {
366
368
 
367
369
  const selectedDictionary = items.find((item) => item.id === selectedId) ?? null
368
370
 
371
+ // Publish the open dictionary's record context to the AppShell-owned
372
+ // `backend:record:current` mount so the enterprise record_locks widget resolves
373
+ // `dictionaries.dictionary` + id while the manager is open. Editing here is
374
+ // inline/dialog-driven (not a `[id]` detail route or a CrudForm with a stable
375
+ // `injectionSpotId`), so mounting presence on the manager keyed to the selected
376
+ // dictionary is the simplest place to hold the lock — it mirrors the
377
+ // `dictionaries.dictionary` resourceKind the dictionary PATCH route guards on.
378
+ // Cleared automatically when no dictionary is selected or on unmount.
379
+ useSetCurrentRecordInjectionContext(
380
+ buildRecordInjectionContext({
381
+ resourceKind: 'dictionaries.dictionary',
382
+ resourceId: selectedDictionary?.id ?? null,
383
+ updatedAt: selectedDictionary?.updatedAt ?? null,
384
+ data: (selectedDictionary ?? null) as Record<string, unknown> | null,
385
+ path: pathname,
386
+ }),
387
+ )
388
+
369
389
  return (
370
390
  <div className="grid gap-6 lg:grid-cols-[320px_1fr]">
371
391
  <div className="rounded-lg border bg-card p-4 shadow-sm">
@@ -276,6 +276,8 @@ export function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly
276
276
  await invalidateDictionaryEntries(queryClient, dictionaryId)
277
277
  flash(t('dictionaries.config.entries.success.delete', 'Dictionary entry deleted.'), 'success')
278
278
  } catch (err) {
279
+ // Route a concurrent-edit 409 through the single conflict surface (matching
280
+ // the entry save path); fall back to a flash for any other delete failure.
279
281
  if (surfaceRecordConflict(err, t)) {
280
282
  return
281
283
  }
@@ -1,5 +1,6 @@
1
1
  "use client"
2
2
  import * as React from 'react'
3
+ import { usePathname } from 'next/navigation'
3
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
4
5
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
5
6
  import { E } from '#generated/entities.ids.generated'
@@ -17,6 +18,7 @@ import { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
17
18
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
18
19
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
19
20
  import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
21
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
20
22
 
21
23
  type TreeResponse = {
22
24
  items: OrganizationTreeNode[]
@@ -46,6 +48,7 @@ const TREE_PADDING = 12
46
48
  export default function EditOrganizationPage({ params }: { params?: { id?: string } }) {
47
49
  const orgId = params?.id
48
50
  const t = useT()
51
+ const pathname = usePathname()
49
52
  const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)
50
53
  const [pathLabel, setPathLabel] = React.useState<string>('')
51
54
  const [tenantId, setTenantId] = React.useState<string | null>(null)
@@ -270,6 +273,20 @@ export default function EditOrganizationPage({ params }: { params?: { id?: strin
270
273
  { id: 'custom', title: t('directory.organizations.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },
271
274
  ]), [detailFields, t])
272
275
 
276
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
277
+ // mount so the enterprise record_locks widget resolves `directory.organization` + id
278
+ // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
279
+ // lock matches the save-time conflict surface for the same organization.
280
+ useSetCurrentRecordInjectionContext(
281
+ buildRecordInjectionContext({
282
+ resourceKind: 'directory.organization',
283
+ resourceId: orgId || null,
284
+ updatedAt: typeof initialValues?.updatedAt === 'string' ? initialValues.updatedAt : null,
285
+ data: initialValues,
286
+ path: pathname,
287
+ }),
288
+ )
289
+
273
290
  if (!orgId) {
274
291
  return (
275
292
  <Page>
@@ -1,5 +1,6 @@
1
1
  "use client"
2
2
  import * as React from 'react'
3
+ import { usePathname } from 'next/navigation'
3
4
  import { E } from '#generated/entities.ids.generated'
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'
@@ -9,6 +10,7 @@ import { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'
9
10
  import { readApiResultOrThrow, apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
10
11
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
11
12
  import { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'
13
+ import { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'
12
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
13
15
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
14
16
 
@@ -21,6 +23,7 @@ type TenantFormValues = {
21
23
 
22
24
  export default function EditTenantPage({ params }: { params?: { id?: string } }) {
23
25
  const tenantId = params?.id
26
+ const pathname = usePathname()
24
27
  const [initial, setInitial] = React.useState<TenantFormValues | null>(null)
25
28
  const [loading, setLoading] = React.useState(true)
26
29
  const [error, setError] = React.useState<string | null>(null)
@@ -84,6 +87,20 @@ export default function EditTenantPage({ params }: { params?: { id?: string } })
84
87
  return () => { cancelled = true }
85
88
  }, [tenantId, t])
86
89
 
90
+ // Publish page-load record context to the AppShell-owned `backend:record:current`
91
+ // mount so the enterprise record_locks widget resolves `directory.tenant` + id
92
+ // explicitly. The resourceKind mirrors the CrudForm `versionHistory` so the held
93
+ // lock matches the save-time conflict surface for the same tenant.
94
+ useSetCurrentRecordInjectionContext(
95
+ buildRecordInjectionContext({
96
+ resourceKind: 'directory.tenant',
97
+ resourceId: tenantId || null,
98
+ updatedAt: initial?.updatedAt ?? null,
99
+ data: initial as Record<string, unknown> | null,
100
+ path: pathname,
101
+ }),
102
+ )
103
+
87
104
  if (!tenantId) return null
88
105
 
89
106
  if (isNotFound) {
@@ -8,7 +8,7 @@ import { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/da
8
8
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
9
9
  import { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'
10
10
  import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
11
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
11
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
12
12
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
13
13
  import {
14
14
  beginEntitiesMutationGuard,
@@ -132,7 +132,7 @@ export async function POST(req: Request) {
132
132
  let ent = await em.findOne(CustomEntity, where)
133
133
  if (ent) {
134
134
  try {
135
- enforceCommandOptimisticLock({
135
+ await enforceCommandOptimisticLockWithGuards(container, {
136
136
  resourceKind: CUSTOM_ENTITY_DEFINITION_RESOURCE_KIND,
137
137
  resourceId: ent.id,
138
138
  current: ent.updatedAt ?? null,
@@ -11,7 +11,7 @@ import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared
11
11
  import { setRecordCustomFields } from '../lib/helpers'
12
12
  import { CustomFieldValue } from '../data/entities'
13
13
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
14
- import { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
14
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
15
15
  import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
16
16
  import { getModules } from '@open-mercato/shared/lib/i18n/server'
17
17
  import { assertEntityAclForRequest } from '../lib/entityAcl'
@@ -107,6 +107,8 @@ const listRecordsQuerySchema = z
107
107
  pageSize: z.coerce.number().int().min(1).max(100).optional(),
108
108
  sortField: z.string().optional(),
109
109
  sortDir: z.enum(['asc', 'desc']).optional(),
110
+ search: z.string().optional(),
111
+ searchFields: z.string().optional(),
110
112
  withDeleted: z.coerce.boolean().optional(),
111
113
  format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),
112
114
  exportScope: z.enum(['full']).optional(),
@@ -145,10 +147,15 @@ export async function GET(req: Request) {
145
147
  const sortField = url.searchParams.get('sortField') || 'id'
146
148
  const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'
147
149
  const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)
150
+ const searchTerm = (url.searchParams.get('search') || '').trim()
151
+ const searchFields = (url.searchParams.get('searchFields') || '')
152
+ .split(',')
153
+ .map((field) => field.trim())
154
+ .filter(Boolean)
148
155
 
149
156
  const qpEntries: Array<[string, string]> = []
150
157
  for (const [key, val] of url.searchParams.entries()) {
151
- if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full'].includes(key)) continue
158
+ if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full','search','searchFields'].includes(key)) continue
152
159
  qpEntries.push([key, val])
153
160
  }
154
161
 
@@ -246,6 +253,15 @@ export async function GET(req: Request) {
246
253
  // even before the first record exists.
247
254
  if (isCustomEntity) qopts.forceCustomEntityStorage = true
248
255
  for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)
256
+ // Server-side full-result search: match the term against the requested fields
257
+ // (defaults to `id`) before pagination so totals/exports stay consistent with
258
+ // the active search instead of filtering only the current client page (#3229).
259
+ if (searchTerm) {
260
+ const fields = searchFields.length ? searchFields : ['id']
261
+ const pattern = `%${searchTerm}%`
262
+ const orClauses = fields.map((field) => ({ [field]: { $ilike: pattern } }))
263
+ ;(filtersObj as any).$or = orClauses
264
+ }
249
265
  const res = await qe.query(entityId as any, qopts)
250
266
  const rawItems = res.items || []
251
267
  const viewPageItems = rawItems.map(mapRow)
@@ -444,7 +460,8 @@ export async function PUT(req: Request) {
444
460
  const { entityId, recordId, values } = parsed.data
445
461
 
446
462
  try {
447
- const { resolve } = await createRequestContainer()
463
+ const container = await createRequestContainer()
464
+ const { resolve } = container
448
465
  const de = resolve('dataEngine') as any
449
466
  const em = resolve('em') as any
450
467
  const rbac = resolve('rbacService') as RbacService
@@ -494,7 +511,7 @@ export async function PUT(req: Request) {
494
511
  entityId: rid,
495
512
  organizationId: targetOrgId,
496
513
  })
497
- enforceCommandOptimisticLock({
514
+ await enforceCommandOptimisticLockWithGuards(container, {
498
515
  resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
499
516
  resourceId: rid,
500
517
  current: currentUpdatedAt,
@@ -542,7 +559,8 @@ export async function DELETE(req: Request) {
542
559
  const { entityId, recordId } = parsed.data
543
560
 
544
561
  try {
545
- const { resolve } = await createRequestContainer()
562
+ const container = await createRequestContainer()
563
+ const { resolve } = container
546
564
  const de = resolve('dataEngine') as any
547
565
  const em = resolve('em') as any
548
566
  const rbac = resolve('rbacService') as RbacService
@@ -560,7 +578,7 @@ export async function DELETE(req: Request) {
560
578
  entityId: recordId,
561
579
  organizationId: targetOrgId,
562
580
  })
563
- enforceCommandOptimisticLock({
581
+ await enforceCommandOptimisticLockWithGuards(container, {
564
582
  resourceKind: CUSTOM_ENTITY_RECORD_RESOURCE_KIND,
565
583
  resourceId: recordId,
566
584
  current: currentUpdatedAt,
@@ -97,7 +97,16 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
97
97
  keyExtras: [scopeVersion],
98
98
  })
99
99
 
100
- // Fetch records whenever paging/sorting/filters change (do NOT refetch on cfDefs/search changes)
100
+ // Fields searched server-side: every visible column plus the base `id`.
101
+ const searchableFields = React.useMemo(() => {
102
+ const fields = (columns || [])
103
+ .map((col) => (col as any).accessorKey)
104
+ .filter((key): key is string => typeof key === 'string' && key.length > 0)
105
+ return Array.from(new Set(['id', ...fields]))
106
+ }, [columns])
107
+
108
+ // Fetch records whenever paging/sorting/filters/search change. Search is applied
109
+ // server-side (before pagination) so totals and exports stay consistent (#3229).
101
110
  React.useEffect(() => {
102
111
  let cancelled = false
103
112
  const run = async () => {
@@ -107,6 +116,11 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
107
116
  params.set('entityId', entityId)
108
117
  params.set('page', String(page))
109
118
  params.set('pageSize', String(pageSize))
119
+ const trimmedSearch = search.trim()
120
+ if (trimmedSearch) {
121
+ params.set('search', trimmedSearch)
122
+ if (searchableFields.length) params.set('searchFields', searchableFields.join(','))
123
+ }
110
124
  const s = sorting?.[0]
111
125
  if (s?.id) {
112
126
  params.set('sortField', String(s.id))
@@ -154,7 +168,7 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
154
168
  }
155
169
  if (entityId) run()
156
170
  return () => { cancelled = true }
157
- }, [entityId, page, pageSize, sorting, filterValues, scopeVersion])
171
+ }, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])
158
172
 
159
173
  // Build columns from custom field definitions only (no data round-trip)
160
174
  React.useEffect(() => {
@@ -175,15 +189,9 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
175
189
  setColumns(cols)
176
190
  }, [cfDefs])
177
191
 
178
- // Client-side quick search filtering without triggering server refetch
179
- const data = React.useMemo(() => {
180
- if (!search.trim()) return rawData
181
- const q = search.trim().toLowerCase()
182
- return (rawData || []).filter((row: any) => {
183
- const values = Object.values(row || {})
184
- return values.some((v) => normalizeCell(v).toLowerCase().includes(q))
185
- })
186
- }, [rawData, search])
192
+ // Search is server-side (see fetch effect); render the fetched page as-is so
193
+ // pagination totals and exports stay consistent with the active search (#3229).
194
+ const data = rawData
187
195
 
188
196
  const viewExportColumns = React.useMemo(() => {
189
197
  return (columns || [])
@@ -213,8 +221,13 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
213
221
  qp.set('sortField', String(sort.id))
214
222
  qp.set('sortDir', sort.desc ? 'desc' : 'asc')
215
223
  }
224
+ const trimmedSearch = search.trim()
225
+ if (trimmedSearch) {
226
+ qp.set('search', trimmedSearch)
227
+ if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))
228
+ }
216
229
  return `/api/entities/records?${qp.toString()}`
217
- }, [entityId, sorting])
230
+ }, [entityId, sorting, search, searchableFields])
218
231
 
219
232
  const exportConfig = React.useMemo(() => {
220
233
  const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'