@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/messages/commands/shared.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData, type RecipientStatus } from '../data/entities'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\n\nexport type MessageCommandExecuteResult = {\n id: string\n externalEmail: string | null\n recipientUserIds: string[]\n}\n\nexport type MessageScopeInput = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport type MessageRecipientSnapshot = {\n id: string\n messageId: string\n recipientUserId: string\n recipientType: 'to' | 'cc' | 'bcc'\n status: RecipientStatus\n readAt: string | null\n archivedAt: string | null\n deletedAt: string | null\n}\n\nexport type MessageObjectSnapshot = {\n id: string\n messageId: string\n entityModule: string\n entityType: string\n entityId: string\n actionRequired: boolean\n actionType: string | null\n actionLabel: string | null\n entitySnapshot: Record<string, unknown> | null\n}\n\nexport type MessageSnapshot = {\n id: string\n type: string\n visibility: 'public' | 'internal' | null\n sourceEntityType: string | null\n sourceEntityId: string | null\n externalEmail: string | null\n externalName: string | null\n threadId: string | null\n parentMessageId: string | null\n senderUserId: string\n subject: string\n body: string\n bodyFormat: 'text' | 'markdown'\n priority: 'low' | 'normal' | 'high' | 'urgent'\n status: 'draft' | 'sent'\n isDraft: boolean\n sentAt: string | null\n actionData: MessageActionData | null\n actionResult: Record<string, unknown> | null\n actionTaken: string | null\n actionTakenByUserId: string | null\n actionTakenAt: string | null\n sendViaEmail: boolean\n tenantId: string\n organizationId: string | null\n deletedAt: string | null\n}\n\nexport type MessageAggregateSnapshot = {\n message: MessageSnapshot\n recipients: MessageRecipientSnapshot[]\n objects: MessageObjectSnapshot[]\n attachmentIds: string[]\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction hasOrganizationAccess(scopeOrganizationId: string | null, messageOrganizationId: string | null | undefined): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport function assertOrganizationAccess(scope: MessageScopeInput, message: Message): void {\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n throw new Error('Access denied')\n }\n}\n\nexport async function getAttachmentIdsForMessage(\n em: EntityManager,\n messageId: string,\n scope: { tenantId: string; organizationId: string | null },\n): Promise<string[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return attachments.map((item) => item.id)\n}\n\nexport async function loadMessageAggregateSnapshot(\n em: EntityManager,\n messageId: string,\n scope?: { tenantId: string; organizationId: string | null },\n): Promise<MessageAggregateSnapshot | null> {\n const where: Record<string, unknown> = { id: messageId }\n if (scope) {\n where.tenantId = scope.tenantId\n where.organizationId = scope.organizationId\n }\n const message = await em.findOne(Message, where)\n if (!message) return null\n const recipients = await em.find(MessageRecipient, { messageId })\n const objects = await em.find(MessageObject, { messageId })\n const attachmentIds = await getAttachmentIdsForMessage(em, messageId, {\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n })\n\n return {\n message: {\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n threadId: message.threadId ?? null,\n parentMessageId: message.parentMessageId ?? null,\n senderUserId: message.senderUserId,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n status: message.status,\n isDraft: message.isDraft,\n sentAt: toIso(message.sentAt),\n actionData: (message.actionData as MessageActionData | null) ?? null,\n actionResult: message.actionResult ?? null,\n actionTaken: message.actionTaken ?? null,\n actionTakenByUserId: message.actionTakenByUserId ?? null,\n actionTakenAt: toIso(message.actionTakenAt),\n sendViaEmail: message.sendViaEmail,\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n deletedAt: toIso(message.deletedAt),\n },\n recipients: recipients.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n recipientUserId: item.recipientUserId,\n recipientType: item.recipientType,\n status: item.status,\n readAt: toIso(item.readAt),\n archivedAt: toIso(item.archivedAt),\n deletedAt: toIso(item.deletedAt),\n })),\n objects: objects.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType ?? null,\n actionLabel: item.actionLabel ?? null,\n entitySnapshot: item.entitySnapshot ?? null,\n })),\n attachmentIds,\n }\n}\n\nexport async function restoreMessageAggregateSnapshot(\n em: EntityManager,\n snapshot: MessageAggregateSnapshot,\n): Promise<void> {\n const existingMessage = await em.findOne(Message, { id: snapshot.message.id })\n if (!existingMessage) {\n const created = em.create(Message, {\n id: snapshot.message.id,\n type: snapshot.message.type,\n visibility: snapshot.message.visibility,\n sourceEntityType: snapshot.message.sourceEntityType,\n sourceEntityId: snapshot.message.sourceEntityId,\n externalEmail: snapshot.message.externalEmail,\n externalName: snapshot.message.externalName,\n threadId: snapshot.message.threadId,\n parentMessageId: snapshot.message.parentMessageId,\n senderUserId: snapshot.message.senderUserId,\n subject: snapshot.message.subject,\n body: snapshot.message.body,\n bodyFormat: snapshot.message.bodyFormat,\n priority: snapshot.message.priority,\n status: snapshot.message.status,\n isDraft: snapshot.message.isDraft,\n sentAt: toDate(snapshot.message.sentAt),\n actionData: snapshot.message.actionData as MessageActionData,\n actionResult: snapshot.message.actionResult,\n actionTaken: snapshot.message.actionTaken,\n actionTakenByUserId: snapshot.message.actionTakenByUserId,\n actionTakenAt: toDate(snapshot.message.actionTakenAt),\n sendViaEmail: snapshot.message.sendViaEmail,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n deletedAt: toDate(snapshot.message.deletedAt),\n })\n em.persist(created)\n } else {\n existingMessage.type = snapshot.message.type\n existingMessage.visibility = snapshot.message.visibility\n existingMessage.sourceEntityType = snapshot.message.sourceEntityType\n existingMessage.sourceEntityId = snapshot.message.sourceEntityId\n existingMessage.externalEmail = snapshot.message.externalEmail\n existingMessage.externalName = snapshot.message.externalName\n existingMessage.threadId = snapshot.message.threadId\n existingMessage.parentMessageId = snapshot.message.parentMessageId\n existingMessage.senderUserId = snapshot.message.senderUserId\n existingMessage.subject = snapshot.message.subject\n existingMessage.body = snapshot.message.body\n existingMessage.bodyFormat = snapshot.message.bodyFormat\n existingMessage.priority = snapshot.message.priority\n existingMessage.status = snapshot.message.status\n existingMessage.isDraft = snapshot.message.isDraft\n existingMessage.sentAt = toDate(snapshot.message.sentAt)\n existingMessage.actionData = snapshot.message.actionData as MessageActionData\n existingMessage.actionResult = snapshot.message.actionResult\n existingMessage.actionTaken = snapshot.message.actionTaken\n existingMessage.actionTakenByUserId = snapshot.message.actionTakenByUserId\n existingMessage.actionTakenAt = toDate(snapshot.message.actionTakenAt)\n existingMessage.sendViaEmail = snapshot.message.sendViaEmail\n existingMessage.tenantId = snapshot.message.tenantId\n existingMessage.organizationId = snapshot.message.organizationId\n existingMessage.deletedAt = toDate(snapshot.message.deletedAt)\n }\n\n const existingRecipients = await em.find(MessageRecipient, { messageId: snapshot.message.id })\n const recipientById = new Map(existingRecipients.map((item) => [item.id, item]))\n const snapshotRecipientIds = new Set(snapshot.recipients.map((item) => item.id))\n for (const current of existingRecipients) {\n if (!snapshotRecipientIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const recipient of snapshot.recipients) {\n const existing = recipientById.get(recipient.id)\n if (!existing) {\n em.persist(em.create(MessageRecipient, {\n id: recipient.id,\n messageId: recipient.messageId,\n recipientUserId: recipient.recipientUserId,\n recipientType: recipient.recipientType,\n status: recipient.status,\n readAt: toDate(recipient.readAt),\n archivedAt: toDate(recipient.archivedAt),\n deletedAt: toDate(recipient.deletedAt),\n }))\n continue\n }\n existing.messageId = recipient.messageId\n existing.recipientUserId = recipient.recipientUserId\n existing.recipientType = recipient.recipientType\n existing.status = recipient.status\n existing.readAt = toDate(recipient.readAt)\n existing.archivedAt = toDate(recipient.archivedAt)\n existing.deletedAt = toDate(recipient.deletedAt)\n }\n\n const existingObjects = await em.find(MessageObject, { messageId: snapshot.message.id })\n const objectById = new Map(existingObjects.map((item) => [item.id, item]))\n const snapshotObjectIds = new Set(snapshot.objects.map((item) => item.id))\n for (const current of existingObjects) {\n if (!snapshotObjectIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const object of snapshot.objects) {\n const existing = objectById.get(object.id)\n if (!existing) {\n em.persist(em.create(MessageObject, {\n id: object.id,\n messageId: object.messageId,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n entitySnapshot: object.entitySnapshot,\n }))\n continue\n }\n existing.messageId = object.messageId\n existing.entityModule = object.entityModule\n existing.entityType = object.entityType\n existing.entityId = object.entityId\n existing.actionRequired = object.actionRequired\n existing.actionType = object.actionType\n existing.actionLabel = object.actionLabel\n existing.entitySnapshot = object.entitySnapshot\n }\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: snapshot.message.id,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n id: { $nin: snapshot.attachmentIds.length > 0 ? snapshot.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },\n })\n if (snapshot.attachmentIds.length > 0) {\n const attachments = await em.find(Attachment, {\n id: { $in: snapshot.attachmentIds },\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n })\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = snapshot.message.id\n }\n }\n\n await em.flush()\n}\n\nexport function buildCommandLogBase(\n actionLabel: string,\n resourceId: string,\n snapshot: { tenantId: string; organizationId: string | null },\n) {\n return {\n actionLabel,\n resourceKind: 'messages.message',\n resourceId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData, type RecipientStatus } from '../data/entities'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\n\nexport type MessageCommandExecuteResult = {\n id: string\n externalEmail: string | null\n recipientUserIds: string[]\n}\n\nexport type MessageScopeInput = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport type MessageRecipientSnapshot = {\n id: string\n messageId: string\n recipientUserId: string\n recipientType: 'to' | 'cc' | 'bcc'\n status: RecipientStatus\n readAt: string | null\n archivedAt: string | null\n deletedAt: string | null\n}\n\nexport type MessageObjectSnapshot = {\n id: string\n messageId: string\n entityModule: string\n entityType: string\n entityId: string\n actionRequired: boolean\n actionType: string | null\n actionLabel: string | null\n entitySnapshot: Record<string, unknown> | null\n}\n\nexport type MessageSnapshot = {\n id: string\n type: string\n visibility: 'public' | 'internal' | null\n sourceEntityType: string | null\n sourceEntityId: string | null\n externalEmail: string | null\n externalName: string | null\n threadId: string | null\n parentMessageId: string | null\n senderUserId: string\n subject: string\n body: string\n bodyFormat: 'text' | 'markdown'\n priority: 'low' | 'normal' | 'high' | 'urgent'\n status: 'draft' | 'sent'\n isDraft: boolean\n sentAt: string | null\n actionData: MessageActionData | null\n actionResult: Record<string, unknown> | null\n actionTaken: string | null\n actionTakenByUserId: string | null\n actionTakenAt: string | null\n sendViaEmail: boolean\n tenantId: string\n organizationId: string | null\n deletedAt: string | null\n}\n\nexport type MessageAggregateSnapshot = {\n message: MessageSnapshot\n recipients: MessageRecipientSnapshot[]\n objects: MessageObjectSnapshot[]\n attachmentIds: string[]\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction hasOrganizationAccess(scopeOrganizationId: string | null, messageOrganizationId: string | null | undefined): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport function assertOrganizationAccess(scope: MessageScopeInput, message: Message): void {\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n throw new Error('Access denied')\n }\n}\n\nexport async function getAttachmentIdsForMessage(\n em: EntityManager,\n messageId: string,\n scope: { tenantId: string; organizationId: string | null },\n): Promise<string[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return attachments.map((item) => item.id)\n}\n\nexport async function loadMessageAggregateSnapshot(\n em: EntityManager,\n messageId: string,\n scope?: { tenantId: string; organizationId: string | null },\n): Promise<MessageAggregateSnapshot | null> {\n const where: Record<string, unknown> = { id: messageId }\n if (scope) {\n where.tenantId = scope.tenantId\n where.organizationId = scope.organizationId\n }\n const message = await em.findOne(Message, where)\n if (!message) return null\n const recipients = await em.find(MessageRecipient, { messageId })\n const objects = await em.find(MessageObject, { messageId })\n const attachmentIds = await getAttachmentIdsForMessage(em, messageId, {\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n })\n\n return {\n message: {\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n threadId: message.threadId ?? null,\n parentMessageId: message.parentMessageId ?? null,\n senderUserId: message.senderUserId,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n status: message.status,\n isDraft: message.isDraft,\n sentAt: toIso(message.sentAt),\n actionData: (message.actionData as MessageActionData | null) ?? null,\n actionResult: message.actionResult ?? null,\n actionTaken: message.actionTaken ?? null,\n actionTakenByUserId: message.actionTakenByUserId ?? null,\n actionTakenAt: toIso(message.actionTakenAt),\n sendViaEmail: message.sendViaEmail,\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n deletedAt: toIso(message.deletedAt),\n },\n recipients: recipients.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n recipientUserId: item.recipientUserId,\n recipientType: item.recipientType,\n status: item.status,\n readAt: toIso(item.readAt),\n archivedAt: toIso(item.archivedAt),\n deletedAt: toIso(item.deletedAt),\n })),\n objects: objects.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType ?? null,\n actionLabel: item.actionLabel ?? null,\n entitySnapshot: item.entitySnapshot ?? null,\n })),\n attachmentIds,\n }\n}\n\nexport async function restoreMessageAggregateSnapshot(\n em: EntityManager,\n snapshot: MessageAggregateSnapshot,\n): Promise<void> {\n await withAtomicFlush(em, [async () => {\n // Resolve every read before mutating: a query issued between a scalar\n // mutation and the flush can silently reset the Unit of Work (SPEC-018\n // Problem 1). withAtomicFlush flushes once at the end, so ordering is ours.\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const existingMessage = await em.findOne(Message, { id: snapshot.message.id })\n const existingRecipients = await em.find(MessageRecipient, { messageId: snapshot.message.id })\n const existingObjects = await em.find(MessageObject, { messageId: snapshot.message.id })\n const attachmentsToRelink = snapshot.attachmentIds.length > 0\n ? await em.find(Attachment, {\n id: { $in: snapshot.attachmentIds },\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n })\n : []\n\n if (!existingMessage) {\n const created = em.create(Message, {\n id: snapshot.message.id,\n type: snapshot.message.type,\n visibility: snapshot.message.visibility,\n sourceEntityType: snapshot.message.sourceEntityType,\n sourceEntityId: snapshot.message.sourceEntityId,\n externalEmail: snapshot.message.externalEmail,\n externalName: snapshot.message.externalName,\n threadId: snapshot.message.threadId,\n parentMessageId: snapshot.message.parentMessageId,\n senderUserId: snapshot.message.senderUserId,\n subject: snapshot.message.subject,\n body: snapshot.message.body,\n bodyFormat: snapshot.message.bodyFormat,\n priority: snapshot.message.priority,\n status: snapshot.message.status,\n isDraft: snapshot.message.isDraft,\n sentAt: toDate(snapshot.message.sentAt),\n actionData: snapshot.message.actionData as MessageActionData,\n actionResult: snapshot.message.actionResult,\n actionTaken: snapshot.message.actionTaken,\n actionTakenByUserId: snapshot.message.actionTakenByUserId,\n actionTakenAt: toDate(snapshot.message.actionTakenAt),\n sendViaEmail: snapshot.message.sendViaEmail,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n deletedAt: toDate(snapshot.message.deletedAt),\n })\n em.persist(created)\n } else {\n existingMessage.type = snapshot.message.type\n existingMessage.visibility = snapshot.message.visibility\n existingMessage.sourceEntityType = snapshot.message.sourceEntityType\n existingMessage.sourceEntityId = snapshot.message.sourceEntityId\n existingMessage.externalEmail = snapshot.message.externalEmail\n existingMessage.externalName = snapshot.message.externalName\n existingMessage.threadId = snapshot.message.threadId\n existingMessage.parentMessageId = snapshot.message.parentMessageId\n existingMessage.senderUserId = snapshot.message.senderUserId\n existingMessage.subject = snapshot.message.subject\n existingMessage.body = snapshot.message.body\n existingMessage.bodyFormat = snapshot.message.bodyFormat\n existingMessage.priority = snapshot.message.priority\n existingMessage.status = snapshot.message.status\n existingMessage.isDraft = snapshot.message.isDraft\n existingMessage.sentAt = toDate(snapshot.message.sentAt)\n existingMessage.actionData = snapshot.message.actionData as MessageActionData\n existingMessage.actionResult = snapshot.message.actionResult\n existingMessage.actionTaken = snapshot.message.actionTaken\n existingMessage.actionTakenByUserId = snapshot.message.actionTakenByUserId\n existingMessage.actionTakenAt = toDate(snapshot.message.actionTakenAt)\n existingMessage.sendViaEmail = snapshot.message.sendViaEmail\n existingMessage.tenantId = snapshot.message.tenantId\n existingMessage.organizationId = snapshot.message.organizationId\n existingMessage.deletedAt = toDate(snapshot.message.deletedAt)\n }\n\n const recipientById = new Map(existingRecipients.map((item) => [item.id, item]))\n const snapshotRecipientIds = new Set(snapshot.recipients.map((item) => item.id))\n for (const current of existingRecipients) {\n if (!snapshotRecipientIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const recipient of snapshot.recipients) {\n const existing = recipientById.get(recipient.id)\n if (!existing) {\n em.persist(em.create(MessageRecipient, {\n id: recipient.id,\n messageId: recipient.messageId,\n recipientUserId: recipient.recipientUserId,\n recipientType: recipient.recipientType,\n status: recipient.status,\n readAt: toDate(recipient.readAt),\n archivedAt: toDate(recipient.archivedAt),\n deletedAt: toDate(recipient.deletedAt),\n }))\n continue\n }\n existing.messageId = recipient.messageId\n existing.recipientUserId = recipient.recipientUserId\n existing.recipientType = recipient.recipientType\n existing.status = recipient.status\n existing.readAt = toDate(recipient.readAt)\n existing.archivedAt = toDate(recipient.archivedAt)\n existing.deletedAt = toDate(recipient.deletedAt)\n }\n\n const objectById = new Map(existingObjects.map((item) => [item.id, item]))\n const snapshotObjectIds = new Set(snapshot.objects.map((item) => item.id))\n for (const current of existingObjects) {\n if (!snapshotObjectIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const object of snapshot.objects) {\n const existing = objectById.get(object.id)\n if (!existing) {\n em.persist(em.create(MessageObject, {\n id: object.id,\n messageId: object.messageId,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n entitySnapshot: object.entitySnapshot,\n }))\n continue\n }\n existing.messageId = object.messageId\n existing.entityModule = object.entityModule\n existing.entityType = object.entityType\n existing.entityId = object.entityId\n existing.actionRequired = object.actionRequired\n existing.actionType = object.actionType\n existing.actionLabel = object.actionLabel\n existing.entitySnapshot = object.entitySnapshot\n }\n\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: snapshot.message.id,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n id: { $nin: snapshot.attachmentIds.length > 0 ? snapshot.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },\n })\n for (const attachment of attachmentsToRelink) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = snapshot.message.id\n }\n }], { transaction: true })\n}\n\nexport function buildCommandLogBase(\n actionLabel: string,\n resourceId: string,\n snapshot: { tenantId: string; organizationId: string | null },\n) {\n return {\n actionLabel,\n resourceKind: 'messages.message',\n resourceId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
4
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
5
6
  import { perspectiveSaveSchema } from "@open-mercato/core/modules/perspectives/data/validators";
6
7
  import {
7
8
  loadPerspectivesState,
@@ -122,15 +123,10 @@ async function POST(req, ctx) {
122
123
  })();
123
124
  const rbac = container.resolve("rbacService");
124
125
  const scope = buildScope(auth);
125
- const saved = await saveUserPerspective(em, cache, {
126
- scope,
127
- tableId,
128
- input: parsed.data
129
- });
130
126
  const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0);
131
127
  const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0);
132
- let updatedRolePerspectives = null;
133
- if (applyToRoles.length > 0 || clearRoleIds.length > 0) {
128
+ const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0;
129
+ if (hasRoleOps) {
134
130
  const canApplyToRoles = await rbac.userHasAllFeatures?.(
135
131
  auth.sub,
136
132
  ["perspectives.role_defaults"],
@@ -151,28 +147,43 @@ async function POST(req, ctx) {
151
147
  if (missing.length) {
152
148
  return NextResponse.json({ error: "Invalid roles", missing }, { status: 400 });
153
149
  }
154
- if (applyToRoles.length) {
155
- updatedRolePerspectives = await saveRolePerspectives(em, cache, {
156
- tableId,
157
- tenantId: auth.tenantId ?? null,
158
- organizationId: auth.orgId ?? null,
159
- input: {
160
- roleIds: applyToRoles,
161
- name: parsed.data.name,
162
- settings: parsed.data.settings,
163
- setDefault: parsed.data.setRoleDefault ?? false
164
- }
165
- });
166
- }
167
- if (clearRoleIds.length) {
168
- await clearRolePerspectives(em, cache, {
150
+ }
151
+ let saved = null;
152
+ let updatedRolePerspectives = null;
153
+ await withAtomicFlush(em, [
154
+ async () => {
155
+ saved = await saveUserPerspective(em, cache, {
156
+ scope,
169
157
  tableId,
170
- tenantId: auth.tenantId ?? null,
171
- organizationId: auth.orgId ?? null,
172
- roleIds: clearRoleIds
158
+ input: parsed.data
173
159
  });
160
+ },
161
+ async () => {
162
+ if (applyToRoles.length) {
163
+ updatedRolePerspectives = await saveRolePerspectives(em, cache, {
164
+ tableId,
165
+ tenantId: auth.tenantId ?? null,
166
+ organizationId: auth.orgId ?? null,
167
+ input: {
168
+ roleIds: applyToRoles,
169
+ name: parsed.data.name,
170
+ settings: parsed.data.settings,
171
+ setDefault: parsed.data.setRoleDefault ?? false
172
+ }
173
+ });
174
+ }
175
+ },
176
+ async () => {
177
+ if (clearRoleIds.length) {
178
+ await clearRolePerspectives(em, cache, {
179
+ tableId,
180
+ tenantId: auth.tenantId ?? null,
181
+ organizationId: auth.orgId ?? null,
182
+ roleIds: clearRoleIds
183
+ });
184
+ }
174
185
  }
175
- }
186
+ ], { transaction: true });
176
187
  return NextResponse.json({
177
188
  perspective: saved,
178
189
  rolePerspectives: updatedRolePerspectives ?? [],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/perspectives/api/%5BtableId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n const saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n })\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n\n if (applyToRoles.length > 0 || clearRoleIds.length > 0) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n\n if (clearRoleIds.length) {\n await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n }\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0\n\n if (hasRoleOps) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n\n await withAtomicFlush(em, [\n async () => {\n saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n })\n },\n async () => {\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n },\n async () => {\n if (clearRoleIds.length) {\n await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n },\n ], { transaction: true })\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { registerCommand } from "@open-mercato/shared/lib/commands";
2
2
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
3
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
4
  import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, parseWithCustomFields, setCustomFieldsIfAny } from "@open-mercato/shared/lib/commands/helpers";
5
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
5
6
  import { buildCustomFieldResetMap, diffCustomFieldChanges, loadCustomFieldSnapshot } from "@open-mercato/shared/lib/commands/customFieldSnapshots";
6
7
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
8
  import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
@@ -172,8 +173,18 @@ const createResourceCommand = {
172
173
  createdAt: now,
173
174
  updatedAt: now
174
175
  });
175
- em.persist(record);
176
- await em.flush();
176
+ await withAtomicFlush(em, [
177
+ async () => {
178
+ em.persist(record);
179
+ await em.flush();
180
+ },
181
+ () => syncResourcesResourceTags(em, {
182
+ resourceId: record.id,
183
+ organizationId: record.organizationId,
184
+ tenantId: record.tenantId,
185
+ tagIds: parsed.tags
186
+ })
187
+ ], { transaction: true });
177
188
  const dataEngine = ctx.container.resolve("dataEngine");
178
189
  await setCustomFieldsIfAny({
179
190
  dataEngine,
@@ -183,13 +194,6 @@ const createResourceCommand = {
183
194
  organizationId: record.organizationId,
184
195
  values: custom
185
196
  });
186
- await syncResourcesResourceTags(em, {
187
- resourceId: record.id,
188
- organizationId: record.organizationId,
189
- tenantId: record.tenantId,
190
- tagIds: parsed.tags
191
- });
192
- await em.flush();
193
197
  await emitCrudSideEffects({
194
198
  dataEngine,
195
199
  action: "created",
@@ -282,42 +286,40 @@ const updateResourceCommand = {
282
286
  if (!record) throw new CrudHttpError(404, { error: "Resources resource not found." });
283
287
  ensureTenantScope(ctx, record.tenantId);
284
288
  ensureOrganizationScope(ctx, record.organizationId);
289
+ let capacityUnit;
285
290
  if (parsed.capacityUnitValue !== void 0) {
286
291
  const unitValue = typeof parsed.capacityUnitValue === "string" ? parsed.capacityUnitValue.trim() : "";
287
- if (!unitValue) {
288
- record.capacityUnitValue = null;
289
- record.capacityUnitName = null;
290
- record.capacityUnitColor = null;
291
- record.capacityUnitIcon = null;
292
- } else {
293
- const unitSnapshot = await resolveCapacityUnit(
294
- em,
295
- { tenantId: record.tenantId, organizationId: record.organizationId },
296
- unitValue
297
- );
298
- record.capacityUnitValue = unitSnapshot.value;
299
- record.capacityUnitName = unitSnapshot.name;
300
- record.capacityUnitColor = unitSnapshot.color;
301
- record.capacityUnitIcon = unitSnapshot.icon;
302
- }
292
+ capacityUnit = unitValue ? await resolveCapacityUnit(
293
+ em,
294
+ { tenantId: record.tenantId, organizationId: record.organizationId },
295
+ unitValue
296
+ ) : null;
303
297
  }
304
- if (parsed.name !== void 0) record.name = parsed.name;
305
- if (parsed.description !== void 0) record.description = parsed.description ?? null;
306
- if (parsed.resourceTypeId !== void 0) record.resourceTypeId = parsed.resourceTypeId ?? null;
307
- if (parsed.capacity !== void 0) record.capacity = parsed.capacity ?? null;
308
- if (parsed.appearanceIcon !== void 0) record.appearanceIcon = parsed.appearanceIcon ?? null;
309
- if (parsed.appearanceColor !== void 0) record.appearanceColor = parsed.appearanceColor ?? null;
310
- if (parsed.availabilityRuleSetId !== void 0) record.availabilityRuleSetId = parsed.availabilityRuleSetId ?? null;
311
- record.updatedAt = /* @__PURE__ */ new Date();
312
- if (parsed.isActive !== void 0) record.isActive = parsed.isActive;
313
- await em.flush();
314
- await syncResourcesResourceTags(em, {
315
- resourceId: record.id,
316
- organizationId: record.organizationId,
317
- tenantId: record.tenantId,
318
- tagIds: parsed.tags
319
- });
320
- await em.flush();
298
+ await withAtomicFlush(em, [
299
+ () => {
300
+ if (parsed.capacityUnitValue !== void 0) {
301
+ record.capacityUnitValue = capacityUnit?.value ?? null;
302
+ record.capacityUnitName = capacityUnit?.name ?? null;
303
+ record.capacityUnitColor = capacityUnit?.color ?? null;
304
+ record.capacityUnitIcon = capacityUnit?.icon ?? null;
305
+ }
306
+ if (parsed.name !== void 0) record.name = parsed.name;
307
+ if (parsed.description !== void 0) record.description = parsed.description ?? null;
308
+ if (parsed.resourceTypeId !== void 0) record.resourceTypeId = parsed.resourceTypeId ?? null;
309
+ if (parsed.capacity !== void 0) record.capacity = parsed.capacity ?? null;
310
+ if (parsed.appearanceIcon !== void 0) record.appearanceIcon = parsed.appearanceIcon ?? null;
311
+ if (parsed.appearanceColor !== void 0) record.appearanceColor = parsed.appearanceColor ?? null;
312
+ if (parsed.availabilityRuleSetId !== void 0) record.availabilityRuleSetId = parsed.availabilityRuleSetId ?? null;
313
+ record.updatedAt = /* @__PURE__ */ new Date();
314
+ if (parsed.isActive !== void 0) record.isActive = parsed.isActive;
315
+ },
316
+ () => syncResourcesResourceTags(em, {
317
+ resourceId: record.id,
318
+ organizationId: record.organizationId,
319
+ tenantId: record.tenantId,
320
+ tagIds: parsed.tags
321
+ })
322
+ ], { transaction: true });
321
323
  const dataEngine = ctx.container.resolve("dataEngine");
322
324
  await setCustomFieldsIfAny({
323
325
  dataEngine,
@@ -402,28 +404,30 @@ const updateResourceCommand = {
402
404
  const em = ctx.container.resolve("em").fork();
403
405
  const record = await em.findOne(ResourcesResource, { id: before.id });
404
406
  if (!record) return;
405
- record.name = before.name;
406
- record.description = before.description ?? null;
407
- record.resourceTypeId = before.resourceTypeId ?? null;
408
- record.capacity = before.capacity ?? null;
409
- record.capacityUnitValue = before.capacityUnitValue ?? null;
410
- record.capacityUnitName = before.capacityUnitName ?? null;
411
- record.capacityUnitColor = before.capacityUnitColor ?? null;
412
- record.capacityUnitIcon = before.capacityUnitIcon ?? null;
413
- record.appearanceIcon = before.appearanceIcon ?? null;
414
- record.appearanceColor = before.appearanceColor ?? null;
415
- record.isActive = before.isActive;
416
- record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
417
- record.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null;
418
- record.updatedAt = /* @__PURE__ */ new Date();
419
- await em.flush();
420
- await syncResourcesResourceTags(em, {
421
- resourceId: record.id,
422
- organizationId: record.organizationId,
423
- tenantId: record.tenantId,
424
- tagIds: before.tags
425
- });
426
- await em.flush();
407
+ await withAtomicFlush(em, [
408
+ () => {
409
+ record.name = before.name;
410
+ record.description = before.description ?? null;
411
+ record.resourceTypeId = before.resourceTypeId ?? null;
412
+ record.capacity = before.capacity ?? null;
413
+ record.capacityUnitValue = before.capacityUnitValue ?? null;
414
+ record.capacityUnitName = before.capacityUnitName ?? null;
415
+ record.capacityUnitColor = before.capacityUnitColor ?? null;
416
+ record.capacityUnitIcon = before.capacityUnitIcon ?? null;
417
+ record.appearanceIcon = before.appearanceIcon ?? null;
418
+ record.appearanceColor = before.appearanceColor ?? null;
419
+ record.isActive = before.isActive;
420
+ record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
421
+ record.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null;
422
+ record.updatedAt = /* @__PURE__ */ new Date();
423
+ },
424
+ () => syncResourcesResourceTags(em, {
425
+ resourceId: record.id,
426
+ organizationId: record.organizationId,
427
+ tenantId: record.tenantId,
428
+ tagIds: before.tags
429
+ })
430
+ ], { transaction: true });
427
431
  const dataEngine = ctx.container.resolve("dataEngine");
428
432
  const customBefore = payload.customBefore ?? fallbackCustomBefore ?? void 0;
429
433
  const customAfter = payload.customAfter ?? fallbackCustomAfter ?? void 0;
@@ -523,52 +527,56 @@ const deleteResourceCommand = {
523
527
  const fallbackCustomBefore = before.customFields ?? null;
524
528
  const em = ctx.container.resolve("em").fork();
525
529
  let record = await em.findOne(ResourcesResource, { id: before.id });
526
- if (!record) {
527
- record = em.create(ResourcesResource, {
528
- id: before.id,
529
- tenantId: before.tenantId,
530
- organizationId: before.organizationId,
531
- name: before.name,
532
- description: before.description ?? null,
533
- resourceTypeId: before.resourceTypeId ?? null,
534
- capacity: before.capacity ?? null,
535
- capacityUnitValue: before.capacityUnitValue ?? null,
536
- capacityUnitName: before.capacityUnitName ?? null,
537
- capacityUnitColor: before.capacityUnitColor ?? null,
538
- capacityUnitIcon: before.capacityUnitIcon ?? null,
539
- appearanceIcon: before.appearanceIcon ?? null,
540
- appearanceColor: before.appearanceColor ?? null,
541
- isActive: before.isActive,
542
- availabilityRuleSetId: before.availabilityRuleSetId ?? null,
543
- deletedAt: null,
544
- createdAt: /* @__PURE__ */ new Date(),
545
- updatedAt: /* @__PURE__ */ new Date()
546
- });
547
- em.persist(record);
548
- } else {
549
- record.name = before.name;
550
- record.description = before.description ?? null;
551
- record.resourceTypeId = before.resourceTypeId ?? null;
552
- record.capacity = before.capacity ?? null;
553
- record.capacityUnitValue = before.capacityUnitValue ?? null;
554
- record.capacityUnitName = before.capacityUnitName ?? null;
555
- record.capacityUnitColor = before.capacityUnitColor ?? null;
556
- record.capacityUnitIcon = before.capacityUnitIcon ?? null;
557
- record.appearanceIcon = before.appearanceIcon ?? null;
558
- record.appearanceColor = before.appearanceColor ?? null;
559
- record.isActive = before.isActive;
560
- record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
561
- record.deletedAt = null;
562
- record.updatedAt = /* @__PURE__ */ new Date();
563
- }
564
- await em.flush();
565
- await syncResourcesResourceTags(em, {
566
- resourceId: record.id,
567
- organizationId: record.organizationId,
568
- tenantId: record.tenantId,
569
- tagIds: before.tags
570
- });
571
- await em.flush();
530
+ await withAtomicFlush(em, [
531
+ async () => {
532
+ if (!record) {
533
+ record = em.create(ResourcesResource, {
534
+ id: before.id,
535
+ tenantId: before.tenantId,
536
+ organizationId: before.organizationId,
537
+ name: before.name,
538
+ description: before.description ?? null,
539
+ resourceTypeId: before.resourceTypeId ?? null,
540
+ capacity: before.capacity ?? null,
541
+ capacityUnitValue: before.capacityUnitValue ?? null,
542
+ capacityUnitName: before.capacityUnitName ?? null,
543
+ capacityUnitColor: before.capacityUnitColor ?? null,
544
+ capacityUnitIcon: before.capacityUnitIcon ?? null,
545
+ appearanceIcon: before.appearanceIcon ?? null,
546
+ appearanceColor: before.appearanceColor ?? null,
547
+ isActive: before.isActive,
548
+ availabilityRuleSetId: before.availabilityRuleSetId ?? null,
549
+ deletedAt: null,
550
+ createdAt: /* @__PURE__ */ new Date(),
551
+ updatedAt: /* @__PURE__ */ new Date()
552
+ });
553
+ em.persist(record);
554
+ } else {
555
+ record.name = before.name;
556
+ record.description = before.description ?? null;
557
+ record.resourceTypeId = before.resourceTypeId ?? null;
558
+ record.capacity = before.capacity ?? null;
559
+ record.capacityUnitValue = before.capacityUnitValue ?? null;
560
+ record.capacityUnitName = before.capacityUnitName ?? null;
561
+ record.capacityUnitColor = before.capacityUnitColor ?? null;
562
+ record.capacityUnitIcon = before.capacityUnitIcon ?? null;
563
+ record.appearanceIcon = before.appearanceIcon ?? null;
564
+ record.appearanceColor = before.appearanceColor ?? null;
565
+ record.isActive = before.isActive;
566
+ record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
567
+ record.deletedAt = null;
568
+ record.updatedAt = /* @__PURE__ */ new Date();
569
+ }
570
+ await em.flush();
571
+ },
572
+ () => syncResourcesResourceTags(em, {
573
+ resourceId: record.id,
574
+ organizationId: record.organizationId,
575
+ tenantId: record.tenantId,
576
+ tagIds: before.tags
577
+ })
578
+ ], { transaction: true });
579
+ const resolvedRecord = record;
572
580
  const dataEngine = ctx.container.resolve("dataEngine");
573
581
  const customBefore = payload.customBefore ?? fallbackCustomBefore ?? void 0;
574
582
  if (customBefore) {
@@ -576,20 +584,20 @@ const deleteResourceCommand = {
576
584
  await setCustomFieldsIfAny({
577
585
  dataEngine,
578
586
  entityId: E.resources.resources_resource,
579
- recordId: record.id,
580
- tenantId: record.tenantId,
581
- organizationId: record.organizationId,
587
+ recordId: resolvedRecord.id,
588
+ tenantId: resolvedRecord.tenantId,
589
+ organizationId: resolvedRecord.organizationId,
582
590
  values: reset
583
591
  });
584
592
  }
585
593
  await emitCrudUndoSideEffects({
586
594
  dataEngine,
587
595
  action: "created",
588
- entity: record,
596
+ entity: resolvedRecord,
589
597
  identifiers: {
590
- id: record.id,
591
- organizationId: record.organizationId,
592
- tenantId: record.tenantId
598
+ id: resolvedRecord.id,
599
+ organizationId: resolvedRecord.organizationId,
600
+ tenantId: resolvedRecord.tenantId
593
601
  },
594
602
  events: resourcesResourceCrudEvents,
595
603
  indexer: resourceCrudIndexer