@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7180.1.9717fbbb43

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 (808) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +14 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
  4. package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
  5. package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +16 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +2 -2
  8. package/dist/generated/entities/staff_time_report/index.js +57 -0
  9. package/dist/generated/entities/staff_time_report/index.js.map +7 -0
  10. package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
  11. package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
  12. package/dist/generated/entities/staff_time_report_event/index.js +21 -0
  13. package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
  14. package/dist/generated/entities/staff_time_report_project/index.js +15 -0
  15. package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
  16. package/dist/generated/entities/staff_time_tag/index.js +21 -0
  17. package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
  18. package/dist/generated/entities/staff_time_task/index.js +37 -0
  19. package/dist/generated/entities/staff_time_task/index.js.map +7 -0
  20. package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
  21. package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
  22. package/dist/generated/entities/staff_time_task_status/index.js +29 -0
  23. package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
  24. package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
  25. package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
  26. package/dist/generated/entities.ids.generated.js +11 -1
  27. package/dist/generated/entities.ids.generated.js.map +2 -2
  28. package/dist/generated/entity-fields-registry.js +149 -0
  29. package/dist/generated/entity-fields-registry.js.map +2 -2
  30. package/dist/helpers/integration/timesheetFixtures.js +7 -0
  31. package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
  32. package/dist/modules/auth/cli.js +16 -0
  33. package/dist/modules/auth/cli.js.map +2 -2
  34. package/dist/modules/auth/lib/backendChrome.js +1 -0
  35. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  36. package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
  37. package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
  38. package/dist/modules/customer_accounts/setup.js +1 -28
  39. package/dist/modules/customer_accounts/setup.js.map +2 -2
  40. package/dist/modules/data_sync/api/options.js +2 -0
  41. package/dist/modules/data_sync/api/options.js.map +2 -2
  42. package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
  43. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  44. package/dist/modules/data_sync/lib/start-controls.js +42 -0
  45. package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
  46. package/dist/modules/staff/acl.js +9 -1
  47. package/dist/modules/staff/acl.js.map +2 -2
  48. package/dist/modules/staff/ai-agents.js +142 -0
  49. package/dist/modules/staff/ai-agents.js.map +7 -0
  50. package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
  51. package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
  52. package/dist/modules/staff/ai-tools/types.js +54 -0
  53. package/dist/modules/staff/ai-tools/types.js.map +7 -0
  54. package/dist/modules/staff/ai-tools.js +8 -0
  55. package/dist/modules/staff/ai-tools.js.map +7 -0
  56. package/dist/modules/staff/analytics.js +84 -1
  57. package/dist/modules/staff/analytics.js.map +2 -2
  58. package/dist/modules/staff/api/guards.js +37 -3
  59. package/dist/modules/staff/api/guards.js.map +2 -2
  60. package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
  61. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  62. package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
  63. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  64. package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
  65. package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
  66. package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
  67. package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
  68. package/dist/modules/staff/api/team-members/self/route.js +1 -3
  69. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  70. package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
  71. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  72. package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
  73. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  74. package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
  75. package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
  76. package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
  77. package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
  78. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
  79. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
  80. package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
  81. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
  82. package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
  83. package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
  84. package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
  85. package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
  86. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
  87. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
  88. package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
  89. package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
  90. package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
  91. package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
  92. package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
  93. package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
  94. package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
  95. package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
  96. package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
  97. package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
  98. package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
  99. package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
  101. package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
  103. package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
  105. package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
  107. package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
  109. package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
  111. package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
  113. package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
  115. package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
  119. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
  121. package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
  122. package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
  123. package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
  124. package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
  125. package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
  126. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
  127. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
  128. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
  129. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
  130. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
  131. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  132. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
  133. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
  134. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
  135. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
  136. package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
  137. package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
  138. package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
  139. package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
  140. package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
  141. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  142. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
  143. package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
  144. package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
  145. package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
  146. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
  147. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
  148. package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
  149. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
  150. package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
  151. package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
  152. package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
  153. package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
  154. package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
  155. package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
  156. package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
  157. package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
  158. package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
  159. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
  160. package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
  161. package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
  162. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
  163. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
  164. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
  165. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
  166. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
  167. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
  168. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
  169. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
  170. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
  171. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
  172. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
  173. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
  174. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
  175. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
  176. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
  177. package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
  178. package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
  179. package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
  180. package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
  181. package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
  182. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
  183. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
  184. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
  185. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
  186. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
  187. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
  188. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
  189. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
  190. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
  191. package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
  192. package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
  193. package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
  194. package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
  195. package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
  196. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
  197. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
  198. package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
  199. package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
  200. package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
  201. package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
  202. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
  203. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
  204. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
  205. package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
  206. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
  207. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  208. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
  209. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
  210. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
  211. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
  212. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
  213. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
  214. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
  215. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
  216. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
  217. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
  218. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
  219. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
  220. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
  221. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
  222. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
  223. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  224. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
  225. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
  226. package/dist/modules/staff/ce.js +40 -0
  227. package/dist/modules/staff/ce.js.map +2 -2
  228. package/dist/modules/staff/cli.js +166 -1
  229. package/dist/modules/staff/cli.js.map +2 -2
  230. package/dist/modules/staff/commands/index.js +5 -0
  231. package/dist/modules/staff/commands/index.js.map +2 -2
  232. package/dist/modules/staff/commands/timesheets-entries.js +569 -21
  233. package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
  234. package/dist/modules/staff/commands/timesheets-projects.js +450 -7
  235. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  236. package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
  237. package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
  238. package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
  239. package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
  240. package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
  241. package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
  242. package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
  243. package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
  244. package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
  245. package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
  246. package/dist/modules/staff/data/enrichers.js +426 -12
  247. package/dist/modules/staff/data/enrichers.js.map +2 -2
  248. package/dist/modules/staff/data/entities.js +552 -3
  249. package/dist/modules/staff/data/entities.js.map +2 -2
  250. package/dist/modules/staff/data/extensions.js +41 -0
  251. package/dist/modules/staff/data/extensions.js.map +7 -0
  252. package/dist/modules/staff/data/validators.js +254 -9
  253. package/dist/modules/staff/data/validators.js.map +2 -2
  254. package/dist/modules/staff/di.js +42 -1
  255. package/dist/modules/staff/di.js.map +2 -2
  256. package/dist/modules/staff/events.js +84 -6
  257. package/dist/modules/staff/events.js.map +2 -2
  258. package/dist/modules/staff/events.payloads.js +158 -0
  259. package/dist/modules/staff/events.payloads.js.map +7 -0
  260. package/dist/modules/staff/extension-points.js +261 -0
  261. package/dist/modules/staff/extension-points.js.map +7 -0
  262. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
  263. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
  264. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
  265. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
  266. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
  267. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
  268. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
  269. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
  270. package/dist/modules/staff/lib/crud.js +11 -1
  271. package/dist/modules/staff/lib/crud.js.map +2 -2
  272. package/dist/modules/staff/lib/time-tracking/access.js +92 -0
  273. package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
  274. package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
  275. package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
  276. package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
  277. package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
  278. package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
  279. package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
  280. package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
  281. package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
  282. package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
  283. package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
  284. package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
  285. package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
  286. package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
  287. package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
  288. package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
  289. package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
  290. package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
  291. package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
  292. package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
  293. package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
  294. package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
  295. package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
  296. package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
  297. package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
  298. package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
  299. package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
  300. package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
  301. package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
  302. package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
  303. package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
  304. package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
  305. package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
  306. package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
  307. package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
  308. package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
  309. package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
  310. package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
  311. package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
  312. package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
  313. package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
  314. package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
  315. package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
  316. package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
  317. package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
  318. package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
  319. package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
  320. package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
  321. package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
  322. package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
  323. package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
  324. package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
  325. package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
  326. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
  327. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
  328. package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
  329. package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
  330. package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
  331. package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
  332. package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
  333. package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
  334. package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
  335. package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
  336. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
  337. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
  338. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
  339. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
  340. package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
  341. package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
  342. package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
  343. package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
  344. package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
  345. package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
  346. package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
  347. package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
  348. package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
  349. package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
  350. package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
  351. package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
  352. package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
  353. package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
  354. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
  355. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
  356. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
  357. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
  358. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
  359. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
  360. package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
  361. package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
  362. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
  363. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
  364. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
  365. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
  366. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
  367. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
  368. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
  369. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
  370. package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
  371. package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
  372. package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
  373. package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
  374. package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
  375. package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
  376. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
  377. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
  378. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
  379. package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
  380. package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
  381. package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
  382. package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
  383. package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
  384. package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
  385. package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
  386. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
  387. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
  388. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
  389. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
  390. package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
  391. package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
  392. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
  393. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
  394. package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
  395. package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
  396. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
  397. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
  398. package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
  399. package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
  400. package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
  401. package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
  402. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
  403. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
  404. package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
  405. package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
  406. package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
  407. package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
  408. package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
  409. package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
  410. package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
  411. package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
  412. package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
  413. package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
  414. package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
  415. package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
  416. package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
  417. package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
  418. package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
  419. package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
  420. package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
  421. package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
  422. package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
  423. package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
  424. package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
  425. package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
  426. package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
  427. package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
  428. package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
  429. package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
  430. package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
  431. package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
  432. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
  433. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
  434. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
  435. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
  436. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
  437. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
  438. package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
  439. package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
  440. package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
  441. package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
  442. package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
  443. package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
  444. package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
  445. package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
  446. package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
  447. package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
  448. package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
  449. package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
  450. package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
  451. package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
  452. package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
  453. package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
  454. package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
  455. package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
  456. package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
  457. package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
  458. package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
  459. package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
  460. package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
  461. package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
  462. package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
  463. package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
  464. package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
  465. package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
  466. package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
  467. package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
  468. package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
  469. package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
  470. package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
  471. package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
  472. package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
  473. package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
  474. package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
  475. package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
  476. package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
  477. package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
  478. package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
  479. package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
  480. package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
  481. package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
  482. package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
  483. package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
  484. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
  485. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
  486. package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
  487. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
  488. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
  489. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
  490. package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
  491. package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
  492. package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
  493. package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
  494. package/dist/modules/staff/notifications.client.js +16 -0
  495. package/dist/modules/staff/notifications.client.js.map +7 -0
  496. package/dist/modules/staff/notifications.handlers.js +80 -0
  497. package/dist/modules/staff/notifications.handlers.js.map +7 -0
  498. package/dist/modules/staff/notifications.js +168 -0
  499. package/dist/modules/staff/notifications.js.map +2 -2
  500. package/dist/modules/staff/search.js +159 -1
  501. package/dist/modules/staff/search.js.map +2 -2
  502. package/dist/modules/staff/setup.js +20 -1
  503. package/dist/modules/staff/setup.js.map +2 -2
  504. package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
  505. package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
  506. package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
  507. package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
  508. package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
  509. package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
  510. package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
  511. package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
  512. package/dist/modules/staff/translations.js +4 -1
  513. package/dist/modules/staff/translations.js.map +2 -2
  514. package/dist/modules/staff/widgets/components.js +7 -0
  515. package/dist/modules/staff/widgets/components.js.map +7 -0
  516. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
  517. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
  518. package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
  519. package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
  520. package/dist/modules/staff/widgets/notifications/index.js +5 -0
  521. package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
  522. package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
  523. package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
  524. package/generated/entities/staff_time_entry/index.ts +7 -0
  525. package/generated/entities/staff_time_entry_tag/index.ts +6 -0
  526. package/generated/entities/staff_time_project/index.ts +8 -0
  527. package/generated/entities/staff_time_report/index.ts +27 -0
  528. package/generated/entities/staff_time_report_entry/index.ts +12 -0
  529. package/generated/entities/staff_time_report_event/index.ts +9 -0
  530. package/generated/entities/staff_time_report_project/index.ts +6 -0
  531. package/generated/entities/staff_time_tag/index.ts +9 -0
  532. package/generated/entities/staff_time_task/index.ts +17 -0
  533. package/generated/entities/staff_time_task_comment/index.ts +9 -0
  534. package/generated/entities/staff_time_task_status/index.ts +13 -0
  535. package/generated/entities/staff_time_task_tag/index.ts +6 -0
  536. package/generated/entities.ids.generated.ts +11 -1
  537. package/generated/entity-fields-registry.ts +149 -0
  538. package/package.json +7 -7
  539. package/src/helpers/integration/timesheetFixtures.ts +31 -1
  540. package/src/modules/auth/README.md +1 -1
  541. package/src/modules/auth/cli.ts +35 -1
  542. package/src/modules/auth/i18n/de.json +7 -0
  543. package/src/modules/auth/i18n/en.json +7 -0
  544. package/src/modules/auth/i18n/es.json +7 -0
  545. package/src/modules/auth/i18n/ko.json +7 -0
  546. package/src/modules/auth/i18n/pl.json +7 -0
  547. package/src/modules/auth/lib/backendChrome.tsx +1 -0
  548. package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
  549. package/src/modules/customer_accounts/setup.ts +1 -45
  550. package/src/modules/data_sync/AGENTS.md +34 -0
  551. package/src/modules/data_sync/api/options.ts +2 -0
  552. package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
  553. package/src/modules/data_sync/i18n/de.json +1 -0
  554. package/src/modules/data_sync/i18n/en.json +1 -0
  555. package/src/modules/data_sync/i18n/es.json +1 -0
  556. package/src/modules/data_sync/i18n/ko.json +1 -0
  557. package/src/modules/data_sync/i18n/pl.json +1 -0
  558. package/src/modules/data_sync/lib/adapter.ts +45 -0
  559. package/src/modules/data_sync/lib/start-controls.ts +89 -0
  560. package/src/modules/staff/AGENTS.md +769 -1
  561. package/src/modules/staff/acl.ts +9 -0
  562. package/src/modules/staff/ai-agents.ts +189 -0
  563. package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
  564. package/src/modules/staff/ai-tools/types.ts +82 -0
  565. package/src/modules/staff/ai-tools.ts +13 -0
  566. package/src/modules/staff/analytics.ts +113 -0
  567. package/src/modules/staff/api/guards.ts +85 -3
  568. package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
  569. package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
  570. package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
  571. package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
  572. package/src/modules/staff/api/team-members/self/route.ts +0 -2
  573. package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
  574. package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
  575. package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
  576. package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
  577. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
  578. package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
  579. package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
  580. package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
  581. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
  582. package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
  583. package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
  584. package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
  585. package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
  586. package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
  587. package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
  588. package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
  589. package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
  590. package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
  591. package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
  592. package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
  593. package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
  594. package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
  595. package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
  596. package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
  597. package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
  598. package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
  599. package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
  600. package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
  601. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
  602. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
  603. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
  604. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
  605. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
  606. package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
  607. package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
  608. package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
  609. package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
  610. package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
  611. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
  612. package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
  613. package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
  614. package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
  615. package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
  616. package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
  617. package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
  618. package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
  619. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
  620. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
  621. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
  622. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
  623. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
  624. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
  625. package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
  626. package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
  627. package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
  628. package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
  629. package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
  630. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
  631. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
  632. package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
  633. package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
  634. package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
  635. package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
  636. package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
  637. package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
  638. package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
  639. package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
  640. package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
  641. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
  642. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
  643. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
  644. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
  645. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
  646. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
  647. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
  648. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
  649. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
  650. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
  651. package/src/modules/staff/ce.ts +63 -0
  652. package/src/modules/staff/cli.ts +207 -1
  653. package/src/modules/staff/commands/index.ts +5 -0
  654. package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
  655. package/src/modules/staff/commands/timesheets-projects.ts +593 -7
  656. package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
  657. package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
  658. package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
  659. package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
  660. package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
  661. package/src/modules/staff/data/enrichers.ts +675 -10
  662. package/src/modules/staff/data/entities.ts +494 -2
  663. package/src/modules/staff/data/extensions.ts +68 -0
  664. package/src/modules/staff/data/validators.ts +348 -3
  665. package/src/modules/staff/di.ts +96 -0
  666. package/src/modules/staff/events.payloads.ts +268 -0
  667. package/src/modules/staff/events.ts +87 -5
  668. package/src/modules/staff/extension-points.ts +308 -0
  669. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
  670. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
  671. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
  672. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
  673. package/src/modules/staff/i18n/de.json +899 -0
  674. package/src/modules/staff/i18n/en.json +899 -0
  675. package/src/modules/staff/i18n/es.json +899 -0
  676. package/src/modules/staff/i18n/ko.json +899 -0
  677. package/src/modules/staff/i18n/pl.json +899 -0
  678. package/src/modules/staff/lib/crud.ts +14 -3
  679. package/src/modules/staff/lib/time-tracking/access.ts +155 -0
  680. package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
  681. package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
  682. package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
  683. package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
  684. package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
  685. package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
  686. package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
  687. package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
  688. package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
  689. package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
  690. package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
  691. package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
  692. package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
  693. package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
  694. package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
  695. package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
  696. package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
  697. package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
  698. package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
  699. package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
  700. package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
  701. package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
  702. package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
  703. package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
  704. package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
  705. package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
  706. package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
  707. package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
  708. package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
  709. package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
  710. package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
  711. package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
  712. package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
  713. package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
  714. package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
  715. package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
  716. package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
  717. package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
  718. package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
  719. package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
  720. package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
  721. package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
  722. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
  723. package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
  724. package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
  725. package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
  726. package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
  727. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
  728. package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
  729. package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
  730. package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
  731. package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
  732. package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
  733. package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
  734. package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
  735. package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
  736. package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
  737. package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
  738. package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
  739. package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
  740. package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
  741. package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
  742. package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
  743. package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
  744. package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
  745. package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
  746. package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
  747. package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
  748. package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
  749. package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
  750. package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
  751. package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
  752. package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
  753. package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
  754. package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
  755. package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
  756. package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
  757. package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
  758. package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
  759. package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
  760. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
  761. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
  762. package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
  763. package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
  764. package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
  765. package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
  766. package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
  767. package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
  768. package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
  769. package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
  770. package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
  771. package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
  772. package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
  773. package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
  774. package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
  775. package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
  776. package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
  777. package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
  778. package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
  779. package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
  780. package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
  781. package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
  782. package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
  783. package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
  784. package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
  785. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
  786. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
  787. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
  788. package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
  789. package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
  790. package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
  791. package/src/modules/staff/notifications.client.ts +16 -0
  792. package/src/modules/staff/notifications.handlers.ts +88 -0
  793. package/src/modules/staff/notifications.ts +163 -0
  794. package/src/modules/staff/search.ts +191 -1
  795. package/src/modules/staff/setup.ts +22 -0
  796. package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
  797. package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
  798. package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
  799. package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
  800. package/src/modules/staff/translations.ts +17 -0
  801. package/src/modules/staff/widgets/components.ts +34 -0
  802. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
  803. package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
  804. package/src/modules/staff/widgets/notifications/index.ts +1 -0
  805. package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
  806. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
  807. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
  808. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
@@ -0,0 +1,1121 @@
1
+ /**
2
+ * Task commands for the Kanban board of screen 6 and the drawer of screen 7 (D-2).
3
+ *
4
+ * A subtask is not a second entity — it is a task with `parent_task_id` set, carrying
5
+ * its own status, assignee and time entries. Everything that follows from that is
6
+ * enforced here rather than trusted from the request:
7
+ *
8
+ * 1. **Depth is capped at one.** Creating or re-parenting under a task that itself has
9
+ * a parent is refused with `400 subtask_depth_exceeded`, and a task that already
10
+ * holds children may not become one. The board therefore renders exactly the
11
+ * top-level tasks and the drawer exactly one level below them.
12
+ * 2. **Soft-deleting a parent soft-deletes its children in the same transaction.**
13
+ * Time entries are deliberately left alone: they keep pointing at the deleted task
14
+ * so a closed report still resolves its line labels.
15
+ * 3. **A task lives in exactly one project (§2).** Moving one between projects would
16
+ * invalidate its reference, its column and every report line quoting it, so the
17
+ * request is refused instead of silently ignored.
18
+ * 4. **Done-ness has one source of truth** — the status column's `is_done`. There is
19
+ * no `is_done` on the task; landing on a terminal column stamps `closed_at` and
20
+ * leaving one clears it.
21
+ *
22
+ * `title` is the only field a caller must supply beyond scoping and the project
23
+ * (US-C1): the status defaults to the project's `is_default` column and the assignee
24
+ * to the creator, both overridable.
25
+ *
26
+ * The rollup (`ownMinutes` / `loggedMinutes` / `childCount`) is NOT computed here — it
27
+ * is a read-side concern owned by `lib/time-tracking/rollup.ts`.
28
+ */
29
+
30
+ import { z } from 'zod'
31
+ import type { EntityManager } from '@mikro-orm/postgresql'
32
+ import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
33
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
34
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
35
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
36
+ import { createLogger } from '@open-mercato/shared/lib/logger'
37
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
38
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
39
+ import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
40
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
41
+ import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
42
+ import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
43
+ import { StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'
44
+ import {
45
+ staffTimeTaskCreateSchema,
46
+ staffTimeTaskStatusChangeSchema,
47
+ staffTimeTaskUpdateSchema,
48
+ type StaffTimeTaskCreateInput,
49
+ type StaffTimeTaskUpdateInput,
50
+ } from '../data/validators'
51
+ import { emitStaffEvent } from '../events'
52
+ import { nextStatusPosition } from '../lib/timesheets-tasks/statusPositions'
53
+ import {
54
+ allocateTaskReference,
55
+ isTaskReferenceConflict,
56
+ withTaskReferenceRetry,
57
+ } from '../lib/timesheets-tasks/taskReference'
58
+ import {
59
+ SUBTASK_DEPTH_EXCEEDED_CODE,
60
+ planTaskSoftDelete,
61
+ rejectTaskParent,
62
+ type TaskParentRejection,
63
+ } from '../lib/timesheets-tasks/taskHierarchy'
64
+ import {
65
+ applyScopeToWhere,
66
+ commandActorScope,
67
+ commandInputScope,
68
+ ensureOrganizationScope,
69
+ ensureTenantScope,
70
+ extractUndoPayload,
71
+ scopeForDecryption,
72
+ scopedStaffSnapshotWhere,
73
+ staffSnapshotScopeFromContext,
74
+ staffSnapshotScopeFromSnapshot,
75
+ type StaffCommandScope,
76
+ type StaffSnapshotScope,
77
+ } from './shared'
78
+
79
+ export const staffTimeTaskCommandIds = {
80
+ create: 'staff.timesheets.tasks.create',
81
+ update: 'staff.timesheets.tasks.update',
82
+ delete: 'staff.timesheets.tasks.delete',
83
+ statusChange: 'staff.timesheets.tasks.status_change',
84
+ } as const
85
+
86
+ /**
87
+ * Resource kind the board's drag endpoint presents to the mutation-guard registry
88
+ * and to the command-level optimistic lock. It matches the entity naming the other
89
+ * hand-written timesheets routes use (`staff.timesheets.time_entry`,
90
+ * `staff.timesheets.time_project`) so an `OM_OPTIMISTIC_LOCK` allow-list can name
91
+ * it. The audit `resourceKind` stays `staff.timesheets.task`, which is what the
92
+ * other task commands already write.
93
+ */
94
+ export const STAFF_TIME_TASK_RESOURCE_KIND = 'staff.timesheets.time_task'
95
+
96
+ export const TASK_DEPTH_EXCEEDED_CODE = SUBTASK_DEPTH_EXCEEDED_CODE
97
+ export const TASK_PARENT_SELF_CODE = 'task_parent_self'
98
+ export const TASK_PARENT_PROJECT_MISMATCH_CODE = 'task_parent_project_mismatch'
99
+ export const TASK_PROJECT_MOVE_UNSUPPORTED_CODE = 'task_project_move_unsupported'
100
+ export const TASK_STATUS_MISSING_CODE = 'task_status_missing'
101
+ export const TASK_REFERENCE_CONFLICT_CODE = 'task_reference_conflict'
102
+
103
+ const logger = createLogger('staff').child({ component: 'commands/timesheets-tasks' })
104
+
105
+ const taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {
106
+ entityType: 'staff:staff_time_task',
107
+ }
108
+
109
+ /**
110
+ * `staff.timesheets.time_task.{created,updated,deleted}` from `events.ts`. The
111
+ * lifecycle `…status_changed` event belongs to the dedicated status endpoint that
112
+ * owns the drag-and-drop move, not to these generic writes.
113
+ */
114
+ const taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {
115
+ module: 'staff',
116
+ entity: 'timesheets.time_task',
117
+ persistent: true,
118
+ buildPayload: (ctx) => ({
119
+ id: ctx.identifiers.id,
120
+ organizationId: ctx.identifiers.organizationId,
121
+ tenantId: ctx.identifiers.tenantId,
122
+ }),
123
+ }
124
+
125
+ /**
126
+ * The resource tag the tasks CRUD route caches its list pages under —
127
+ * `makeCrudRoute` derives it from `events.module` + `events.entity`, so it is read
128
+ * from the same config here rather than restated.
129
+ *
130
+ * Commands executed straight from a route (the board's move endpoint) are
131
+ * invalidated by the command bus from the audit `resourceKind`, which for tasks is
132
+ * `staff.timesheets.task` — a different tag. Without this alias a drag left every
133
+ * cached page of the tasks list in place, so the board's refetch after a successful
134
+ * move was answered with the pre-move page: the card snapped back with no error,
135
+ * and the stale `updated_at` that page carried made the next move answer 409
136
+ * "changed by someone else" until the entry expired. `ENABLE_CRUD_API_CACHE` off
137
+ * hides it; the ephemeral QA and integration environments run with it on.
138
+ */
139
+ const TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`
140
+
141
+ type Translate = (key: string, fallback: string) => string
142
+
143
+ type TaskSnapshot = {
144
+ id: string
145
+ tenantId: string
146
+ organizationId: string
147
+ timeProjectId: string
148
+ parentTaskId: string | null
149
+ taskStatusId: string
150
+ sequenceNumber: number
151
+ reference: string
152
+ title: string
153
+ description: string | null
154
+ assigneeStaffMemberId: string | null
155
+ position: number
156
+ createdByUserId: string | null
157
+ closedAt: string | null
158
+ deletedAt: string | null
159
+ }
160
+
161
+ type TaskUndoPayload = {
162
+ before?: TaskSnapshot | null
163
+ after?: TaskSnapshot | null
164
+ /** Children the delete took with it, so undo can put the whole set back. */
165
+ cascadedChildren?: TaskSnapshot[] | null
166
+ }
167
+
168
+ const DIFFED_FIELDS = [
169
+ 'parentTaskId',
170
+ 'taskStatusId',
171
+ 'title',
172
+ 'description',
173
+ 'assigneeStaffMemberId',
174
+ 'position',
175
+ 'closedAt',
176
+ ] as const
177
+
178
+ function toSnapshot(task: StaffTimeTask): TaskSnapshot {
179
+ return {
180
+ id: task.id,
181
+ tenantId: task.tenantId,
182
+ organizationId: task.organizationId,
183
+ timeProjectId: task.timeProjectId,
184
+ parentTaskId: task.parentTaskId ?? null,
185
+ taskStatusId: task.taskStatusId,
186
+ sequenceNumber: task.sequenceNumber,
187
+ reference: task.reference,
188
+ title: task.title,
189
+ description: task.description ?? null,
190
+ assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,
191
+ position: task.position,
192
+ createdByUserId: task.createdByUserId ?? null,
193
+ closedAt: task.closedAt ? task.closedAt.toISOString() : null,
194
+ deletedAt: task.deletedAt ? task.deletedAt.toISOString() : null,
195
+ }
196
+ }
197
+
198
+ function snapshotScopeWhere(scope?: StaffSnapshotScope | null): { tenantId?: string; organizationId?: string } {
199
+ const where: { tenantId?: string; organizationId?: string } = {}
200
+ if (scope?.tenantId) where.tenantId = scope.tenantId
201
+ if (scope?.organizationId) where.organizationId = scope.organizationId
202
+ return where
203
+ }
204
+
205
+ async function loadTaskSnapshot(
206
+ em: EntityManager,
207
+ id: string,
208
+ scope?: StaffSnapshotScope | null,
209
+ ): Promise<TaskSnapshot | null> {
210
+ const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(id, scope))
211
+ return task ? toSnapshot(task) : null
212
+ }
213
+
214
+ async function loadChildSnapshots(
215
+ em: EntityManager,
216
+ parentTaskId: string,
217
+ scope?: StaffSnapshotScope | null,
218
+ ): Promise<TaskSnapshot[]> {
219
+ const children = await em.find(StaffTimeTask, {
220
+ parentTaskId,
221
+ deletedAt: null,
222
+ ...snapshotScopeWhere(scope),
223
+ })
224
+ return children.map(toSnapshot)
225
+ }
226
+
227
+ function notFoundError(translate: Translate): CrudHttpError {
228
+ return new CrudHttpError(404, {
229
+ error: translate('staff.time_tracking.tasks.errors.notFound', 'Task not found or not accessible.'),
230
+ })
231
+ }
232
+
233
+ function projectNotFoundError(translate: Translate): CrudHttpError {
234
+ const message = translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.')
235
+ return new CrudHttpError(422, { error: message, fieldErrors: { timeProjectId: message } })
236
+ }
237
+
238
+ function statusNotFoundError(translate: Translate): CrudHttpError {
239
+ const message = translate(
240
+ 'staff.time_tracking.tasks.errors.statusNotFound',
241
+ 'That column does not belong to this project.',
242
+ )
243
+ return new CrudHttpError(422, { error: message, fieldErrors: { taskStatusId: message } })
244
+ }
245
+
246
+ function statusMissingError(translate: Translate): CrudHttpError {
247
+ return new CrudHttpError(422, {
248
+ code: TASK_STATUS_MISSING_CODE,
249
+ error: translate(
250
+ 'staff.time_tracking.tasks.errors.statusMissing',
251
+ 'This project has no columns yet, so a task cannot be placed on its board.',
252
+ ),
253
+ })
254
+ }
255
+
256
+ function parentNotFoundError(translate: Translate): CrudHttpError {
257
+ const message = translate(
258
+ 'staff.time_tracking.tasks.errors.parentNotFound',
259
+ 'Parent task not found or not accessible.',
260
+ )
261
+ return new CrudHttpError(422, { error: message, fieldErrors: { parentTaskId: message } })
262
+ }
263
+
264
+ function assigneeNotFoundError(translate: Translate): CrudHttpError {
265
+ const message = translate(
266
+ 'staff.timesheets.errors.staffMemberNotFound',
267
+ 'Staff member not found or not accessible.',
268
+ )
269
+ return new CrudHttpError(422, { error: message, fieldErrors: { assigneeStaffMemberId: message } })
270
+ }
271
+
272
+ /**
273
+ * The one refusal the drawer has to be able to explain: nesting is one level deep, so
274
+ * a subtask can neither gain a subtask nor be handed to another subtask.
275
+ */
276
+ function parentRejectionError(translate: Translate, rejection: TaskParentRejection): CrudHttpError {
277
+ if (rejection === 'self_parent') {
278
+ const message = translate(
279
+ 'staff.time_tracking.tasks.errors.parentSelf',
280
+ 'A task cannot be its own subtask.',
281
+ )
282
+ return new CrudHttpError(400, {
283
+ code: TASK_PARENT_SELF_CODE,
284
+ error: message,
285
+ fieldErrors: { parentTaskId: message },
286
+ })
287
+ }
288
+ if (rejection === 'parent_project_mismatch') {
289
+ const message = translate(
290
+ 'staff.time_tracking.tasks.errors.parentProjectMismatch',
291
+ 'A subtask must stay in the same project as its parent.',
292
+ )
293
+ return new CrudHttpError(422, {
294
+ code: TASK_PARENT_PROJECT_MISMATCH_CODE,
295
+ error: message,
296
+ fieldErrors: { parentTaskId: message },
297
+ })
298
+ }
299
+ const message = translate(
300
+ 'staff.time_tracking.tasks.errors.subtaskDepthExceeded',
301
+ 'Subtasks are one level deep. A subtask cannot have subtasks of its own.',
302
+ )
303
+ return new CrudHttpError(400, {
304
+ code: TASK_DEPTH_EXCEEDED_CODE,
305
+ error: message,
306
+ fieldErrors: { parentTaskId: message },
307
+ })
308
+ }
309
+
310
+ function referenceConflictError(translate: Translate): CrudHttpError {
311
+ return new CrudHttpError(409, {
312
+ code: TASK_REFERENCE_CONFLICT_CODE,
313
+ error: translate(
314
+ 'staff.time_tracking.tasks.errors.referenceConflict',
315
+ 'Could not allocate a task number right now. Try again.',
316
+ ),
317
+ })
318
+ }
319
+
320
+ async function requireProject(
321
+ em: EntityManager,
322
+ timeProjectId: string,
323
+ scope: StaffCommandScope,
324
+ translate: Translate,
325
+ ): Promise<StaffTimeProject> {
326
+ const project = await em.findOne(
327
+ StaffTimeProject,
328
+ applyScopeToWhere<StaffTimeProject>({ id: timeProjectId, deletedAt: null }, scope),
329
+ )
330
+ if (!project) throw projectNotFoundError(translate)
331
+ return project
332
+ }
333
+
334
+ /**
335
+ * The column a task lands on: the requested one when it belongs to this project, and
336
+ * otherwise the project's `is_default` entry point. The board invariants guarantee one
337
+ * exists; the positional fallback only covers a board repaired by hand.
338
+ */
339
+ async function resolveTaskStatus(
340
+ em: EntityManager,
341
+ timeProjectId: string,
342
+ requestedStatusId: string | null | undefined,
343
+ scope: StaffCommandScope,
344
+ translate: Translate,
345
+ ): Promise<StaffTimeTaskStatus> {
346
+ if (requestedStatusId) {
347
+ const status = await em.findOne(
348
+ StaffTimeTaskStatus,
349
+ applyScopeToWhere<StaffTimeTaskStatus>({ id: requestedStatusId, timeProjectId, deletedAt: null }, scope),
350
+ )
351
+ if (!status) throw statusNotFoundError(translate)
352
+ return status
353
+ }
354
+ const board = await em.find(
355
+ StaffTimeTaskStatus,
356
+ applyScopeToWhere<StaffTimeTaskStatus>({ timeProjectId, deletedAt: null }, scope),
357
+ )
358
+ if (board.length === 0) throw statusMissingError(translate)
359
+ const ordered = [...board].sort((left, right) => left.position - right.position)
360
+ return ordered.find((status) => status.isDefault) ?? ordered[0]
361
+ }
362
+
363
+ async function requireParentTask(
364
+ em: EntityManager,
365
+ parentTaskId: string,
366
+ scope: StaffCommandScope,
367
+ translate: Translate,
368
+ ): Promise<StaffTimeTask> {
369
+ const parent = await em.findOne(
370
+ StaffTimeTask,
371
+ applyScopeToWhere<StaffTimeTask>({ id: parentTaskId, deletedAt: null }, scope),
372
+ )
373
+ if (!parent) throw parentNotFoundError(translate)
374
+ return parent
375
+ }
376
+
377
+ async function resolveStaffMemberIdForUser(
378
+ em: EntityManager,
379
+ userId: string | null | undefined,
380
+ scope: StaffCommandScope,
381
+ ): Promise<string | null> {
382
+ if (!userId) return null
383
+ const member = await findOneWithDecryption(
384
+ em,
385
+ StaffTeamMember,
386
+ applyScopeToWhere<StaffTeamMember>({ userId, deletedAt: null }, scope),
387
+ undefined,
388
+ scopeForDecryption(scope),
389
+ )
390
+ return member?.id ?? null
391
+ }
392
+
393
+ async function requireStaffMember(
394
+ em: EntityManager,
395
+ staffMemberId: string,
396
+ scope: StaffCommandScope,
397
+ translate: Translate,
398
+ ): Promise<string> {
399
+ const member = await em.findOne(
400
+ StaffTeamMember,
401
+ applyScopeToWhere<StaffTeamMember>({ id: staffMemberId, deletedAt: null }, scope),
402
+ )
403
+ if (!member) throw assigneeNotFoundError(translate)
404
+ return member.id
405
+ }
406
+
407
+ /**
408
+ * Order within a column, on the same gap grid the columns themselves use: a drop
409
+ * between two cards writes one row instead of renumbering the stack.
410
+ */
411
+ async function nextTaskPosition(
412
+ em: EntityManager,
413
+ timeProjectId: string,
414
+ taskStatusId: string,
415
+ scope: StaffCommandScope,
416
+ ): Promise<number> {
417
+ const siblings = await em.find(
418
+ StaffTimeTask,
419
+ applyScopeToWhere<StaffTimeTask>({ timeProjectId, taskStatusId, deletedAt: null }, scope),
420
+ )
421
+ return nextStatusPosition(siblings.map((task) => task.position))
422
+ }
423
+
424
+ /**
425
+ * The highest number the project has EVER handed out, deleted rows included. The
426
+ * unique index only covers live rows, so a soft-deleted `TT-142` would otherwise let
427
+ * the next task reuse that reference — and a closed report quoting the old one would
428
+ * suddenly read as the new one.
429
+ */
430
+ async function readHighestSequenceNumber(
431
+ em: EntityManager,
432
+ timeProjectId: string,
433
+ scope: StaffCommandScope,
434
+ ): Promise<number> {
435
+ const tasks = await em.find(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ timeProjectId }, scope))
436
+ return tasks.reduce((highest, task) => Math.max(highest, task.sequenceNumber ?? 0), 0)
437
+ }
438
+
439
+ /**
440
+ * §2: a task lives in exactly one project. The update schema has no `timeProjectId`,
441
+ * so a caller sending one would otherwise have it silently dropped and believe the
442
+ * move succeeded.
443
+ */
444
+ function assertNoProjectMove(rawInput: unknown, task: StaffTimeTask, translate: Translate): void {
445
+ if (!rawInput || typeof rawInput !== 'object') return
446
+ const requested = (rawInput as Record<string, unknown>).timeProjectId
447
+ if (requested === undefined || requested === null) return
448
+ if (typeof requested === 'string' && requested === task.timeProjectId) return
449
+ const message = translate(
450
+ 'staff.time_tracking.tasks.errors.projectMoveUnsupported',
451
+ 'A task cannot be moved to another project. Create it in the target project instead.',
452
+ )
453
+ throw new CrudHttpError(400, {
454
+ code: TASK_PROJECT_MOVE_UNSUPPORTED_CODE,
455
+ error: message,
456
+ fieldErrors: { timeProjectId: message },
457
+ })
458
+ }
459
+
460
+ function applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {
461
+ task.taskStatusId = status.id
462
+ // Done-ness comes from the column, never from a boolean on the task: landing on a
463
+ // terminal column closes it, leaving one re-opens it.
464
+ if (status.isDone) {
465
+ if (!task.closedAt) task.closedAt = now
466
+ } else {
467
+ task.closedAt = null
468
+ }
469
+ }
470
+
471
+ function actorUserId(ctx: CommandRuntimeContext): string | null {
472
+ return typeof ctx.auth?.sub === 'string' ? ctx.auth.sub : null
473
+ }
474
+
475
+ const createTaskCommand: CommandHandler<StaffTimeTaskCreateInput, { taskId: string }> = {
476
+ id: staffTimeTaskCommandIds.create,
477
+ async execute(rawInput, ctx) {
478
+ const parsed = staffTimeTaskCreateSchema.parse(rawInput)
479
+ ensureTenantScope(ctx, parsed.tenantId)
480
+ ensureOrganizationScope(ctx, parsed.organizationId)
481
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
482
+ const { translate } = await resolveTranslations()
483
+ const baseEm = ctx.container.resolve('em') as EntityManager
484
+ const createdByUserId = actorUserId(ctx)
485
+
486
+ let record: StaffTimeTask
487
+ try {
488
+ record = await withTaskReferenceRetry(async () => {
489
+ // A fresh fork per attempt: a lost race aborts the transaction, so the retry
490
+ // re-reads the project's highest number rather than reusing a stale max.
491
+ const em = baseEm.fork()
492
+ let created: StaffTimeTask | null = null
493
+ let plan: {
494
+ project: StaffTimeProject
495
+ status: StaffTimeTaskStatus
496
+ parentTaskId: string | null
497
+ assigneeStaffMemberId: string | null
498
+ position: number
499
+ highestSequenceNumber: number
500
+ } | null = null
501
+
502
+ await withAtomicFlush(
503
+ em,
504
+ [
505
+ async () => {
506
+ const project = await requireProject(em, parsed.timeProjectId, scope, translate)
507
+ const status = await resolveTaskStatus(em, parsed.timeProjectId, parsed.taskStatusId, scope, translate)
508
+
509
+ let parentTaskId: string | null = null
510
+ if (parsed.parentTaskId) {
511
+ const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)
512
+ const rejection = rejectTaskParent(parent, { timeProjectId: parsed.timeProjectId })
513
+ if (rejection) throw parentRejectionError(translate, rejection)
514
+ parentTaskId = parent.id
515
+ }
516
+
517
+ // US-C1: the creator owns the task unless the request says otherwise.
518
+ const assigneeStaffMemberId = parsed.assigneeStaffMemberId
519
+ ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)
520
+ : await resolveStaffMemberIdForUser(em, createdByUserId, scope)
521
+
522
+ plan = {
523
+ project,
524
+ status,
525
+ parentTaskId,
526
+ assigneeStaffMemberId,
527
+ position:
528
+ parsed.position ?? (await nextTaskPosition(em, parsed.timeProjectId, status.id, scope)),
529
+ highestSequenceNumber: await readHighestSequenceNumber(em, parsed.timeProjectId, scope),
530
+ }
531
+ },
532
+ () => {
533
+ if (!plan) throw notFoundError(translate)
534
+ const now = new Date()
535
+ const allocation = allocateTaskReference(plan.project.code, plan.highestSequenceNumber)
536
+ created = em.create(StaffTimeTask, {
537
+ tenantId: parsed.tenantId,
538
+ organizationId: parsed.organizationId,
539
+ timeProjectId: parsed.timeProjectId,
540
+ parentTaskId: plan.parentTaskId,
541
+ taskStatusId: plan.status.id,
542
+ sequenceNumber: allocation.sequenceNumber,
543
+ reference: allocation.reference,
544
+ title: parsed.title,
545
+ description: parsed.description ?? null,
546
+ assigneeStaffMemberId: plan.assigneeStaffMemberId,
547
+ position: plan.position,
548
+ createdByUserId,
549
+ closedAt: plan.status.isDone ? now : null,
550
+ createdAt: now,
551
+ updatedAt: now,
552
+ deletedAt: null,
553
+ })
554
+ em.persist(created)
555
+ },
556
+ ],
557
+ { transaction: true, label: staffTimeTaskCommandIds.create },
558
+ )
559
+
560
+ if (!created) throw notFoundError(translate)
561
+ return created as StaffTimeTask
562
+ })
563
+ } catch (err) {
564
+ // The retry budget ran out — the caller retries rather than getting a duplicate.
565
+ if (isTaskReferenceConflict(err)) throw referenceConflictError(translate)
566
+ throw err
567
+ }
568
+
569
+ await emitCrudSideEffects({
570
+ dataEngine: ctx.container.resolve('dataEngine'),
571
+ action: 'created',
572
+ entity: record,
573
+ identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },
574
+ events: taskCrudEvents,
575
+ indexer: taskCrudIndexer,
576
+ })
577
+
578
+ return { taskId: record.id }
579
+ },
580
+ captureAfter: async (_input, result, ctx) => {
581
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
582
+ const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))
583
+ if (!snapshot) return null
584
+ return { snapshot }
585
+ },
586
+ buildLog: async ({ result, ctx }) => {
587
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
588
+ const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))
589
+ if (!snapshot) return null
590
+ const { translate } = await resolveTranslations()
591
+ return {
592
+ actionLabel: translate('staff.audit.timesheets.tasks.create', 'Create task'),
593
+ resourceKind: 'staff.timesheets.task',
594
+ resourceId: snapshot.id,
595
+ tenantId: snapshot.tenantId,
596
+ organizationId: snapshot.organizationId,
597
+ snapshotAfter: snapshot,
598
+ payload: {
599
+ undo: { after: snapshot } satisfies TaskUndoPayload,
600
+ },
601
+ }
602
+ },
603
+ undo: async ({ logEntry, ctx }) => {
604
+ const payload = extractUndoPayload<TaskUndoPayload>(logEntry)
605
+ const after = payload?.after
606
+ if (!after) return
607
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
608
+ const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
609
+ if (!task) return
610
+ task.deletedAt = new Date()
611
+ task.updatedAt = new Date()
612
+ await em.flush()
613
+
614
+ await emitCrudUndoSideEffects({
615
+ dataEngine: ctx.container.resolve('dataEngine'),
616
+ action: 'deleted',
617
+ entity: task,
618
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
619
+ events: taskCrudEvents,
620
+ indexer: taskCrudIndexer,
621
+ })
622
+ },
623
+ }
624
+
625
+ const updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: string }> = {
626
+ id: staffTimeTaskCommandIds.update,
627
+ async prepare(rawInput, ctx) {
628
+ const parsed = staffTimeTaskUpdateSchema.safeParse(rawInput)
629
+ if (!parsed.success) return {}
630
+ const em = ctx.container.resolve('em') as EntityManager
631
+ const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))
632
+ if (!before) return {}
633
+ return { before: { snapshot: before } }
634
+ },
635
+ async execute(rawInput, ctx) {
636
+ const parsed = staffTimeTaskUpdateSchema.parse(rawInput)
637
+ const { translate } = await resolveTranslations()
638
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
639
+ const scope = commandActorScope(ctx)
640
+
641
+ const task = await em.findOne(
642
+ StaffTimeTask,
643
+ applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),
644
+ )
645
+ if (!task) throw notFoundError(translate)
646
+ ensureTenantScope(ctx, task.tenantId)
647
+ ensureOrganizationScope(ctx, task.organizationId)
648
+ assertNoProjectMove(rawInput, task, translate)
649
+
650
+ let targetStatus: StaffTimeTaskStatus | null = null
651
+ let targetParentTaskId: string | null | undefined
652
+ let targetAssigneeStaffMemberId: string | null | undefined
653
+
654
+ await withAtomicFlush(
655
+ em,
656
+ [
657
+ async () => {
658
+ if (parsed.taskStatusId !== undefined) {
659
+ targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)
660
+ }
661
+
662
+ if (parsed.parentTaskId !== undefined) {
663
+ if (parsed.parentTaskId === null) {
664
+ targetParentTaskId = null
665
+ } else {
666
+ const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)
667
+ const childCount = await em.count(
668
+ StaffTimeTask,
669
+ applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),
670
+ )
671
+ const rejection = rejectTaskParent(parent, {
672
+ id: task.id,
673
+ timeProjectId: task.timeProjectId,
674
+ childCount,
675
+ })
676
+ if (rejection) throw parentRejectionError(translate, rejection)
677
+ targetParentTaskId = parent.id
678
+ }
679
+ }
680
+
681
+ if (parsed.assigneeStaffMemberId !== undefined) {
682
+ targetAssigneeStaffMemberId = parsed.assigneeStaffMemberId
683
+ ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)
684
+ : null
685
+ }
686
+ },
687
+ () => {
688
+ const now = new Date()
689
+ if (parsed.title !== undefined) task.title = parsed.title
690
+ if (parsed.description !== undefined) task.description = parsed.description ?? null
691
+ if (parsed.position !== undefined) task.position = parsed.position
692
+ if (targetParentTaskId !== undefined) task.parentTaskId = targetParentTaskId
693
+ if (targetAssigneeStaffMemberId !== undefined) task.assigneeStaffMemberId = targetAssigneeStaffMemberId
694
+ if (targetStatus) applyStatusToTask(task, targetStatus, now)
695
+ task.updatedAt = now
696
+ },
697
+ ],
698
+ { transaction: true, label: staffTimeTaskCommandIds.update },
699
+ )
700
+
701
+ await emitCrudSideEffects({
702
+ dataEngine: ctx.container.resolve('dataEngine'),
703
+ action: 'updated',
704
+ entity: task,
705
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
706
+ events: taskCrudEvents,
707
+ indexer: taskCrudIndexer,
708
+ })
709
+
710
+ return { taskId: task.id }
711
+ },
712
+ buildLog: async ({ snapshots, ctx }) => {
713
+ const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot
714
+ if (!before) return null
715
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
716
+ const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
717
+ if (!after) return null
718
+ const { translate } = await resolveTranslations()
719
+ return {
720
+ actionLabel: translate('staff.audit.timesheets.tasks.update', 'Update task'),
721
+ resourceKind: 'staff.timesheets.task',
722
+ resourceId: before.id,
723
+ tenantId: before.tenantId,
724
+ organizationId: before.organizationId,
725
+ snapshotBefore: before,
726
+ snapshotAfter: after,
727
+ changes: buildChanges(
728
+ before as unknown as Record<string, unknown>,
729
+ after as unknown as Record<string, unknown>,
730
+ [...DIFFED_FIELDS],
731
+ ),
732
+ payload: {
733
+ undo: { before, after } satisfies TaskUndoPayload,
734
+ },
735
+ }
736
+ },
737
+ undo: async ({ logEntry, ctx }) => {
738
+ const payload = extractUndoPayload<TaskUndoPayload>(logEntry)
739
+ const before = payload?.before
740
+ if (!before) return
741
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
742
+ const task = await em.findOne(
743
+ StaffTimeTask,
744
+ scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),
745
+ )
746
+ if (!task) return
747
+
748
+ task.parentTaskId = before.parentTaskId
749
+ task.taskStatusId = before.taskStatusId
750
+ task.title = before.title
751
+ task.description = before.description
752
+ task.assigneeStaffMemberId = before.assigneeStaffMemberId
753
+ task.position = before.position
754
+ task.closedAt = before.closedAt ? new Date(before.closedAt) : null
755
+ task.updatedAt = new Date()
756
+ await em.flush()
757
+
758
+ await emitCrudUndoSideEffects({
759
+ dataEngine: ctx.container.resolve('dataEngine'),
760
+ action: 'updated',
761
+ entity: task,
762
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
763
+ events: taskCrudEvents,
764
+ indexer: taskCrudIndexer,
765
+ })
766
+ },
767
+ }
768
+
769
+ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; childIds: string[] }> = {
770
+ id: staffTimeTaskCommandIds.delete,
771
+ async prepare(input, ctx) {
772
+ const id = input?.id
773
+ if (!id) return {}
774
+ const em = ctx.container.resolve('em') as EntityManager
775
+ const scope = staffSnapshotScopeFromContext(ctx)
776
+ const before = await loadTaskSnapshot(em, id, scope)
777
+ if (!before) return {}
778
+ const cascadedChildren = before.parentTaskId ? [] : await loadChildSnapshots(em, before.id, scope)
779
+ return { before: { snapshot: before, cascadedChildren } }
780
+ },
781
+ async execute(input, ctx) {
782
+ const { translate } = await resolveTranslations()
783
+ const id = input?.id
784
+ if (!id) {
785
+ throw new CrudHttpError(400, {
786
+ error: translate('staff.time_tracking.tasks.errors.idRequired', 'Task id is required.'),
787
+ })
788
+ }
789
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
790
+ const scope = commandActorScope(ctx)
791
+ const task = await em.findOne(
792
+ StaffTimeTask,
793
+ applyScopeToWhere<StaffTimeTask>({ id, deletedAt: null }, scope),
794
+ )
795
+ if (!task) throw notFoundError(translate)
796
+ ensureTenantScope(ctx, task.tenantId)
797
+ ensureOrganizationScope(ctx, task.organizationId)
798
+
799
+ let children: StaffTimeTask[] = []
800
+ let childIds: string[] = []
801
+
802
+ await withAtomicFlush(
803
+ em,
804
+ [
805
+ async () => {
806
+ // Only a top-level task can carry a cascade; the depth rule guarantees a
807
+ // child has none, so the walk is exactly one level.
808
+ children = task.parentTaskId
809
+ ? []
810
+ : await em.find(
811
+ StaffTimeTask,
812
+ applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),
813
+ )
814
+ },
815
+ () => {
816
+ const plan = planTaskSoftDelete(task, children)
817
+ childIds = plan.childIds
818
+ const now = new Date()
819
+ const byId = new Map(children.map((child) => [child.id, child]))
820
+ task.deletedAt = now
821
+ task.updatedAt = now
822
+ for (const childId of childIds) {
823
+ const child = byId.get(childId)
824
+ if (!child) continue
825
+ child.deletedAt = now
826
+ child.updatedAt = now
827
+ }
828
+ // Time entries are untouched on purpose: a closed report still has to
829
+ // resolve this task's label months after it was removed from the board.
830
+ },
831
+ ],
832
+ { transaction: true, label: staffTimeTaskCommandIds.delete },
833
+ )
834
+
835
+ const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
836
+ for (const removed of [task, ...children.filter((child) => childIds.includes(child.id))]) {
837
+ await emitCrudSideEffects({
838
+ dataEngine,
839
+ action: 'deleted',
840
+ entity: removed,
841
+ identifiers: { id: removed.id, organizationId: removed.organizationId, tenantId: removed.tenantId },
842
+ events: taskCrudEvents,
843
+ indexer: taskCrudIndexer,
844
+ })
845
+ }
846
+
847
+ return { taskId: task.id, childIds }
848
+ },
849
+ buildLog: async ({ snapshots }) => {
850
+ const prepared = snapshots.before as
851
+ | { snapshot?: TaskSnapshot; cascadedChildren?: TaskSnapshot[] }
852
+ | undefined
853
+ const before = prepared?.snapshot
854
+ if (!before) return null
855
+ const { translate } = await resolveTranslations()
856
+ return {
857
+ actionLabel: translate('staff.audit.timesheets.tasks.delete', 'Delete task'),
858
+ resourceKind: 'staff.timesheets.task',
859
+ resourceId: before.id,
860
+ tenantId: before.tenantId,
861
+ organizationId: before.organizationId,
862
+ snapshotBefore: before,
863
+ payload: {
864
+ undo: {
865
+ before,
866
+ cascadedChildren: prepared?.cascadedChildren ?? null,
867
+ } satisfies TaskUndoPayload,
868
+ },
869
+ }
870
+ },
871
+ undo: async ({ logEntry, ctx }) => {
872
+ const payload = extractUndoPayload<TaskUndoPayload>(logEntry)
873
+ const before = payload?.before
874
+ if (!before) return
875
+ const snapshotScope = staffSnapshotScopeFromSnapshot(before)
876
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
877
+ const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)]
878
+ const restored: StaffTimeTask[] = []
879
+
880
+ await withAtomicFlush(
881
+ em,
882
+ [
883
+ async () => {
884
+ const tasks = await em.find(StaffTimeTask, {
885
+ id: { $in: ids },
886
+ ...snapshotScopeWhere(snapshotScope),
887
+ })
888
+ restored.push(...tasks)
889
+ },
890
+ () => {
891
+ // Undo restores the whole set the cascade removed, not just the parent.
892
+ for (const task of restored) {
893
+ task.deletedAt = null
894
+ task.updatedAt = new Date()
895
+ }
896
+ },
897
+ ],
898
+ { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` },
899
+ )
900
+
901
+ const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
902
+ for (const task of restored) {
903
+ await emitCrudUndoSideEffects({
904
+ dataEngine,
905
+ action: 'created',
906
+ entity: task,
907
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
908
+ events: taskCrudEvents,
909
+ indexer: taskCrudIndexer,
910
+ })
911
+ }
912
+ },
913
+ }
914
+
915
+ /**
916
+ * US-C2: the board drag, and the drawer's subtask tick, which is the same move
917
+ * expressed without a position.
918
+ *
919
+ * It is a command of its own rather than a flavour of `update` for three reasons:
920
+ * the move is the one write on this entity where two people racing the same record
921
+ * is genuinely likely, so it enforces the `updated_at` version and answers 409 on a
922
+ * stale one; the board needs a lifecycle event carrying enough to relocate the card
923
+ * on another client without a refetch; and the column change is the whole payload,
924
+ * so the audit row reads "Move task" instead of a generic update.
925
+ *
926
+ * `closedAt` is NOT re-derived here — `applyStatusToTask` above is the single place
927
+ * that reads the column's `is_done`, shared verbatim with `update`.
928
+ */
929
+ const statusChangeInputSchema = staffTimeTaskStatusChangeSchema.extend({
930
+ id: z.string().uuid(),
931
+ })
932
+
933
+ export type StaffTimeTaskStatusChangeCommandInput = z.infer<typeof statusChangeInputSchema>
934
+
935
+ export type StaffTimeTaskStatusChangeResult = {
936
+ taskId: string
937
+ timeProjectId: string
938
+ previousTaskStatusId: string
939
+ taskStatusId: string
940
+ position: number
941
+ closedAt: string | null
942
+ updatedAt: string | null
943
+ }
944
+
945
+ const STATUS_CHANGE_DIFFED_FIELDS = ['taskStatusId', 'position', 'closedAt'] as const
946
+
947
+ const statusChangeTaskCommand: CommandHandler<
948
+ StaffTimeTaskStatusChangeCommandInput,
949
+ StaffTimeTaskStatusChangeResult
950
+ > = {
951
+ id: staffTimeTaskCommandIds.statusChange,
952
+ async prepare(rawInput, ctx) {
953
+ const parsed = statusChangeInputSchema.safeParse(rawInput)
954
+ if (!parsed.success) return {}
955
+ const em = ctx.container.resolve('em') as EntityManager
956
+ const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))
957
+ if (!before) return {}
958
+ return { before: { snapshot: before } }
959
+ },
960
+ async execute(rawInput, ctx) {
961
+ const parsed = statusChangeInputSchema.parse(rawInput)
962
+ const { translate } = await resolveTranslations()
963
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
964
+ const scope = commandActorScope(ctx)
965
+
966
+ const task = await em.findOne(
967
+ StaffTimeTask,
968
+ applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),
969
+ )
970
+ if (!task) throw notFoundError(translate)
971
+ ensureTenantScope(ctx, task.tenantId)
972
+ ensureOrganizationScope(ctx, task.organizationId)
973
+
974
+ // Two people dragging the same card is the realistic conflict on this board,
975
+ // so a stale version loses here and the client rolls the card back to the
976
+ // column it came from.
977
+ await enforceCommandOptimisticLockWithGuards(ctx.container, {
978
+ resourceKind: STAFF_TIME_TASK_RESOURCE_KIND,
979
+ resourceId: task.id,
980
+ current: task.updatedAt,
981
+ request: ctx.request,
982
+ })
983
+
984
+ const previousTaskStatusId = task.taskStatusId
985
+ let targetStatus: StaffTimeTaskStatus | null = null
986
+ let targetPosition = task.position
987
+
988
+ await withAtomicFlush(
989
+ em,
990
+ [
991
+ async () => {
992
+ // Refuses a column belonging to another project: `resolveTaskStatus`
993
+ // looks the status up inside this task's project, so a foreign id is a
994
+ // 422 rather than a silent cross-board move.
995
+ targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)
996
+ targetPosition =
997
+ parsed.position ?? (await nextTaskPosition(em, task.timeProjectId, targetStatus.id, scope))
998
+ },
999
+ () => {
1000
+ if (!targetStatus) throw notFoundError(translate)
1001
+ const now = new Date()
1002
+ task.position = targetPosition
1003
+ applyStatusToTask(task, targetStatus, now)
1004
+ task.updatedAt = now
1005
+ },
1006
+ ],
1007
+ { transaction: true, label: staffTimeTaskCommandIds.statusChange },
1008
+ )
1009
+
1010
+ await emitCrudSideEffects({
1011
+ dataEngine: ctx.container.resolve('dataEngine'),
1012
+ action: 'updated',
1013
+ entity: task,
1014
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
1015
+ events: taskCrudEvents,
1016
+ indexer: taskCrudIndexer,
1017
+ })
1018
+
1019
+ const result: StaffTimeTaskStatusChangeResult = {
1020
+ taskId: task.id,
1021
+ timeProjectId: task.timeProjectId,
1022
+ previousTaskStatusId,
1023
+ taskStatusId: task.taskStatusId,
1024
+ position: task.position,
1025
+ closedAt: task.closedAt ? task.closedAt.toISOString() : null,
1026
+ updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,
1027
+ }
1028
+
1029
+ // `clientBroadcast: true` (events.ts) puts this on the DOM Event Bridge, so the
1030
+ // payload carries everything a second board needs to move the card itself —
1031
+ // origin column, destination column and the new slot — without a refetch. The
1032
+ // scope fields are what the SSE stream filters its audience on. A failed
1033
+ // broadcast must never fail a committed drag.
1034
+ void emitStaffEvent('staff.timesheets.time_task.status_changed', {
1035
+ id: task.id,
1036
+ tenantId: task.tenantId,
1037
+ organizationId: task.organizationId,
1038
+ ...result,
1039
+ }).catch((err) => {
1040
+ logger.error('staff.timesheets emit time_task.status_changed failed', { err })
1041
+ })
1042
+
1043
+ return result
1044
+ },
1045
+ buildLog: async ({ snapshots, ctx }) => {
1046
+ const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot
1047
+ if (!before) return null
1048
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
1049
+ const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
1050
+ if (!after) return null
1051
+ const { translate } = await resolveTranslations()
1052
+ return {
1053
+ actionLabel: translate('staff.audit.timesheets.tasks.statusChange', 'Move task'),
1054
+ resourceKind: 'staff.timesheets.task',
1055
+ resourceId: before.id,
1056
+ tenantId: before.tenantId,
1057
+ organizationId: before.organizationId,
1058
+ // Flushes the cached tasks list the board refetches right after the move,
1059
+ // and again after an undo — the audit resourceKind above names a different
1060
+ // tag. See TASK_LIST_CACHE_RESOURCE.
1061
+ context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },
1062
+ snapshotBefore: before,
1063
+ snapshotAfter: after,
1064
+ changes: buildChanges(
1065
+ before as unknown as Record<string, unknown>,
1066
+ after as unknown as Record<string, unknown>,
1067
+ [...STATUS_CHANGE_DIFFED_FIELDS],
1068
+ ),
1069
+ payload: {
1070
+ undo: { before, after } satisfies TaskUndoPayload,
1071
+ },
1072
+ }
1073
+ },
1074
+ undo: async ({ logEntry, ctx }) => {
1075
+ const payload = extractUndoPayload<TaskUndoPayload>(logEntry)
1076
+ const before = payload?.before
1077
+ if (!before) return
1078
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
1079
+ const task = await em.findOne(
1080
+ StaffTimeTask,
1081
+ scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),
1082
+ )
1083
+ if (!task) return
1084
+
1085
+ const previousTaskStatusId = task.taskStatusId
1086
+ task.taskStatusId = before.taskStatusId
1087
+ task.position = before.position
1088
+ task.closedAt = before.closedAt ? new Date(before.closedAt) : null
1089
+ task.updatedAt = new Date()
1090
+ await em.flush()
1091
+
1092
+ await emitCrudUndoSideEffects({
1093
+ dataEngine: ctx.container.resolve('dataEngine'),
1094
+ action: 'updated',
1095
+ entity: task,
1096
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
1097
+ events: taskCrudEvents,
1098
+ indexer: taskCrudIndexer,
1099
+ })
1100
+
1101
+ void emitStaffEvent('staff.timesheets.time_task.status_changed', {
1102
+ id: task.id,
1103
+ tenantId: task.tenantId,
1104
+ organizationId: task.organizationId,
1105
+ taskId: task.id,
1106
+ timeProjectId: task.timeProjectId,
1107
+ previousTaskStatusId,
1108
+ taskStatusId: task.taskStatusId,
1109
+ position: task.position,
1110
+ closedAt: task.closedAt ? task.closedAt.toISOString() : null,
1111
+ updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,
1112
+ }).catch((err) => {
1113
+ logger.error('staff.timesheets emit time_task.status_changed failed', { err })
1114
+ })
1115
+ },
1116
+ }
1117
+
1118
+ registerCommand(createTaskCommand)
1119
+ registerCommand(updateTaskCommand)
1120
+ registerCommand(deleteTaskCommand)
1121
+ registerCommand(statusChangeTaskCommand)