@open-mercato/core 0.6.4-develop.4210.1.d412061cfe → 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
@@ -0,0 +1,504 @@
1
+ import type { CommandHandler } from '@open-mercato/shared/lib/commands'
2
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
8
+ import type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
9
+ import { StaffTimeEntry, StaffTimeProject, type StaffTimeEntrySource } from '../data/entities'
10
+
11
+ const timeEntryCrudIndexer: CrudIndexerConfig<StaffTimeEntry> = {
12
+ entityType: 'staff:staff_time_entry',
13
+ }
14
+ import {
15
+ staffTimeEntryCreateSchema,
16
+ staffTimeEntryUpdateSchema,
17
+ type StaffTimeEntryCreateInput,
18
+ type StaffTimeEntryUpdateInput,
19
+ } from '../data/validators'
20
+ import { staffTimeEntryCrudEvents } from '../lib/crud'
21
+ import { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'
22
+ import { getStaffMemberByUserId } from '../lib/staffMemberResolver'
23
+
24
+ type RbacServiceLike = {
25
+ userHasAllFeatures: (
26
+ userId: string,
27
+ required: string[],
28
+ scope: { tenantId: string | null; organizationId: string | null },
29
+ ) => Promise<boolean>
30
+ }
31
+
32
+ /**
33
+ * Returns true when the caller holds `staff.timesheets.manage_all`, honoring
34
+ * wildcard ACL grants (`staff.*`, `*`) and the super-admin flag via the cached
35
+ * rbacService. Returns false when no auth context (e.g. system/CLI ctx) so
36
+ * write paths that lack a caller identity are NOT silently elevated.
37
+ */
38
+ async function callerHasManageAll(ctx: {
39
+ auth?: { sub?: string | null; tenantId?: string | null; orgId?: string | null } | null
40
+ container: { resolve: (token: string) => unknown }
41
+ }): Promise<boolean> {
42
+ const userId = ctx.auth?.sub
43
+ if (!userId) return false
44
+ try {
45
+ const rbac = ctx.container.resolve('rbacService') as RbacServiceLike | undefined
46
+ if (!rbac?.userHasAllFeatures) return false
47
+ return await rbac.userHasAllFeatures(
48
+ userId,
49
+ ['staff.timesheets.manage_all'],
50
+ { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
51
+ )
52
+ } catch {
53
+ return false
54
+ }
55
+ }
56
+
57
+ async function resolveCallerStaffMemberId(
58
+ em: EntityManager,
59
+ ctx: { auth?: { sub?: string | null; tenantId?: string | null; orgId?: string | null } | null },
60
+ ): Promise<string | null> {
61
+ const userId = ctx.auth?.sub
62
+ if (!userId) return null
63
+ const member = await getStaffMemberByUserId(
64
+ em,
65
+ userId,
66
+ ctx.auth?.tenantId ?? null,
67
+ ctx.auth?.orgId ?? null,
68
+ )
69
+ return member?.id ?? null
70
+ }
71
+
72
+ /**
73
+ * Verifies the referenced time project exists and is in-scope (same tenant + org,
74
+ * not soft-deleted). Throws 422 if the ID is provided but unresolvable.
75
+ * No-op when projectId is null/undefined (timeProjectId is optional on entries).
76
+ */
77
+ async function assertTimeProjectInScope(
78
+ em: EntityManager,
79
+ projectId: string | null | undefined,
80
+ tenantId: string,
81
+ organizationId: string,
82
+ ): Promise<void> {
83
+ if (!projectId) return
84
+ const exists = await em.findOne(
85
+ StaffTimeProject,
86
+ { id: projectId, tenantId, organizationId, deletedAt: null },
87
+ { fields: ['id'] },
88
+ )
89
+ if (!exists) {
90
+ const { translate } = await resolveTranslations()
91
+ throw new CrudHttpError(422, {
92
+ error: translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.'),
93
+ fieldErrors: {
94
+ timeProjectId: translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.'),
95
+ },
96
+ })
97
+ }
98
+ }
99
+
100
+ type TimeEntrySnapshot = {
101
+ id: string
102
+ tenantId: string
103
+ organizationId: string
104
+ staffMemberId: string
105
+ date: string
106
+ durationMinutes: number
107
+ startedAt: string | null
108
+ endedAt: string | null
109
+ notes: string | null
110
+ timeProjectId: string | null
111
+ customerId: string | null
112
+ dealId: string | null
113
+ orderId: string | null
114
+ source: string
115
+ deletedAt: string | null
116
+ }
117
+
118
+ type TimeEntryUndoPayload = {
119
+ before?: TimeEntrySnapshot | null
120
+ after?: TimeEntrySnapshot | null
121
+ }
122
+
123
+ async function loadTimeEntrySnapshot(em: EntityManager, id: string): Promise<TimeEntrySnapshot | null> {
124
+ const entry = await findOneWithDecryption(em, StaffTimeEntry, { id }, undefined, { tenantId: null, organizationId: null })
125
+ if (!entry) return null
126
+ return {
127
+ id: entry.id,
128
+ tenantId: entry.tenantId,
129
+ organizationId: entry.organizationId,
130
+ staffMemberId: entry.staffMemberId,
131
+ date: entry.date instanceof Date ? entry.date.toISOString().split('T')[0] : String(entry.date),
132
+ durationMinutes: entry.durationMinutes,
133
+ startedAt: entry.startedAt ? entry.startedAt.toISOString() : null,
134
+ endedAt: entry.endedAt ? entry.endedAt.toISOString() : null,
135
+ notes: entry.notes ?? null,
136
+ timeProjectId: entry.timeProjectId ?? null,
137
+ customerId: entry.customerId ?? null,
138
+ dealId: entry.dealId ?? null,
139
+ orderId: entry.orderId ?? null,
140
+ source: entry.source,
141
+ deletedAt: entry.deletedAt ? entry.deletedAt.toISOString() : null,
142
+ }
143
+ }
144
+
145
+ const createTimeEntryCommand: CommandHandler<StaffTimeEntryCreateInput, { timeEntryId: string }> = {
146
+ id: 'staff.timesheets.time_entries.create',
147
+ async execute(rawInput, ctx) {
148
+ const parsed = staffTimeEntryCreateSchema.parse(rawInput)
149
+ ensureTenantScope(ctx, parsed.tenantId)
150
+ ensureOrganizationScope(ctx, parsed.organizationId)
151
+
152
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
153
+
154
+ // Ownership enforcement: callers without `staff.timesheets.manage_all`
155
+ // can only create entries attributed to themselves. Silent override
156
+ // (mirrors `bulk/route.ts` behavior) so the request body's staffMemberId
157
+ // can't forge an entry under a colleague's identity.
158
+ let effectiveStaffMemberId = parsed.staffMemberId
159
+ if (!(await callerHasManageAll(ctx))) {
160
+ const callerStaffMemberId = await resolveCallerStaffMemberId(em, ctx)
161
+ if (!callerStaffMemberId) {
162
+ const { translate } = await resolveTranslations()
163
+ throw new CrudHttpError(403, {
164
+ error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.'),
165
+ })
166
+ }
167
+ effectiveStaffMemberId = callerStaffMemberId
168
+ }
169
+
170
+ // Validate referenced timeProjectId is in-scope before persisting.
171
+ // Without this check a foreign or stale UUID would produce a dangling reference.
172
+ await assertTimeProjectInScope(em, parsed.timeProjectId ?? null, parsed.tenantId, parsed.organizationId)
173
+
174
+ const now = new Date()
175
+ const entry = em.create(StaffTimeEntry, {
176
+ tenantId: parsed.tenantId,
177
+ organizationId: parsed.organizationId,
178
+ staffMemberId: effectiveStaffMemberId,
179
+ date: parsed.date,
180
+ durationMinutes: parsed.durationMinutes,
181
+ startedAt: parsed.startedAt ?? null,
182
+ endedAt: parsed.endedAt ?? null,
183
+ notes: parsed.notes ?? null,
184
+ timeProjectId: parsed.timeProjectId ?? null,
185
+ customerId: parsed.customerId ?? null,
186
+ dealId: parsed.dealId ?? null,
187
+ orderId: parsed.orderId ?? null,
188
+ source: parsed.source ?? 'manual',
189
+ createdAt: now,
190
+ updatedAt: now,
191
+ deletedAt: null,
192
+ })
193
+ em.persist(entry)
194
+ await em.flush()
195
+
196
+ await emitCrudSideEffects({
197
+ dataEngine: ctx.container.resolve('dataEngine'),
198
+ action: 'created',
199
+ entity: entry,
200
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
201
+ events: staffTimeEntryCrudEvents,
202
+ indexer: timeEntryCrudIndexer,
203
+ })
204
+
205
+ return { timeEntryId: entry.id }
206
+ },
207
+ captureAfter: async (_input, result, ctx) => {
208
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
209
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
210
+ if (!snapshot) return null
211
+ return { snapshot }
212
+ },
213
+ buildLog: async ({ result, ctx }) => {
214
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
215
+ const snapshot = await loadTimeEntrySnapshot(em, result.timeEntryId)
216
+ if (!snapshot) return null
217
+ const { translate } = await resolveTranslations()
218
+ return {
219
+ actionLabel: translate('staff.audit.timesheets.time_entries.create', 'Create time entry'),
220
+ resourceKind: 'staff.timesheets.time_entry',
221
+ resourceId: snapshot.id,
222
+ tenantId: snapshot.tenantId,
223
+ organizationId: snapshot.organizationId,
224
+ snapshotAfter: snapshot,
225
+ payload: {
226
+ undo: {
227
+ after: snapshot,
228
+ } satisfies TimeEntryUndoPayload,
229
+ },
230
+ }
231
+ },
232
+ undo: async ({ logEntry, ctx }) => {
233
+ const payload = extractUndoPayload<TimeEntryUndoPayload>(logEntry)
234
+ const after = payload?.after
235
+ if (!after) return
236
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
237
+ const entry = await em.findOne(StaffTimeEntry, { id: after.id })
238
+ if (entry) {
239
+ entry.deletedAt = new Date()
240
+ await em.flush()
241
+
242
+ await emitCrudUndoSideEffects({
243
+ dataEngine: ctx.container.resolve('dataEngine'),
244
+ action: 'deleted',
245
+ entity: entry,
246
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
247
+ events: staffTimeEntryCrudEvents,
248
+ })
249
+ }
250
+ },
251
+ }
252
+
253
+ const updateTimeEntryCommand: CommandHandler<StaffTimeEntryUpdateInput, { timeEntryId: string }> = {
254
+ id: 'staff.timesheets.time_entries.update',
255
+ async prepare(rawInput, ctx) {
256
+ const parsed = staffTimeEntryUpdateSchema.parse(rawInput)
257
+ const em = (ctx.container.resolve('em') as EntityManager)
258
+ const snapshot = await loadTimeEntrySnapshot(em, parsed.id)
259
+ if (!snapshot) return {}
260
+ return { before: snapshot }
261
+ },
262
+ async execute(rawInput, ctx) {
263
+ const parsed = staffTimeEntryUpdateSchema.parse(rawInput)
264
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
265
+ const entry = await findOneWithDecryption(
266
+ em,
267
+ StaffTimeEntry,
268
+ { id: parsed.id, deletedAt: null },
269
+ undefined,
270
+ { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
271
+ )
272
+ if (!entry) throw new CrudHttpError(404, { error: 'Time entry not found.' })
273
+ ensureTenantScope(ctx, entry.tenantId)
274
+ ensureOrganizationScope(ctx, entry.organizationId)
275
+
276
+ // Ownership enforcement: callers without `staff.timesheets.manage_all`
277
+ // can only update entries they own.
278
+ if (!(await callerHasManageAll(ctx))) {
279
+ const callerStaffMemberId = await resolveCallerStaffMemberId(em, ctx)
280
+ if (!callerStaffMemberId || entry.staffMemberId !== callerStaffMemberId) {
281
+ const { translate } = await resolveTranslations()
282
+ throw new CrudHttpError(403, {
283
+ error: translate('staff.timesheets.errors.notOwner', 'You can only manage your own time entries.'),
284
+ })
285
+ }
286
+ }
287
+
288
+ // Validate referenced timeProjectId is in-scope when it's being changed to a non-null value.
289
+ if (parsed.timeProjectId !== undefined && parsed.timeProjectId !== null) {
290
+ await assertTimeProjectInScope(em, parsed.timeProjectId, entry.tenantId, entry.organizationId)
291
+ }
292
+
293
+ if (parsed.date !== undefined) entry.date = parsed.date
294
+ if (parsed.durationMinutes !== undefined) entry.durationMinutes = parsed.durationMinutes
295
+ if (parsed.timeProjectId !== undefined) entry.timeProjectId = parsed.timeProjectId ?? null
296
+ if (parsed.customerId !== undefined) entry.customerId = parsed.customerId ?? null
297
+ if (parsed.dealId !== undefined) entry.dealId = parsed.dealId ?? null
298
+ if (parsed.orderId !== undefined) entry.orderId = parsed.orderId ?? null
299
+ if (parsed.notes !== undefined) entry.notes = parsed.notes ?? null
300
+ entry.updatedAt = new Date()
301
+ await em.flush()
302
+
303
+ await emitCrudSideEffects({
304
+ dataEngine: ctx.container.resolve('dataEngine'),
305
+ action: 'updated',
306
+ entity: entry,
307
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
308
+ events: staffTimeEntryCrudEvents,
309
+ indexer: timeEntryCrudIndexer,
310
+ })
311
+
312
+ return { timeEntryId: entry.id }
313
+ },
314
+ buildLog: async ({ snapshots, ctx }) => {
315
+ const before = snapshots.before as TimeEntrySnapshot | undefined
316
+ if (!before) return null
317
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
318
+ const after = await loadTimeEntrySnapshot(em, before.id)
319
+ if (!after) return null
320
+ const changes = buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [
321
+ 'date',
322
+ 'durationMinutes',
323
+ 'timeProjectId',
324
+ 'customerId',
325
+ 'dealId',
326
+ 'orderId',
327
+ 'notes',
328
+ 'deletedAt',
329
+ ])
330
+ const { translate } = await resolveTranslations()
331
+ return {
332
+ actionLabel: translate('staff.audit.timesheets.time_entries.update', 'Update time entry'),
333
+ resourceKind: 'staff.timesheets.time_entry',
334
+ resourceId: before.id,
335
+ tenantId: before.tenantId,
336
+ organizationId: before.organizationId,
337
+ snapshotBefore: before,
338
+ snapshotAfter: after,
339
+ changes,
340
+ payload: {
341
+ undo: {
342
+ before,
343
+ after,
344
+ } satisfies TimeEntryUndoPayload,
345
+ },
346
+ }
347
+ },
348
+ undo: async ({ logEntry, ctx }) => {
349
+ const payload = extractUndoPayload<TimeEntryUndoPayload>(logEntry)
350
+ const before = payload?.before
351
+ if (!before) return
352
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
353
+ const entry = await em.findOne(StaffTimeEntry, { id: before.id })
354
+ if (!entry) return
355
+ entry.date = before.date as unknown as Date
356
+ entry.durationMinutes = before.durationMinutes
357
+ entry.timeProjectId = before.timeProjectId ?? null
358
+ entry.customerId = before.customerId ?? null
359
+ entry.dealId = before.dealId ?? null
360
+ entry.orderId = before.orderId ?? null
361
+ entry.notes = before.notes ?? null
362
+ entry.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null
363
+ entry.updatedAt = new Date()
364
+ await em.flush()
365
+
366
+ await emitCrudUndoSideEffects({
367
+ dataEngine: ctx.container.resolve('dataEngine'),
368
+ action: 'updated',
369
+ entity: entry,
370
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
371
+ events: staffTimeEntryCrudEvents,
372
+ indexer: timeEntryCrudIndexer,
373
+ })
374
+ },
375
+ }
376
+
377
+ const deleteTimeEntryCommand: CommandHandler<{ id?: string }, { timeEntryId: string }> = {
378
+ id: 'staff.timesheets.time_entries.delete',
379
+ async prepare(input, ctx) {
380
+ const id = input?.id
381
+ if (!id) throw new CrudHttpError(400, { error: 'Time entry id is required.' })
382
+ const em = (ctx.container.resolve('em') as EntityManager)
383
+ const snapshot = await loadTimeEntrySnapshot(em, id)
384
+ if (!snapshot) return {}
385
+ return { before: snapshot }
386
+ },
387
+ async execute(input, ctx) {
388
+ const id = input?.id
389
+ if (!id) throw new CrudHttpError(400, { error: 'Time entry id is required.' })
390
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
391
+ const entry = await findOneWithDecryption(
392
+ em,
393
+ StaffTimeEntry,
394
+ { id, deletedAt: null },
395
+ undefined,
396
+ { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },
397
+ )
398
+ if (!entry) throw new CrudHttpError(404, { error: 'Time entry not found.' })
399
+ ensureTenantScope(ctx, entry.tenantId)
400
+ ensureOrganizationScope(ctx, entry.organizationId)
401
+
402
+ // Ownership enforcement: callers without `staff.timesheets.manage_all`
403
+ // can only delete entries they own.
404
+ if (!(await callerHasManageAll(ctx))) {
405
+ const callerStaffMemberId = await resolveCallerStaffMemberId(em, ctx)
406
+ if (!callerStaffMemberId || entry.staffMemberId !== callerStaffMemberId) {
407
+ const { translate } = await resolveTranslations()
408
+ throw new CrudHttpError(403, {
409
+ error: translate('staff.timesheets.errors.notOwner', 'You can only manage your own time entries.'),
410
+ })
411
+ }
412
+ }
413
+
414
+ entry.deletedAt = new Date()
415
+ entry.updatedAt = new Date()
416
+ await em.flush()
417
+
418
+ await emitCrudSideEffects({
419
+ dataEngine: ctx.container.resolve('dataEngine'),
420
+ action: 'deleted',
421
+ entity: entry,
422
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
423
+ events: staffTimeEntryCrudEvents,
424
+ indexer: timeEntryCrudIndexer,
425
+ })
426
+
427
+ return { timeEntryId: entry.id }
428
+ },
429
+ buildLog: async ({ snapshots }) => {
430
+ const before = snapshots.before as TimeEntrySnapshot | undefined
431
+ if (!before) return null
432
+ const { translate } = await resolveTranslations()
433
+ return {
434
+ actionLabel: translate('staff.audit.timesheets.time_entries.delete', 'Delete time entry'),
435
+ resourceKind: 'staff.timesheets.time_entry',
436
+ resourceId: before.id,
437
+ tenantId: before.tenantId,
438
+ organizationId: before.organizationId,
439
+ snapshotBefore: before,
440
+ payload: {
441
+ undo: {
442
+ before,
443
+ } satisfies TimeEntryUndoPayload,
444
+ },
445
+ }
446
+ },
447
+ undo: async ({ logEntry, ctx }) => {
448
+ const payload = extractUndoPayload<TimeEntryUndoPayload>(logEntry)
449
+ const before = payload?.before
450
+ if (!before) return
451
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
452
+ let entry = await em.findOne(StaffTimeEntry, { id: before.id })
453
+ if (!entry) {
454
+ entry = em.create(StaffTimeEntry, {
455
+ id: before.id,
456
+ tenantId: before.tenantId,
457
+ organizationId: before.organizationId,
458
+ staffMemberId: before.staffMemberId,
459
+ date: before.date as unknown as Date,
460
+ durationMinutes: before.durationMinutes,
461
+ startedAt: before.startedAt ? new Date(before.startedAt) : null,
462
+ endedAt: before.endedAt ? new Date(before.endedAt) : null,
463
+ notes: before.notes ?? null,
464
+ timeProjectId: before.timeProjectId ?? null,
465
+ customerId: before.customerId ?? null,
466
+ dealId: before.dealId ?? null,
467
+ orderId: before.orderId ?? null,
468
+ source: (before.source ?? 'manual') as StaffTimeEntrySource,
469
+ deletedAt: null,
470
+ createdAt: new Date(),
471
+ updatedAt: new Date(),
472
+ })
473
+ em.persist(entry)
474
+ } else {
475
+ entry.staffMemberId = before.staffMemberId
476
+ entry.date = before.date as unknown as Date
477
+ entry.durationMinutes = before.durationMinutes
478
+ entry.startedAt = before.startedAt ? new Date(before.startedAt) : null
479
+ entry.endedAt = before.endedAt ? new Date(before.endedAt) : null
480
+ entry.notes = before.notes ?? null
481
+ entry.timeProjectId = before.timeProjectId ?? null
482
+ entry.customerId = before.customerId ?? null
483
+ entry.dealId = before.dealId ?? null
484
+ entry.orderId = before.orderId ?? null
485
+ entry.source = (before.source ?? 'manual') as StaffTimeEntrySource
486
+ entry.deletedAt = null
487
+ entry.updatedAt = new Date()
488
+ }
489
+ await em.flush()
490
+
491
+ await emitCrudUndoSideEffects({
492
+ dataEngine: ctx.container.resolve('dataEngine'),
493
+ action: 'created',
494
+ entity: entry,
495
+ identifiers: { id: entry.id, organizationId: entry.organizationId, tenantId: entry.tenantId },
496
+ events: staffTimeEntryCrudEvents,
497
+ indexer: timeEntryCrudIndexer,
498
+ })
499
+ },
500
+ }
501
+
502
+ registerCommand(createTimeEntryCommand)
503
+ registerCommand(updateTimeEntryCommand)
504
+ registerCommand(deleteTimeEntryCommand)