@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4239.1.4a264a5828

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 (408) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +37 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
  4. package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
  5. package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +35 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +7 -0
  8. package/dist/generated/entities/staff_time_project_member/index.js +29 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
  10. package/dist/generated/entities.ids.generated.js +5 -1
  11. package/dist/generated/entities.ids.generated.js.map +2 -2
  12. package/dist/generated/entity-fields-registry.js +64 -0
  13. package/dist/generated/entity-fields-registry.js.map +2 -2
  14. package/dist/helpers/integration/timesheetFixtures.js +50 -0
  15. package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
  16. package/dist/modules/attachments/api/library/[id]/route.js +20 -16
  17. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  18. package/dist/modules/attachments/api/route.js +18 -14
  19. package/dist/modules/attachments/api/route.js.map +2 -2
  20. package/dist/modules/auth/api/roles/acl/route.js +10 -4
  21. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  22. package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
  23. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  24. package/dist/modules/auth/api/users/acl/route.js +16 -11
  25. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +87 -71
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
  29. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  30. package/dist/modules/business_rules/backend/logs/[id]/page.js +24 -5
  31. package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
  32. package/dist/modules/catalog/api/offers/route.js +15 -5
  33. package/dist/modules/catalog/api/offers/route.js.map +2 -2
  34. package/dist/modules/catalog/commands/categories.js +61 -12
  35. package/dist/modules/catalog/commands/categories.js.map +2 -2
  36. package/dist/modules/catalog/commands/products.js +79 -54
  37. package/dist/modules/catalog/commands/products.js.map +2 -2
  38. package/dist/modules/catalog/commands/variants.js +29 -16
  39. package/dist/modules/catalog/commands/variants.js.map +2 -2
  40. package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
  41. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  42. package/dist/modules/currencies/commands/currencies.js +15 -8
  43. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  44. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  46. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  47. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  48. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  49. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  50. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
  51. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
  55. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  56. package/dist/modules/customers/commands/addresses.js +35 -21
  57. package/dist/modules/customers/commands/addresses.js.map +2 -2
  58. package/dist/modules/customers/commands/companies.js +163 -162
  59. package/dist/modules/customers/commands/companies.js.map +2 -2
  60. package/dist/modules/customers/commands/deals.js +3 -4
  61. package/dist/modules/customers/commands/deals.js.map +2 -2
  62. package/dist/modules/customers/commands/interactions.js +19 -22
  63. package/dist/modules/customers/commands/interactions.js.map +2 -2
  64. package/dist/modules/customers/commands/people.js +18 -15
  65. package/dist/modules/customers/commands/people.js.map +2 -2
  66. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  67. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  68. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  69. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  70. package/dist/modules/customers/commands/pipelines.js +27 -20
  71. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  72. package/dist/modules/customers/commands/tags.js +13 -5
  73. package/dist/modules/customers/commands/tags.js.map +2 -2
  74. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  75. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  76. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  77. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  78. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  79. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  80. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  81. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  82. package/dist/modules/directory/commands/organizations.js +192 -158
  83. package/dist/modules/directory/commands/organizations.js.map +3 -3
  84. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  85. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  86. package/dist/modules/messages/commands/messages.js +77 -75
  87. package/dist/modules/messages/commands/messages.js.map +2 -2
  88. package/dist/modules/messages/commands/shared.js +132 -132
  89. package/dist/modules/messages/commands/shared.js.map +2 -2
  90. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  91. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  92. package/dist/modules/progress/acl.js +8 -4
  93. package/dist/modules/progress/acl.js.map +2 -2
  94. package/dist/modules/resources/commands/resources.js +125 -117
  95. package/dist/modules/resources/commands/resources.js.map +2 -2
  96. package/dist/modules/resources/commands/tags.js +7 -3
  97. package/dist/modules/resources/commands/tags.js.map +2 -2
  98. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  99. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  100. package/dist/modules/sales/commands/documents.js +629 -478
  101. package/dist/modules/sales/commands/documents.js.map +2 -2
  102. package/dist/modules/sales/commands/payments.js +146 -146
  103. package/dist/modules/sales/commands/payments.js.map +2 -2
  104. package/dist/modules/sales/commands/returns.js +68 -60
  105. package/dist/modules/sales/commands/returns.js.map +2 -2
  106. package/dist/modules/staff/acl.js +10 -1
  107. package/dist/modules/staff/acl.js.map +2 -2
  108. package/dist/modules/staff/analytics.js +33 -0
  109. package/dist/modules/staff/analytics.js.map +7 -0
  110. package/dist/modules/staff/api/guards.js +31 -0
  111. package/dist/modules/staff/api/guards.js.map +7 -0
  112. package/dist/modules/staff/api/interceptors.js +96 -0
  113. package/dist/modules/staff/api/interceptors.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  115. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  117. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  119. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  121. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  122. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  123. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  124. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  125. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  126. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  127. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  128. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  129. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  130. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  131. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  132. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  133. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  134. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  135. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  137. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  139. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  144. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  145. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  146. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  147. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  148. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  149. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  150. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  151. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  152. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  153. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  154. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  155. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  156. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  157. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  158. package/dist/modules/staff/cli.js +38 -1
  159. package/dist/modules/staff/cli.js.map +2 -2
  160. package/dist/modules/staff/commands/index.js +2 -0
  161. package/dist/modules/staff/commands/index.js.map +2 -2
  162. package/dist/modules/staff/commands/leave-requests.js +30 -28
  163. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  164. package/dist/modules/staff/commands/team-members.js +21 -20
  165. package/dist/modules/staff/commands/team-members.js.map +2 -2
  166. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  167. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  168. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  169. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  170. package/dist/modules/staff/data/enrichers.js +104 -0
  171. package/dist/modules/staff/data/enrichers.js.map +7 -0
  172. package/dist/modules/staff/data/entities.js +226 -1
  173. package/dist/modules/staff/data/entities.js.map +2 -2
  174. package/dist/modules/staff/data/validators.js +113 -1
  175. package/dist/modules/staff/data/validators.js.map +2 -2
  176. package/dist/modules/staff/events.js +13 -1
  177. package/dist/modules/staff/events.js.map +2 -2
  178. package/dist/modules/staff/lib/crud.js +7 -1
  179. package/dist/modules/staff/lib/crud.js.map +2 -2
  180. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  181. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  183. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  185. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  187. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  189. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  191. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  193. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  195. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  197. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  199. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  201. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  203. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  205. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  207. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  209. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  211. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  212. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  213. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  214. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  215. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  216. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  217. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  218. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  219. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  220. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  221. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  222. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  223. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  224. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  225. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  226. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  227. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  228. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  229. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  230. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  231. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  232. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  233. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  234. package/dist/modules/staff/search.js +35 -0
  235. package/dist/modules/staff/search.js.map +2 -2
  236. package/dist/modules/staff/setup.js +15 -1
  237. package/dist/modules/staff/setup.js.map +2 -2
  238. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  239. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  240. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  241. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  242. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  243. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  244. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  245. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  246. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  247. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  248. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  249. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  250. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  251. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  252. package/dist/modules/staff/widgets/injection-table.js +12 -0
  253. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  254. package/dist/modules/sync_excel/api/import/route.js +19 -17
  255. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  256. package/dist/modules/translations/commands/translations.js +22 -19
  257. package/dist/modules/translations/commands/translations.js.map +2 -2
  258. package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
  259. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  260. package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
  261. package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
  262. package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
  263. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  264. package/generated/entities/staff_time_entry/index.ts +17 -0
  265. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  266. package/generated/entities/staff_time_project/index.ts +16 -0
  267. package/generated/entities/staff_time_project_member/index.ts +13 -0
  268. package/generated/entities.ids.generated.ts +5 -1
  269. package/generated/entity-fields-registry.ts +64 -0
  270. package/package.json +7 -7
  271. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  272. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  273. package/src/modules/attachments/api/route.ts +20 -14
  274. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  275. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  276. package/src/modules/auth/api/users/acl/route.ts +17 -12
  277. package/src/modules/auth/commands/users.ts +96 -80
  278. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  279. package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
  280. package/src/modules/catalog/api/offers/route.ts +20 -5
  281. package/src/modules/catalog/commands/categories.ts +61 -12
  282. package/src/modules/catalog/commands/products.ts +93 -60
  283. package/src/modules/catalog/commands/variants.ts +29 -16
  284. package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
  285. package/src/modules/currencies/commands/currencies.ts +27 -14
  286. package/src/modules/currencies/i18n/de.json +1 -0
  287. package/src/modules/currencies/i18n/en.json +1 -0
  288. package/src/modules/currencies/i18n/es.json +1 -0
  289. package/src/modules/currencies/i18n/pl.json +1 -0
  290. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  291. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  292. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  293. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
  294. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
  295. package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
  296. package/src/modules/customers/commands/addresses.ts +35 -23
  297. package/src/modules/customers/commands/companies.ts +166 -165
  298. package/src/modules/customers/commands/deals.ts +2 -4
  299. package/src/modules/customers/commands/interactions.ts +20 -26
  300. package/src/modules/customers/commands/people.ts +18 -15
  301. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  302. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  303. package/src/modules/customers/commands/pipelines.ts +29 -23
  304. package/src/modules/customers/commands/tags.ts +13 -5
  305. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  306. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  307. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  308. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  309. package/src/modules/directory/commands/organizations.ts +203 -166
  310. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  311. package/src/modules/messages/commands/messages.ts +82 -80
  312. package/src/modules/messages/commands/shared.ts +138 -133
  313. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  314. package/src/modules/progress/acl.ts +4 -0
  315. package/src/modules/resources/commands/resources.ts +127 -117
  316. package/src/modules/resources/commands/tags.ts +7 -3
  317. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  318. package/src/modules/sales/commands/documents.ts +673 -481
  319. package/src/modules/sales/commands/payments.ts +158 -152
  320. package/src/modules/sales/commands/returns.ts +74 -63
  321. package/src/modules/staff/acl.ts +11 -0
  322. package/src/modules/staff/analytics.ts +30 -0
  323. package/src/modules/staff/api/guards.ts +59 -0
  324. package/src/modules/staff/api/interceptors.ts +122 -0
  325. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  326. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  327. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  328. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  329. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  330. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  331. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  332. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  333. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  334. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  335. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  336. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  337. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  338. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  339. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  340. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  341. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  342. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  343. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  344. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  345. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  346. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  347. package/src/modules/staff/cli.ts +40 -1
  348. package/src/modules/staff/commands/index.ts +2 -0
  349. package/src/modules/staff/commands/leave-requests.ts +37 -29
  350. package/src/modules/staff/commands/team-members.ts +25 -20
  351. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  352. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  353. package/src/modules/staff/data/enrichers.ts +134 -0
  354. package/src/modules/staff/data/entities.ts +198 -0
  355. package/src/modules/staff/data/validators.ts +129 -0
  356. package/src/modules/staff/events.ts +13 -0
  357. package/src/modules/staff/i18n/de.json +209 -1
  358. package/src/modules/staff/i18n/en.json +209 -1
  359. package/src/modules/staff/i18n/es.json +209 -1
  360. package/src/modules/staff/i18n/pl.json +209 -1
  361. package/src/modules/staff/lib/crud.ts +8 -0
  362. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  363. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  364. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  365. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  366. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  367. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  368. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  369. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  370. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  371. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  372. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  373. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  374. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  375. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  376. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  377. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  378. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  379. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  380. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  381. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  382. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  383. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  384. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  385. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  386. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  387. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  388. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  389. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  390. package/src/modules/staff/search.ts +35 -0
  391. package/src/modules/staff/setup.ts +15 -0
  392. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  393. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  394. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  395. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  396. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  397. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  398. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  399. package/src/modules/staff/widgets/injection-table.ts +10 -0
  400. package/src/modules/sync_excel/api/import/route.ts +23 -18
  401. package/src/modules/translations/commands/translations.ts +49 -41
  402. package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
  403. package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
  404. package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
  405. package/src/modules/workflows/i18n/de.json +1 -0
  406. package/src/modules/workflows/i18n/en.json +1 -0
  407. package/src/modules/workflows/i18n/es.json +1 -0
  408. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -1,5 +1,6 @@
1
1
  import { EntityManager, type FilterQuery } from '@mikro-orm/postgresql'
2
2
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
3
4
  import { Role, RoleSidebarPreference, SidebarVariant, User, UserSidebarPreference } from '../data/entities'
4
5
  import {
5
6
  SIDEBAR_PREFERENCES_VERSION,
@@ -346,21 +347,24 @@ export async function createSidebarVariant(
346
347
  version: input.settings?.version ?? SIDEBAR_PREFERENCES_VERSION,
347
348
  })
348
349
 
349
- if (input.isActive === true) {
350
- await deactivateAllVariants(em, scope)
351
- }
352
-
353
- const variant = em.create(SidebarVariant, {
354
- user: em.getReference(User, userId),
355
- tenantId,
356
- organizationId,
357
- locale,
358
- name: finalName,
359
- settingsJson: settings,
360
- isActive: input.isActive === true,
361
- createdAt: new Date(),
362
- })
363
- await em.flush()
350
+ let variant!: SidebarVariant
351
+ await withAtomicFlush(em, [
352
+ async () => {
353
+ if (input.isActive === true) {
354
+ await deactivateAllVariants(em, scope)
355
+ }
356
+ variant = em.create(SidebarVariant, {
357
+ user: em.getReference(User, userId),
358
+ tenantId,
359
+ organizationId,
360
+ locale,
361
+ name: finalName,
362
+ settingsJson: settings,
363
+ isActive: input.isActive === true,
364
+ createdAt: new Date(),
365
+ })
366
+ },
367
+ ], { transaction: true })
364
368
  return toVariantRecord(variant)
365
369
  }
366
370
 
@@ -383,23 +387,27 @@ export async function updateSidebarVariant(
383
387
  { tenantId, organizationId },
384
388
  )
385
389
  if (!variant) return null
386
- if (typeof input.name === 'string' && input.name.trim().length > 0) {
387
- variant.name = input.name.trim()
388
- }
389
- if (input.settings) {
390
- variant.settingsJson = normalizeSidebarSettings({
391
- ...input.settings,
392
- version: input.settings.version ?? SIDEBAR_PREFERENCES_VERSION,
393
- })
394
- }
395
- if (typeof input.isActive === 'boolean') {
396
- if (input.isActive) {
397
- await deactivateAllVariants(em, scope, variantId)
398
- }
399
- variant.isActive = input.isActive
400
- }
401
- await em.flush()
402
- return toVariantRecord(variant)
390
+ const target = variant
391
+ await withAtomicFlush(em, [
392
+ async () => {
393
+ if (typeof input.name === 'string' && input.name.trim().length > 0) {
394
+ target.name = input.name.trim()
395
+ }
396
+ if (input.settings) {
397
+ target.settingsJson = normalizeSidebarSettings({
398
+ ...input.settings,
399
+ version: input.settings.version ?? SIDEBAR_PREFERENCES_VERSION,
400
+ })
401
+ }
402
+ if (typeof input.isActive === 'boolean') {
403
+ if (input.isActive) {
404
+ await deactivateAllVariants(em, scope, variantId)
405
+ }
406
+ target.isActive = input.isActive
407
+ }
408
+ },
409
+ ], { transaction: true })
410
+ return toVariantRecord(target)
403
411
  }
404
412
 
405
413
  export async function deleteSidebarVariant(
@@ -10,6 +10,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
10
10
  import { Button } from '@open-mercato/ui/primitives/button'
11
11
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
12
12
  import { JsonDisplay } from '@open-mercato/ui/backend/JsonDisplay'
13
+ import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
13
14
 
14
15
  type RuleExecutionLog = {
15
16
  id: string
@@ -54,7 +55,13 @@ export default function ExecutionLogDetailPage() {
54
55
  queryFn: async () => {
55
56
  const response = await apiFetch(`/api/business_rules/logs/${logId}`)
56
57
  if (!response.ok) {
57
- throw new Error(t('business_rules.logs.errors.fetchFailed'))
58
+ const httpErr = new Error(
59
+ response.status === 404
60
+ ? t('business_rules.logs.errors.notFound', 'Execution log not found.')
61
+ : t('business_rules.logs.errors.fetchFailed')
62
+ ) as Error & { status: number }
63
+ httpErr.status = response.status
64
+ throw httpErr
58
65
  }
59
66
  const result = await response.json()
60
67
  return result as RuleExecutionLog
@@ -75,16 +82,34 @@ export default function ExecutionLogDetailPage() {
75
82
  )
76
83
  }
77
84
 
85
+ const isNotFound = !isLoading && (error as (Error & { status?: number }) | null)?.status === 404
86
+
87
+ if (isNotFound) {
88
+ return (
89
+ <Page>
90
+ <PageBody>
91
+ <RecordNotFoundState
92
+ label={t('business_rules.logs.errors.notFound', 'Execution log not found.')}
93
+ backHref="/backend/logs"
94
+ backLabel={t('business_rules.logs.backToList', 'Back to logs')}
95
+ />
96
+ </PageBody>
97
+ </Page>
98
+ )
99
+ }
100
+
78
101
  if (error || !log) {
79
102
  return (
80
103
  <Page>
81
104
  <PageBody>
82
- <div className="flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground">
83
- <p>{error ? t('business_rules.logs.errors.loadFailed') : t('business_rules.logs.errors.notFound')}</p>
84
- <Button asChild variant="outline">
85
- <Link href="/backend/logs">{t('business_rules.logs.backToList')}</Link>
86
- </Button>
87
- </div>
105
+ <ErrorMessage
106
+ label={(error as Error | null)?.message ?? t('business_rules.logs.errors.loadFailed')}
107
+ action={
108
+ <Button asChild variant="outline" size="sm">
109
+ <Link href="/backend/logs">{t('business_rules.logs.backToList', 'Back to logs')}</Link>
110
+ </Button>
111
+ }
112
+ />
88
113
  </PageBody>
89
114
  </Page>
90
115
  )
@@ -94,11 +94,25 @@ export async function decorateOffersWithDetails(
94
94
  .filter((value): value is string => !!value)
95
95
  if (!offerIds.length && !productIds.length) return
96
96
  const em = ctx.container.resolve('em') as EntityManager
97
+ const scopeTenantId = ctx.auth?.tenantId ?? null
98
+ if (!scopeTenantId) {
99
+ throw new CrudHttpError(403, '[internal] Missing tenant scope for offer decoration')
100
+ }
101
+ const scopeOrgIds =
102
+ Array.isArray(ctx.organizationIds) && ctx.organizationIds.length
103
+ ? Array.from(new Set(ctx.organizationIds))
104
+ : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)
105
+ ? [(ctx.selectedOrganizationId ?? ctx.auth?.orgId) as string]
106
+ : []
107
+ const scopeWhere: Record<string, unknown> = { tenantId: scopeTenantId }
108
+ if (scopeOrgIds.length === 1) scopeWhere.organizationId = scopeOrgIds[0]
109
+ else if (scopeOrgIds.length > 1) scopeWhere.organizationId = { $in: scopeOrgIds }
110
+ const scope = { tenantId: scopeTenantId, organizationId: scopeOrgIds.length === 1 ? scopeOrgIds[0] : null }
97
111
  const [products, prices, defaultVariants] = await Promise.all([
98
112
  productIds.length
99
113
  ? em.find(
100
114
  CatalogProduct,
101
- { id: { $in: productIds } },
115
+ { id: { $in: productIds }, ...scopeWhere },
102
116
  {
103
117
  fields: ['id', 'title', 'description', 'defaultMediaId', 'defaultMediaUrl', 'sku'],
104
118
  },
@@ -108,15 +122,15 @@ export async function decorateOffersWithDetails(
108
122
  ? findWithDecryption(
109
123
  em,
110
124
  CatalogProductPrice,
111
- { offer: { $in: offerIds } },
125
+ { offer: { $in: offerIds }, ...scopeWhere },
112
126
  { populate: ['priceKind'] },
113
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
127
+ scope,
114
128
  )
115
129
  : [],
116
130
  productIds.length
117
131
  ? em.find(
118
132
  CatalogProductVariant,
119
- { product: { $in: productIds }, isDefault: true },
133
+ { product: { $in: productIds }, isDefault: true, ...scopeWhere },
120
134
  { fields: ['id', 'product'] },
121
135
  )
122
136
  : [],
@@ -227,6 +241,7 @@ export async function decorateOffersWithDetails(
227
241
  CatalogProductPrice,
228
242
  {
229
243
  offer: null,
244
+ ...scopeWhere,
230
245
  $and: [
231
246
  { $or: fallbackTargets },
232
247
  channelFilterValues.includes(null)
@@ -240,7 +255,7 @@ export async function decorateOffersWithDetails(
240
255
  ],
241
256
  },
242
257
  { populate: ['priceKind'] },
243
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
258
+ scope,
244
259
  )
245
260
  : []
246
261
  fallbackEntries.forEach((entry) => {
@@ -2,6 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandHandler } from '@open-mercato/shared/lib/commands'
3
3
  import { buildChanges, requireId, parseWithCustomFields, setCustomFieldsIfAny, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
4
4
  import { loadCustomFieldSnapshot, buildCustomFieldResetMap } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
5
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
5
6
  import type { EntityManager } from '@mikro-orm/postgresql'
6
7
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
8
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
@@ -160,8 +161,16 @@ const createCategoryCommand: CommandHandler<CategoryCreateInput, { categoryId: s
160
161
  updatedAt: now,
161
162
  })
162
163
  em.persist(record)
163
- await em.flush()
164
- await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
164
+ await withAtomicFlush(
165
+ em,
166
+ [
167
+ () => em.flush(),
168
+ async () => {
169
+ await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
170
+ },
171
+ ],
172
+ { transaction: true }
173
+ )
165
174
  await setCustomFieldsIfAny({
166
175
  dataEngine: ctx.container.resolve('dataEngine'),
167
176
  entityId: E.catalog.catalog_product_category,
@@ -215,8 +224,16 @@ const createCategoryCommand: CommandHandler<CategoryCreateInput, { categoryId: s
215
224
  ensureOrganizationScope(ctx, record.organizationId)
216
225
  record.deletedAt = new Date()
217
226
  record.isActive = false
218
- await em.flush()
219
- await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
227
+ await withAtomicFlush(
228
+ em,
229
+ [
230
+ () => em.flush(),
231
+ async () => {
232
+ await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
233
+ },
234
+ ],
235
+ { transaction: true }
236
+ )
220
237
  const resetValues = buildCustomFieldResetMap(undefined, after.custom ?? undefined)
221
238
  if (Object.keys(resetValues).length) {
222
239
  await setCustomFieldsIfAny({
@@ -288,8 +305,16 @@ const updateCategoryCommand: CommandHandler<CategoryUpdateInput, { categoryId: s
288
305
  record.isActive = parsed.isActive
289
306
  }
290
307
 
291
- await em.flush()
292
- await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
308
+ await withAtomicFlush(
309
+ em,
310
+ [
311
+ () => em.flush(),
312
+ async () => {
313
+ await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
314
+ },
315
+ ],
316
+ { transaction: true }
317
+ )
293
318
  await setCustomFieldsIfAny({
294
319
  dataEngine: ctx.container.resolve('dataEngine'),
295
320
  entityId: E.catalog.catalog_product_category,
@@ -377,8 +402,16 @@ const updateCategoryCommand: CommandHandler<CategoryUpdateInput, { categoryId: s
377
402
  record.isActive = before.isActive
378
403
  record.deletedAt = null
379
404
  }
380
- await em.flush()
381
- await rebuildCategoryHierarchyForOrganization(em, before.organizationId, before.tenantId)
405
+ await withAtomicFlush(
406
+ em,
407
+ [
408
+ () => em.flush(),
409
+ async () => {
410
+ await rebuildCategoryHierarchyForOrganization(em, before.organizationId, before.tenantId)
411
+ },
412
+ ],
413
+ { transaction: true }
414
+ )
382
415
  const resetValues = buildCustomFieldResetMap(payload?.after?.custom ?? undefined, before.custom ?? undefined)
383
416
  if (Object.keys(resetValues).length) {
384
417
  await setCustomFieldsIfAny({
@@ -417,8 +450,16 @@ const deleteCategoryCommand: CommandHandler<{ id?: string }, { categoryId: strin
417
450
 
418
451
  record.deletedAt = new Date()
419
452
  record.isActive = false
420
- await em.flush()
421
- await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
453
+ await withAtomicFlush(
454
+ em,
455
+ [
456
+ () => em.flush(),
457
+ async () => {
458
+ await rebuildCategoryHierarchyForOrganization(em, record.organizationId, record.tenantId)
459
+ },
460
+ ],
461
+ { transaction: true }
462
+ )
422
463
  if (snapshot?.custom && Object.keys(snapshot.custom).length) {
423
464
  const resetValues = buildCustomFieldResetMap(snapshot.custom, undefined)
424
465
  if (Object.keys(resetValues).length) {
@@ -504,8 +545,16 @@ const deleteCategoryCommand: CommandHandler<{ id?: string }, { categoryId: strin
504
545
  record.childIds = before.childIds
505
546
  record.descendantIds = before.descendantIds
506
547
  }
507
- await em.flush()
508
- await rebuildCategoryHierarchyForOrganization(em, before.organizationId, before.tenantId)
548
+ await withAtomicFlush(
549
+ em,
550
+ [
551
+ () => em.flush(),
552
+ async () => {
553
+ await rebuildCategoryHierarchyForOrganization(em, before.organizationId, before.tenantId)
554
+ },
555
+ ],
556
+ { transaction: true }
557
+ )
509
558
  if (before.custom && Object.keys(before.custom).length) {
510
559
  await setCustomFieldsIfAny({
511
560
  dataEngine: ctx.container.resolve('dataEngine'),
@@ -26,6 +26,7 @@ import {
26
26
  loadCustomFieldSnapshot,
27
27
  buildCustomFieldResetMap,
28
28
  } from "@open-mercato/shared/lib/commands/customFieldSnapshots";
29
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
29
30
  import { E } from "#generated/entities.ids.generated";
30
31
  import { slugifyTagLabel } from "@open-mercato/shared/lib/utils";
31
32
  import { parseObjectLike } from "@open-mercato/shared/lib/json/parseObjectLike";
@@ -950,15 +951,15 @@ type VariantCleanupSnapshot = {
950
951
  custom: Record<string, unknown> | null;
951
952
  };
952
953
 
953
- async function deleteProductVariantsAndRelatedData(opts: {
954
- em: EntityManager;
955
- product: CatalogProduct;
956
- dataEngine: DataEngine;
957
- ctx: CommandRuntimeContext;
958
- }): Promise<void> {
959
- const { em, product, dataEngine, ctx } = opts;
954
+ async function collectProductVariantCleanup(
955
+ em: EntityManager,
956
+ product: CatalogProduct,
957
+ ): Promise<{
958
+ variants: CatalogProductVariant[];
959
+ cleanupEntries: VariantCleanupSnapshot[];
960
+ }> {
960
961
  const variants = await em.find(CatalogProductVariant, { product });
961
- if (!variants.length) return;
962
+ if (!variants.length) return { variants: [], cleanupEntries: [] };
962
963
  const cleanupEntries: VariantCleanupSnapshot[] = await Promise.all(
963
964
  variants.map(async (variant) => {
964
965
  const custom = await loadCustomFieldSnapshot(em, {
@@ -975,6 +976,14 @@ async function deleteProductVariantsAndRelatedData(opts: {
975
976
  };
976
977
  }),
977
978
  );
979
+ return { variants, cleanupEntries };
980
+ }
981
+
982
+ async function removeProductVariants(
983
+ em: EntityManager,
984
+ variants: CatalogProductVariant[],
985
+ ): Promise<void> {
986
+ if (!variants.length) return;
978
987
  const variantIds = variants.map((variant) => variant.id);
979
988
  if (variantIds.length) {
980
989
  await em.nativeDelete(CatalogProductPrice, {
@@ -984,7 +993,14 @@ async function deleteProductVariantsAndRelatedData(opts: {
984
993
  for (const variant of variants) {
985
994
  em.remove(variant);
986
995
  }
987
- await em.flush();
996
+ }
997
+
998
+ async function emitProductVariantCleanupSideEffects(opts: {
999
+ dataEngine: DataEngine;
1000
+ ctx: CommandRuntimeContext;
1001
+ cleanupEntries: VariantCleanupSnapshot[];
1002
+ }): Promise<void> {
1003
+ const { dataEngine, ctx, cleanupEntries } = opts;
988
1004
  for (const cleanup of cleanupEntries) {
989
1005
  if (!cleanup.custom) continue;
990
1006
  const resetValues = buildCustomFieldResetMap(cleanup.custom, undefined);
@@ -1307,15 +1323,16 @@ const createProductCommand: CommandHandler<
1307
1323
  }
1308
1324
  em.persist(record);
1309
1325
  try {
1310
- await em.flush();
1311
- } catch (error) {
1312
- await rethrowProductUniqueConstraint(error);
1313
- }
1314
- await syncOffers(em, record, parsed.offers);
1315
- await syncCategoryAssignments(em, record, parsed.categoryIds);
1316
- await syncProductTags(em, record, parsed.tags);
1317
- try {
1318
- await em.flush();
1326
+ await withAtomicFlush(
1327
+ em,
1328
+ [
1329
+ () => em.flush(),
1330
+ () => syncOffers(em, record, parsed.offers),
1331
+ () => syncCategoryAssignments(em, record, parsed.categoryIds),
1332
+ () => syncProductTags(em, record, parsed.tags),
1333
+ ],
1334
+ { transaction: true },
1335
+ );
1319
1336
  } catch (error) {
1320
1337
  await rethrowProductUniqueConstraint(error);
1321
1338
  }
@@ -1614,15 +1631,16 @@ const updateProductCommand: CommandHandler<
1614
1631
  record.isConfigurable = parsed.isConfigurable;
1615
1632
  if (parsed.isActive !== undefined) record.isActive = parsed.isActive;
1616
1633
  try {
1617
- await em.flush();
1618
- } catch (error) {
1619
- await rethrowProductUniqueConstraint(error);
1620
- }
1621
- await syncOffers(em, record, parsed.offers);
1622
- await syncCategoryAssignments(em, record, parsed.categoryIds);
1623
- await syncProductTags(em, record, parsed.tags);
1624
- try {
1625
- await em.flush();
1634
+ await withAtomicFlush(
1635
+ em,
1636
+ [
1637
+ () => em.flush(),
1638
+ () => syncOffers(em, record, parsed.offers),
1639
+ () => syncCategoryAssignments(em, record, parsed.categoryIds),
1640
+ () => syncProductTags(em, record, parsed.tags),
1641
+ ],
1642
+ { transaction: true },
1643
+ );
1626
1644
  } catch (error) {
1627
1645
  await rethrowProductUniqueConstraint(error);
1628
1646
  }
@@ -1732,16 +1750,16 @@ const updateProductCommand: CommandHandler<
1732
1750
  ensureTenantScope(ctx, before.tenantId);
1733
1751
  ensureOrganizationScope(ctx, before.organizationId);
1734
1752
  applyProductSnapshot(em, record, before);
1735
- await em.flush();
1736
-
1737
- const relationEm = em.fork();
1738
- const relationRecord = await findOneWithDecryption(relationEm, CatalogProduct, { id: before.id });
1739
- if (relationRecord) {
1740
- await restoreOffersFromSnapshot(relationEm, relationRecord, before.offers);
1741
- await syncCategoryAssignments(relationEm, relationRecord, before.categoryIds);
1742
- await syncProductTags(relationEm, relationRecord, before.tags);
1743
- await relationEm.flush();
1744
- }
1753
+ await withAtomicFlush(
1754
+ em,
1755
+ [
1756
+ () => em.flush(),
1757
+ () => restoreOffersFromSnapshot(em, record, before.offers),
1758
+ () => syncCategoryAssignments(em, record, before.categoryIds),
1759
+ () => syncProductTags(em, record, before.tags),
1760
+ ],
1761
+ { transaction: true },
1762
+ );
1745
1763
  const dataEngine = ctx.container.resolve("dataEngine") as DataEngine;
1746
1764
  const resetValues = buildCustomFieldResetMap(
1747
1765
  before.custom ?? undefined,
@@ -1803,23 +1821,38 @@ const deleteProductCommand: CommandHandler<
1803
1821
  ensureTenantScope(ctx, record.tenantId);
1804
1822
  ensureOrganizationScope(ctx, record.organizationId);
1805
1823
  const dataEngine = ctx.container.resolve("dataEngine") as DataEngine;
1806
- await deleteProductVariantsAndRelatedData({
1824
+ const { variants, cleanupEntries } = await collectProductVariantCleanup(
1807
1825
  em,
1808
- product: record,
1826
+ record,
1827
+ );
1828
+ await withAtomicFlush(
1829
+ em,
1830
+ [
1831
+ () => removeProductVariants(em, variants),
1832
+ async () => {
1833
+ await em.nativeDelete(CatalogProductPrice, { product: record.id });
1834
+ },
1835
+ async () => {
1836
+ const templateToRemove = await resolveOptionSchemaTemplateForRemoval(
1837
+ em,
1838
+ record,
1839
+ );
1840
+ if (templateToRemove) {
1841
+ record.optionSchemaTemplate = null;
1842
+ em.remove(templateToRemove);
1843
+ }
1844
+ },
1845
+ () => {
1846
+ em.remove(record);
1847
+ },
1848
+ ],
1849
+ { transaction: true },
1850
+ );
1851
+ await emitProductVariantCleanupSideEffects({
1809
1852
  dataEngine,
1810
1853
  ctx,
1854
+ cleanupEntries,
1811
1855
  });
1812
- await em.nativeDelete(CatalogProductPrice, { product: record.id });
1813
- const templateToRemove = await resolveOptionSchemaTemplateForRemoval(
1814
- em,
1815
- record,
1816
- );
1817
- if (templateToRemove) {
1818
- record.optionSchemaTemplate = null;
1819
- em.remove(templateToRemove);
1820
- }
1821
- em.remove(record);
1822
- await em.flush();
1823
1856
  if (snapshot?.custom && Object.keys(snapshot.custom).length) {
1824
1857
  const resetValues = buildCustomFieldResetMap(snapshot.custom, undefined);
1825
1858
  if (Object.keys(resetValues).length) {
@@ -1908,16 +1941,16 @@ const deleteProductCommand: CommandHandler<
1908
1941
  ensureTenantScope(ctx, before.tenantId);
1909
1942
  ensureOrganizationScope(ctx, before.organizationId);
1910
1943
  applyProductSnapshot(em, record, before);
1911
- await em.flush();
1912
-
1913
- const relationEm = em.fork();
1914
- const relationRecord = await findOneWithDecryption(relationEm, CatalogProduct, { id: before.id });
1915
- if (relationRecord) {
1916
- await restoreOffersFromSnapshot(relationEm, relationRecord, before.offers);
1917
- await syncCategoryAssignments(relationEm, relationRecord, before.categoryIds);
1918
- await syncProductTags(relationEm, relationRecord, before.tags);
1919
- await relationEm.flush();
1920
- }
1944
+ await withAtomicFlush(
1945
+ em,
1946
+ [
1947
+ () => em.flush(),
1948
+ () => restoreOffersFromSnapshot(em, record, before.offers),
1949
+ () => syncCategoryAssignments(em, record, before.categoryIds),
1950
+ () => syncProductTags(em, record, before.tags),
1951
+ ],
1952
+ { transaction: true },
1953
+ );
1921
1954
  const dataEngine = ctx.container.resolve("dataEngine") as DataEngine;
1922
1955
  if (before.custom && Object.keys(before.custom).length) {
1923
1956
  await setCustomFieldsIfAny({
@@ -6,6 +6,7 @@ import { UniqueConstraintViolationException } from '@mikro-orm/core'
6
6
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
7
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
8
8
  import { loadCustomFieldSnapshot, buildCustomFieldResetMap } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
9
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
9
10
  import { E } from '#generated/entities.ids.generated'
10
11
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
12
  import {
@@ -588,21 +589,25 @@ const createVariantCommand: CommandHandler<VariantCreateInput, { variantId: stri
588
589
  updatedAt: now,
589
590
  })
590
591
  em.persist(record)
592
+ let previousDefaultVariantId: string | null = null
591
593
  try {
592
- await em.flush()
594
+ await withAtomicFlush(
595
+ em,
596
+ [
597
+ () => em.flush(),
598
+ async () => {
599
+ if (record.isDefault) {
600
+ previousDefaultVariantId = await enforceSingleDefaultVariant(em, record)
601
+ await em.flush()
602
+ }
603
+ },
604
+ () => aggregateVariantMediaToProduct(em, record),
605
+ ],
606
+ { transaction: true }
607
+ )
593
608
  } catch (error) {
594
609
  await rethrowVariantUniqueConstraint(error)
595
610
  }
596
- let previousDefaultVariantId: string | null = null
597
- if (record.isDefault) {
598
- previousDefaultVariantId = await enforceSingleDefaultVariant(em, record)
599
- try {
600
- await em.flush()
601
- } catch (error) {
602
- await rethrowVariantUniqueConstraint(error)
603
- }
604
- }
605
- await aggregateVariantMediaToProduct(em, record)
606
611
  await setCustomFieldsIfAny({
607
612
  dataEngine: ctx.container.resolve('dataEngine'),
608
613
  entityId: E.catalog.catalog_product_variant,
@@ -753,15 +758,23 @@ const updateVariantCommand: CommandHandler<VariantUpdateInput, { variantId: stri
753
758
  }
754
759
 
755
760
  let previousDefaultVariantId: string | null = null
756
- if (parsed.isDefault === true) {
757
- previousDefaultVariantId = await enforceSingleDefaultVariant(em, record)
758
- }
759
761
  try {
760
- await em.flush()
762
+ await withAtomicFlush(
763
+ em,
764
+ [
765
+ async () => {
766
+ if (parsed.isDefault === true) {
767
+ previousDefaultVariantId = await enforceSingleDefaultVariant(em, record)
768
+ }
769
+ },
770
+ () => em.flush(),
771
+ () => aggregateVariantMediaToProduct(em, record),
772
+ ],
773
+ { transaction: true }
774
+ )
761
775
  } catch (error) {
762
776
  await rethrowVariantUniqueConstraint(error)
763
777
  }
764
- await aggregateVariantMediaToProduct(em, record)
765
778
  if (custom && Object.keys(custom).length) {
766
779
  await setCustomFieldsIfAny({
767
780
  dataEngine: ctx.container.resolve('dataEngine'),