@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/api/timesheets/_shared/withTimesheetInterceptors.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * API interceptors for the hand-rolled `/api/staff/timesheets/*` routes.\n *\n * The eight `makeCrudRoute` time-tracking resources run `runApiInterceptorsBefore`\n * and `runApiInterceptorsAfter` through the CRUD factory. The ~23 routes written by\n * hand beside them ran neither, so a third party could rewrite the body of a create\n * on `/time-entries` but not on `/time-entries/bulk`, and could not shape the\n * response of a single aggregate. This wraps the two shared runners in the one shape\n * those routes need: one call before the work, one call around the response.\n *\n * `routePath` is derived from the request URL exactly as the factory derives it\n * (`normalizeInterceptorRoutePath`) \u2014 the pathname with the leading `/api/` removed,\n * so an interceptor targets `staff/timesheets/time-entries/bulk`. A route with a\n * dynamic segment carries the CONCRETE id in that path\n * (`staff/timesheets/time-entries/<uuid>/duplicate`), so an interceptor for one of\n * those must use the registry's prefix wildcard \u2014 `staff/timesheets/time-entries/*`\n * \u2014 rather than a literal with a placeholder in it.\n *\n * Scope: the interceptor context carries `tenantId` and `organizationId` and this\n * helper fails closed with a 400 when either is missing, so no interceptor ever runs\n * \u2014 and no route body is ever handed to one \u2014 outside a resolved tenant scope.\n */\n\nimport { NextResponse } from 'next/server'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type {\n ApiInterceptorMethod,\n InterceptorRequest,\n} from '@open-mercato/shared/lib/crud/api-interceptor'\nimport { getApiInterceptorsForRoute } from '@open-mercato/shared/lib/crud/interceptor-registry'\nimport { runApiInterceptorsBefore } from '@open-mercato/shared/lib/crud/interceptor-runner'\nimport { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'\nimport { parseExtensionHeaders } from '@open-mercato/shared/lib/umes/extension-headers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\nexport type TimesheetInterceptorScope = {\n container: AwilixContainer\n userId: string | null | undefined\n tenantId: string | null | undefined\n organizationId: string | null | undefined\n /**\n * The caller's granted features, when the route already resolved them \u2014 through\n * `resolveFeatureAccess`, which always yields an array. Deliberately not\n * nullable: a grant list that can be `null` cannot say whether an empty answer\n * means \"no grants\" or \"could not ask\", and passing that ambiguity on is what\n * this helper's own fallback exists to avoid.\n *\n * Omitted, the helper asks `rbacService.getGrantedFeatures` once \u2014 the same\n * source the CRUD factory uses \u2014 so a feature-gated interceptor is evaluated\n * against a real grant set rather than an empty one, and an unreadable set gates\n * every feature-scoped interceptor off.\n */\n userFeatures?: readonly string[]\n /**\n * Opt-in for the tenant-global routes (`/settings`, `/settings/reapply-rounding`),\n * whose records have no organization at all. Without it a request that resolved no\n * organization is refused rather than interceptor-scoped to an empty organization \u2014\n * the same fail-closed default every org-scoped route here keeps. With it, the\n * context carries the empty organization id the CRUD factory also passes for a\n * tenant-global caller.\n */\n tenantGlobal?: boolean\n}\n\nexport type TimesheetInterceptorSession = {\n /** The request body after the before-pass, or `{}` for a route that sends none. */\n body: Record<string, unknown>\n /** The query after the before-pass. */\n query: Record<string, unknown>\n /** `query` as `URLSearchParams`, for routes that read their input that way. */\n searchParams: URLSearchParams\n /**\n * Runs the after-pass over a successful JSON response and returns it. An\n * interceptor that fails or times out replaces the response with its own error.\n */\n respond(statusCode: number, body: Record<string, unknown>): Promise<NextResponse>\n /**\n * Runs the after-pass over a descriptor for a non-JSON response (a file export).\n * The interceptors cannot rewrite the bytes, so the descriptor is what they shape;\n * the caller applies whatever came back. A failed interceptor yields a JSON error\n * response instead of the file.\n */\n respondWithDescriptor(\n descriptor: Record<string, unknown>,\n ): Promise<{ ok: true; descriptor: Record<string, unknown> } | { ok: false; response: NextResponse }>\n}\n\nexport type TimesheetInterceptorRun =\n | { ok: true; session: TimesheetInterceptorSession }\n | { ok: false; response: NextResponse }\n\n/**\n * Duplicates and repeated keys collapse to arrays so a rewritten query round-trips\n * `?tagIds=a&tagIds=b` unchanged.\n */\nexport function readSearchParamsRecord(url: string): Record<string, unknown> {\n const record: Record<string, unknown> = {}\n let params: URLSearchParams\n try {\n params = new URL(url).searchParams\n } catch {\n return record\n }\n params.forEach((value, key) => {\n const existing = record[key]\n if (existing === undefined) {\n record[key] = value\n return\n }\n if (Array.isArray(existing)) {\n existing.push(value)\n return\n }\n record[key] = [existing, value]\n })\n return record\n}\n\nfunction toSearchParams(query: Record<string, unknown>): URLSearchParams {\n const params = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n if (value === null || value === undefined) continue\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item === null || item === undefined) continue\n params.append(key, String(item))\n }\n continue\n }\n params.append(key, String(value))\n }\n return params\n}\n\nfunction normalizeRoutePath(url: string): string {\n try {\n const pathname = new URL(url).pathname\n if (pathname.startsWith('/api/')) return pathname.slice(5)\n if (pathname === '/api') return ''\n return pathname.replace(/^\\/+/, '')\n } catch {\n return ''\n }\n}\n\nfunction toHeaderRecord(headers: Headers): Record<string, string> {\n const output: Record<string, string> = {}\n headers.forEach((value, key) => {\n output[key] = value\n })\n return output\n}\n\nasync function resolveUserFeatures(scope: TimesheetInterceptorScope): Promise<string[]> {\n if (scope.userFeatures) return [...scope.userFeatures]\n if (!scope.userId) return []\n try {\n const rbac = scope.container.resolve('rbacService') as RbacServiceLike | undefined\n if (!rbac?.getGrantedFeatures) return []\n return await rbac.getGrantedFeatures(scope.userId, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n })\n } catch {\n // An unreadable grant set gates every feature-scoped interceptor off rather\n // than running it unauthorized.\n return []\n }\n}\n\nexport async function runTimesheetInterceptors(args: {\n request: Request\n method: ApiInterceptorMethod\n scope: TimesheetInterceptorScope\n /** `readJsonSafe` returns `null` for an unparseable body; that reaches here as `{}`. */\n body?: Record<string, unknown> | null\n query?: Record<string, unknown>\n}): Promise<TimesheetInterceptorRun> {\n const { request, method, scope } = args\n const tenantId = scope.tenantId || null\n const resolvedOrganizationId = scope.organizationId || null\n const organizationId = resolvedOrganizationId ?? (scope.tenantGlobal ? '' : null)\n if (!tenantId || organizationId === null) {\n const { translate } = await resolveTranslations()\n return {\n ok: false,\n response: NextResponse.json(\n { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') },\n { status: 400 },\n ),\n }\n }\n\n const routePath = normalizeRoutePath(request.url)\n const headers = toHeaderRecord(request.headers)\n const requestPayload: InterceptorRequest = {\n method,\n url: request.url,\n body: args.body ?? {},\n query: args.query ?? {},\n headers,\n }\n\n // No interceptor targets this route and method, so neither pass can observe or\n // change anything. Skipping here keeps the request off the RBAC grant read and the\n // `em` resolve that assembling an interceptor context would otherwise cost on every\n // call to every one of these routes.\n if (getApiInterceptorsForRoute(routePath, method).length === 0) {\n const query = requestPayload.query ?? {}\n return {\n ok: true,\n session: {\n body: requestPayload.body ?? {},\n query,\n searchParams: toSearchParams(query),\n async respond(statusCode, body) {\n return NextResponse.json(body, { status: statusCode })\n },\n async respondWithDescriptor(descriptor) {\n return { ok: true, descriptor }\n },\n },\n }\n }\n\n const context = {\n userId: scope.userId ?? '',\n organizationId,\n tenantId,\n em: scope.container.resolve('em') as EntityManager,\n container: scope.container,\n userFeatures: await resolveUserFeatures(scope),\n extensionHeaders: parseExtensionHeaders(headers),\n }\n\n const before = await runApiInterceptorsBefore({\n routePath,\n method,\n request: requestPayload,\n context,\n })\n if (!before.ok) {\n return { ok: false, response: NextResponse.json(before.body, { status: before.statusCode }) }\n }\n\n const interceptedRequest = before.request\n const query = interceptedRequest.query ?? {}\n\n const runAfter = async (body: Record<string, unknown>, statusCode: number) =>\n runCustomRouteAfterInterceptors({\n routePath,\n method,\n request: interceptedRequest,\n response: { statusCode, body, headers: {} },\n context,\n metadataByInterceptor: before.metadataByInterceptor,\n })\n\n return {\n ok: true,\n session: {\n body: interceptedRequest.body ?? {},\n query,\n searchParams: toSearchParams(query),\n async respond(statusCode, body) {\n const after = await runAfter(body, statusCode)\n return NextResponse.json(after.body, {\n status: after.statusCode,\n ...(Object.keys(after.headers).length ? { headers: after.headers } : {}),\n })\n },\n async respondWithDescriptor(descriptor) {\n const after = await runAfter(descriptor, 200)\n if (!after.ok) {\n return {\n ok: false,\n response: NextResponse.json(after.body, { status: after.statusCode }),\n }\n }\n return { ok: true, descriptor: after.body }\n },\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["query"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
+
import { runRouteMutationGuards } from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
7
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
8
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
9
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
10
|
+
import { StaffTeamMember, StaffTimeProject } from "../../../data/entities.js";
|
|
11
|
+
import { emitStaffEvent } from "../../../events.js";
|
|
12
|
+
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
13
|
+
import { STAFF_TIME_TRACKING_RESOURCE_KINDS } from "../../guards.js";
|
|
14
|
+
import { runTimesheetInterceptors } from "../_shared/withTimesheetInterceptors.js";
|
|
15
|
+
import { resolveFeatureAccess } from "../../../lib/time-tracking/featureAccess.js";
|
|
16
|
+
const logger = createLogger("staff").child({ component: "api/timesheets/access-requests" });
|
|
17
|
+
const VIEW_FEATURE = "staff.timesheets.view";
|
|
18
|
+
const RESOURCE_KIND = STAFF_TIME_TRACKING_RESOURCE_KINDS.accessRequest;
|
|
19
|
+
const ACCESS_REQUEST_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
20
|
+
const GENERAL_REQUEST_SCOPE = "general";
|
|
21
|
+
const metadata = {
|
|
22
|
+
POST: { requireAuth: true, requireFeatures: [VIEW_FEATURE] }
|
|
23
|
+
};
|
|
24
|
+
const accessRequestSchema = z.object({
|
|
25
|
+
timeProjectId: z.string().uuid().nullish()
|
|
26
|
+
});
|
|
27
|
+
const accessRequestResponseSchema = z.object({
|
|
28
|
+
ok: z.literal(true),
|
|
29
|
+
deduplicated: z.boolean()
|
|
30
|
+
});
|
|
31
|
+
async function resolveAccessRequestContext(req) {
|
|
32
|
+
const container = await createRequestContainer();
|
|
33
|
+
const auth = await getAuthFromRequest(req);
|
|
34
|
+
const { translate } = await resolveTranslations();
|
|
35
|
+
if (!auth || !auth.tenantId) {
|
|
36
|
+
throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
|
|
37
|
+
}
|
|
38
|
+
const requesterUserId = typeof auth.sub === "string" && auth.sub.trim().length > 0 && auth.sub || typeof auth.userId === "string" && auth.userId.trim().length > 0 && auth.userId || null;
|
|
39
|
+
if (!requesterUserId) {
|
|
40
|
+
throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
|
|
41
|
+
}
|
|
42
|
+
if (!auth.orgId) {
|
|
43
|
+
throw new CrudHttpError(400, {
|
|
44
|
+
error: translate("staff.errors.missingScope", "Missing tenant or organization scope.")
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
container,
|
|
49
|
+
em: container.resolve("em").fork(),
|
|
50
|
+
tenantId: auth.tenantId,
|
|
51
|
+
organizationId: auth.orgId,
|
|
52
|
+
requesterUserId,
|
|
53
|
+
requesterEmail: typeof auth.email === "string" && auth.email.trim().length > 0 ? auth.email : null,
|
|
54
|
+
translate
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function resolveDedupeCache(context) {
|
|
58
|
+
try {
|
|
59
|
+
const cache = context.container.resolve("cache");
|
|
60
|
+
if (typeof cache?.get !== "function" || typeof cache?.set !== "function") return null;
|
|
61
|
+
return cache;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function buildAccessRequestDedupeKey(input) {
|
|
67
|
+
const scope = input.timeProjectId ?? GENERAL_REQUEST_SCOPE;
|
|
68
|
+
return `staff:timesheets:access-request:${input.tenantId}:${input.organizationId}:${input.requesterUserId}:${scope}`;
|
|
69
|
+
}
|
|
70
|
+
async function resolveRequestedProject(context, timeProjectId) {
|
|
71
|
+
if (!timeProjectId) return null;
|
|
72
|
+
const scope = { tenantId: context.tenantId, organizationId: context.organizationId };
|
|
73
|
+
const project = await findOneWithDecryption(
|
|
74
|
+
context.em,
|
|
75
|
+
StaffTimeProject,
|
|
76
|
+
{ id: timeProjectId, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },
|
|
77
|
+
{},
|
|
78
|
+
scope
|
|
79
|
+
);
|
|
80
|
+
if (!project) return null;
|
|
81
|
+
return { id: project.id, name: project.name };
|
|
82
|
+
}
|
|
83
|
+
async function resolveRequesterName(context) {
|
|
84
|
+
const scope = { tenantId: context.tenantId, organizationId: context.organizationId };
|
|
85
|
+
const staffMember = await findOneWithDecryption(
|
|
86
|
+
context.em,
|
|
87
|
+
StaffTeamMember,
|
|
88
|
+
{
|
|
89
|
+
userId: context.requesterUserId,
|
|
90
|
+
tenantId: context.tenantId,
|
|
91
|
+
organizationId: context.organizationId,
|
|
92
|
+
deletedAt: null
|
|
93
|
+
},
|
|
94
|
+
{},
|
|
95
|
+
scope
|
|
96
|
+
);
|
|
97
|
+
const displayName = staffMember?.displayName;
|
|
98
|
+
if (typeof displayName === "string" && displayName.trim().length > 0) return displayName;
|
|
99
|
+
return context.requesterEmail ?? context.requesterUserId;
|
|
100
|
+
}
|
|
101
|
+
async function POST(req) {
|
|
102
|
+
try {
|
|
103
|
+
const context = await resolveAccessRequestContext(req);
|
|
104
|
+
const grantedFeatures = (await resolveFeatureAccess(context.container, context.requesterUserId, [VIEW_FEATURE], {
|
|
105
|
+
tenantId: context.tenantId,
|
|
106
|
+
organizationId: context.organizationId
|
|
107
|
+
})).grantedFeatures;
|
|
108
|
+
const interceptors = await runTimesheetInterceptors({
|
|
109
|
+
request: req,
|
|
110
|
+
method: "POST",
|
|
111
|
+
scope: {
|
|
112
|
+
container: context.container,
|
|
113
|
+
userId: context.requesterUserId,
|
|
114
|
+
tenantId: context.tenantId,
|
|
115
|
+
organizationId: context.organizationId,
|
|
116
|
+
userFeatures: grantedFeatures
|
|
117
|
+
},
|
|
118
|
+
body: await readJsonSafe(req, {})
|
|
119
|
+
});
|
|
120
|
+
if (!interceptors.ok) return interceptors.response;
|
|
121
|
+
const { session } = interceptors;
|
|
122
|
+
const parsed = accessRequestSchema.parse(session.body);
|
|
123
|
+
const guardResult = await runRouteMutationGuards({
|
|
124
|
+
container: context.container,
|
|
125
|
+
req,
|
|
126
|
+
auth: {
|
|
127
|
+
userId: context.requesterUserId,
|
|
128
|
+
tenantId: context.tenantId,
|
|
129
|
+
organizationId: context.organizationId,
|
|
130
|
+
userFeatures: grantedFeatures
|
|
131
|
+
},
|
|
132
|
+
input: {
|
|
133
|
+
resourceKind: RESOURCE_KIND,
|
|
134
|
+
resourceId: parsed.timeProjectId ?? GENERAL_REQUEST_SCOPE,
|
|
135
|
+
operation: "create",
|
|
136
|
+
mutationPayload: { timeProjectId: parsed.timeProjectId ?? null }
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (!guardResult.ok) return guardResult.response;
|
|
140
|
+
const effective = guardResult.modifiedPayload ? accessRequestSchema.parse({ ...parsed, ...guardResult.modifiedPayload }) : parsed;
|
|
141
|
+
const project = await resolveRequestedProject(context, effective.timeProjectId ?? null);
|
|
142
|
+
const dedupeKey = buildAccessRequestDedupeKey({
|
|
143
|
+
tenantId: context.tenantId,
|
|
144
|
+
organizationId: context.organizationId,
|
|
145
|
+
requesterUserId: context.requesterUserId,
|
|
146
|
+
timeProjectId: project?.id ?? null
|
|
147
|
+
});
|
|
148
|
+
const cache = resolveDedupeCache(context);
|
|
149
|
+
if (cache) {
|
|
150
|
+
const pending = await cache.get(dedupeKey).catch(() => null);
|
|
151
|
+
if (pending != null) {
|
|
152
|
+
await guardResult.runAfterSuccess();
|
|
153
|
+
return session.respond(200, { ok: true, deduplicated: true });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const requesterName = await resolveRequesterName(context);
|
|
157
|
+
await emitStaffEvent("staff.timesheets.project_access.requested", {
|
|
158
|
+
tenantId: context.tenantId,
|
|
159
|
+
organizationId: context.organizationId,
|
|
160
|
+
requesterUserId: context.requesterUserId,
|
|
161
|
+
requesterName,
|
|
162
|
+
timeProjectId: project?.id ?? null,
|
|
163
|
+
timeProjectName: project?.name ?? null,
|
|
164
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
165
|
+
});
|
|
166
|
+
if (cache) {
|
|
167
|
+
await cache.set(dedupeKey, { requestedAt: Date.now() }, { ttl: ACCESS_REQUEST_DEDUPE_WINDOW_MS }).catch((err) => {
|
|
168
|
+
logger.warn("Failed to record the access-request dedupe window", { err });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
await guardResult.runAfterSuccess();
|
|
172
|
+
return session.respond(200, { ok: true, deduplicated: false });
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return errorResponse(err, "staff.timesheets.access-requests.POST failed");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function errorResponse(err, message) {
|
|
178
|
+
if (isCrudHttpError(err)) {
|
|
179
|
+
return NextResponse.json(err.body, { status: err.status });
|
|
180
|
+
}
|
|
181
|
+
const { translate } = await resolveTranslations();
|
|
182
|
+
if (err instanceof z.ZodError) {
|
|
183
|
+
return NextResponse.json(
|
|
184
|
+
{ error: translate("staff.errors.invalid_request", "Invalid request"), details: err.issues },
|
|
185
|
+
{ status: 400 }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
logger.error(message, { err });
|
|
189
|
+
return NextResponse.json(
|
|
190
|
+
{ error: translate("staff.errors.internal", "Internal server error") },
|
|
191
|
+
{ status: 500 }
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const postDoc = {
|
|
195
|
+
summary: "Request time tracking project access",
|
|
196
|
+
description: "Notifies every holder of staff.timesheets.projects.manage in the caller scope that a team member asked for project access. Omitting timeProjectId sends a general request from the no-assignments empty state.",
|
|
197
|
+
tags: ["Staff"],
|
|
198
|
+
requestBody: { schema: accessRequestSchema },
|
|
199
|
+
responses: [
|
|
200
|
+
{
|
|
201
|
+
status: 200,
|
|
202
|
+
description: "Access request accepted (deduplicated when an identical request is still pending)",
|
|
203
|
+
schema: accessRequestResponseSchema
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
errors: [
|
|
207
|
+
{ status: 400, description: "Invalid request body or missing organization scope" },
|
|
208
|
+
{ status: 401, description: "Unauthorized" },
|
|
209
|
+
{ status: 403, description: "Missing staff.timesheets.view" }
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
const openApi = {
|
|
213
|
+
tag: "Staff",
|
|
214
|
+
summary: "Time tracking project access requests",
|
|
215
|
+
methods: {
|
|
216
|
+
POST: postDoc
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
export {
|
|
220
|
+
ACCESS_REQUEST_DEDUPE_WINDOW_MS,
|
|
221
|
+
POST,
|
|
222
|
+
buildAccessRequestDedupeKey,
|
|
223
|
+
metadata,
|
|
224
|
+
openApi
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/staff/api/timesheets/access-requests/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { runRouteMutationGuards } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTeamMember, StaffTimeProject } from '../../../data/entities'\nimport { emitStaffEvent } from '../../../events'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { STAFF_TIME_TRACKING_RESOURCE_KINDS } from '../../guards'\nimport { runTimesheetInterceptors } from '../_shared/withTimesheetInterceptors'\nimport { resolveFeatureAccess } from '../../../lib/time-tracking/featureAccess'\n\nconst logger = createLogger('staff').child({ component: 'api/timesheets/access-requests' })\n\nconst VIEW_FEATURE = 'staff.timesheets.view'\nconst RESOURCE_KIND = STAFF_TIME_TRACKING_RESOURCE_KINDS.accessRequest\n\n/**\n * Spec D-6: one pending request per user per project (or per user for the\n * general, project-less request) per 24h. The window is held in the shared\n * cache rather than a table \u2014 the notification itself carries a `groupKey`, so\n * even if the cache is cold the manager still sees a single refreshed\n * notification instead of a duplicate row.\n */\nexport const ACCESS_REQUEST_DEDUPE_WINDOW_MS = 24 * 60 * 60 * 1000\n\nconst GENERAL_REQUEST_SCOPE = 'general'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n}\n\nconst accessRequestSchema = z.object({\n timeProjectId: z.string().uuid().nullish(),\n})\n\nconst accessRequestResponseSchema = z.object({\n ok: z.literal(true),\n deduplicated: z.boolean(),\n})\n\ntype DedupeCacheLike = {\n get: (key: string) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number }) => Promise<void>\n}\n\ntype AccessRequestContext = {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n em: EntityManager\n tenantId: string\n organizationId: string\n requesterUserId: string\n requesterEmail: string | null\n translate: (key: string, fallback?: string) => string\n}\n\nasync function resolveAccessRequestContext(req: Request): Promise<AccessRequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n const requesterUserId =\n (typeof auth.sub === 'string' && auth.sub.trim().length > 0 && auth.sub) ||\n (typeof auth.userId === 'string' && auth.userId.trim().length > 0 && auth.userId) ||\n null\n if (!requesterUserId) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n if (!auth.orgId) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n return {\n container,\n em: (container.resolve('em') as EntityManager).fork(),\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n requesterUserId,\n requesterEmail: typeof auth.email === 'string' && auth.email.trim().length > 0 ? auth.email : null,\n translate,\n }\n}\n\nfunction resolveDedupeCache(context: AccessRequestContext): DedupeCacheLike | null {\n try {\n const cache = context.container.resolve('cache') as Partial<DedupeCacheLike> | undefined\n if (typeof cache?.get !== 'function' || typeof cache?.set !== 'function') return null\n return cache as DedupeCacheLike\n } catch {\n return null\n }\n}\n\nexport function buildAccessRequestDedupeKey(input: {\n tenantId: string\n organizationId: string\n requesterUserId: string\n timeProjectId: string | null\n}): string {\n const scope = input.timeProjectId ?? GENERAL_REQUEST_SCOPE\n return `staff:timesheets:access-request:${input.tenantId}:${input.organizationId}:${input.requesterUserId}:${scope}`\n}\n\n/**\n * Resolves the requested project inside the caller's scope. A project the\n * caller's tenant/organization does not own resolves to `null` and the request\n * degrades to a general one \u2014 the response never differentiates, so the route\n * cannot be used to probe which project ids exist (screen 17 note 1).\n */\nasync function resolveRequestedProject(\n context: AccessRequestContext,\n timeProjectId: string | null,\n): Promise<{ id: string; name: string } | null> {\n if (!timeProjectId) return null\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const project = await findOneWithDecryption(\n context.em,\n StaffTimeProject,\n { id: timeProjectId, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n scope,\n )\n if (!project) return null\n return { id: project.id, name: project.name }\n}\n\nasync function resolveRequesterName(context: AccessRequestContext): Promise<string> {\n const scope = { tenantId: context.tenantId, organizationId: context.organizationId }\n const staffMember = await findOneWithDecryption(\n context.em,\n StaffTeamMember,\n {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n },\n {},\n scope,\n )\n const displayName = staffMember?.displayName\n if (typeof displayName === 'string' && displayName.trim().length > 0) return displayName\n return context.requesterEmail ?? context.requesterUserId\n}\n\nasync function POST(req: Request) {\n try {\n const context = await resolveAccessRequestContext(req)\n // The grant list the interceptors and the mutation-guard registry gate on\n // comes from the module's single RBAC authority, so it is always an array:\n // the nullable read it replaces could not say whether an empty answer meant\n // \"no grants\" or \"could not ask\". Authorization itself stays with the\n // declarative `requireFeatures` guard in `metadata`.\n const grantedFeatures = (\n await resolveFeatureAccess(context.container, context.requesterUserId, [VIEW_FEATURE], {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n ).grantedFeatures\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'POST',\n scope: {\n container: context.container,\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n body: await readJsonSafe<Record<string, unknown>>(req, {}),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const parsed = accessRequestSchema.parse(session.body)\n\n const guardResult = await runRouteMutationGuards({\n container: context.container,\n req,\n auth: {\n userId: context.requesterUserId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: grantedFeatures,\n },\n input: {\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.timeProjectId ?? GENERAL_REQUEST_SCOPE,\n operation: 'create',\n mutationPayload: { timeProjectId: parsed.timeProjectId ?? null },\n },\n })\n if (!guardResult.ok) return guardResult.response\n\n const effective = guardResult.modifiedPayload\n ? accessRequestSchema.parse({ ...parsed, ...guardResult.modifiedPayload })\n : parsed\n\n const project = await resolveRequestedProject(context, effective.timeProjectId ?? null)\n const dedupeKey = buildAccessRequestDedupeKey({\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n timeProjectId: project?.id ?? null,\n })\n\n const cache = resolveDedupeCache(context)\n if (cache) {\n const pending = await cache.get(dedupeKey).catch(() => null)\n if (pending != null) {\n await guardResult.runAfterSuccess()\n return session.respond(200, { ok: true, deduplicated: true })\n }\n }\n\n const requesterName = await resolveRequesterName(context)\n await emitStaffEvent('staff.timesheets.project_access.requested', {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n requesterUserId: context.requesterUserId,\n requesterName,\n timeProjectId: project?.id ?? null,\n timeProjectName: project?.name ?? null,\n requestedAt: new Date().toISOString(),\n })\n\n if (cache) {\n await cache\n .set(dedupeKey, { requestedAt: Date.now() }, { ttl: ACCESS_REQUEST_DEDUPE_WINDOW_MS })\n .catch((err: unknown) => {\n logger.warn('Failed to record the access-request dedupe window', { err })\n })\n }\n\n await guardResult.runAfterSuccess()\n\n return session.respond(200, { ok: true, deduplicated: false })\n } catch (err) {\n return errorResponse(err, 'staff.timesheets.access-requests.POST failed')\n }\n}\n\nasync function errorResponse(err: unknown, message: string) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: translate('staff.errors.invalid_request', 'Invalid request'), details: err.issues },\n { status: 400 },\n )\n }\n logger.error(message, { err })\n return NextResponse.json(\n { error: translate('staff.errors.internal', 'Internal server error') },\n { status: 500 },\n )\n}\n\nconst postDoc: OpenApiMethodDoc = {\n summary: 'Request time tracking project access',\n description:\n 'Notifies every holder of staff.timesheets.projects.manage in the caller scope that a team member asked for project access. Omitting timeProjectId sends a general request from the no-assignments empty state.',\n tags: ['Staff'],\n requestBody: { schema: accessRequestSchema },\n responses: [\n {\n status: 200,\n description: 'Access request accepted (deduplicated when an identical request is still pending)',\n schema: accessRequestResponseSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Invalid request body or missing organization scope' },\n { status: 401, description: 'Unauthorized' },\n { status: 403, description: 'Missing staff.timesheets.view' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Time tracking project access requests',\n methods: {\n POST: postDoc,\n },\n}\n\nexport { POST }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -10,11 +10,12 @@ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
|
10
10
|
import { StaffTimeProjectMember, StaffTeamMember } from "../../../../data/entities.js";
|
|
11
11
|
import { staffMyProjectVisibilityUpdateSchema } from "../../../../data/validators.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
STAFF_TIME_TRACKING_RESOURCE_KINDS,
|
|
14
14
|
runStaffMutationGuardAfterSuccess,
|
|
15
15
|
runStaffMutationGuards
|
|
16
16
|
} from "../../../guards.js";
|
|
17
17
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
18
|
+
import { runTimesheetInterceptors } from "../../_shared/withTimesheetInterceptors.js";
|
|
18
19
|
const logger = createLogger("staff");
|
|
19
20
|
const metadata = {
|
|
20
21
|
PATCH: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] }
|
|
@@ -50,8 +51,15 @@ async function PATCH(req) {
|
|
|
50
51
|
error: translate("staff.errors.missingScope", "Missing tenant or organization scope.")
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
54
|
+
const interceptors = await runTimesheetInterceptors({
|
|
55
|
+
request: req,
|
|
56
|
+
method: "PATCH",
|
|
57
|
+
scope: { container, userId: auth.sub, tenantId, organizationId },
|
|
58
|
+
body: await readJsonSafe(req, {})
|
|
59
|
+
});
|
|
60
|
+
if (!interceptors.ok) return interceptors.response;
|
|
61
|
+
const { session } = interceptors;
|
|
62
|
+
const parsed = staffMyProjectVisibilityUpdateSchema.safeParse(session.body);
|
|
55
63
|
if (!parsed.success) {
|
|
56
64
|
throw new CrudHttpError(400, {
|
|
57
65
|
error: translate("staff.timesheets.errors.invalidBody", "Invalid request body."),
|
|
@@ -97,14 +105,13 @@ async function PATCH(req) {
|
|
|
97
105
|
tenantId,
|
|
98
106
|
organizationId,
|
|
99
107
|
userId: auth.sub ?? "",
|
|
100
|
-
resourceKind:
|
|
108
|
+
resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeProjectMember,
|
|
101
109
|
resourceId: membership.id,
|
|
102
110
|
operation: "update",
|
|
103
111
|
requestMethod: req.method,
|
|
104
112
|
requestHeaders: req.headers,
|
|
105
113
|
mutationPayload: parsed.data
|
|
106
|
-
}
|
|
107
|
-
resolveUserFeatures(auth)
|
|
114
|
+
}
|
|
108
115
|
);
|
|
109
116
|
if (!guardResult.ok) {
|
|
110
117
|
return NextResponse.json(
|
|
@@ -119,14 +126,14 @@ async function PATCH(req) {
|
|
|
119
126
|
tenantId,
|
|
120
127
|
organizationId,
|
|
121
128
|
userId: auth.sub ?? "",
|
|
122
|
-
resourceKind:
|
|
129
|
+
resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeProjectMember,
|
|
123
130
|
resourceId: membership.id,
|
|
124
131
|
operation: "update",
|
|
125
132
|
requestMethod: req.method,
|
|
126
133
|
requestHeaders: req.headers
|
|
127
134
|
});
|
|
128
135
|
}
|
|
129
|
-
return
|
|
136
|
+
return session.respond(200, { ok: true, showInGrid: membership.showInGrid });
|
|
130
137
|
} catch (err) {
|
|
131
138
|
if (err instanceof CrudHttpError) {
|
|
132
139
|
return NextResponse.json(err.body, { status: err.status });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/staff/api/timesheets/my-projects/%5BprojectId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProjectMember, StaffTeamMember } from '../../../../data/entities'\nimport { staffMyProjectVisibilityUpdateSchema } from '../../../../data/validators'\nimport {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAG7B,SAAS,wBAAwB,uBAAuB;AACxD,SAAS,4CAA4C;AACrD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProjectMember, StaffTeamMember } from '../../../../data/entities'\nimport { staffMyProjectVisibilityUpdateSchema } from '../../../../data/validators'\nimport {\n STAFF_TIME_TRACKING_RESOURCE_KINDS,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { runTimesheetInterceptors } from '../../_shared/withTimesheetInterceptors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nfunction extractProjectIdFromUrl(req: Request): string | null {\n try {\n const url = new URL(req.url)\n const match = url.pathname.match(/\\/my-projects\\/([^/]+)(?:\\/|$)/)\n return match?.[1] ?? null\n } catch {\n return null\n }\n}\n\n/**\n * PATCH /api/staff/timesheets/my-projects/{projectId}\n *\n * Self-service endpoint for the authenticated user to toggle visibility of a\n * time project on their own My Timesheets grid. Does not require the admin-only\n * `staff.timesheets.projects.manage` feature \u2014 only `staff.timesheets.manage_own`.\n *\n * Added 2026-04-13 to close a gap in the original UX enhancements spec:\n * \"+ Add row\" and the new X remove button need to persist per-user grid membership.\n */\nexport async function PATCH(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) {\n throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n }\n\n const projectId = extractProjectIdFromUrl(req)\n if (!projectId || !z.string().uuid().safeParse(projectId).success) {\n throw new CrudHttpError(400, {\n error: translate('staff.timesheets.errors.invalidProjectId', 'Invalid project id.'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'PATCH',\n scope: { container, userId: auth.sub, tenantId, organizationId },\n body: await readJsonSafe<Record<string, unknown>>(req, {}),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const parsed = staffMyProjectVisibilityUpdateSchema.safeParse(session.body)\n if (!parsed.success) {\n throw new CrudHttpError(400, {\n error: translate('staff.timesheets.errors.invalidBody', 'Invalid request body.'),\n details: parsed.error.flatten(),\n })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, {\n error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.'),\n })\n }\n\n const membership = await findOneWithDecryption(\n em,\n StaffTimeProjectMember,\n {\n timeProjectId: projectId,\n staffMemberId: staffMember.id,\n tenantId,\n organizationId,\n deletedAt: null,\n status: 'active',\n },\n {},\n scopeCtx,\n )\n if (!membership) {\n throw new CrudHttpError(404, {\n error: translate('staff.timesheets.errors.notAssigned', 'You are not assigned to this project.'),\n })\n }\n\n const guardResult = await runStaffMutationGuards(\n container,\n {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeProjectMember,\n resourceId: membership.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n },\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n membership.showInGrid = parsed.data.showInGrid\n await em.flush()\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeProjectMember,\n resourceId: membership.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return session.respond(200, { ok: true, showInGrid: membership.showInGrid })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('staff.timesheets.my-projects.patch failed', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.updateMyProject', 'Failed to update project visibility.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'My project grid visibility',\n methods: {\n PATCH: {\n summary: 'Toggle grid visibility for one of the caller\\'s assigned time projects',\n description:\n 'Self-service endpoint. Only the authenticated user can toggle `show_in_grid` on their own active membership for the given project. Does not require the admin-only `staff.timesheets.projects.manage` feature.',\n requestBody: {\n contentType: 'application/json',\n schema: staffMyProjectVisibilityUpdateSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Visibility updated',\n schema: z.object({ ok: z.boolean(), showInGrid: z.boolean() }),\n },\n { status: 400, description: 'Invalid input', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'No staff member linked', schema: z.object({ error: z.string() }) },\n { status: 404, description: 'Project membership not found', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,10 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
|
8
8
|
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
9
9
|
import { StaffTimeProjectMember, StaffTeamMember } from "../../../data/entities.js";
|
|
10
10
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
11
|
+
import {
|
|
12
|
+
readSearchParamsRecord,
|
|
13
|
+
runTimesheetInterceptors
|
|
14
|
+
} from "../_shared/withTimesheetInterceptors.js";
|
|
11
15
|
const logger = createLogger("staff");
|
|
12
16
|
const metadata = {
|
|
13
17
|
GET: { requireAuth: true, requireFeatures: ["staff.timesheets.view"] }
|
|
@@ -24,6 +28,14 @@ async function GET(req) {
|
|
|
24
28
|
if (!tenantId || !organizationId) {
|
|
25
29
|
throw new CrudHttpError(400, { error: translate("staff.errors.missingScope", "Missing tenant or organization scope.") });
|
|
26
30
|
}
|
|
31
|
+
const interceptors = await runTimesheetInterceptors({
|
|
32
|
+
request: req,
|
|
33
|
+
method: "GET",
|
|
34
|
+
scope: { container, userId: auth.sub, tenantId, organizationId },
|
|
35
|
+
query: readSearchParamsRecord(req.url)
|
|
36
|
+
});
|
|
37
|
+
if (!interceptors.ok) return interceptors.response;
|
|
38
|
+
const { session } = interceptors;
|
|
27
39
|
const em = container.resolve("em").fork();
|
|
28
40
|
const scopeCtx = { tenantId, organizationId };
|
|
29
41
|
const staffMember = await findOneWithDecryption(
|
|
@@ -53,7 +65,7 @@ async function GET(req) {
|
|
|
53
65
|
assigned_end_date: assignment.assignedEndDate ?? null,
|
|
54
66
|
show_in_grid: assignment.showInGrid ?? false
|
|
55
67
|
}));
|
|
56
|
-
return
|
|
68
|
+
return session.respond(200, { items, total: items.length });
|
|
57
69
|
} catch (err) {
|
|
58
70
|
if (err instanceof CrudHttpError) {
|
|
59
71
|
return NextResponse.json(err.body, { status: err.status });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/timesheets/my-projects/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProjectMember, StaffTeamMember } from '../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.timesheets.view'] },\n}\n\n/**\n * GET /api/staff/timesheets/my-projects\n *\n * Spec N+1 Mitigation \u2014 Query 1:\n * Returns staff_time_project_members for the authenticated staff member.\n * Used by \"My Timesheets\" grid to resolve assigned project IDs.\n */\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n\n const assignments = await findWithDecryption(\n em,\n StaffTimeProjectMember,\n { staffMemberId: staffMember.id, tenantId, organizationId, deletedAt: null, status: 'active' },\n {},\n scopeCtx,\n )\n\n const items = assignments.map((assignment) => ({\n id: assignment.id,\n time_project_id: assignment.timeProjectId,\n staff_member_id: assignment.staffMemberId,\n role: assignment.role ?? null,\n status: assignment.status ?? null,\n assigned_start_date: assignment.assignedStartDate ?? null,\n assigned_end_date: assignment.assignedEndDate ?? null,\n show_in_grid: assignment.showInGrid ?? false,\n }))\n\n return
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,oBAAoB,6BAA6B;AAG1D,SAAS,wBAAwB,uBAAuB;AACxD,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProjectMember, StaffTeamMember } from '../../../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n readSearchParamsRecord,\n runTimesheetInterceptors,\n} from '../_shared/withTimesheetInterceptors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.timesheets.view'] },\n}\n\n/**\n * GET /api/staff/timesheets/my-projects\n *\n * Spec N+1 Mitigation \u2014 Query 1:\n * Returns staff_time_project_members for the authenticated staff member.\n * Used by \"My Timesheets\" grid to resolve assigned project IDs.\n */\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'GET',\n scope: { container, userId: auth.sub, tenantId, organizationId },\n query: readSearchParamsRecord(req.url),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n\n const assignments = await findWithDecryption(\n em,\n StaffTimeProjectMember,\n { staffMemberId: staffMember.id, tenantId, organizationId, deletedAt: null, status: 'active' },\n {},\n scopeCtx,\n )\n\n const items = assignments.map((assignment) => ({\n id: assignment.id,\n time_project_id: assignment.timeProjectId,\n staff_member_id: assignment.staffMemberId,\n role: assignment.role ?? null,\n status: assignment.status ?? null,\n assigned_start_date: assignment.assignedStartDate ?? null,\n assigned_end_date: assignment.assignedEndDate ?? null,\n show_in_grid: assignment.showInGrid ?? false,\n }))\n\n return session.respond(200, { items, total: items.length })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('staff.timesheets.my-projects failed', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.myProjects', 'Failed to load your projects.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'My assigned time projects',\n methods: {\n GET: {\n summary: 'List assigned time project memberships for the current user',\n description: 'Returns staff_time_project_members where the authenticated user is an active member. Used by the My Timesheets grid to resolve assigned project IDs (spec N+1 mitigation query 1).',\n responses: [\n {\n status: 200,\n description: 'Assigned project memberships',\n schema: z.object({\n items: z.array(z.object({\n id: z.string().uuid(),\n time_project_id: z.string().uuid(),\n staff_member_id: z.string().uuid(),\n role: z.string().nullable(),\n status: z.string().nullable(),\n assigned_start_date: z.string().nullable(),\n assigned_end_date: z.string().nullable(),\n show_in_grid: z.boolean(),\n })),\n total: z.number(),\n }),\n },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'No staff member linked', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { countWorkingDays } from "../../../lib/time-tracking-ui/timesheetPeriod.js";
|
|
2
|
+
function buildMyWorkKpis(totals, ranges, dailyHours) {
|
|
3
|
+
const hasTarget = typeof dailyHours === "number" && Number.isFinite(dailyHours) && dailyHours > 0;
|
|
4
|
+
const weekWorkingDays = countWorkingDays(ranges.week);
|
|
5
|
+
const monthWorkingDays = countWorkingDays(ranges.month);
|
|
6
|
+
const dailyTargetMinutes = hasTarget ? Math.round(dailyHours * 60) : null;
|
|
7
|
+
return {
|
|
8
|
+
...totals,
|
|
9
|
+
dailyTargetMinutes,
|
|
10
|
+
weekTargetMinutes: dailyTargetMinutes === null ? null : dailyTargetMinutes * weekWorkingDays,
|
|
11
|
+
monthTargetMinutes: dailyTargetMinutes === null ? null : dailyTargetMinutes * monthWorkingDays,
|
|
12
|
+
weekWorkingDays,
|
|
13
|
+
monthWorkingDays,
|
|
14
|
+
nonBillableSharePercent: totals.monthMinutes > 0 ? Math.round(totals.monthNonBillableMinutes / totals.monthMinutes * 1e3) / 10 : null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function toMinutes(value) {
|
|
18
|
+
if (value === null || value === void 0) return 0;
|
|
19
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
20
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
21
|
+
}
|
|
22
|
+
function pickRecentTaskIds(entries, limit) {
|
|
23
|
+
const ordered = [...entries].sort((left, right) => {
|
|
24
|
+
if (left.date === right.date) return right.id.localeCompare(left.id);
|
|
25
|
+
return right.date.localeCompare(left.date);
|
|
26
|
+
});
|
|
27
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28
|
+
const picked = [];
|
|
29
|
+
for (const entry of ordered) {
|
|
30
|
+
if (!entry.taskId || seen.has(entry.taskId)) continue;
|
|
31
|
+
seen.add(entry.taskId);
|
|
32
|
+
picked.push(entry.taskId);
|
|
33
|
+
if (picked.length >= limit) break;
|
|
34
|
+
}
|
|
35
|
+
return picked;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
buildMyWorkKpis,
|
|
39
|
+
pickRecentTaskIds,
|
|
40
|
+
toMinutes
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=myWorkAggregate.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * The arithmetic behind screen 1 (`GET /api/staff/timesheets/my-work`), kept out\n * of the route so the numbers a consultant reads first thing in the morning can\n * be tested without a database.\n *\n * Three rules it encodes:\n *\n * 1. **Targets scale with working days, not calendar days.** \"cel 8:00\" is one\n * day; \"z 40:00\" is the week's Mon\u2013Fri days times the daily target; the month\n * KPI's context is its working-day count. A tenant with no `targets.dailyHours`\n * gets `null` everywhere rather than a zero to be under.\n * 2. **The non-billable KPI is a share of the month, and a share of nothing is\n * `null`** \u2014 not `0%`, which would read as \"all your time is billable\".\n * 3. **Money never crosses currencies.** The payload carries no total cost at\n * all: budget burn is per project, in that project's own terms, and the rate\n * is only present for a caller who may see rates.\n */\n\nimport { countWorkingDays, type TimesheetDateRange } from '../../../lib/time-tracking-ui/timesheetPeriod'\n\nexport type MyWorkTotals = {\n todayMinutes: number\n weekMinutes: number\n monthMinutes: number\n monthNonBillableMinutes: number\n}\n\nexport type MyWorkKpis = MyWorkTotals & {\n dailyTargetMinutes: number | null\n weekTargetMinutes: number | null\n monthTargetMinutes: number | null\n weekWorkingDays: number\n monthWorkingDays: number\n /** Percent of the month that is non-billable, rounded to one decimal; `null` when nothing is logged. */\n nonBillableSharePercent: number | null\n}\n\nexport function buildMyWorkKpis(\n totals: MyWorkTotals,\n ranges: { week: TimesheetDateRange; month: TimesheetDateRange },\n dailyHours: number | null,\n): MyWorkKpis {\n const hasTarget = typeof dailyHours === 'number' && Number.isFinite(dailyHours) && dailyHours > 0\n const weekWorkingDays = countWorkingDays(ranges.week)\n const monthWorkingDays = countWorkingDays(ranges.month)\n const dailyTargetMinutes = hasTarget ? Math.round(dailyHours * 60) : null\n return {\n ...totals,\n dailyTargetMinutes,\n weekTargetMinutes: dailyTargetMinutes === null ? null : dailyTargetMinutes * weekWorkingDays,\n monthTargetMinutes: dailyTargetMinutes === null ? null : dailyTargetMinutes * monthWorkingDays,\n weekWorkingDays,\n monthWorkingDays,\n nonBillableSharePercent:\n totals.monthMinutes > 0\n ? Math.round((totals.monthNonBillableMinutes / totals.monthMinutes) * 1000) / 10\n : null,\n }\n}\n\nexport function toMinutes(value: string | number | null | undefined): number {\n if (value === null || value === undefined) return 0\n const parsed = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\n/**\n * The most recently worked tasks, newest first, de-duplicated. Screen 1's\n * \"Ostatnie zadania\" is a shortcut back into work already started, so it is\n * derived from the caller's own entries rather than from task assignment: a task\n * assigned but never touched is not something to resume.\n */\nexport function pickRecentTaskIds(\n entries: readonly { taskId: string | null; date: string; id: string }[],\n limit: number,\n): string[] {\n const ordered = [...entries].sort((left, right) => {\n if (left.date === right.date) return right.id.localeCompare(left.id)\n return right.date.localeCompare(left.date)\n })\n const seen = new Set<string>()\n const picked: string[] = []\n for (const entry of ordered) {\n if (!entry.taskId || seen.has(entry.taskId)) continue\n seen.add(entry.taskId)\n picked.push(entry.taskId)\n if (picked.length >= limit) break\n }\n return picked\n}\n"],
|
|
5
|
+
"mappings": "AAkBA,SAAS,wBAAiD;AAmBnD,SAAS,gBACd,QACA,QACA,YACY;AACZ,QAAM,YAAY,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,KAAK,aAAa;AAChG,QAAM,kBAAkB,iBAAiB,OAAO,IAAI;AACpD,QAAM,mBAAmB,iBAAiB,OAAO,KAAK;AACtD,QAAM,qBAAqB,YAAY,KAAK,MAAM,aAAa,EAAE,IAAI;AACrE,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,mBAAmB,uBAAuB,OAAO,OAAO,qBAAqB;AAAA,IAC7E,oBAAoB,uBAAuB,OAAO,OAAO,qBAAqB;AAAA,IAC9E;AAAA,IACA;AAAA,IACA,yBACE,OAAO,eAAe,IAClB,KAAK,MAAO,OAAO,0BAA0B,OAAO,eAAgB,GAAI,IAAI,KAC5E;AAAA,EACR;AACF;AAEO,SAAS,UAAU,OAAmD;AAC3E,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,QAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAC/D,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAQO,SAAS,kBACd,SACA,OACU;AACV,QAAM,UAAU,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,MAAM,UAAU;AACjD,QAAI,KAAK,SAAS,MAAM,KAAM,QAAO,MAAM,GAAG,cAAc,KAAK,EAAE;AACnE,WAAO,MAAM,KAAK,cAAc,KAAK,IAAI;AAAA,EAC3C,CAAC;AACD,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAmB,CAAC;AAC1B,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,UAAU,KAAK,IAAI,MAAM,MAAM,EAAG;AAC7C,SAAK,IAAI,MAAM,MAAM;AACrB,WAAO,KAAK,MAAM,MAAM;AACxB,QAAI,OAAO,UAAU,MAAO;AAAA,EAC9B;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|