@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
@@ -17,8 +17,15 @@ See [`.ai/specs/implemented/2026-05-08-staff-decouple-from-core.md`](../../../..
17
17
  | Key | Contract |
18
18
  |-----|----------|
19
19
  | `availabilityAccessResolver` | Resolves an `AvailabilityWriteAccess` shape for the authenticated request, including whether the caller may edit availability for all members vs only themselves. Consumed by `planner/api/access.ts` via `container.resolve(..., { allowUnregistered: true })` — planner gracefully degrades to `403 staff_module_not_loaded` when staff is absent. |
20
+ | `timeRoundingResolver` | Server entry point to the rounding registry (EP-32). `resolveStrategy(ctx?)` returns the strategy a scoped call site should run; `roundMinutes(raw, settings, ctx?)` is the module function. See § Time-tracking strategy registries. |
21
+ | `timeRateResolver` | Server entry point to the rate resolver chain (EP-33). `resolveRate(ctx)`. |
22
+ | `timeBillabilityResolver` | Server entry point to the billability chain (EP-34). `resolveBillability(ctx)`. |
23
+ | `timeCapacityResolver` | Server entry point to the capacity provider (EP-40). `resolveCapacity(staffMemberId, dateRange, ctx)`. |
24
+ | `timeOverlapPolicyResolver` | Server entry point to the overlap policy (EP-38). `evaluate(spans, ctx)`. |
25
+ | `timeProjectCodeResolver` | Server entry point to the project code generator (EP-39). `generate(name, taken, ctx?)`. |
26
+ | `timeTrackingAccessResolver` | The single project-access resolver every time-tracking route consults (board, tasks, entries, timesheets, reports). Returns `{ canManageAll, projectIds, staffMemberId }`. `canManageAll: true` (holder of `staff.timesheets.projects.manage`, wildcard-aware) means **unrestricted** — `projectIds` is then empty and MUST NOT be read as "no projects". Otherwise `projectIds` lists the projects with an active, non-deleted `staff_time_project_members` row for the caller. A caller with no staff profile is a normal outcome (`staffMemberId: null`, empty `projectIds`) that drives the no-access screen, never an error. Every query is scoped by `tenantId` + `organizationId`; a request missing either fails closed. |
20
27
 
21
- Resolver shape (from `lib/availabilityAccess.ts`):
28
+ Resolver shapes (from `lib/availabilityAccess.ts` and `lib/time-tracking/access.ts`):
22
29
 
23
30
  ```ts
24
31
  type AvailabilityAccessResolver = {
@@ -26,10 +33,35 @@ type AvailabilityAccessResolver = {
26
33
  ctx: AvailabilityAccessContext,
27
34
  ): Promise<AvailabilityWriteAccess>
28
35
  }
36
+
37
+ type TimeTrackingAccessResolver = {
38
+ resolveProjectAccess(ctx: {
39
+ em: EntityManager
40
+ userId?: string | null
41
+ tenantId?: string | null
42
+ organizationId?: string | null
43
+ userFeatures?: readonly string[]
44
+ /** The manage-all decision, resolved by the caller through `resolveFeatureAccess`. Preferred over `userFeatures`. */
45
+ canManageAll?: boolean
46
+ /** Resolved `access.assignmentGraceDays`; omitted/null falls back to the documented default (14). */
47
+ assignmentGraceDays?: number | null
48
+ /** Injectable clock for the assignment window; defaults to now. */
49
+ now?: Date
50
+ }): Promise<{ canManageAll: boolean; projectIds: string[]; staffMemberId: string | null }>
51
+ }
29
52
  ```
30
53
 
54
+ **Assignment window (spec D-12).** Membership alone is not access: a non-manager reaches a project only while `assigned_start_date <= today <= assigned_end_date + assignmentGraceDays` (a null end date is open-ended). Pass `assignmentGraceDays` from `readTimeTrackingSettings(...).access.assignmentGraceDays` — the resolver deliberately does not read `ModuleConfigService` itself, so it stays testable and a caller that already loaded settings avoids a second lookup. Both extra fields are optional and default safely; existing callers are unaffected. An unparseable date bound or an unusable clock fails **closed**.
55
+
31
56
  `AvailabilityWriteAccess.unregistered?: boolean` is an additive sentinel field (BC surface #2 — STABLE) set to `true` only when staff DI is missing. Existing required fields MUST NOT be removed.
32
57
 
58
+ Route-side, gate a project id with `assertProjectAccess(access, projectId)` from `lib/time-tracking/access.ts` instead of re-implementing the `canManageAll` / `projectIds` check. `userFeatures` is matched with the shared `authorizeFeatures` policy, so `staff.*` and `staff.timesheets.*` grants are honoured — MUST NOT compare the raw feature array with exact string equality.
59
+
60
+ **One RBAC authority (time tracking).** Every time-tracking feature question MUST go through `resolveFeatureAccess(container, userId, features, scope)` in [`lib/time-tracking/featureAccess.ts`](./lib/time-tracking/featureAccess.ts) — the module MUST NOT hand-roll a `rbacService.getGrantedFeatures` / `userHasAllFeatures` read at a call site. It asks `userHasAllFeatures` (the call that carries `isSuperAdmin`), fails closed on every path, and logs the failure. It returns `{ allowed, grantedFeatures }`:
61
+
62
+ - `allowed` is the decision. Authorize on this, and pass it to `resolveProjectAccess` as `canManageAll` rather than letting the resolver re-derive it from an array.
63
+ - `grantedFeatures` is plumbing for the surfaces that take a list — approval policies, the interceptor context, `runRouteMutationGuards`. It is **always an array, never `null`**: a nullable grant list cannot say whether an empty answer means "no grants" or "could not ask", and every consumer treats an empty one the same fail-closed way. MUST NOT gate money, or anything else, on it.
64
+
33
65
  ### API routes (BC surface #7 — STABLE)
34
66
 
35
67
  | Route | Owner | Notes |
@@ -38,14 +70,750 @@ type AvailabilityAccessResolver = {
38
70
 
39
71
  Replaces the deprecated `GET /api/customers/assignable-staff`, which now returns `308 Permanent Redirect` and will be removed no earlier than the next major release.
40
72
 
73
+ #### Time tracking (`/api/staff/timesheets/*`)
74
+
75
+ The consulting suite added the routes below. The URL namespace stays `staff/timesheets` even though the pages moved to `/backend/staff/time-tracking/*` — the routes are a STABLE surface and were not renamed.
76
+
77
+ | Route | Features | Notes |
78
+ |-------|----------|-------|
79
+ | `GET/POST/PUT/DELETE /tasks`, `PATCH /tasks/[id]/status`, `GET/POST/PUT/DELETE /tasks/[id]/comments` | `tasks.view` / `tasks.manage` | Tasks are depth-1 (a subtask is a child task, D-2). Every query is intersected with `resolveProjectAccess`. `?tagIds=` narrows to tasks carrying **every** listed tag. |
80
+ | `GET/POST/PUT/DELETE /task-statuses` | `tasks.view` / `tasks.manage` | Per-project Kanban columns (D-1), seeded when a project is created. |
81
+ | `GET/POST/PUT/DELETE /tags`, `POST/DELETE /tags/{task,entry}-assignments` | `tasks.view` / `tasks.manage` | Refuses to tag an entry locked in a closed report. |
82
+ | `POST /time-entries/[id]/duplicate`, `POST /time-entries/copy-day`, `GET /time-entries/overlaps` | `manage_own` / `view` | Overlaps is advisory and midnight-aware (D-8); copy-day refuses a non-empty target unless `allowDuplicates`. |
83
+ | `GET/POST/PUT/DELETE /reports`, `POST /reports/preview`, `GET /reports/[id]/sheet`, `POST /reports/[id]/{close,unlock}`, `GET /reports/[id]/export` | `reports.view` / `reports.manage` / `reports.unlock` | Close freezes per-entry values and locks the entries; unlock requires a reason. Export never locks. |
84
+ | `GET /my-work` | `view` | Screen 1 aggregate; always the caller's own, no `staffMemberId` parameter exists. |
85
+ | `POST /access-requests` | `view` | Raises a notification instead of disclosing that a project exists. |
86
+ | `GET/PUT /settings` | `view` / `settings.manage` | Tenant-global (§10). Response groups: `rounding`, `defaults`, `targets`, `warnings`, `access`. |
87
+ | `GET /settings/rounding-impact` | `settings.manage` | Projects a candidate rounding rule over a recent window (default 90 days). Locked entries are excluded from the projection and counted separately. |
88
+ | `POST /settings/reapply-rounding` | `settings.manage` | Enqueues the retro-rounding `ProgressJob`; `202` with `progressJobId`, or `200` with a null id when nothing is eligible. **Never touches a locked entry.** |
89
+
90
+ Retro-rounding runs as worker `staff:timesheets-reapply-rounding` on queue `staff-time-reapply-rounding` (`workers/timesheets-reapply-rounding.ts`), driving the `staff.timesheets.time_entries.reapply_rounding` command in batches with `bulkImport.skipEvents`/`skipNotifications` so a tenant-wide restatement refreshes the query index without emitting an event per entry. That command is reachable only by a system actor or a super-admin.
91
+
41
92
  ### ACL feature IDs (BC surface #10 — FROZEN)
42
93
 
43
94
  The following feature IDs are stored in role configurations and MUST NOT be renamed or removed:
44
95
 
45
96
  - `staff.my_availability.manage`
46
97
  - `staff.my_availability.unavailability`
98
+ - Time tracking: `staff.timesheets.view`, `staff.timesheets.manage_own`, `staff.timesheets.manage_all`, `staff.timesheets.projects.view`, `staff.timesheets.projects.manage`, `staff.timesheets.approve`, `staff.timesheets.lock`, `staff.timesheets.tasks.view`, `staff.timesheets.tasks.manage`, `staff.timesheets.reports.view`, `staff.timesheets.reports.manage`, `staff.timesheets.reports.unlock`, `staff.timesheets.settings.manage`, `staff.timesheets.rates.view`
47
99
  - Other `staff.*` features declared in [`acl.ts`](./acl.ts)
48
100
 
101
+ Money is gated on `staff.timesheets.rates.view` and is **absent** from payloads for anyone else, not blanked — never re-add a rate or cost field to a response for a caller without it.
102
+
103
+ ### Time-tracking mutation-guard `resourceKind` taxonomy (BC surface #2 — STABLE)
104
+
105
+ Every hand-rolled `/api/staff/timesheets/*` write route runs the same guard set as
106
+ `makeCrudRoute` (`api/guards.ts` → `runStaffMutationGuards`, which collects
107
+ `getAllMutationGuardInstances()` plus the bridged legacy DI service). It resolves the
108
+ caller's granted features itself, through `resolveFeatureAccess` — **do not pass a
109
+ grant list**. `runMutationGuards` drops any guard whose declared `features` the caller
110
+ does not hold, so supplying an empty list silently disables every feature-gated guard;
111
+ the deprecated `resolveUserFeatures(auth)` did exactly that, because `AuthContext` has
112
+ no `features` field. The
113
+ `resourceKind` a guard matches on is published as
114
+ `STAFF_TIME_TRACKING_RESOURCE_KINDS` in [`api/guards.ts`](./api/guards.ts) — import it
115
+ rather than re-typing a string:
116
+
117
+ | Key | `resourceKind` | Routes |
118
+ |-----|----------------|--------|
119
+ | `timeEntry` | `staff.timesheets.time_entry` | `time-entries/{bulk,copy-day,start-timer}`, `time-entries/[id]/{duplicate,timer-start,timer-stop}` |
120
+ | `timeEntrySegment` | `staff.timesheets.time_entry_segment` | `time-entries/[id]/segments`, `time-entries/[id]/segments/[segmentId]` |
121
+ | `timeTask` | `staff.timesheets.time_task` | `tasks/[id]/status` |
122
+ | `timeTaskComment` | `staff.timesheets.task_comment` | `tasks/[id]/comments` |
123
+ | `timeProject` | `staff.timesheets.time_project` | `time-projects/[id]/change-currency` |
124
+ | `timeProjectMember` | `staff.timesheets.time_project_member` | `my-projects/[projectId]` |
125
+ | `timeReport` | `staff.timesheets.time_report` | `reports/[id]/close`, `reports/[id]/unlock` |
126
+ | `entryTag` | `staff.timesheets.entry_tag` | `tags/entry-assignments` |
127
+ | `taskTag` | `staff.timesheets.task_tag` | `tags/task-assignments` |
128
+ | `settings` | `staff.timesheets.settings` | `settings`, `settings/reapply-rounding` |
129
+ | `accessRequest` | `staff.timesheets.access_request` | `access-requests` |
130
+
131
+ These are the **guard** resource kinds. The eight `makeCrudRoute` time-tracking
132
+ resources derive their own tag from the `events` config the factory reads
133
+ (`resolveResourceAliasesList`), so a guard targeting a factory route matches that
134
+ derived tag instead — for example `staff.timesheets.time.entry` for
135
+ `/api/staff/timesheets/time-entries`. `packages/core/src/modules/staff/__tests__/time-tracking-write-path-contracts.test.ts`
136
+ fails if a route re-types a literal or an entry above stops being used.
137
+
138
+ ### Time-tracking API interceptors (BC surface #7 — STABLE)
139
+
140
+ Every hand-rolled `/api/staff/timesheets/*` route runs the same `before` and `after`
141
+ API interceptor passes the CRUD factory runs, through
142
+ [`api/timesheets/_shared/withTimesheetInterceptors.ts`](./api/timesheets/_shared/withTimesheetInterceptors.ts).
143
+
144
+ - `targetRoute` is the pathname **without** the `/api/` prefix — `staff/timesheets/time-entries/bulk`,
145
+ `staff/timesheets/my-work` — matching `normalizeInterceptorRoutePath` in the factory.
146
+ - A route with a dynamic segment carries the **concrete id** in that path, so target it
147
+ with the registry's prefix wildcard: `staff/timesheets/time-entries/*`, never a literal
148
+ containing `[id]`.
149
+ - The before pass can rewrite the body (mutations) or the query (read aggregates) and can
150
+ deny with its own status; the after pass shapes the JSON body. `reports/[id]/export`
151
+ answers with bytes, so its after pass shapes a **descriptor** instead —
152
+ `filename` and `contentType` are read back and applied to the download headers.
153
+ - The interceptor context always carries `tenantId` + `organizationId` and fails closed
154
+ with `400` when either is missing. `/settings` and `/settings/reapply-rounding` are
155
+ tenant-global and opt in with `tenantGlobal: true`.
156
+
157
+ `__tests__/time-tracking-write-path-contracts.test.ts` fails if one of the routes drops
158
+ the wiring.
159
+
160
+ ### Time-tracking response enricher hosts (BC surface #2 — STABLE)
161
+
162
+ Declared in [`data/enrichers.ts`](./data/enrichers.ts). Register your own enricher against
163
+ the same `targetEntity` and it composes with these rather than replacing them.
164
+
165
+ | `targetEntity` | Enricher id | Adds |
166
+ |-----|-----|-----|
167
+ | `staff:staff_time_project` | `staff.timesheets-projects-portfolio` | `_staff`: hours trend, financials, member preview, customer name |
168
+ | `staff:staff_time_task` | `staff.timesheets-tasks-rollup` | `ownMinutes`, `loggedMinutes`, `childCount`, `doneChildCount` (top level, by spec) |
169
+ | `staff:staff_time_task` | `staff.timesheets-tasks-tags` | `tagIds`, `tags` |
170
+ | `staff:staff_time_task` | `staff.timesheets-tasks-context` | `_staff`: project name/code/colour, status, assignee |
171
+ | `staff:staff_time_entry` | `staff.timesheets-time-entries` | `description` alias, `roundedMinutes`, `isLocked`, `lockedReportId`, `tags`, plus `cost`/`currencyCode` |
172
+ | `staff:staff_time_report` | `staff.timesheets-reports` | `_staff`: freeze state, frozen entry count, export history, totals |
173
+
174
+ Money (`hourlyRate`, `cost`, `currencyCode`, `totalAmount`) is **added** for a holder of
175
+ `staff.timesheets.rates.view` and absent for everyone else — never blanked.
176
+
177
+ Each of the six is also published under a `<id>.query-engine` alias whose `targetEntity`
178
+ is the **dot** form (`staff.staff_time_entry`) with `queryEngine: { enabled: true }`. The
179
+ CRUD factory looks an enricher up by the colon form a route declares; the query engine
180
+ looks it up by `entityIdToEventEntity(entity)`, which is the dot form — so an enricher
181
+ published only under the colon form never runs in a query pipeline. The two lookups never
182
+ both match, so nothing runs twice.
183
+
184
+ ### Time-tracking events: browser and webhooks (BC surface #5 — FROZEN ids)
185
+
186
+ - **Browser.** `clientBroadcast: true` is set on `time_entry.{created,updated,deleted,timer_started,timer_stopped}`,
187
+ `time_report.{closed,unlocked}`, `time_project.budget_threshold_reached` and
188
+ `time_task.status_changed`. The DOM Event Bridge filters only by tenant + organization —
189
+ **no feature check** — so a broadcast payload MUST NOT carry a rate, a cost or an amount.
190
+ `time_report.closed` therefore carries minute totals but no `totalAmount`, and
191
+ `time_project.budget_threshold_reached` carries `budgetValue`/`usedValue` only for an
192
+ `hours` budget. **Money is not the only thing that audience rules out.** A broadcast
193
+ payload also carries no operator free text and no customer identity:
194
+ `time_report.unlocked` omits its mandatory `reason` (2000 characters of prose about a
195
+ client's billing, kept on the `StaffTimeReportEvent` audit row instead) and
196
+ `time_report.closed` omits `customerId`. `reference` and the minute totals stay —
197
+ stable identifiers and non-money aggregates the published webhook contract and the
198
+ live reports screen both need, and the DOM bridge can narrow an audience only by user
199
+ or role id, which this module deliberately does not gate on.
200
+ `__tests__/timeTrackingEventPayloads.test.ts` fails if a forbidden field reappears.
201
+ - **Webhooks.** There is no per-event registration: `packages/webhooks` subscribes with
202
+ `event: '*'` and matches each id against a webhook's subscribed patterns, so declaring
203
+ the event in `events.ts` IS its registration. Delivery needs `tenantId` in the payload —
204
+ the dispatcher drops anything without one. The payload contract every subscriber codes
205
+ against is [`events.payloads.ts`](./events.payloads.ts); `__tests__/timeTrackingEventPayloads.test.ts`
206
+ fails if a declared `staff.timesheets.*` id has no schema or a schema without `tenantId`.
207
+
208
+ ### Customer-portal time reports (EP-50 — BC surface #7, STABLE)
209
+
210
+ | Route / page | Guard | Notes |
211
+ |---|---|---|
212
+ | `GET /api/staff/portal/time-reports` | portal session + `portal.time_reports.view` | Closed reports of the signed-in customer only |
213
+ | `GET /api/staff/portal/time-reports/{id}` | same | A report of another customer answers `404`, never `403` |
214
+ | `/{orgSlug}/portal/time-reports` | `requireCustomerAuth` + `requireCustomerFeatures: ['portal.time_reports.view']` | Listed in the portal sidebar (`nav.group: 'main'`) |
215
+ | `/{orgSlug}/portal/time-reports/{id}` | same | Hosts `portal:staff.time_report:{before,after}` |
216
+
217
+ **The ownership check is four predicates in one WHERE clause, and all four come
218
+ from the session:**
219
+
220
+ ```
221
+ tenant_id = auth.tenantId
222
+ organization_id = auth.orgId
223
+ customer_id = auth.customerEntityId -- customer_users.customer_entity_id
224
+ status = 'closed' AND deleted_at IS NULL
225
+ ```
226
+
227
+ `customerEntityId` is the FK into `customers:customer_entity`, the same id
228
+ `staff_time_reports.customer_id` holds (the link EP-44 declares). A portal session
229
+ without one is refused with `403` rather than shown an unscoped list. The detail
230
+ route loads the row **with** the predicates rather than loading it and checking
231
+ afterwards, so a foreign id is never read even momentarily. The clause is written
232
+ once, in [`lib/time-tracking/portalReports.ts`](./lib/time-tracking/portalReports.ts),
233
+ and pinned by `api/portal/time-reports/__tests__/route.test.ts`.
234
+
235
+ **Money is structurally absent on this surface, not conditionally hidden.**
236
+ `staff.timesheets.rates.view` is a *staff* feature graded by `rbacService`; a
237
+ portal identity is graded by `CustomerRbacService` against the disjoint `portal.*`
238
+ namespace and can never hold it. So the portal response schemas carry no rate,
239
+ cost, amount or currency field at all — the SQL does not select
240
+ `frozen_rate_amount` or `frozen_amount`, and a test fails if a money-shaped key
241
+ appears in a response body.
242
+
243
+ `portal.time_reports.view` is granted to the `buyer` and `viewer` customer roles
244
+ through `setup.defaultCustomerRoleFeatures`. It is deliberately **not** in
245
+ [`acl.ts`](./acl.ts) — that file is the staff feature catalog.
246
+
247
+ **Staff takes no static dependency on `customer_accounts`.** The routes resolve
248
+ the portal identity with a dynamic `import()` and answer `401` if it fails, and
249
+ `lib/time-tracking/portalRecipients.ts` reads `customer_users` by table name
250
+ through Kysely rather than importing an entity. `requires` in
251
+ [`index.ts`](./index.ts) is unchanged.
252
+
253
+ ### The portal event mirror (EP-06 — BC surface #5, FROZEN id)
254
+
255
+ `staff.timesheets.time_report.portal_published` is `portalBroadcast: true` and
256
+ `excludeFromTriggers: true`. `time_report.closed` and `.exported` are **not**
257
+ portal-broadcast, and that is a security decision rather than an omission: the
258
+ portal SSE stream (`customer_accounts/api/portal/events/stream.ts`) filters a
259
+ broadcast by tenant + organization and narrows to named people only when the
260
+ payload carries `recipientUserIds`. One organization serves many customers, so
261
+ flagging `time_report.closed` — which carries `reference` and the minute totals —
262
+ would give every client a feed of every other client's reports.
263
+ That event is also `clientBroadcast: true` with a published webhook payload, so it
264
+ cannot be narrowed without breaking its backoffice consumers.
265
+
266
+ [`subscribers/time-report-portal-broadcast.ts`](./subscribers/time-report-portal-broadcast.ts)
267
+ is the only emitter. It resolves the portal users of the report's own customer and
268
+ **does not emit at all** when that list is empty — the same rule
269
+ `warranty_claims/AGENTS.md` states for its portal event. The payload carries the
270
+ reference and the period; no money, because SSE applies no feature check.
271
+ Subscribe on the portal with `usePortalAppEvent`.
272
+
273
+ ### Time-tracking sync lifecycle subscribers (BC surface #5 — STABLE)
274
+
275
+ The seven `makeCrudRoute` time-tracking resources declare an `events:` config, which is
276
+ the only thing `deriveLifecycleEventIds` (`shared/lib/crud/factory.ts`) needs to resolve —
277
+ so `runSyncBeforeEvent` / `runSyncAfterEvent` dispatch on every create, update and delete
278
+ they serve. A sync subscriber runs **inside** the write pipeline: it can veto the write
279
+ before the command executes, and rewrite the payload the command receives.
280
+
281
+ The ids are `<events.module>.<events.entity>.<phase>`, with phases `creating|created`,
282
+ `updating|updated`, `deleting|deleted` (`LIFECYCLE_ACTION_MAP`, `factory.ts:631`):
283
+
284
+ | Route | Entity | `before` phases | `after` phases |
285
+ |-------|--------|-----------------|----------------|
286
+ | `/time-entries` | `staff.timesheets.time_entry` | `.creating` `.updating` `.deleting` | `.created` `.updated` `.deleted` |
287
+ | `/time-projects` | `staff.timesheets.time_project` | same three | same three |
288
+ | `/time-projects/[id]/employees` | `staff.timesheets.time_project_member` | same three | same three |
289
+ | `/tasks` | `staff.timesheets.time_task` | same three | same three |
290
+ | `/task-statuses` | `staff.timesheets.time_task_status` | same three | same three |
291
+ | `/tags` | `staff.timesheets.time_tag` | same three | same three |
292
+ | `/reports` | `staff.timesheets.time_report` | same three | same three |
293
+
294
+ `/tasks/[id]/comments` declares no `events:` config and therefore dispatches no sync
295
+ lifecycle event. `__tests__/time-tracking-write-path-contracts.test.ts` derives this table
296
+ from the `events` configs and fails if the two disagree.
297
+
298
+ **Declaring one.** A subscriber file under `subscribers/` exporting
299
+ `metadata = { event, sync: true, priority?, id? }` plus a default handler. `event` accepts
300
+ the wildcards `matchWildcardPattern` supports, so `staff.timesheets.*.creating` covers the
301
+ whole family. `priority` sorts **ascending** and defaults to `50` — a lower number runs
302
+ earlier, and an earlier subscriber's `modifiedPayload` is visible to the next one.
303
+
304
+ **What a handler may return** (`SyncCrudEventResult`):
305
+
306
+ - `undefined` — allow, change nothing.
307
+ - `{ ok: false, status?, message?, body? }` — **veto**. The route answers with that status
308
+ (default `422`, body defaults to `{ error: message, subscriberId }`) and the command
309
+ never runs. Before-phase only.
310
+ - `{ modifiedPayload }` — shallow-merged into the mutation payload.
311
+
312
+ **Four things the types do not tell you.**
313
+
314
+ 1. On these routes the payload is the **mapped command input**, not the request body:
315
+ `mapInput` (`parseScopedCommandInput`) has already run, so field names are the
316
+ validator's camelCase, dates are coerced, and `tenantId` / `organizationId` are present.
317
+ 2. `modifiedPayload` is merged into that input and handed straight to the command — it is
318
+ **not** re-validated against the create/update schema. A subscriber can therefore write
319
+ a field the schema would have rejected; validate your own contribution.
320
+ 3. The **delete** phase is narrower: its payload carries no mutation data at all
321
+ (`payload` is `undefined`) and a `modifiedPayload` is ignored. Delete subscribers can
322
+ veto, not rewrite.
323
+ 4. The `after` ids are the same strings as the async CRUD events in `events.ts`, and
324
+ bootstrap registers a `sync: true` subscriber in **both** registries
325
+ (`bootstrap.ts` → `registerModuleSubscribers` and `registerSyncSubscribers`), so an
326
+ after-phase handler is invoked twice: once in-pipeline, once from the event bus. Put
327
+ in-pipeline work on the `before` phase and plain reactions on an ordinary async
328
+ subscriber. The `before` ids are never emitted on the bus and need no `events.ts` entry.
329
+
330
+ Proved end to end in `__tests__/time-tracking-sync-subscribers.test.ts`.
331
+
332
+ ## Host extension points
333
+
334
+ Declared in [`extension-points.ts`](./extension-points.ts) and emitted into the generated
335
+ extension-point catalog and module-facts by `yarn generate` + the CLI build. Every id below
336
+ is a **FROZEN** contract surface (BC surface #6): a third-party widget names it verbatim, so
337
+ renaming one silently unbinds every contribution aimed at it. `__tests__/timeTrackingExtensionHosts.test.ts`
338
+ pins the whole list, verifies each declaration's `source` file exists and references
339
+ `extensionPoints.hosts.<key>` (an unreferenced declaration is emitted as an
340
+ `unbound-declaration` diagnostic, not a usable host).
341
+
342
+ **Two id conventions meet here and neither is negotiable.** `crud-form:` hosts carry the
343
+ **dot** form of the entity id (`staff.staff_time_project`) because `CrudForm` derives its
344
+ own spot id from `entityIds` by replacing every colon with a dot — the colon form would
345
+ name a spot no widget is ever loaded for. `detail:` hosts carry the **colon** form
346
+ (`detail:staff:staff_time_task:header`); nothing derives those, the host picks them.
347
+
348
+ ### DataTable hosts
349
+
350
+ | Table id | Screen |
351
+ |---|---|
352
+ | `staff.time_entries.list` | `backend/staff/time-tracking/entries/page.tsx` |
353
+ | `staff.time_projects.list` | `backend/staff/time-tracking/projects/page.tsx` |
354
+ | `staff.time_reports.list` | `backend/staff/time-tracking/reports/page.tsx` |
355
+
356
+ Each derives the nine standard surfaces — `data-table:<id>:{columns,row-actions,bulk-actions,filters,toolbar,search-trailing,header,footer,empty-state}`.
357
+
358
+ ### CrudForm hosts
359
+
360
+ | Spot id | Host |
361
+ |---|---|
362
+ | `crud-form:staff.staff_time_project` | `backend/staff/time-tracking/projects/{create,[id]/edit}/page.tsx` (real `CrudForm`) |
363
+ | `crud-form:staff.staff_time_entry` | `lib/time-tracking-ui/TimeEntryDialog.tsx` (hand-rolled host) |
364
+ | `crud-form:staff.staff_time_task` | `lib/time-tracking-ui/NewTaskDialog.tsx` (hand-rolled host) |
365
+ | `crud-form:staff.staff_time_report` | `backend/staff/time-tracking/reports/create/page.tsx` (hand-rolled host) |
366
+
367
+ Each carries the standard child spots (`:before-fields`, `:fields`, `:after-fields`,
368
+ `:footer`, `:group:<groupId>`, `:field:<fieldId>:{before,after}`, …).
369
+
370
+ The project form's group ids are a published contract — `PROJECT_FORM_GROUP_IDS` in
371
+ [`backend/staff/time-tracking/projects/projectFormConfig.ts`](./backend/staff/time-tracking/projects/projectFormConfig.ts):
372
+ `basics`, `billing`, `budget`, `status`, `team`, `rounding`, `details` (a `compact` host
373
+ renders `basics` + `billing` only). Address one with `CrudFormInjectionSpots.group(...)`.
374
+
375
+ The three hand-rolled hosts are not `CrudForm`s; they replay its contract by hand. What
376
+ they support and what they do not:
377
+
378
+ | Lifecycle | TimeEntryDialog | NewTaskDialog | Report create |
379
+ |---|---|---|---|
380
+ | `onFieldChange` (value + `sideEffects` + messages written back) | yes | no | no |
381
+ | `onBeforeSave` (`ok: false` blocks the write, `message` is flashed, `fieldErrors` map onto the task/duration fields, `requestHeaders` are merged into the request **under** the optimistic-lock header) | yes | `ok`/`message` only | no |
382
+ | `onAfterSave` | yes | yes | no |
383
+ | `transformFormData` / delete lifecycle | no | no | no |
384
+
385
+ Pinned by `lib/time-tracking-ui/__tests__/TimeEntryDialog.test.tsx` →
386
+ "crud-form host lifecycle".
387
+
388
+ ### Injection spots
389
+
390
+ | Spot id | Host | Context |
391
+ |---|---|---|
392
+ | `portal:staff.time_report:{before,after}` | `frontend/[orgSlug]/portal/time-reports/[id]/page.tsx` | `{ orgSlug, reportId, reference, periodFrom, periodTo, resolvedFeatures }` — a **customer** session, never a staff one, and no money |
393
+ | `detail:staff:staff_time_project:{header,status-badges,tabs,sidebar,footer}` | `backend/staff/time-tracking/projects/[id]/page.tsx` | `{ entityId, recordId, projectId, path, retryLastMutation }` |
394
+ | `detail:staff:staff_time_task:{header,status-badges,tabs,sidebar,footer}` | `lib/time-tracking-ui/TaskDrawer.tsx` | `{ entityId, recordId, taskId, timeProjectId, retryLastMutation }` |
395
+ | `detail:staff:staff_time_report:{header,status-badges,footer}` | `backend/staff/time-tracking/reports/[id]/page.tsx` | `{ entityId, recordId, reportId, reference, isClosed, periodFrom, periodTo, retryLastMutation }` |
396
+ | `staff.time_report.sheet:{before-lines,after-totals}` | `lib/time-tracking-ui/ReportSheet.tsx` | `{ entityId, recordId, reportId, reference, periodLabel, currencyCode }` |
397
+ | `staff.timesheet:toolbar` | `backend/staff/time-tracking/timesheet/page.tsx` | `{ staffMemberId, periodKind, periodFrom, periodTo, view, readOnly, retryLastMutation }` |
398
+ | `staff.timesheet:period-footer` | `lib/time-tracking-ui/TimesheetPeriodFooter.tsx` | `{ workingDays, dailyHours }` |
399
+ | `staff.timesheet:day-cell-actions` | `lib/time-tracking-ui/TimesheetCalendar.tsx` | `{ date, isToday, isWeekend, totalMinutes }` |
400
+ | `staff.time_task.board:toolbar` | `lib/time-tracking-ui/KanbanBoard.tsx` | `{ timeProjectId, projectName, statusIds }` |
401
+ | `staff.time_task.board:column-header` | `lib/time-tracking-ui/KanbanColumn.tsx` | `{ statusId, statusName, isDone, total, loggedMinutes }` |
402
+ | `staff.time_task.board:{card-badges,card-footer}` | `lib/time-tracking-ui/KanbanCard.tsx` | `{ entityId, recordId, taskId, timeProjectId, taskStatusId, timerRunning }` |
403
+ | `staff.my_work:{before-sections,after-sections}` | `backend/staff/time-tracking/page.tsx` | `{ staffMemberId, today, projectIds, retryLastMutation }` |
404
+ | `staff.time_tracking.settings:sections` | `backend/staff/time-tracking/settings/page.tsx` | `{ moduleId, canManage }` |
405
+ | `staff.timesheets.timer-bar:actions` | `lib/timesheets-ui/TimerBar.tsx` | `{ staffMemberId, retryLastMutation }` |
406
+
407
+ No context carries a rate, cost or amount; contributions that need money must ask for it
408
+ behind `staff.timesheets.rates.view` themselves. An empty spot renders nothing and changes
409
+ no DOM — pinned by `__tests__/timeTrackingInjectionSpots.test.ts`.
410
+
411
+ **Tabs.** `detail:staff:staff_time_project:tabs` is a real tab host: a widget mapped to it
412
+ with `placement: { kind: 'tab', groupId, groupLabel, priority }` gets its own
413
+ `TabsTrigger` and its own `TabsContent` in the page's `<Tabs>` (higher `priority` first,
414
+ `metadata.title` as the label fallback). `detail:staff:staff_time_task:tabs` is **not** —
415
+ the task drawer is a single-column stack with no tab strip, so the spot renders a
416
+ contributed widget as an additional panel at the end of the drawer body.
417
+
418
+ **Server-side "my work" sections (deferred).** `staff.my_work:{before,after}-sections` are
419
+ client render spots only. The spec's server-side section-contribution contract — a module
420
+ adding its own section to `api/timesheets/my-work/myWorkAggregate.ts` — is **not**
421
+ implemented: the aggregate returns one closed, zod-validated shape that the page's KPI
422
+ strip, quick-entry targets and totals all read positionally, so a contributed section
423
+ needs its own registry, its own per-section scoping and its own response slot before it can
424
+ be added without loosening that contract. A client spot fetching its own data covers the
425
+ same use case today.
426
+
427
+ ### Replaceable components
428
+
429
+ `replace` / `wrapper` / `props` targets, registered by each component's own module through
430
+ `registerComponent` and resolved with `useRegisteredComponent`. Every one publishes a zod
431
+ `propsSchema`, which `useRegisteredComponent` parses in development — a replacement that
432
+ does not satisfy it falls back to the original component. Catalogued in
433
+ [`widgets/components.ts`](./widgets/components.ts).
434
+
435
+ | Handle | Component |
436
+ |---|---|
437
+ | `staff.time_entry_dialog` | `lib/time-tracking-ui/TimeEntryDialog.tsx` |
438
+ | `staff.timer_bar` | `lib/timesheets-ui/TimerBar.tsx` |
439
+ | `staff.kanban_card` | `lib/time-tracking-ui/KanbanCard.tsx` |
440
+ | `staff.kanban_column` | `lib/time-tracking-ui/KanbanColumn.tsx` |
441
+ | `staff.timesheet_grid` | `backend/staff/time-tracking/timesheet/GridView.tsx` |
442
+ | `staff.timesheet_list` | `lib/timesheets-ui/ListView.tsx` |
443
+ | `staff.timesheet_calendar` | `lib/time-tracking-ui/TimesheetCalendar.tsx` |
444
+ | `staff.report_sheet` | `lib/time-tracking-ui/ReportSheet.tsx` |
445
+ | `staff.project_card` | `lib/timesheets-projects-ui/ProjectCard.tsx` |
446
+ | `staff.entries_summary_footer` | `lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx` |
447
+
448
+ ## Time-tracking strategy registries
449
+
450
+ EP-32…EP-41. Ten closed pure functions and DB enums are now registries. **Every one
451
+ registers the module's existing implementation as its built-in default at module load,
452
+ so with no contribution the observable behaviour is the one the module shipped.** The
453
+ built-in ids below are the `runtimeContract` of the matching `specialized-registry` host
454
+ in [`extension-points.ts`](./extension-points.ts).
455
+
456
+ | EP | Registry id / host | Register with | Built-in default | Resolution |
457
+ |---|---|---|---|---|
458
+ | 32 | `staff.time_tracking.rounding` | `registerTimeRoundingStrategy({ id, labelKey, priority?, round(raw, ctx) })` — [`lib/time-tracking/rounding.ts`](./lib/time-tracking/rounding.ts) | `staff.time_tracking.rounding.unit` (`up`/`nearest` × `0\|5\|10\|15`) | single winner |
459
+ | 33 | `staff.time_tracking.rate` | `registerTimeRateResolver({ id, priority?, resolve(ctx): number \| null })` — [`lib/time-tracking/cost.ts`](./lib/time-tracking/cost.ts) | `staff.time_tracking.rate.entry_override_then_project` | chain, first non-null |
460
+ | 34 | `staff.time_tracking.billability` | `registerBillabilityResolver({ id, priority?, resolve(ctx): boolean \| null })` — [`lib/time-tracking/billability.ts`](./lib/time-tracking/billability.ts) | `staff.time_tracking.billability.project_then_tenant` | chain, first non-null |
461
+ | 35 | `staff.time_tracking.report_export_format` | `registerReportExportFormat({ id, labelKey, mimeType, extension, serialize(input) })` — [`lib/timesheets-reports/reportExportFormats.ts`](./lib/timesheets-reports/reportExportFormats.ts) | `pdf`, `csv`, `xlsx` | keyed by id |
462
+ | 36 | `staff.time_tracking.report_grouping` | `registerReportGrouping({ id, labelKey, groupOf(entry), labelOf(key, ctx), sort })` — [`lib/timesheets-reports/reportGroupings.ts`](./lib/timesheets-reports/reportGroupings.ts) | `project_task`, `project_person`, `project_day` | keyed by id |
463
+ | 37 | `staff.time_tracking.time_entry_source` | `registerTimeEntrySource({ id, labelKey, icon, editable })` — [`lib/time-tracking/timeEntrySources.ts`](./lib/time-tracking/timeEntrySources.ts) | `manual`, `timer`, `kiosk`, `mobile` | keyed by id |
464
+ | 38 | `staff.time_tracking.overlap_policy` | `registerOverlapPolicy({ id, priority?, evaluate(spans, ctx) })` — [`lib/time-tracking/overlap.ts`](./lib/time-tracking/overlap.ts) | `staff.time_tracking.overlap.warn_when_enabled` | max severity |
465
+ | 39 | `staff.time_tracking.project_code_generator` | `registerProjectCodeGenerator({ id, priority?, generate(name, taken, ctx) })` — [`lib/time-tracking/projectCode.ts`](./lib/time-tracking/projectCode.ts) | `staff.time_tracking.project_code.initials` | single winner |
466
+ | 40 | `staff.time_tracking.capacity_provider` | `registerCapacityProvider({ id, priority?, resolve(staffMemberId, dateRange, ctx) })` — [`lib/time-tracking/capacity.ts`](./lib/time-tracking/capacity.ts) | `staff.time_tracking.capacity.flat_daily_hours` | single winner |
467
+ | 41 | `staff.time_tracking.report_approval_policy` | `registerReportApprovalPolicy({ id, priority?, canClose?, canUnlock?, onClosed? })` — [`lib/timesheets-reports/reportApprovalPolicies.ts`](./lib/timesheets-reports/reportApprovalPolicies.ts) | `staff.time_tracking.report_approval.acl_only` | conjunction, first refusal |
468
+ | 42 | `staff.time_tracking.setting_key` | `registerTimeTrackingSettingKey({ group, key, schema, default, labelKey, priority? })` — [`lib/time-tracking/settingKeys.ts`](./lib/time-tracking/settingKeys.ts) | the eight frozen keys | keyed by `<group>.<key>` |
469
+ | 51 | `staff.time_tracking.recalculation` | `registerTimeTrackingRecalculation({ id, labelKey, priority?, run(ctx) })` — [`lib/time-tracking/recalculations.ts`](./lib/time-tracking/recalculations.ts) | `staff.time_tracking.recalculation.rounding` | keyed by id, run in order |
470
+
471
+ ### The four resolution orders
472
+
473
+ All four order candidates by **descending `priority` (default `0`), ties by registration
474
+ order**, and every built-in registers at `BUILT_IN_STRATEGY_PRIORITY` (`-1000`) so it is
475
+ always the last candidate. They differ only in what they do with that order:
476
+
477
+ - **single winner** — the first candidate runs; nothing else is consulted.
478
+ - **chain, first non-null** — candidates are asked in order and the first non-`null`
479
+ answer wins. Returning `null` is an abstention, not a "no", so an unopinionated
480
+ contribution cannot change an entry.
481
+ - **max severity** (`block` > `warn` > `allow`) — a contributed overlap policy can only
482
+ ESCALATE. Nothing can suppress a warning the tenant asked for.
483
+ - **conjunction, first refusal** — every approval policy must agree. `canClose`/`canUnlock`
484
+ return a refusal or nothing, **never a grant**, so no policy can open a door the ACL
485
+ closed. The ACL check in the close/unlock routes runs first and unconditionally.
486
+
487
+ ### A built-in is a separate slot, and a contribution's answer is not trusted
488
+
489
+ Two rules the registry itself enforces, so a call site cannot get them wrong:
490
+
491
+ - **`register()` refuses a built-in id and no disposer can remove a built-in.** The
492
+ eleven built-in ids are published above, and the map used to be last-writer-wins:
493
+ registering `{ id: 'staff.time_tracking.rounding.unit', … }` made you the built-in,
494
+ put you on the unscoped path the fail-closed gate exists to keep byte-identical,
495
+ and let your disposer delete the real built-in for the life of the process. Built-ins
496
+ now live in a map `register()` cannot reach (`registerBuiltIn`, module-load only),
497
+ and `selectScopedStrategy` takes the built-in **by reference** rather than looking
498
+ its id up among the candidates.
499
+ - **Every strategy invocation is wrapped, and every numeric answer is clamped.**
500
+ `registries/invoke.ts` publishes `runStrategy` (single-winner: fall back to the
501
+ built-in), `tryStrategy` (chain: skip this candidate, ask the next) and
502
+ `clampToStoredMinutes`. `roundMinutes` clamps to `Math.max(0, Math.round(v))` or
503
+ falls back — `rounded_minutes` is an `integer` column and the sole input to cost, and
504
+ the obvious contributed strategy (`Math.floor(raw / ctx.settings.unitMinutes) * …`)
505
+ is `NaN` under the shipped default `unitMinutes: 0`, which stores garbage and then
506
+ bills 0.00 in silence. `CapacityProvider.resolve` and `ProjectCodeGenerator.generate`
507
+ validate their answers the same way. Two deliberate exceptions: a failing export
508
+ `serialize` re-raises as an internal error naming the format (falling back would
509
+ answer with another format's bytes under the requested MIME type), and a throwing
510
+ `canClose`/`canUnlock` becomes a **refusal** — those are gates, and the built-in
511
+ grants, so falling back to it would turn a broken policy into permission.
512
+
513
+ ### Scoping — fail closed to the built-in
514
+
515
+ EP-42 is the one exception to the paragraph below: settings are tenant-global by spec
516
+ §10, so its host declares `scopeContract: 'tenant'` and a contributed key is stored with
517
+ `{ tenantId }` and nothing else.
518
+
519
+ Every resolver context carries `tenantId` + `organizationId`
520
+ ([`lib/time-tracking/registries/scope.ts`](./lib/time-tracking/registries/scope.ts)). A
521
+ **contributed** strategy is consulted only when both are present; with either missing the
522
+ built-in is the only candidate. Failing closed lands on the built-in rather than on an
523
+ error, because the built-in is the same pure code that ran before the registries existed.
524
+
525
+ ### Client-side call sites
526
+
527
+ The registries are plain module-scope `Map`s (the `registerPaymentProvider` idiom), not DI
528
+ singletons, so the browser bundle reaches them by importing the `register*` function
529
+ directly — no container. The DI keys in the table above are the *server* entry point and
530
+ exist so an app can replace a resolver through `entry.overrides` DI; resolving one and
531
+ calling the module function are equivalent.
532
+
533
+ Four call sites run in the browser and none of them has a tenant id to hand, so all four
534
+ resolve the built-in today, byte-identically to before:
535
+ `lib/time-tracking-ui/TimeEntryDialog.tsx` (rate + cost preview),
536
+ `lib/time-tracking-ui/timeTrackingSettingsForm.ts` (`buildRoundingExamples`),
537
+ `lib/time-tracking-ui/ProjectCodeField.tsx` (`deriveProjectCode`), and
538
+ `lib/time-tracking/roundingImpact.ts`. A client host that wants a contribution to apply
539
+ must pass the scope explicitly.
540
+
541
+ ### Schema
542
+
543
+ EP-37 and EP-36 replaced two database enums with registry-backed validation:
544
+ `staff_time_entries.source` and `staff_time_reports.grouping` are plain `text` columns
545
+ (`Migration20260824143357_staff.ts` drops `staff_time_entries_source_check` and
546
+ `staff_time_reports_grouping_check`). The write-side guard moved into
547
+ `data/validators.ts`, which asks the registries rather than a literal `z.enum`, and
548
+ `normalizeTimeEntrySource` / `normalizeReportGrouping` coerce a stored value whose
549
+ contributing module has since been removed back to `manual` / `project_task`.
550
+
551
+ Pinned by [`lib/time-tracking/__tests__/strategyRegistries.test.ts`](./lib/time-tracking/__tests__/strategyRegistries.test.ts).
552
+
553
+ ## Time-tracking data model and settings
554
+
555
+ EP-42…EP-45. Four declaration surfaces. Read the "does not" column of each before
556
+ building on one — three of the four are contracts that a later phase still has to make
557
+ load-bearing, and pretending otherwise is how a third-party module ships a broken screen.
558
+
559
+ ### Contributed settings keys (EP-42 — BC surface #2, STABLE)
560
+
561
+ `TIME_TRACKING_SETTING_KEYS`, `normalizeTimeTrackingSettings` and
562
+ `staffTimeTrackingSettingsSchema` all read
563
+ [`lib/time-tracking/settingKeys.ts`](./lib/time-tracking/settingKeys.ts) now. The eight
564
+ keys the module shipped are registered there as built-ins, so with no contribution the
565
+ defaults, the validating schema, the read and the eight `ModuleConfigService` rows are
566
+ what they always were.
567
+
568
+ ```ts
569
+ registerTimeTrackingSettingKey({
570
+ group: 'jira', // top-level group in the settings object
571
+ key: 'projectKey', // leaf inside it; the config row is named `jira.projectKey`
572
+ schema: z.string().min(1).max(20),
573
+ default: 'OPS', // must satisfy `schema`, or registration throws
574
+ labelKey: 'jira.settings.projectKey',
575
+ priority: 0,
576
+ })
577
+ ```
578
+
579
+ - **Storage and scope.** One `ModuleConfigService` row per key under module id
580
+ `staff.time_tracking`, named `<group>.<key>`, written with `{ tenantId }` only.
581
+ A contributed value is therefore **tenant-global**, with `organization_id` null —
582
+ there is no per-organization, per-project or per-customer override, and a contribution
583
+ cannot opt into one. `GET`/`PUT /api/staff/timesheets/settings` carry it in the group
584
+ it declared.
585
+ - **Validation.** `buildTimeTrackingSettingsSchema()` composes
586
+ `schema.optional().default(default)` per key and one `.optional().default({...})` per
587
+ group. The settings route builds it **per request**, and publishes it through OpenAPI
588
+ getters, so a key registered after the route module first loaded still validates and
589
+ still appears in the published schema. The exported `staffTimeTrackingSettingsSchema`
590
+ const is the load-time snapshot and stays for backward compatibility.
591
+ - **A stored value that no longer validates falls back to the registered default**, the
592
+ same way a stored rounding unit the schema stopped accepting always did. Registering a
593
+ key whose id collides with one of the eight built-ins throws.
594
+ - **Rendering it.** Pair the key with a widget on `staff.time_tracking.settings:sections`
595
+ (EP-26). The spot context carries `{ moduleId, canManage, keys, values, setValue }`:
596
+ `keys` is `contributedTimeTrackingSettingKeys()`, `values` is keyed by `<group>.<key>`
597
+ and holds contributed keys only, and `setValue(id, value)` writes into the page draft
598
+ so the page's own Save round-trips a key it knows nothing about. The eight built-ins
599
+ are absent from `values` — the page renders those itself.
600
+
601
+ ### Time-tracking custom fields (EP-43)
602
+
603
+ [`ce.ts`](./ce.ts) declares `staff_time_entry`, `staff_time_project`, `staff_time_task`,
604
+ `staff_time_report` and `staff_time_tag` next to `staff_team_member`, each with
605
+ `showInSidebar: false` and no default fields.
606
+
607
+ **What the declaration does.** All five are *system* entities, so
608
+ `entities/lib/install-from-ce.ts` seeds only their `fields` and never writes a
609
+ `custom_entities` row — `label`, `description` and `showInSidebar` are metadata for code,
610
+ not for the installer. The one runtime behaviour it changes today is `labelField`, read by
611
+ `attachments/lib/assignmentDetails.ts` to label an attached record. It also marks the
612
+ entity `customFields: true` in the generated module facts.
613
+
614
+ **What it does NOT do, and what each surface still needs:**
615
+
616
+ | Surface | State |
617
+ |---|---|
618
+ | Defining a custom field on these entities | Already worked before EP-43. `entities/api/entities.ts` lists every generated entity id, so the Data designer always offered them. |
619
+ | Storing a value through the CRUD routes | **Does not work.** All five `makeCrudRoute` resources are command-backed, and `factory.ts` honours `actions.create.customFields` / `update.customFields` only on its ORM write path (`factory.ts:2403`, `:2741`). A `cf_*` key posted to `/api/staff/timesheets/*` is dropped in silence. Closing it means threading `splitCustomFieldPayload` through each route's `mapInput` and calling `setCustomFieldsIfAny` in the command, the way `commands/team-members.ts` already does. |
620
+ | Reading values back on a list | **Does not work.** None of the five declares `list.decorateCustomFields`, so no `customFields` / `customValues` reaches a list item. |
621
+ | `CrudForm` rendering the inputs | Works for the project form only — it is the one real `CrudForm` and it passes `entityIds={[E.staff.staff_time_project]}`. Its `onSubmit` builds the body with `buildProjectPayload`, which copies named fields, so the rendered values are dropped before the request. `TimeEntryDialog`, `NewTaskDialog` and the report create page are hand-rolled hosts and render no custom fields at all. |
622
+ | Filtering/sorting by a custom field | Not reachable: each list route validates its query with a closed zod schema and builds filters itself, so a `cf:` filter never survives parsing. |
623
+
624
+ Treat EP-43 as the *declaration* half of custom-field support. Do not tell a customer the
625
+ fields are usable end to end until the write path lands.
626
+
627
+ ### Cross-module links (EP-44 — BC surface #2, STABLE)
628
+
629
+ [`data/extensions.ts`](./data/extensions.ts) declares four links over the foreign-key
630
+ columns time tracking already carried:
631
+
632
+ | Base | Extension | Join |
633
+ |---|---|---|
634
+ | `customers:customer_entity` | `staff:staff_time_entry` | `id` → `customer_id` |
635
+ | `customers:customer_deal` | `staff:staff_time_entry` | `id` → `deal_id` |
636
+ | `sales:sales_order` | `staff:staff_time_entry` | `id` → `order_id` |
637
+ | `customers:customer_entity` | `staff:staff_time_project` | `id` → `customer_id` |
638
+
639
+ `base` is the other module's entity, `extension` is the staff entity holding the FK —
640
+ the query engine only ever looks a link up by `base`. **These are strings, never
641
+ imports.** Staff is slated for extraction into a standalone package, so
642
+ `data/extensions.ts` may not import a `customers` or `sales` symbol and
643
+ `requires: ['planner', 'resources']` in [`index.ts`](./index.ts) must not grow;
644
+ `__tests__/entityExtensions.test.ts` fails if either happens, and pins every table and
645
+ column against the migration snapshots.
646
+
647
+ **Declaration-only, exactly as in `apps/mercato/src/modules/example/data/extensions.ts`.**
648
+ Nothing in the platform passes `includeExtensions` to a query, the basic engine's
649
+ extension join adds no projection and exposes no filterable or sortable alias, and the
650
+ hybrid engine DI registers ignores the flag outside its basic-engine fallback. What the
651
+ declaration *is* today is the traversal contract `yarn generate` emits as
652
+ `entityExtensions` in the module registry.
653
+
654
+ The matching catalog entry is not in [`extension-points.ts`](./extension-points.ts) on
655
+ purpose: `module-extension-facts.ts` already emits an `entity`-family fact host with the
656
+ `entity-extension` capability for every entity a module owns, so a hand-written
657
+ declaration would duplicate it and, having no source that references
658
+ `extensionPoints.hosts.<key>`, would be emitted as an `unbound-declaration` diagnostic
659
+ rather than a usable host.
660
+
661
+ ### Translatable fields (EP-45)
662
+
663
+ [`translations.ts`](./translations.ts) adds `staff_time_project` (`name`, `description`),
664
+ `staff_time_task_status` (`name`) and `staff_time_tag` (`label`). Values are **database
665
+ column** names. Only columns that exist are listed — task statuses have no description and
666
+ a tag's human-readable column is `label`.
667
+
668
+ Registration is via the generator: `translations-fields.generated.ts` imports every
669
+ module's `translations.ts` and calls `registerTranslatableFields`. The hard-coded list in
670
+ `translations/di.ts` covers four core modules and deliberately does not include staff — a
671
+ core module importing staff would violate MUST rule 1.
672
+
673
+ One rendered change: `translations/widgets/injection-table.ts` derives
674
+ `crud-form:<module>.<entity>:header` spots from the registry, so the project **edit** form
675
+ now shows the translation-manager affordance. It is gated on `translations.view` and on a
676
+ record id, so create forms and callers without the feature see nothing. No list or detail
677
+ output changes until a tenant actually writes a translation — the overlay substitutes a
678
+ value only when a row exists for the request locale.
679
+
680
+ ## Time-tracking search, analytics, notifications and AI
681
+
682
+ EP-46…EP-49 and EP-51. Four contribution surfaces plus the recalculation hook.
683
+
684
+ ### Search (EP-46) — and the one thing it cannot do
685
+
686
+ [`search.ts`](./search.ts) indexes all five time-tracking entities. **Read this
687
+ before adding a sixth.** An entity's `aclFeatures` is the *only* authorization the
688
+ search pipeline applies. It is enforced twice per query — `resolveReadableEntityTypes`
689
+ narrows the entity list before the query, `filterSearchResultsByEntityAccess`
690
+ filters the results after it — but both decide **per entity type**.
691
+ `SearchEntityConfig` has no row-level hook and `SearchOptions` has no record-id
692
+ allowlist, so a search gate cannot reproduce the per-project membership
693
+ intersection `resolveProjectAccess` applies on every time-tracking route.
694
+
695
+ So each gate names the feature set whose holder already sees **every** record of
696
+ that entity through the API:
697
+
698
+ | Entity | `aclFeatures` | Why |
699
+ |---|---|---|
700
+ | `staff:staff_time_project` | `staff.timesheets.projects.view` | Unchanged; the projects list is not membership-narrowed |
701
+ | `staff:staff_time_task` | `tasks.view` **+** `projects.manage` | `projects.manage` is what makes `resolveProjectAccess` answer `canManageAll` |
702
+ | `staff:staff_time_report` | `reports.view` **+** `projects.manage` | Same; the reports list intersects with project membership otherwise |
703
+ | `staff:staff_time_tag` | `staff.timesheets.view` | Tags are organization-global on their own route; exact match |
704
+ | `staff:staff_time_entry` | `staff.timesheets.view` **+** `projects.manage` | Entry notes are the most sensitive text the module holds |
705
+
706
+ A project member without `projects.manage` therefore gets **no** task, report or
707
+ entry hits — including for their own records. That is deliberate: the gate cannot
708
+ express "own", and half a gate would disclose other clients' work. Money columns
709
+ are `fieldPolicy.excluded`, not merely left out of `searchable`. The tag entity
710
+ resolves no URL — there is no tag detail screen and the entries list deep-links
711
+ only `taskId` and `ids`, so an unlinked hit is more honest than a wrong link.
712
+
713
+ ### Analytics (EP-47)
714
+
715
+ [`analytics.ts`](./analytics.ts) declares `staff:staff_time_entries`,
716
+ `staff:staff_time_tasks`, `staff:staff_time_projects` and `staff:staff_time_reports`.
717
+
718
+ - **No money column is mapped.** An `AnalyticsEntityConfig` carries one
719
+ `requiredFeatures` list for the whole entity, so there is no shape in which
720
+ `total_amount` or `hourly_rate` is gated on `staff.timesheets.rates.view` while
721
+ the rest stays readable. Minutes carry the same analysis without the disclosure.
722
+ - **Custom fields are not dimensions and cannot be made into dimensions.**
723
+ `AnalyticsFieldMapping` is `{ dbColumn, type }` and the aggregation builder emits
724
+ `dbColumn` as a bare identifier against the one `tableName`, so a mapping can only
725
+ ever name a real column on that table. The EP-43 custom fields are EAV rows in the
726
+ `entities` module's tables — no join, and the jsonb-path form needs the values on
727
+ the row itself. The point is moot anyway while the write path drops `cf_*`.
728
+ - `is_billable` / `billable_by_default` are typed `text` because
729
+ `AnalyticsFieldType` has no boolean; the type is read only to decide whether a
730
+ group-by column gets a `date_trunc`.
731
+
732
+ ### Notification types and reactive handlers (EP-48)
733
+
734
+ [`notifications.ts`](./notifications.ts) adds four ids;
735
+ [`notifications.handlers.ts`](./notifications.handlers.ts) is the module's browser
736
+ reaction to them, one handler per type with a stable override key
737
+ (`notifications.handlers.<id>`).
738
+
739
+ | Type | Raised by |
740
+ |---|---|
741
+ | `staff.timesheets.time_report.approved` | **Core.** [`subscribers/time-report-approved-notification.ts`](./subscribers/time-report-approved-notification.ts) on `time_report.closed` — closing IS the approval; the recipient is the report's author, never the closer |
742
+ | `staff.timesheets.time_report.ready_for_approval` | Contributable only — pairs with a `registerReportApprovalPolicy` (EP-41) that adds a review step. The module has one transition, draft → closed, so core has nothing to announce |
743
+ | `staff.timesheets.time_entry.timer_running_long` | Contributable only — needs a scheduled sweep over open timers, which the module does not ship |
744
+ | `staff.timesheets.timesheet.period_incomplete` | Contributable only — "incomplete" is the capacity provider's question (EP-40), and the built-in provider has no schedule and no opinion about when to complain |
745
+
746
+ A contributable-only type is not a dormant id: the renderer, the delivery
747
+ preferences and the browser handler are all in place, so a contributed raiser gets
748
+ a working notification on day one without patching core.
749
+
750
+ ### AI tool pack and agent (EP-49)
751
+
752
+ [`ai-tools.ts`](./ai-tools.ts) publishes six tools and
753
+ [`ai-agents.ts`](./ai-agents.ts) one agent, `staff.time_tracking_assistant`
754
+ (`mutationPolicy: 'confirm-required'`).
755
+
756
+ | Tool | Kind | Gate | Backing route |
757
+ |---|---|---|---|
758
+ | `staff.log_time` | mutation | `manage_own` | `POST /staff/timesheets/time-entries` |
759
+ | `staff.start_timer` | mutation | `manage_own` | `POST /staff/timesheets/time-entries/start-timer` |
760
+ | `staff.stop_timer` | mutation | `manage_own` | `POST /staff/timesheets/time-entries/{id}/timer-stop` |
761
+ | `staff.summarize_week` | read | `view` | `GET /staff/timesheets/time-entries` |
762
+ | `staff.find_missing_days` | read | `view` | same |
763
+ | `staff.draft_client_report` | read | `reports.view` | `POST /staff/timesheets/reports/preview` |
764
+
765
+ **A module tool never calls `prepareMutation`.** That function is framework code:
766
+ its only call site is `agent-tools.ts`, which intercepts the model's call,
767
+ short-circuits the handler, persists an `AiPendingAction` and returns the preview
768
+ card. A tool opts in by declaring `isMutation: true` plus a `loadBeforeRecord`
769
+ resolver that renders the diff; the handler runs later, once, from the confirm
770
+ route, with the stored input. Calling `prepareMutation` from a tool would be
771
+ calling the interceptor from inside the thing it intercepts.
772
+
773
+ Every tool is API-backed through `createAiApiOperationRunner`, which refuses a
774
+ route whose `requiredFeatures` the tool does not itself declare. The gate is
775
+ therefore checked twice, and the route still applies the project-access
776
+ intersection, the mutation guards, the interceptors and the commands. Nothing in
777
+ the pack writes a time entry itself. The tools act as the caller's own staff member
778
+ and none accepts a `staffMemberId`. `staff.draft_client_report` asks for a preview
779
+ with `showRates: false` and projects only minute columns, so an agent transcript
780
+ never becomes a second, ungated copy of a customer's rate card.
781
+
782
+ ### Recalculation hooks and the CLI (EP-51)
783
+
784
+ ```ts
785
+ registerTimeTrackingRecalculation({
786
+ id: 'billing.recalculate_rates',
787
+ labelKey: 'billing.recalculations.rates',
788
+ async run({ container, scope, report }) {
789
+ await report.setTotal(total)
790
+ // …batch, then `await report.advance(n)` and check `report.isCancellationRequested()`
791
+ return { totalCount, processedCount, updatedCount, unchangedCount, skippedCount, cancelled }
792
+ },
793
+ })
794
+ ```
795
+
796
+ - A hook does **not** own the `ProgressJob`. `runTimeTrackingRecalculations`
797
+ ([`lib/time-tracking/recalculationRunner.ts`](./lib/time-tracking/recalculationRunner.ts))
798
+ owns start / totals / cancel / complete, so several hooks share one progress bar;
799
+ marking a job failed is the worker's job. Hooks run **sequentially** in registry
800
+ order — they write the same rows.
801
+ - `run` must carry `scope.tenantId` into every query, honour `organizationIds` when
802
+ it is not `null`, and never touch an entry locked into a closed report.
803
+ - **A job with no `hookIds` runs the built-in rounding pass alone.** That is what
804
+ `POST /api/staff/timesheets/settings/reapply-rounding` enqueues, so a contributed
805
+ hook cannot attach itself to the retro-rounding button a tenant pressed. An
806
+ explicit list comes only from the CLI.
807
+
808
+ ```bash
809
+ yarn mercato staff timesheets:recalculate --list
810
+ yarn mercato staff timesheets:recalculate --tenant <id> [--org <id>] [--hook <id>[,<id>]]
811
+ ```
812
+
813
+ The CLI enqueues onto the same queue with the same `ProgressJob` the settings
814
+ screen uses; it does not do the work in process. An unknown `--hook` id refuses to
815
+ start rather than silently doing nothing.
816
+
49
817
  ## Internal-Only Surfaces (NOT public contract)
50
818
 
51
819
  These are subject to change without deprecation; do not import them from non-staff code: