@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7180.1.9717fbbb43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/staff_time_entry/index.js +14 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
- package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +16 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_report/index.js +57 -0
- package/dist/generated/entities/staff_time_report/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
- package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_event/index.js +21 -0
- package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_project/index.js +15 -0
- package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_tag/index.js +21 -0
- package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task/index.js +37 -0
- package/dist/generated/entities/staff_time_task/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
- package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_status/index.js +29 -0
- package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +149 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +7 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
- package/dist/modules/auth/cli.js +16 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +1 -0
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +1 -28
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +2 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/start-controls.js +42 -0
- package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
- package/dist/modules/staff/acl.js +9 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/ai-agents.js +142 -0
- package/dist/modules/staff/ai-agents.js.map +7 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
- package/dist/modules/staff/ai-tools/types.js +54 -0
- package/dist/modules/staff/ai-tools/types.js.map +7 -0
- package/dist/modules/staff/ai-tools.js +8 -0
- package/dist/modules/staff/ai-tools.js.map +7 -0
- package/dist/modules/staff/analytics.js +84 -1
- package/dist/modules/staff/analytics.js.map +2 -2
- package/dist/modules/staff/api/guards.js +37 -3
- package/dist/modules/staff/api/guards.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
- package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
- package/dist/modules/staff/api/team-members/self/route.js +1 -3
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
- package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
- package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
- package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
- package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
- package/dist/modules/staff/ce.js +40 -0
- package/dist/modules/staff/ce.js.map +2 -2
- package/dist/modules/staff/cli.js +166 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +5 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +569 -21
- package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
- package/dist/modules/staff/commands/timesheets-projects.js +450 -7
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
- package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
- package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
- package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +426 -12
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +552 -3
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +41 -0
- package/dist/modules/staff/data/extensions.js.map +7 -0
- package/dist/modules/staff/data/validators.js +254 -9
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/di.js +42 -1
- package/dist/modules/staff/di.js.map +2 -2
- package/dist/modules/staff/events.js +84 -6
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +158 -0
- package/dist/modules/staff/events.payloads.js.map +7 -0
- package/dist/modules/staff/extension-points.js +261 -0
- package/dist/modules/staff/extension-points.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
- package/dist/modules/staff/lib/crud.js +11 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/access.js +92 -0
- package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
- package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
- package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
- package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
- package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
- package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
- package/dist/modules/staff/notifications.client.js +16 -0
- package/dist/modules/staff/notifications.client.js.map +7 -0
- package/dist/modules/staff/notifications.handlers.js +80 -0
- package/dist/modules/staff/notifications.handlers.js.map +7 -0
- package/dist/modules/staff/notifications.js +168 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/search.js +159 -1
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +20 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
- package/dist/modules/staff/translations.js +4 -1
- package/dist/modules/staff/translations.js.map +2 -2
- package/dist/modules/staff/widgets/components.js +7 -0
- package/dist/modules/staff/widgets/components.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
- package/dist/modules/staff/widgets/notifications/index.js +5 -0
- package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
- package/generated/entities/staff_time_entry/index.ts +7 -0
- package/generated/entities/staff_time_entry_tag/index.ts +6 -0
- package/generated/entities/staff_time_project/index.ts +8 -0
- package/generated/entities/staff_time_report/index.ts +27 -0
- package/generated/entities/staff_time_report_entry/index.ts +12 -0
- package/generated/entities/staff_time_report_event/index.ts +9 -0
- package/generated/entities/staff_time_report_project/index.ts +6 -0
- package/generated/entities/staff_time_tag/index.ts +9 -0
- package/generated/entities/staff_time_task/index.ts +17 -0
- package/generated/entities/staff_time_task_comment/index.ts +9 -0
- package/generated/entities/staff_time_task_status/index.ts +13 -0
- package/generated/entities/staff_time_task_tag/index.ts +6 -0
- package/generated/entities.ids.generated.ts +11 -1
- package/generated/entity-fields-registry.ts +149 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +31 -1
- package/src/modules/auth/README.md +1 -1
- package/src/modules/auth/cli.ts +35 -1
- package/src/modules/auth/i18n/de.json +7 -0
- package/src/modules/auth/i18n/en.json +7 -0
- package/src/modules/auth/i18n/es.json +7 -0
- package/src/modules/auth/i18n/ko.json +7 -0
- package/src/modules/auth/i18n/pl.json +7 -0
- package/src/modules/auth/lib/backendChrome.tsx +1 -0
- package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
- package/src/modules/customer_accounts/setup.ts +1 -45
- package/src/modules/data_sync/AGENTS.md +34 -0
- package/src/modules/data_sync/api/options.ts +2 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/ko.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/adapter.ts +45 -0
- package/src/modules/data_sync/lib/start-controls.ts +89 -0
- package/src/modules/staff/AGENTS.md +769 -1
- package/src/modules/staff/acl.ts +9 -0
- package/src/modules/staff/ai-agents.ts +189 -0
- package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
- package/src/modules/staff/ai-tools/types.ts +82 -0
- package/src/modules/staff/ai-tools.ts +13 -0
- package/src/modules/staff/analytics.ts +113 -0
- package/src/modules/staff/api/guards.ts +85 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
- package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
- package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
- package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
- package/src/modules/staff/api/team-members/self/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
- package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
- package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
- package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
- package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
- package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
- package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
- package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
- package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
- package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
- package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
- package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
- package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
- package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
- package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
- package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
- package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
- package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
- package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
- package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
- package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
- package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
- package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
- package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
- package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
- package/src/modules/staff/ce.ts +63 -0
- package/src/modules/staff/cli.ts +207 -1
- package/src/modules/staff/commands/index.ts +5 -0
- package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
- package/src/modules/staff/commands/timesheets-projects.ts +593 -7
- package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
- package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
- package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
- package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
- package/src/modules/staff/data/enrichers.ts +675 -10
- package/src/modules/staff/data/entities.ts +494 -2
- package/src/modules/staff/data/extensions.ts +68 -0
- package/src/modules/staff/data/validators.ts +348 -3
- package/src/modules/staff/di.ts +96 -0
- package/src/modules/staff/events.payloads.ts +268 -0
- package/src/modules/staff/events.ts +87 -5
- package/src/modules/staff/extension-points.ts +308 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
- package/src/modules/staff/i18n/de.json +899 -0
- package/src/modules/staff/i18n/en.json +899 -0
- package/src/modules/staff/i18n/es.json +899 -0
- package/src/modules/staff/i18n/ko.json +899 -0
- package/src/modules/staff/i18n/pl.json +899 -0
- package/src/modules/staff/lib/crud.ts +14 -3
- package/src/modules/staff/lib/time-tracking/access.ts +155 -0
- package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
- package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
- package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
- package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
- package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
- package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
- package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
- package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
- package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
- package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
- package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
- package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
- package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
- package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
- package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
- package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
- package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
- package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
- package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
- package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
- package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
- package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
- package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
- package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
- package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
- package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
- package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
- package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
- package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
- package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
- package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
- package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
- package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
- package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
- package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
- package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
- package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
- package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
- package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
- package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
- package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
- package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
- package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
- package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
- package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
- package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
- package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
- package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
- package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
- package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
- package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
- package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
- package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
- package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
- package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
- package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
- package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
- package/src/modules/staff/notifications.client.ts +16 -0
- package/src/modules/staff/notifications.handlers.ts +88 -0
- package/src/modules/staff/notifications.ts +163 -0
- package/src/modules/staff/search.ts +191 -1
- package/src/modules/staff/setup.ts +22 -0
- package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
- package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
- package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
- package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
- package/src/modules/staff/translations.ts +17 -0
- package/src/modules/staff/widgets/components.ts +34 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
- package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
- package/src/modules/staff/widgets/notifications/index.ts +1 -0
- package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
|
@@ -8,17 +8,41 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
|
8
8
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
9
9
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
10
10
|
import { emitCrudSideEffects, flushCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
|
|
11
|
-
import { StaffTimeEntry, StaffTeamMember, StaffTimeProject } from "../../../../data/entities.js";
|
|
12
|
-
import {
|
|
11
|
+
import { StaffTimeEntry, StaffTeamMember, StaffTimeProject, StaffTimeTask } from "../../../../data/entities.js";
|
|
12
|
+
import {
|
|
13
|
+
staffTimeEntryBulkSaveSchema
|
|
14
|
+
} from "../../../../data/validators.js";
|
|
15
|
+
import {
|
|
16
|
+
buildTimeEntryLockedError,
|
|
17
|
+
rebaseTimeEntryIntervalToDate,
|
|
18
|
+
reconcileTimeEntryInterval,
|
|
19
|
+
resolveTimeEntryBillable,
|
|
20
|
+
resolveTimeEntryNotesInput,
|
|
21
|
+
resolveTimeEntryProjectId,
|
|
22
|
+
resolveTimeEntrySettings,
|
|
23
|
+
roundedMinutesFor,
|
|
24
|
+
timeEntryTaskMatchesProject,
|
|
25
|
+
toStoredTimeEntryRateOverride
|
|
26
|
+
} from "../../../../commands/timesheets-entries.js";
|
|
13
27
|
import { staffTimeEntryCrudEvents } from "../../../../lib/crud.js";
|
|
28
|
+
import { emitStaffEvent } from "../../../../events.js";
|
|
14
29
|
import { invalidateStaffTimeEntryCache } from "../../../../lib/timesheets/timeEntryCacheInvalidation.js";
|
|
15
30
|
import {
|
|
16
|
-
|
|
31
|
+
STAFF_TIME_TRACKING_RESOURCE_KINDS,
|
|
17
32
|
runStaffMutationGuardAfterSuccess,
|
|
18
33
|
runStaffMutationGuards
|
|
19
34
|
} from "../../../guards.js";
|
|
35
|
+
import { runTimesheetInterceptors } from "../../_shared/withTimesheetInterceptors.js";
|
|
20
36
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
21
37
|
const logger = createLogger("staff");
|
|
38
|
+
function cellDateKey(value) {
|
|
39
|
+
if (!value) return "";
|
|
40
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
41
|
+
return Number.isNaN(date.getTime()) ? String(value) : date.toISOString().slice(0, 10);
|
|
42
|
+
}
|
|
43
|
+
function bulkValidationError(errors) {
|
|
44
|
+
return NextResponse.json({ ok: false, errors }, { status: 422 });
|
|
45
|
+
}
|
|
22
46
|
const metadata = {
|
|
23
47
|
POST: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] }
|
|
24
48
|
};
|
|
@@ -34,8 +58,15 @@ async function POST(req) {
|
|
|
34
58
|
if (!tenantId || !organizationId) {
|
|
35
59
|
throw new CrudHttpError(400, { error: translate("staff.errors.missingScope", "Missing tenant or organization scope.") });
|
|
36
60
|
}
|
|
37
|
-
const
|
|
38
|
-
|
|
61
|
+
const interceptors = await runTimesheetInterceptors({
|
|
62
|
+
request: req,
|
|
63
|
+
method: "POST",
|
|
64
|
+
scope: { container, userId: auth.sub, tenantId, organizationId },
|
|
65
|
+
body: await readJsonSafe(req, {})
|
|
66
|
+
});
|
|
67
|
+
if (!interceptors.ok) return interceptors.response;
|
|
68
|
+
const { session } = interceptors;
|
|
69
|
+
const parsed = staffTimeEntryBulkSaveSchema.safeParse(session.body);
|
|
39
70
|
if (!parsed.success) {
|
|
40
71
|
const errors = parsed.error.issues.map((issue) => ({
|
|
41
72
|
path: issue.path.join("."),
|
|
@@ -44,6 +75,14 @@ async function POST(req) {
|
|
|
44
75
|
return NextResponse.json({ ok: false, errors }, { status: 422 });
|
|
45
76
|
}
|
|
46
77
|
const { entries } = parsed.data;
|
|
78
|
+
const taggedRows = entries.filter((entry) => entry.tagIds !== void 0);
|
|
79
|
+
if (taggedRows.length > 0) {
|
|
80
|
+
const message = translate(
|
|
81
|
+
"staff.timesheets.errors.bulkTagsUnsupported",
|
|
82
|
+
"Tags cannot be changed from the timesheet grid. Open the time entry to edit its tags."
|
|
83
|
+
);
|
|
84
|
+
return bulkValidationError(taggedRows.map(() => ({ path: "entries[].tagIds", message })));
|
|
85
|
+
}
|
|
47
86
|
const em = container.resolve("em").fork();
|
|
48
87
|
const scopeCtx = { tenantId, organizationId };
|
|
49
88
|
const staffMember = await findOneWithDecryption(
|
|
@@ -57,31 +96,77 @@ async function POST(req) {
|
|
|
57
96
|
throw new CrudHttpError(403, { error: translate("staff.timesheets.errors.noStaffMember", "No staff member linked to your account.") });
|
|
58
97
|
}
|
|
59
98
|
const staffMemberId = staffMember.id;
|
|
60
|
-
const
|
|
99
|
+
const referencedTaskIds = [
|
|
61
100
|
...new Set(
|
|
62
|
-
entries.map((
|
|
101
|
+
entries.map((entry) => entry.taskId).filter((id) => typeof id === "string" && id.length > 0)
|
|
63
102
|
)
|
|
64
103
|
];
|
|
104
|
+
const tasksById = /* @__PURE__ */ new Map();
|
|
105
|
+
if (referencedTaskIds.length > 0) {
|
|
106
|
+
const validTasks = await em.find(StaffTimeTask, {
|
|
107
|
+
id: { $in: referencedTaskIds },
|
|
108
|
+
tenantId,
|
|
109
|
+
organizationId,
|
|
110
|
+
deletedAt: null
|
|
111
|
+
}, { fields: ["id", "timeProjectId"] });
|
|
112
|
+
for (const task of validTasks) tasksById.set(task.id, { id: task.id, timeProjectId: task.timeProjectId });
|
|
113
|
+
const invalidTaskIds = referencedTaskIds.filter((id) => !tasksById.has(id));
|
|
114
|
+
if (invalidTaskIds.length > 0) {
|
|
115
|
+
const message = translate("staff.timesheets.errors.taskNotFound", "Task not found or not accessible.");
|
|
116
|
+
return bulkValidationError(
|
|
117
|
+
invalidTaskIds.map((id) => ({ path: "entries[].taskId", message, value: id }))
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const resolvedRows = [];
|
|
122
|
+
const missingProjectErrors = [];
|
|
123
|
+
const taskProjectMismatchErrors = [];
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const task = entry.taskId ? tasksById.get(entry.taskId) ?? null : null;
|
|
126
|
+
const timeProjectId = resolveTimeEntryProjectId(entry.timeProjectId, task);
|
|
127
|
+
if (!timeProjectId) {
|
|
128
|
+
missingProjectErrors.push({
|
|
129
|
+
path: "entries[].timeProjectId",
|
|
130
|
+
message: translate("staff.timesheets.errors.projectRequired", "Time project id is required.")
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (!timeEntryTaskMatchesProject(task, timeProjectId)) {
|
|
135
|
+
taskProjectMismatchErrors.push({
|
|
136
|
+
path: "entries[].taskId",
|
|
137
|
+
message: translate("staff.timesheets.errors.taskNotFound", "Task not found or not accessible."),
|
|
138
|
+
value: entry.taskId
|
|
139
|
+
});
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
resolvedRows.push({ entry, task, timeProjectId });
|
|
143
|
+
}
|
|
144
|
+
if (missingProjectErrors.length > 0 || taskProjectMismatchErrors.length > 0) {
|
|
145
|
+
return bulkValidationError([...missingProjectErrors, ...taskProjectMismatchErrors]);
|
|
146
|
+
}
|
|
147
|
+
const referencedProjectIds = [...new Set(resolvedRows.map((row) => row.timeProjectId))];
|
|
148
|
+
const projectsById = /* @__PURE__ */ new Map();
|
|
65
149
|
if (referencedProjectIds.length > 0) {
|
|
66
150
|
const validProjects = await em.find(StaffTimeProject, {
|
|
67
151
|
id: { $in: referencedProjectIds },
|
|
68
152
|
tenantId,
|
|
69
153
|
organizationId,
|
|
70
154
|
deletedAt: null
|
|
71
|
-
}, { fields: ["id"] });
|
|
72
|
-
const
|
|
73
|
-
|
|
155
|
+
}, { fields: ["id", "currencyCode", "billableByDefault"] });
|
|
156
|
+
for (const project of validProjects) {
|
|
157
|
+
projectsById.set(project.id, {
|
|
158
|
+
currencyCode: project.currencyCode ?? null,
|
|
159
|
+
billableByDefault: project.billableByDefault ?? null
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const invalidIds = referencedProjectIds.filter((id) => !projectsById.has(id));
|
|
74
163
|
if (invalidIds.length > 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
value: id
|
|
82
|
-
}))
|
|
83
|
-
},
|
|
84
|
-
{ status: 422 }
|
|
164
|
+
const message = translate(
|
|
165
|
+
"staff.timesheets.errors.projectNotFound",
|
|
166
|
+
"Time project not found or not accessible."
|
|
167
|
+
);
|
|
168
|
+
return bulkValidationError(
|
|
169
|
+
invalidIds.map((id) => ({ path: "entries[].timeProjectId", message, value: id }))
|
|
85
170
|
);
|
|
86
171
|
}
|
|
87
172
|
}
|
|
@@ -89,21 +174,21 @@ async function POST(req) {
|
|
|
89
174
|
tenantId,
|
|
90
175
|
organizationId,
|
|
91
176
|
userId: auth.sub ?? "",
|
|
92
|
-
resourceKind:
|
|
177
|
+
resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeEntry,
|
|
93
178
|
resourceId: staffMemberId,
|
|
94
179
|
operation: "update",
|
|
95
180
|
requestMethod: req.method,
|
|
96
181
|
requestHeaders: req.headers,
|
|
97
182
|
mutationPayload: parsed.data
|
|
98
183
|
};
|
|
99
|
-
const guardResult = await runStaffMutationGuards(container, guardInput
|
|
184
|
+
const guardResult = await runStaffMutationGuards(container, guardInput);
|
|
100
185
|
if (!guardResult.ok) {
|
|
101
186
|
return NextResponse.json(
|
|
102
187
|
guardResult.errorBody ?? { error: "Operation blocked by guard" },
|
|
103
188
|
{ status: guardResult.errorStatus ?? 422 }
|
|
104
189
|
);
|
|
105
190
|
}
|
|
106
|
-
const existingIds =
|
|
191
|
+
const existingIds = resolvedRows.map((row) => row.entry.id).filter((id) => typeof id === "string" && id.length > 0);
|
|
107
192
|
if (existingIds.length > 0) {
|
|
108
193
|
const resolvedExisting = await em.find(
|
|
109
194
|
StaffTimeEntry,
|
|
@@ -129,6 +214,42 @@ async function POST(req) {
|
|
|
129
214
|
);
|
|
130
215
|
}
|
|
131
216
|
}
|
|
217
|
+
const idlessRows = resolvedRows.filter((row) => !row.entry.id);
|
|
218
|
+
const idlessCells = idlessRows.map((row) => `${cellDateKey(row.entry.date)}|${row.timeProjectId}`);
|
|
219
|
+
const lockConditions = [];
|
|
220
|
+
if (existingIds.length > 0) lockConditions.push({ id: { $in: existingIds } });
|
|
221
|
+
if (idlessCells.length > 0) {
|
|
222
|
+
lockConditions.push({
|
|
223
|
+
date: { $in: idlessRows.map((row) => row.entry.date) },
|
|
224
|
+
timeProjectId: { $in: [...new Set(idlessRows.map((row) => row.timeProjectId))] }
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (lockConditions.length > 0) {
|
|
228
|
+
const existingIdSet = new Set(existingIds);
|
|
229
|
+
const idlessCellSet = new Set(idlessCells);
|
|
230
|
+
const lockedCandidates = await em.find(
|
|
231
|
+
StaffTimeEntry,
|
|
232
|
+
{
|
|
233
|
+
tenantId,
|
|
234
|
+
organizationId,
|
|
235
|
+
staffMemberId,
|
|
236
|
+
deletedAt: null,
|
|
237
|
+
lockedReportId: { $ne: null },
|
|
238
|
+
$or: lockConditions
|
|
239
|
+
},
|
|
240
|
+
{ fields: ["id", "lockedReportId", "date", "timeProjectId"] }
|
|
241
|
+
);
|
|
242
|
+
const blocked = lockedCandidates.filter(
|
|
243
|
+
(row) => existingIdSet.has(row.id) || idlessCellSet.has(`${cellDateKey(row.date)}|${row.timeProjectId ?? ""}`)
|
|
244
|
+
);
|
|
245
|
+
if (blocked.length > 0) {
|
|
246
|
+
throw buildTimeEntryLockedError(
|
|
247
|
+
blocked.map((row) => ({ id: row.id, lockedReportId: row.lockedReportId })),
|
|
248
|
+
translate
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const settings = await resolveTimeEntrySettings(container, tenantId);
|
|
132
253
|
const { counts, pendingChanges } = await em.transactional(async (trx) => {
|
|
133
254
|
let created = 0;
|
|
134
255
|
let updated = 0;
|
|
@@ -142,7 +263,9 @@ async function POST(req) {
|
|
|
142
263
|
scopeCtx
|
|
143
264
|
) : [];
|
|
144
265
|
const existingMap = new Map(existingEntries.map((entry) => [entry.id, entry]));
|
|
145
|
-
for (const entry of
|
|
266
|
+
for (const { entry, task, timeProjectId } of resolvedRows) {
|
|
267
|
+
const project = projectsById.get(timeProjectId) ?? null;
|
|
268
|
+
const notes = resolveTimeEntryNotesInput(entry);
|
|
146
269
|
if (entry.id && existingMap.has(entry.id)) {
|
|
147
270
|
const existing = existingMap.get(entry.id);
|
|
148
271
|
if (entry.durationMinutes === 0) {
|
|
@@ -150,10 +273,37 @@ async function POST(req) {
|
|
|
150
273
|
deleted++;
|
|
151
274
|
changes.push({ action: "deleted", entity: existing });
|
|
152
275
|
} else {
|
|
276
|
+
const interval = reconcileTimeEntryInterval({
|
|
277
|
+
stored: rebaseTimeEntryIntervalToDate({
|
|
278
|
+
stored: {
|
|
279
|
+
startedAt: existing.startedAt ?? null,
|
|
280
|
+
endedAt: existing.endedAt ?? null,
|
|
281
|
+
durationMinutes: existing.durationMinutes
|
|
282
|
+
},
|
|
283
|
+
fromDate: existing.date,
|
|
284
|
+
toDate: entry.date
|
|
285
|
+
}),
|
|
286
|
+
startedAt: void 0,
|
|
287
|
+
endedAt: void 0,
|
|
288
|
+
durationMinutes: entry.durationMinutes
|
|
289
|
+
});
|
|
290
|
+
const projectChanged = timeProjectId !== (existing.timeProjectId ?? null);
|
|
153
291
|
existing.date = entry.date;
|
|
154
|
-
existing.timeProjectId =
|
|
155
|
-
existing.
|
|
156
|
-
existing.
|
|
292
|
+
existing.timeProjectId = timeProjectId;
|
|
293
|
+
if (projectChanged) existing.rateCurrencyCode = project?.currencyCode ?? null;
|
|
294
|
+
existing.startedAt = interval.startedAt;
|
|
295
|
+
existing.endedAt = interval.endedAt;
|
|
296
|
+
existing.durationMinutes = interval.durationMinutes;
|
|
297
|
+
existing.roundedMinutes = roundedMinutesFor(interval.durationMinutes, settings, {
|
|
298
|
+
tenantId,
|
|
299
|
+
organizationId
|
|
300
|
+
});
|
|
301
|
+
if (entry.taskId !== void 0) existing.taskId = task?.id ?? null;
|
|
302
|
+
if (entry.isBillable !== void 0) existing.isBillable = entry.isBillable;
|
|
303
|
+
if (entry.rateOverrideAmount !== void 0) {
|
|
304
|
+
existing.rateOverrideAmount = toStoredTimeEntryRateOverride(entry.rateOverrideAmount);
|
|
305
|
+
}
|
|
306
|
+
if (notes !== void 0) existing.notes = notes;
|
|
157
307
|
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
158
308
|
updated++;
|
|
159
309
|
changes.push({ action: "updated", entity: existing });
|
|
@@ -165,9 +315,22 @@ async function POST(req) {
|
|
|
165
315
|
organizationId,
|
|
166
316
|
staffMemberId,
|
|
167
317
|
date: entry.date,
|
|
168
|
-
timeProjectId
|
|
318
|
+
timeProjectId,
|
|
319
|
+
taskId: task?.id ?? null,
|
|
169
320
|
durationMinutes: entry.durationMinutes,
|
|
170
|
-
|
|
321
|
+
roundedMinutes: roundedMinutesFor(entry.durationMinutes, settings, {
|
|
322
|
+
tenantId,
|
|
323
|
+
organizationId
|
|
324
|
+
}),
|
|
325
|
+
notes: notes ?? null,
|
|
326
|
+
isBillable: resolveTimeEntryBillable({
|
|
327
|
+
requested: entry.isBillable,
|
|
328
|
+
project,
|
|
329
|
+
settings,
|
|
330
|
+
scope: { tenantId, organizationId }
|
|
331
|
+
}),
|
|
332
|
+
rateOverrideAmount: toStoredTimeEntryRateOverride(entry.rateOverrideAmount),
|
|
333
|
+
rateCurrencyCode: project?.currencyCode ?? null,
|
|
171
334
|
source: "manual",
|
|
172
335
|
createdAt: now,
|
|
173
336
|
updatedAt: now
|
|
@@ -214,14 +377,23 @@ async function POST(req) {
|
|
|
214
377
|
tenantId,
|
|
215
378
|
organizationId,
|
|
216
379
|
userId: auth.sub ?? "",
|
|
217
|
-
resourceKind:
|
|
380
|
+
resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeEntry,
|
|
218
381
|
resourceId: staffMemberId,
|
|
219
382
|
operation: "update",
|
|
220
383
|
requestMethod: req.method,
|
|
221
384
|
requestHeaders: req.headers
|
|
222
385
|
});
|
|
223
386
|
}
|
|
224
|
-
|
|
387
|
+
void emitStaffEvent("staff.timesheets.time_entry.bulk_updated", {
|
|
388
|
+
tenantId,
|
|
389
|
+
organizationId,
|
|
390
|
+
staffMemberId,
|
|
391
|
+
...counts,
|
|
392
|
+
entryIds: pendingChanges.map((change) => change.entity.id)
|
|
393
|
+
}, { persistent: true }).catch((err) => {
|
|
394
|
+
logger.error("staff.timesheets emit time_entry.bulk_updated failed", { err });
|
|
395
|
+
});
|
|
396
|
+
return session.respond(200, { ok: true, ...counts });
|
|
225
397
|
} catch (err) {
|
|
226
398
|
if (err instanceof CrudHttpError) {
|
|
227
399
|
return NextResponse.json(err.body, { status: err.status });
|
|
@@ -240,7 +412,7 @@ const openApi = {
|
|
|
240
412
|
methods: {
|
|
241
413
|
POST: {
|
|
242
414
|
summary: "Bulk save time entries",
|
|
243
|
-
description: "Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted.",
|
|
415
|
+
description: "Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted. A row may name a taskId and let timeProjectId follow from it; task, description/notes precedence, isBillable defaulting, the rate override and the project currency snapshot follow the single-entry path exactly. tagIds is rejected with 422 \u2014 tags are edited from the time entry itself so their audit trail and undo stay intact. The whole batch is rejected with 409 time_entry_locked when any row targets \u2014 by id, or by landing on its (project, date) cell \u2014 an entry frozen in a closed report.",
|
|
244
416
|
requestBody: {
|
|
245
417
|
contentType: "application/json",
|
|
246
418
|
schema: staffTimeEntryBulkSaveSchema
|
|
@@ -264,6 +436,17 @@ const openApi = {
|
|
|
264
436
|
errors: z.array(z.object({ path: z.string(), message: z.string() }))
|
|
265
437
|
})
|
|
266
438
|
},
|
|
439
|
+
{
|
|
440
|
+
status: 409,
|
|
441
|
+
description: "One or more rows target a time entry locked in a closed report; nothing was saved",
|
|
442
|
+
schema: z.object({
|
|
443
|
+
code: z.literal("time_entry_locked"),
|
|
444
|
+
error: z.string(),
|
|
445
|
+
lockedReportId: z.string().uuid().nullable(),
|
|
446
|
+
lockedEntryIds: z.array(z.string().uuid()),
|
|
447
|
+
lockedReportIds: z.array(z.string().uuid())
|
|
448
|
+
})
|
|
449
|
+
},
|
|
267
450
|
{ status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
|
|
268
451
|
{ status: 403, description: "Forbidden", schema: z.object({ error: z.string() }) }
|
|
269
452
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/staff/api/timesheets/time-entries/bulk/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { emitCrudSideEffects, flushCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeEntry, StaffTeamMember, StaffTimeProject } from '../../../../data/entities'\nimport { staffTimeEntryBulkSaveSchema } from '../../../../data/validators'\nimport { staffTimeEntryCrudEvents } from '../../../../lib/crud'\nimport { invalidateStaffTimeEntryCache } from '../../../../lib/timesheets/timeEntryCacheInvalidation'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const body = await readJsonSafe(req, {})\n const parsed = staffTimeEntryBulkSaveSchema.safeParse(body)\n if (!parsed.success) {\n const errors = parsed.error.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n }))\n return NextResponse.json({ ok: false, errors }, { status: 422 })\n }\n\n const { entries } = parsed.data\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n const staffMemberId = staffMember.id\n\n // Validate that all referenced timeProjectIds exist and are in-scope\n const referencedProjectIds = [\n ...new Set(\n entries\n .map((e) => e.timeProjectId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0),\n ),\n ]\n if (referencedProjectIds.length > 0) {\n const validProjects = await em.find(StaffTimeProject, {\n id: { $in: referencedProjectIds },\n tenantId,\n organizationId,\n deletedAt: null,\n }, { fields: ['id'] })\n const validIds = new Set(validProjects.map((p) => p.id))\n const invalidIds = referencedProjectIds.filter((id) => !validIds.has(id))\n if (invalidIds.length > 0) {\n return NextResponse.json(\n {\n ok: false,\n errors: invalidIds.map((id) => ({\n path: 'entries[].timeProjectId',\n message: translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.'),\n value: id,\n })),\n },\n { status: 422 },\n )\n }\n }\n\n const guardInput = {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n }\n const guardResult = await runStaffMutationGuards(container, guardInput, resolveUserFeatures(auth))\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 existingIds = entries\n .map((entry) => entry.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n\n // Validate referenced entry IDs upfront: a stale or foreign UUID would\n // otherwise fall through to the create branch in the loop below and insert\n // a duplicate row with that ID-less new identity. Reject as 422 instead.\n if (existingIds.length > 0) {\n const resolvedExisting = await em.find(\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n { fields: ['id'] },\n )\n const resolvedIdSet = new Set(resolvedExisting.map((entry) => entry.id))\n const invalidIds = existingIds.filter((id) => !resolvedIdSet.has(id))\n if (invalidIds.length > 0) {\n return NextResponse.json(\n {\n ok: false,\n errors: invalidIds.map((id) => ({\n path: 'entries[].id',\n message: translate(\n 'staff.timesheets.errors.entryNotFound',\n 'Time entry not found, deleted, or not owned by you.',\n ),\n value: id,\n })),\n },\n { status: 422 },\n )\n }\n }\n\n type PendingChange = {\n action: 'created' | 'updated' | 'deleted'\n entity: StaffTimeEntry\n }\n\n const { counts, pendingChanges } = await em.transactional(async (trx) => {\n let created = 0\n let updated = 0\n let deleted = 0\n const changes: PendingChange[] = []\n\n const existingEntries = existingIds.length > 0\n ? await findWithDecryption(\n trx,\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n {},\n scopeCtx,\n )\n : []\n\n const existingMap = new Map(existingEntries.map((entry) => [entry.id, entry]))\n\n for (const entry of entries) {\n if (entry.id && existingMap.has(entry.id)) {\n const existing = existingMap.get(entry.id)!\n if (entry.durationMinutes === 0) {\n existing.deletedAt = new Date()\n deleted++\n changes.push({ action: 'deleted', entity: existing })\n } else {\n existing.date = entry.date\n existing.timeProjectId = entry.timeProjectId\n existing.durationMinutes = entry.durationMinutes\n existing.notes = entry.notes ?? existing.notes\n existing.updatedAt = new Date()\n updated++\n changes.push({ action: 'updated', entity: existing })\n }\n } else {\n const now = new Date()\n const newEntry = trx.create(StaffTimeEntry, {\n tenantId,\n organizationId,\n staffMemberId,\n date: entry.date,\n timeProjectId: entry.timeProjectId,\n durationMinutes: entry.durationMinutes,\n notes: entry.notes ?? null,\n source: 'manual',\n createdAt: now,\n updatedAt: now,\n })\n created++\n changes.push({ action: 'created', entity: newEntry })\n }\n }\n\n await trx.flush()\n return { counts: { created, updated, deleted }, pendingChanges: changes }\n })\n\n const dataEngine = container.resolve<DataEngine>('dataEngine')\n for (const change of pendingChanges) {\n await emitCrudSideEffects({\n dataEngine,\n action: change.action,\n entity: change.entity,\n identifiers: {\n id: change.entity.id,\n organizationId: change.entity.organizationId,\n tenantId: change.entity.tenantId,\n },\n events: staffTimeEntryCrudEvents,\n })\n }\n await flushCrudSideEffects(dataEngine)\n\n const invalidatedRecordIds = new Set<string>()\n for (const change of pendingChanges) {\n if (invalidatedRecordIds.has(change.entity.id)) continue\n invalidatedRecordIds.add(change.entity.id)\n await invalidateStaffTimeEntryCache(\n container,\n {\n id: change.entity.id,\n organizationId: change.entity.organizationId,\n tenantId: change.entity.tenantId,\n },\n tenantId,\n `bulk:${change.action}`,\n )\n }\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return NextResponse.json({ ok: true, ...counts }, { status: 200 })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.timesheets.time-entries.bulk failed', { err })\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.bulkSave', 'Failed to bulk save time entries.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Bulk save time entries',\n methods: {\n POST: {\n summary: 'Bulk save time entries',\n description: 'Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTimeEntryBulkSaveSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Bulk save completed',\n schema: z.object({\n ok: z.literal(true),\n created: z.number(),\n updated: z.number(),\n deleted: z.number(),\n }),\n },\n {\n status: 422,\n description: 'Validation error',\n schema: z.object({\n ok: z.literal(false),\n errors: z.array(z.object({ path: z.string(), message: z.string() })),\n }),\n },\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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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { emitCrudSideEffects, flushCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeEntry, StaffTeamMember, StaffTimeProject, StaffTimeTask } from '../../../../data/entities'\nimport {\n staffTimeEntryBulkSaveSchema,\n type StaffTimeEntryBulkSaveInput,\n} from '../../../../data/validators'\nimport {\n buildTimeEntryLockedError,\n rebaseTimeEntryIntervalToDate,\n reconcileTimeEntryInterval,\n resolveTimeEntryBillable,\n resolveTimeEntryNotesInput,\n resolveTimeEntryProjectId,\n resolveTimeEntrySettings,\n roundedMinutesFor,\n timeEntryTaskMatchesProject,\n toStoredTimeEntryRateOverride,\n type LockedTimeEntryRef,\n} from '../../../../commands/timesheets-entries'\nimport { staffTimeEntryCrudEvents } from '../../../../lib/crud'\nimport { emitStaffEvent } from '../../../../events'\nimport { invalidateStaffTimeEntryCache } from '../../../../lib/timesheets/timeEntryCacheInvalidation'\nimport {\n STAFF_TIME_TRACKING_RESOURCE_KINDS,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\nimport { runTimesheetInterceptors } from '../../_shared/withTimesheetInterceptors'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\n/**\n * A grid cell is addressed by the day the work started, never by the timestamp,\n * so both sides of the comparison collapse to `YYYY-MM-DD` (D-8).\n */\nfunction cellDateKey(value: Date | string | null | undefined): string {\n if (!value) return ''\n const date = value instanceof Date ? value : new Date(value)\n return Number.isNaN(date.getTime()) ? String(value) : date.toISOString().slice(0, 10)\n}\n\ntype BulkRowError = { path: string; message: string; value?: unknown }\n\n/**\n * The batch answers with a per-row error list rather than a thrown 422: one grid\n * save spans a week of cells, so the client needs to know WHICH row it must fix.\n * The single-entry command raises the same refusals as `CrudHttpError` field\n * errors \u2014 same rule, different reporting shape.\n */\nfunction bulkValidationError(errors: BulkRowError[]): NextResponse {\n return NextResponse.json({ ok: false, errors }, { status: 422 })\n}\n\n/**\n * A row as the write loop needs it: the payload plus the two things that must be\n * settled before the lock gate can address a cell \u2014 the task it names (validated\n * in scope) and the project the entry actually lands on, which a task-only row\n * inherits from its task exactly as the single-entry path does.\n */\ntype BulkEntryInput = StaffTimeEntryBulkSaveInput['entries'][number]\n\ntype ResolvedBulkRow = {\n entry: BulkEntryInput\n task: { id: string; timeProjectId: string } | null\n timeProjectId: string\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const interceptors = await runTimesheetInterceptors({\n request: req,\n method: 'POST',\n scope: { container, userId: auth.sub, tenantId, organizationId },\n body: await readJsonSafe<Record<string, unknown>>(req, {}),\n })\n if (!interceptors.ok) return interceptors.response\n const { session } = interceptors\n\n const parsed = staffTimeEntryBulkSaveSchema.safeParse(session.body)\n if (!parsed.success) {\n const errors = parsed.error.issues.map((issue) => ({\n path: issue.path.join('.'),\n message: issue.message,\n }))\n return NextResponse.json({ ok: false, errors }, { status: 422 })\n }\n\n const { entries } = parsed.data\n\n // Tags are REFUSED here rather than accepted and dropped.\n //\n // The single-entry path assigns tags by dispatching the tag commands, which\n // fork their own EntityManager so they can carry their own audit entry, undo\n // and lock check. Inside this route's `em.transactional` that fork would not\n // see the rows the transaction has not committed yet, and dispatching AFTER\n // the commit would answer `{ ok: true, created: n }` for a batch whose tags\n // silently failed \u2014 a response carrying only counts cannot say which cell\n // lost them, which is the very silent-success class this route is being\n // fixed for. So the row is rejected and the user is pointed at the entry\n // dialog, which writes tags through the commands with all of their\n // guarantees intact.\n const taggedRows = entries.filter((entry) => entry.tagIds !== undefined)\n if (taggedRows.length > 0) {\n const message = translate(\n 'staff.timesheets.errors.bulkTagsUnsupported',\n 'Tags cannot be changed from the timesheet grid. Open the time entry to edit its tags.',\n )\n return bulkValidationError(taggedRows.map(() => ({ path: 'entries[].tagIds', message })))\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n const staffMemberId = staffMember.id\n\n // Validate that all referenced taskIds exist and are in-scope, in one query\n // for the whole batch. Same rule the single-entry path applies through\n // `requireTaskInScope`: a dangling task reference is refused rather than\n // stored, because the task rollups aggregate entries by `task_id` and a stale\n // or foreign UUID would silently move hours onto another project's card.\n const referencedTaskIds = [\n ...new Set(\n entries\n .map((entry) => entry.taskId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0),\n ),\n ]\n const tasksById = new Map<string, { id: string; timeProjectId: string }>()\n if (referencedTaskIds.length > 0) {\n const validTasks = await em.find(StaffTimeTask, {\n id: { $in: referencedTaskIds },\n tenantId,\n organizationId,\n deletedAt: null,\n }, { fields: ['id', 'timeProjectId'] })\n for (const task of validTasks) tasksById.set(task.id, { id: task.id, timeProjectId: task.timeProjectId })\n const invalidTaskIds = referencedTaskIds.filter((id) => !tasksById.has(id))\n if (invalidTaskIds.length > 0) {\n const message = translate('staff.timesheets.errors.taskNotFound', 'Task not found or not accessible.')\n return bulkValidationError(\n invalidTaskIds.map((id) => ({ path: 'entries[].taskId', message, value: id })),\n )\n }\n }\n\n // Every row is pinned to the project it will actually land on BEFORE the lock\n // gate runs, because the gate addresses a cell by (project, date) \u2014 a task row\n // that inherited its project must be checked against that inherited cell, not\n // against a blank one.\n const resolvedRows: ResolvedBulkRow[] = []\n const missingProjectErrors: BulkRowError[] = []\n const taskProjectMismatchErrors: BulkRowError[] = []\n for (const entry of entries) {\n const task = entry.taskId ? tasksById.get(entry.taskId) ?? null : null\n const timeProjectId = resolveTimeEntryProjectId(entry.timeProjectId, task)\n if (!timeProjectId) {\n missingProjectErrors.push({\n path: 'entries[].timeProjectId',\n message: translate('staff.timesheets.errors.projectRequired', 'Time project id is required.'),\n })\n continue\n }\n if (!timeEntryTaskMatchesProject(task, timeProjectId)) {\n taskProjectMismatchErrors.push({\n path: 'entries[].taskId',\n message: translate('staff.timesheets.errors.taskNotFound', 'Task not found or not accessible.'),\n value: entry.taskId,\n })\n continue\n }\n resolvedRows.push({ entry, task, timeProjectId })\n }\n if (missingProjectErrors.length > 0 || taskProjectMismatchErrors.length > 0) {\n return bulkValidationError([...missingProjectErrors, ...taskProjectMismatchErrors])\n }\n\n // Validate that all referenced timeProjectIds exist and are in-scope. The\n // project rows are kept, not just their ids: a created entry snapshots the\n // project's currency and inherits its billable default (D-3), so one query\n // serves validation and both defaults.\n const referencedProjectIds = [...new Set(resolvedRows.map((row) => row.timeProjectId))]\n const projectsById = new Map<string, { currencyCode?: string | null; billableByDefault?: boolean | null }>()\n if (referencedProjectIds.length > 0) {\n const validProjects = await em.find(StaffTimeProject, {\n id: { $in: referencedProjectIds },\n tenantId,\n organizationId,\n deletedAt: null,\n }, { fields: ['id', 'currencyCode', 'billableByDefault'] })\n for (const project of validProjects) {\n projectsById.set(project.id, {\n currencyCode: project.currencyCode ?? null,\n billableByDefault: project.billableByDefault ?? null,\n })\n }\n const invalidIds = referencedProjectIds.filter((id) => !projectsById.has(id))\n if (invalidIds.length > 0) {\n const message = translate(\n 'staff.timesheets.errors.projectNotFound',\n 'Time project not found or not accessible.',\n )\n return bulkValidationError(\n invalidIds.map((id) => ({ path: 'entries[].timeProjectId', message, value: id })),\n )\n }\n }\n\n const guardInput = {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeEntry,\n resourceId: staffMemberId,\n operation: 'update' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n }\n const guardResult = await runStaffMutationGuards(container, guardInput)\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 existingIds = resolvedRows\n .map((row) => row.entry.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n\n // Validate referenced entry IDs upfront: a stale or foreign UUID would\n // otherwise fall through to the create branch in the loop below and insert\n // a duplicate row with that ID-less new identity. Reject as 422 instead.\n if (existingIds.length > 0) {\n const resolvedExisting = await em.find(\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n { fields: ['id'] },\n )\n const resolvedIdSet = new Set(resolvedExisting.map((entry) => entry.id))\n const invalidIds = existingIds.filter((id) => !resolvedIdSet.has(id))\n if (invalidIds.length > 0) {\n return NextResponse.json(\n {\n ok: false,\n errors: invalidIds.map((id) => ({\n path: 'entries[].id',\n message: translate(\n 'staff.timesheets.errors.entryNotFound',\n 'Time entry not found, deleted, or not owned by you.',\n ),\n value: id,\n })),\n },\n { status: 422 },\n )\n }\n }\n\n // --- Lock gate (risk R3) -------------------------------------------------\n //\n // This is the endpoint the lock is most likely to leak through, because the\n // grid addresses a cell by (project, date) and only carries an id when the\n // client happened to have one loaded. So both shapes are checked:\n //\n // * a row naming an id that resolves to a frozen entry, and\n // * an id-less row landing on a (project, date) cell a frozen entry already\n // occupies \u2014 the grid renders one summed value per cell, so creating a\n // second row there silently restates hours a closed report has billed.\n //\n // The batch fails WHOLESALE, before the transaction opens. A grid save is one\n // user gesture spanning a week of cells and the response carries only counts,\n // so applying the unlocked rows would leave the client unable to tell which\n // cells landed \u2014 and the route already rejects the whole payload for an\n // unknown project or a stale entry id, so a partial 409 would be the odd one\n // out. The 409 names every offending id and the reports that froze them, so\n // the grid can mark exactly those cells read-only and the user retries the\n // rest after reloading (which is also what makes the lock badge appear).\n //\n // The cell is addressed by the RESOLVED project, so a task row that inherited\n // its project is checked against the cell it will really occupy.\n const idlessRows = resolvedRows.filter((row) => !row.entry.id)\n const idlessCells = idlessRows.map((row) => `${cellDateKey(row.entry.date)}|${row.timeProjectId}`)\n const lockConditions: Record<string, unknown>[] = []\n if (existingIds.length > 0) lockConditions.push({ id: { $in: existingIds } })\n if (idlessCells.length > 0) {\n lockConditions.push({\n date: { $in: idlessRows.map((row) => row.entry.date) },\n timeProjectId: { $in: [...new Set(idlessRows.map((row) => row.timeProjectId))] },\n })\n }\n if (lockConditions.length > 0) {\n const existingIdSet = new Set(existingIds)\n const idlessCellSet = new Set(idlessCells)\n const lockedCandidates = await em.find(\n StaffTimeEntry,\n {\n tenantId,\n organizationId,\n staffMemberId,\n deletedAt: null,\n lockedReportId: { $ne: null },\n $or: lockConditions,\n },\n { fields: ['id', 'lockedReportId', 'date', 'timeProjectId'] },\n )\n // The (date \u00D7 project) condition above is a cross product, so it can match a\n // locked entry on a cell the payload never touches \u2014 narrowed here.\n const blocked: LockedTimeEntryRef[] = lockedCandidates.filter(\n (row) =>\n existingIdSet.has(row.id) ||\n idlessCellSet.has(`${cellDateKey(row.date)}|${row.timeProjectId ?? ''}`),\n )\n if (blocked.length > 0) {\n throw buildTimeEntryLockedError(\n blocked.map((row) => ({ id: row.id, lockedReportId: row.lockedReportId })),\n translate,\n )\n }\n }\n\n type PendingChange = {\n action: 'created' | 'updated' | 'deleted'\n entity: StaffTimeEntry\n }\n\n // D-7 makes `rounded_minutes` the only input to cost, and this route writes\n // durations outside the entries command \u2014 so every row it touches is rounded\n // with the same tenant rule the command uses. The settings read is hoisted out\n // of the loop because it is tenant-scoped and identical for every row; the\n // billable default a created row falls back to comes from the same snapshot.\n const settings = await resolveTimeEntrySettings(container, tenantId)\n\n const { counts, pendingChanges } = await em.transactional(async (trx) => {\n let created = 0\n let updated = 0\n let deleted = 0\n const changes: PendingChange[] = []\n\n const existingEntries = existingIds.length > 0\n ? await findWithDecryption(\n trx,\n StaffTimeEntry,\n { id: { $in: existingIds }, tenantId, organizationId, staffMemberId, deletedAt: null },\n {},\n scopeCtx,\n )\n : []\n\n const existingMap = new Map(existingEntries.map((entry) => [entry.id, entry]))\n\n for (const { entry, task, timeProjectId } of resolvedRows) {\n const project = projectsById.get(timeProjectId) ?? null\n // Both branches read the five fields the schema has always accepted\n // through the SAME helpers the single-entry command uses, so a grid write\n // and a dialog write cannot settle them differently (#silent-no-op).\n const notes = resolveTimeEntryNotesInput(entry)\n if (entry.id && existingMap.has(entry.id)) {\n const existing = existingMap.get(entry.id)!\n if (entry.durationMinutes === 0) {\n existing.deletedAt = new Date()\n deleted++\n changes.push({ action: 'deleted', entity: existing })\n } else {\n // T4.10(a). A grid cell carries a duration and nothing else, so writing\n // it straight onto an entry that has clocks would store a two-hour\n // duration against a 09:00\u201310:00 span \u2014 exactly the contradiction the\n // single-entry path already refuses to store. The same reconciliation\n // is called here rather than re-derived: only-a-duration means\n // `started_at` anchors and `ended_at` shifts to match, a clock that\n // does not exist is never manufactured (so a cell whose entry has no\n // clocks keeps having none, and a running timer is not stopped), and a\n // re-dated row takes its clocks with it.\n const interval = reconcileTimeEntryInterval({\n stored: rebaseTimeEntryIntervalToDate({\n stored: {\n startedAt: existing.startedAt ?? null,\n endedAt: existing.endedAt ?? null,\n durationMinutes: existing.durationMinutes,\n },\n fromDate: existing.date,\n toDate: entry.date,\n }),\n startedAt: undefined,\n endedAt: undefined,\n durationMinutes: entry.durationMinutes,\n })\n const projectChanged = timeProjectId !== (existing.timeProjectId ?? null)\n existing.date = entry.date\n existing.timeProjectId = timeProjectId\n // Snapshotted, never joined at read time, so a later project currency\n // change cannot re-denominate money already reported (D-3) \u2014 and only\n // restated when the entry actually moves between projects, exactly as\n // the single-entry update command does it.\n if (projectChanged) existing.rateCurrencyCode = project?.currencyCode ?? null\n existing.startedAt = interval.startedAt\n existing.endedAt = interval.endedAt\n existing.durationMinutes = interval.durationMinutes\n // D-7 keeps `rounded_minutes` the only input to cost, so it is restated\n // from the effective duration the reconciliation settled on.\n existing.roundedMinutes = roundedMinutesFor(interval.durationMinutes, settings, {\n tenantId,\n organizationId,\n })\n if (entry.taskId !== undefined) existing.taskId = task?.id ?? null\n if (entry.isBillable !== undefined) existing.isBillable = entry.isBillable\n if (entry.rateOverrideAmount !== undefined) {\n existing.rateOverrideAmount = toStoredTimeEntryRateOverride(entry.rateOverrideAmount)\n }\n if (notes !== undefined) existing.notes = notes\n existing.updatedAt = new Date()\n updated++\n changes.push({ action: 'updated', entity: existing })\n }\n } else {\n const now = new Date()\n const newEntry = trx.create(StaffTimeEntry, {\n tenantId,\n organizationId,\n staffMemberId,\n date: entry.date,\n timeProjectId,\n taskId: task?.id ?? null,\n durationMinutes: entry.durationMinutes,\n roundedMinutes: roundedMinutesFor(entry.durationMinutes, settings, {\n tenantId,\n organizationId,\n }),\n notes: notes ?? null,\n isBillable: resolveTimeEntryBillable({\n requested: entry.isBillable,\n project,\n settings,\n scope: { tenantId, organizationId },\n }),\n rateOverrideAmount: toStoredTimeEntryRateOverride(entry.rateOverrideAmount),\n rateCurrencyCode: project?.currencyCode ?? null,\n source: 'manual',\n createdAt: now,\n updatedAt: now,\n })\n created++\n changes.push({ action: 'created', entity: newEntry })\n }\n }\n\n await trx.flush()\n return { counts: { created, updated, deleted }, pendingChanges: changes }\n })\n\n const dataEngine = container.resolve<DataEngine>('dataEngine')\n for (const change of pendingChanges) {\n await emitCrudSideEffects({\n dataEngine,\n action: change.action,\n entity: change.entity,\n identifiers: {\n id: change.entity.id,\n organizationId: change.entity.organizationId,\n tenantId: change.entity.tenantId,\n },\n events: staffTimeEntryCrudEvents,\n })\n }\n await flushCrudSideEffects(dataEngine)\n\n const invalidatedRecordIds = new Set<string>()\n for (const change of pendingChanges) {\n if (invalidatedRecordIds.has(change.entity.id)) continue\n invalidatedRecordIds.add(change.entity.id)\n await invalidateStaffTimeEntryCache(\n container,\n {\n id: change.entity.id,\n organizationId: change.entity.organizationId,\n tenantId: change.entity.tenantId,\n },\n tenantId,\n `bulk:${change.action}`,\n )\n }\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: STAFF_TIME_TRACKING_RESOURCE_KINDS.timeEntry,\n resourceId: staffMemberId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n // One batch-level event beside the per-row CRUD events already flushed above.\n // It carries no `id`, so the entry-scoped subscribers that key off one record\n // skip it and only the batch-aware ones (payroll exports, audit mirrors) react.\n void emitStaffEvent('staff.timesheets.time_entry.bulk_updated', {\n tenantId,\n organizationId,\n staffMemberId,\n ...counts,\n entryIds: pendingChanges.map((change) => change.entity.id),\n }, { persistent: true }).catch((err) => {\n logger.error('staff.timesheets emit time_entry.bulk_updated failed', { err })\n })\n\n return session.respond(200, { ok: true, ...counts })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('staff.timesheets.time-entries.bulk failed', { err })\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.bulkSave', 'Failed to bulk save time entries.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Bulk save time entries',\n methods: {\n POST: {\n summary: 'Bulk save time entries',\n description:\n 'Creates, updates, or soft-deletes multiple time entries in a single request. Entries with durationMinutes=0 and an existing id are soft-deleted. A row may name a taskId and let timeProjectId follow from it; task, description/notes precedence, isBillable defaulting, the rate override and the project currency snapshot follow the single-entry path exactly. tagIds is rejected with 422 \u2014 tags are edited from the time entry itself so their audit trail and undo stay intact. The whole batch is rejected with 409 time_entry_locked when any row targets \u2014 by id, or by landing on its (project, date) cell \u2014 an entry frozen in a closed report.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTimeEntryBulkSaveSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Bulk save completed',\n schema: z.object({\n ok: z.literal(true),\n created: z.number(),\n updated: z.number(),\n deleted: z.number(),\n }),\n },\n {\n status: 422,\n description: 'Validation error',\n schema: z.object({\n ok: z.literal(false),\n errors: z.array(z.object({ path: z.string(), message: z.string() })),\n }),\n },\n {\n status: 409,\n description: 'One or more rows target a time entry locked in a closed report; nothing was saved',\n schema: z.object({\n code: z.literal('time_entry_locked'),\n error: z.string(),\n lockedReportId: z.string().uuid().nullable(),\n lockedEntryIds: z.array(z.string().uuid()),\n lockedReportIds: z.array(z.string().uuid()),\n }),\n },\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
|
}
|