@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
@@ -2480,6 +2480,70 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
2480
2480
  "updated_at": "updated_at",
2481
2481
  "deleted_at": "deleted_at"
2482
2482
  },
2483
+ "staff_time_entry": {
2484
+ "id": "id",
2485
+ "tenant_id": "tenant_id",
2486
+ "organization_id": "organization_id",
2487
+ "staff_member_id": "staff_member_id",
2488
+ "date": "date",
2489
+ "duration_minutes": "duration_minutes",
2490
+ "started_at": "started_at",
2491
+ "ended_at": "ended_at",
2492
+ "notes": "notes",
2493
+ "time_project_id": "time_project_id",
2494
+ "customer_id": "customer_id",
2495
+ "deal_id": "deal_id",
2496
+ "order_id": "order_id",
2497
+ "source": "source",
2498
+ "created_at": "created_at",
2499
+ "updated_at": "updated_at",
2500
+ "deleted_at": "deleted_at"
2501
+ },
2502
+ "staff_time_entry_segment": {
2503
+ "id": "id",
2504
+ "tenant_id": "tenant_id",
2505
+ "organization_id": "organization_id",
2506
+ "time_entry_id": "time_entry_id",
2507
+ "started_at": "started_at",
2508
+ "ended_at": "ended_at",
2509
+ "segment_type": "segment_type",
2510
+ "created_at": "created_at",
2511
+ "updated_at": "updated_at",
2512
+ "deleted_at": "deleted_at"
2513
+ },
2514
+ "staff_time_project": {
2515
+ "id": "id",
2516
+ "tenant_id": "tenant_id",
2517
+ "organization_id": "organization_id",
2518
+ "name": "name",
2519
+ "customer_id": "customer_id",
2520
+ "code": "code",
2521
+ "description": "description",
2522
+ "project_type": "project_type",
2523
+ "color": "color",
2524
+ "status": "status",
2525
+ "owner_user_id": "owner_user_id",
2526
+ "cost_center": "cost_center",
2527
+ "start_date": "start_date",
2528
+ "created_at": "created_at",
2529
+ "updated_at": "updated_at",
2530
+ "deleted_at": "deleted_at"
2531
+ },
2532
+ "staff_time_project_member": {
2533
+ "id": "id",
2534
+ "tenant_id": "tenant_id",
2535
+ "organization_id": "organization_id",
2536
+ "time_project_id": "time_project_id",
2537
+ "staff_member_id": "staff_member_id",
2538
+ "role": "role",
2539
+ "status": "status",
2540
+ "show_in_grid": "show_in_grid",
2541
+ "assigned_start_date": "assigned_start_date",
2542
+ "assigned_end_date": "assigned_end_date",
2543
+ "created_at": "created_at",
2544
+ "updated_at": "updated_at",
2545
+ "deleted_at": "deleted_at"
2546
+ },
2483
2547
  "step_instance": {
2484
2548
  "id": "id",
2485
2549
  "workflow_instance_id": "workflow_instance_id",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.4-develop.4217.1.c9aa050183",
3
+ "version": "0.6.4-develop.4239.1.4a264a5828",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.4-develop.4217.1.c9aa050183",
247
- "@open-mercato/shared": "0.6.4-develop.4217.1.c9aa050183",
248
- "@open-mercato/ui": "0.6.4-develop.4217.1.c9aa050183",
246
+ "@open-mercato/ai-assistant": "0.6.4-develop.4239.1.4a264a5828",
247
+ "@open-mercato/shared": "0.6.4-develop.4239.1.4a264a5828",
248
+ "@open-mercato/ui": "0.6.4-develop.4239.1.4a264a5828",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.4-develop.4217.1.c9aa050183",
254
- "@open-mercato/shared": "0.6.4-develop.4217.1.c9aa050183",
255
- "@open-mercato/ui": "0.6.4-develop.4217.1.c9aa050183",
253
+ "@open-mercato/ai-assistant": "0.6.4-develop.4239.1.4a264a5828",
254
+ "@open-mercato/shared": "0.6.4-develop.4239.1.4a264a5828",
255
+ "@open-mercato/ui": "0.6.4-develop.4239.1.4a264a5828",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -0,0 +1,61 @@
1
+ import { expect, type APIRequestContext } from '@playwright/test'
2
+ import { apiRequest } from './api'
3
+ import { deleteStaffEntityIfExists } from './staffFixtures'
4
+
5
+ export async function createTimeProjectFixture(
6
+ request: APIRequestContext,
7
+ token: string,
8
+ input?: { name?: string; code?: string },
9
+ ): Promise<string> {
10
+ const response = await apiRequest(request, 'POST', '/api/staff/timesheets/time-projects', {
11
+ token,
12
+ data: {
13
+ name: input?.name ?? `QA Project ${Date.now()}`,
14
+ code: input?.code ?? `QA-${Date.now()}`,
15
+ projectType: 'internal',
16
+ status: 'active',
17
+ },
18
+ })
19
+ expect(response.ok(), `Failed to create time project fixture: ${response.status()}`).toBeTruthy()
20
+ const body = (await response.json()) as { id?: string }
21
+ expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy()
22
+ return body.id as string
23
+ }
24
+
25
+ export async function assignEmployeeToProjectFixture(
26
+ request: APIRequestContext,
27
+ token: string,
28
+ projectId: string,
29
+ staffMemberId: string,
30
+ ): Promise<string> {
31
+ const response = await apiRequest(request, 'POST', `/api/staff/timesheets/time-projects/${projectId}/employees`, {
32
+ token,
33
+ data: { staffMemberId, status: 'active', assignedStartDate: new Date().toISOString().slice(0, 10) },
34
+ })
35
+ expect(response.ok(), `Failed to assign employee to project: ${response.status()}`).toBeTruthy()
36
+ const body = (await response.json()) as { id?: string }
37
+ return body.id ?? ''
38
+ }
39
+
40
+ export async function createTimeEntryFixture(
41
+ request: APIRequestContext,
42
+ token: string,
43
+ input: { staffMemberId: string; timeProjectId: string; date: string; durationMinutes: number },
44
+ ): Promise<string> {
45
+ const response = await apiRequest(request, 'POST', '/api/staff/timesheets/time-entries', {
46
+ token,
47
+ data: {
48
+ staffMemberId: input.staffMemberId,
49
+ timeProjectId: input.timeProjectId,
50
+ date: input.date,
51
+ durationMinutes: input.durationMinutes,
52
+ source: 'manual',
53
+ },
54
+ })
55
+ expect(response.ok(), `Failed to create time entry fixture: ${response.status()}`).toBeTruthy()
56
+ const body = (await response.json()) as { id?: string }
57
+ expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy()
58
+ return body.id as string
59
+ }
60
+
61
+ export { deleteStaffEntityIfExists }
@@ -163,22 +163,25 @@ export async function PATCH(req: NextRequest, ctx: RouteContext) {
163
163
  if (parsed.data.tags) patch.tags = normalizeAttachmentTags(parsed.data.tags)
164
164
  if (parsed.data.assignments) patch.assignments = normalizeAttachmentAssignments(parsed.data.assignments)
165
165
  record.storageMetadata = mergeAttachmentMetadata(record.storageMetadata, patch)
166
- await em.flush()
167
-
168
- if (dataEngine && custom && Object.keys(custom).length) {
169
- try {
170
- await setCustomFieldsIfAny({
171
- dataEngine,
172
- entityId: E.attachments.attachment,
173
- recordId: record.id,
174
- tenantId: record.tenantId ?? auth.tenantId ?? null,
175
- organizationId: record.organizationId ?? auth.orgId ?? null,
176
- values: custom,
177
- })
178
- } catch (error) {
179
- console.error('[attachments] failed to persist custom attributes', error)
180
- return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })
181
- }
166
+ // Commit the metadata mutation and the custom-field write atomically so a
167
+ // custom-field failure cannot leave the attachment metadata partially updated.
168
+ try {
169
+ await em.transactional(async (tx) => {
170
+ await tx.flush()
171
+ if (dataEngine && custom && Object.keys(custom).length) {
172
+ await setCustomFieldsIfAny({
173
+ dataEngine,
174
+ entityId: E.attachments.attachment,
175
+ recordId: record.id,
176
+ tenantId: record.tenantId ?? auth.tenantId ?? null,
177
+ organizationId: record.organizationId ?? auth.orgId ?? null,
178
+ values: custom,
179
+ })
180
+ }
181
+ })
182
+ } catch (error) {
183
+ console.error('[attachments] failed to persist custom attributes', error)
184
+ return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })
182
185
  }
183
186
 
184
187
  if (dataEngine) {
@@ -242,8 +245,12 @@ export async function DELETE(req: NextRequest, ctx: RouteContext) {
242
245
  tenantId: record.tenantId ?? auth.tenantId,
243
246
  organizationId: record.organizationId ?? auth.orgId,
244
247
  })
245
- await deleteDriver.delete(record.partitionCode, record.storagePath)
248
+ // Commit the DB row removal before deleting the irreversible storage file so a
249
+ // failed commit cannot leave a dangling record whose backing file is already gone.
250
+ const recordPartitionCode = record.partitionCode
251
+ const recordStoragePath = record.storagePath
246
252
  await em.remove(record).flush()
253
+ await deleteDriver.delete(recordPartitionCode, recordStoragePath)
247
254
 
248
255
  if (dataEngine) {
249
256
  await emitCrudSideEffects({
@@ -437,7 +437,26 @@ export async function POST(req: Request) {
437
437
  content: extractedContent,
438
438
  storageMetadata: metadata,
439
439
  })
440
- await em.persist(att).flush()
440
+ // Persist the attachment row and its custom-field values atomically so a
441
+ // custom-field failure cannot leave behind a committed orphan attachment.
442
+ try {
443
+ await em.transactional(async (tx) => {
444
+ await tx.persist(att).flush()
445
+ if (dataEngine) {
446
+ await setCustomFieldsIfAny({
447
+ dataEngine,
448
+ entityId: E.attachments.attachment,
449
+ recordId: attachmentId,
450
+ tenantId,
451
+ organizationId: orgId,
452
+ values: customFieldValues,
453
+ })
454
+ }
455
+ })
456
+ } catch (error) {
457
+ console.error('[attachments] failed to persist attachment with custom attributes', error)
458
+ return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })
459
+ }
441
460
 
442
461
  if (useLlmOcr) {
443
462
  requestOcrProcessing(em, att, uploadDriver, storedPath).catch((error) => {
@@ -448,19 +467,6 @@ export async function POST(req: Request) {
448
467
  }
449
468
 
450
469
  if (dataEngine) {
451
- try {
452
- await setCustomFieldsIfAny({
453
- dataEngine,
454
- entityId: E.attachments.attachment,
455
- recordId: attachmentId,
456
- tenantId,
457
- organizationId: orgId,
458
- values: customFieldValues,
459
- })
460
- } catch (error) {
461
- console.error('[attachments] failed to persist custom attributes', error)
462
- return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })
463
- }
464
470
  await emitCrudSideEffects({
465
471
  dataEngine,
466
472
  action: 'created',
@@ -9,6 +9,7 @@ import { RoleAcl, Role } from '@open-mercato/core/modules/auth/data/entities'
9
9
  import type { EntityManager } from '@mikro-orm/postgresql'
10
10
  import { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
11
11
  import { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
12
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
12
13
  import {
13
14
  assertActorCanGrantAcl,
14
15
  assertActorCanModifySuperAdminRoleTarget,
@@ -208,11 +209,16 @@ export async function PUT(req: Request) {
208
209
  throw err
209
210
  }
210
211
 
211
- acl.organizationsJson = requestedOrganizations
212
- acl.isSuperAdmin = requestedIsSuperAdmin
213
- acl.featuresJson = requestedFeatures
214
- await em.persist(acl).flush()
215
-
212
+ const aclToPersist = acl
213
+ await withAtomicFlush(em, [
214
+ () => {
215
+ aclToPersist.organizationsJson = requestedOrganizations
216
+ aclToPersist.isSuperAdmin = requestedIsSuperAdmin
217
+ aclToPersist.featuresJson = requestedFeatures
218
+ em.persist(aclToPersist)
219
+ },
220
+ ], { transaction: true })
221
+
216
222
  // Invalidate cache for all users in this tenant since role ACL changed
217
223
  if (targetTenantId) {
218
224
  await rbacService.invalidateTenantCache(targetTenantId)
@@ -15,6 +15,7 @@ import {
15
15
  saveSidebarPreference,
16
16
  } from '../../../services/sidebarPreferencesService'
17
17
  import { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'
18
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
18
19
  import { Role, RoleSidebarPreference } from '../../../data/entities'
19
20
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
20
21
  import { z } from 'zod'
@@ -376,38 +377,46 @@ export async function PUT(req: Request) {
376
377
  : []
377
378
  const roleMap = new Map<string, Role>(availableRoles.map((role: Role) => [String(role.id), role]))
378
379
 
379
- const updatedRoleIds: string[] = []
380
380
  if (applyToRoles.length > 0) {
381
381
  const missing = applyToRoles.filter((id) => !roleMap.has(id))
382
382
  if (missing.length) {
383
383
  return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })
384
384
  }
385
- for (const roleId of applyToRoles) {
386
- const role = roleMap.get(roleId)!
387
- await saveRoleSidebarPreference(em, {
388
- roleId: role.id,
389
- tenantId: auth.tenantId ?? null,
390
- locale,
391
- }, payload)
392
- updatedRoleIds.push(role.id)
393
- }
394
385
  }
395
386
 
396
- const filteredClearRoleIds = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))
387
+ const updatedRoleIds: string[] = []
388
+ const filteredClearRoleIds: string[] = []
389
+ await withAtomicFlush(em, [
390
+ async () => {
391
+ for (const roleId of applyToRoles) {
392
+ const role = roleMap.get(roleId)!
393
+ await saveRoleSidebarPreference(em, {
394
+ roleId: role.id,
395
+ tenantId: auth.tenantId ?? null,
396
+ locale,
397
+ }, payload)
398
+ updatedRoleIds.push(role.id)
399
+ }
397
400
 
398
- if (filteredClearRoleIds.length > 0) {
399
- // Cross-locale: role preferences are unique per (role, tenantId); keep the delete
400
- // filter aligned with save/load helpers so a clear from one locale does not leave
401
- // a row created under another locale orphaned.
402
- await em.nativeDelete(RoleSidebarPreference, {
403
- role: { $in: filteredClearRoleIds },
404
- tenantId: auth.tenantId ?? null,
405
- })
406
- if (cache?.deleteByTags) {
407
- try {
408
- await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))
409
- } catch {}
410
- }
401
+ const clearTargets = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))
402
+ filteredClearRoleIds.push(...clearTargets)
403
+
404
+ if (filteredClearRoleIds.length > 0) {
405
+ // Cross-locale: role preferences are unique per (role, tenantId); keep the delete
406
+ // filter aligned with save/load helpers so a clear from one locale does not leave
407
+ // a row created under another locale orphaned.
408
+ await em.nativeDelete(RoleSidebarPreference, {
409
+ role: { $in: filteredClearRoleIds },
410
+ tenantId: auth.tenantId ?? null,
411
+ })
412
+ }
413
+ },
414
+ ], { transaction: true })
415
+
416
+ if (filteredClearRoleIds.length > 0 && cache?.deleteByTags) {
417
+ try {
418
+ await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))
419
+ } catch {}
411
420
  }
412
421
 
413
422
  if (cache?.deleteByTags) {
@@ -6,6 +6,7 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { logCrudAccess } from '@open-mercato/shared/lib/crud/factory'
7
7
  import { forbidden, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
8
  import { UserAcl } from '@open-mercato/core/modules/auth/data/entities'
9
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
9
10
  import { assertActorCanModifySuperAdminUserTarget } from '@open-mercato/core/modules/auth/lib/grantChecks'
10
11
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
11
12
  import type { EntityManager } from '@mikro-orm/postgresql'
@@ -150,18 +151,22 @@ export async function PUT(req: Request) {
150
151
 
151
152
  const hasCustomAcl = effectiveIsSuperAdmin || effectiveFeatures.length > 0
152
153
 
153
- if (!hasCustomAcl) {
154
- if (acl) await em.remove(acl).flush()
155
- } else {
156
- if (!acl) {
157
- acl = em.create(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })
158
- }
159
- const aclRecord = acl as any
160
- aclRecord.isSuperAdmin = effectiveIsSuperAdmin
161
- aclRecord.featuresJson = effectiveFeatures
162
- aclRecord.organizationsJson = organizations
163
- await em.persist(acl).flush()
164
- }
154
+ await withAtomicFlush(em, [
155
+ () => {
156
+ if (!hasCustomAcl) {
157
+ if (acl) em.remove(acl)
158
+ } else {
159
+ if (!acl) {
160
+ acl = em.create(UserAcl, { user: parsed.data.userId as any, tenantId: auth.tenantId as any })
161
+ }
162
+ const aclRecord = acl as any
163
+ aclRecord.isSuperAdmin = effectiveIsSuperAdmin
164
+ aclRecord.featuresJson = effectiveFeatures
165
+ aclRecord.organizationsJson = organizations
166
+ em.persist(acl)
167
+ }
168
+ },
169
+ ], { transaction: true })
165
170
 
166
171
  // Invalidate cache for this user
167
172
  await rbacService.invalidateUserCache(parsed.data.userId)
@@ -25,6 +25,7 @@ import {
25
25
  diffCustomFieldChanges,
26
26
  } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
27
27
  import { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'
28
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
28
29
  import { normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'
29
30
  import { computeEmailHash } from '@open-mercato/core/modules/auth/lib/emailHash'
30
31
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -300,31 +301,37 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
300
301
  if (!userId) return
301
302
  const snapshot = logEntry?.snapshotAfter as SerializedUser | undefined
302
303
  const em = (ctx.container.resolve('em') as EntityManager)
303
- await em.nativeDelete(UserAcl, { user: userId })
304
- await em.nativeDelete(UserRole, { user: userId })
305
- await em.nativeDelete(Session, { user: userId })
306
- await em.nativeDelete(PasswordReset, { user: userId })
307
-
308
304
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
309
- if (snapshot?.custom && Object.keys(snapshot.custom).length) {
310
- const reset = buildCustomFieldResetMap(undefined, snapshot.custom)
311
- if (Object.keys(reset).length) {
312
- await setCustomFieldsIfAny({
313
- dataEngine: de,
314
- entityId: E.auth.user,
315
- recordId: userId,
316
- organizationId: snapshot.organizationId,
317
- tenantId: snapshot.tenantId,
318
- values: reset,
319
- notify: false,
305
+
306
+ let removed: User | null = null
307
+ await withAtomicFlush(em, [
308
+ async () => {
309
+ await em.nativeDelete(UserAcl, { user: userId })
310
+ await em.nativeDelete(UserRole, { user: userId })
311
+ await em.nativeDelete(Session, { user: userId })
312
+ await em.nativeDelete(PasswordReset, { user: userId })
313
+
314
+ if (snapshot?.custom && Object.keys(snapshot.custom).length) {
315
+ const reset = buildCustomFieldResetMap(undefined, snapshot.custom)
316
+ if (Object.keys(reset).length) {
317
+ await setCustomFieldsIfAny({
318
+ dataEngine: de,
319
+ entityId: E.auth.user,
320
+ recordId: userId,
321
+ organizationId: snapshot.organizationId,
322
+ tenantId: snapshot.tenantId,
323
+ values: reset,
324
+ notify: false,
325
+ })
326
+ }
327
+ }
328
+ removed = await de.deleteOrmEntity({
329
+ entity: User,
330
+ where: { id: userId, deletedAt: null } as FilterQuery<User>,
331
+ soft: false,
320
332
  })
321
- }
322
- }
323
- const removed = await de.deleteOrmEntity({
324
- entity: User,
325
- where: { id: userId, deletedAt: null } as FilterQuery<User>,
326
- soft: false,
327
- })
333
+ },
334
+ ], { transaction: true })
328
335
 
329
336
  await emitCrudUndoSideEffects({
330
337
  dataEngine: de,
@@ -648,19 +655,24 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
648
655
  async execute(input, ctx) {
649
656
  const id = requireId(input, 'User id required')
650
657
  const em = (ctx.container.resolve('em') as EntityManager)
651
-
652
- await em.nativeDelete(UserAcl, { user: id })
653
- await em.nativeDelete(UserRole, { user: id })
654
- await em.nativeDelete(Session, { user: id })
655
- await em.nativeDelete(PasswordReset, { user: id })
656
-
657
658
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
658
- const user = await de.deleteOrmEntity({
659
- entity: User,
660
- where: { id, deletedAt: null } as FilterQuery<User>,
661
- soft: false,
662
- })
663
- if (!user) throw new CrudHttpError(404, { error: 'User not found' })
659
+
660
+ let user!: User
661
+ await withAtomicFlush(em, [
662
+ async () => {
663
+ await em.nativeDelete(UserAcl, { user: id })
664
+ await em.nativeDelete(UserRole, { user: id })
665
+ await em.nativeDelete(Session, { user: id })
666
+ await em.nativeDelete(PasswordReset, { user: id })
667
+ const removed = await de.deleteOrmEntity({
668
+ entity: User,
669
+ where: { id, deletedAt: null } as FilterQuery<User>,
670
+ soft: false,
671
+ })
672
+ if (!removed) throw new CrudHttpError(404, { error: 'User not found' })
673
+ user = removed
674
+ },
675
+ ], { transaction: true })
664
676
 
665
677
  await emitCrudSideEffects({
666
678
  dataEngine: de,
@@ -707,51 +719,55 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
707
719
  let user = await findOneWithDecryption(em, User, { id: before.id }, {}, { tenantId: null, organizationId: null })
708
720
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
709
721
 
710
- if (user) {
711
- if (user.deletedAt) {
712
- user.deletedAt = null
713
- }
714
- user.email = before.email
715
- user.organizationId = before.organizationId ?? null
716
- user.tenantId = before.tenantId ?? null
717
- user.passwordHash = before.passwordHash ?? null
718
- user.name = before.name ?? null
719
- user.isConfirmed = before.isConfirmed
720
- await em.flush()
721
- } else {
722
- user = await de.createOrmEntity({
723
- entity: User,
724
- data: {
725
- id: before.id,
726
- email: before.email,
727
- organizationId: before.organizationId ?? null,
728
- tenantId: before.tenantId ?? null,
729
- passwordHash: before.passwordHash ?? null,
730
- name: before.name ?? null,
731
- isConfirmed: before.isConfirmed,
732
- },
733
- })
734
- }
735
-
736
- if (!user) return
737
-
738
- await em.nativeDelete(UserRole, { user: before.id })
739
- await syncUserRoles(em, user, before.roles, before.tenantId)
740
-
741
- await restoreUserAcls(em, user, before.acls)
742
-
743
- const reset = buildCustomFieldResetMap(before.custom, undefined)
744
- if (Object.keys(reset).length) {
745
- await setCustomFieldsIfAny({
746
- dataEngine: de,
747
- entityId: E.auth.user,
748
- recordId: before.id,
749
- organizationId: before.organizationId ?? null,
750
- tenantId: before.tenantId ?? null,
751
- values: reset,
752
- notify: false,
753
- })
754
- }
722
+ await withAtomicFlush(em, [
723
+ async () => {
724
+ if (user) {
725
+ if (user.deletedAt) {
726
+ user.deletedAt = null
727
+ }
728
+ user.email = before.email
729
+ user.organizationId = before.organizationId ?? null
730
+ user.tenantId = before.tenantId ?? null
731
+ user.passwordHash = before.passwordHash ?? null
732
+ user.name = before.name ?? null
733
+ user.isConfirmed = before.isConfirmed
734
+ await em.flush()
735
+ } else {
736
+ user = await de.createOrmEntity({
737
+ entity: User,
738
+ data: {
739
+ id: before.id,
740
+ email: before.email,
741
+ organizationId: before.organizationId ?? null,
742
+ tenantId: before.tenantId ?? null,
743
+ passwordHash: before.passwordHash ?? null,
744
+ name: before.name ?? null,
745
+ isConfirmed: before.isConfirmed,
746
+ },
747
+ })
748
+ }
749
+
750
+ if (!user) return
751
+
752
+ await em.nativeDelete(UserRole, { user: before.id })
753
+ await syncUserRoles(em, user, before.roles, before.tenantId)
754
+
755
+ await restoreUserAcls(em, user, before.acls)
756
+
757
+ const reset = buildCustomFieldResetMap(before.custom, undefined)
758
+ if (Object.keys(reset).length) {
759
+ await setCustomFieldsIfAny({
760
+ dataEngine: de,
761
+ entityId: E.auth.user,
762
+ recordId: before.id,
763
+ organizationId: before.organizationId ?? null,
764
+ tenantId: before.tenantId ?? null,
765
+ values: reset,
766
+ notify: false,
767
+ })
768
+ }
769
+ },
770
+ ], { transaction: true })
755
771
 
756
772
  await invalidateUserCache(ctx, before.id)
757
773
  },