@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/staff_time_entry/index.js +14 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
- package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +16 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_report/index.js +57 -0
- package/dist/generated/entities/staff_time_report/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
- package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_event/index.js +21 -0
- package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_project/index.js +15 -0
- package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_tag/index.js +21 -0
- package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task/index.js +37 -0
- package/dist/generated/entities/staff_time_task/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
- package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_status/index.js +29 -0
- package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +149 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +7 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
- package/dist/modules/auth/cli.js +16 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +1 -0
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +1 -28
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +2 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/start-controls.js +42 -0
- package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
- package/dist/modules/staff/acl.js +9 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/ai-agents.js +142 -0
- package/dist/modules/staff/ai-agents.js.map +7 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
- package/dist/modules/staff/ai-tools/types.js +54 -0
- package/dist/modules/staff/ai-tools/types.js.map +7 -0
- package/dist/modules/staff/ai-tools.js +8 -0
- package/dist/modules/staff/ai-tools.js.map +7 -0
- package/dist/modules/staff/analytics.js +84 -1
- package/dist/modules/staff/analytics.js.map +2 -2
- package/dist/modules/staff/api/guards.js +37 -3
- package/dist/modules/staff/api/guards.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
- package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
- package/dist/modules/staff/api/team-members/self/route.js +1 -3
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
- package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
- package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
- package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
- package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
- package/dist/modules/staff/ce.js +40 -0
- package/dist/modules/staff/ce.js.map +2 -2
- package/dist/modules/staff/cli.js +166 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +5 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +569 -21
- package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
- package/dist/modules/staff/commands/timesheets-projects.js +450 -7
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
- package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
- package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
- package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +426 -12
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +552 -3
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +41 -0
- package/dist/modules/staff/data/extensions.js.map +7 -0
- package/dist/modules/staff/data/validators.js +254 -9
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/di.js +42 -1
- package/dist/modules/staff/di.js.map +2 -2
- package/dist/modules/staff/events.js +84 -6
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +158 -0
- package/dist/modules/staff/events.payloads.js.map +7 -0
- package/dist/modules/staff/extension-points.js +261 -0
- package/dist/modules/staff/extension-points.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
- package/dist/modules/staff/lib/crud.js +11 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/access.js +92 -0
- package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
- package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
- package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
- package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
- package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
- package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
- package/dist/modules/staff/notifications.client.js +16 -0
- package/dist/modules/staff/notifications.client.js.map +7 -0
- package/dist/modules/staff/notifications.handlers.js +80 -0
- package/dist/modules/staff/notifications.handlers.js.map +7 -0
- package/dist/modules/staff/notifications.js +168 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/search.js +159 -1
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +20 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
- package/dist/modules/staff/translations.js +4 -1
- package/dist/modules/staff/translations.js.map +2 -2
- package/dist/modules/staff/widgets/components.js +7 -0
- package/dist/modules/staff/widgets/components.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
- package/dist/modules/staff/widgets/notifications/index.js +5 -0
- package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
- package/generated/entities/staff_time_entry/index.ts +7 -0
- package/generated/entities/staff_time_entry_tag/index.ts +6 -0
- package/generated/entities/staff_time_project/index.ts +8 -0
- package/generated/entities/staff_time_report/index.ts +27 -0
- package/generated/entities/staff_time_report_entry/index.ts +12 -0
- package/generated/entities/staff_time_report_event/index.ts +9 -0
- package/generated/entities/staff_time_report_project/index.ts +6 -0
- package/generated/entities/staff_time_tag/index.ts +9 -0
- package/generated/entities/staff_time_task/index.ts +17 -0
- package/generated/entities/staff_time_task_comment/index.ts +9 -0
- package/generated/entities/staff_time_task_status/index.ts +13 -0
- package/generated/entities/staff_time_task_tag/index.ts +6 -0
- package/generated/entities.ids.generated.ts +11 -1
- package/generated/entity-fields-registry.ts +149 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +31 -1
- package/src/modules/auth/README.md +1 -1
- package/src/modules/auth/cli.ts +35 -1
- package/src/modules/auth/i18n/de.json +7 -0
- package/src/modules/auth/i18n/en.json +7 -0
- package/src/modules/auth/i18n/es.json +7 -0
- package/src/modules/auth/i18n/ko.json +7 -0
- package/src/modules/auth/i18n/pl.json +7 -0
- package/src/modules/auth/lib/backendChrome.tsx +1 -0
- package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
- package/src/modules/customer_accounts/setup.ts +1 -45
- package/src/modules/data_sync/AGENTS.md +34 -0
- package/src/modules/data_sync/api/options.ts +2 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/ko.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/adapter.ts +45 -0
- package/src/modules/data_sync/lib/start-controls.ts +89 -0
- package/src/modules/staff/AGENTS.md +769 -1
- package/src/modules/staff/acl.ts +9 -0
- package/src/modules/staff/ai-agents.ts +189 -0
- package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
- package/src/modules/staff/ai-tools/types.ts +82 -0
- package/src/modules/staff/ai-tools.ts +13 -0
- package/src/modules/staff/analytics.ts +113 -0
- package/src/modules/staff/api/guards.ts +85 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
- package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
- package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
- package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
- package/src/modules/staff/api/team-members/self/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
- package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
- package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
- package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
- package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
- package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
- package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
- package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
- package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
- package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
- package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
- package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
- package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
- package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
- package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
- package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
- package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
- package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
- package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
- package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
- package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
- package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
- package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
- package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
- package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
- package/src/modules/staff/ce.ts +63 -0
- package/src/modules/staff/cli.ts +207 -1
- package/src/modules/staff/commands/index.ts +5 -0
- package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
- package/src/modules/staff/commands/timesheets-projects.ts +593 -7
- package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
- package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
- package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
- package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
- package/src/modules/staff/data/enrichers.ts +675 -10
- package/src/modules/staff/data/entities.ts +494 -2
- package/src/modules/staff/data/extensions.ts +68 -0
- package/src/modules/staff/data/validators.ts +348 -3
- package/src/modules/staff/di.ts +96 -0
- package/src/modules/staff/events.payloads.ts +268 -0
- package/src/modules/staff/events.ts +87 -5
- package/src/modules/staff/extension-points.ts +308 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
- package/src/modules/staff/i18n/de.json +899 -0
- package/src/modules/staff/i18n/en.json +899 -0
- package/src/modules/staff/i18n/es.json +899 -0
- package/src/modules/staff/i18n/ko.json +899 -0
- package/src/modules/staff/i18n/pl.json +899 -0
- package/src/modules/staff/lib/crud.ts +14 -3
- package/src/modules/staff/lib/time-tracking/access.ts +155 -0
- package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
- package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
- package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
- package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
- package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
- package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
- package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
- package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
- package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
- package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
- package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
- package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
- package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
- package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
- package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
- package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
- package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
- package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
- package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
- package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
- package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
- package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
- package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
- package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
- package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
- package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
- package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
- package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
- package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
- package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
- package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
- package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
- package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
- package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
- package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
- package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
- package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
- package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
- package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
- package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
- package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
- package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
- package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
- package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
- package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
- package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
- package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
- package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
- package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
- package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
- package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
- package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
- package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
- package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
- package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
- package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
- package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
- package/src/modules/staff/notifications.client.ts +16 -0
- package/src/modules/staff/notifications.handlers.ts +88 -0
- package/src/modules/staff/notifications.ts +163 -0
- package/src/modules/staff/search.ts +191 -1
- package/src/modules/staff/setup.ts +22 -0
- package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
- package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
- package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
- package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
- package/src/modules/staff/translations.ts +17 -0
- package/src/modules/staff/widgets/components.ts +34 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
- package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
- package/src/modules/staff/widgets/notifications/index.ts +1 -0
- package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
|
@@ -2,18 +2,62 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
2
2
|
import type { AwilixContainer } from 'awilix'
|
|
3
3
|
import type { ResponseEnricher, EnricherContext } from '@open-mercato/shared/lib/crud/response-enricher'
|
|
4
4
|
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
5
|
-
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
|
-
import {
|
|
5
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
|
+
import { CustomerEntity } from '@open-mercato/core/modules/customers/data/entities'
|
|
7
|
+
import {
|
|
8
|
+
StaffTeamMember,
|
|
9
|
+
StaffTimeProject,
|
|
10
|
+
StaffTimeReport,
|
|
11
|
+
StaffTimeReportEntry,
|
|
12
|
+
StaffTimeReportEvent,
|
|
13
|
+
StaffTimeTask,
|
|
14
|
+
StaffTimeTaskStatus,
|
|
15
|
+
} from './entities'
|
|
16
|
+
import { decorateTimeEntryRows } from '../lib/timesheets/timeEntryDecoration'
|
|
7
17
|
import { computeProjectHoursTrend } from '../lib/timesheets-projects/computeProjectHoursTrend'
|
|
18
|
+
import { computeProjectFinancials } from '../lib/timesheets-projects/computeProjectFinancials'
|
|
19
|
+
import type { ProjectBudgetKind } from '../lib/timesheets-projects/budgetBurn'
|
|
8
20
|
import {
|
|
9
21
|
listProjectMembersPreview,
|
|
10
22
|
type MemberPreview,
|
|
11
23
|
} from '../lib/timesheets-projects/listProjectMembersPreview'
|
|
24
|
+
import { computeTaskRollups, EMPTY_TASK_ROLLUP } from '../lib/timesheets-tasks/computeTaskRollups'
|
|
12
25
|
|
|
13
26
|
const MANAGE_FEATURE = 'staff.timesheets.projects.manage'
|
|
27
|
+
const RATES_FEATURE = 'staff.timesheets.rates.view'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The CRUD factory looks an enricher up by the `enrichers.entityId` its route
|
|
31
|
+
* declares — the colon form, `staff:staff_time_entry`. The query engine looks the
|
|
32
|
+
* same enricher up by the query entity id with `:` replaced by `.`
|
|
33
|
+
* (`entityIdToEventEntity`, `query-extension-runner.ts`), so an enricher that only
|
|
34
|
+
* declares the colon form never participates in a query-engine pipeline no matter
|
|
35
|
+
* what its `queryEngine` config says.
|
|
36
|
+
*
|
|
37
|
+
* `withQueryEngineSurface` publishes a second registry entry for the same enricher
|
|
38
|
+
* under the dot form, carrying the query-engine opt-in. The two entries never both
|
|
39
|
+
* match one lookup — the API surface asks for the colon form and the query-engine
|
|
40
|
+
* surface asks for the dot form — so nothing runs twice.
|
|
41
|
+
*/
|
|
42
|
+
function withQueryEngineSurface<TRecord, TEnriched>(
|
|
43
|
+
enricher: ResponseEnricher<TRecord, TEnriched>,
|
|
44
|
+
): ResponseEnricher<TRecord, TEnriched> {
|
|
45
|
+
return {
|
|
46
|
+
...enricher,
|
|
47
|
+
id: `${enricher.id}.query-engine`,
|
|
48
|
+
targetEntity: enricher.targetEntity.replace(/:/g, '.'),
|
|
49
|
+
queryEngine: { enabled: true },
|
|
50
|
+
}
|
|
51
|
+
}
|
|
14
52
|
|
|
15
53
|
type EntityRecord = Record<string, unknown> & { id: string }
|
|
16
54
|
|
|
55
|
+
type ProjectBudget = {
|
|
56
|
+
kind: ProjectBudgetKind
|
|
57
|
+
value: number | null
|
|
58
|
+
warnAtPercent: number | null
|
|
59
|
+
}
|
|
60
|
+
|
|
17
61
|
type StaffEnrichment = {
|
|
18
62
|
_staff: {
|
|
19
63
|
hoursWeek: number
|
|
@@ -21,6 +65,12 @@ type StaffEnrichment = {
|
|
|
21
65
|
myRole: string | null
|
|
22
66
|
members?: MemberPreview[]
|
|
23
67
|
memberCount?: number
|
|
68
|
+
customerName?: string | null
|
|
69
|
+
totalMinutes?: number
|
|
70
|
+
billableMinutes?: number
|
|
71
|
+
budget?: ProjectBudget
|
|
72
|
+
hourlyRate?: number | null
|
|
73
|
+
cost?: number | null
|
|
24
74
|
}
|
|
25
75
|
}
|
|
26
76
|
|
|
@@ -37,11 +87,10 @@ type InternalContext = EnricherContext & {
|
|
|
37
87
|
container: AwilixContainer
|
|
38
88
|
}
|
|
39
89
|
|
|
40
|
-
async function
|
|
41
|
-
if (ctx.userFeatures?.includes(MANAGE_FEATURE)) return true
|
|
90
|
+
async function callerHasFeature(ctx: InternalContext, feature: string): Promise<boolean> {
|
|
42
91
|
try {
|
|
43
92
|
const rbac = ctx.container.resolve('rbacService') as RbacService
|
|
44
|
-
return await rbac.userHasAllFeatures(ctx.userId, [
|
|
93
|
+
return await rbac.userHasAllFeatures(ctx.userId, [feature], {
|
|
45
94
|
tenantId: ctx.tenantId,
|
|
46
95
|
organizationId: ctx.organizationId,
|
|
47
96
|
})
|
|
@@ -50,6 +99,55 @@ async function callerHasManage(ctx: InternalContext): Promise<boolean> {
|
|
|
50
99
|
}
|
|
51
100
|
}
|
|
52
101
|
|
|
102
|
+
function toNullableNumber(value: string | number | null | undefined): number | null {
|
|
103
|
+
if (value === null || value === undefined) return null
|
|
104
|
+
const parsed = typeof value === 'number' ? value : Number(value)
|
|
105
|
+
return Number.isFinite(parsed) ? parsed : null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveCustomerName(snapshot: Record<string, unknown> | null | undefined): string | null {
|
|
109
|
+
if (!snapshot || typeof snapshot !== 'object') return null
|
|
110
|
+
for (const key of ['displayName', 'name', 'companyName', 'label', 'email']) {
|
|
111
|
+
const value = snapshot[key]
|
|
112
|
+
if (typeof value === 'string' && value.trim().length > 0) return value.trim()
|
|
113
|
+
}
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Second line of defence behind the write-side denormalization: a row written
|
|
119
|
+
* before the commands derived the snapshot — or by any path that bypassed them —
|
|
120
|
+
* still carries the FK, so the name is read live for exactly those projects,
|
|
121
|
+
* in one scoped query for the page rather than one per row. A customers module
|
|
122
|
+
* that is absent degrades to no name, never to a failed list.
|
|
123
|
+
*/
|
|
124
|
+
async function loadCustomerNames(
|
|
125
|
+
em: EntityManager,
|
|
126
|
+
tenantId: string,
|
|
127
|
+
organizationId: string,
|
|
128
|
+
customerIds: readonly string[],
|
|
129
|
+
): Promise<Map<string, string>> {
|
|
130
|
+
const names = new Map<string, string>()
|
|
131
|
+
const ids = [...new Set(customerIds)]
|
|
132
|
+
if (ids.length === 0) return names
|
|
133
|
+
try {
|
|
134
|
+
const customers = await findWithDecryption(
|
|
135
|
+
em,
|
|
136
|
+
CustomerEntity,
|
|
137
|
+
{ id: { $in: ids }, tenantId, organizationId, deletedAt: null },
|
|
138
|
+
undefined,
|
|
139
|
+
{ tenantId, organizationId },
|
|
140
|
+
)
|
|
141
|
+
for (const customer of customers) {
|
|
142
|
+
const name = customer.displayName?.trim() || customer.primaryEmail?.trim()
|
|
143
|
+
if (name) names.set(customer.id, name)
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
return names
|
|
147
|
+
}
|
|
148
|
+
return names
|
|
149
|
+
}
|
|
150
|
+
|
|
53
151
|
async function resolveCallerStaffMemberId(ctx: InternalContext): Promise<string | null> {
|
|
54
152
|
const member = await findOneWithDecryption(
|
|
55
153
|
ctx.em.fork(),
|
|
@@ -87,18 +185,36 @@ const portfolioEnricher: ResponseEnricher<EntityRecord, StaffEnrichment> = {
|
|
|
87
185
|
const ctx = context as InternalContext
|
|
88
186
|
const projectIds = records.map((r) => r.id)
|
|
89
187
|
|
|
90
|
-
const [callerStaffMemberId, hasManage] = await Promise.all([
|
|
188
|
+
const [callerStaffMemberId, hasManage, hasRates] = await Promise.all([
|
|
91
189
|
resolveCallerStaffMemberId(ctx),
|
|
92
|
-
|
|
190
|
+
callerHasFeature(ctx, MANAGE_FEATURE),
|
|
191
|
+
callerHasFeature(ctx, RATES_FEATURE),
|
|
93
192
|
])
|
|
193
|
+
const ownEntriesOnly = hasManage ? null : callerStaffMemberId
|
|
194
|
+
|
|
195
|
+
const projects = await ctx.em.fork().find(StaffTimeProject, {
|
|
196
|
+
id: { $in: projectIds },
|
|
197
|
+
tenantId: ctx.tenantId,
|
|
198
|
+
organizationId: ctx.organizationId,
|
|
199
|
+
deletedAt: null,
|
|
200
|
+
})
|
|
201
|
+
const projectById = new Map(projects.map((project) => [project.id, project]))
|
|
202
|
+
const hourlyRateByProjectId = new Map(
|
|
203
|
+
projects.map((project) => [project.id, toNullableNumber(project.hourlyRate)]),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const customerIdsMissingSnapshot = projects
|
|
207
|
+
.filter((project) => !resolveCustomerName(project.customerSnapshot))
|
|
208
|
+
.map((project) => project.customerId)
|
|
209
|
+
.filter((customerId): customerId is string => typeof customerId === 'string' && customerId.length > 0)
|
|
94
210
|
|
|
95
|
-
const [trendMap, membersMap] = await Promise.all([
|
|
211
|
+
const [trendMap, membersMap, financialsMap, customerNameById] = await Promise.all([
|
|
96
212
|
computeProjectHoursTrend({
|
|
97
213
|
em: ctx.em,
|
|
98
214
|
tenantId: ctx.tenantId,
|
|
99
215
|
organizationId: ctx.organizationId,
|
|
100
216
|
projectIds,
|
|
101
|
-
staffMemberId:
|
|
217
|
+
staffMemberId: ownEntriesOnly,
|
|
102
218
|
}),
|
|
103
219
|
listProjectMembersPreview({
|
|
104
220
|
em: ctx.em,
|
|
@@ -107,6 +223,15 @@ const portfolioEnricher: ResponseEnricher<EntityRecord, StaffEnrichment> = {
|
|
|
107
223
|
projectIds,
|
|
108
224
|
callerStaffMemberId,
|
|
109
225
|
}),
|
|
226
|
+
computeProjectFinancials({
|
|
227
|
+
em: ctx.em.fork(),
|
|
228
|
+
tenantId: ctx.tenantId,
|
|
229
|
+
organizationId: ctx.organizationId,
|
|
230
|
+
projectIds,
|
|
231
|
+
hourlyRateByProjectId,
|
|
232
|
+
staffMemberId: ownEntriesOnly,
|
|
233
|
+
}),
|
|
234
|
+
loadCustomerNames(ctx.em.fork(), ctx.tenantId, ctx.organizationId, customerIdsMissingSnapshot),
|
|
110
235
|
])
|
|
111
236
|
|
|
112
237
|
return records.map((record) => {
|
|
@@ -115,20 +240,560 @@ const portfolioEnricher: ResponseEnricher<EntityRecord, StaffEnrichment> = {
|
|
|
115
240
|
hoursTrend: [0, 0, 0, 0, 0, 0, 0],
|
|
116
241
|
}
|
|
117
242
|
const members = membersMap.get(record.id)
|
|
243
|
+
const financials = financialsMap.get(record.id)
|
|
244
|
+
const project = projectById.get(record.id)
|
|
118
245
|
const enrichment: StaffEnrichment['_staff'] = {
|
|
119
246
|
hoursWeek: trend.hoursWeek,
|
|
120
247
|
hoursTrend: trend.hoursTrend,
|
|
121
248
|
myRole: members?.myRole ?? null,
|
|
249
|
+
customerName:
|
|
250
|
+
resolveCustomerName(project?.customerSnapshot)
|
|
251
|
+
?? (project?.customerId ? customerNameById.get(project.customerId) ?? null : null),
|
|
252
|
+
totalMinutes: financials?.totalMinutes ?? 0,
|
|
253
|
+
billableMinutes: financials?.billableMinutes ?? 0,
|
|
254
|
+
budget: {
|
|
255
|
+
kind: project?.budgetKind ?? 'none',
|
|
256
|
+
value: toNullableNumber(project?.budgetValue),
|
|
257
|
+
warnAtPercent: project?.budgetWarnAtPercent ?? null,
|
|
258
|
+
},
|
|
122
259
|
}
|
|
123
260
|
if (hasManage && members) {
|
|
124
261
|
enrichment.members = members.preview
|
|
125
262
|
enrichment.memberCount = members.total
|
|
126
263
|
}
|
|
264
|
+
if (hasRates) {
|
|
265
|
+
enrichment.hourlyRate = toNullableNumber(project?.hourlyRate)
|
|
266
|
+
enrichment.cost = financials?.cost ?? null
|
|
267
|
+
}
|
|
127
268
|
return { ...record, _staff: enrichment }
|
|
128
269
|
})
|
|
129
270
|
},
|
|
130
271
|
}
|
|
131
272
|
|
|
132
|
-
|
|
273
|
+
/**
|
|
274
|
+
* `ownMinutes` is this task's own entries; `loggedMinutes` is the inclusive rollup
|
|
275
|
+
* (own + every child's), which is what every hours display shows (D-2).
|
|
276
|
+
*
|
|
277
|
+
* The two names are the guard against risk R10: a caller that sums parent and child
|
|
278
|
+
* `loggedMinutes` double-counts, because the parent's figure already contains the
|
|
279
|
+
* child's. `ownMinutes` is the field to sum when a total spans both levels.
|
|
280
|
+
*
|
|
281
|
+
* These land at the top level rather than under the usual `_staff` namespace.
|
|
282
|
+
* The namespace exists so one module's enrichment cannot collide with another's
|
|
283
|
+
* fields — here staff enriches its own entity, and the spec fixes these exact names as
|
|
284
|
+
* the task contract. Publishing the same number twice, under two paths, is precisely
|
|
285
|
+
* the ambiguity D-2 removes.
|
|
286
|
+
*/
|
|
287
|
+
type TaskRollupEnrichment = {
|
|
288
|
+
ownMinutes: number
|
|
289
|
+
loggedMinutes: number
|
|
290
|
+
childCount: number
|
|
291
|
+
/**
|
|
292
|
+
* How many of those children sit in a done column (D-2). Published beside
|
|
293
|
+
* `childCount` so `3/5` is readable from the row itself — deriving it costs a
|
|
294
|
+
* page of child rows per opened task, for a number the same aggregate knows.
|
|
295
|
+
*/
|
|
296
|
+
doneChildCount: number
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const taskRollupEnricher: ResponseEnricher<EntityRecord, TaskRollupEnrichment> = {
|
|
300
|
+
id: 'staff.timesheets-tasks-rollup',
|
|
301
|
+
targetEntity: 'staff:staff_time_task',
|
|
302
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
303
|
+
// which also intersects the page with the caller's project access; the aggregate
|
|
304
|
+
// narrows entries to the projects of that already-filtered page.
|
|
305
|
+
priority: 10,
|
|
306
|
+
timeout: 3000,
|
|
307
|
+
critical: false,
|
|
308
|
+
fallback: { ...EMPTY_TASK_ROLLUP },
|
|
309
|
+
// Minutes come from `staff_time_entries`, a table the task list cache never
|
|
310
|
+
// invalidates on, so the rollup must be recomputed on every hit.
|
|
311
|
+
cacheableOnListHit: false,
|
|
312
|
+
|
|
313
|
+
async enrichOne(record, context) {
|
|
314
|
+
const enriched = await this.enrichMany!([record], context)
|
|
315
|
+
return enriched[0]
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
async enrichMany(records, context) {
|
|
319
|
+
if (records.length === 0) return records as (EntityRecord & TaskRollupEnrichment)[]
|
|
320
|
+
|
|
321
|
+
const ctx = context as InternalContext
|
|
322
|
+
const rollups = await computeTaskRollups({
|
|
323
|
+
em: ctx.em.fork(),
|
|
324
|
+
tenantId: ctx.tenantId,
|
|
325
|
+
organizationId: ctx.organizationId,
|
|
326
|
+
taskIds: records.map((record) => record.id).filter((id): id is string => typeof id === 'string'),
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return records.map((record) => ({ ...record, ...(rollups.get(record.id) ?? EMPTY_TASK_ROLLUP) }))
|
|
330
|
+
},
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The tags a task carries, as ids and as renderable chips.
|
|
335
|
+
*
|
|
336
|
+
* `tagIds` is the contract the board and the drawer read: the board resolves the
|
|
337
|
+
* labels once per page through `/api/staff/timesheets/tags?ids=`, so it needs
|
|
338
|
+
* nothing but the ids, while a surface with no such lookup can render `tags`
|
|
339
|
+
* directly. Both come from the same row, so publishing the second costs nothing.
|
|
340
|
+
*
|
|
341
|
+
* Without this, a tag assigned to a task is invisible to every reader — the
|
|
342
|
+
* assignment route only writes — so the drawer could add a tag it could never
|
|
343
|
+
* show or remove.
|
|
344
|
+
*/
|
|
345
|
+
type TaskTagSummary = {
|
|
346
|
+
id: string
|
|
347
|
+
slug: string
|
|
348
|
+
label: string
|
|
349
|
+
color: string | null
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
type TaskTagEnrichment = {
|
|
353
|
+
tagIds: string[]
|
|
354
|
+
tags: TaskTagSummary[]
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const EMPTY_TASK_TAGS: TaskTagEnrichment = { tagIds: [], tags: [] }
|
|
358
|
+
|
|
359
|
+
type TaskTagRow = {
|
|
360
|
+
task_id: string
|
|
361
|
+
tag_id: string
|
|
362
|
+
slug: string | null
|
|
363
|
+
label: string | null
|
|
364
|
+
color: string | null
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* One join per page, never one per card: the assignment table is filtered by the
|
|
369
|
+
* page's already-access-filtered task ids, and the tag row travels with it rather
|
|
370
|
+
* than costing a second lookup.
|
|
371
|
+
*/
|
|
372
|
+
async function loadTaskTags(
|
|
373
|
+
em: EntityManager,
|
|
374
|
+
taskIds: readonly string[],
|
|
375
|
+
tenantId: string,
|
|
376
|
+
organizationId: string,
|
|
377
|
+
): Promise<Map<string, TaskTagEnrichment>> {
|
|
378
|
+
const byTaskId = new Map<string, TaskTagEnrichment>()
|
|
379
|
+
const ids = [...new Set(taskIds)].filter((id) => typeof id === 'string' && id.length > 0)
|
|
380
|
+
if (ids.length === 0) return byTaskId
|
|
381
|
+
|
|
382
|
+
const placeholders = ids.map(() => '?').join(', ')
|
|
383
|
+
const sql = `
|
|
384
|
+
SELECT
|
|
385
|
+
tt.task_id,
|
|
386
|
+
tt.tag_id,
|
|
387
|
+
tg.slug,
|
|
388
|
+
tg.label,
|
|
389
|
+
tg.color
|
|
390
|
+
FROM staff_time_task_tags tt
|
|
391
|
+
JOIN staff_time_tags tg
|
|
392
|
+
ON tg.id = tt.tag_id
|
|
393
|
+
AND tg.tenant_id = tt.tenant_id
|
|
394
|
+
AND tg.organization_id = tt.organization_id
|
|
395
|
+
AND tg.deleted_at IS NULL
|
|
396
|
+
WHERE tt.tenant_id = ?
|
|
397
|
+
AND tt.organization_id = ?
|
|
398
|
+
AND tt.task_id IN (${placeholders})
|
|
399
|
+
ORDER BY tg.label ASC, tg.id ASC
|
|
400
|
+
`
|
|
401
|
+
|
|
402
|
+
const rows = (await em.getConnection().execute(sql, [tenantId, organizationId, ...ids])) as TaskTagRow[]
|
|
403
|
+
for (const row of rows) {
|
|
404
|
+
if (typeof row.task_id !== 'string' || typeof row.tag_id !== 'string') continue
|
|
405
|
+
const bucket = byTaskId.get(row.task_id) ?? { tagIds: [], tags: [] }
|
|
406
|
+
if (bucket.tagIds.includes(row.tag_id)) continue
|
|
407
|
+
bucket.tagIds.push(row.tag_id)
|
|
408
|
+
bucket.tags.push({
|
|
409
|
+
id: row.tag_id,
|
|
410
|
+
slug: row.slug ?? '',
|
|
411
|
+
label: row.label ?? row.tag_id,
|
|
412
|
+
color: row.color ?? null,
|
|
413
|
+
})
|
|
414
|
+
byTaskId.set(row.task_id, bucket)
|
|
415
|
+
}
|
|
416
|
+
return byTaskId
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const taskTagEnricher: ResponseEnricher<EntityRecord, TaskTagEnrichment> = {
|
|
420
|
+
id: 'staff.timesheets-tasks-tags',
|
|
421
|
+
targetEntity: 'staff:staff_time_task',
|
|
422
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
423
|
+
// which also intersects the page with `resolveProjectAccess`; the join is keyed by
|
|
424
|
+
// that already-filtered page, so a tag can only be read through a task the caller
|
|
425
|
+
// may already see.
|
|
426
|
+
priority: 10,
|
|
427
|
+
timeout: 3000,
|
|
428
|
+
critical: false,
|
|
429
|
+
fallback: { ...EMPTY_TASK_TAGS },
|
|
430
|
+
// Assignments live in `staff_time_task_tags`, a table the task list cache never
|
|
431
|
+
// invalidates on, so the chips must be re-read on every hit.
|
|
432
|
+
cacheableOnListHit: false,
|
|
433
|
+
|
|
434
|
+
async enrichOne(record, context) {
|
|
435
|
+
const enriched = await this.enrichMany!([record], context)
|
|
436
|
+
return enriched[0]
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
async enrichMany(records, context) {
|
|
440
|
+
if (records.length === 0) return records as (EntityRecord & TaskTagEnrichment)[]
|
|
441
|
+
|
|
442
|
+
const ctx = context as InternalContext
|
|
443
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
444
|
+
return records.map((record) => ({ ...record, tagIds: [], tags: [] }))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const tagsByTaskId = await loadTaskTags(
|
|
448
|
+
ctx.em.fork(),
|
|
449
|
+
records.map((record) => record.id).filter((id): id is string => typeof id === 'string'),
|
|
450
|
+
ctx.tenantId,
|
|
451
|
+
ctx.organizationId,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return records.map((record) => {
|
|
455
|
+
const assigned = tagsByTaskId.get(record.id)
|
|
456
|
+
return { ...record, tagIds: assigned?.tagIds ?? [], tags: assigned?.tags ?? [] }
|
|
457
|
+
})
|
|
458
|
+
},
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The consulting-suite fields on a time-entry row: the `description` alias, the
|
|
463
|
+
* rounded minutes, the lock state, the assigned tags, and — only for a caller
|
|
464
|
+
* holding `staff.timesheets.rates.view` — `cost`, `currencyCode` and the stored
|
|
465
|
+
* rate override, which the route's projection deliberately does not select.
|
|
466
|
+
*
|
|
467
|
+
* This was a route-private `hooks.afterList` until EP-14. As a declared enricher a
|
|
468
|
+
* third-party enricher for the same entity composes with it and can read what it
|
|
469
|
+
* added, and the CRUD list cache stores the pre-enrichment rows instead of one
|
|
470
|
+
* caller's decorated copy.
|
|
471
|
+
*
|
|
472
|
+
* **`critical: false` with no `fallback`, and the money gate does not depend on
|
|
473
|
+
* either.** A non-critical enricher that throws or times out leaves the items as
|
|
474
|
+
* the route produced them, which is a page of entries without the decoration —
|
|
475
|
+
* degraded, but never a page of entries WITH money the caller may not see, because
|
|
476
|
+
* every money key on the row is one this enricher put there. A `fallback` is
|
|
477
|
+
* deliberately absent: it merges one fixed object into every row, and there is no
|
|
478
|
+
* value of `description`, `roundedMinutes` or `tags` that is correct for all of
|
|
479
|
+
* them. Omitting the decoration is honest; inventing it is not.
|
|
480
|
+
*/
|
|
481
|
+
type TimeEntryEnrichment = Record<string, unknown>
|
|
482
|
+
|
|
483
|
+
const timeEntryEnricher: ResponseEnricher<EntityRecord, TimeEntryEnrichment> = {
|
|
484
|
+
id: 'staff.timesheets-time-entries',
|
|
485
|
+
targetEntity: 'staff:staff_time_entry',
|
|
486
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.view']`),
|
|
487
|
+
// which also intersects the page with `resolveProjectAccess`. The money fields
|
|
488
|
+
// below are gated per caller on `staff.timesheets.rates.view`.
|
|
489
|
+
priority: 10,
|
|
490
|
+
timeout: 3000,
|
|
491
|
+
critical: false,
|
|
492
|
+
// Tags live in `staff_time_entry_tags` and rates in `staff_time_projects` —
|
|
493
|
+
// neither invalidates the entry list cache, and the money keys are per-caller,
|
|
494
|
+
// so the decoration must be recomputed on every hit.
|
|
495
|
+
cacheableOnListHit: false,
|
|
496
|
+
|
|
497
|
+
async enrichOne(record, context) {
|
|
498
|
+
const enriched = await this.enrichMany!([record], context)
|
|
499
|
+
return enriched[0]
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
async enrichMany(records, context) {
|
|
503
|
+
if (records.length === 0) return records as (EntityRecord & TimeEntryEnrichment)[]
|
|
504
|
+
|
|
505
|
+
const ctx = context as InternalContext
|
|
506
|
+
// Copies, so the enricher stays additive with respect to its input while the
|
|
507
|
+
// shared decoration keeps writing in place.
|
|
508
|
+
const rows = records.map((record) => ({ ...record }))
|
|
509
|
+
await decorateTimeEntryRows(rows, {
|
|
510
|
+
em: ctx.em,
|
|
511
|
+
tenantId: ctx.tenantId,
|
|
512
|
+
organizationId: ctx.organizationId,
|
|
513
|
+
canSeeRates: await callerHasFeature(ctx, RATES_FEATURE),
|
|
514
|
+
})
|
|
515
|
+
return rows as (EntityRecord & TimeEntryEnrichment)[]
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* The context a task row needs to render outside its own board: the project it
|
|
521
|
+
* belongs to, the column it sits in and who it is assigned to. Without it every
|
|
522
|
+
* task table pays one lookup per row for three names the same page already knows.
|
|
523
|
+
*
|
|
524
|
+
* `hourlyRate` / `currencyCode` are the PROJECT's rate, which is money, so they
|
|
525
|
+
* exist only for a caller holding `staff.timesheets.rates.view`.
|
|
526
|
+
*/
|
|
527
|
+
type TaskContextEnrichment = {
|
|
528
|
+
_staff: {
|
|
529
|
+
projectName: string | null
|
|
530
|
+
projectCode: string | null
|
|
531
|
+
projectColor: string | null
|
|
532
|
+
statusName: string | null
|
|
533
|
+
statusIsDone: boolean | null
|
|
534
|
+
assigneeName: string | null
|
|
535
|
+
hourlyRate?: number | null
|
|
536
|
+
currencyCode?: string | null
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const EMPTY_TASK_CONTEXT: TaskContextEnrichment = {
|
|
541
|
+
_staff: {
|
|
542
|
+
projectName: null,
|
|
543
|
+
projectCode: null,
|
|
544
|
+
projectColor: null,
|
|
545
|
+
statusName: null,
|
|
546
|
+
statusIsDone: null,
|
|
547
|
+
assigneeName: null,
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const taskContextEnricher: ResponseEnricher<EntityRecord, TaskContextEnrichment> = {
|
|
552
|
+
id: 'staff.timesheets-tasks-context',
|
|
553
|
+
targetEntity: 'staff:staff_time_task',
|
|
554
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
555
|
+
// which intersects the page with `resolveProjectAccess`; every lookup below is keyed
|
|
556
|
+
// by that already-filtered page and re-scoped to the caller tenant and organization.
|
|
557
|
+
priority: 5,
|
|
558
|
+
timeout: 3000,
|
|
559
|
+
critical: false,
|
|
560
|
+
fallback: { ...EMPTY_TASK_CONTEXT },
|
|
561
|
+
cacheableOnListHit: false,
|
|
562
|
+
|
|
563
|
+
async enrichOne(record, context) {
|
|
564
|
+
const enriched = await this.enrichMany!([record], context)
|
|
565
|
+
return enriched[0]
|
|
566
|
+
},
|
|
567
|
+
|
|
568
|
+
async enrichMany(records, context) {
|
|
569
|
+
if (records.length === 0) return records as (EntityRecord & TaskContextEnrichment)[]
|
|
570
|
+
|
|
571
|
+
const ctx = context as InternalContext
|
|
572
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
573
|
+
return records.map((record) => ({ ...record, ...EMPTY_TASK_CONTEXT }))
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
577
|
+
const taskIds = records.map((record) => record.id).filter((id): id is string => typeof id === 'string')
|
|
578
|
+
const tasks = await ctx.em.fork().find(StaffTimeTask, { id: { $in: taskIds }, ...scope, deletedAt: null })
|
|
579
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]))
|
|
580
|
+
|
|
581
|
+
const projectIds = [...new Set(tasks.map((task) => task.timeProjectId).filter(Boolean))]
|
|
582
|
+
const statusIds = [...new Set(tasks.map((task) => task.taskStatusId).filter(Boolean))]
|
|
583
|
+
const assigneeIds = [
|
|
584
|
+
...new Set(
|
|
585
|
+
tasks
|
|
586
|
+
.map((task) => task.assigneeStaffMemberId)
|
|
587
|
+
.filter((id): id is string => typeof id === 'string' && id.length > 0),
|
|
588
|
+
),
|
|
589
|
+
]
|
|
590
|
+
|
|
591
|
+
const hasRates = await callerHasFeature(ctx, RATES_FEATURE)
|
|
592
|
+
|
|
593
|
+
const [projects, statuses, assignees] = await Promise.all([
|
|
594
|
+
projectIds.length
|
|
595
|
+
? ctx.em.fork().find(StaffTimeProject, { id: { $in: projectIds }, ...scope, deletedAt: null })
|
|
596
|
+
: Promise.resolve([]),
|
|
597
|
+
statusIds.length
|
|
598
|
+
? ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope, deletedAt: null })
|
|
599
|
+
: Promise.resolve([]),
|
|
600
|
+
assigneeIds.length
|
|
601
|
+
? findWithDecryption(
|
|
602
|
+
ctx.em.fork(),
|
|
603
|
+
StaffTeamMember,
|
|
604
|
+
{ id: { $in: assigneeIds }, ...scope, deletedAt: null },
|
|
605
|
+
undefined,
|
|
606
|
+
scope,
|
|
607
|
+
)
|
|
608
|
+
: Promise.resolve([]),
|
|
609
|
+
])
|
|
610
|
+
|
|
611
|
+
const projectById = new Map(projects.map((project) => [project.id, project]))
|
|
612
|
+
const statusById = new Map(statuses.map((status) => [status.id, status]))
|
|
613
|
+
const assigneeById = new Map(assignees.map((member) => [member.id, member]))
|
|
614
|
+
|
|
615
|
+
return records.map((record) => {
|
|
616
|
+
const task = taskById.get(record.id)
|
|
617
|
+
if (!task) return { ...record, ...EMPTY_TASK_CONTEXT }
|
|
618
|
+
const project = projectById.get(task.timeProjectId)
|
|
619
|
+
const status = statusById.get(task.taskStatusId)
|
|
620
|
+
const assignee = task.assigneeStaffMemberId ? assigneeById.get(task.assigneeStaffMemberId) : undefined
|
|
621
|
+
const enrichment: TaskContextEnrichment['_staff'] = {
|
|
622
|
+
projectName: project?.name ?? null,
|
|
623
|
+
projectCode: project?.code ?? null,
|
|
624
|
+
projectColor: project?.color ?? null,
|
|
625
|
+
statusName: status?.name ?? null,
|
|
626
|
+
statusIsDone: status ? status.isDone : null,
|
|
627
|
+
assigneeName: assignee?.displayName ?? null,
|
|
628
|
+
}
|
|
629
|
+
if (hasRates) {
|
|
630
|
+
enrichment.hourlyRate = toNullableNumber(project?.hourlyRate)
|
|
631
|
+
enrichment.currencyCode = project?.currencyCode ?? null
|
|
632
|
+
}
|
|
633
|
+
return { ...record, _staff: enrichment }
|
|
634
|
+
})
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* What a report row cannot say about itself: whether it is frozen, how many entries
|
|
640
|
+
* the freeze holds, and whether anyone has taken a file out of it.
|
|
641
|
+
*
|
|
642
|
+
* `lockedEntryCount` counts the frozen `staff_time_report_entries` rows, which is the
|
|
643
|
+
* number a close actually wrote — the report's own columns carry only minute and
|
|
644
|
+
* amount totals. The export history comes from the append-only
|
|
645
|
+
* `staff_time_report_events` audit (screen 14 note 5: an export never locks), so
|
|
646
|
+
* "last exported" is answerable without opening the report.
|
|
647
|
+
*
|
|
648
|
+
* `totalAmount` is money and exists only for a caller holding
|
|
649
|
+
* `staff.timesheets.rates.view`; the minute totals are not money and stay for everyone.
|
|
650
|
+
*/
|
|
651
|
+
type ReportEnrichment = {
|
|
652
|
+
_staff: {
|
|
653
|
+
isClosed: boolean
|
|
654
|
+
closedAt: string | null
|
|
655
|
+
billableMinutes: number
|
|
656
|
+
nonbillableMinutes: number
|
|
657
|
+
totalMinutes: number
|
|
658
|
+
lockedEntryCount: number
|
|
659
|
+
exportCount: number
|
|
660
|
+
lastExportedAt: string | null
|
|
661
|
+
lastExportFormat: string | null
|
|
662
|
+
totalAmount?: number | null
|
|
663
|
+
currencyCode?: string | null
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const EMPTY_REPORT_ENRICHMENT: ReportEnrichment = {
|
|
668
|
+
_staff: {
|
|
669
|
+
isClosed: false,
|
|
670
|
+
closedAt: null,
|
|
671
|
+
billableMinutes: 0,
|
|
672
|
+
nonbillableMinutes: 0,
|
|
673
|
+
totalMinutes: 0,
|
|
674
|
+
lockedEntryCount: 0,
|
|
675
|
+
exportCount: 0,
|
|
676
|
+
lastExportedAt: null,
|
|
677
|
+
lastExportFormat: null,
|
|
678
|
+
},
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
type ReportExportSummary = { count: number; lastAt: Date | null; lastFormat: string | null }
|
|
682
|
+
|
|
683
|
+
function readExportFormat(metadata: Record<string, unknown> | null | undefined): string | null {
|
|
684
|
+
if (!metadata || typeof metadata !== 'object') return null
|
|
685
|
+
const format = metadata.format
|
|
686
|
+
return typeof format === 'string' && format.length > 0 ? format : null
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const reportEnricher: ResponseEnricher<EntityRecord, ReportEnrichment> = {
|
|
690
|
+
id: 'staff.timesheets-reports',
|
|
691
|
+
targetEntity: 'staff:staff_time_report',
|
|
692
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.reports.view']`);
|
|
693
|
+
// `totalAmount` is gated per caller on `staff.timesheets.rates.view` below.
|
|
694
|
+
priority: 10,
|
|
695
|
+
timeout: 3000,
|
|
696
|
+
critical: false,
|
|
697
|
+
fallback: { ...EMPTY_REPORT_ENRICHMENT },
|
|
698
|
+
// The freeze and export counts live in tables the report list cache never
|
|
699
|
+
// invalidates on, so they are re-read on every hit.
|
|
700
|
+
cacheableOnListHit: false,
|
|
701
|
+
|
|
702
|
+
async enrichOne(record, context) {
|
|
703
|
+
const enriched = await this.enrichMany!([record], context)
|
|
704
|
+
return enriched[0]
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
async enrichMany(records, context) {
|
|
708
|
+
if (records.length === 0) return records as (EntityRecord & ReportEnrichment)[]
|
|
709
|
+
|
|
710
|
+
const ctx = context as InternalContext
|
|
711
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
712
|
+
return records.map((record) => ({ ...record, ...EMPTY_REPORT_ENRICHMENT }))
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
|
|
716
|
+
const reportIds = records.map((record) => record.id).filter((id): id is string => typeof id === 'string')
|
|
717
|
+
if (reportIds.length === 0) {
|
|
718
|
+
return records.map((record) => ({ ...record, ...EMPTY_REPORT_ENRICHMENT }))
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const [reports, frozenEntries, exportEvents, hasRates] = await Promise.all([
|
|
722
|
+
ctx.em.fork().find(StaffTimeReport, { id: { $in: reportIds }, ...scope, deletedAt: null }),
|
|
723
|
+
ctx.em.fork().find(
|
|
724
|
+
StaffTimeReportEntry,
|
|
725
|
+
{ reportId: { $in: reportIds }, ...scope },
|
|
726
|
+
{ fields: ['reportId'] },
|
|
727
|
+
),
|
|
728
|
+
ctx.em.fork().find(
|
|
729
|
+
StaffTimeReportEvent,
|
|
730
|
+
{ reportId: { $in: reportIds }, eventType: 'exported', ...scope },
|
|
731
|
+
{ orderBy: { createdAt: 'desc' } },
|
|
732
|
+
),
|
|
733
|
+
callerHasFeature(ctx, RATES_FEATURE),
|
|
734
|
+
])
|
|
735
|
+
|
|
736
|
+
const reportById = new Map(reports.map((report) => [report.id, report]))
|
|
737
|
+
const frozenCountByReportId = new Map<string, number>()
|
|
738
|
+
for (const entry of frozenEntries) {
|
|
739
|
+
frozenCountByReportId.set(entry.reportId, (frozenCountByReportId.get(entry.reportId) ?? 0) + 1)
|
|
740
|
+
}
|
|
741
|
+
const exportsByReportId = new Map<string, ReportExportSummary>()
|
|
742
|
+
for (const event of exportEvents) {
|
|
743
|
+
const summary = exportsByReportId.get(event.reportId) ?? { count: 0, lastAt: null, lastFormat: null }
|
|
744
|
+
summary.count += 1
|
|
745
|
+
// The rows arrive newest first, so the first one seen for a report is the last export.
|
|
746
|
+
if (summary.lastAt === null) {
|
|
747
|
+
summary.lastAt = event.createdAt ?? null
|
|
748
|
+
summary.lastFormat = readExportFormat(event.metadata)
|
|
749
|
+
}
|
|
750
|
+
exportsByReportId.set(event.reportId, summary)
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return records.map((record) => {
|
|
754
|
+
const report = reportById.get(record.id)
|
|
755
|
+
if (!report) return { ...record, ...EMPTY_REPORT_ENRICHMENT }
|
|
756
|
+
const exports = exportsByReportId.get(report.id)
|
|
757
|
+
const billableMinutes = report.totalBillableMinutes ?? 0
|
|
758
|
+
const nonbillableMinutes = report.totalNonbillableMinutes ?? 0
|
|
759
|
+
const enrichment: ReportEnrichment['_staff'] = {
|
|
760
|
+
isClosed: report.status === 'closed',
|
|
761
|
+
closedAt: report.closedAt ? report.closedAt.toISOString() : null,
|
|
762
|
+
billableMinutes,
|
|
763
|
+
nonbillableMinutes,
|
|
764
|
+
totalMinutes: billableMinutes + nonbillableMinutes,
|
|
765
|
+
lockedEntryCount: frozenCountByReportId.get(report.id) ?? 0,
|
|
766
|
+
exportCount: exports?.count ?? 0,
|
|
767
|
+
lastExportedAt: exports?.lastAt ? exports.lastAt.toISOString() : null,
|
|
768
|
+
lastExportFormat: exports?.lastFormat ?? null,
|
|
769
|
+
}
|
|
770
|
+
if (hasRates) {
|
|
771
|
+
enrichment.totalAmount = toNullableNumber(report.totalAmount)
|
|
772
|
+
enrichment.currencyCode = report.currencyCode ?? null
|
|
773
|
+
}
|
|
774
|
+
return { ...record, _staff: enrichment }
|
|
775
|
+
})
|
|
776
|
+
},
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* The API-response surface. Every one of these also participates in query-engine
|
|
781
|
+
* pipelines through the dot-form aliases below, so a dashboard, an export or an AI
|
|
782
|
+
* tool that passes `QueryOptions.extensions` sees the same enrichment the REST
|
|
783
|
+
* response carries.
|
|
784
|
+
*/
|
|
785
|
+
const apiEnrichers: ResponseEnricher[] = [
|
|
786
|
+
portfolioEnricher,
|
|
787
|
+
taskRollupEnricher,
|
|
788
|
+
taskTagEnricher,
|
|
789
|
+
taskContextEnricher,
|
|
790
|
+
timeEntryEnricher,
|
|
791
|
+
reportEnricher,
|
|
792
|
+
]
|
|
793
|
+
|
|
794
|
+
export const enrichers: ResponseEnricher[] = [
|
|
795
|
+
...apiEnrichers,
|
|
796
|
+
...apiEnrichers.map((enricher) => withQueryEngineSurface(enricher)),
|
|
797
|
+
]
|
|
133
798
|
|
|
134
799
|
export default enrichers
|