@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4236.1.9fa6806b34

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 (370) 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/catalog/commands/categories.js +61 -12
  31. package/dist/modules/catalog/commands/categories.js.map +2 -2
  32. package/dist/modules/catalog/commands/products.js +79 -54
  33. package/dist/modules/catalog/commands/products.js.map +2 -2
  34. package/dist/modules/catalog/commands/variants.js +29 -16
  35. package/dist/modules/catalog/commands/variants.js.map +2 -2
  36. package/dist/modules/currencies/commands/currencies.js +15 -8
  37. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  39. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  41. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  43. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  44. package/dist/modules/customers/commands/addresses.js +35 -21
  45. package/dist/modules/customers/commands/addresses.js.map +2 -2
  46. package/dist/modules/customers/commands/companies.js +163 -162
  47. package/dist/modules/customers/commands/companies.js.map +2 -2
  48. package/dist/modules/customers/commands/deals.js +3 -4
  49. package/dist/modules/customers/commands/deals.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +19 -22
  51. package/dist/modules/customers/commands/interactions.js.map +2 -2
  52. package/dist/modules/customers/commands/people.js +18 -15
  53. package/dist/modules/customers/commands/people.js.map +2 -2
  54. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  55. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  56. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  57. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  58. package/dist/modules/customers/commands/pipelines.js +27 -20
  59. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  60. package/dist/modules/customers/commands/tags.js +13 -5
  61. package/dist/modules/customers/commands/tags.js.map +2 -2
  62. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  63. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  64. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  65. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  66. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  68. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  70. package/dist/modules/directory/commands/organizations.js +192 -158
  71. package/dist/modules/directory/commands/organizations.js.map +3 -3
  72. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  73. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  74. package/dist/modules/messages/commands/messages.js +77 -75
  75. package/dist/modules/messages/commands/messages.js.map +2 -2
  76. package/dist/modules/messages/commands/shared.js +132 -132
  77. package/dist/modules/messages/commands/shared.js.map +2 -2
  78. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  79. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  80. package/dist/modules/resources/commands/resources.js +125 -117
  81. package/dist/modules/resources/commands/resources.js.map +2 -2
  82. package/dist/modules/resources/commands/tags.js +7 -3
  83. package/dist/modules/resources/commands/tags.js.map +2 -2
  84. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  85. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  86. package/dist/modules/sales/commands/documents.js +629 -478
  87. package/dist/modules/sales/commands/documents.js.map +2 -2
  88. package/dist/modules/sales/commands/payments.js +146 -146
  89. package/dist/modules/sales/commands/payments.js.map +2 -2
  90. package/dist/modules/sales/commands/returns.js +68 -60
  91. package/dist/modules/sales/commands/returns.js.map +2 -2
  92. package/dist/modules/staff/acl.js +10 -1
  93. package/dist/modules/staff/acl.js.map +2 -2
  94. package/dist/modules/staff/analytics.js +33 -0
  95. package/dist/modules/staff/analytics.js.map +7 -0
  96. package/dist/modules/staff/api/guards.js +31 -0
  97. package/dist/modules/staff/api/guards.js.map +7 -0
  98. package/dist/modules/staff/api/interceptors.js +96 -0
  99. package/dist/modules/staff/api/interceptors.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  101. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  103. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  105. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  107. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  109. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  111. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  113. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  115. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  119. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  121. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  122. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  123. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  124. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  125. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  126. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  127. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  128. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  129. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  130. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  131. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  132. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  133. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  134. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  135. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  137. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  139. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  144. package/dist/modules/staff/cli.js +38 -1
  145. package/dist/modules/staff/cli.js.map +2 -2
  146. package/dist/modules/staff/commands/index.js +2 -0
  147. package/dist/modules/staff/commands/index.js.map +2 -2
  148. package/dist/modules/staff/commands/leave-requests.js +30 -28
  149. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  150. package/dist/modules/staff/commands/team-members.js +21 -20
  151. package/dist/modules/staff/commands/team-members.js.map +2 -2
  152. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  153. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  154. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  155. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  156. package/dist/modules/staff/data/enrichers.js +104 -0
  157. package/dist/modules/staff/data/enrichers.js.map +7 -0
  158. package/dist/modules/staff/data/entities.js +226 -1
  159. package/dist/modules/staff/data/entities.js.map +2 -2
  160. package/dist/modules/staff/data/validators.js +113 -1
  161. package/dist/modules/staff/data/validators.js.map +2 -2
  162. package/dist/modules/staff/events.js +13 -1
  163. package/dist/modules/staff/events.js.map +2 -2
  164. package/dist/modules/staff/lib/crud.js +7 -1
  165. package/dist/modules/staff/lib/crud.js.map +2 -2
  166. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  167. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  168. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  169. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  170. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  171. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  172. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  173. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  174. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  175. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  176. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  177. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  178. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  179. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  180. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  181. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  183. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  185. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  187. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  189. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  191. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  193. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  195. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  197. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  199. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  201. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  203. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  205. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  207. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  209. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  211. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  212. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  213. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  214. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  215. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  216. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  217. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  218. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  219. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  220. package/dist/modules/staff/search.js +35 -0
  221. package/dist/modules/staff/search.js.map +2 -2
  222. package/dist/modules/staff/setup.js +15 -1
  223. package/dist/modules/staff/setup.js.map +2 -2
  224. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  225. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  226. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  227. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  228. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  229. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  230. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  231. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  232. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  233. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  234. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  235. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  237. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection-table.js +12 -0
  239. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  240. package/dist/modules/sync_excel/api/import/route.js +19 -17
  241. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  242. package/dist/modules/translations/commands/translations.js +22 -19
  243. package/dist/modules/translations/commands/translations.js.map +2 -2
  244. package/generated/entities/staff_time_entry/index.ts +17 -0
  245. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  246. package/generated/entities/staff_time_project/index.ts +16 -0
  247. package/generated/entities/staff_time_project_member/index.ts +13 -0
  248. package/generated/entities.ids.generated.ts +5 -1
  249. package/generated/entity-fields-registry.ts +64 -0
  250. package/package.json +7 -7
  251. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  252. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  253. package/src/modules/attachments/api/route.ts +20 -14
  254. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  255. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  256. package/src/modules/auth/api/users/acl/route.ts +17 -12
  257. package/src/modules/auth/commands/users.ts +96 -80
  258. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  259. package/src/modules/catalog/commands/categories.ts +61 -12
  260. package/src/modules/catalog/commands/products.ts +93 -60
  261. package/src/modules/catalog/commands/variants.ts +29 -16
  262. package/src/modules/currencies/commands/currencies.ts +27 -14
  263. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  264. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  265. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  266. package/src/modules/customers/commands/addresses.ts +35 -23
  267. package/src/modules/customers/commands/companies.ts +166 -165
  268. package/src/modules/customers/commands/deals.ts +2 -4
  269. package/src/modules/customers/commands/interactions.ts +20 -26
  270. package/src/modules/customers/commands/people.ts +18 -15
  271. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  272. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  273. package/src/modules/customers/commands/pipelines.ts +29 -23
  274. package/src/modules/customers/commands/tags.ts +13 -5
  275. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  276. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  277. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  278. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  279. package/src/modules/directory/commands/organizations.ts +203 -166
  280. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  281. package/src/modules/messages/commands/messages.ts +82 -80
  282. package/src/modules/messages/commands/shared.ts +138 -133
  283. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  284. package/src/modules/resources/commands/resources.ts +127 -117
  285. package/src/modules/resources/commands/tags.ts +7 -3
  286. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  287. package/src/modules/sales/commands/documents.ts +673 -481
  288. package/src/modules/sales/commands/payments.ts +158 -152
  289. package/src/modules/sales/commands/returns.ts +74 -63
  290. package/src/modules/staff/acl.ts +11 -0
  291. package/src/modules/staff/analytics.ts +30 -0
  292. package/src/modules/staff/api/guards.ts +59 -0
  293. package/src/modules/staff/api/interceptors.ts +122 -0
  294. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  295. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  296. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  297. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  298. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  299. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  300. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  301. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  302. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  303. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  304. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  305. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  306. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  307. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  308. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  309. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  310. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  311. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  312. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  313. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  314. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  315. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  316. package/src/modules/staff/cli.ts +40 -1
  317. package/src/modules/staff/commands/index.ts +2 -0
  318. package/src/modules/staff/commands/leave-requests.ts +37 -29
  319. package/src/modules/staff/commands/team-members.ts +25 -20
  320. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  321. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  322. package/src/modules/staff/data/enrichers.ts +134 -0
  323. package/src/modules/staff/data/entities.ts +198 -0
  324. package/src/modules/staff/data/validators.ts +129 -0
  325. package/src/modules/staff/events.ts +13 -0
  326. package/src/modules/staff/i18n/de.json +209 -1
  327. package/src/modules/staff/i18n/en.json +209 -1
  328. package/src/modules/staff/i18n/es.json +209 -1
  329. package/src/modules/staff/i18n/pl.json +209 -1
  330. package/src/modules/staff/lib/crud.ts +8 -0
  331. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  332. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  333. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  334. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  335. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  336. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  337. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  338. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  339. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  340. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  341. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  342. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  343. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  344. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  345. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  346. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  347. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  348. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  349. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  350. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  351. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  352. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  353. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  354. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  355. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  356. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  357. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  358. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  359. package/src/modules/staff/search.ts +35 -0
  360. package/src/modules/staff/setup.ts +15 -0
  361. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  362. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  363. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  364. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  365. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  366. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  367. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  368. package/src/modules/staff/widgets/injection-table.ts +10 -0
  369. package/src/modules/sync_excel/api/import/route.ts +23 -18
  370. package/src/modules/translations/commands/translations.ts +49 -41
@@ -30,31 +30,34 @@ const saveTranslationCommand = {
30
30
  },
31
31
  async execute(input, ctx) {
32
32
  const db = resolveDb(ctx);
33
- const existing = await db.selectFrom("entity_translations").select(["id"]).where("entity_type", "=", input.entityType).where("entity_id", "=", input.entityId).where(sql`tenant_id is not distinct from ${input.tenantId}`).where(sql`organization_id is not distinct from ${input.organizationId}`).executeTakeFirst();
34
- if (existing) {
35
- await db.updateTable("entity_translations").set({
36
- translations: sql`${JSON.stringify(input.translations)}::jsonb`,
37
- updated_at: sql`now()`
38
- }).where("id", "=", existing.id).execute();
39
- } else {
40
- await db.insertInto("entity_translations").values({
41
- entity_type: input.entityType,
42
- entity_id: input.entityId,
43
- organization_id: input.organizationId,
44
- tenant_id: input.tenantId,
45
- translations: sql`${JSON.stringify(input.translations)}::jsonb`,
46
- created_at: sql`now()`,
47
- updated_at: sql`now()`
48
- }).execute();
49
- }
33
+ const rowId = await db.transaction().execute(async (trx) => {
34
+ const existing = await trx.selectFrom("entity_translations").select(["id"]).where("entity_type", "=", input.entityType).where("entity_id", "=", input.entityId).where(sql`tenant_id is not distinct from ${input.tenantId}`).where(sql`organization_id is not distinct from ${input.organizationId}`).executeTakeFirst();
35
+ if (existing) {
36
+ await trx.updateTable("entity_translations").set({
37
+ translations: sql`${JSON.stringify(input.translations)}::jsonb`,
38
+ updated_at: sql`now()`
39
+ }).where("id", "=", existing.id).execute();
40
+ } else {
41
+ await trx.insertInto("entity_translations").values({
42
+ entity_type: input.entityType,
43
+ entity_id: input.entityId,
44
+ organization_id: input.organizationId,
45
+ tenant_id: input.tenantId,
46
+ translations: sql`${JSON.stringify(input.translations)}::jsonb`,
47
+ created_at: sql`now()`,
48
+ updated_at: sql`now()`
49
+ }).execute();
50
+ }
51
+ const saved = await trx.selectFrom("entity_translations").select(["id"]).where("entity_type", "=", input.entityType).where("entity_id", "=", input.entityId).where(sql`tenant_id is not distinct from ${input.tenantId}`).where(sql`organization_id is not distinct from ${input.organizationId}`).executeTakeFirst();
52
+ return saved?.id ?? "";
53
+ });
50
54
  await emitTranslationsEvent("translations.translation.updated", {
51
55
  entityType: input.entityType,
52
56
  entityId: input.entityId,
53
57
  organizationId: input.organizationId,
54
58
  tenantId: input.tenantId
55
59
  }, { persistent: true }).catch(() => void 0);
56
- const saved = await db.selectFrom("entity_translations").select(["id"]).where("entity_type", "=", input.entityType).where("entity_id", "=", input.entityId).where(sql`tenant_id is not distinct from ${input.tenantId}`).where(sql`organization_id is not distinct from ${input.organizationId}`).executeTakeFirst();
57
- return { rowId: saved?.id ?? "" };
60
+ return { rowId };
58
61
  },
59
62
  async captureAfter(input, _result, ctx) {
60
63
  const db = resolveDb(ctx);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/translations/commands/translations.ts"],
4
- "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { type Kysely, sql } from 'kysely'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { emitTranslationsEvent } from '../events'\n\ntype TranslationSnapshot = {\n id: string | null\n entityType: string\n entityId: string\n translations: Record<string, Record<string, string | null>> | null\n organizationId: string | null\n tenantId: string\n}\n\ntype TranslationUndoPayload = {\n before?: TranslationSnapshot | null\n after?: TranslationSnapshot | null\n}\n\ntype SaveInput = {\n entityType: string\n entityId: string\n translations: Record<string, Record<string, string | null>>\n organizationId: string | null\n tenantId: string\n}\n\ntype DeleteInput = {\n entityType: string\n entityId: string\n organizationId: string | null\n tenantId: string\n}\n\nfunction resolveDb(ctx: CommandRuntimeContext): Kysely<any> {\n const em = ctx.container.resolve('em') as EntityManager\n return em.getKysely<any>()\n}\n\nasync function loadTranslationSnapshot(\n db: Kysely<any>,\n entityType: string,\n entityId: string,\n tenantId: string,\n organizationId: string | null,\n): Promise<TranslationSnapshot | null> {\n const row = await (db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('entity_type', '=', entityType)\n .where('entity_id', '=', entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${organizationId}`)\n .executeTakeFirst() as Record<string, any> | undefined\n\n if (!row) return null\n return {\n id: row.id,\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations ?? null,\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id,\n }\n}\n\nconst saveTranslationCommand: CommandHandler<SaveInput, { rowId: string }> = {\n id: 'translations.translation.save',\n\n async prepare(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n const db = resolveDb(ctx)\n const snapshot = await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n return { before: snapshot }\n },\n\n async execute(input, ctx) {\n const db = resolveDb(ctx) as any\n const existing = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (existing) {\n await db\n .updateTable('entity_translations')\n .set({\n translations: sql`${JSON.stringify(input.translations)}::jsonb`,\n updated_at: sql`now()`,\n } as any)\n .where('id', '=', existing.id)\n .execute()\n } else {\n await db\n .insertInto('entity_translations')\n .values({\n entity_type: input.entityType,\n entity_id: input.entityId,\n organization_id: input.organizationId,\n tenant_id: input.tenantId,\n translations: sql`${JSON.stringify(input.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n\n await emitTranslationsEvent('translations.translation.updated', {\n entityType: input.entityType,\n entityId: input.entityId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }, { persistent: true }).catch(() => undefined)\n\n const saved = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n return { rowId: saved?.id ?? '' }\n },\n\n async captureAfter(input, _result, ctx) {\n const db = resolveDb(ctx)\n return await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n },\n\n async buildLog({ snapshots, result }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as TranslationSnapshot | null | undefined\n const after = snapshots.after as TranslationSnapshot | null | undefined\n return {\n actionLabel: translate('translations.audit.save', 'Save translation'),\n resourceKind: 'translations.translation',\n resourceId: result.rowId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies TranslationUndoPayload,\n },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const payload = extractUndoPayload<TranslationUndoPayload>(logEntry)\n const before = payload?.before ?? null\n const db = resolveDb(ctx) as any\n\n if (!before || !before.translations) {\n // Was a create \u2014 delete the record\n const resourceId = logEntry?.resourceId\n if (resourceId) {\n await db.deleteFrom('entity_translations').where('id', '=', resourceId).execute()\n }\n } else {\n // Was an update \u2014 restore previous translations\n const existing = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', before.entityType)\n .where('entity_id', '=', before.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${before.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${before.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (existing) {\n await db\n .updateTable('entity_translations')\n .set({\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n updated_at: sql`now()`,\n } as any)\n .where('id', '=', existing.id)\n .execute()\n } else {\n await db\n .insertInto('entity_translations')\n .values({\n entity_type: before.entityType,\n entity_id: before.entityId,\n organization_id: before.organizationId,\n tenant_id: before.tenantId,\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n }\n },\n}\n\nconst deleteTranslationCommand: CommandHandler<DeleteInput, { deleted: boolean }> = {\n id: 'translations.translation.delete',\n\n async prepare(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n const db = resolveDb(ctx)\n const snapshot = await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n return { before: snapshot }\n },\n\n async execute(input, ctx) {\n const db = resolveDb(ctx) as any\n const result = await db\n .deleteFrom('entity_translations')\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { numDeletedRows?: bigint | number } | undefined\n const count = Number(result?.numDeletedRows ?? 0)\n\n await emitTranslationsEvent('translations.translation.deleted', {\n entityType: input.entityType,\n entityId: input.entityId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }, { persistent: true }).catch(() => undefined)\n\n return { deleted: count > 0 }\n },\n\n async buildLog({ snapshots }) {\n const before = snapshots.before as TranslationSnapshot | null | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('translations.audit.delete', 'Delete translation'),\n resourceKind: 'translations.translation',\n resourceId: before.id ?? undefined,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies TranslationUndoPayload,\n },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const payload = extractUndoPayload<TranslationUndoPayload>(logEntry)\n const before = payload?.before\n if (!before || !before.translations) return\n const db = resolveDb(ctx) as any\n\n const existing = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', before.entityType)\n .where('entity_id', '=', before.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${before.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${before.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (!existing) {\n await db\n .insertInto('entity_translations')\n .values({\n entity_type: before.entityType,\n entity_id: before.entityId,\n organization_id: before.organizationId,\n tenant_id: before.tenantId,\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n },\n}\n\nregisterCommand(saveTranslationCommand)\nregisterCommand(deleteTranslationCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { type Kysely, sql } from 'kysely'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { emitTranslationsEvent } from '../events'\n\ntype TranslationSnapshot = {\n id: string | null\n entityType: string\n entityId: string\n translations: Record<string, Record<string, string | null>> | null\n organizationId: string | null\n tenantId: string\n}\n\ntype TranslationUndoPayload = {\n before?: TranslationSnapshot | null\n after?: TranslationSnapshot | null\n}\n\ntype SaveInput = {\n entityType: string\n entityId: string\n translations: Record<string, Record<string, string | null>>\n organizationId: string | null\n tenantId: string\n}\n\ntype DeleteInput = {\n entityType: string\n entityId: string\n organizationId: string | null\n tenantId: string\n}\n\nfunction resolveDb(ctx: CommandRuntimeContext): Kysely<any> {\n const em = ctx.container.resolve('em') as EntityManager\n return em.getKysely<any>()\n}\n\nasync function loadTranslationSnapshot(\n db: Kysely<any>,\n entityType: string,\n entityId: string,\n tenantId: string,\n organizationId: string | null,\n): Promise<TranslationSnapshot | null> {\n const row = await (db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('entity_type', '=', entityType)\n .where('entity_id', '=', entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${organizationId}`)\n .executeTakeFirst() as Record<string, any> | undefined\n\n if (!row) return null\n return {\n id: row.id,\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations ?? null,\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id,\n }\n}\n\nconst saveTranslationCommand: CommandHandler<SaveInput, { rowId: string }> = {\n id: 'translations.translation.save',\n\n async prepare(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n const db = resolveDb(ctx)\n const snapshot = await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n return { before: snapshot }\n },\n\n async execute(input, ctx) {\n const db = resolveDb(ctx) as any\n\n // Run the lookup + upsert + id read in one transaction so a concurrent\n // writer cannot slip between the existence check and the insert/update.\n const rowId = await db.transaction().execute(async (trx: any) => {\n const existing = await trx\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (existing) {\n await trx\n .updateTable('entity_translations')\n .set({\n translations: sql`${JSON.stringify(input.translations)}::jsonb`,\n updated_at: sql`now()`,\n } as any)\n .where('id', '=', existing.id)\n .execute()\n } else {\n await trx\n .insertInto('entity_translations')\n .values({\n entity_type: input.entityType,\n entity_id: input.entityId,\n organization_id: input.organizationId,\n tenant_id: input.tenantId,\n translations: sql`${JSON.stringify(input.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n\n const saved = await trx\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n return saved?.id ?? ''\n })\n\n // Emit AFTER the write commits \u2014 never announce a change that rolled back.\n await emitTranslationsEvent('translations.translation.updated', {\n entityType: input.entityType,\n entityId: input.entityId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }, { persistent: true }).catch(() => undefined)\n\n return { rowId }\n },\n\n async captureAfter(input, _result, ctx) {\n const db = resolveDb(ctx)\n return await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n },\n\n async buildLog({ snapshots, result }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as TranslationSnapshot | null | undefined\n const after = snapshots.after as TranslationSnapshot | null | undefined\n return {\n actionLabel: translate('translations.audit.save', 'Save translation'),\n resourceKind: 'translations.translation',\n resourceId: result.rowId,\n tenantId: after?.tenantId ?? before?.tenantId ?? null,\n organizationId: after?.organizationId ?? before?.organizationId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: after ?? null,\n payload: {\n undo: { before: before ?? null, after: after ?? null } satisfies TranslationUndoPayload,\n },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const payload = extractUndoPayload<TranslationUndoPayload>(logEntry)\n const before = payload?.before ?? null\n const db = resolveDb(ctx) as any\n\n if (!before || !before.translations) {\n // Was a create \u2014 delete the record\n const resourceId = logEntry?.resourceId\n if (resourceId) {\n await db.deleteFrom('entity_translations').where('id', '=', resourceId).execute()\n }\n } else {\n // Was an update \u2014 restore previous translations\n const existing = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', before.entityType)\n .where('entity_id', '=', before.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${before.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${before.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (existing) {\n await db\n .updateTable('entity_translations')\n .set({\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n updated_at: sql`now()`,\n } as any)\n .where('id', '=', existing.id)\n .execute()\n } else {\n await db\n .insertInto('entity_translations')\n .values({\n entity_type: before.entityType,\n entity_id: before.entityId,\n organization_id: before.organizationId,\n tenant_id: before.tenantId,\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n }\n },\n}\n\nconst deleteTranslationCommand: CommandHandler<DeleteInput, { deleted: boolean }> = {\n id: 'translations.translation.delete',\n\n async prepare(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n const db = resolveDb(ctx)\n const snapshot = await loadTranslationSnapshot(db, input.entityType, input.entityId, input.tenantId, input.organizationId)\n return { before: snapshot }\n },\n\n async execute(input, ctx) {\n const db = resolveDb(ctx) as any\n const result = await db\n .deleteFrom('entity_translations')\n .where('entity_type', '=', input.entityType)\n .where('entity_id', '=', input.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${input.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${input.organizationId}`)\n .executeTakeFirst() as { numDeletedRows?: bigint | number } | undefined\n const count = Number(result?.numDeletedRows ?? 0)\n\n await emitTranslationsEvent('translations.translation.deleted', {\n entityType: input.entityType,\n entityId: input.entityId,\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n }, { persistent: true }).catch(() => undefined)\n\n return { deleted: count > 0 }\n },\n\n async buildLog({ snapshots }) {\n const before = snapshots.before as TranslationSnapshot | null | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('translations.audit.delete', 'Delete translation'),\n resourceKind: 'translations.translation',\n resourceId: before.id ?? undefined,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies TranslationUndoPayload,\n },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const payload = extractUndoPayload<TranslationUndoPayload>(logEntry)\n const before = payload?.before\n if (!before || !before.translations) return\n const db = resolveDb(ctx) as any\n\n const existing = await db\n .selectFrom('entity_translations')\n .select(['id'])\n .where('entity_type', '=', before.entityType)\n .where('entity_id', '=', before.entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${before.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${before.organizationId}`)\n .executeTakeFirst() as { id: string } | undefined\n\n if (!existing) {\n await db\n .insertInto('entity_translations')\n .values({\n entity_type: before.entityType,\n entity_id: before.entityId,\n organization_id: before.organizationId,\n tenant_id: before.tenantId,\n translations: sql`${JSON.stringify(before.translations)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n } as any)\n .execute()\n }\n },\n}\n\nregisterCommand(saveTranslationCommand)\nregisterCommand(deleteTranslationCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,17 @@
1
+ export const id = "id";
2
+ export const tenant_id = "tenant_id";
3
+ export const organization_id = "organization_id";
4
+ export const staff_member_id = "staff_member_id";
5
+ export const date = "date";
6
+ export const duration_minutes = "duration_minutes";
7
+ export const started_at = "started_at";
8
+ export const ended_at = "ended_at";
9
+ export const notes = "notes";
10
+ export const time_project_id = "time_project_id";
11
+ export const customer_id = "customer_id";
12
+ export const deal_id = "deal_id";
13
+ export const order_id = "order_id";
14
+ export const source = "source";
15
+ export const created_at = "created_at";
16
+ export const updated_at = "updated_at";
17
+ export const deleted_at = "deleted_at";
@@ -0,0 +1,10 @@
1
+ export const id = "id";
2
+ export const tenant_id = "tenant_id";
3
+ export const organization_id = "organization_id";
4
+ export const time_entry_id = "time_entry_id";
5
+ export const started_at = "started_at";
6
+ export const ended_at = "ended_at";
7
+ export const segment_type = "segment_type";
8
+ export const created_at = "created_at";
9
+ export const updated_at = "updated_at";
10
+ export const deleted_at = "deleted_at";
@@ -0,0 +1,16 @@
1
+ export const id = "id";
2
+ export const tenant_id = "tenant_id";
3
+ export const organization_id = "organization_id";
4
+ export const name = "name";
5
+ export const customer_id = "customer_id";
6
+ export const code = "code";
7
+ export const description = "description";
8
+ export const project_type = "project_type";
9
+ export const color = "color";
10
+ export const status = "status";
11
+ export const owner_user_id = "owner_user_id";
12
+ export const cost_center = "cost_center";
13
+ export const start_date = "start_date";
14
+ export const created_at = "created_at";
15
+ export const updated_at = "updated_at";
16
+ export const deleted_at = "deleted_at";
@@ -0,0 +1,13 @@
1
+ export const id = "id";
2
+ export const tenant_id = "tenant_id";
3
+ export const organization_id = "organization_id";
4
+ export const time_project_id = "time_project_id";
5
+ export const staff_member_id = "staff_member_id";
6
+ export const role = "role";
7
+ export const status = "status";
8
+ export const show_in_grid = "show_in_grid";
9
+ export const assigned_start_date = "assigned_start_date";
10
+ export const assigned_end_date = "assigned_end_date";
11
+ export const created_at = "created_at";
12
+ export const updated_at = "updated_at";
13
+ export const deleted_at = "deleted_at";
@@ -209,7 +209,11 @@ export const M = {
209
209
  "staff_team_member_comment": "staff:staff_team_member_comment",
210
210
  "staff_team_member_activity": "staff:staff_team_member_activity",
211
211
  "staff_team_member_job_history": "staff:staff_team_member_job_history",
212
- "staff_team_member_address": "staff:staff_team_member_address"
212
+ "staff_team_member_address": "staff:staff_team_member_address",
213
+ "staff_time_entry": "staff:staff_time_entry",
214
+ "staff_time_entry_segment": "staff:staff_time_entry_segment",
215
+ "staff_time_project": "staff:staff_time_project",
216
+ "staff_time_project_member": "staff:staff_time_project_member"
213
217
  },
214
218
  "notifications": {
215
219
  "notification": "notifications:notification"
@@ -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.4236.1.9fa6806b34",
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.4236.1.9fa6806b34",
247
+ "@open-mercato/shared": "0.6.4-develop.4236.1.9fa6806b34",
248
+ "@open-mercato/ui": "0.6.4-develop.4236.1.9fa6806b34",
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.4236.1.9fa6806b34",
254
+ "@open-mercato/shared": "0.6.4-develop.4236.1.9fa6806b34",
255
+ "@open-mercato/ui": "0.6.4-develop.4236.1.9fa6806b34",
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) {