@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
@@ -294,35 +294,36 @@ const createPersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkCreateInpu
294
294
  )
295
295
  if (!link) return
296
296
 
297
- link.isPrimary = false
298
- link.deletedAt = new Date()
299
- await em.flush()
300
-
301
- const person = await findOneWithDecryption(
302
- em,
303
- CustomerEntity,
304
- { id: after.personEntityId, kind: 'person', tenantId: after.tenantId, organizationId: after.organizationId, deletedAt: null },
305
- undefined,
306
- { tenantId: after.tenantId, organizationId: after.organizationId },
307
- )
308
- if (person) {
309
- const profile = await findOneWithDecryption(
310
- em,
311
- CustomerPersonProfile,
312
- { entity: person },
313
- { populate: ['company'] },
314
- { tenantId: person.tenantId, organizationId: person.organizationId },
315
- )
316
- if (profile) {
317
- const remainingLinks = await loadPersonCompanyLinks(em, person)
318
- if (after.isPrimary) {
319
- await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, after.companyEntityId)
320
- } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === after.companyEntityId) {
321
- profile.company = null
297
+ await withAtomicFlush(em, [
298
+ async () => {
299
+ link.isPrimary = false
300
+ link.deletedAt = new Date()
301
+ const person = await findOneWithDecryption(
302
+ em,
303
+ CustomerEntity,
304
+ { id: after.personEntityId, kind: 'person', tenantId: after.tenantId, organizationId: after.organizationId, deletedAt: null },
305
+ undefined,
306
+ { tenantId: after.tenantId, organizationId: after.organizationId },
307
+ )
308
+ if (person) {
309
+ const profile = await findOneWithDecryption(
310
+ em,
311
+ CustomerPersonProfile,
312
+ { entity: person },
313
+ { populate: ['company'] },
314
+ { tenantId: person.tenantId, organizationId: person.organizationId },
315
+ )
316
+ if (profile) {
317
+ const remainingLinks = await loadPersonCompanyLinks(em, person)
318
+ if (after.isPrimary) {
319
+ await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, after.companyEntityId)
320
+ } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === after.companyEntityId) {
321
+ profile.company = null
322
+ }
323
+ }
322
324
  }
323
- await em.flush()
324
- }
325
- }
325
+ },
326
+ ], { transaction: true })
326
327
 
327
328
  const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
328
329
  await emitCrudUndoSideEffects({
@@ -376,21 +377,24 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
376
377
  const profile = await requirePersonProfile(em, person)
377
378
  const linkedCompany = await requireCompanyEntity(em, companyId, parsed.tenantId, parsed.organizationId)
378
379
 
379
- if (parsed.isPrimary) {
380
- await clearPrimaryFlagsForPerson(em, person)
381
- link.isPrimary = true
382
- profile.company = linkedCompany
383
- } else if (!parsed.isPrimary) {
384
- const linkWasPrimary = link.isPrimary
385
- link.isPrimary = false
386
- if (linkWasPrimary) {
387
- const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)
388
- await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)
389
- } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {
390
- profile.company = null
391
- }
392
- }
393
- await em.flush()
380
+ await withAtomicFlush(em, [
381
+ async () => {
382
+ if (parsed.isPrimary) {
383
+ await clearPrimaryFlagsForPerson(em, person)
384
+ link.isPrimary = true
385
+ profile.company = linkedCompany
386
+ } else if (!parsed.isPrimary) {
387
+ const linkWasPrimary = link.isPrimary
388
+ link.isPrimary = false
389
+ if (linkWasPrimary) {
390
+ const remainingLinks = (await loadPersonCompanyLinks(em, person)).filter((entry) => entry.id !== link.id)
391
+ await promoteFallbackPrimaryLink(em, person, profile, remainingLinks, companyId)
392
+ } else if (profile.company && typeof profile.company !== 'string' && profile.company.id === companyId) {
393
+ profile.company = null
394
+ }
395
+ }
396
+ },
397
+ ], { transaction: true })
394
398
 
395
399
  const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
396
400
  await emitCrudSideEffects({
@@ -449,39 +453,42 @@ const updatePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkUpdateInpu
449
453
  )
450
454
  if (!link) return
451
455
 
452
- const person = await findOneWithDecryption(
453
- em,
454
- CustomerEntity,
455
- { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
456
- undefined,
457
- { tenantId: before.tenantId, organizationId: before.organizationId },
458
- )
459
- if (person) {
460
- const profile = await findOneWithDecryption(
461
- em,
462
- CustomerPersonProfile,
463
- { entity: person },
464
- { populate: ['company'] },
465
- { tenantId: person.tenantId, organizationId: person.organizationId },
466
- )
467
- if (profile) {
468
- if (before.isPrimary) {
469
- await clearPrimaryFlagsForPerson(em, person)
470
- link.isPrimary = true
471
- const company = await findOneWithDecryption(
456
+ await withAtomicFlush(em, [
457
+ async () => {
458
+ const person = await findOneWithDecryption(
459
+ em,
460
+ CustomerEntity,
461
+ { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
462
+ undefined,
463
+ { tenantId: before.tenantId, organizationId: before.organizationId },
464
+ )
465
+ if (person) {
466
+ const profile = await findOneWithDecryption(
472
467
  em,
473
- CustomerEntity,
474
- { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
475
- undefined,
476
- { tenantId: before.tenantId, organizationId: before.organizationId },
468
+ CustomerPersonProfile,
469
+ { entity: person },
470
+ { populate: ['company'] },
471
+ { tenantId: person.tenantId, organizationId: person.organizationId },
477
472
  )
478
- if (company) profile.company = company
479
- } else {
480
- link.isPrimary = false
473
+ if (profile) {
474
+ if (before.isPrimary) {
475
+ await clearPrimaryFlagsForPerson(em, person)
476
+ link.isPrimary = true
477
+ const company = await findOneWithDecryption(
478
+ em,
479
+ CustomerEntity,
480
+ { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
481
+ undefined,
482
+ { tenantId: before.tenantId, organizationId: before.organizationId },
483
+ )
484
+ if (company) profile.company = company
485
+ } else {
486
+ link.isPrimary = false
487
+ }
488
+ }
481
489
  }
482
- }
483
- }
484
- await em.flush()
490
+ },
491
+ ], { transaction: true })
485
492
 
486
493
  const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
487
494
  await emitCrudUndoSideEffects({
@@ -603,39 +610,41 @@ const deletePersonCompanyLinkCommand: CommandHandler<PersonCompanyLinkDeleteInpu
603
610
  )
604
611
  if (!link) return
605
612
 
606
- link.deletedAt = null
607
- link.isPrimary = before.isPrimary
613
+ await withAtomicFlush(em, [
614
+ async () => {
615
+ link.deletedAt = null
616
+ link.isPrimary = before.isPrimary
608
617
 
609
- const person = await findOneWithDecryption(
610
- em,
611
- CustomerEntity,
612
- { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
613
- undefined,
614
- { tenantId: before.tenantId, organizationId: before.organizationId },
615
- )
616
- if (person && before.isPrimary) {
617
- await clearPrimaryFlagsForPerson(em, person)
618
- link.isPrimary = true
619
- const profile = await findOneWithDecryption(
620
- em,
621
- CustomerPersonProfile,
622
- { entity: person },
623
- { populate: ['company'] },
624
- { tenantId: person.tenantId, organizationId: person.organizationId },
625
- )
626
- if (profile) {
627
- const company = await findOneWithDecryption(
618
+ const person = await findOneWithDecryption(
628
619
  em,
629
620
  CustomerEntity,
630
- { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
621
+ { id: before.personEntityId, kind: 'person', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
631
622
  undefined,
632
623
  { tenantId: before.tenantId, organizationId: before.organizationId },
633
624
  )
634
- if (company) profile.company = company
635
- }
636
- }
637
-
638
- await em.flush()
625
+ if (person && before.isPrimary) {
626
+ await clearPrimaryFlagsForPerson(em, person)
627
+ link.isPrimary = true
628
+ const profile = await findOneWithDecryption(
629
+ em,
630
+ CustomerPersonProfile,
631
+ { entity: person },
632
+ { populate: ['company'] },
633
+ { tenantId: person.tenantId, organizationId: person.organizationId },
634
+ )
635
+ if (profile) {
636
+ const company = await findOneWithDecryption(
637
+ em,
638
+ CustomerEntity,
639
+ { id: before.companyEntityId, kind: 'company', tenantId: before.tenantId, organizationId: before.organizationId, deletedAt: null },
640
+ undefined,
641
+ { tenantId: before.tenantId, organizationId: before.organizationId },
642
+ )
643
+ if (company) profile.company = company
644
+ }
645
+ }
646
+ },
647
+ ], { transaction: true })
639
648
 
640
649
  const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
641
650
  await emitCrudUndoSideEffects({
@@ -15,6 +15,7 @@ import {
15
15
  } from '../data/validators'
16
16
  import { ensureOrganizationScope, ensureTenantScope, ensureDictionaryEntry } from './shared'
17
17
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
18
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
18
19
 
19
20
  const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { stageId: string }> = {
20
21
  id: 'customers.pipeline-stages.create',
@@ -48,9 +49,7 @@ const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { sta
48
49
  ? existingStages.length
49
50
  : Math.max(0, Math.min(requestedOrder, existingStages.length))
50
51
 
51
- // Shift the order of every stage at or after the insert position. We use the
52
- // already-forked EM so the shifts and the new INSERT land in a single `flush()`
53
- // (one transaction by default with MikroORM's `forceTransactions` config). Skipping
52
+ // Shift the order of every stage at or after the insert position. Skipping
54
53
  // this step would either duplicate `order` values (silently corrupting kanban
55
54
  // ordering) or push the new stage to the wrong spot when re-sorting.
56
55
  if (requestedOrder !== undefined) {
@@ -71,18 +70,22 @@ const createPipelineStageCommand: CommandHandler<PipelineStageCreateInput, { sta
71
70
  createdAt: new Date(),
72
71
  updatedAt: new Date(),
73
72
  })
74
- em.persist(stage)
75
- await em.flush()
76
73
 
77
- await ensureDictionaryEntry(em, {
78
- tenantId: parsed.tenantId,
79
- organizationId: parsed.organizationId,
80
- kind: 'pipeline_stage',
81
- value: stage.label,
82
- color: parsed.color,
83
- icon: parsed.icon,
84
- })
85
- await em.flush()
74
+ await withAtomicFlush(em, [
75
+ () => {
76
+ em.persist(stage)
77
+ },
78
+ async () => {
79
+ await ensureDictionaryEntry(em, {
80
+ tenantId: parsed.tenantId,
81
+ organizationId: parsed.organizationId,
82
+ kind: 'pipeline_stage',
83
+ value: stage.label,
84
+ color: parsed.color,
85
+ icon: parsed.icon,
86
+ })
87
+ },
88
+ ], { transaction: true })
86
89
 
87
90
  return { stageId: stage.id }
88
91
  },
@@ -113,19 +116,20 @@ const updatePipelineStageCommand: CommandHandler<PipelineStageUpdateInput, void>
113
116
  if (parsed.order !== undefined) stage.order = parsed.order
114
117
  stage.updatedAt = new Date()
115
118
 
116
- await em.flush()
117
-
118
- if (parsed.label !== undefined || parsed.color !== undefined || parsed.icon !== undefined) {
119
- await ensureDictionaryEntry(em, {
120
- tenantId: stage.tenantId,
121
- organizationId: stage.organizationId,
122
- kind: 'pipeline_stage',
123
- value: stage.label,
124
- color: parsed.color,
125
- icon: parsed.icon,
126
- })
127
- await em.flush()
128
- }
119
+ await withAtomicFlush(em, [
120
+ async () => {
121
+ if (parsed.label !== undefined || parsed.color !== undefined || parsed.icon !== undefined) {
122
+ await ensureDictionaryEntry(em, {
123
+ tenantId: stage.tenantId,
124
+ organizationId: stage.organizationId,
125
+ kind: 'pipeline_stage',
126
+ value: stage.label,
127
+ color: parsed.color,
128
+ icon: parsed.icon,
129
+ })
130
+ }
131
+ },
132
+ ], { transaction: true })
129
133
  },
130
134
  }
131
135
 
@@ -12,6 +12,7 @@ import {
12
12
  } from '../data/validators'
13
13
  import { ensureOrganizationScope, ensureTenantScope } from './shared'
14
14
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
15
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
15
16
 
16
17
  const createPipelineCommand: CommandHandler<PipelineCreateInput, { pipelineId: string }> = {
17
18
  id: 'customers.pipelines.create',
@@ -22,14 +23,6 @@ const createPipelineCommand: CommandHandler<PipelineCreateInput, { pipelineId: s
22
23
 
23
24
  const em = (ctx.container.resolve('em') as EntityManager).fork()
24
25
 
25
- if (parsed.isDefault) {
26
- await em.nativeUpdate(
27
- CustomerPipeline,
28
- { organizationId: parsed.organizationId, tenantId: parsed.tenantId, isDefault: true },
29
- { isDefault: false }
30
- )
31
- }
32
-
33
26
  const pipeline = em.create(CustomerPipeline, {
34
27
  organizationId: parsed.organizationId,
35
28
  tenantId: parsed.tenantId,
@@ -38,8 +31,19 @@ const createPipelineCommand: CommandHandler<PipelineCreateInput, { pipelineId: s
38
31
  createdAt: new Date(),
39
32
  updatedAt: new Date(),
40
33
  })
41
- em.persist(pipeline)
42
- await em.flush()
34
+
35
+ await withAtomicFlush(em, [
36
+ async () => {
37
+ if (parsed.isDefault) {
38
+ await em.nativeUpdate(
39
+ CustomerPipeline,
40
+ { organizationId: parsed.organizationId, tenantId: parsed.tenantId, isDefault: true },
41
+ { isDefault: false }
42
+ )
43
+ }
44
+ em.persist(pipeline)
45
+ },
46
+ ], { transaction: true })
43
47
 
44
48
  return { pipelineId: pipeline.id }
45
49
  },
@@ -57,19 +61,21 @@ const updatePipelineCommand: CommandHandler<PipelineUpdateInput, void> = {
57
61
  ensureTenantScope(ctx, pipeline.tenantId)
58
62
  ensureOrganizationScope(ctx, pipeline.organizationId)
59
63
 
60
- if (parsed.isDefault && !pipeline.isDefault) {
61
- await em.nativeUpdate(
62
- CustomerPipeline,
63
- { organizationId: pipeline.organizationId, tenantId: pipeline.tenantId, isDefault: true },
64
- { isDefault: false }
65
- )
66
- }
67
-
68
- if (parsed.name !== undefined) pipeline.name = parsed.name
69
- if (parsed.isDefault !== undefined) pipeline.isDefault = parsed.isDefault
70
- pipeline.updatedAt = new Date()
71
-
72
- await em.flush()
64
+ await withAtomicFlush(em, [
65
+ async () => {
66
+ if (parsed.isDefault && !pipeline.isDefault) {
67
+ await em.nativeUpdate(
68
+ CustomerPipeline,
69
+ { organizationId: pipeline.organizationId, tenantId: pipeline.tenantId, isDefault: true },
70
+ { isDefault: false }
71
+ )
72
+ }
73
+
74
+ if (parsed.name !== undefined) pipeline.name = parsed.name
75
+ if (parsed.isDefault !== undefined) pipeline.isDefault = parsed.isDefault
76
+ pipeline.updatedAt = new Date()
77
+ },
78
+ ], { transaction: true })
73
79
  },
74
80
  }
75
81
 
@@ -25,6 +25,7 @@ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
25
25
  import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
26
26
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
27
27
  import { emitCustomersEvent } from '../events'
28
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
28
29
 
29
30
  const tagCrudEvents: CrudEventsConfig = {
30
31
  module: 'customers',
@@ -286,9 +287,12 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
286
287
  if (!tag) throw new CrudHttpError(404, { error: 'Tag not found' })
287
288
  ensureTenantScope(ctx, tag.tenantId)
288
289
  ensureOrganizationScope(ctx, tag.organizationId)
289
- await em.nativeDelete(CustomerTagAssignment, { tag })
290
- em.remove(tag)
291
- await em.flush()
290
+ await withAtomicFlush(em, [
291
+ async () => {
292
+ await em.nativeDelete(CustomerTagAssignment, { tag })
293
+ em.remove(tag)
294
+ },
295
+ ], { transaction: true })
292
296
 
293
297
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
294
298
  await emitCrudSideEffects({
@@ -345,7 +349,12 @@ const deleteTagCommand: CommandHandler<{ body?: Record<string, unknown>; query?:
345
349
  tag.color = before.color
346
350
  tag.description = before.description
347
351
  }
348
- await em.flush()
352
+ const restoredTag = tag
353
+ await withAtomicFlush(em, [
354
+ () => {
355
+ em.persist(restoredTag)
356
+ },
357
+ ], { transaction: true })
349
358
 
350
359
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
351
360
  await emitCrudUndoSideEffects({
@@ -471,7 +480,6 @@ const unassignTagCommand: CommandHandler<TagAssignmentInput, { assignmentId: str
471
480
  })
472
481
  if (!existing) throw new CrudHttpError(404, { error: 'Tag assignment not found' })
473
482
  await em.remove(existing).flush()
474
- await em.flush()
475
483
 
476
484
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
477
485
  await emitCrudSideEffects({
@@ -129,7 +129,6 @@ export async function PUT(req: Request) {
129
129
  if (parsed.data.mode === 'inherit') {
130
130
  if (record) {
131
131
  await em.remove(record).flush()
132
- await em.flush()
133
132
  }
134
133
  return NextResponse.json({ ok: true, mode: 'inherit', widgetIds: [] })
135
134
  }
@@ -10,6 +10,7 @@ import {
10
10
  WidgetDataValidationError,
11
11
  } from '../../../services/widgetDataService'
12
12
  import type { AnalyticsRegistry } from '../../../services/analyticsRegistry'
13
+ import { runApiInterceptorsBefore } from '@open-mercato/shared/lib/crud/interceptor-runner'
13
14
  import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
14
15
  import { dashboardsTag, dashboardsErrorSchema } from '../../openapi'
15
16
  import { widgetDataRequestSchema, widgetDataResponseSchema } from './schema'
@@ -81,10 +82,44 @@ export async function POST(req: Request) {
81
82
  return undefined
82
83
  })()
83
84
 
85
+ const userFeatures = Array.isArray(auth.features)
86
+ ? auth.features.filter((value): value is string => typeof value === 'string')
87
+ : []
88
+
89
+ const headers: Record<string, string> = {}
90
+ req.headers.forEach((value, key) => {
91
+ headers[key] = value
92
+ })
93
+
94
+ const interceptorResult = await runApiInterceptorsBefore({
95
+ routePath: 'dashboards/widgets/data',
96
+ method: 'POST',
97
+ request: {
98
+ method: 'POST',
99
+ url: req.url,
100
+ headers,
101
+ body: parsed.data as unknown as Record<string, unknown>,
102
+ },
103
+ context: {
104
+ em,
105
+ container,
106
+ userId: auth.sub ?? '',
107
+ tenantId,
108
+ organizationId: organizationIds?.[0] ?? auth.orgId ?? '',
109
+ userFeatures,
110
+ },
111
+ })
112
+
113
+ if (!interceptorResult.ok) {
114
+ return NextResponse.json(interceptorResult.body, { status: interceptorResult.statusCode })
115
+ }
116
+
117
+ const requestData = (interceptorResult.request.body ?? parsed.data) as WidgetDataRequest
118
+
84
119
  try {
85
120
  const cache = container.resolve<CacheStrategy>('cache')
86
121
  const service = createWidgetDataService(em, { tenantId, organizationIds }, analyticsRegistry, cache)
87
- const result = await service.fetchWidgetData(parsed.data as WidgetDataRequest)
122
+ const result = await service.fetchWidgetData(requestData)
88
123
  return NextResponse.json(result)
89
124
  } catch (err) {
90
125
  console.error('[widgets/data] Error:', err)
@@ -445,15 +445,15 @@ export function createSyncEngine(deps: EngineDeps) {
445
445
  processedCount += processedBatchCount
446
446
  totalCount = batch.totalEstimate ?? totalCount
447
447
 
448
- await syncRunService.updateCounts(
448
+ await syncRunService.commitBatchProgress(
449
449
  run.id,
450
450
  {
451
451
  ...delta,
452
452
  batchesCompleted: 1,
453
453
  },
454
+ batch.cursor,
454
455
  scope,
455
456
  )
456
- await syncRunService.updateCursor(run.id, batch.cursor, scope)
457
457
 
458
458
  await updateProgress(run.progressJobId, processedCount, totalCount, scope)
459
459
  await refreshCoverageSnapshots(batch.refreshCoverageEntityTypes, scope)
@@ -587,7 +587,7 @@ export function createSyncEngine(deps: EngineDeps) {
587
587
  const delta = applyExportCounters(batch)
588
588
  processedCount += delta.processedCount
589
589
 
590
- await syncRunService.updateCounts(
590
+ await syncRunService.commitBatchProgress(
591
591
  run.id,
592
592
  {
593
593
  createdCount: 0,
@@ -596,10 +596,9 @@ export function createSyncEngine(deps: EngineDeps) {
596
596
  failedCount: delta.failedCount,
597
597
  batchesCompleted: 1,
598
598
  },
599
+ batch.cursor,
599
600
  scope,
600
601
  )
601
-
602
- await syncRunService.updateCursor(run.id, batch.cursor, scope)
603
602
  await updateProgress(run.progressJobId, processedCount, null, scope)
604
603
 
605
604
  await writeOperationalLog({
@@ -1,5 +1,6 @@
1
1
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
2
2
  import { findAndCountWithDecryption, findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
3
4
  import { SyncCursor, SyncRun } from '../data/entities'
4
5
 
5
6
  type SyncScope = {
@@ -8,6 +9,38 @@ type SyncScope = {
8
9
  }
9
10
 
10
11
  export function createSyncRunService(em: EntityManager) {
12
+ async function resolveCursorRow(run: SyncRun, scope: SyncScope): Promise<SyncCursor | null> {
13
+ return findOneWithDecryption(
14
+ em,
15
+ SyncCursor,
16
+ {
17
+ integrationId: run.integrationId,
18
+ entityType: run.entityType,
19
+ direction: run.direction,
20
+ organizationId: scope.organizationId,
21
+ tenantId: scope.tenantId,
22
+ },
23
+ undefined,
24
+ scope,
25
+ )
26
+ }
27
+
28
+ function applyCursorMutation(run: SyncRun, cursorRow: SyncCursor | null, cursor: string, scope: SyncScope): void {
29
+ run.cursor = cursor
30
+ if (cursorRow) {
31
+ cursorRow.cursor = cursor
32
+ } else {
33
+ em.create(SyncCursor, {
34
+ integrationId: run.integrationId,
35
+ entityType: run.entityType,
36
+ direction: run.direction,
37
+ cursor,
38
+ organizationId: scope.organizationId,
39
+ tenantId: scope.tenantId,
40
+ })
41
+ }
42
+ }
43
+
11
44
  return {
12
45
  async createRun(input: {
13
46
  integrationId: string
@@ -142,36 +175,32 @@ export function createSyncRunService(em: EntityManager) {
142
175
  async updateCursor(runId: string, cursor: string, scope: SyncScope): Promise<void> {
143
176
  const run = await this.getRun(runId, scope)
144
177
  if (!run) return
145
- run.cursor = cursor
178
+ const cursorRow = await resolveCursorRow(run, scope)
179
+ await withAtomicFlush(em, [
180
+ () => applyCursorMutation(run, cursorRow, cursor, scope),
181
+ ], { transaction: true })
182
+ },
146
183
 
147
- const cursorRow = await findOneWithDecryption(
148
- em,
149
- SyncCursor,
150
- {
151
- integrationId: run.integrationId,
152
- entityType: run.entityType,
153
- direction: run.direction,
154
- organizationId: scope.organizationId,
155
- tenantId: scope.tenantId,
184
+ async commitBatchProgress(
185
+ runId: string,
186
+ delta: Partial<Pick<SyncRun, 'createdCount' | 'updatedCount' | 'skippedCount' | 'failedCount' | 'batchesCompleted'>>,
187
+ cursor: string,
188
+ scope: SyncScope,
189
+ ): Promise<SyncRun | null> {
190
+ const run = await this.getRun(runId, scope)
191
+ if (!run) return null
192
+ const cursorRow = await resolveCursorRow(run, scope)
193
+ await withAtomicFlush(em, [
194
+ () => {
195
+ run.createdCount += delta.createdCount ?? 0
196
+ run.updatedCount += delta.updatedCount ?? 0
197
+ run.skippedCount += delta.skippedCount ?? 0
198
+ run.failedCount += delta.failedCount ?? 0
199
+ run.batchesCompleted += delta.batchesCompleted ?? 0
200
+ applyCursorMutation(run, cursorRow, cursor, scope)
156
201
  },
157
- undefined,
158
- scope,
159
- )
160
-
161
- if (cursorRow) {
162
- cursorRow.cursor = cursor
163
- } else {
164
- em.create(SyncCursor, {
165
- integrationId: run.integrationId,
166
- entityType: run.entityType,
167
- direction: run.direction,
168
- cursor,
169
- organizationId: scope.organizationId,
170
- tenantId: scope.tenantId,
171
- })
172
- }
173
-
174
- await em.flush()
202
+ ], { transaction: true })
203
+ return run
175
204
  },
176
205
 
177
206
  async resolveCursor(integrationId: string, entityType: string, direction: 'import' | 'export', scope: SyncScope): Promise<string | null> {