@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
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/staff/commands/timesheets-tasks.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Task commands for the Kanban board of screen 6 and the drawer of screen 7 (D-2).\n *\n * A subtask is not a second entity \u2014 it is a task with `parent_task_id` set, carrying\n * its own status, assignee and time entries. Everything that follows from that is\n * enforced here rather than trusted from the request:\n *\n * 1. **Depth is capped at one.** Creating or re-parenting under a task that itself has\n * a parent is refused with `400 subtask_depth_exceeded`, and a task that already\n * holds children may not become one. The board therefore renders exactly the\n * top-level tasks and the drawer exactly one level below them.\n * 2. **Soft-deleting a parent soft-deletes its children in the same transaction.**\n * Time entries are deliberately left alone: they keep pointing at the deleted task\n * so a closed report still resolves its line labels.\n * 3. **A task lives in exactly one project (\u00A72).** Moving one between projects would\n * invalidate its reference, its column and every report line quoting it, so the\n * request is refused instead of silently ignored.\n * 4. **Done-ness has one source of truth** \u2014 the status column's `is_done`. There is\n * no `is_done` on the task; landing on a terminal column stamps `closed_at` and\n * leaving one clears it.\n *\n * `title` is the only field a caller must supply beyond scoping and the project\n * (US-C1): the status defaults to the project's `is_default` column and the assignee\n * to the creator, both overridable.\n *\n * The rollup (`ownMinutes` / `loggedMinutes` / `childCount`) is NOT computed here \u2014 it\n * is a read-side concern owned by `lib/time-tracking/rollup.ts`.\n */\n\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'\nimport {\n staffTimeTaskCreateSchema,\n staffTimeTaskStatusChangeSchema,\n staffTimeTaskUpdateSchema,\n type StaffTimeTaskCreateInput,\n type StaffTimeTaskUpdateInput,\n} from '../data/validators'\nimport { emitStaffEvent } from '../events'\nimport { nextStatusPosition } from '../lib/timesheets-tasks/statusPositions'\nimport {\n allocateTaskReference,\n isTaskReferenceConflict,\n withTaskReferenceRetry,\n} from '../lib/timesheets-tasks/taskReference'\nimport {\n SUBTASK_DEPTH_EXCEEDED_CODE,\n planTaskSoftDelete,\n rejectTaskParent,\n type TaskParentRejection,\n} from '../lib/timesheets-tasks/taskHierarchy'\nimport {\n applyScopeToWhere,\n commandActorScope,\n commandInputScope,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n scopeForDecryption,\n scopedStaffSnapshotWhere,\n staffSnapshotScopeFromContext,\n staffSnapshotScopeFromSnapshot,\n type StaffCommandScope,\n type StaffSnapshotScope,\n} from './shared'\n\nexport const staffTimeTaskCommandIds = {\n create: 'staff.timesheets.tasks.create',\n update: 'staff.timesheets.tasks.update',\n delete: 'staff.timesheets.tasks.delete',\n statusChange: 'staff.timesheets.tasks.status_change',\n} as const\n\n/**\n * Resource kind the board's drag endpoint presents to the mutation-guard registry\n * and to the command-level optimistic lock. It matches the entity naming the other\n * hand-written timesheets routes use (`staff.timesheets.time_entry`,\n * `staff.timesheets.time_project`) so an `OM_OPTIMISTIC_LOCK` allow-list can name\n * it. The audit `resourceKind` stays `staff.timesheets.task`, which is what the\n * other task commands already write.\n */\nexport const STAFF_TIME_TASK_RESOURCE_KIND = 'staff.timesheets.time_task'\n\nexport const TASK_DEPTH_EXCEEDED_CODE = SUBTASK_DEPTH_EXCEEDED_CODE\nexport const TASK_PARENT_SELF_CODE = 'task_parent_self'\nexport const TASK_PARENT_PROJECT_MISMATCH_CODE = 'task_parent_project_mismatch'\nexport const TASK_PROJECT_MOVE_UNSUPPORTED_CODE = 'task_project_move_unsupported'\nexport const TASK_STATUS_MISSING_CODE = 'task_status_missing'\nexport const TASK_REFERENCE_CONFLICT_CODE = 'task_reference_conflict'\n\nconst logger = createLogger('staff').child({ component: 'commands/timesheets-tasks' })\n\nconst taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {\n entityType: 'staff:staff_time_task',\n}\n\n/**\n * `staff.timesheets.time_task.{created,updated,deleted}` from `events.ts`. The\n * lifecycle `\u2026status_changed` event belongs to the dedicated status endpoint that\n * owns the drag-and-drop move, not to these generic writes.\n */\nconst taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {\n module: 'staff',\n entity: 'timesheets.time_task',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\n/**\n * The resource tag the tasks CRUD route caches its list pages under \u2014\n * `makeCrudRoute` derives it from `events.module` + `events.entity`, so it is read\n * from the same config here rather than restated.\n *\n * Commands executed straight from a route (the board's move endpoint) are\n * invalidated by the command bus from the audit `resourceKind`, which for tasks is\n * `staff.timesheets.task` \u2014 a different tag. Without this alias a drag left every\n * cached page of the tasks list in place, so the board's refetch after a successful\n * move was answered with the pre-move page: the card snapped back with no error,\n * and the stale `updated_at` that page carried made the next move answer 409\n * \"changed by someone else\" until the entry expired. `ENABLE_CRUD_API_CACHE` off\n * hides it; the ephemeral QA and integration environments run with it on.\n */\nconst TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`\n\ntype Translate = (key: string, fallback: string) => string\n\ntype TaskSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n timeProjectId: string\n parentTaskId: string | null\n taskStatusId: string\n sequenceNumber: number\n reference: string\n title: string\n description: string | null\n assigneeStaffMemberId: string | null\n position: number\n createdByUserId: string | null\n closedAt: string | null\n deletedAt: string | null\n}\n\ntype TaskUndoPayload = {\n before?: TaskSnapshot | null\n after?: TaskSnapshot | null\n /** Children the delete took with it, so undo can put the whole set back. */\n cascadedChildren?: TaskSnapshot[] | null\n}\n\nconst DIFFED_FIELDS = [\n 'parentTaskId',\n 'taskStatusId',\n 'title',\n 'description',\n 'assigneeStaffMemberId',\n 'position',\n 'closedAt',\n] as const\n\nfunction toSnapshot(task: StaffTimeTask): TaskSnapshot {\n return {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n timeProjectId: task.timeProjectId,\n parentTaskId: task.parentTaskId ?? null,\n taskStatusId: task.taskStatusId,\n sequenceNumber: task.sequenceNumber,\n reference: task.reference,\n title: task.title,\n description: task.description ?? null,\n assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,\n position: task.position,\n createdByUserId: task.createdByUserId ?? null,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n deletedAt: task.deletedAt ? task.deletedAt.toISOString() : null,\n }\n}\n\nfunction snapshotScopeWhere(scope?: StaffSnapshotScope | null): { tenantId?: string; organizationId?: string } {\n const where: { tenantId?: string; organizationId?: string } = {}\n if (scope?.tenantId) where.tenantId = scope.tenantId\n if (scope?.organizationId) where.organizationId = scope.organizationId\n return where\n}\n\nasync function loadTaskSnapshot(\n em: EntityManager,\n id: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot | null> {\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(id, scope))\n return task ? toSnapshot(task) : null\n}\n\nasync function loadChildSnapshots(\n em: EntityManager,\n parentTaskId: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot[]> {\n const children = await em.find(StaffTimeTask, {\n parentTaskId,\n deletedAt: null,\n ...snapshotScopeWhere(scope),\n })\n return children.map(toSnapshot)\n}\n\nfunction notFoundError(translate: Translate): CrudHttpError {\n return new CrudHttpError(404, {\n error: translate('staff.time_tracking.tasks.errors.notFound', 'Task not found or not accessible.'),\n })\n}\n\nfunction projectNotFoundError(translate: Translate): CrudHttpError {\n const message = translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.')\n return new CrudHttpError(422, { error: message, fieldErrors: { timeProjectId: message } })\n}\n\nfunction statusNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.statusNotFound',\n 'That column does not belong to this project.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { taskStatusId: message } })\n}\n\nfunction statusMissingError(translate: Translate): CrudHttpError {\n return new CrudHttpError(422, {\n code: TASK_STATUS_MISSING_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.statusMissing',\n 'This project has no columns yet, so a task cannot be placed on its board.',\n ),\n })\n}\n\nfunction parentNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentNotFound',\n 'Parent task not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { parentTaskId: message } })\n}\n\nfunction assigneeNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.timesheets.errors.staffMemberNotFound',\n 'Staff member not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { assigneeStaffMemberId: message } })\n}\n\n/**\n * The one refusal the drawer has to be able to explain: nesting is one level deep, so\n * a subtask can neither gain a subtask nor be handed to another subtask.\n */\nfunction parentRejectionError(translate: Translate, rejection: TaskParentRejection): CrudHttpError {\n if (rejection === 'self_parent') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentSelf',\n 'A task cannot be its own subtask.',\n )\n return new CrudHttpError(400, {\n code: TASK_PARENT_SELF_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n if (rejection === 'parent_project_mismatch') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentProjectMismatch',\n 'A subtask must stay in the same project as its parent.',\n )\n return new CrudHttpError(422, {\n code: TASK_PARENT_PROJECT_MISMATCH_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n const message = translate(\n 'staff.time_tracking.tasks.errors.subtaskDepthExceeded',\n 'Subtasks are one level deep. A subtask cannot have subtasks of its own.',\n )\n return new CrudHttpError(400, {\n code: TASK_DEPTH_EXCEEDED_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n}\n\nfunction referenceConflictError(translate: Translate): CrudHttpError {\n return new CrudHttpError(409, {\n code: TASK_REFERENCE_CONFLICT_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.referenceConflict',\n 'Could not allocate a task number right now. Try again.',\n ),\n })\n}\n\nasync function requireProject(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeProject> {\n const project = await em.findOne(\n StaffTimeProject,\n applyScopeToWhere<StaffTimeProject>({ id: timeProjectId, deletedAt: null }, scope),\n )\n if (!project) throw projectNotFoundError(translate)\n return project\n}\n\n/**\n * The column a task lands on: the requested one when it belongs to this project, and\n * otherwise the project's `is_default` entry point. The board invariants guarantee one\n * exists; the positional fallback only covers a board repaired by hand.\n */\nasync function resolveTaskStatus(\n em: EntityManager,\n timeProjectId: string,\n requestedStatusId: string | null | undefined,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTaskStatus> {\n if (requestedStatusId) {\n const status = await em.findOne(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ id: requestedStatusId, timeProjectId, deletedAt: null }, scope),\n )\n if (!status) throw statusNotFoundError(translate)\n return status\n }\n const board = await em.find(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ timeProjectId, deletedAt: null }, scope),\n )\n if (board.length === 0) throw statusMissingError(translate)\n const ordered = [...board].sort((left, right) => left.position - right.position)\n return ordered.find((status) => status.isDefault) ?? ordered[0]\n}\n\nasync function requireParentTask(\n em: EntityManager,\n parentTaskId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTask> {\n const parent = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parentTaskId, deletedAt: null }, scope),\n )\n if (!parent) throw parentNotFoundError(translate)\n return parent\n}\n\nasync function resolveStaffMemberIdForUser(\n em: EntityManager,\n userId: string | null | undefined,\n scope: StaffCommandScope,\n): Promise<string | null> {\n if (!userId) return null\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ userId, deletedAt: null }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n return member?.id ?? null\n}\n\nasync function requireStaffMember(\n em: EntityManager,\n staffMemberId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<string> {\n const member = await em.findOne(\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ id: staffMemberId, deletedAt: null }, scope),\n )\n if (!member) throw assigneeNotFoundError(translate)\n return member.id\n}\n\n/**\n * Order within a column, on the same gap grid the columns themselves use: a drop\n * between two cards writes one row instead of renumbering the stack.\n */\nasync function nextTaskPosition(\n em: EntityManager,\n timeProjectId: string,\n taskStatusId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const siblings = await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ timeProjectId, taskStatusId, deletedAt: null }, scope),\n )\n return nextStatusPosition(siblings.map((task) => task.position))\n}\n\n/**\n * The highest number the project has EVER handed out, deleted rows included. The\n * unique index only covers live rows, so a soft-deleted `TT-142` would otherwise let\n * the next task reuse that reference \u2014 and a closed report quoting the old one would\n * suddenly read as the new one.\n */\nasync function readHighestSequenceNumber(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const tasks = await em.find(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ timeProjectId }, scope))\n return tasks.reduce((highest, task) => Math.max(highest, task.sequenceNumber ?? 0), 0)\n}\n\n/**\n * \u00A72: a task lives in exactly one project. The update schema has no `timeProjectId`,\n * so a caller sending one would otherwise have it silently dropped and believe the\n * move succeeded.\n */\nfunction assertNoProjectMove(rawInput: unknown, task: StaffTimeTask, translate: Translate): void {\n if (!rawInput || typeof rawInput !== 'object') return\n const requested = (rawInput as Record<string, unknown>).timeProjectId\n if (requested === undefined || requested === null) return\n if (typeof requested === 'string' && requested === task.timeProjectId) return\n const message = translate(\n 'staff.time_tracking.tasks.errors.projectMoveUnsupported',\n 'A task cannot be moved to another project. Create it in the target project instead.',\n )\n throw new CrudHttpError(400, {\n code: TASK_PROJECT_MOVE_UNSUPPORTED_CODE,\n error: message,\n fieldErrors: { timeProjectId: message },\n })\n}\n\nfunction applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {\n task.taskStatusId = status.id\n // Done-ness comes from the column, never from a boolean on the task: landing on a\n // terminal column closes it, leaving one re-opens it.\n if (status.isDone) {\n if (!task.closedAt) task.closedAt = now\n } else {\n task.closedAt = null\n }\n}\n\nfunction actorUserId(ctx: CommandRuntimeContext): string | null {\n return typeof ctx.auth?.sub === 'string' ? ctx.auth.sub : null\n}\n\nconst createTaskCommand: CommandHandler<StaffTimeTaskCreateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.create,\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)\n const { translate } = await resolveTranslations()\n const baseEm = ctx.container.resolve('em') as EntityManager\n const createdByUserId = actorUserId(ctx)\n\n let record: StaffTimeTask\n try {\n record = await withTaskReferenceRetry(async () => {\n // A fresh fork per attempt: a lost race aborts the transaction, so the retry\n // re-reads the project's highest number rather than reusing a stale max.\n const em = baseEm.fork()\n let created: StaffTimeTask | null = null\n let plan: {\n project: StaffTimeProject\n status: StaffTimeTaskStatus\n parentTaskId: string | null\n assigneeStaffMemberId: string | null\n position: number\n highestSequenceNumber: number\n } | null = null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const project = await requireProject(em, parsed.timeProjectId, scope, translate)\n const status = await resolveTaskStatus(em, parsed.timeProjectId, parsed.taskStatusId, scope, translate)\n\n let parentTaskId: string | null = null\n if (parsed.parentTaskId) {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const rejection = rejectTaskParent(parent, { timeProjectId: parsed.timeProjectId })\n if (rejection) throw parentRejectionError(translate, rejection)\n parentTaskId = parent.id\n }\n\n // US-C1: the creator owns the task unless the request says otherwise.\n const assigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : await resolveStaffMemberIdForUser(em, createdByUserId, scope)\n\n plan = {\n project,\n status,\n parentTaskId,\n assigneeStaffMemberId,\n position:\n parsed.position ?? (await nextTaskPosition(em, parsed.timeProjectId, status.id, scope)),\n highestSequenceNumber: await readHighestSequenceNumber(em, parsed.timeProjectId, scope),\n }\n },\n () => {\n if (!plan) throw notFoundError(translate)\n const now = new Date()\n const allocation = allocateTaskReference(plan.project.code, plan.highestSequenceNumber)\n created = em.create(StaffTimeTask, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n timeProjectId: parsed.timeProjectId,\n parentTaskId: plan.parentTaskId,\n taskStatusId: plan.status.id,\n sequenceNumber: allocation.sequenceNumber,\n reference: allocation.reference,\n title: parsed.title,\n description: parsed.description ?? null,\n assigneeStaffMemberId: plan.assigneeStaffMemberId,\n position: plan.position,\n createdByUserId,\n closedAt: plan.status.isDone ? now : null,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(created)\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.create },\n )\n\n if (!created) throw notFoundError(translate)\n return created as StaffTimeTask\n })\n } catch (err) {\n // The retry budget ran out \u2014 the caller retries rather than getting a duplicate.\n if (isTaskReferenceConflict(err)) throw referenceConflictError(translate)\n throw err\n }\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'created',\n entity: record,\n identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n return { taskId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n return { snapshot }\n },\n buildLog: async ({ result, ctx }) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.create', 'Create task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: { after: snapshot } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))\n if (!task) return\n task.deletedAt = new Date()\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nconst updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.update,\n async prepare(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n assertNoProjectMove(rawInput, task, translate)\n\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetParentTaskId: string | null | undefined\n let targetAssigneeStaffMemberId: string | null | undefined\n\n await withAtomicFlush(\n em,\n [\n async () => {\n if (parsed.taskStatusId !== undefined) {\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n }\n\n if (parsed.parentTaskId !== undefined) {\n if (parsed.parentTaskId === null) {\n targetParentTaskId = null\n } else {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const childCount = await em.count(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n const rejection = rejectTaskParent(parent, {\n id: task.id,\n timeProjectId: task.timeProjectId,\n childCount,\n })\n if (rejection) throw parentRejectionError(translate, rejection)\n targetParentTaskId = parent.id\n }\n }\n\n if (parsed.assigneeStaffMemberId !== undefined) {\n targetAssigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : null\n }\n },\n () => {\n const now = new Date()\n if (parsed.title !== undefined) task.title = parsed.title\n if (parsed.description !== undefined) task.description = parsed.description ?? null\n if (parsed.position !== undefined) task.position = parsed.position\n if (targetParentTaskId !== undefined) task.parentTaskId = targetParentTaskId\n if (targetAssigneeStaffMemberId !== undefined) task.assigneeStaffMemberId = targetAssigneeStaffMemberId\n if (targetStatus) applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.update },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n return { taskId: task.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.update', 'Update task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n task.parentTaskId = before.parentTaskId\n task.taskStatusId = before.taskStatusId\n task.title = before.title\n task.description = before.description\n task.assigneeStaffMemberId = before.assigneeStaffMemberId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nconst deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; childIds: string[] }> = {\n id: staffTimeTaskCommandIds.delete,\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const scope = staffSnapshotScopeFromContext(ctx)\n const before = await loadTaskSnapshot(em, id, scope)\n if (!before) return {}\n const cascadedChildren = before.parentTaskId ? [] : await loadChildSnapshots(em, before.id, scope)\n return { before: { snapshot: before, cascadedChildren } }\n },\n async execute(input, ctx) {\n const { translate } = await resolveTranslations()\n const id = input?.id\n if (!id) {\n throw new CrudHttpError(400, {\n error: translate('staff.time_tracking.tasks.errors.idRequired', 'Task id is required.'),\n })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n let children: StaffTimeTask[] = []\n let childIds: string[] = []\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Only a top-level task can carry a cascade; the depth rule guarantees a\n // child has none, so the walk is exactly one level.\n children = task.parentTaskId\n ? []\n : await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n },\n () => {\n const plan = planTaskSoftDelete(task, children)\n childIds = plan.childIds\n const now = new Date()\n const byId = new Map(children.map((child) => [child.id, child]))\n task.deletedAt = now\n task.updatedAt = now\n for (const childId of childIds) {\n const child = byId.get(childId)\n if (!child) continue\n child.deletedAt = now\n child.updatedAt = now\n }\n // Time entries are untouched on purpose: a closed report still has to\n // resolve this task's label months after it was removed from the board.\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.delete },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const removed of [task, ...children.filter((child) => childIds.includes(child.id))]) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: removed,\n identifiers: { id: removed.id, organizationId: removed.organizationId, tenantId: removed.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n\n return { taskId: task.id, childIds }\n },\n buildLog: async ({ snapshots }) => {\n const prepared = snapshots.before as\n | { snapshot?: TaskSnapshot; cascadedChildren?: TaskSnapshot[] }\n | undefined\n const before = prepared?.snapshot\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.delete', 'Delete task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n cascadedChildren: prepared?.cascadedChildren ?? null,\n } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const snapshotScope = staffSnapshotScopeFromSnapshot(before)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)]\n const restored: StaffTimeTask[] = []\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const tasks = await em.find(StaffTimeTask, {\n id: { $in: ids },\n ...snapshotScopeWhere(snapshotScope),\n })\n restored.push(...tasks)\n },\n () => {\n // Undo restores the whole set the cascade removed, not just the parent.\n for (const task of restored) {\n task.deletedAt = null\n task.updatedAt = new Date()\n }\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const task of restored) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n },\n}\n\n/**\n * US-C2: the board drag, and the drawer's subtask tick, which is the same move\n * expressed without a position.\n *\n * It is a command of its own rather than a flavour of `update` for three reasons:\n * the move is the one write on this entity where two people racing the same record\n * is genuinely likely, so it enforces the `updated_at` version and answers 409 on a\n * stale one; the board needs a lifecycle event carrying enough to relocate the card\n * on another client without a refetch; and the column change is the whole payload,\n * so the audit row reads \"Move task\" instead of a generic update.\n *\n * `closedAt` is NOT re-derived here \u2014 `applyStatusToTask` above is the single place\n * that reads the column's `is_done`, shared verbatim with `update`.\n */\nconst statusChangeInputSchema = staffTimeTaskStatusChangeSchema.extend({\n id: z.string().uuid(),\n})\n\nexport type StaffTimeTaskStatusChangeCommandInput = z.infer<typeof statusChangeInputSchema>\n\nexport type StaffTimeTaskStatusChangeResult = {\n taskId: string\n timeProjectId: string\n previousTaskStatusId: string\n taskStatusId: string\n position: number\n closedAt: string | null\n updatedAt: string | null\n}\n\nconst STATUS_CHANGE_DIFFED_FIELDS = ['taskStatusId', 'position', 'closedAt'] as const\n\nconst statusChangeTaskCommand: CommandHandler<\n StaffTimeTaskStatusChangeCommandInput,\n StaffTimeTaskStatusChangeResult\n> = {\n id: staffTimeTaskCommandIds.statusChange,\n async prepare(rawInput, ctx) {\n const parsed = statusChangeInputSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = statusChangeInputSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n // Two people dragging the same card is the realistic conflict on this board,\n // so a stale version loses here and the client rolls the card back to the\n // column it came from.\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: STAFF_TIME_TASK_RESOURCE_KIND,\n resourceId: task.id,\n current: task.updatedAt,\n request: ctx.request,\n })\n\n const previousTaskStatusId = task.taskStatusId\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetPosition = task.position\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Refuses a column belonging to another project: `resolveTaskStatus`\n // looks the status up inside this task's project, so a foreign id is a\n // 422 rather than a silent cross-board move.\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n targetPosition =\n parsed.position ?? (await nextTaskPosition(em, task.timeProjectId, targetStatus.id, scope))\n },\n () => {\n if (!targetStatus) throw notFoundError(translate)\n const now = new Date()\n task.position = targetPosition\n applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.statusChange },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n const result: StaffTimeTaskStatusChangeResult = {\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }\n\n // `clientBroadcast: true` (events.ts) puts this on the DOM Event Bridge, so the\n // payload carries everything a second board needs to move the card itself \u2014\n // origin column, destination column and the new slot \u2014 without a refetch. The\n // scope fields are what the SSE stream filters its audience on. A failed\n // broadcast must never fail a committed drag.\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n ...result,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n\n return result\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.statusChange', 'Move task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n // Flushes the cached tasks list the board refetches right after the move,\n // and again after an undo \u2014 the audit resourceKind above names a different\n // tag. See TASK_LIST_CACHE_RESOURCE.\n context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...STATUS_CHANGE_DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n const previousTaskStatusId = task.taskStatusId\n task.taskStatusId = before.taskStatusId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n },\n}\n\nregisterCommand(createTaskCommand)\nregisterCommand(updateTaskCommand)\nregisterCommand(deleteTaskCommand)\nregisterCommand(statusChangeTaskCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["message"]
|
|
7
|
+
}
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
-
import {
|
|
1
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { CustomerEntity } from "@open-mercato/core/modules/customers/data/entities";
|
|
3
|
+
import {
|
|
4
|
+
StaffTeamMember,
|
|
5
|
+
StaffTimeProject,
|
|
6
|
+
StaffTimeReport,
|
|
7
|
+
StaffTimeReportEntry,
|
|
8
|
+
StaffTimeReportEvent,
|
|
9
|
+
StaffTimeTask,
|
|
10
|
+
StaffTimeTaskStatus
|
|
11
|
+
} from "./entities.js";
|
|
12
|
+
import { decorateTimeEntryRows } from "../lib/timesheets/timeEntryDecoration.js";
|
|
3
13
|
import { computeProjectHoursTrend } from "../lib/timesheets-projects/computeProjectHoursTrend.js";
|
|
14
|
+
import { computeProjectFinancials } from "../lib/timesheets-projects/computeProjectFinancials.js";
|
|
4
15
|
import {
|
|
5
16
|
listProjectMembersPreview
|
|
6
17
|
} from "../lib/timesheets-projects/listProjectMembersPreview.js";
|
|
18
|
+
import { computeTaskRollups, EMPTY_TASK_ROLLUP } from "../lib/timesheets-tasks/computeTaskRollups.js";
|
|
7
19
|
const MANAGE_FEATURE = "staff.timesheets.projects.manage";
|
|
20
|
+
const RATES_FEATURE = "staff.timesheets.rates.view";
|
|
21
|
+
function withQueryEngineSurface(enricher) {
|
|
22
|
+
return {
|
|
23
|
+
...enricher,
|
|
24
|
+
id: `${enricher.id}.query-engine`,
|
|
25
|
+
targetEntity: enricher.targetEntity.replace(/:/g, "."),
|
|
26
|
+
queryEngine: { enabled: true }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
8
29
|
const FALLBACK = {
|
|
9
30
|
_staff: {
|
|
10
31
|
hoursWeek: 0,
|
|
@@ -12,11 +33,10 @@ const FALLBACK = {
|
|
|
12
33
|
myRole: null
|
|
13
34
|
}
|
|
14
35
|
};
|
|
15
|
-
async function
|
|
16
|
-
if (ctx.userFeatures?.includes(MANAGE_FEATURE)) return true;
|
|
36
|
+
async function callerHasFeature(ctx, feature) {
|
|
17
37
|
try {
|
|
18
38
|
const rbac = ctx.container.resolve("rbacService");
|
|
19
|
-
return await rbac.userHasAllFeatures(ctx.userId, [
|
|
39
|
+
return await rbac.userHasAllFeatures(ctx.userId, [feature], {
|
|
20
40
|
tenantId: ctx.tenantId,
|
|
21
41
|
organizationId: ctx.organizationId
|
|
22
42
|
});
|
|
@@ -24,6 +44,40 @@ async function callerHasManage(ctx) {
|
|
|
24
44
|
return false;
|
|
25
45
|
}
|
|
26
46
|
}
|
|
47
|
+
function toNullableNumber(value) {
|
|
48
|
+
if (value === null || value === void 0) return null;
|
|
49
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
50
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
51
|
+
}
|
|
52
|
+
function resolveCustomerName(snapshot) {
|
|
53
|
+
if (!snapshot || typeof snapshot !== "object") return null;
|
|
54
|
+
for (const key of ["displayName", "name", "companyName", "label", "email"]) {
|
|
55
|
+
const value = snapshot[key];
|
|
56
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
async function loadCustomerNames(em, tenantId, organizationId, customerIds) {
|
|
61
|
+
const names = /* @__PURE__ */ new Map();
|
|
62
|
+
const ids = [...new Set(customerIds)];
|
|
63
|
+
if (ids.length === 0) return names;
|
|
64
|
+
try {
|
|
65
|
+
const customers = await findWithDecryption(
|
|
66
|
+
em,
|
|
67
|
+
CustomerEntity,
|
|
68
|
+
{ id: { $in: ids }, tenantId, organizationId, deletedAt: null },
|
|
69
|
+
void 0,
|
|
70
|
+
{ tenantId, organizationId }
|
|
71
|
+
);
|
|
72
|
+
for (const customer of customers) {
|
|
73
|
+
const name = customer.displayName?.trim() || customer.primaryEmail?.trim();
|
|
74
|
+
if (name) names.set(customer.id, name);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
return names;
|
|
78
|
+
}
|
|
79
|
+
return names;
|
|
80
|
+
}
|
|
27
81
|
async function resolveCallerStaffMemberId(ctx) {
|
|
28
82
|
const member = await findOneWithDecryption(
|
|
29
83
|
ctx.em.fork(),
|
|
@@ -56,17 +110,30 @@ const portfolioEnricher = {
|
|
|
56
110
|
if (records.length === 0) return records;
|
|
57
111
|
const ctx = context;
|
|
58
112
|
const projectIds = records.map((r) => r.id);
|
|
59
|
-
const [callerStaffMemberId, hasManage] = await Promise.all([
|
|
113
|
+
const [callerStaffMemberId, hasManage, hasRates] = await Promise.all([
|
|
60
114
|
resolveCallerStaffMemberId(ctx),
|
|
61
|
-
|
|
115
|
+
callerHasFeature(ctx, MANAGE_FEATURE),
|
|
116
|
+
callerHasFeature(ctx, RATES_FEATURE)
|
|
62
117
|
]);
|
|
63
|
-
const
|
|
118
|
+
const ownEntriesOnly = hasManage ? null : callerStaffMemberId;
|
|
119
|
+
const projects = await ctx.em.fork().find(StaffTimeProject, {
|
|
120
|
+
id: { $in: projectIds },
|
|
121
|
+
tenantId: ctx.tenantId,
|
|
122
|
+
organizationId: ctx.organizationId,
|
|
123
|
+
deletedAt: null
|
|
124
|
+
});
|
|
125
|
+
const projectById = new Map(projects.map((project) => [project.id, project]));
|
|
126
|
+
const hourlyRateByProjectId = new Map(
|
|
127
|
+
projects.map((project) => [project.id, toNullableNumber(project.hourlyRate)])
|
|
128
|
+
);
|
|
129
|
+
const customerIdsMissingSnapshot = projects.filter((project) => !resolveCustomerName(project.customerSnapshot)).map((project) => project.customerId).filter((customerId) => typeof customerId === "string" && customerId.length > 0);
|
|
130
|
+
const [trendMap, membersMap, financialsMap, customerNameById] = await Promise.all([
|
|
64
131
|
computeProjectHoursTrend({
|
|
65
132
|
em: ctx.em,
|
|
66
133
|
tenantId: ctx.tenantId,
|
|
67
134
|
organizationId: ctx.organizationId,
|
|
68
135
|
projectIds,
|
|
69
|
-
staffMemberId:
|
|
136
|
+
staffMemberId: ownEntriesOnly
|
|
70
137
|
}),
|
|
71
138
|
listProjectMembersPreview({
|
|
72
139
|
em: ctx.em,
|
|
@@ -74,7 +141,16 @@ const portfolioEnricher = {
|
|
|
74
141
|
organizationId: ctx.organizationId,
|
|
75
142
|
projectIds,
|
|
76
143
|
callerStaffMemberId
|
|
77
|
-
})
|
|
144
|
+
}),
|
|
145
|
+
computeProjectFinancials({
|
|
146
|
+
em: ctx.em.fork(),
|
|
147
|
+
tenantId: ctx.tenantId,
|
|
148
|
+
organizationId: ctx.organizationId,
|
|
149
|
+
projectIds,
|
|
150
|
+
hourlyRateByProjectId,
|
|
151
|
+
staffMemberId: ownEntriesOnly
|
|
152
|
+
}),
|
|
153
|
+
loadCustomerNames(ctx.em.fork(), ctx.tenantId, ctx.organizationId, customerIdsMissingSnapshot)
|
|
78
154
|
]);
|
|
79
155
|
return records.map((record) => {
|
|
80
156
|
const trend = trendMap.get(record.id) ?? {
|
|
@@ -82,20 +158,358 @@ const portfolioEnricher = {
|
|
|
82
158
|
hoursTrend: [0, 0, 0, 0, 0, 0, 0]
|
|
83
159
|
};
|
|
84
160
|
const members = membersMap.get(record.id);
|
|
161
|
+
const financials = financialsMap.get(record.id);
|
|
162
|
+
const project = projectById.get(record.id);
|
|
85
163
|
const enrichment = {
|
|
86
164
|
hoursWeek: trend.hoursWeek,
|
|
87
165
|
hoursTrend: trend.hoursTrend,
|
|
88
|
-
myRole: members?.myRole ?? null
|
|
166
|
+
myRole: members?.myRole ?? null,
|
|
167
|
+
customerName: resolveCustomerName(project?.customerSnapshot) ?? (project?.customerId ? customerNameById.get(project.customerId) ?? null : null),
|
|
168
|
+
totalMinutes: financials?.totalMinutes ?? 0,
|
|
169
|
+
billableMinutes: financials?.billableMinutes ?? 0,
|
|
170
|
+
budget: {
|
|
171
|
+
kind: project?.budgetKind ?? "none",
|
|
172
|
+
value: toNullableNumber(project?.budgetValue),
|
|
173
|
+
warnAtPercent: project?.budgetWarnAtPercent ?? null
|
|
174
|
+
}
|
|
89
175
|
};
|
|
90
176
|
if (hasManage && members) {
|
|
91
177
|
enrichment.members = members.preview;
|
|
92
178
|
enrichment.memberCount = members.total;
|
|
93
179
|
}
|
|
180
|
+
if (hasRates) {
|
|
181
|
+
enrichment.hourlyRate = toNullableNumber(project?.hourlyRate);
|
|
182
|
+
enrichment.cost = financials?.cost ?? null;
|
|
183
|
+
}
|
|
184
|
+
return { ...record, _staff: enrichment };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const taskRollupEnricher = {
|
|
189
|
+
id: "staff.timesheets-tasks-rollup",
|
|
190
|
+
targetEntity: "staff:staff_time_task",
|
|
191
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
192
|
+
// which also intersects the page with the caller's project access; the aggregate
|
|
193
|
+
// narrows entries to the projects of that already-filtered page.
|
|
194
|
+
priority: 10,
|
|
195
|
+
timeout: 3e3,
|
|
196
|
+
critical: false,
|
|
197
|
+
fallback: { ...EMPTY_TASK_ROLLUP },
|
|
198
|
+
// Minutes come from `staff_time_entries`, a table the task list cache never
|
|
199
|
+
// invalidates on, so the rollup must be recomputed on every hit.
|
|
200
|
+
cacheableOnListHit: false,
|
|
201
|
+
async enrichOne(record, context) {
|
|
202
|
+
const enriched = await this.enrichMany([record], context);
|
|
203
|
+
return enriched[0];
|
|
204
|
+
},
|
|
205
|
+
async enrichMany(records, context) {
|
|
206
|
+
if (records.length === 0) return records;
|
|
207
|
+
const ctx = context;
|
|
208
|
+
const rollups = await computeTaskRollups({
|
|
209
|
+
em: ctx.em.fork(),
|
|
210
|
+
tenantId: ctx.tenantId,
|
|
211
|
+
organizationId: ctx.organizationId,
|
|
212
|
+
taskIds: records.map((record) => record.id).filter((id) => typeof id === "string")
|
|
213
|
+
});
|
|
214
|
+
return records.map((record) => ({ ...record, ...rollups.get(record.id) ?? EMPTY_TASK_ROLLUP }));
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const EMPTY_TASK_TAGS = { tagIds: [], tags: [] };
|
|
218
|
+
async function loadTaskTags(em, taskIds, tenantId, organizationId) {
|
|
219
|
+
const byTaskId = /* @__PURE__ */ new Map();
|
|
220
|
+
const ids = [...new Set(taskIds)].filter((id) => typeof id === "string" && id.length > 0);
|
|
221
|
+
if (ids.length === 0) return byTaskId;
|
|
222
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
223
|
+
const sql = `
|
|
224
|
+
SELECT
|
|
225
|
+
tt.task_id,
|
|
226
|
+
tt.tag_id,
|
|
227
|
+
tg.slug,
|
|
228
|
+
tg.label,
|
|
229
|
+
tg.color
|
|
230
|
+
FROM staff_time_task_tags tt
|
|
231
|
+
JOIN staff_time_tags tg
|
|
232
|
+
ON tg.id = tt.tag_id
|
|
233
|
+
AND tg.tenant_id = tt.tenant_id
|
|
234
|
+
AND tg.organization_id = tt.organization_id
|
|
235
|
+
AND tg.deleted_at IS NULL
|
|
236
|
+
WHERE tt.tenant_id = ?
|
|
237
|
+
AND tt.organization_id = ?
|
|
238
|
+
AND tt.task_id IN (${placeholders})
|
|
239
|
+
ORDER BY tg.label ASC, tg.id ASC
|
|
240
|
+
`;
|
|
241
|
+
const rows = await em.getConnection().execute(sql, [tenantId, organizationId, ...ids]);
|
|
242
|
+
for (const row of rows) {
|
|
243
|
+
if (typeof row.task_id !== "string" || typeof row.tag_id !== "string") continue;
|
|
244
|
+
const bucket = byTaskId.get(row.task_id) ?? { tagIds: [], tags: [] };
|
|
245
|
+
if (bucket.tagIds.includes(row.tag_id)) continue;
|
|
246
|
+
bucket.tagIds.push(row.tag_id);
|
|
247
|
+
bucket.tags.push({
|
|
248
|
+
id: row.tag_id,
|
|
249
|
+
slug: row.slug ?? "",
|
|
250
|
+
label: row.label ?? row.tag_id,
|
|
251
|
+
color: row.color ?? null
|
|
252
|
+
});
|
|
253
|
+
byTaskId.set(row.task_id, bucket);
|
|
254
|
+
}
|
|
255
|
+
return byTaskId;
|
|
256
|
+
}
|
|
257
|
+
const taskTagEnricher = {
|
|
258
|
+
id: "staff.timesheets-tasks-tags",
|
|
259
|
+
targetEntity: "staff:staff_time_task",
|
|
260
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
261
|
+
// which also intersects the page with `resolveProjectAccess`; the join is keyed by
|
|
262
|
+
// that already-filtered page, so a tag can only be read through a task the caller
|
|
263
|
+
// may already see.
|
|
264
|
+
priority: 10,
|
|
265
|
+
timeout: 3e3,
|
|
266
|
+
critical: false,
|
|
267
|
+
fallback: { ...EMPTY_TASK_TAGS },
|
|
268
|
+
// Assignments live in `staff_time_task_tags`, a table the task list cache never
|
|
269
|
+
// invalidates on, so the chips must be re-read on every hit.
|
|
270
|
+
cacheableOnListHit: false,
|
|
271
|
+
async enrichOne(record, context) {
|
|
272
|
+
const enriched = await this.enrichMany([record], context);
|
|
273
|
+
return enriched[0];
|
|
274
|
+
},
|
|
275
|
+
async enrichMany(records, context) {
|
|
276
|
+
if (records.length === 0) return records;
|
|
277
|
+
const ctx = context;
|
|
278
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
279
|
+
return records.map((record) => ({ ...record, tagIds: [], tags: [] }));
|
|
280
|
+
}
|
|
281
|
+
const tagsByTaskId = await loadTaskTags(
|
|
282
|
+
ctx.em.fork(),
|
|
283
|
+
records.map((record) => record.id).filter((id) => typeof id === "string"),
|
|
284
|
+
ctx.tenantId,
|
|
285
|
+
ctx.organizationId
|
|
286
|
+
);
|
|
287
|
+
return records.map((record) => {
|
|
288
|
+
const assigned = tagsByTaskId.get(record.id);
|
|
289
|
+
return { ...record, tagIds: assigned?.tagIds ?? [], tags: assigned?.tags ?? [] };
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const timeEntryEnricher = {
|
|
294
|
+
id: "staff.timesheets-time-entries",
|
|
295
|
+
targetEntity: "staff:staff_time_entry",
|
|
296
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.view']`),
|
|
297
|
+
// which also intersects the page with `resolveProjectAccess`. The money fields
|
|
298
|
+
// below are gated per caller on `staff.timesheets.rates.view`.
|
|
299
|
+
priority: 10,
|
|
300
|
+
timeout: 3e3,
|
|
301
|
+
critical: false,
|
|
302
|
+
// Tags live in `staff_time_entry_tags` and rates in `staff_time_projects` —
|
|
303
|
+
// neither invalidates the entry list cache, and the money keys are per-caller,
|
|
304
|
+
// so the decoration must be recomputed on every hit.
|
|
305
|
+
cacheableOnListHit: false,
|
|
306
|
+
async enrichOne(record, context) {
|
|
307
|
+
const enriched = await this.enrichMany([record], context);
|
|
308
|
+
return enriched[0];
|
|
309
|
+
},
|
|
310
|
+
async enrichMany(records, context) {
|
|
311
|
+
if (records.length === 0) return records;
|
|
312
|
+
const ctx = context;
|
|
313
|
+
const rows = records.map((record) => ({ ...record }));
|
|
314
|
+
await decorateTimeEntryRows(rows, {
|
|
315
|
+
em: ctx.em,
|
|
316
|
+
tenantId: ctx.tenantId,
|
|
317
|
+
organizationId: ctx.organizationId,
|
|
318
|
+
canSeeRates: await callerHasFeature(ctx, RATES_FEATURE)
|
|
319
|
+
});
|
|
320
|
+
return rows;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const EMPTY_TASK_CONTEXT = {
|
|
324
|
+
_staff: {
|
|
325
|
+
projectName: null,
|
|
326
|
+
projectCode: null,
|
|
327
|
+
projectColor: null,
|
|
328
|
+
statusName: null,
|
|
329
|
+
statusIsDone: null,
|
|
330
|
+
assigneeName: null
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const taskContextEnricher = {
|
|
334
|
+
id: "staff.timesheets-tasks-context",
|
|
335
|
+
targetEntity: "staff:staff_time_task",
|
|
336
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.tasks.view']`),
|
|
337
|
+
// which intersects the page with `resolveProjectAccess`; every lookup below is keyed
|
|
338
|
+
// by that already-filtered page and re-scoped to the caller tenant and organization.
|
|
339
|
+
priority: 5,
|
|
340
|
+
timeout: 3e3,
|
|
341
|
+
critical: false,
|
|
342
|
+
fallback: { ...EMPTY_TASK_CONTEXT },
|
|
343
|
+
cacheableOnListHit: false,
|
|
344
|
+
async enrichOne(record, context) {
|
|
345
|
+
const enriched = await this.enrichMany([record], context);
|
|
346
|
+
return enriched[0];
|
|
347
|
+
},
|
|
348
|
+
async enrichMany(records, context) {
|
|
349
|
+
if (records.length === 0) return records;
|
|
350
|
+
const ctx = context;
|
|
351
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
352
|
+
return records.map((record) => ({ ...record, ...EMPTY_TASK_CONTEXT }));
|
|
353
|
+
}
|
|
354
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
355
|
+
const taskIds = records.map((record) => record.id).filter((id) => typeof id === "string");
|
|
356
|
+
const tasks = await ctx.em.fork().find(StaffTimeTask, { id: { $in: taskIds }, ...scope, deletedAt: null });
|
|
357
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
358
|
+
const projectIds = [...new Set(tasks.map((task) => task.timeProjectId).filter(Boolean))];
|
|
359
|
+
const statusIds = [...new Set(tasks.map((task) => task.taskStatusId).filter(Boolean))];
|
|
360
|
+
const assigneeIds = [
|
|
361
|
+
...new Set(
|
|
362
|
+
tasks.map((task) => task.assigneeStaffMemberId).filter((id) => typeof id === "string" && id.length > 0)
|
|
363
|
+
)
|
|
364
|
+
];
|
|
365
|
+
const hasRates = await callerHasFeature(ctx, RATES_FEATURE);
|
|
366
|
+
const [projects, statuses, assignees] = await Promise.all([
|
|
367
|
+
projectIds.length ? ctx.em.fork().find(StaffTimeProject, { id: { $in: projectIds }, ...scope, deletedAt: null }) : Promise.resolve([]),
|
|
368
|
+
statusIds.length ? ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope, deletedAt: null }) : Promise.resolve([]),
|
|
369
|
+
assigneeIds.length ? findWithDecryption(
|
|
370
|
+
ctx.em.fork(),
|
|
371
|
+
StaffTeamMember,
|
|
372
|
+
{ id: { $in: assigneeIds }, ...scope, deletedAt: null },
|
|
373
|
+
void 0,
|
|
374
|
+
scope
|
|
375
|
+
) : Promise.resolve([])
|
|
376
|
+
]);
|
|
377
|
+
const projectById = new Map(projects.map((project) => [project.id, project]));
|
|
378
|
+
const statusById = new Map(statuses.map((status) => [status.id, status]));
|
|
379
|
+
const assigneeById = new Map(assignees.map((member) => [member.id, member]));
|
|
380
|
+
return records.map((record) => {
|
|
381
|
+
const task = taskById.get(record.id);
|
|
382
|
+
if (!task) return { ...record, ...EMPTY_TASK_CONTEXT };
|
|
383
|
+
const project = projectById.get(task.timeProjectId);
|
|
384
|
+
const status = statusById.get(task.taskStatusId);
|
|
385
|
+
const assignee = task.assigneeStaffMemberId ? assigneeById.get(task.assigneeStaffMemberId) : void 0;
|
|
386
|
+
const enrichment = {
|
|
387
|
+
projectName: project?.name ?? null,
|
|
388
|
+
projectCode: project?.code ?? null,
|
|
389
|
+
projectColor: project?.color ?? null,
|
|
390
|
+
statusName: status?.name ?? null,
|
|
391
|
+
statusIsDone: status ? status.isDone : null,
|
|
392
|
+
assigneeName: assignee?.displayName ?? null
|
|
393
|
+
};
|
|
394
|
+
if (hasRates) {
|
|
395
|
+
enrichment.hourlyRate = toNullableNumber(project?.hourlyRate);
|
|
396
|
+
enrichment.currencyCode = project?.currencyCode ?? null;
|
|
397
|
+
}
|
|
398
|
+
return { ...record, _staff: enrichment };
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
const EMPTY_REPORT_ENRICHMENT = {
|
|
403
|
+
_staff: {
|
|
404
|
+
isClosed: false,
|
|
405
|
+
closedAt: null,
|
|
406
|
+
billableMinutes: 0,
|
|
407
|
+
nonbillableMinutes: 0,
|
|
408
|
+
totalMinutes: 0,
|
|
409
|
+
lockedEntryCount: 0,
|
|
410
|
+
exportCount: 0,
|
|
411
|
+
lastExportedAt: null,
|
|
412
|
+
lastExportFormat: null
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
function readExportFormat(metadata) {
|
|
416
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
417
|
+
const format = metadata.format;
|
|
418
|
+
return typeof format === "string" && format.length > 0 ? format : null;
|
|
419
|
+
}
|
|
420
|
+
const reportEnricher = {
|
|
421
|
+
id: "staff.timesheets-reports",
|
|
422
|
+
targetEntity: "staff:staff_time_report",
|
|
423
|
+
// ACL is enforced by the route (`requireFeatures: ['staff.timesheets.reports.view']`);
|
|
424
|
+
// `totalAmount` is gated per caller on `staff.timesheets.rates.view` below.
|
|
425
|
+
priority: 10,
|
|
426
|
+
timeout: 3e3,
|
|
427
|
+
critical: false,
|
|
428
|
+
fallback: { ...EMPTY_REPORT_ENRICHMENT },
|
|
429
|
+
// The freeze and export counts live in tables the report list cache never
|
|
430
|
+
// invalidates on, so they are re-read on every hit.
|
|
431
|
+
cacheableOnListHit: false,
|
|
432
|
+
async enrichOne(record, context) {
|
|
433
|
+
const enriched = await this.enrichMany([record], context);
|
|
434
|
+
return enriched[0];
|
|
435
|
+
},
|
|
436
|
+
async enrichMany(records, context) {
|
|
437
|
+
if (records.length === 0) return records;
|
|
438
|
+
const ctx = context;
|
|
439
|
+
if (!ctx.tenantId || !ctx.organizationId) {
|
|
440
|
+
return records.map((record) => ({ ...record, ...EMPTY_REPORT_ENRICHMENT }));
|
|
441
|
+
}
|
|
442
|
+
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
443
|
+
const reportIds = records.map((record) => record.id).filter((id) => typeof id === "string");
|
|
444
|
+
if (reportIds.length === 0) {
|
|
445
|
+
return records.map((record) => ({ ...record, ...EMPTY_REPORT_ENRICHMENT }));
|
|
446
|
+
}
|
|
447
|
+
const [reports, frozenEntries, exportEvents, hasRates] = await Promise.all([
|
|
448
|
+
ctx.em.fork().find(StaffTimeReport, { id: { $in: reportIds }, ...scope, deletedAt: null }),
|
|
449
|
+
ctx.em.fork().find(
|
|
450
|
+
StaffTimeReportEntry,
|
|
451
|
+
{ reportId: { $in: reportIds }, ...scope },
|
|
452
|
+
{ fields: ["reportId"] }
|
|
453
|
+
),
|
|
454
|
+
ctx.em.fork().find(
|
|
455
|
+
StaffTimeReportEvent,
|
|
456
|
+
{ reportId: { $in: reportIds }, eventType: "exported", ...scope },
|
|
457
|
+
{ orderBy: { createdAt: "desc" } }
|
|
458
|
+
),
|
|
459
|
+
callerHasFeature(ctx, RATES_FEATURE)
|
|
460
|
+
]);
|
|
461
|
+
const reportById = new Map(reports.map((report) => [report.id, report]));
|
|
462
|
+
const frozenCountByReportId = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const entry of frozenEntries) {
|
|
464
|
+
frozenCountByReportId.set(entry.reportId, (frozenCountByReportId.get(entry.reportId) ?? 0) + 1);
|
|
465
|
+
}
|
|
466
|
+
const exportsByReportId = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const event of exportEvents) {
|
|
468
|
+
const summary = exportsByReportId.get(event.reportId) ?? { count: 0, lastAt: null, lastFormat: null };
|
|
469
|
+
summary.count += 1;
|
|
470
|
+
if (summary.lastAt === null) {
|
|
471
|
+
summary.lastAt = event.createdAt ?? null;
|
|
472
|
+
summary.lastFormat = readExportFormat(event.metadata);
|
|
473
|
+
}
|
|
474
|
+
exportsByReportId.set(event.reportId, summary);
|
|
475
|
+
}
|
|
476
|
+
return records.map((record) => {
|
|
477
|
+
const report = reportById.get(record.id);
|
|
478
|
+
if (!report) return { ...record, ...EMPTY_REPORT_ENRICHMENT };
|
|
479
|
+
const exports = exportsByReportId.get(report.id);
|
|
480
|
+
const billableMinutes = report.totalBillableMinutes ?? 0;
|
|
481
|
+
const nonbillableMinutes = report.totalNonbillableMinutes ?? 0;
|
|
482
|
+
const enrichment = {
|
|
483
|
+
isClosed: report.status === "closed",
|
|
484
|
+
closedAt: report.closedAt ? report.closedAt.toISOString() : null,
|
|
485
|
+
billableMinutes,
|
|
486
|
+
nonbillableMinutes,
|
|
487
|
+
totalMinutes: billableMinutes + nonbillableMinutes,
|
|
488
|
+
lockedEntryCount: frozenCountByReportId.get(report.id) ?? 0,
|
|
489
|
+
exportCount: exports?.count ?? 0,
|
|
490
|
+
lastExportedAt: exports?.lastAt ? exports.lastAt.toISOString() : null,
|
|
491
|
+
lastExportFormat: exports?.lastFormat ?? null
|
|
492
|
+
};
|
|
493
|
+
if (hasRates) {
|
|
494
|
+
enrichment.totalAmount = toNullableNumber(report.totalAmount);
|
|
495
|
+
enrichment.currencyCode = report.currencyCode ?? null;
|
|
496
|
+
}
|
|
94
497
|
return { ...record, _staff: enrichment };
|
|
95
498
|
});
|
|
96
499
|
}
|
|
97
500
|
};
|
|
98
|
-
const
|
|
501
|
+
const apiEnrichers = [
|
|
502
|
+
portfolioEnricher,
|
|
503
|
+
taskRollupEnricher,
|
|
504
|
+
taskTagEnricher,
|
|
505
|
+
taskContextEnricher,
|
|
506
|
+
timeEntryEnricher,
|
|
507
|
+
reportEnricher
|
|
508
|
+
];
|
|
509
|
+
const enrichers = [
|
|
510
|
+
...apiEnrichers,
|
|
511
|
+
...apiEnrichers.map((enricher) => withQueryEngineSurface(enricher))
|
|
512
|
+
];
|
|
99
513
|
var enrichers_default = enrichers;
|
|
100
514
|
export {
|
|
101
515
|
enrichers_default as default,
|