@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7179.1.e8c03264b6
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/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/staff/AGENTS.md +769 -1
- package/src/modules/staff/acl.ts +9 -0
- package/src/modules/staff/ai-agents.ts +189 -0
- package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
- package/src/modules/staff/ai-tools/types.ts +82 -0
- package/src/modules/staff/ai-tools.ts +13 -0
- package/src/modules/staff/analytics.ts +113 -0
- package/src/modules/staff/api/guards.ts +85 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
- package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
- package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
- package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
- package/src/modules/staff/api/team-members/self/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
- package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
- package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
- package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
- package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
- package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
- package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
- package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
- package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
- package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
- package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
- package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
- package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
- package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
- package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
- package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
- package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
- package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
- package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
- package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
- package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
- package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
- package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
- package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
- package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
- package/src/modules/staff/ce.ts +63 -0
- package/src/modules/staff/cli.ts +207 -1
- package/src/modules/staff/commands/index.ts +5 -0
- package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
- package/src/modules/staff/commands/timesheets-projects.ts +593 -7
- package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
- package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
- package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
- package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
- package/src/modules/staff/data/enrichers.ts +675 -10
- package/src/modules/staff/data/entities.ts +494 -2
- package/src/modules/staff/data/extensions.ts +68 -0
- package/src/modules/staff/data/validators.ts +348 -3
- package/src/modules/staff/di.ts +96 -0
- package/src/modules/staff/events.payloads.ts +268 -0
- package/src/modules/staff/events.ts +87 -5
- package/src/modules/staff/extension-points.ts +308 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
- package/src/modules/staff/i18n/de.json +899 -0
- package/src/modules/staff/i18n/en.json +899 -0
- package/src/modules/staff/i18n/es.json +899 -0
- package/src/modules/staff/i18n/ko.json +899 -0
- package/src/modules/staff/i18n/pl.json +899 -0
- package/src/modules/staff/lib/crud.ts +14 -3
- package/src/modules/staff/lib/time-tracking/access.ts +155 -0
- package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
- package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
- package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
- package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
- package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
- package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
- package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
- package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
- package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
- package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
- package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
- package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
- package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
- package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
- package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
- package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
- package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
- package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
- package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
- package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
- package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
- package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
- package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
- package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
- package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
- package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
- package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
- package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
- package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
- package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
- package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
- package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
- package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
- package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
- package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
- package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
- package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
- package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
- package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
- package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
- package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
- package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
- package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
- package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
- package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
- package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
- package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
- package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
- package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
- package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
- package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
- package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
- package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
- package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
- package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
- package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
- package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
- package/src/modules/staff/notifications.client.ts +16 -0
- package/src/modules/staff/notifications.handlers.ts +88 -0
- package/src/modules/staff/notifications.ts +163 -0
- package/src/modules/staff/search.ts +191 -1
- package/src/modules/staff/setup.ts +22 -0
- package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
- package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
- package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
- package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
- package/src/modules/staff/translations.ts +17 -0
- package/src/modules/staff/widgets/components.ts +34 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
- package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
- package/src/modules/staff/widgets/notifications/index.ts +1 -0
- package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
|
@@ -2,17 +2,50 @@ import {
|
|
|
2
2
|
bridgeLegacyGuard,
|
|
3
3
|
runMutationGuards
|
|
4
4
|
} from "@open-mercato/shared/lib/crud/mutation-guard-registry";
|
|
5
|
+
import { getAllMutationGuardInstances } from "@open-mercato/shared/lib/crud/mutation-guard-store";
|
|
6
|
+
import { resolveFeatureAccess } from "../lib/time-tracking/featureAccess.js";
|
|
5
7
|
function resolveUserFeatures(auth) {
|
|
6
8
|
const features = auth?.features;
|
|
7
9
|
if (!Array.isArray(features)) return [];
|
|
8
10
|
return features.filter((value) => typeof value === "string");
|
|
9
11
|
}
|
|
10
|
-
|
|
12
|
+
const STAFF_TIME_TRACKING_RESOURCE_KINDS = {
|
|
13
|
+
/** `time-entries/{bulk,copy-day,start-timer}`, `time-entries/[id]/{duplicate,timer-start,timer-stop}` */
|
|
14
|
+
timeEntry: "staff.timesheets.time_entry",
|
|
15
|
+
/** `time-entries/[id]/segments`, `time-entries/[id]/segments/[segmentId]` */
|
|
16
|
+
timeEntrySegment: "staff.timesheets.time_entry_segment",
|
|
17
|
+
/** `tasks/[id]/status` */
|
|
18
|
+
timeTask: "staff.timesheets.time_task",
|
|
19
|
+
/** `tasks/[id]/comments` */
|
|
20
|
+
timeTaskComment: "staff.timesheets.task_comment",
|
|
21
|
+
/** `time-projects/[id]/change-currency` */
|
|
22
|
+
timeProject: "staff.timesheets.time_project",
|
|
23
|
+
/** `my-projects/[projectId]` */
|
|
24
|
+
timeProjectMember: "staff.timesheets.time_project_member",
|
|
25
|
+
/** `reports/[id]/close`, `reports/[id]/unlock` */
|
|
26
|
+
timeReport: "staff.timesheets.time_report",
|
|
27
|
+
/** `tags/entry-assignments` */
|
|
28
|
+
entryTag: "staff.timesheets.entry_tag",
|
|
29
|
+
/** `tags/task-assignments` */
|
|
30
|
+
taskTag: "staff.timesheets.task_tag",
|
|
31
|
+
/** `settings`, `settings/reapply-rounding` */
|
|
32
|
+
settings: "staff.timesheets.settings",
|
|
33
|
+
/** `access-requests` */
|
|
34
|
+
accessRequest: "staff.timesheets.access_request"
|
|
35
|
+
};
|
|
36
|
+
async function runStaffMutationGuards(container, input, extraFeatures = []) {
|
|
37
|
+
const allGuards = [...getAllMutationGuardInstances()];
|
|
11
38
|
const legacyGuard = bridgeLegacyGuard(container);
|
|
12
|
-
if (
|
|
39
|
+
if (legacyGuard) allGuards.push(legacyGuard);
|
|
40
|
+
if (!allGuards.length) {
|
|
13
41
|
return { ok: true, afterSuccessCallbacks: [] };
|
|
14
42
|
}
|
|
15
|
-
|
|
43
|
+
const { grantedFeatures } = await resolveFeatureAccess(container, input.userId ?? null, [], {
|
|
44
|
+
tenantId: input.tenantId ?? null,
|
|
45
|
+
organizationId: input.organizationId ?? null
|
|
46
|
+
});
|
|
47
|
+
const userFeatures = Array.from(/* @__PURE__ */ new Set([...grantedFeatures, ...extraFeatures]));
|
|
48
|
+
return runMutationGuards(allGuards, input, { userFeatures });
|
|
16
49
|
}
|
|
17
50
|
async function runStaffMutationGuardAfterSuccess(callbacks, input) {
|
|
18
51
|
for (const callback of callbacks) {
|
|
@@ -24,6 +57,7 @@ async function runStaffMutationGuardAfterSuccess(callbacks, input) {
|
|
|
24
57
|
}
|
|
25
58
|
}
|
|
26
59
|
export {
|
|
60
|
+
STAFF_TIME_TRACKING_RESOURCE_KINDS,
|
|
27
61
|
resolveUserFeatures,
|
|
28
62
|
runStaffMutationGuardAfterSuccess,
|
|
29
63
|
runStaffMutationGuards
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/staff/api/guards.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\n\ntype GuardAfterCallback = {\n guard: MutationGuard\n metadata: Record<string, unknown> | null\n}\n\nexport function resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nexport async function runStaffMutationGuards(\n container: AwilixContainer,\n input: MutationGuardInput,\n
|
|
5
|
-
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'\nimport { resolveFeatureAccess } from '../lib/time-tracking/featureAccess'\n\ntype GuardAfterCallback = {\n guard: MutationGuard\n metadata: Record<string, unknown> | null\n}\n\n/**\n * @deprecated `AuthContext` has no `features` field, so this always answered `[]` \u2014\n * which silently filtered out every registered guard declaring `features`, leaving\n * feature-gated guards enforced on the `makeCrudRoute` resources (the factory\n * resolves real grants) and skipped on every hand-rolled write route.\n * `runStaffMutationGuards` now resolves the caller's grants itself through\n * `resolveFeatureAccess`; pass nothing. Kept as an exported symbol for one release.\n */\nexport function resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\n/**\n * The `resourceKind` every hand-rolled time-tracking write route passes into the\n * mutation-guard contract, published as one map so a guard author can target a route\n * without reading it.\n *\n * These strings are what `MutationGuardInput.resourceKind` carries; they are NOT the\n * CRUD factory's cache/audit resource tag, which the factory derives on its own from\n * each route's `events` config. A guard that filters on `resourceKind` matches against\n * the values below on the custom routes and against the factory-derived tag on the\n * eight `makeCrudRoute` resources.\n *\n * `staff.timesheets.time_task` and `staff.timesheets.time_report` are re-exported from\n * the command modules that also feed them to `enforceCommandOptimisticLockWithGuards`\n * (`STAFF_TIME_TASK_RESOURCE_KIND`, `STAFF_TIME_REPORT_RESOURCE_KIND`); the unit test\n * pins both copies to the same string so they cannot drift.\n */\nexport const STAFF_TIME_TRACKING_RESOURCE_KINDS = {\n /** `time-entries/{bulk,copy-day,start-timer}`, `time-entries/[id]/{duplicate,timer-start,timer-stop}` */\n timeEntry: 'staff.timesheets.time_entry',\n /** `time-entries/[id]/segments`, `time-entries/[id]/segments/[segmentId]` */\n timeEntrySegment: 'staff.timesheets.time_entry_segment',\n /** `tasks/[id]/status` */\n timeTask: 'staff.timesheets.time_task',\n /** `tasks/[id]/comments` */\n timeTaskComment: 'staff.timesheets.task_comment',\n /** `time-projects/[id]/change-currency` */\n timeProject: 'staff.timesheets.time_project',\n /** `my-projects/[projectId]` */\n timeProjectMember: 'staff.timesheets.time_project_member',\n /** `reports/[id]/close`, `reports/[id]/unlock` */\n timeReport: 'staff.timesheets.time_report',\n /** `tags/entry-assignments` */\n entryTag: 'staff.timesheets.entry_tag',\n /** `tags/task-assignments` */\n taskTag: 'staff.timesheets.task_tag',\n /** `settings`, `settings/reapply-rounding` */\n settings: 'staff.timesheets.settings',\n /** `access-requests` */\n accessRequest: 'staff.timesheets.access_request',\n} as const\n\nexport type StaffTimeTrackingResourceKind =\n typeof STAFF_TIME_TRACKING_RESOURCE_KINDS[keyof typeof STAFF_TIME_TRACKING_RESOURCE_KINDS]\n\n/**\n * Runs the same guard set as `makeCrudRoute`: every guard a module registered through\n * `data/guards.ts` (`getAllMutationGuardInstances()`) plus the bridged legacy DI\n * service. Mirrors `collectAndRunGuards()` in the CRUD factory; bridging only the\n * legacy service \u2014 as this did originally \u2014 left registry guards unenforced on every\n * hand-rolled time-tracking write route.\n *\n * **The grant list is resolved here, not passed in.** `runMutationGuards` drops any\n * guard whose `features` the caller does not hold, so an empty list silently disables\n * every feature-gated guard \u2014 a third-party `record_locks.enforce` guard fired on the\n * `makeCrudRoute` resources (the factory asks RBAC) and was skipped on `/bulk`, the\n * timer transitions, segments, tag assignments and report close/unlock. Callers used\n * to supply `resolveUserFeatures(auth)`, which read a field `AuthContext` does not\n * have and therefore always returned `[]`. The lookup now goes through\n * `resolveFeatureAccess`, the module's one RBAC authority, which fails closed and\n * logs rather than failing silently.\n *\n * `extraFeatures` is additive and exists only for a caller that already holds a\n * resolved grant list; it is never the sole source.\n */\nexport async function runStaffMutationGuards(\n container: AwilixContainer,\n input: MutationGuardInput,\n extraFeatures: readonly string[] = [],\n): Promise<{\n ok: boolean\n errorBody?: Record<string, unknown>\n errorStatus?: number\n modifiedPayload?: Record<string, unknown>\n afterSuccessCallbacks: GuardAfterCallback[]\n}> {\n const allGuards: MutationGuard[] = [...getAllMutationGuardInstances()]\n const legacyGuard = bridgeLegacyGuard(container)\n if (legacyGuard) allGuards.push(legacyGuard)\n if (!allGuards.length) {\n return { ok: true, afterSuccessCallbacks: [] }\n }\n\n const { grantedFeatures } = await resolveFeatureAccess(container, input.userId ?? null, [], {\n tenantId: input.tenantId ?? null,\n organizationId: input.organizationId ?? null,\n })\n const userFeatures = Array.from(new Set([...grantedFeatures, ...extraFeatures]))\n\n return runMutationGuards(allGuards, input, { userFeatures })\n}\n\nexport async function runStaffMutationGuardAfterSuccess(\n callbacks: GuardAfterCallback[],\n input: {\n tenantId: string\n organizationId: string | null\n userId: string\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update' | 'delete'\n requestMethod: string\n requestHeaders: Headers\n },\n): Promise<void> {\n for (const callback of callbacks) {\n if (!callback.guard.afterSuccess) continue\n await callback.guard.afterSuccess({\n ...input,\n metadata: callback.metadata ?? null,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,oCAAoC;AAC7C,SAAS,4BAA4B;AAe9B,SAAS,oBAAoB,MAAyB;AAC3D,QAAM,WAAY,MAAiC;AACnD,MAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG,QAAO,CAAC;AACtC,SAAO,SAAS,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAC9E;AAkBO,MAAM,qCAAqC;AAAA;AAAA,EAEhD,WAAW;AAAA;AAAA,EAEX,kBAAkB;AAAA;AAAA,EAElB,UAAU;AAAA;AAAA,EAEV,iBAAiB;AAAA;AAAA,EAEjB,aAAa;AAAA;AAAA,EAEb,mBAAmB;AAAA;AAAA,EAEnB,YAAY;AAAA;AAAA,EAEZ,UAAU;AAAA;AAAA,EAEV,SAAS;AAAA;AAAA,EAET,UAAU;AAAA;AAAA,EAEV,eAAe;AACjB;AAyBA,eAAsB,uBACpB,WACA,OACA,gBAAmC,CAAC,GAOnC;AACD,QAAM,YAA6B,CAAC,GAAG,6BAA6B,CAAC;AACrE,QAAM,cAAc,kBAAkB,SAAS;AAC/C,MAAI,YAAa,WAAU,KAAK,WAAW;AAC3C,MAAI,CAAC,UAAU,QAAQ;AACrB,WAAO,EAAE,IAAI,MAAM,uBAAuB,CAAC,EAAE;AAAA,EAC/C;AAEA,QAAM,EAAE,gBAAgB,IAAI,MAAM,qBAAqB,WAAW,MAAM,UAAU,MAAM,CAAC,GAAG;AAAA,IAC1F,UAAU,MAAM,YAAY;AAAA,IAC5B,gBAAgB,MAAM,kBAAkB;AAAA,EAC1C,CAAC;AACD,QAAM,eAAe,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,iBAAiB,GAAG,aAAa,CAAC,CAAC;AAE/E,SAAO,kBAAkB,WAAW,OAAO,EAAE,aAAa,CAAC;AAC7D;AAEA,eAAsB,kCACpB,WACA,OAUe;AACf,aAAW,YAAY,WAAW;AAChC,QAAI,CAAC,SAAS,MAAM,aAAc;AAClC,UAAM,SAAS,MAAM,aAAa;AAAA,MAChC,GAAG;AAAA,MACH,UAAU,SAAS,YAAY;AAAA,IACjC,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,7 +9,6 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
|
9
9
|
import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
|
|
10
10
|
import { staffLeaveRequestDecisionSchema } from "../../../data/validators.js";
|
|
11
11
|
import {
|
|
12
|
-
resolveUserFeatures,
|
|
13
12
|
runStaffMutationGuardAfterSuccess,
|
|
14
13
|
runStaffMutationGuards
|
|
15
14
|
} from "../../guards.js";
|
|
@@ -55,8 +54,7 @@ async function POST(req) {
|
|
|
55
54
|
requestMethod: req.method,
|
|
56
55
|
requestHeaders: req.headers,
|
|
57
56
|
mutationPayload: input
|
|
58
|
-
}
|
|
59
|
-
resolveUserFeatures(auth)
|
|
57
|
+
}
|
|
60
58
|
);
|
|
61
59
|
if (!guardResult.ok) {
|
|
62
60
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/leave-requests/accept/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,uCAA4E;AACrF;AAAA,EACE;AAAA,EACA;AAAA,
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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 const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\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 const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.accept',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.accept failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.accept', 'Failed to approve leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Approve leave request',\n methods: {\n POST: {\n summary: 'Approve leave request',\n description: 'Approves a leave request and adds unavailability rules for the staff member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request approved', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,7 +9,6 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
|
9
9
|
import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
|
|
10
10
|
import { staffLeaveRequestDecisionSchema } from "../../../data/validators.js";
|
|
11
11
|
import {
|
|
12
|
-
resolveUserFeatures,
|
|
13
12
|
runStaffMutationGuardAfterSuccess,
|
|
14
13
|
runStaffMutationGuards
|
|
15
14
|
} from "../../guards.js";
|
|
@@ -55,8 +54,7 @@ async function POST(req) {
|
|
|
55
54
|
requestMethod: req.method,
|
|
56
55
|
requestHeaders: req.headers,
|
|
57
56
|
mutationPayload: input
|
|
58
|
-
}
|
|
59
|
-
resolveUserFeatures(auth)
|
|
57
|
+
}
|
|
60
58
|
);
|
|
61
59
|
if (!guardResult.ok) {
|
|
62
60
|
return NextResponse.json(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/staff/api/leave-requests/reject/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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,kCAAkC;AAE3C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,uCAA4E;AACrF;AAAA,EACE;AAAA,EACA;AAAA,
|
|
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 { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { staffLeaveRequestDecisionSchema, type StaffLeaveRequestDecisionInput } from '../../../data/validators'\nimport {\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.leave_requests.manage'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\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 const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffLeaveRequestDecisionSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\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 const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffLeaveRequestDecisionInput, { requestId: string }>(\n 'staff.leave-requests.reject',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.leave_request',\n resourceId: result?.requestId ?? input.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ ok: true, id: result?.requestId ?? null }, { status: 200 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.leave_request',\n resourceId: logEntry.resourceId ?? result?.requestId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const interceptorRejection = getCommandInterceptorHttpRejection(err)\n if (interceptorRejection) {\n return NextResponse.json(interceptorRejection.body, { status: interceptorRejection.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.leave-requests.reject failed', { err })\n return NextResponse.json({ error: translate('staff.leaveRequests.errors.reject', 'Failed to reject leave request.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Reject leave request',\n methods: {\n POST: {\n summary: 'Reject leave request',\n description: 'Rejects a leave request with an optional comment.',\n requestBody: {\n contentType: 'application/json',\n schema: staffLeaveRequestDecisionSchema,\n },\n responses: [\n { status: 200, description: 'Leave request rejected', schema: z.object({ ok: z.literal(true), id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', 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,131 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
|
+
import { portalOwnedReportClause } from "../../../../lib/time-tracking/portalReports.js";
|
|
5
|
+
import { resolvePortalScope, toIsoDate, toMinutes } from "../route.js";
|
|
6
|
+
const logger = createLogger("staff").child({ component: "api/portal/time-reports/[id]" });
|
|
7
|
+
const metadata = { requireAuth: false };
|
|
8
|
+
const paramsSchema = z.object({ id: z.string().uuid() });
|
|
9
|
+
const errorResponseSchema = z.object({ ok: z.literal(false), error: z.string() });
|
|
10
|
+
const projectLineSchema = z.object({
|
|
11
|
+
timeProjectId: z.string().nullable(),
|
|
12
|
+
projectName: z.string().nullable(),
|
|
13
|
+
billableMinutes: z.number().int(),
|
|
14
|
+
nonbillableMinutes: z.number().int()
|
|
15
|
+
});
|
|
16
|
+
const detailResponseSchema = z.object({
|
|
17
|
+
id: z.string().uuid(),
|
|
18
|
+
reference: z.string(),
|
|
19
|
+
title: z.string(),
|
|
20
|
+
periodFrom: z.string(),
|
|
21
|
+
periodTo: z.string(),
|
|
22
|
+
closedAt: z.string().nullable(),
|
|
23
|
+
totalBillableMinutes: z.number().int(),
|
|
24
|
+
totalNonbillableMinutes: z.number().int(),
|
|
25
|
+
projects: z.array(projectLineSchema)
|
|
26
|
+
});
|
|
27
|
+
function readReportId(req) {
|
|
28
|
+
const segments = new URL(req.url).pathname.split("/").filter(Boolean);
|
|
29
|
+
const last = segments[segments.length - 1];
|
|
30
|
+
const parsed = paramsSchema.safeParse({ id: last });
|
|
31
|
+
return parsed.success ? parsed.data.id : null;
|
|
32
|
+
}
|
|
33
|
+
async function GET(req) {
|
|
34
|
+
try {
|
|
35
|
+
const scopeOrResponse = await resolvePortalScope(req);
|
|
36
|
+
if (scopeOrResponse instanceof Response) return scopeOrResponse;
|
|
37
|
+
const scope = scopeOrResponse;
|
|
38
|
+
const reportId = readReportId(req);
|
|
39
|
+
if (!reportId) {
|
|
40
|
+
return NextResponse.json({ ok: false, error: "staff.errors.invalidInput" }, { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
const connection = scope.em.getConnection();
|
|
43
|
+
const owned = portalOwnedReportClause(scope);
|
|
44
|
+
const rows = await connection.execute(
|
|
45
|
+
`
|
|
46
|
+
SELECT id, reference, title, period_from, period_to, closed_at,
|
|
47
|
+
total_billable_minutes, total_nonbillable_minutes
|
|
48
|
+
FROM staff_time_reports
|
|
49
|
+
WHERE ${owned.sql} AND id = ?
|
|
50
|
+
LIMIT 1
|
|
51
|
+
`,
|
|
52
|
+
[...owned.params, reportId]
|
|
53
|
+
);
|
|
54
|
+
const report = rows?.[0];
|
|
55
|
+
if (!report) {
|
|
56
|
+
return NextResponse.json({ ok: false, error: "staff.errors.notFound" }, { status: 404 });
|
|
57
|
+
}
|
|
58
|
+
const periodFrom = toIsoDate(report.period_from);
|
|
59
|
+
const periodTo = toIsoDate(report.period_to);
|
|
60
|
+
if (!periodFrom || !periodTo) {
|
|
61
|
+
return NextResponse.json({ ok: false, error: "staff.errors.notFound" }, { status: 404 });
|
|
62
|
+
}
|
|
63
|
+
const projectRows = await connection.execute(
|
|
64
|
+
`
|
|
65
|
+
SELECT entry.time_project_id AS time_project_id,
|
|
66
|
+
project.name AS project_name,
|
|
67
|
+
COALESCE(SUM(CASE WHEN line.frozen_is_billable THEN line.frozen_rounded_minutes ELSE 0 END), 0) AS billable_minutes,
|
|
68
|
+
COALESCE(SUM(CASE WHEN line.frozen_is_billable THEN 0 ELSE line.frozen_rounded_minutes END), 0) AS nonbillable_minutes
|
|
69
|
+
FROM staff_time_report_entries line
|
|
70
|
+
JOIN staff_time_entries entry
|
|
71
|
+
ON entry.id = line.time_entry_id
|
|
72
|
+
AND entry.tenant_id = line.tenant_id
|
|
73
|
+
AND entry.organization_id = line.organization_id
|
|
74
|
+
LEFT JOIN staff_time_projects project
|
|
75
|
+
ON project.id = entry.time_project_id
|
|
76
|
+
AND project.tenant_id = line.tenant_id
|
|
77
|
+
AND project.organization_id = line.organization_id
|
|
78
|
+
WHERE line.report_id = ?
|
|
79
|
+
AND line.tenant_id = ?
|
|
80
|
+
AND line.organization_id = ?
|
|
81
|
+
GROUP BY entry.time_project_id, project.name
|
|
82
|
+
ORDER BY project.name ASC NULLS LAST
|
|
83
|
+
`,
|
|
84
|
+
[report.id, scope.tenantId, scope.organizationId]
|
|
85
|
+
);
|
|
86
|
+
return NextResponse.json(
|
|
87
|
+
detailResponseSchema.parse({
|
|
88
|
+
id: report.id,
|
|
89
|
+
reference: report.reference,
|
|
90
|
+
title: report.title,
|
|
91
|
+
periodFrom,
|
|
92
|
+
periodTo,
|
|
93
|
+
closedAt: report.closed_at instanceof Date ? report.closed_at.toISOString() : typeof report.closed_at === "string" && report.closed_at.trim().length > 0 ? report.closed_at : null,
|
|
94
|
+
totalBillableMinutes: toMinutes(report.total_billable_minutes),
|
|
95
|
+
totalNonbillableMinutes: toMinutes(report.total_nonbillable_minutes),
|
|
96
|
+
projects: projectRows.map((row) => ({
|
|
97
|
+
timeProjectId: row.time_project_id ?? null,
|
|
98
|
+
projectName: row.project_name ?? null,
|
|
99
|
+
billableMinutes: toMinutes(row.billable_minutes),
|
|
100
|
+
nonbillableMinutes: toMinutes(row.nonbillable_minutes)
|
|
101
|
+
}))
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.error("staff portal time-report detail failed", { err });
|
|
106
|
+
return NextResponse.json({ ok: false, error: "staff.errors.internal" }, { status: 500 });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const openApi = {
|
|
110
|
+
tag: "Staff Portal",
|
|
111
|
+
pathParams: paramsSchema,
|
|
112
|
+
methods: {
|
|
113
|
+
GET: {
|
|
114
|
+
summary: "Read one closed time report belonging to the signed-in customer",
|
|
115
|
+
description: "Loads the report by id **together with** the tenant, organization, customer and closed-status predicates, so a report belonging to another customer of the same organization answers 404. Returns the frozen minute totals per project; no rate, cost, amount or currency is present in the response.",
|
|
116
|
+
pathParams: paramsSchema,
|
|
117
|
+
responses: [{ status: 200, description: "The report", schema: detailResponseSchema }],
|
|
118
|
+
errors: [
|
|
119
|
+
{ status: 401, description: "Not signed in to the portal", schema: errorResponseSchema },
|
|
120
|
+
{ status: 403, description: "The portal account is not linked to a customer", schema: errorResponseSchema },
|
|
121
|
+
{ status: 404, description: "No closed report with that id belongs to this customer", schema: errorResponseSchema }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
export {
|
|
127
|
+
GET,
|
|
128
|
+
metadata,
|
|
129
|
+
openApi
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/staff/api/portal/time-reports/%5Bid%5D/route.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `GET /api/staff/portal/time-reports/{id}` \u2014 EP-50.\n *\n * The ownership check is the list route's, unchanged and re-applied here rather\n * than inherited from it: the id in the path is caller-supplied, so the row it\n * names is loaded WITH `tenant_id`, `organization_id`, `customer_id`,\n * `status = 'closed'` and `deleted_at IS NULL` in the same WHERE clause, never\n * loaded first and checked after. A report belonging to another customer of the\n * same organization is a `404`, not a `403` \u2014 the portal must not confirm that\n * somebody else's report exists.\n *\n * The per-project breakdown reads the FROZEN minutes on `staff_time_report_entries`,\n * which is what the close wrote and what the client was told. It carries no\n * `frozen_rate_amount` and no `frozen_amount`; see the list route's header for why\n * money is structurally absent from this surface rather than conditionally hidden.\n */\n\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { portalOwnedReportClause } from '../../../../lib/time-tracking/portalReports'\nimport { resolvePortalScope, toIsoDate, toMinutes } from '../route'\n\nconst logger = createLogger('staff').child({ component: 'api/portal/time-reports/[id]' })\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst paramsSchema = z.object({ id: z.string().uuid() })\n\nconst errorResponseSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst projectLineSchema = z.object({\n timeProjectId: z.string().nullable(),\n projectName: z.string().nullable(),\n billableMinutes: z.number().int(),\n nonbillableMinutes: z.number().int(),\n})\n\nconst detailResponseSchema = z.object({\n id: z.string().uuid(),\n reference: z.string(),\n title: z.string(),\n periodFrom: z.string(),\n periodTo: z.string(),\n closedAt: z.string().nullable(),\n totalBillableMinutes: z.number().int(),\n totalNonbillableMinutes: z.number().int(),\n projects: z.array(projectLineSchema),\n})\n\ntype ReportRow = {\n id: string\n reference: string\n title: string\n period_from: Date | string\n period_to: Date | string\n closed_at: Date | string | null\n total_billable_minutes: number | string | null\n total_nonbillable_minutes: number | string | null\n}\n\ntype ProjectRow = {\n time_project_id: string | null\n project_name: string | null\n billable_minutes: number | string | null\n nonbillable_minutes: number | string | null\n}\n\nfunction readReportId(req: Request): string | null {\n const segments = new URL(req.url).pathname.split('/').filter(Boolean)\n const last = segments[segments.length - 1]\n const parsed = paramsSchema.safeParse({ id: last })\n return parsed.success ? parsed.data.id : null\n}\n\nexport async function GET(req: Request) {\n try {\n const scopeOrResponse = await resolvePortalScope(req)\n if (scopeOrResponse instanceof Response) return scopeOrResponse\n const scope = scopeOrResponse\n\n const reportId = readReportId(req)\n if (!reportId) {\n return NextResponse.json({ ok: false, error: 'staff.errors.invalidInput' }, { status: 400 })\n }\n\n const connection = scope.em.getConnection()\n const owned = portalOwnedReportClause(scope)\n const rows = (await connection.execute(\n `\n SELECT id, reference, title, period_from, period_to, closed_at,\n total_billable_minutes, total_nonbillable_minutes\n FROM staff_time_reports\n WHERE ${owned.sql} AND id = ?\n LIMIT 1\n `,\n [...owned.params, reportId],\n )) as ReportRow[]\n\n const report = rows?.[0]\n if (!report) {\n return NextResponse.json({ ok: false, error: 'staff.errors.notFound' }, { status: 404 })\n }\n\n const periodFrom = toIsoDate(report.period_from)\n const periodTo = toIsoDate(report.period_to)\n if (!periodFrom || !periodTo) {\n return NextResponse.json({ ok: false, error: 'staff.errors.notFound' }, { status: 404 })\n }\n\n const projectRows = (await connection.execute(\n `\n SELECT entry.time_project_id AS time_project_id,\n project.name AS project_name,\n COALESCE(SUM(CASE WHEN line.frozen_is_billable THEN line.frozen_rounded_minutes ELSE 0 END), 0) AS billable_minutes,\n COALESCE(SUM(CASE WHEN line.frozen_is_billable THEN 0 ELSE line.frozen_rounded_minutes END), 0) AS nonbillable_minutes\n FROM staff_time_report_entries line\n JOIN staff_time_entries entry\n ON entry.id = line.time_entry_id\n AND entry.tenant_id = line.tenant_id\n AND entry.organization_id = line.organization_id\n LEFT JOIN staff_time_projects project\n ON project.id = entry.time_project_id\n AND project.tenant_id = line.tenant_id\n AND project.organization_id = line.organization_id\n WHERE line.report_id = ?\n AND line.tenant_id = ?\n AND line.organization_id = ?\n GROUP BY entry.time_project_id, project.name\n ORDER BY project.name ASC NULLS LAST\n `,\n [report.id, scope.tenantId, scope.organizationId],\n )) as ProjectRow[]\n\n return NextResponse.json(\n detailResponseSchema.parse({\n id: report.id,\n reference: report.reference,\n title: report.title,\n periodFrom,\n periodTo,\n closedAt:\n report.closed_at instanceof Date\n ? report.closed_at.toISOString()\n : typeof report.closed_at === 'string' && report.closed_at.trim().length > 0\n ? report.closed_at\n : null,\n totalBillableMinutes: toMinutes(report.total_billable_minutes),\n totalNonbillableMinutes: toMinutes(report.total_nonbillable_minutes),\n projects: projectRows.map((row) => ({\n timeProjectId: row.time_project_id ?? null,\n projectName: row.project_name ?? null,\n billableMinutes: toMinutes(row.billable_minutes),\n nonbillableMinutes: toMinutes(row.nonbillable_minutes),\n })),\n }),\n )\n } catch (err) {\n logger.error('staff portal time-report detail failed', { err })\n return NextResponse.json({ ok: false, error: 'staff.errors.internal' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff Portal',\n pathParams: paramsSchema,\n methods: {\n GET: {\n summary: 'Read one closed time report belonging to the signed-in customer',\n description:\n 'Loads the report by id **together with** the tenant, organization, customer and closed-status predicates, so a report belonging to another customer of the same organization answers 404. Returns the frozen minute totals per project; no rate, cost, amount or currency is present in the response.',\n pathParams: paramsSchema,\n responses: [{ status: 200, description: 'The report', schema: detailResponseSchema }],\n errors: [\n { status: 401, description: 'Not signed in to the portal', schema: errorResponseSchema },\n { status: 403, description: 'The portal account is not linked to a customer', schema: errorResponseSchema },\n { status: 404, description: 'No closed report with that id belongs to this customer', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
5
|
+
import {
|
|
6
|
+
PORTAL_TIME_REPORTS_VIEW_FEATURE,
|
|
7
|
+
portalOwnedReportClause
|
|
8
|
+
} from "../../../lib/time-tracking/portalReports.js";
|
|
9
|
+
const logger = createLogger("staff").child({ component: "api/portal/time-reports" });
|
|
10
|
+
const metadata = { requireAuth: false };
|
|
11
|
+
const listQuerySchema = z.object({
|
|
12
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
13
|
+
pageSize: z.coerce.number().int().min(1).max(50).default(20)
|
|
14
|
+
});
|
|
15
|
+
const reportItemSchema = z.object({
|
|
16
|
+
id: z.string().uuid(),
|
|
17
|
+
reference: z.string(),
|
|
18
|
+
title: z.string(),
|
|
19
|
+
periodFrom: z.string(),
|
|
20
|
+
periodTo: z.string(),
|
|
21
|
+
closedAt: z.string().nullable(),
|
|
22
|
+
totalBillableMinutes: z.number().int(),
|
|
23
|
+
totalNonbillableMinutes: z.number().int()
|
|
24
|
+
});
|
|
25
|
+
const errorResponseSchema = z.object({ ok: z.literal(false), error: z.string() });
|
|
26
|
+
const listResponseSchema = z.object({
|
|
27
|
+
items: z.array(reportItemSchema),
|
|
28
|
+
total: z.number().int(),
|
|
29
|
+
page: z.number().int(),
|
|
30
|
+
pageSize: z.number().int(),
|
|
31
|
+
totalPages: z.number().int()
|
|
32
|
+
});
|
|
33
|
+
function toIsoDate(value) {
|
|
34
|
+
if (value instanceof Date) {
|
|
35
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
36
|
+
return value.toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim().slice(0, 10);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function toIsoDateTime(value) {
|
|
42
|
+
if (value instanceof Date) {
|
|
43
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
44
|
+
return value.toISOString();
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function toMinutes(value) {
|
|
50
|
+
const parsed = typeof value === "number" ? value : Number(value ?? 0);
|
|
51
|
+
return Number.isFinite(parsed) ? Math.trunc(parsed) : 0;
|
|
52
|
+
}
|
|
53
|
+
async function resolvePortalScope(req) {
|
|
54
|
+
let auth = null;
|
|
55
|
+
let requireFeature = null;
|
|
56
|
+
try {
|
|
57
|
+
const customerAuth = await import("@open-mercato/core/modules/customer_accounts/lib/customerAuth");
|
|
58
|
+
auth = await customerAuth.getCustomerAuthFromRequest(req);
|
|
59
|
+
const guard = customerAuth.requireCustomerFeature;
|
|
60
|
+
requireFeature = typeof guard === "function" ? guard : null;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
logger.warn("staff portal time-reports could not resolve customer auth", { err });
|
|
63
|
+
return NextResponse.json({ ok: false, error: "staff.errors.unauthorized" }, { status: 401 });
|
|
64
|
+
}
|
|
65
|
+
if (!auth) {
|
|
66
|
+
return NextResponse.json({ ok: false, error: "staff.errors.unauthorized" }, { status: 401 });
|
|
67
|
+
}
|
|
68
|
+
const customerId = typeof auth.customerEntityId === "string" && auth.customerEntityId.trim().length > 0 ? auth.customerEntityId.trim() : null;
|
|
69
|
+
if (!customerId) {
|
|
70
|
+
return NextResponse.json({ ok: false, error: "staff.errors.customerAccountNotLinked" }, { status: 403 });
|
|
71
|
+
}
|
|
72
|
+
if (!requireFeature) {
|
|
73
|
+
logger.error("staff portal time-reports could not resolve requireCustomerFeature; refusing the request", {
|
|
74
|
+
feature: PORTAL_TIME_REPORTS_VIEW_FEATURE
|
|
75
|
+
});
|
|
76
|
+
return NextResponse.json({ ok: false, error: "staff.errors.forbidden" }, { status: 403 });
|
|
77
|
+
}
|
|
78
|
+
const container = await createRequestContainer();
|
|
79
|
+
try {
|
|
80
|
+
const rbac = container.resolve("customerRbacService");
|
|
81
|
+
await requireFeature(auth, [PORTAL_TIME_REPORTS_VIEW_FEATURE], rbac);
|
|
82
|
+
} catch (response) {
|
|
83
|
+
if (response instanceof Response) return response;
|
|
84
|
+
logger.warn("staff portal time-reports feature check failed", { err: response });
|
|
85
|
+
return NextResponse.json({ ok: false, error: "staff.errors.forbidden" }, { status: 403 });
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
auth,
|
|
89
|
+
customerId,
|
|
90
|
+
tenantId: auth.tenantId,
|
|
91
|
+
organizationId: auth.orgId,
|
|
92
|
+
em: container.resolve("em").fork()
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function GET(req) {
|
|
96
|
+
try {
|
|
97
|
+
const scopeOrResponse = await resolvePortalScope(req);
|
|
98
|
+
if (scopeOrResponse instanceof Response) return scopeOrResponse;
|
|
99
|
+
const scope = scopeOrResponse;
|
|
100
|
+
const url = new URL(req.url);
|
|
101
|
+
const parsedQuery = listQuerySchema.safeParse({
|
|
102
|
+
page: url.searchParams.get("page") ?? void 0,
|
|
103
|
+
pageSize: url.searchParams.get("pageSize") ?? void 0
|
|
104
|
+
});
|
|
105
|
+
if (!parsedQuery.success) {
|
|
106
|
+
return NextResponse.json({ ok: false, error: "staff.errors.invalidInput" }, { status: 400 });
|
|
107
|
+
}
|
|
108
|
+
const { page, pageSize } = parsedQuery.data;
|
|
109
|
+
const connection = scope.em.getConnection();
|
|
110
|
+
const owned = portalOwnedReportClause(scope);
|
|
111
|
+
const countRows = await connection.execute(
|
|
112
|
+
`SELECT COUNT(*)::bigint AS total FROM staff_time_reports WHERE ${owned.sql}`,
|
|
113
|
+
owned.params
|
|
114
|
+
);
|
|
115
|
+
const total = toMinutes(countRows?.[0]?.total);
|
|
116
|
+
const rows = await connection.execute(
|
|
117
|
+
`
|
|
118
|
+
SELECT id, reference, title, period_from, period_to, closed_at,
|
|
119
|
+
total_billable_minutes, total_nonbillable_minutes
|
|
120
|
+
FROM staff_time_reports
|
|
121
|
+
WHERE ${owned.sql}
|
|
122
|
+
ORDER BY period_from DESC, reference DESC
|
|
123
|
+
LIMIT ? OFFSET ?
|
|
124
|
+
`,
|
|
125
|
+
[...owned.params, pageSize, (page - 1) * pageSize]
|
|
126
|
+
);
|
|
127
|
+
const items = rows.map((row) => {
|
|
128
|
+
const periodFrom = toIsoDate(row.period_from);
|
|
129
|
+
const periodTo = toIsoDate(row.period_to);
|
|
130
|
+
if (!periodFrom || !periodTo) return null;
|
|
131
|
+
return {
|
|
132
|
+
id: row.id,
|
|
133
|
+
reference: row.reference,
|
|
134
|
+
title: row.title,
|
|
135
|
+
periodFrom,
|
|
136
|
+
periodTo,
|
|
137
|
+
closedAt: toIsoDateTime(row.closed_at),
|
|
138
|
+
totalBillableMinutes: toMinutes(row.total_billable_minutes),
|
|
139
|
+
totalNonbillableMinutes: toMinutes(row.total_nonbillable_minutes)
|
|
140
|
+
};
|
|
141
|
+
}).filter((item) => item !== null);
|
|
142
|
+
return NextResponse.json(
|
|
143
|
+
listResponseSchema.parse({
|
|
144
|
+
items,
|
|
145
|
+
total,
|
|
146
|
+
page,
|
|
147
|
+
pageSize,
|
|
148
|
+
totalPages: Math.max(1, Math.ceil(total / pageSize))
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logger.error("staff portal time-reports list failed", { err });
|
|
153
|
+
return NextResponse.json({ ok: false, error: "staff.errors.internal" }, { status: 500 });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const openApi = {
|
|
157
|
+
tag: "Staff Portal",
|
|
158
|
+
methods: {
|
|
159
|
+
GET: {
|
|
160
|
+
summary: "List the signed-in customer\u2019s closed time reports",
|
|
161
|
+
description: "Returns the closed, non-deleted `staff_time_reports` rows whose `customer_id` matches the portal session\u2019s own customer entity, inside that session\u2019s tenant and organization. Draft reports are never listed. The response carries hours only \u2014 no rate, cost, amount or currency \u2014 because `staff.timesheets.rates.view` is a staff feature a portal identity cannot hold.",
|
|
162
|
+
query: listQuerySchema,
|
|
163
|
+
responses: [{ status: 200, description: "Closed reports for the signed-in customer", schema: listResponseSchema }],
|
|
164
|
+
errors: [
|
|
165
|
+
{ status: 401, description: "Not signed in to the portal", schema: errorResponseSchema },
|
|
166
|
+
{ status: 403, description: "The portal account is not linked to a customer", schema: errorResponseSchema }
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export {
|
|
172
|
+
GET,
|
|
173
|
+
metadata,
|
|
174
|
+
openApi,
|
|
175
|
+
resolvePortalScope,
|
|
176
|
+
toIsoDate,
|
|
177
|
+
toMinutes
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/staff/api/portal/time-reports/route.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `GET /api/staff/portal/time-reports` \u2014 EP-50, the customer-facing half of the\n * time-tracking suite.\n *\n * **The ownership check, spelled out, because it is the only thing standing\n * between a client and another client's hours.** Every row this route can return\n * satisfies all four of:\n *\n * `tenant_id = auth.tenantId` (from the customer JWT, never input)\n * `organization_id= auth.orgId` (ditto; the portal shell already\n * refuses a URL whose org slug does\n * not resolve to this org)\n * `customer_id = auth.customerEntityId` (the signed-in portal user's own\n * customer entity \u2014 the same id\n * `staff_time_reports.customer_id`\n * holds, see `data/extensions.ts`)\n * `status = 'closed' AND deleted_at IS NULL`\n *\n * A portal user with no `customerEntityId` is refused with `403` rather than\n * being shown an unscoped list: an unlinked portal account is not a customer, and\n * a null in that column would otherwise match nothing or \u2014 worse, in a future\n * refactor \u2014 everything.\n *\n * **Money is absent, not blanked.** The response has no rate, cost, amount or\n * currency field in its schema at all. `staff.timesheets.rates.view` is a *staff*\n * feature resolved by `rbacService`; a portal identity is graded by\n * `CustomerRbacService` against the disjoint `portal.*` namespace and can never\n * hold it, so the module's own rule \u2014 money is added for a holder and absent for\n * everyone else \u2014 resolves to \"absent, always\" here. Making that structural\n * rather than conditional means no later edit can accidentally open it.\n *\n * Draft reports are invisible. A draft is working material whose entries are not\n * frozen; only a closed report is a statement the business has stood behind.\n */\n\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CustomerAuthContext } from '@open-mercato/shared/modules/customer-auth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n PORTAL_TIME_REPORTS_VIEW_FEATURE,\n portalOwnedReportClause,\n} from '../../../lib/time-tracking/portalReports'\n\nconst logger = createLogger('staff').child({ component: 'api/portal/time-reports' })\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(50).default(20),\n})\n\nconst reportItemSchema = z.object({\n id: z.string().uuid(),\n reference: z.string(),\n title: z.string(),\n periodFrom: z.string(),\n periodTo: z.string(),\n closedAt: z.string().nullable(),\n totalBillableMinutes: z.number().int(),\n totalNonbillableMinutes: z.number().int(),\n})\n\nconst errorResponseSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst listResponseSchema = z.object({\n items: z.array(reportItemSchema),\n total: z.number().int(),\n page: z.number().int(),\n pageSize: z.number().int(),\n totalPages: z.number().int(),\n})\n\ntype PortalScope = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n}\n\ntype ReportRow = {\n id: string\n reference: string\n title: string\n period_from: Date | string\n period_to: Date | string\n closed_at: Date | string | null\n total_billable_minutes: number | string | null\n total_nonbillable_minutes: number | string | null\n}\n\nexport function toIsoDate(value: Date | string | null | undefined): string | null {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString().slice(0, 10)\n }\n if (typeof value === 'string' && value.trim().length > 0) return value.trim().slice(0, 10)\n return null\n}\n\nfunction toIsoDateTime(value: Date | string | null | undefined): string | null {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString()\n }\n if (typeof value === 'string' && value.trim().length > 0) return value.trim()\n return null\n}\n\nexport function toMinutes(value: number | string | null | undefined): number {\n const parsed = typeof value === 'number' ? value : Number(value ?? 0)\n return Number.isFinite(parsed) ? Math.trunc(parsed) : 0\n}\n\n/**\n * Resolves the portal identity through `customer_accounts` with a dynamic import.\n * Staff must stay extractable into its own package, so the module graph carries no\n * static edge to `customer_accounts`; a deployment without the portal installed\n * answers `401` here instead of failing to load.\n */\ntype CustomerFeatureGuard = (\n auth: CustomerAuthContext,\n features: string[],\n rbac: unknown,\n) => Promise<void>\n\nexport async function resolvePortalScope(req: Request): Promise<PortalScope | Response> {\n let auth: CustomerAuthContext | null = null\n let requireFeature: CustomerFeatureGuard | null = null\n try {\n const customerAuth = await import('@open-mercato/core/modules/customer_accounts/lib/customerAuth')\n auth = await customerAuth.getCustomerAuthFromRequest(req)\n const guard: unknown = (customerAuth as { requireCustomerFeature?: unknown }).requireCustomerFeature\n requireFeature = typeof guard === 'function' ? (guard as CustomerFeatureGuard) : null\n } catch (err) {\n logger.warn('staff portal time-reports could not resolve customer auth', { err })\n return NextResponse.json({ ok: false, error: 'staff.errors.unauthorized' }, { status: 401 })\n }\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'staff.errors.unauthorized' }, { status: 401 })\n }\n const customerId =\n typeof auth.customerEntityId === 'string' && auth.customerEntityId.trim().length > 0\n ? auth.customerEntityId.trim()\n : null\n if (!customerId) {\n return NextResponse.json({ ok: false, error: 'staff.errors.customerAccountNotLinked' }, { status: 403 })\n }\n\n // A missing guard is a failed feature check, not an absent one: without it the\n // tenant loses the ability to REVOKE `portal.time_reports.view`, since a customer\n // role stripped of the grant would keep reading reports. (Not a disclosure \u2014\n // `portalOwnedReportClause` still pins tenant, organization, customer and\n // `status = 'closed'` \u2014 but revocation has to keep working.)\n if (!requireFeature) {\n logger.error('staff portal time-reports could not resolve requireCustomerFeature; refusing the request', {\n feature: PORTAL_TIME_REPORTS_VIEW_FEATURE,\n })\n return NextResponse.json({ ok: false, error: 'staff.errors.forbidden' }, { status: 403 })\n }\n\n const container = await createRequestContainer()\n try {\n const rbac = container.resolve('customerRbacService')\n await requireFeature(auth, [PORTAL_TIME_REPORTS_VIEW_FEATURE], rbac)\n } catch (response) {\n if (response instanceof Response) return response\n logger.warn('staff portal time-reports feature check failed', { err: response })\n return NextResponse.json({ ok: false, error: 'staff.errors.forbidden' }, { status: 403 })\n }\n\n return {\n auth,\n customerId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em: (container.resolve('em') as EntityManager).fork(),\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const scopeOrResponse = await resolvePortalScope(req)\n if (scopeOrResponse instanceof Response) return scopeOrResponse\n const scope = scopeOrResponse\n\n const url = new URL(req.url)\n const parsedQuery = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsedQuery.success) {\n return NextResponse.json({ ok: false, error: 'staff.errors.invalidInput' }, { status: 400 })\n }\n const { page, pageSize } = parsedQuery.data\n\n const connection = scope.em.getConnection()\n const owned = portalOwnedReportClause(scope)\n const countRows = (await connection.execute(\n `SELECT COUNT(*)::bigint AS total FROM staff_time_reports WHERE ${owned.sql}`,\n owned.params,\n )) as Array<{ total: string | number | null }>\n const total = toMinutes(countRows?.[0]?.total)\n\n const rows = (await connection.execute(\n `\n SELECT id, reference, title, period_from, period_to, closed_at,\n total_billable_minutes, total_nonbillable_minutes\n FROM staff_time_reports\n WHERE ${owned.sql}\n ORDER BY period_from DESC, reference DESC\n LIMIT ? OFFSET ?\n `,\n [...owned.params, pageSize, (page - 1) * pageSize],\n )) as ReportRow[]\n\n const items = rows\n .map((row) => {\n const periodFrom = toIsoDate(row.period_from)\n const periodTo = toIsoDate(row.period_to)\n if (!periodFrom || !periodTo) return null\n return {\n id: row.id,\n reference: row.reference,\n title: row.title,\n periodFrom,\n periodTo,\n closedAt: toIsoDateTime(row.closed_at),\n totalBillableMinutes: toMinutes(row.total_billable_minutes),\n totalNonbillableMinutes: toMinutes(row.total_nonbillable_minutes),\n }\n })\n .filter((item): item is z.infer<typeof reportItemSchema> => item !== null)\n\n return NextResponse.json(\n listResponseSchema.parse({\n items,\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n }),\n )\n } catch (err) {\n logger.error('staff portal time-reports list failed', { err })\n return NextResponse.json({ ok: false, error: 'staff.errors.internal' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff Portal',\n methods: {\n GET: {\n summary: 'List the signed-in customer\\u2019s closed time reports',\n description:\n 'Returns the closed, non-deleted `staff_time_reports` rows whose `customer_id` matches the portal session\\u2019s own customer entity, inside that session\\u2019s tenant and organization. Draft reports are never listed. The response carries hours only \\u2014 no rate, cost, amount or currency \\u2014 because `staff.timesheets.rates.view` is a staff feature a portal identity cannot hold.',\n query: listQuerySchema,\n responses: [{ status: 200, description: 'Closed reports for the signed-in customer', schema: listResponseSchema }],\n errors: [\n { status: 401, description: 'Not signed in to the portal', schema: errorResponseSchema },\n { status: 403, description: 'The portal account is not linked to a customer', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|