@open-mercato/core 0.7.1-develop.7178.1.ab93fbaf4e → 0.7.1-develop.7180.1.9717fbbb43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/staff_time_entry/index.js +14 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +2 -2
- package/dist/generated/entities/staff_time_entry_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_entry_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +16 -0
- package/dist/generated/entities/staff_time_project/index.js.map +2 -2
- package/dist/generated/entities/staff_time_report/index.js +57 -0
- package/dist/generated/entities/staff_time_report/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_entry/index.js +27 -0
- package/dist/generated/entities/staff_time_report_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_event/index.js +21 -0
- package/dist/generated/entities/staff_time_report_event/index.js.map +7 -0
- package/dist/generated/entities/staff_time_report_project/index.js +15 -0
- package/dist/generated/entities/staff_time_report_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_tag/index.js +21 -0
- package/dist/generated/entities/staff_time_tag/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task/index.js +37 -0
- package/dist/generated/entities/staff_time_task/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_comment/index.js +21 -0
- package/dist/generated/entities/staff_time_task_comment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_status/index.js +29 -0
- package/dist/generated/entities/staff_time_task_status/index.js.map +7 -0
- package/dist/generated/entities/staff_time_task_tag/index.js +15 -0
- package/dist/generated/entities/staff_time_task_tag/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +149 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +7 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +2 -2
- package/dist/modules/auth/cli.js +16 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +1 -0
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js +44 -0
- package/dist/modules/customer_accounts/lib/customerRoleAcls.js.map +7 -0
- package/dist/modules/customer_accounts/setup.js +1 -28
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +2 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +39 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/start-controls.js +42 -0
- package/dist/modules/data_sync/lib/start-controls.js.map +7 -0
- package/dist/modules/staff/acl.js +9 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/ai-agents.js +142 -0
- package/dist/modules/staff/ai-agents.js.map +7 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js +348 -0
- package/dist/modules/staff/ai-tools/time-tracking-pack.js.map +7 -0
- package/dist/modules/staff/ai-tools/types.js +54 -0
- package/dist/modules/staff/ai-tools/types.js.map +7 -0
- package/dist/modules/staff/ai-tools.js +8 -0
- package/dist/modules/staff/ai-tools.js.map +7 -0
- package/dist/modules/staff/analytics.js +84 -1
- package/dist/modules/staff/analytics.js.map +2 -2
- package/dist/modules/staff/api/guards.js +37 -3
- package/dist/modules/staff/api/guards.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +1 -3
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js +131 -0
- package/dist/modules/staff/api/portal/time-reports/[id]/route.js.map +7 -0
- package/dist/modules/staff/api/portal/time-reports/route.js +179 -0
- package/dist/modules/staff/api/portal/time-reports/route.js.map +7 -0
- package/dist/modules/staff/api/team-members/self/route.js +1 -3
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +1 -3
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js +174 -0
- package/dist/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js +226 -0
- package/dist/modules/staff/api/timesheets/access-requests/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +15 -8
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-projects/route.js +13 -1
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js +42 -0
- package/dist/modules/staff/api/timesheets/my-work/myWorkAggregate.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js +383 -0
- package/dist/modules/staff/api/timesheets/my-work/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +14 -2
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js +168 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/close/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js +246 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/export/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js +183 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/sheet/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js +164 -0
- package/dist/modules/staff/api/timesheets/reports/[id]/unlock/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js +360 -0
- package/dist/modules/staff/api/timesheets/reports/preview/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/route.js +316 -0
- package/dist/modules/staff/api/timesheets/reports/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js +63 -0
- package/dist/modules/staff/api/timesheets/reports/shared.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js +164 -0
- package/dist/modules/staff/api/timesheets/settings/reapply-rounding/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js +168 -0
- package/dist/modules/staff/api/timesheets/settings/rounding-impact/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/settings/route.js +199 -0
- package/dist/modules/staff/api/timesheets/settings/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/access.js +76 -0
- package/dist/modules/staff/api/timesheets/tags/access.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js +235 -0
- package/dist/modules/staff/api/timesheets/tags/entry-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/route.js +160 -0
- package/dist/modules/staff/api/timesheets/tags/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js +233 -0
- package/dist/modules/staff/api/timesheets/tags/task-assignments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js +188 -0
- package/dist/modules/staff/api/timesheets/task-statuses/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +399 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js +210 -0
- package/dist/modules/staff/api/timesheets/tasks/[id]/status/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js +333 -0
- package/dist/modules/staff/api/timesheets/tasks/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js +228 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +29 -10
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +31 -18
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +21 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +27 -7
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +215 -32
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js +378 -0
- package/dist/modules/staff/api/timesheets/time-entries/copy-day/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js +340 -0
- package/dist/modules/staff/api/timesheets/time-entries/overlaps/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +238 -26
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js +23 -8
- package/dist/modules/staff/api/timesheets/time-entries/start-timer/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js +209 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +59 -2
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-projects/route.js +264 -22
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js +110 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +981 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js +583 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js +15 -0
- package/dist/modules/staff/backend/staff/time-tracking/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js +65 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +229 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +1014 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js +97 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js +1040 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js +19 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +353 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +457 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js +697 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js +174 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/reports/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +546 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/settings/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js +710 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/GridView.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js +646 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js +18 -0
- package/dist/modules/staff/backend/staff/time-tracking/timesheet/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +4 -793
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +2 -7
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +5 -136
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +5 -445
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +3 -3
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +4 -76
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +1 -7
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +4 -613
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +2 -17
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +2 -2
- package/dist/modules/staff/ce.js +40 -0
- package/dist/modules/staff/ce.js.map +2 -2
- package/dist/modules/staff/cli.js +166 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +5 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +569 -21
- package/dist/modules/staff/commands/timesheets-entries.js.map +3 -3
- package/dist/modules/staff/commands/timesheets-projects.js +450 -7
- package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-reports.js +1036 -0
- package/dist/modules/staff/commands/timesheets-reports.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tags.js +1015 -0
- package/dist/modules/staff/commands/timesheets-tags.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js +438 -0
- package/dist/modules/staff/commands/timesheets-task-comments.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js +723 -0
- package/dist/modules/staff/commands/timesheets-task-statuses.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-tasks.js +834 -0
- package/dist/modules/staff/commands/timesheets-tasks.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +426 -12
- package/dist/modules/staff/data/enrichers.js.map +2 -2
- package/dist/modules/staff/data/entities.js +552 -3
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/extensions.js +41 -0
- package/dist/modules/staff/data/extensions.js.map +7 -0
- package/dist/modules/staff/data/validators.js +254 -9
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/di.js +42 -1
- package/dist/modules/staff/di.js.map +2 -2
- package/dist/modules/staff/events.js +84 -6
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/events.payloads.js +158 -0
- package/dist/modules/staff/events.payloads.js.map +7 -0
- package/dist/modules/staff/extension-points.js +261 -0
- package/dist/modules/staff/extension-points.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +138 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js +12 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +133 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +7 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js +19 -0
- package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.js.map +7 -0
- package/dist/modules/staff/lib/crud.js +11 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking/access.js +92 -0
- package/dist/modules/staff/lib/time-tracking/access.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/billability.js +39 -0
- package/dist/modules/staff/lib/time-tracking/billability.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js +68 -0
- package/dist/modules/staff/lib/time-tracking/capacity.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js +15 -0
- package/dist/modules/staff/lib/time-tracking/componentContracts.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/cost.js +82 -0
- package/dist/modules/staff/lib/time-tracking/cost.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/duration.js +55 -0
- package/dist/modules/staff/lib/time-tracking/duration.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js +41 -0
- package/dist/modules/staff/lib/time-tracking/featureAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/interval.js +72 -0
- package/dist/modules/staff/lib/time-tracking/interval.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js +88 -0
- package/dist/modules/staff/lib/time-tracking/migrateProjectCodes.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js +10 -0
- package/dist/modules/staff/lib/time-tracking/moneyVisibility.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js +87 -0
- package/dist/modules/staff/lib/time-tracking/overlap.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js +27 -0
- package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js +16 -0
- package/dist/modules/staff/lib/time-tracking/portalReports.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js +135 -0
- package/dist/modules/staff/lib/time-tracking/projectCode.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js +187 -0
- package/dist/modules/staff/lib/time-tracking/reapplyRounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js +80 -0
- package/dist/modules/staff/lib/time-tracking/recalculationRunner.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js +56 -0
- package/dist/modules/staff/lib/time-tracking/recalculations.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js +63 -0
- package/dist/modules/staff/lib/time-tracking/registries/invoke.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js +62 -0
- package/dist/modules/staff/lib/time-tracking/registries/registry.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js +21 -0
- package/dist/modules/staff/lib/time-tracking/registries/scope.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js +34 -0
- package/dist/modules/staff/lib/time-tracking/rollup.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js +66 -0
- package/dist/modules/staff/lib/time-tracking/rounding.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js +39 -0
- package/dist/modules/staff/lib/time-tracking/roundingImpact.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js +214 -0
- package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/settings.js +87 -0
- package/dist/modules/staff/lib/time-tracking/settings.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js +9 -0
- package/dist/modules/staff/lib/time-tracking/sqlInClause.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js +48 -0
- package/dist/modules/staff/lib/time-tracking/timeEntrySources.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +283 -0
- package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js +324 -0
- package/dist/modules/staff/lib/time-tracking-ui/CustomerPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js +121 -0
- package/dist/modules/staff/lib/time-tracking-ui/DurationInput.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryDetailsFields.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js +89 -0
- package/dist/modules/staff/lib/time-tracking-ui/EntryFilterBar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +855 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +360 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js +259 -0
- package/dist/modules/staff/lib/time-tracking-ui/KanbanColumn.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js +238 -0
- package/dist/modules/staff/lib/time-tracking-ui/NewTaskDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js +130 -0
- package/dist/modules/staff/lib/time-tracking-ui/NoProjectAccess.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js +98 -0
- package/dist/modules/staff/lib/time-tracking-ui/PeriodSelector.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js +27 -0
- package/dist/modules/staff/lib/time-tracking-ui/PhasePlaceholder.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js +166 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCodeField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js +295 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +212 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +729 -0
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +217 -0
- package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js +158 -0
- package/dist/modules/staff/lib/time-tracking-ui/TagPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +224 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +93 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +1196 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js +307 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskPicker.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js +284 -0
- package/dist/modules/staff/lib/time-tracking-ui/TaskQuickLog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js +73 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +1499 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js +95 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js +188 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetCalendar.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js +61 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js +45 -0
- package/dist/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +176 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +82 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js +5 -0
- package/dist/modules/staff/lib/time-tracking-ui/boardSummary.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +152 -0
- package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js +92 -0
- package/dist/modules/staff/lib/time-tracking-ui/projectTeamAssignment.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +111 -0
- package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js +243 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryDialogState.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeEntryListData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +105 -0
- package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js +132 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetData.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js +205 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetPeriod.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js +53 -0
- package/dist/modules/staff/lib/time-tracking-ui/timesheetTargets.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js +77 -0
- package/dist/modules/staff/lib/time-tracking-ui/useBoardFilters.js.map +7 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js +151 -0
- package/dist/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.js.map +7 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js +809 -0
- package/dist/modules/staff/lib/timeTrackingSeeds.js.map +7 -0
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js +2 -3
- package/dist/modules/staff/lib/timesheets/timeEntryCacheInvalidation.js.map +2 -2
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js +152 -0
- package/dist/modules/staff/lib/timesheets/timeEntryDecoration.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js +36 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetBurn.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThreshold.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js +78 -0
- package/dist/modules/staff/lib/timesheets-projects/budgetThresholdState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectFinancials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js +44 -0
- package/dist/modules/staff/lib/timesheets-projects/reportSelection.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js +169 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js +22 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +51 -1
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js +70 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js +63 -0
- package/dist/modules/staff/lib/timesheets-reports/buildReportSheet.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js +170 -0
- package/dist/modules/staff/lib/timesheets-reports/loadReportData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js +205 -0
- package/dist/modules/staff/lib/timesheets-reports/pdf.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js +68 -0
- package/dist/modules/staff/lib/timesheets-reports/reportApprovalPolicies.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js +135 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js +48 -0
- package/dist/modules/staff/lib/timesheets-reports/reportConfigState.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js +262 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExport.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js +34 -0
- package/dist/modules/staff/lib/timesheets-reports/reportExportFormats.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js +79 -0
- package/dist/modules/staff/lib/timesheets-reports/reportGroupings.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js +11 -0
- package/dist/modules/staff/lib/timesheets-reports/reportLabels.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js +83 -0
- package/dist/modules/staff/lib/timesheets-reports/reportReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js +42 -0
- package/dist/modules/staff/lib/timesheets-reports/reportRows.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js +107 -0
- package/dist/modules/staff/lib/timesheets-reports/reportSheetData.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js +289 -0
- package/dist/modules/staff/lib/timesheets-reports/reportTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js +145 -0
- package/dist/modules/staff/lib/timesheets-reports/xlsx.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js +116 -0
- package/dist/modules/staff/lib/timesheets-tasks/computeTaskRollups.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js +50 -0
- package/dist/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js +27 -0
- package/dist/modules/staff/lib/timesheets-tasks/seedProjectStatuses.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js +65 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusPositions.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/statusSlug.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js +31 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHierarchy.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js +17 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskHoursTotals.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js +51 -0
- package/dist/modules/staff/lib/timesheets-tasks/taskReference.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +11 -4
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +369 -111
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +47 -2
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +2 -2
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js +79 -0
- package/dist/modules/staff/migrations/Migration20260813073131_staff.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js +15 -0
- package/dist/modules/staff/migrations/Migration20260824143357_staff.js.map +7 -0
- package/dist/modules/staff/notifications.client.js +16 -0
- package/dist/modules/staff/notifications.client.js.map +7 -0
- package/dist/modules/staff/notifications.handlers.js +80 -0
- package/dist/modules/staff/notifications.handlers.js.map +7 -0
- package/dist/modules/staff/notifications.js +168 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/staff/search.js +159 -1
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +20 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js +68 -0
- package/dist/modules/staff/subscribers/time-project-access-requested-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js +149 -0
- package/dist/modules/staff/subscribers/time-project-budget-threshold-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js +78 -0
- package/dist/modules/staff/subscribers/time-report-approved-notification.js.map +7 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js +78 -0
- package/dist/modules/staff/subscribers/time-report-portal-broadcast.js.map +7 -0
- package/dist/modules/staff/translations.js +4 -1
- package/dist/modules/staff/translations.js.map +2 -2
- package/dist/modules/staff/widgets/components.js +7 -0
- package/dist/modules/staff/widgets/components.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +5 -4
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +2 -2
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js +135 -0
- package/dist/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.js.map +7 -0
- package/dist/modules/staff/widgets/notifications/index.js +5 -0
- package/dist/modules/staff/widgets/notifications/index.js.map +7 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js +43 -0
- package/dist/modules/staff/workers/timesheets-reapply-rounding.js.map +7 -0
- package/generated/entities/staff_time_entry/index.ts +7 -0
- package/generated/entities/staff_time_entry_tag/index.ts +6 -0
- package/generated/entities/staff_time_project/index.ts +8 -0
- package/generated/entities/staff_time_report/index.ts +27 -0
- package/generated/entities/staff_time_report_entry/index.ts +12 -0
- package/generated/entities/staff_time_report_event/index.ts +9 -0
- package/generated/entities/staff_time_report_project/index.ts +6 -0
- package/generated/entities/staff_time_tag/index.ts +9 -0
- package/generated/entities/staff_time_task/index.ts +17 -0
- package/generated/entities/staff_time_task_comment/index.ts +9 -0
- package/generated/entities/staff_time_task_status/index.ts +13 -0
- package/generated/entities/staff_time_task_tag/index.ts +6 -0
- package/generated/entities.ids.generated.ts +11 -1
- package/generated/entity-fields-registry.ts +149 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +31 -1
- package/src/modules/auth/README.md +1 -1
- package/src/modules/auth/cli.ts +35 -1
- package/src/modules/auth/i18n/de.json +7 -0
- package/src/modules/auth/i18n/en.json +7 -0
- package/src/modules/auth/i18n/es.json +7 -0
- package/src/modules/auth/i18n/ko.json +7 -0
- package/src/modules/auth/i18n/pl.json +7 -0
- package/src/modules/auth/lib/backendChrome.tsx +1 -0
- package/src/modules/customer_accounts/lib/customerRoleAcls.ts +79 -0
- package/src/modules/customer_accounts/setup.ts +1 -45
- package/src/modules/data_sync/AGENTS.md +34 -0
- package/src/modules/data_sync/api/options.ts +2 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +82 -33
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/ko.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/adapter.ts +45 -0
- package/src/modules/data_sync/lib/start-controls.ts +89 -0
- package/src/modules/staff/AGENTS.md +769 -1
- package/src/modules/staff/acl.ts +9 -0
- package/src/modules/staff/ai-agents.ts +189 -0
- package/src/modules/staff/ai-tools/time-tracking-pack.ts +494 -0
- package/src/modules/staff/ai-tools/types.ts +82 -0
- package/src/modules/staff/ai-tools.ts +13 -0
- package/src/modules/staff/analytics.ts +113 -0
- package/src/modules/staff/api/guards.ts +85 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +0 -2
- package/src/modules/staff/api/leave-requests/reject/route.ts +0 -2
- package/src/modules/staff/api/portal/time-reports/[id]/route.ts +182 -0
- package/src/modules/staff/api/portal/time-reports/route.ts +270 -0
- package/src/modules/staff/api/team-members/self/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/assign/route.ts +0 -2
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +0 -2
- package/src/modules/staff/api/timesheets/_shared/withTimesheetInterceptors.ts +292 -0
- package/src/modules/staff/api/timesheets/access-requests/route.ts +299 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +15 -7
- package/src/modules/staff/api/timesheets/my-projects/route.ts +14 -1
- package/src/modules/staff/api/timesheets/my-work/myWorkAggregate.ts +90 -0
- package/src/modules/staff/api/timesheets/my-work/route.ts +475 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +15 -2
- package/src/modules/staff/api/timesheets/reports/[id]/close/route.ts +203 -0
- package/src/modules/staff/api/timesheets/reports/[id]/export/route.ts +315 -0
- package/src/modules/staff/api/timesheets/reports/[id]/sheet/route.ts +220 -0
- package/src/modules/staff/api/timesheets/reports/[id]/unlock/route.ts +208 -0
- package/src/modules/staff/api/timesheets/reports/preview/route.ts +449 -0
- package/src/modules/staff/api/timesheets/reports/route.ts +386 -0
- package/src/modules/staff/api/timesheets/reports/shared.ts +121 -0
- package/src/modules/staff/api/timesheets/settings/reapply-rounding/route.ts +205 -0
- package/src/modules/staff/api/timesheets/settings/rounding-impact/route.ts +226 -0
- package/src/modules/staff/api/timesheets/settings/route.ts +242 -0
- package/src/modules/staff/api/timesheets/tags/access.ts +134 -0
- package/src/modules/staff/api/timesheets/tags/entry-assignments/route.ts +290 -0
- package/src/modules/staff/api/timesheets/tags/route.ts +177 -0
- package/src/modules/staff/api/timesheets/tags/task-assignments/route.ts +294 -0
- package/src/modules/staff/api/timesheets/task-statuses/route.ts +232 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +532 -0
- package/src/modules/staff/api/timesheets/tasks/[id]/status/route.ts +271 -0
- package/src/modules/staff/api/timesheets/tasks/route.ts +443 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/duplicate/route.ts +306 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +32 -10
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +32 -17
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +27 -6
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +36 -6
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +331 -34
- package/src/modules/staff/api/timesheets/time-entries/copy-day/route.ts +533 -0
- package/src/modules/staff/api/timesheets/time-entries/overlaps/route.ts +457 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +378 -24
- package/src/modules/staff/api/timesheets/time-entries/start-timer/route.ts +31 -9
- package/src/modules/staff/api/timesheets/time-projects/[id]/change-currency/route.ts +246 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +62 -2
- package/src/modules/staff/api/timesheets/time-projects/route.ts +397 -22
- package/src/modules/staff/backend/staff/time-tracking/board/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/board/page.tsx +151 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +1229 -0
- package/src/modules/staff/backend/staff/time-tracking/page.meta.ts +11 -0
- package/src/modules/staff/backend/staff/time-tracking/page.tsx +823 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/board/page.tsx +81 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +275 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +1420 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/create/page.tsx +109 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.meta.ts +15 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/page.tsx +1254 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +466 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +632 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/create/page.tsx +867 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/reports/page.tsx +246 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +701 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/GridView.tsx +953 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.meta.ts +14 -0
- package/src/modules/staff/backend/staff/time-tracking/timesheet/page.tsx +800 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +6 -6
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1010
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +4 -158
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -609
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +0 -6
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +3 -89
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +2 -19
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +3 -778
- package/src/modules/staff/ce.ts +63 -0
- package/src/modules/staff/cli.ts +207 -1
- package/src/modules/staff/commands/index.ts +5 -0
- package/src/modules/staff/commands/timesheets-entries.ts +1006 -28
- package/src/modules/staff/commands/timesheets-projects.ts +593 -7
- package/src/modules/staff/commands/timesheets-reports.ts +1329 -0
- package/src/modules/staff/commands/timesheets-tags.ts +1315 -0
- package/src/modules/staff/commands/timesheets-task-comments.ts +583 -0
- package/src/modules/staff/commands/timesheets-task-statuses.ts +889 -0
- package/src/modules/staff/commands/timesheets-tasks.ts +1121 -0
- package/src/modules/staff/data/enrichers.ts +675 -10
- package/src/modules/staff/data/entities.ts +494 -2
- package/src/modules/staff/data/extensions.ts +68 -0
- package/src/modules/staff/data/validators.ts +348 -3
- package/src/modules/staff/di.ts +96 -0
- package/src/modules/staff/events.payloads.ts +268 -0
- package/src/modules/staff/events.ts +87 -5
- package/src/modules/staff/extension-points.ts +308 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.meta.ts +10 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +225 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.meta.ts +17 -0
- package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +182 -0
- package/src/modules/staff/i18n/de.json +899 -0
- package/src/modules/staff/i18n/en.json +899 -0
- package/src/modules/staff/i18n/es.json +899 -0
- package/src/modules/staff/i18n/ko.json +899 -0
- package/src/modules/staff/i18n/pl.json +899 -0
- package/src/modules/staff/lib/crud.ts +14 -3
- package/src/modules/staff/lib/time-tracking/access.ts +155 -0
- package/src/modules/staff/lib/time-tracking/billability.ts +68 -0
- package/src/modules/staff/lib/time-tracking/capacity.ts +124 -0
- package/src/modules/staff/lib/time-tracking/componentContracts.ts +26 -0
- package/src/modules/staff/lib/time-tracking/cost.ts +144 -0
- package/src/modules/staff/lib/time-tracking/duration.ts +68 -0
- package/src/modules/staff/lib/time-tracking/featureAccess.ts +115 -0
- package/src/modules/staff/lib/time-tracking/interval.ts +90 -0
- package/src/modules/staff/lib/time-tracking/migrateProjectCodes.ts +161 -0
- package/src/modules/staff/lib/time-tracking/moneyVisibility.ts +37 -0
- package/src/modules/staff/lib/time-tracking/overlap.ts +159 -0
- package/src/modules/staff/lib/time-tracking/portalRecipients.ts +76 -0
- package/src/modules/staff/lib/time-tracking/portalReports.ts +31 -0
- package/src/modules/staff/lib/time-tracking/projectCode.ts +221 -0
- package/src/modules/staff/lib/time-tracking/reapplyRounding.ts +313 -0
- package/src/modules/staff/lib/time-tracking/recalculationRunner.ts +118 -0
- package/src/modules/staff/lib/time-tracking/recalculations.ts +135 -0
- package/src/modules/staff/lib/time-tracking/registries/invoke.ts +122 -0
- package/src/modules/staff/lib/time-tracking/registries/registry.ts +124 -0
- package/src/modules/staff/lib/time-tracking/registries/scope.ts +56 -0
- package/src/modules/staff/lib/time-tracking/rollup.ts +59 -0
- package/src/modules/staff/lib/time-tracking/rounding.ts +126 -0
- package/src/modules/staff/lib/time-tracking/roundingImpact.ts +75 -0
- package/src/modules/staff/lib/time-tracking/settingKeys.ts +274 -0
- package/src/modules/staff/lib/time-tracking/settings.ts +168 -0
- package/src/modules/staff/lib/time-tracking/sqlInClause.ts +22 -0
- package/src/modules/staff/lib/time-tracking/timeEntrySources.ts +85 -0
- package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +360 -0
- package/src/modules/staff/lib/time-tracking-ui/CustomerPicker.tsx +427 -0
- package/src/modules/staff/lib/time-tracking-ui/DurationInput.tsx +192 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryDetailsFields.tsx +224 -0
- package/src/modules/staff/lib/time-tracking-ui/EntryFilterBar.tsx +144 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +1065 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/KanbanColumn.tsx +316 -0
- package/src/modules/staff/lib/time-tracking-ui/NewTaskDialog.tsx +291 -0
- package/src/modules/staff/lib/time-tracking-ui/NoProjectAccess.tsx +162 -0
- package/src/modules/staff/lib/time-tracking-ui/PeriodSelector.tsx +149 -0
- package/src/modules/staff/lib/time-tracking-ui/PhasePlaceholder.tsx +31 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCodeField.tsx +230 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectCurrencyField.tsx +432 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +323 -0
- package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +930 -0
- package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +357 -0
- package/src/modules/staff/lib/time-tracking-ui/TagPicker.tsx +195 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +243 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +137 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +1405 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +260 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskPicker.tsx +435 -0
- package/src/modules/staff/lib/time-tracking-ui/TaskQuickLog.tsx +296 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntriesSummaryFooter.tsx +97 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +1929 -0
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDurationCell.tsx +126 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetCalendar.tsx +265 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetPeriodFooter.tsx +78 -0
- package/src/modules/staff/lib/time-tracking-ui/TimesheetViewSwitch.tsx +70 -0
- package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +231 -0
- package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +128 -0
- package/src/modules/staff/lib/time-tracking-ui/boardSummary.ts +20 -0
- package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +228 -0
- package/src/modules/staff/lib/time-tracking-ui/projectTeamAssignment.ts +143 -0
- package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +194 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryDialogState.ts +400 -0
- package/src/modules/staff/lib/time-tracking-ui/timeEntryListData.ts +271 -0
- package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +168 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetData.ts +225 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetPeriod.ts +278 -0
- package/src/modules/staff/lib/time-tracking-ui/timesheetTargets.ts +93 -0
- package/src/modules/staff/lib/time-tracking-ui/useBoardFilters.ts +107 -0
- package/src/modules/staff/lib/time-tracking-ui/useTimesheetPreferences.ts +245 -0
- package/src/modules/staff/lib/timeTrackingSeeds.ts +1037 -0
- package/src/modules/staff/lib/timesheets/timeEntryCacheInvalidation.ts +11 -10
- package/src/modules/staff/lib/timesheets/timeEntryDecoration.ts +256 -0
- package/src/modules/staff/lib/timesheets-projects/budgetBurn.ts +60 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThreshold.ts +135 -0
- package/src/modules/staff/lib/timesheets-projects/budgetThresholdState.ts +137 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectFinancials.ts +142 -0
- package/src/modules/staff/lib/timesheets-projects/reportSelection.ts +82 -0
- package/src/modules/staff/lib/timesheets-projects-ui/AssignMembersDialog.tsx +237 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectBudgetCell.tsx +33 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +56 -1
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsBulkActionsBar.tsx +90 -0
- package/src/modules/staff/lib/timesheets-reports/buildReportSheet.ts +125 -0
- package/src/modules/staff/lib/timesheets-reports/loadReportData.ts +251 -0
- package/src/modules/staff/lib/timesheets-reports/pdf.ts +263 -0
- package/src/modules/staff/lib/timesheets-reports/reportApprovalPolicies.ts +137 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigData.ts +247 -0
- package/src/modules/staff/lib/timesheets-reports/reportConfigState.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportExport.ts +373 -0
- package/src/modules/staff/lib/timesheets-reports/reportExportFormats.ts +71 -0
- package/src/modules/staff/lib/timesheets-reports/reportGroupings.ts +153 -0
- package/src/modules/staff/lib/timesheets-reports/reportLabels.ts +23 -0
- package/src/modules/staff/lib/timesheets-reports/reportReference.ts +146 -0
- package/src/modules/staff/lib/timesheets-reports/reportRows.ts +89 -0
- package/src/modules/staff/lib/timesheets-reports/reportSheetData.ts +196 -0
- package/src/modules/staff/lib/timesheets-reports/reportTotals.ts +544 -0
- package/src/modules/staff/lib/timesheets-reports/xlsx.ts +207 -0
- package/src/modules/staff/lib/timesheets-tasks/computeTaskRollups.ts +238 -0
- package/src/modules/staff/lib/timesheets-tasks/defaultStatusTemplate.ts +90 -0
- package/src/modules/staff/lib/timesheets-tasks/seedProjectStatuses.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/statusPositions.ts +132 -0
- package/src/modules/staff/lib/timesheets-tasks/statusSlug.ts +43 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHierarchy.ts +99 -0
- package/src/modules/staff/lib/timesheets-tasks/taskHoursTotals.ts +42 -0
- package/src/modules/staff/lib/timesheets-tasks/taskReference.ts +108 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +18 -4
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +504 -149
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +50 -2
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +3244 -228
- package/src/modules/staff/migrations/Migration20260813073131_staff.ts +94 -0
- package/src/modules/staff/migrations/Migration20260824143357_staff.ts +17 -0
- package/src/modules/staff/notifications.client.ts +16 -0
- package/src/modules/staff/notifications.handlers.ts +88 -0
- package/src/modules/staff/notifications.ts +163 -0
- package/src/modules/staff/search.ts +191 -1
- package/src/modules/staff/setup.ts +22 -0
- package/src/modules/staff/subscribers/time-project-access-requested-notification.ts +95 -0
- package/src/modules/staff/subscribers/time-project-budget-threshold-notification.ts +226 -0
- package/src/modules/staff/subscribers/time-report-approved-notification.ts +116 -0
- package/src/modules/staff/subscribers/time-report-portal-broadcast.ts +120 -0
- package/src/modules/staff/translations.ts +17 -0
- package/src/modules/staff/widgets/components.ts +34 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +6 -5
- package/src/modules/staff/widgets/notifications/TimeProjectAccessRequestedRenderer.tsx +138 -0
- package/src/modules/staff/widgets/notifications/index.ts +1 -0
- package/src/modules/staff/workers/timesheets-reapply-rounding.ts +59 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +0 -123
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +0 -7
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +0 -138
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { extensionPoints } from "@open-mercato/core/modules/staff/extension-points";
|
|
2
|
+
import { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from "./registries/registry.js";
|
|
3
|
+
import { tryStrategy } from "./registries/invoke.js";
|
|
4
|
+
import { hasResolverScope } from "./registries/scope.js";
|
|
5
|
+
const BILLABILITY_REGISTRY_ID = extensionPoints.hosts.timeBillabilityRegistry.spotId;
|
|
6
|
+
const BUILT_IN_BILLABILITY_RESOLVER_ID = "staff.time_tracking.billability.project_then_tenant";
|
|
7
|
+
const registry = createStrategyRegistry(BILLABILITY_REGISTRY_ID);
|
|
8
|
+
function registerBillabilityResolver(resolver) {
|
|
9
|
+
return registry.register(resolver);
|
|
10
|
+
}
|
|
11
|
+
function listBillabilityResolvers() {
|
|
12
|
+
return registry.list();
|
|
13
|
+
}
|
|
14
|
+
function getBillabilityResolver(id) {
|
|
15
|
+
return registry.get(id);
|
|
16
|
+
}
|
|
17
|
+
const builtInBillabilityResolver = registry.registerBuiltIn({
|
|
18
|
+
id: BUILT_IN_BILLABILITY_RESOLVER_ID,
|
|
19
|
+
priority: BUILT_IN_STRATEGY_PRIORITY,
|
|
20
|
+
resolve: (ctx) => ctx.requested ?? ctx.project?.billableByDefault ?? ctx.settings.defaults.billable
|
|
21
|
+
});
|
|
22
|
+
function resolveBillability(ctx) {
|
|
23
|
+
const scoped = hasResolverScope(ctx);
|
|
24
|
+
for (const resolver of registry.list()) {
|
|
25
|
+
if (!scoped && resolver.id !== BUILT_IN_BILLABILITY_RESOLVER_ID) continue;
|
|
26
|
+
const answer = tryStrategy(BILLABILITY_REGISTRY_ID, resolver.id, () => resolver.resolve(ctx));
|
|
27
|
+
if (typeof answer === "boolean") return answer;
|
|
28
|
+
}
|
|
29
|
+
return ctx.settings.defaults.billable;
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
BILLABILITY_REGISTRY_ID,
|
|
33
|
+
BUILT_IN_BILLABILITY_RESOLVER_ID,
|
|
34
|
+
getBillabilityResolver,
|
|
35
|
+
listBillabilityResolvers,
|
|
36
|
+
registerBillabilityResolver,
|
|
37
|
+
resolveBillability
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=billability.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/billability.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * EP-34 \u2014 the billability resolver chain.\n *\n * The rule `commands/timesheets-entries.ts` documented in prose \u2014 an explicit\n * value wins, then the project's own default, then the tenant setting \u2014 is now\n * the built-in resolver `staff.time_tracking.billability.project_then_tenant`.\n * Contributed resolvers are asked first, in descending priority, and the first\n * non-null answer wins; a resolver that returns `null` abstains and the chain\n * carries on, so an unopinionated contribution cannot change an entry.\n *\n * As everywhere else in this group, a contribution is consulted only when the\n * caller supplies a complete tenant + organization scope.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from './registries/registry'\nimport { tryStrategy } from './registries/invoke'\nimport { hasResolverScope, type ScopedResolverContext } from './registries/scope'\nimport type { TimeTrackingSettings } from './settings'\n\nexport type BillabilityContext = ScopedResolverContext & {\n requested?: boolean | null\n project?: { id?: string | null; billableByDefault?: boolean | null } | null\n task?: { id?: string | null } | null\n staffMemberId?: string | null\n date?: string | null\n settings: TimeTrackingSettings\n}\n\nexport type BillabilityResolver = {\n id: string\n priority?: number\n resolve(ctx: BillabilityContext): boolean | null\n}\n\nexport const BILLABILITY_REGISTRY_ID = extensionPoints.hosts.timeBillabilityRegistry.spotId\n\nexport const BUILT_IN_BILLABILITY_RESOLVER_ID = 'staff.time_tracking.billability.project_then_tenant'\n\nconst registry = createStrategyRegistry<BillabilityResolver>(BILLABILITY_REGISTRY_ID)\n\nexport function registerBillabilityResolver(resolver: BillabilityResolver): () => void {\n return registry.register(resolver)\n}\n\nexport function listBillabilityResolvers(): BillabilityResolver[] {\n return registry.list()\n}\n\nexport function getBillabilityResolver(id: string | null | undefined): BillabilityResolver | null {\n return registry.get(id)\n}\n\nconst builtInBillabilityResolver: BillabilityResolver = registry.registerBuiltIn({\n id: BUILT_IN_BILLABILITY_RESOLVER_ID,\n priority: BUILT_IN_STRATEGY_PRIORITY,\n resolve: (ctx) => ctx.requested ?? ctx.project?.billableByDefault ?? ctx.settings.defaults.billable,\n})\n\nexport function resolveBillability(ctx: BillabilityContext): boolean {\n const scoped = hasResolverScope(ctx)\n for (const resolver of registry.list()) {\n if (!scoped && resolver.id !== BUILT_IN_BILLABILITY_RESOLVER_ID) continue\n const answer = tryStrategy(BILLABILITY_REGISTRY_ID, resolver.id, () => resolver.resolve(ctx))\n if (typeof answer === 'boolean') return answer\n }\n return ctx.settings.defaults.billable\n}\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,uBAAuB;AAChC,SAAS,wBAAwB,kCAAkC;AACnE,SAAS,mBAAmB;AAC5B,SAAS,wBAAoD;AAkBtD,MAAM,0BAA0B,gBAAgB,MAAM,wBAAwB;AAE9E,MAAM,mCAAmC;AAEhD,MAAM,WAAW,uBAA4C,uBAAuB;AAE7E,SAAS,4BAA4B,UAA2C;AACrF,SAAO,SAAS,SAAS,QAAQ;AACnC;AAEO,SAAS,2BAAkD;AAChE,SAAO,SAAS,KAAK;AACvB;AAEO,SAAS,uBAAuB,IAA2D;AAChG,SAAO,SAAS,IAAI,EAAE;AACxB;AAEA,MAAM,6BAAkD,SAAS,gBAAgB;AAAA,EAC/E,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS,CAAC,QAAQ,IAAI,aAAa,IAAI,SAAS,qBAAqB,IAAI,SAAS,SAAS;AAC7F,CAAC;AAEM,SAAS,mBAAmB,KAAkC;AACnE,QAAM,SAAS,iBAAiB,GAAG;AACnC,aAAW,YAAY,SAAS,KAAK,GAAG;AACtC,QAAI,CAAC,UAAU,SAAS,OAAO,iCAAkC;AACjE,UAAM,SAAS,YAAY,yBAAyB,SAAS,IAAI,MAAM,SAAS,QAAQ,GAAG,CAAC;AAC5F,QAAI,OAAO,WAAW,UAAW,QAAO;AAAA,EAC1C;AACA,SAAO,IAAI,SAAS,SAAS;AAC/B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { extensionPoints } from "@open-mercato/core/modules/staff/extension-points";
|
|
2
|
+
import { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from "./registries/registry.js";
|
|
3
|
+
import { selectScopedStrategy } from "./registries/scope.js";
|
|
4
|
+
import { runStrategy } from "./registries/invoke.js";
|
|
5
|
+
const CAPACITY_PROVIDER_REGISTRY_ID = extensionPoints.hosts.capacityProviderRegistry.spotId;
|
|
6
|
+
const BUILT_IN_CAPACITY_PROVIDER_ID = "staff.time_tracking.capacity.flat_daily_hours";
|
|
7
|
+
const registry = createStrategyRegistry(CAPACITY_PROVIDER_REGISTRY_ID);
|
|
8
|
+
function registerCapacityProvider(provider) {
|
|
9
|
+
return registry.register(provider);
|
|
10
|
+
}
|
|
11
|
+
function listCapacityProviders() {
|
|
12
|
+
return registry.list();
|
|
13
|
+
}
|
|
14
|
+
function getCapacityProvider(id) {
|
|
15
|
+
return registry.get(id);
|
|
16
|
+
}
|
|
17
|
+
const builtInCapacityProvider = registry.registerBuiltIn({
|
|
18
|
+
id: BUILT_IN_CAPACITY_PROVIDER_ID,
|
|
19
|
+
priority: BUILT_IN_STRATEGY_PRIORITY,
|
|
20
|
+
resolve: (_staffMemberId, dateRange, ctx) => {
|
|
21
|
+
const dailyHours = ctx.dailyHours;
|
|
22
|
+
if (dailyHours === null || !Number.isFinite(dailyHours)) {
|
|
23
|
+
return { targetMinutesByDate: {}, totalTargetMinutes: null };
|
|
24
|
+
}
|
|
25
|
+
const dailyMinutes = Math.round(dailyHours * 60);
|
|
26
|
+
const targetMinutesByDate = {};
|
|
27
|
+
for (const date of dateRange.workingDays ?? []) targetMinutesByDate[date] = dailyMinutes;
|
|
28
|
+
return {
|
|
29
|
+
targetMinutesByDate,
|
|
30
|
+
totalTargetMinutes: dailyMinutes * (dateRange.workingDays?.length ?? 0)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
function resolveCapacityProvider(ctx) {
|
|
35
|
+
return selectScopedStrategy(registry.list(), builtInCapacityProvider, ctx);
|
|
36
|
+
}
|
|
37
|
+
function isCapacityResult(value) {
|
|
38
|
+
if (!value || typeof value !== "object") return false;
|
|
39
|
+
const candidate = value;
|
|
40
|
+
if (!candidate.targetMinutesByDate || typeof candidate.targetMinutesByDate !== "object") return false;
|
|
41
|
+
const total = candidate.totalTargetMinutes;
|
|
42
|
+
if (total !== null && (typeof total !== "number" || !Number.isFinite(total))) return false;
|
|
43
|
+
return Object.values(candidate.targetMinutesByDate).every(
|
|
44
|
+
(minutes) => typeof minutes === "number" && Number.isFinite(minutes)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function resolveTimesheetCapacity(staffMemberId, dateRange, ctx) {
|
|
48
|
+
const provider = resolveCapacityProvider(ctx);
|
|
49
|
+
const builtIn = () => builtInCapacityProvider.resolve(staffMemberId, dateRange, ctx);
|
|
50
|
+
if (provider === builtInCapacityProvider) return builtIn();
|
|
51
|
+
const answer = runStrategy(
|
|
52
|
+
CAPACITY_PROVIDER_REGISTRY_ID,
|
|
53
|
+
provider.id,
|
|
54
|
+
() => provider.resolve(staffMemberId, dateRange, ctx),
|
|
55
|
+
() => null
|
|
56
|
+
);
|
|
57
|
+
return isCapacityResult(answer) ? answer : builtIn();
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
BUILT_IN_CAPACITY_PROVIDER_ID,
|
|
61
|
+
CAPACITY_PROVIDER_REGISTRY_ID,
|
|
62
|
+
getCapacityProvider,
|
|
63
|
+
listCapacityProviders,
|
|
64
|
+
registerCapacityProvider,
|
|
65
|
+
resolveCapacityProvider,
|
|
66
|
+
resolveTimesheetCapacity
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=capacity.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/capacity.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * EP-40 \u2014 the capacity / target provider.\n *\n * The timesheet's \"target\" is one flat number today: `targets.dailyHours` from\n * the tenant settings, applied to every working day of every person. The\n * built-in `staff.time_tracking.capacity.flat_daily_hours` reproduces exactly\n * that, including its two edge cases \u2014 a `null` setting means \"no target\", and a\n * day the caller did not mark as working contributes nothing.\n *\n * A contributed provider answers per day instead, which is what a contract-hours\n * or leave-aware capacity model needs. It is consulted only when the caller\n * supplies a complete tenant + organization scope.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from './registries/registry'\nimport { selectScopedStrategy, type ScopedResolverContext } from './registries/scope'\nimport { runStrategy } from './registries/invoke'\n\nexport type CapacityDateRange = {\n /** `yyyy-mm-dd`, inclusive. */\n from: string\n /** `yyyy-mm-dd`, inclusive. */\n to: string\n /** The days the caller counts as working; the built-in targets only these. */\n workingDays: readonly string[]\n}\n\nexport type CapacityContext = ScopedResolverContext & {\n /** The tenant's `targets.dailyHours`; `null` means the tenant set no target. */\n dailyHours: number | null\n}\n\nexport type CapacityResult = {\n /** Target minutes per `yyyy-mm-dd`, for the working days that carry one. */\n targetMinutesByDate: Record<string, number>\n /** Sum of `targetMinutesByDate`, or `null` when the tenant set no target. */\n totalTargetMinutes: number | null\n}\n\nexport type CapacityProvider = {\n id: string\n priority?: number\n resolve(\n staffMemberId: string | null,\n dateRange: CapacityDateRange,\n ctx: CapacityContext,\n ): CapacityResult\n}\n\nexport const CAPACITY_PROVIDER_REGISTRY_ID = extensionPoints.hosts.capacityProviderRegistry.spotId\n\nexport const BUILT_IN_CAPACITY_PROVIDER_ID = 'staff.time_tracking.capacity.flat_daily_hours'\n\nconst registry = createStrategyRegistry<CapacityProvider>(CAPACITY_PROVIDER_REGISTRY_ID)\n\nexport function registerCapacityProvider(provider: CapacityProvider): () => void {\n return registry.register(provider)\n}\n\nexport function listCapacityProviders(): CapacityProvider[] {\n return registry.list()\n}\n\nexport function getCapacityProvider(id: string | null | undefined): CapacityProvider | null {\n return registry.get(id)\n}\n\nconst builtInCapacityProvider: CapacityProvider = registry.registerBuiltIn({\n id: BUILT_IN_CAPACITY_PROVIDER_ID,\n priority: BUILT_IN_STRATEGY_PRIORITY,\n resolve: (_staffMemberId, dateRange, ctx) => {\n const dailyHours = ctx.dailyHours\n if (dailyHours === null || !Number.isFinite(dailyHours)) {\n return { targetMinutesByDate: {}, totalTargetMinutes: null }\n }\n const dailyMinutes = Math.round(dailyHours * 60)\n const targetMinutesByDate: Record<string, number> = {}\n for (const date of dateRange.workingDays ?? []) targetMinutesByDate[date] = dailyMinutes\n return {\n targetMinutesByDate,\n totalTargetMinutes: dailyMinutes * (dateRange.workingDays?.length ?? 0),\n }\n },\n})\n\nexport function resolveCapacityProvider(ctx?: ScopedResolverContext | null): CapacityProvider {\n return selectScopedStrategy(registry.list(), builtInCapacityProvider, ctx)\n}\n\nfunction isCapacityResult(value: unknown): value is CapacityResult {\n if (!value || typeof value !== 'object') return false\n const candidate = value as { targetMinutesByDate?: unknown; totalTargetMinutes?: unknown }\n if (!candidate.targetMinutesByDate || typeof candidate.targetMinutesByDate !== 'object') return false\n const total = candidate.totalTargetMinutes\n if (total !== null && (typeof total !== 'number' || !Number.isFinite(total))) return false\n return Object.values(candidate.targetMinutesByDate as Record<string, unknown>).every(\n (minutes) => typeof minutes === 'number' && Number.isFinite(minutes),\n )\n}\n\n/**\n * The targets the timesheet renders and compares logged minutes against. A provider\n * that throws, or that answers with something the caller cannot subtract from, falls\n * back to the flat daily target the module shipped \u2014 a timesheet showing the wrong\n * target reads as the person being behind, which is worse than showing the default.\n */\nexport function resolveTimesheetCapacity(\n staffMemberId: string | null,\n dateRange: CapacityDateRange,\n ctx: CapacityContext,\n): CapacityResult {\n const provider = resolveCapacityProvider(ctx)\n const builtIn = () => builtInCapacityProvider.resolve(staffMemberId, dateRange, ctx)\n if (provider === builtInCapacityProvider) return builtIn()\n\n const answer = runStrategy(\n CAPACITY_PROVIDER_REGISTRY_ID,\n provider.id,\n () => provider.resolve(staffMemberId, dateRange, ctx) as unknown,\n () => null,\n )\n return isCapacityResult(answer) ? answer : builtIn()\n}\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,uBAAuB;AAChC,SAAS,wBAAwB,kCAAkC;AACnE,SAAS,4BAAwD;AACjE,SAAS,mBAAmB;AAiCrB,MAAM,gCAAgC,gBAAgB,MAAM,yBAAyB;AAErF,MAAM,gCAAgC;AAE7C,MAAM,WAAW,uBAAyC,6BAA6B;AAEhF,SAAS,yBAAyB,UAAwC;AAC/E,SAAO,SAAS,SAAS,QAAQ;AACnC;AAEO,SAAS,wBAA4C;AAC1D,SAAO,SAAS,KAAK;AACvB;AAEO,SAAS,oBAAoB,IAAwD;AAC1F,SAAO,SAAS,IAAI,EAAE;AACxB;AAEA,MAAM,0BAA4C,SAAS,gBAAgB;AAAA,EACzE,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS,CAAC,gBAAgB,WAAW,QAAQ;AAC3C,UAAM,aAAa,IAAI;AACvB,QAAI,eAAe,QAAQ,CAAC,OAAO,SAAS,UAAU,GAAG;AACvD,aAAO,EAAE,qBAAqB,CAAC,GAAG,oBAAoB,KAAK;AAAA,IAC7D;AACA,UAAM,eAAe,KAAK,MAAM,aAAa,EAAE;AAC/C,UAAM,sBAA8C,CAAC;AACrD,eAAW,QAAQ,UAAU,eAAe,CAAC,EAAG,qBAAoB,IAAI,IAAI;AAC5E,WAAO;AAAA,MACL;AAAA,MACA,oBAAoB,gBAAgB,UAAU,aAAa,UAAU;AAAA,IACvE;AAAA,EACF;AACF,CAAC;AAEM,SAAS,wBAAwB,KAAsD;AAC5F,SAAO,qBAAqB,SAAS,KAAK,GAAG,yBAAyB,GAAG;AAC3E;AAEA,SAAS,iBAAiB,OAAyC;AACjE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,YAAY;AAClB,MAAI,CAAC,UAAU,uBAAuB,OAAO,UAAU,wBAAwB,SAAU,QAAO;AAChG,QAAM,QAAQ,UAAU;AACxB,MAAI,UAAU,SAAS,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAI,QAAO;AACrF,SAAO,OAAO,OAAO,UAAU,mBAA8C,EAAE;AAAA,IAC7E,CAAC,YAAY,OAAO,YAAY,YAAY,OAAO,SAAS,OAAO;AAAA,EACrE;AACF;AAQO,SAAS,yBACd,eACA,WACA,KACgB;AAChB,QAAM,WAAW,wBAAwB,GAAG;AAC5C,QAAM,UAAU,MAAM,wBAAwB,QAAQ,eAAe,WAAW,GAAG;AACnF,MAAI,aAAa,wBAAyB,QAAO,QAAQ;AAEzD,QAAM,SAAS;AAAA,IACb;AAAA,IACA,SAAS;AAAA,IACT,MAAM,SAAS,QAAQ,eAAe,WAAW,GAAG;AAAA,IACpD,MAAM;AAAA,EACR;AACA,SAAO,iBAAiB,MAAM,IAAI,SAAS,QAAQ;AACrD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const callbackProp = () => z.custom((value) => typeof value === "function", {
|
|
3
|
+
message: "[internal] expected a function prop"
|
|
4
|
+
});
|
|
5
|
+
const optionalCallbackProp = () => z.custom(
|
|
6
|
+
(value) => value === void 0 || typeof value === "function",
|
|
7
|
+
{ message: "[internal] expected a function prop or undefined" }
|
|
8
|
+
);
|
|
9
|
+
const opaqueProp = () => z.custom(() => true);
|
|
10
|
+
export {
|
|
11
|
+
callbackProp,
|
|
12
|
+
opaqueProp,
|
|
13
|
+
optionalCallbackProp
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=componentContracts.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/componentContracts.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\n/**\n * Shared building blocks for the `propsSchema` every replaceable time-tracking\n * component publishes (EP-31).\n *\n * The schema is the contract a replacement must satisfy: `useRegisteredComponent`\n * parses the resolved props against it in development and falls back to the\n * original component when they do not match, so a schema that accepts anything\n * would let a broken replacement render instead of being caught. Scalars are\n * therefore described exactly; callbacks and React nodes, which zod cannot\n * describe structurally, are narrowed to the one runtime check that matters.\n */\n\nexport const callbackProp = <TCallback>() =>\n z.custom<TCallback>((value) => typeof value === 'function', {\n message: '[internal] expected a function prop',\n })\n\nexport const optionalCallbackProp = <TCallback>() =>\n z.custom<TCallback | undefined>(\n (value) => value === undefined || typeof value === 'function',\n { message: '[internal] expected a function prop or undefined' },\n )\n\nexport const opaqueProp = <TValue>() => z.custom<TValue>(() => true)\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAcX,MAAM,eAAe,MAC1B,EAAE,OAAkB,CAAC,UAAU,OAAO,UAAU,YAAY;AAAA,EAC1D,SAAS;AACX,CAAC;AAEI,MAAM,uBAAuB,MAClC,EAAE;AAAA,EACA,CAAC,UAAU,UAAU,UAAa,OAAO,UAAU;AAAA,EACnD,EAAE,SAAS,mDAAmD;AAChE;AAEK,MAAM,aAAa,MAAc,EAAE,OAAe,MAAM,IAAI;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { extensionPoints } from "@open-mercato/core/modules/staff/extension-points";
|
|
2
|
+
import { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from "./registries/registry.js";
|
|
3
|
+
import { tryStrategy } from "./registries/invoke.js";
|
|
4
|
+
import { hasResolverScope } from "./registries/scope.js";
|
|
5
|
+
const TIME_RATE_REGISTRY_ID = extensionPoints.hosts.timeRateRegistry.spotId;
|
|
6
|
+
const BUILT_IN_TIME_RATE_RESOLVER_ID = "staff.time_tracking.rate.entry_override_then_project";
|
|
7
|
+
const registry = createStrategyRegistry(TIME_RATE_REGISTRY_ID);
|
|
8
|
+
function registerTimeRateResolver(resolver) {
|
|
9
|
+
return registry.register(resolver);
|
|
10
|
+
}
|
|
11
|
+
function listTimeRateResolvers() {
|
|
12
|
+
return registry.list();
|
|
13
|
+
}
|
|
14
|
+
function getTimeRateResolver(id) {
|
|
15
|
+
return registry.get(id);
|
|
16
|
+
}
|
|
17
|
+
function round2(value) {
|
|
18
|
+
if (!Number.isFinite(value)) return 0;
|
|
19
|
+
const sign = value < 0 ? -1 : 1;
|
|
20
|
+
const magnitude = Math.abs(value);
|
|
21
|
+
const shifted = Number(`${magnitude}e2`);
|
|
22
|
+
if (!Number.isFinite(shifted)) return sign * (Math.round(magnitude * 100) / 100);
|
|
23
|
+
const rounded = Math.round(shifted);
|
|
24
|
+
const restored = Number(`${rounded}e-2`);
|
|
25
|
+
if (!Number.isFinite(restored)) return sign * (rounded / 100);
|
|
26
|
+
return sign * restored;
|
|
27
|
+
}
|
|
28
|
+
function toCents(amount) {
|
|
29
|
+
return Math.round(round2(amount) * 100);
|
|
30
|
+
}
|
|
31
|
+
function builtInRate(ctx) {
|
|
32
|
+
const override = ctx.entry?.rateOverrideAmount;
|
|
33
|
+
if (override !== null && override !== void 0 && Number.isFinite(override)) return override;
|
|
34
|
+
const projectRate = ctx.project?.hourlyRate;
|
|
35
|
+
if (projectRate !== null && projectRate !== void 0 && Number.isFinite(projectRate)) return projectRate;
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const builtInRateResolver = registry.registerBuiltIn({
|
|
39
|
+
id: BUILT_IN_TIME_RATE_RESOLVER_ID,
|
|
40
|
+
priority: BUILT_IN_STRATEGY_PRIORITY,
|
|
41
|
+
resolve: builtInRate
|
|
42
|
+
});
|
|
43
|
+
function resolveTimeRate(ctx) {
|
|
44
|
+
const scoped = hasResolverScope(ctx);
|
|
45
|
+
for (const resolver of registry.list()) {
|
|
46
|
+
if (!scoped && resolver.id !== BUILT_IN_TIME_RATE_RESOLVER_ID) continue;
|
|
47
|
+
const rate = tryStrategy(TIME_RATE_REGISTRY_ID, resolver.id, () => resolver.resolve(ctx));
|
|
48
|
+
if (rate !== null && rate !== void 0 && Number.isFinite(rate)) return rate;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function applicableRate(entry, project, ctx) {
|
|
53
|
+
return resolveTimeRate({ ...ctx ?? {}, entry: entry ?? null, project: project ?? null });
|
|
54
|
+
}
|
|
55
|
+
function entryAmount(entry, project, ctx) {
|
|
56
|
+
if (!entry?.isBillable) return null;
|
|
57
|
+
const rate = applicableRate(entry, project, ctx);
|
|
58
|
+
if (rate === null) return null;
|
|
59
|
+
const minutes = Number.isFinite(entry.roundedMinutes) ? entry.roundedMinutes : 0;
|
|
60
|
+
return round2(minutes / 60 * rate);
|
|
61
|
+
}
|
|
62
|
+
function sumAmounts(amounts) {
|
|
63
|
+
let cents = 0;
|
|
64
|
+
for (const amount of amounts) {
|
|
65
|
+
if (amount === null || amount === void 0 || !Number.isFinite(amount)) continue;
|
|
66
|
+
cents += toCents(amount);
|
|
67
|
+
}
|
|
68
|
+
return round2(cents / 100);
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
BUILT_IN_TIME_RATE_RESOLVER_ID,
|
|
72
|
+
TIME_RATE_REGISTRY_ID,
|
|
73
|
+
applicableRate,
|
|
74
|
+
entryAmount,
|
|
75
|
+
getTimeRateResolver,
|
|
76
|
+
listTimeRateResolvers,
|
|
77
|
+
registerTimeRateResolver,
|
|
78
|
+
resolveTimeRate,
|
|
79
|
+
round2,
|
|
80
|
+
sumAmounts
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/cost.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * EP-33 \u2014 the rate resolver chain.\n *\n * `applicableRate` is still the single place an hourly rate is chosen, and\n * `entryAmount` still the single place an amount is produced (D-7). What changed\n * is that the override \u2192 project-rate chain now lives in the built-in resolver\n * `staff.time_tracking.rate.entry_override_then_project`, registered at module\n * load, instead of being spelled inline. Contributed resolvers are consulted in\n * descending priority ahead of it, but only when the caller supplies a complete\n * tenant + organization scope; with no contribution \u2014 or with none of them\n * answering \u2014 the built-in decides, so the number is the one this file always\n * produced.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { createStrategyRegistry, BUILT_IN_STRATEGY_PRIORITY } from './registries/registry'\nimport { tryStrategy } from './registries/invoke'\nimport { hasResolverScope, type ScopedResolverContext } from './registries/scope'\n\nexport type CostEntry = {\n isBillable: boolean\n roundedMinutes: number\n rateOverrideAmount?: number | null\n}\n\nexport type CostProject = {\n hourlyRate?: number | null\n}\n\n/**\n * Everything a rate resolver may reason about. Only `entry` and `project` are\n * populated by the module's own call sites; the remaining fields exist so a\n * contributed resolver can key off seniority, task type, customer contract or an\n * effective date without the call site having to invent its own context shape.\n */\nexport type TimeRateContext = ScopedResolverContext & {\n entry?: Pick<CostEntry, 'rateOverrideAmount'> | null\n project?: CostProject | null\n task?: { id?: string | null; timeProjectId?: string | null } | null\n staffMember?: { id?: string | null } | null\n role?: { id?: string | null; name?: string | null } | null\n customer?: { id?: string | null } | null\n date?: string | null\n}\n\nexport type TimeRateResolver = {\n id: string\n priority?: number\n resolve(ctx: TimeRateContext): number | null\n}\n\nexport const TIME_RATE_REGISTRY_ID = extensionPoints.hosts.timeRateRegistry.spotId\n\nexport const BUILT_IN_TIME_RATE_RESOLVER_ID = 'staff.time_tracking.rate.entry_override_then_project'\n\nconst registry = createStrategyRegistry<TimeRateResolver>(TIME_RATE_REGISTRY_ID)\n\nexport function registerTimeRateResolver(resolver: TimeRateResolver): () => void {\n return registry.register(resolver)\n}\n\nexport function listTimeRateResolvers(): TimeRateResolver[] {\n return registry.list()\n}\n\nexport function getTimeRateResolver(id: string | null | undefined): TimeRateResolver | null {\n return registry.get(id)\n}\n\nexport function round2(value: number): number {\n if (!Number.isFinite(value)) return 0\n const sign = value < 0 ? -1 : 1\n const magnitude = Math.abs(value)\n const shifted = Number(`${magnitude}e2`)\n if (!Number.isFinite(shifted)) return sign * (Math.round(magnitude * 100) / 100)\n const rounded = Math.round(shifted)\n const restored = Number(`${rounded}e-2`)\n if (!Number.isFinite(restored)) return sign * (rounded / 100)\n return sign * restored\n}\n\nfunction toCents(amount: number): number {\n return Math.round(round2(amount) * 100)\n}\n\nfunction builtInRate(ctx: TimeRateContext): number | null {\n const override = ctx.entry?.rateOverrideAmount\n if (override !== null && override !== undefined && Number.isFinite(override)) return override\n const projectRate = ctx.project?.hourlyRate\n if (projectRate !== null && projectRate !== undefined && Number.isFinite(projectRate)) return projectRate\n return null\n}\n\nconst builtInRateResolver: TimeRateResolver = registry.registerBuiltIn({\n id: BUILT_IN_TIME_RATE_RESOLVER_ID,\n priority: BUILT_IN_STRATEGY_PRIORITY,\n resolve: builtInRate,\n})\n\n/**\n * Walks the registry in priority order and takes the first non-null answer.\n * Contributed resolvers are skipped entirely when the context carries no\n * complete scope, which leaves the built-in as the only candidate.\n */\nexport function resolveTimeRate(ctx: TimeRateContext): number | null {\n const scoped = hasResolverScope(ctx)\n for (const resolver of registry.list()) {\n if (!scoped && resolver.id !== BUILT_IN_TIME_RATE_RESOLVER_ID) continue\n // A chain asks the next candidate anyway, so a thrower is skipped rather than\n // replaced \u2014 and the built-in is always the last candidate.\n const rate = tryStrategy(TIME_RATE_REGISTRY_ID, resolver.id, () => resolver.resolve(ctx))\n if (rate !== null && rate !== undefined && Number.isFinite(rate)) return rate\n }\n return null\n}\n\nexport function applicableRate(\n entry: Pick<CostEntry, 'rateOverrideAmount'> | null | undefined,\n project: CostProject | null | undefined,\n ctx?: Omit<TimeRateContext, 'entry' | 'project'> | null,\n): number | null {\n return resolveTimeRate({ ...(ctx ?? {}), entry: entry ?? null, project: project ?? null })\n}\n\nexport function entryAmount(\n entry: CostEntry,\n project: CostProject | null | undefined,\n ctx?: Omit<TimeRateContext, 'entry' | 'project'> | null,\n): number | null {\n if (!entry?.isBillable) return null\n const rate = applicableRate(entry, project, ctx)\n if (rate === null) return null\n const minutes = Number.isFinite(entry.roundedMinutes) ? entry.roundedMinutes : 0\n return round2((minutes / 60) * rate)\n}\n\nexport function sumAmounts(amounts: readonly (number | null | undefined)[]): number {\n let cents = 0\n for (const amount of amounts) {\n if (amount === null || amount === undefined || !Number.isFinite(amount)) continue\n cents += toCents(amount)\n }\n return round2(cents / 100)\n}\n"],
|
|
5
|
+
"mappings": "AAcA,SAAS,uBAAuB;AAChC,SAAS,wBAAwB,kCAAkC;AACnE,SAAS,mBAAmB;AAC5B,SAAS,wBAAoD;AAkCtD,MAAM,wBAAwB,gBAAgB,MAAM,iBAAiB;AAErE,MAAM,iCAAiC;AAE9C,MAAM,WAAW,uBAAyC,qBAAqB;AAExE,SAAS,yBAAyB,UAAwC;AAC/E,SAAO,SAAS,SAAS,QAAQ;AACnC;AAEO,SAAS,wBAA4C;AAC1D,SAAO,SAAS,KAAK;AACvB;AAEO,SAAS,oBAAoB,IAAwD;AAC1F,SAAO,SAAS,IAAI,EAAE;AACxB;AAEO,SAAS,OAAO,OAAuB;AAC5C,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,QAAM,OAAO,QAAQ,IAAI,KAAK;AAC9B,QAAM,YAAY,KAAK,IAAI,KAAK;AAChC,QAAM,UAAU,OAAO,GAAG,SAAS,IAAI;AACvC,MAAI,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO,QAAQ,KAAK,MAAM,YAAY,GAAG,IAAI;AAC5E,QAAM,UAAU,KAAK,MAAM,OAAO;AAClC,QAAM,WAAW,OAAO,GAAG,OAAO,KAAK;AACvC,MAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,QAAO,QAAQ,UAAU;AACzD,SAAO,OAAO;AAChB;AAEA,SAAS,QAAQ,QAAwB;AACvC,SAAO,KAAK,MAAM,OAAO,MAAM,IAAI,GAAG;AACxC;AAEA,SAAS,YAAY,KAAqC;AACxD,QAAM,WAAW,IAAI,OAAO;AAC5B,MAAI,aAAa,QAAQ,aAAa,UAAa,OAAO,SAAS,QAAQ,EAAG,QAAO;AACrF,QAAM,cAAc,IAAI,SAAS;AACjC,MAAI,gBAAgB,QAAQ,gBAAgB,UAAa,OAAO,SAAS,WAAW,EAAG,QAAO;AAC9F,SAAO;AACT;AAEA,MAAM,sBAAwC,SAAS,gBAAgB;AAAA,EACrE,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AACX,CAAC;AAOM,SAAS,gBAAgB,KAAqC;AACnE,QAAM,SAAS,iBAAiB,GAAG;AACnC,aAAW,YAAY,SAAS,KAAK,GAAG;AACtC,QAAI,CAAC,UAAU,SAAS,OAAO,+BAAgC;AAG/D,UAAM,OAAO,YAAY,uBAAuB,SAAS,IAAI,MAAM,SAAS,QAAQ,GAAG,CAAC;AACxF,QAAI,SAAS,QAAQ,SAAS,UAAa,OAAO,SAAS,IAAI,EAAG,QAAO;AAAA,EAC3E;AACA,SAAO;AACT;AAEO,SAAS,eACd,OACA,SACA,KACe;AACf,SAAO,gBAAgB,EAAE,GAAI,OAAO,CAAC,GAAI,OAAO,SAAS,MAAM,SAAS,WAAW,KAAK,CAAC;AAC3F;AAEO,SAAS,YACd,OACA,SACA,KACe;AACf,MAAI,CAAC,OAAO,WAAY,QAAO;AAC/B,QAAM,OAAO,eAAe,OAAO,SAAS,GAAG;AAC/C,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,UAAU,OAAO,SAAS,MAAM,cAAc,IAAI,MAAM,iBAAiB;AAC/E,SAAO,OAAQ,UAAU,KAAM,IAAI;AACrC;AAEO,SAAS,WAAW,SAAyD;AAClF,MAAI,QAAQ;AACZ,aAAW,UAAU,SAAS;AAC5B,QAAI,WAAW,QAAQ,WAAW,UAAa,CAAC,OAAO,SAAS,MAAM,EAAG;AACzE,aAAS,QAAQ,MAAM;AAAA,EACzB;AACA,SAAO,OAAO,QAAQ,GAAG;AAC3B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const MAX_DURATION_MINUTES = 1440;
|
|
2
|
+
const CLOCK_PATTERN = /^(\d{1,3}):([0-5]\d)$/;
|
|
3
|
+
const UNIT_PATTERN = /^(?:(\d+(?:[.,]\d+)?)h)?(?:(\d+(?:[.,]\d+)?)m(?:in)?)?$/;
|
|
4
|
+
const BARE_NUMBER_PATTERN = /^\d+(?:[.,]\d+)?$/;
|
|
5
|
+
function toNumber(value) {
|
|
6
|
+
return Number.parseFloat(value.replace(",", "."));
|
|
7
|
+
}
|
|
8
|
+
function clampMinutes(minutes) {
|
|
9
|
+
if (!Number.isFinite(minutes)) return 0;
|
|
10
|
+
if (minutes < 0) return 0;
|
|
11
|
+
if (minutes > MAX_DURATION_MINUTES) return MAX_DURATION_MINUTES;
|
|
12
|
+
return Math.round(minutes);
|
|
13
|
+
}
|
|
14
|
+
function parseDuration(input) {
|
|
15
|
+
if (typeof input !== "string") return { ok: false, reason: "unparseable" };
|
|
16
|
+
const trimmed = input.trim();
|
|
17
|
+
if (!trimmed) return { ok: false, reason: "empty" };
|
|
18
|
+
const compact = trimmed.replace(/\s+/g, "").toLowerCase();
|
|
19
|
+
if (compact.startsWith("-")) return { ok: false, reason: "unparseable" };
|
|
20
|
+
const clock = CLOCK_PATTERN.exec(compact);
|
|
21
|
+
if (clock) {
|
|
22
|
+
const hours = Number.parseInt(clock[1], 10);
|
|
23
|
+
const minutes = Number.parseInt(clock[2], 10);
|
|
24
|
+
return { ok: true, minutes: clampMinutes(hours * 60 + minutes) };
|
|
25
|
+
}
|
|
26
|
+
const units = UNIT_PATTERN.exec(compact);
|
|
27
|
+
if (units && (units[1] !== void 0 || units[2] !== void 0)) {
|
|
28
|
+
const hours = units[1] !== void 0 ? toNumber(units[1]) : 0;
|
|
29
|
+
const minutes = units[2] !== void 0 ? toNumber(units[2]) : 0;
|
|
30
|
+
if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return { ok: false, reason: "unparseable" };
|
|
31
|
+
return { ok: true, minutes: clampMinutes(hours * 60 + minutes) };
|
|
32
|
+
}
|
|
33
|
+
if (BARE_NUMBER_PATTERN.test(compact)) {
|
|
34
|
+
const hours = toNumber(compact);
|
|
35
|
+
if (!Number.isFinite(hours)) return { ok: false, reason: "unparseable" };
|
|
36
|
+
return { ok: true, minutes: clampMinutes(hours * 60) };
|
|
37
|
+
}
|
|
38
|
+
return { ok: false, reason: "unparseable" };
|
|
39
|
+
}
|
|
40
|
+
function formatDuration(minutes, style) {
|
|
41
|
+
const safe = Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : 0;
|
|
42
|
+
const hours = Math.floor(safe / 60);
|
|
43
|
+
const rest = safe % 60;
|
|
44
|
+
if (style === "clock") return `${hours}:${String(rest).padStart(2, "0")}`;
|
|
45
|
+
if (style === "decimal") return (safe / 60).toFixed(2);
|
|
46
|
+
if (hours === 0) return `${rest}m`;
|
|
47
|
+
if (rest === 0) return `${hours}h`;
|
|
48
|
+
return `${hours}h ${rest}m`;
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
MAX_DURATION_MINUTES,
|
|
52
|
+
formatDuration,
|
|
53
|
+
parseDuration
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=duration.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/duration.ts"],
|
|
4
|
+
"sourcesContent": ["export const MAX_DURATION_MINUTES = 1440\n\nexport type DurationParseFailureReason = 'empty' | 'unparseable'\n\nexport type ParseDurationResult =\n | { ok: true; minutes: number }\n | { ok: false; reason: DurationParseFailureReason }\n\nexport type DurationFormatStyle = 'hm' | 'clock' | 'decimal'\n\nconst CLOCK_PATTERN = /^(\\d{1,3}):([0-5]\\d)$/\nconst UNIT_PATTERN = /^(?:(\\d+(?:[.,]\\d+)?)h)?(?:(\\d+(?:[.,]\\d+)?)m(?:in)?)?$/\nconst BARE_NUMBER_PATTERN = /^\\d+(?:[.,]\\d+)?$/\n\nfunction toNumber(value: string): number {\n return Number.parseFloat(value.replace(',', '.'))\n}\n\nfunction clampMinutes(minutes: number): number {\n if (!Number.isFinite(minutes)) return 0\n if (minutes < 0) return 0\n if (minutes > MAX_DURATION_MINUTES) return MAX_DURATION_MINUTES\n return Math.round(minutes)\n}\n\nexport function parseDuration(input: string): ParseDurationResult {\n if (typeof input !== 'string') return { ok: false, reason: 'unparseable' }\n const trimmed = input.trim()\n if (!trimmed) return { ok: false, reason: 'empty' }\n\n const compact = trimmed.replace(/\\s+/g, '').toLowerCase()\n if (compact.startsWith('-')) return { ok: false, reason: 'unparseable' }\n\n const clock = CLOCK_PATTERN.exec(compact)\n if (clock) {\n const hours = Number.parseInt(clock[1], 10)\n const minutes = Number.parseInt(clock[2], 10)\n return { ok: true, minutes: clampMinutes(hours * 60 + minutes) }\n }\n\n const units = UNIT_PATTERN.exec(compact)\n if (units && (units[1] !== undefined || units[2] !== undefined)) {\n const hours = units[1] !== undefined ? toNumber(units[1]) : 0\n const minutes = units[2] !== undefined ? toNumber(units[2]) : 0\n if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return { ok: false, reason: 'unparseable' }\n return { ok: true, minutes: clampMinutes(hours * 60 + minutes) }\n }\n\n if (BARE_NUMBER_PATTERN.test(compact)) {\n const hours = toNumber(compact)\n if (!Number.isFinite(hours)) return { ok: false, reason: 'unparseable' }\n return { ok: true, minutes: clampMinutes(hours * 60) }\n }\n\n return { ok: false, reason: 'unparseable' }\n}\n\nexport function formatDuration(minutes: number, style: DurationFormatStyle): string {\n const safe = Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : 0\n const hours = Math.floor(safe / 60)\n const rest = safe % 60\n\n if (style === 'clock') return `${hours}:${String(rest).padStart(2, '0')}`\n if (style === 'decimal') return (safe / 60).toFixed(2)\n if (hours === 0) return `${rest}m`\n if (rest === 0) return `${hours}h`\n return `${hours}h ${rest}m`\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,uBAAuB;AAUpC,MAAM,gBAAgB;AACtB,MAAM,eAAe;AACrB,MAAM,sBAAsB;AAE5B,SAAS,SAAS,OAAuB;AACvC,SAAO,OAAO,WAAW,MAAM,QAAQ,KAAK,GAAG,CAAC;AAClD;AAEA,SAAS,aAAa,SAAyB;AAC7C,MAAI,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO;AACtC,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,UAAU,qBAAsB,QAAO;AAC3C,SAAO,KAAK,MAAM,OAAO;AAC3B;AAEO,SAAS,cAAc,OAAoC;AAChE,MAAI,OAAO,UAAU,SAAU,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AACzE,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO,EAAE,IAAI,OAAO,QAAQ,QAAQ;AAElD,QAAM,UAAU,QAAQ,QAAQ,QAAQ,EAAE,EAAE,YAAY;AACxD,MAAI,QAAQ,WAAW,GAAG,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AAEvE,QAAM,QAAQ,cAAc,KAAK,OAAO;AACxC,MAAI,OAAO;AACT,UAAM,QAAQ,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAC1C,UAAM,UAAU,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAC5C,WAAO,EAAE,IAAI,MAAM,SAAS,aAAa,QAAQ,KAAK,OAAO,EAAE;AAAA,EACjE;AAEA,QAAM,QAAQ,aAAa,KAAK,OAAO;AACvC,MAAI,UAAU,MAAM,CAAC,MAAM,UAAa,MAAM,CAAC,MAAM,SAAY;AAC/D,UAAM,QAAQ,MAAM,CAAC,MAAM,SAAY,SAAS,MAAM,CAAC,CAAC,IAAI;AAC5D,UAAM,UAAU,MAAM,CAAC,MAAM,SAAY,SAAS,MAAM,CAAC,CAAC,IAAI;AAC9D,QAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AACpG,WAAO,EAAE,IAAI,MAAM,SAAS,aAAa,QAAQ,KAAK,OAAO,EAAE;AAAA,EACjE;AAEA,MAAI,oBAAoB,KAAK,OAAO,GAAG;AACrC,UAAM,QAAQ,SAAS,OAAO;AAC9B,QAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AACvE,WAAO,EAAE,IAAI,MAAM,SAAS,aAAa,QAAQ,EAAE,EAAE;AAAA,EACvD;AAEA,SAAO,EAAE,IAAI,OAAO,QAAQ,cAAc;AAC5C;AAEO,SAAS,eAAe,SAAiB,OAAoC;AAClF,QAAM,OAAO,OAAO,SAAS,OAAO,KAAK,UAAU,IAAI,KAAK,MAAM,OAAO,IAAI;AAC7E,QAAM,QAAQ,KAAK,MAAM,OAAO,EAAE;AAClC,QAAM,OAAO,OAAO;AAEpB,MAAI,UAAU,QAAS,QAAO,GAAG,KAAK,IAAI,OAAO,IAAI,EAAE,SAAS,GAAG,GAAG,CAAC;AACvE,MAAI,UAAU,UAAW,SAAQ,OAAO,IAAI,QAAQ,CAAC;AACrD,MAAI,UAAU,EAAG,QAAO,GAAG,IAAI;AAC/B,MAAI,SAAS,EAAG,QAAO,GAAG,KAAK;AAC/B,SAAO,GAAG,KAAK,KAAK,IAAI;AAC1B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
2
|
+
const logger = createLogger("staff").child({ component: "time-tracking/featureAccess" });
|
|
3
|
+
const DENIED = { allowed: false, grantedFeatures: [], resolved: false };
|
|
4
|
+
async function resolveFeatureAccess(container, userId, required, scope) {
|
|
5
|
+
if (!userId) return { ...DENIED, resolved: true };
|
|
6
|
+
let rbac;
|
|
7
|
+
try {
|
|
8
|
+
rbac = container.resolve("rbacService");
|
|
9
|
+
} catch (err) {
|
|
10
|
+
logger.warn("rbacService could not be resolved; denying", { err, userId, required });
|
|
11
|
+
return { ...DENIED };
|
|
12
|
+
}
|
|
13
|
+
if (!rbac?.userHasAllFeatures) {
|
|
14
|
+
logger.warn("rbacService cannot answer feature checks; denying", { userId, required });
|
|
15
|
+
return { ...DENIED };
|
|
16
|
+
}
|
|
17
|
+
let allowed = false;
|
|
18
|
+
try {
|
|
19
|
+
allowed = await rbac.userHasAllFeatures(userId, [...required], scope) === true;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
logger.warn("feature check failed; denying", { err, userId, required });
|
|
22
|
+
return { ...DENIED };
|
|
23
|
+
}
|
|
24
|
+
let grantedFeatures = [];
|
|
25
|
+
let resolved = false;
|
|
26
|
+
if (!rbac.getGrantedFeatures) {
|
|
27
|
+
logger.warn("rbacService cannot list grants; feature decision still stands", { userId, required });
|
|
28
|
+
} else {
|
|
29
|
+
try {
|
|
30
|
+
grantedFeatures = await rbac.getGrantedFeatures(userId, scope) ?? [];
|
|
31
|
+
resolved = true;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
logger.warn("grant list could not be read; feature decision still stands", { err, userId });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { allowed, grantedFeatures, resolved };
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
resolveFeatureAccess
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=featureAccess.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/featureAccess.ts"],
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/featureAccess' })\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n userHasAllFeatures?: (\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n}\n\ntype ContainerLike = { resolve: (name: string) => unknown }\n\nexport type FeatureAccess = {\n /** The decision. Fail-closed: `false` whenever it could not be established. */\n allowed: boolean\n /**\n * The caller's grants, for the plumbing that genuinely needs a list (mutation\n * guards, enrichers). Empty when unresolved \u2014 check `resolved` before reading\n * anything into an empty array.\n */\n grantedFeatures: string[]\n /**\n * `false` when RBAC could not be consulted \u2014 the service was unresolvable, it\n * exposes no way to answer, or the call threw. An unresolved lookup is not the\n * same as \"no permission\", and a caller that treats it as one silently\n * downgrades somebody's access.\n *\n * `true` therefore means the answer is definite: RBAC produced the grant list,\n * or there was no one to ask about (no user). An empty `grantedFeatures` is\n * only ever a real answer under `resolved`.\n */\n resolved: boolean\n}\n\nconst DENIED: FeatureAccess = { allowed: false, grantedFeatures: [], resolved: false }\n\n/**\n * One question, one authority, one failure direction.\n *\n * The module previously carried twelve hand-rolled copies of this lookup, each\n * choosing its own semantics: nine returned `[]` on failure (fail-closed, but\n * silently \u2014 a manager was demoted to their own project memberships and nothing\n * said why), six returned `null` and were then read as `granted === null || \u2026`\n * (fail-open \u2014 which is how an RBAC outage handed hourly rates to plain report\n * viewers), and the rest were somewhere in between. The same\n * `staff.timesheets.rates.view` flag ended up failing closed on two surfaces and\n * open on three.\n *\n * The decision comes from `userHasAllFeatures` rather than from matching the\n * grant array locally. Both paths agree in a healthy runtime \u2014 `getGrantedFeatures`\n * expands a super-admin's `'*'` into per-module wildcards that `authorizeFeatures`\n * understands \u2014 but only the service call carries `isSuperAdmin` explicitly, and\n * asking one question in one way is what stops two surfaces disagreeing about the\n * same person.\n *\n * The array is returned alongside it because guards and enrichers legitimately\n * need a list. It is deliberately *not* the thing to authorize on.\n *\n * A failed lookup logs. That is the point: the previous silence meant an RBAC\n * outage presented to users as missing data and to operators as nothing at all.\n */\nexport async function resolveFeatureAccess(\n container: ContainerLike,\n userId: string | null,\n required: readonly string[],\n scope: { tenantId: string | null; organizationId: string | null },\n): Promise<FeatureAccess> {\n if (!userId) return { ...DENIED, resolved: true }\n\n let rbac: RbacServiceLike | undefined\n try {\n rbac = container.resolve('rbacService') as RbacServiceLike | undefined\n } catch (err) {\n logger.warn('rbacService could not be resolved; denying', { err, userId, required })\n return { ...DENIED }\n }\n if (!rbac?.userHasAllFeatures) {\n logger.warn('rbacService cannot answer feature checks; denying', { userId, required })\n return { ...DENIED }\n }\n\n let allowed = false\n try {\n allowed = (await rbac.userHasAllFeatures(userId, [...required], scope)) === true\n } catch (err) {\n logger.warn('feature check failed; denying', { err, userId, required })\n return { ...DENIED }\n }\n\n // The grant list is a convenience, not the decision, so a failure to read it\n // downgrades the plumbing that wants a list without touching `allowed`.\n // A service that cannot produce a list at all is the same kind of failure as\n // one whose call throws: the empty array is a fallback, not RBAC's answer, and\n // saying otherwise is how an empty list gets read as authoritative.\n let grantedFeatures: string[] = []\n let resolved = false\n if (!rbac.getGrantedFeatures) {\n logger.warn('rbacService cannot list grants; feature decision still stands', { userId, required })\n } else {\n try {\n grantedFeatures = (await rbac.getGrantedFeatures(userId, scope)) ?? []\n resolved = true\n } catch (err) {\n logger.warn('grant list could not be read; feature decision still stands', { err, userId })\n }\n }\n\n return { allowed, grantedFeatures, resolved }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,OAAO,EAAE,MAAM,EAAE,WAAW,8BAA8B,CAAC;AAsCvF,MAAM,SAAwB,EAAE,SAAS,OAAO,iBAAiB,CAAC,GAAG,UAAU,MAAM;AA2BrF,eAAsB,qBACpB,WACA,QACA,UACA,OACwB;AACxB,MAAI,CAAC,OAAQ,QAAO,EAAE,GAAG,QAAQ,UAAU,KAAK;AAEhD,MAAI;AACJ,MAAI;AACF,WAAO,UAAU,QAAQ,aAAa;AAAA,EACxC,SAAS,KAAK;AACZ,WAAO,KAAK,8CAA8C,EAAE,KAAK,QAAQ,SAAS,CAAC;AACnF,WAAO,EAAE,GAAG,OAAO;AAAA,EACrB;AACA,MAAI,CAAC,MAAM,oBAAoB;AAC7B,WAAO,KAAK,qDAAqD,EAAE,QAAQ,SAAS,CAAC;AACrF,WAAO,EAAE,GAAG,OAAO;AAAA,EACrB;AAEA,MAAI,UAAU;AACd,MAAI;AACF,cAAW,MAAM,KAAK,mBAAmB,QAAQ,CAAC,GAAG,QAAQ,GAAG,KAAK,MAAO;AAAA,EAC9E,SAAS,KAAK;AACZ,WAAO,KAAK,iCAAiC,EAAE,KAAK,QAAQ,SAAS,CAAC;AACtE,WAAO,EAAE,GAAG,OAAO;AAAA,EACrB;AAOA,MAAI,kBAA4B,CAAC;AACjC,MAAI,WAAW;AACf,MAAI,CAAC,KAAK,oBAAoB;AAC5B,WAAO,KAAK,iEAAiE,EAAE,QAAQ,SAAS,CAAC;AAAA,EACnG,OAAO;AACL,QAAI;AACF,wBAAmB,MAAM,KAAK,mBAAmB,QAAQ,KAAK,KAAM,CAAC;AACrE,iBAAW;AAAA,IACb,SAAS,KAAK;AACZ,aAAO,KAAK,+DAA+D,EAAE,KAAK,OAAO,CAAC;AAAA,IAC5F;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,iBAAiB,SAAS;AAC9C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const MINUTES_PER_DAY = 1440;
|
|
2
|
+
const CLOCK_PATTERN = /^(\d{1,2}):(\d{2})$/;
|
|
3
|
+
function parseClock(value) {
|
|
4
|
+
if (typeof value !== "string") return null;
|
|
5
|
+
const match = CLOCK_PATTERN.exec(value.trim());
|
|
6
|
+
if (!match) return null;
|
|
7
|
+
const hours = Number.parseInt(match[1], 10);
|
|
8
|
+
const minutes = Number.parseInt(match[2], 10);
|
|
9
|
+
if (hours > 23 || minutes > 59) return null;
|
|
10
|
+
return hours * 60 + minutes;
|
|
11
|
+
}
|
|
12
|
+
function formatClock(minutesOfDay) {
|
|
13
|
+
const normalized = (Math.round(minutesOfDay) % MINUTES_PER_DAY + MINUTES_PER_DAY) % MINUTES_PER_DAY;
|
|
14
|
+
const hours = Math.floor(normalized / 60);
|
|
15
|
+
const minutes = normalized % 60;
|
|
16
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
17
|
+
}
|
|
18
|
+
function normalizeDuration(value) {
|
|
19
|
+
if (value === null || value === void 0) return null;
|
|
20
|
+
if (!Number.isFinite(value) || value < 0) return null;
|
|
21
|
+
return Math.round(value);
|
|
22
|
+
}
|
|
23
|
+
function deriveInterval(input) {
|
|
24
|
+
const start = parseClock(input.start);
|
|
25
|
+
const end = parseClock(input.end);
|
|
26
|
+
const duration = normalizeDuration(input.durationMinutes);
|
|
27
|
+
if (start !== null && end !== null) {
|
|
28
|
+
const crossesMidnight = end < start;
|
|
29
|
+
const computedDuration = crossesMidnight ? MINUTES_PER_DAY - start + end : end - start;
|
|
30
|
+
return {
|
|
31
|
+
start: formatClock(start),
|
|
32
|
+
end: formatClock(end),
|
|
33
|
+
durationMinutes: computedDuration,
|
|
34
|
+
computed: duration === computedDuration ? null : "duration",
|
|
35
|
+
crossesMidnight
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (start !== null && duration !== null) {
|
|
39
|
+
const absoluteEnd = start + duration;
|
|
40
|
+
return {
|
|
41
|
+
start: formatClock(start),
|
|
42
|
+
end: formatClock(absoluteEnd),
|
|
43
|
+
durationMinutes: duration,
|
|
44
|
+
computed: "end",
|
|
45
|
+
crossesMidnight: absoluteEnd >= MINUTES_PER_DAY
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (end !== null && duration !== null) {
|
|
49
|
+
const absoluteStart = end - duration;
|
|
50
|
+
return {
|
|
51
|
+
start: formatClock(absoluteStart),
|
|
52
|
+
end: formatClock(end),
|
|
53
|
+
durationMinutes: duration,
|
|
54
|
+
computed: "start",
|
|
55
|
+
crossesMidnight: absoluteStart < 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
start: start === null ? null : formatClock(start),
|
|
60
|
+
end: end === null ? null : formatClock(end),
|
|
61
|
+
durationMinutes: duration,
|
|
62
|
+
computed: null,
|
|
63
|
+
crossesMidnight: false
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
MINUTES_PER_DAY,
|
|
68
|
+
deriveInterval,
|
|
69
|
+
formatClock,
|
|
70
|
+
parseClock
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=interval.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/interval.ts"],
|
|
4
|
+
"sourcesContent": ["export const MINUTES_PER_DAY = 1440\n\nexport type IntervalInput = {\n start?: string | null\n end?: string | null\n durationMinutes?: number | null\n}\n\nexport type ComputedIntervalField = 'start' | 'end' | 'duration' | null\n\nexport type DerivedInterval = {\n start: string | null\n end: string | null\n durationMinutes: number | null\n computed: ComputedIntervalField\n crossesMidnight: boolean\n}\n\nconst CLOCK_PATTERN = /^(\\d{1,2}):(\\d{2})$/\n\nexport function parseClock(value: string | null | undefined): number | null {\n if (typeof value !== 'string') return null\n const match = CLOCK_PATTERN.exec(value.trim())\n if (!match) return null\n const hours = Number.parseInt(match[1], 10)\n const minutes = Number.parseInt(match[2], 10)\n if (hours > 23 || minutes > 59) return null\n return hours * 60 + minutes\n}\n\nexport function formatClock(minutesOfDay: number): string {\n const normalized = ((Math.round(minutesOfDay) % MINUTES_PER_DAY) + MINUTES_PER_DAY) % MINUTES_PER_DAY\n const hours = Math.floor(normalized / 60)\n const minutes = normalized % 60\n return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`\n}\n\nfunction normalizeDuration(value: number | null | undefined): number | null {\n if (value === null || value === undefined) return null\n if (!Number.isFinite(value) || value < 0) return null\n return Math.round(value)\n}\n\nexport function deriveInterval(input: IntervalInput): DerivedInterval {\n const start = parseClock(input.start)\n const end = parseClock(input.end)\n const duration = normalizeDuration(input.durationMinutes)\n\n if (start !== null && end !== null) {\n const crossesMidnight = end < start\n const computedDuration = crossesMidnight ? MINUTES_PER_DAY - start + end : end - start\n return {\n start: formatClock(start),\n end: formatClock(end),\n durationMinutes: computedDuration,\n computed: duration === computedDuration ? null : 'duration',\n crossesMidnight,\n }\n }\n\n if (start !== null && duration !== null) {\n const absoluteEnd = start + duration\n return {\n start: formatClock(start),\n end: formatClock(absoluteEnd),\n durationMinutes: duration,\n computed: 'end',\n crossesMidnight: absoluteEnd >= MINUTES_PER_DAY,\n }\n }\n\n if (end !== null && duration !== null) {\n const absoluteStart = end - duration\n return {\n start: formatClock(absoluteStart),\n end: formatClock(end),\n durationMinutes: duration,\n computed: 'start',\n crossesMidnight: absoluteStart < 0,\n }\n }\n\n return {\n start: start === null ? null : formatClock(start),\n end: end === null ? null : formatClock(end),\n durationMinutes: duration,\n computed: null,\n crossesMidnight: false,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,kBAAkB;AAkB/B,MAAM,gBAAgB;AAEf,SAAS,WAAW,OAAiD;AAC1E,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,QAAQ,cAAc,KAAK,MAAM,KAAK,CAAC;AAC7C,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAC1C,QAAM,UAAU,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAC5C,MAAI,QAAQ,MAAM,UAAU,GAAI,QAAO;AACvC,SAAO,QAAQ,KAAK;AACtB;AAEO,SAAS,YAAY,cAA8B;AACxD,QAAM,cAAe,KAAK,MAAM,YAAY,IAAI,kBAAmB,mBAAmB;AACtF,QAAM,QAAQ,KAAK,MAAM,aAAa,EAAE;AACxC,QAAM,UAAU,aAAa;AAC7B,SAAO,GAAG,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,OAAO,EAAE,SAAS,GAAG,GAAG,CAAC;AAC9E;AAEA,SAAS,kBAAkB,OAAiD;AAC1E,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,EAAG,QAAO;AACjD,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,OAAuC;AACpE,QAAM,QAAQ,WAAW,MAAM,KAAK;AACpC,QAAM,MAAM,WAAW,MAAM,GAAG;AAChC,QAAM,WAAW,kBAAkB,MAAM,eAAe;AAExD,MAAI,UAAU,QAAQ,QAAQ,MAAM;AAClC,UAAM,kBAAkB,MAAM;AAC9B,UAAM,mBAAmB,kBAAkB,kBAAkB,QAAQ,MAAM,MAAM;AACjF,WAAO;AAAA,MACL,OAAO,YAAY,KAAK;AAAA,MACxB,KAAK,YAAY,GAAG;AAAA,MACpB,iBAAiB;AAAA,MACjB,UAAU,aAAa,mBAAmB,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,QAAQ,aAAa,MAAM;AACvC,UAAM,cAAc,QAAQ;AAC5B,WAAO;AAAA,MACL,OAAO,YAAY,KAAK;AAAA,MACxB,KAAK,YAAY,WAAW;AAAA,MAC5B,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,iBAAiB,eAAe;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,QAAQ,QAAQ,aAAa,MAAM;AACrC,UAAM,gBAAgB,MAAM;AAC5B,WAAO;AAAA,MACL,OAAO,YAAY,aAAa;AAAA,MAChC,KAAK,YAAY,GAAG;AAAA,MACpB,iBAAiB;AAAA,MACjB,UAAU;AAAA,MACV,iBAAiB,gBAAgB;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,UAAU,OAAO,OAAO,YAAY,KAAK;AAAA,IAChD,KAAK,QAAQ,OAAO,OAAO,YAAY,GAAG;AAAA,IAC1C,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,iBAAiB;AAAA,EACnB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { StaffTimeProject, StaffTimeTask } from "../../data/entities.js";
|
|
2
|
+
import { PROJECT_CODE_TARGET_LENGTH, deriveProjectCodeBase } from "./projectCode.js";
|
|
3
|
+
import { formatTaskReference } from "../timesheets-tasks/taskReference.js";
|
|
4
|
+
function planProjectCodeMigration(projects, taskCountByProjectId) {
|
|
5
|
+
const taken = /* @__PURE__ */ new Set();
|
|
6
|
+
const shortening = [];
|
|
7
|
+
let skipped = 0;
|
|
8
|
+
for (const project of projects) {
|
|
9
|
+
const code = (project.code ?? "").trim();
|
|
10
|
+
if (code.length > 0 && code.length <= PROJECT_CODE_TARGET_LENGTH) {
|
|
11
|
+
taken.add(code.toUpperCase());
|
|
12
|
+
skipped += 1;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
shortening.push(project);
|
|
16
|
+
}
|
|
17
|
+
const ordered = [...shortening].sort((left, right) => left.id.localeCompare(right.id));
|
|
18
|
+
const changes = [];
|
|
19
|
+
for (const project of ordered) {
|
|
20
|
+
const base = deriveProjectCodeBase(project.name ?? "");
|
|
21
|
+
let candidate = base;
|
|
22
|
+
for (let counter = 2; taken.has(candidate.toUpperCase()); counter += 1) {
|
|
23
|
+
candidate = `${base}${counter}`;
|
|
24
|
+
}
|
|
25
|
+
taken.add(candidate.toUpperCase());
|
|
26
|
+
changes.push({
|
|
27
|
+
projectId: project.id,
|
|
28
|
+
projectName: project.name ?? "",
|
|
29
|
+
fromCode: project.code ?? "",
|
|
30
|
+
toCode: candidate,
|
|
31
|
+
taskCount: taskCountByProjectId.get(project.id) ?? 0
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { changes, skipped };
|
|
35
|
+
}
|
|
36
|
+
async function migrateProjectCodes(em, scope, options = {}) {
|
|
37
|
+
const projects = await em.find(StaffTimeProject, {
|
|
38
|
+
tenantId: scope.tenantId,
|
|
39
|
+
organizationId: scope.organizationId,
|
|
40
|
+
deletedAt: null
|
|
41
|
+
});
|
|
42
|
+
const tasks = await em.find(StaffTimeTask, {
|
|
43
|
+
tenantId: scope.tenantId,
|
|
44
|
+
organizationId: scope.organizationId,
|
|
45
|
+
deletedAt: null
|
|
46
|
+
});
|
|
47
|
+
const tasksByProjectId = /* @__PURE__ */ new Map();
|
|
48
|
+
for (const task of tasks) {
|
|
49
|
+
const bucket = tasksByProjectId.get(task.timeProjectId) ?? [];
|
|
50
|
+
bucket.push(task);
|
|
51
|
+
tasksByProjectId.set(task.timeProjectId, bucket);
|
|
52
|
+
}
|
|
53
|
+
const counts = new Map([...tasksByProjectId].map(([id, list]) => [id, list.length]));
|
|
54
|
+
const { changes, skipped } = planProjectCodeMigration(projects, counts);
|
|
55
|
+
if (options.dryRun) {
|
|
56
|
+
return {
|
|
57
|
+
changes,
|
|
58
|
+
skipped,
|
|
59
|
+
tasksRenumbered: changes.reduce((total, change) => total + change.taskCount, 0)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const projectById = new Map(projects.map((project) => [project.id, project]));
|
|
63
|
+
const now = /* @__PURE__ */ new Date();
|
|
64
|
+
let tasksRenumbered = 0;
|
|
65
|
+
await em.transactional(async (tem) => {
|
|
66
|
+
for (const change of changes) {
|
|
67
|
+
const project = projectById.get(change.projectId);
|
|
68
|
+
if (!project) continue;
|
|
69
|
+
const managed = tem.getReference(StaffTimeProject, project.id);
|
|
70
|
+
managed.code = change.toCode;
|
|
71
|
+
managed.updatedAt = now;
|
|
72
|
+
tem.persist(managed);
|
|
73
|
+
for (const task of tasksByProjectId.get(change.projectId) ?? []) {
|
|
74
|
+
const managedTask = tem.getReference(StaffTimeTask, task.id);
|
|
75
|
+
managedTask.reference = formatTaskReference(change.toCode, task.sequenceNumber);
|
|
76
|
+
managedTask.updatedAt = now;
|
|
77
|
+
tem.persist(managedTask);
|
|
78
|
+
tasksRenumbered += 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return { changes, skipped, tasksRenumbered };
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
migrateProjectCodes,
|
|
86
|
+
planProjectCodeMigration
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=migrateProjectCodes.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/staff/lib/time-tracking/migrateProjectCodes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Rewrites existing project codes to the short form and re-derives every task\n * reference to match.\n *\n * `staff_time_tasks.reference` is denormalised (`<project code>-<sequence>`) and\n * frozen at creation, because it is quoted where the task row is no longer\n * joined. That freeze is what makes this a migration rather than a settings\n * change: shortening a project's code does not retro-number its tasks, so\n * without this the tenant ends up with `EHK` projects whose tasks still say\n * `ERGO-HESTIA-KORPO-4`.\n *\n * What this deliberately preserves:\n *\n * * **Sequence numbers.** `APOLLO-14` becomes `APO-14`, never `APO-1`. The\n * number is the task's identity to the people who use it; only the prefix\n * changes.\n * * **Hand-picked codes.** A code that is already at or under the target length\n * was chosen, not derived, so it is left exactly as it is.\n *\n * What it cannot preserve: a reference somebody wrote down outside the system.\n * Reports do not quote task references \u2014 report lines are labelled by task title\n * and `staff_time_report_entries` freezes minutes and money, never the reference\n * \u2014 so nothing already exported changes meaning. Anything a person memorised\n * does.\n */\n\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTimeProject, StaffTimeTask } from '../../data/entities'\nimport { PROJECT_CODE_TARGET_LENGTH, deriveProjectCodeBase } from './projectCode'\nimport { formatTaskReference } from '../timesheets-tasks/taskReference'\n\nexport type MigrateProjectCodesScope = { tenantId: string; organizationId: string }\n\nexport type ProjectCodeChange = {\n projectId: string\n projectName: string\n fromCode: string\n toCode: string\n taskCount: number\n}\n\nexport type MigrateProjectCodesResult = {\n changes: ProjectCodeChange[]\n /** Projects left alone because their code was already short. */\n skipped: number\n tasksRenumbered: number\n}\n\n/**\n * Plans the rename without writing anything. Exported so the CLI can offer a dry\n * run: a rewrite of every code in a tenant is not something to discover the\n * shape of by executing it.\n */\nexport function planProjectCodeMigration(\n projects: readonly Pick<StaffTimeProject, 'id' | 'name' | 'code'>[],\n taskCountByProjectId: ReadonlyMap<string, number>,\n): { changes: ProjectCodeChange[]; skipped: number } {\n // Codes that survive untouched are reserved first, so a project keeping `HBH`\n // cannot have it taken from under it by a project being shortened to `HBH`.\n const taken = new Set<string>()\n const shortening: typeof projects[number][] = []\n let skipped = 0\n\n for (const project of projects) {\n const code = (project.code ?? '').trim()\n if (code.length > 0 && code.length <= PROJECT_CODE_TARGET_LENGTH) {\n taken.add(code.toUpperCase())\n skipped += 1\n continue\n }\n shortening.push(project)\n }\n\n // Oldest first, so a re-run is stable and the same project keeps the same code\n // rather than the order of a query deciding who gets the unsuffixed one.\n const ordered = [...shortening].sort((left, right) => left.id.localeCompare(right.id))\n\n const changes: ProjectCodeChange[] = []\n for (const project of ordered) {\n const base = deriveProjectCodeBase(project.name ?? '')\n let candidate = base\n for (let counter = 2; taken.has(candidate.toUpperCase()); counter += 1) {\n candidate = `${base}${counter}`\n }\n taken.add(candidate.toUpperCase())\n changes.push({\n projectId: project.id,\n projectName: project.name ?? '',\n fromCode: project.code ?? '',\n toCode: candidate,\n taskCount: taskCountByProjectId.get(project.id) ?? 0,\n })\n }\n\n return { changes, skipped }\n}\n\nexport async function migrateProjectCodes(\n em: EntityManager,\n scope: MigrateProjectCodesScope,\n options: { dryRun?: boolean } = {},\n): Promise<MigrateProjectCodesResult> {\n const projects = await em.find(StaffTimeProject, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n\n const tasks = await em.find(StaffTimeTask, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n\n const tasksByProjectId = new Map<string, StaffTimeTask[]>()\n for (const task of tasks) {\n const bucket = tasksByProjectId.get(task.timeProjectId) ?? []\n bucket.push(task)\n tasksByProjectId.set(task.timeProjectId, bucket)\n }\n const counts = new Map([...tasksByProjectId].map(([id, list]) => [id, list.length]))\n\n const { changes, skipped } = planProjectCodeMigration(projects, counts)\n if (options.dryRun) {\n return {\n changes,\n skipped,\n tasksRenumbered: changes.reduce((total, change) => total + change.taskCount, 0),\n }\n }\n\n const projectById = new Map(projects.map((project) => [project.id, project]))\n const now = new Date()\n let tasksRenumbered = 0\n\n // One transaction: a half-applied rename leaves tasks quoting a prefix their\n // project no longer carries, which is the exact inconsistency this exists to\n // remove.\n await em.transactional(async (tem) => {\n for (const change of changes) {\n const project = projectById.get(change.projectId)\n if (!project) continue\n const managed = tem.getReference(StaffTimeProject, project.id)\n managed.code = change.toCode\n managed.updatedAt = now\n tem.persist(managed)\n\n for (const task of tasksByProjectId.get(change.projectId) ?? []) {\n const managedTask = tem.getReference(StaffTimeTask, task.id)\n // The sequence number is the task's identity to the people who use it;\n // only the prefix moves.\n managedTask.reference = formatTaskReference(change.toCode, task.sequenceNumber)\n managedTask.updatedAt = now\n tem.persist(managedTask)\n tasksRenumbered += 1\n }\n }\n })\n\n return { changes, skipped, tasksRenumbered }\n}\n"],
|
|
5
|
+
"mappings": "AA2BA,SAAS,kBAAkB,qBAAqB;AAChD,SAAS,4BAA4B,6BAA6B;AAClE,SAAS,2BAA2B;AAwB7B,SAAS,yBACd,UACA,sBACmD;AAGnD,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,aAAwC,CAAC;AAC/C,MAAI,UAAU;AAEd,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,QAAQ,QAAQ,IAAI,KAAK;AACvC,QAAI,KAAK,SAAS,KAAK,KAAK,UAAU,4BAA4B;AAChE,YAAM,IAAI,KAAK,YAAY,CAAC;AAC5B,iBAAW;AACX;AAAA,IACF;AACA,eAAW,KAAK,OAAO;AAAA,EACzB;AAIA,QAAM,UAAU,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,GAAG,cAAc,MAAM,EAAE,CAAC;AAErF,QAAM,UAA+B,CAAC;AACtC,aAAW,WAAW,SAAS;AAC7B,UAAM,OAAO,sBAAsB,QAAQ,QAAQ,EAAE;AACrD,QAAI,YAAY;AAChB,aAAS,UAAU,GAAG,MAAM,IAAI,UAAU,YAAY,CAAC,GAAG,WAAW,GAAG;AACtE,kBAAY,GAAG,IAAI,GAAG,OAAO;AAAA,IAC/B;AACA,UAAM,IAAI,UAAU,YAAY,CAAC;AACjC,YAAQ,KAAK;AAAA,MACX,WAAW,QAAQ;AAAA,MACnB,aAAa,QAAQ,QAAQ;AAAA,MAC7B,UAAU,QAAQ,QAAQ;AAAA,MAC1B,QAAQ;AAAA,MACR,WAAW,qBAAqB,IAAI,QAAQ,EAAE,KAAK;AAAA,IACrD,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,SAAS,QAAQ;AAC5B;AAEA,eAAsB,oBACpB,IACA,OACA,UAAgC,CAAC,GACG;AACpC,QAAM,WAAW,MAAM,GAAG,KAAK,kBAAkB;AAAA,IAC/C,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AAED,QAAM,QAAQ,MAAM,GAAG,KAAK,eAAe;AAAA,IACzC,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AAED,QAAM,mBAAmB,oBAAI,IAA6B;AAC1D,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,iBAAiB,IAAI,KAAK,aAAa,KAAK,CAAC;AAC5D,WAAO,KAAK,IAAI;AAChB,qBAAiB,IAAI,KAAK,eAAe,MAAM;AAAA,EACjD;AACA,QAAM,SAAS,IAAI,IAAI,CAAC,GAAG,gBAAgB,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;AAEnF,QAAM,EAAE,SAAS,QAAQ,IAAI,yBAAyB,UAAU,MAAM;AACtE,MAAI,QAAQ,QAAQ;AAClB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,iBAAiB,QAAQ,OAAO,CAAC,OAAO,WAAW,QAAQ,OAAO,WAAW,CAAC;AAAA,IAChF;AAAA,EACF;AAEA,QAAM,cAAc,IAAI,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;AAC5E,QAAM,MAAM,oBAAI,KAAK;AACrB,MAAI,kBAAkB;AAKtB,QAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,YAAY,IAAI,OAAO,SAAS;AAChD,UAAI,CAAC,QAAS;AACd,YAAM,UAAU,IAAI,aAAa,kBAAkB,QAAQ,EAAE;AAC7D,cAAQ,OAAO,OAAO;AACtB,cAAQ,YAAY;AACpB,UAAI,QAAQ,OAAO;AAEnB,iBAAW,QAAQ,iBAAiB,IAAI,OAAO,SAAS,KAAK,CAAC,GAAG;AAC/D,cAAM,cAAc,IAAI,aAAa,eAAe,KAAK,EAAE;AAG3D,oBAAY,YAAY,oBAAoB,OAAO,QAAQ,KAAK,cAAc;AAC9E,oBAAY,YAAY;AACxB,YAAI,QAAQ,WAAW;AACvB,2BAAmB;AAAA,MACrB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,SAAS,SAAS,gBAAgB;AAC7C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { resolveFeatureAccess } from "./featureAccess.js";
|
|
2
|
+
const RATES_FEATURE = "staff.timesheets.rates.view";
|
|
3
|
+
async function resolveMoneyVisibility(container, userId, scope) {
|
|
4
|
+
return (await resolveFeatureAccess(container, userId, [RATES_FEATURE], scope)).allowed;
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
RATES_FEATURE,
|
|
8
|
+
resolveMoneyVisibility
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=moneyVisibility.js.map
|