@open-mercato/core 0.6.4-develop.4210.1.d412061cfe → 0.6.4-develop.4236.1.9fa6806b34
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 +37 -0
- package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
- package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
- package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project/index.js +35 -0
- package/dist/generated/entities/staff_time_project/index.js.map +7 -0
- package/dist/generated/entities/staff_time_project_member/index.js +29 -0
- package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +64 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/timesheetFixtures.js +50 -0
- package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
- package/dist/modules/attachments/api/library/[id]/route.js +20 -16
- package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +18 -14
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/auth/api/roles/acl/route.js +10 -4
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +16 -11
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/commands/users.js +87 -71
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +61 -12
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +79 -54
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +29 -16
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/currencies/commands/currencies.js +15 -8
- package/dist/modules/currencies/commands/currencies.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +27 -26
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
- package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +35 -21
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +163 -162
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +3 -4
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +19 -22
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/people.js +18 -15
- package/dist/modules/customers/commands/people.js.map +2 -2
- package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +30 -23
- package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
- package/dist/modules/customers/commands/pipelines.js +27 -20
- package/dist/modules/customers/commands/pipelines.js.map +2 -2
- package/dist/modules/customers/commands/tags.js +13 -5
- package/dist/modules/customers/commands/tags.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +4 -4
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/directory/commands/organizations.js +192 -158
- package/dist/modules/directory/commands/organizations.js.map +3 -3
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +77 -75
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/commands/shared.js +132 -132
- package/dist/modules/messages/commands/shared.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/resources/commands/resources.js +125 -117
- package/dist/modules/resources/commands/resources.js.map +2 -2
- package/dist/modules/resources/commands/tags.js +7 -3
- package/dist/modules/resources/commands/tags.js.map +2 -2
- package/dist/modules/sales/api/quotes/send/route.js +12 -11
- package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +629 -478
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +146 -146
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +68 -60
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/staff/acl.js +10 -1
- package/dist/modules/staff/acl.js.map +2 -2
- package/dist/modules/staff/analytics.js +33 -0
- package/dist/modules/staff/analytics.js.map +7 -0
- package/dist/modules/staff/api/guards.js +31 -0
- package/dist/modules/staff/api/guards.js.map +7 -0
- package/dist/modules/staff/api/interceptors.js +96 -0
- package/dist/modules/staff/api/interceptors.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
- package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
- package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
- package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
- package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
- package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
- package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
- package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
- package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
- package/dist/modules/staff/cli.js +38 -1
- package/dist/modules/staff/cli.js.map +2 -2
- package/dist/modules/staff/commands/index.js +2 -0
- package/dist/modules/staff/commands/index.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +30 -28
- package/dist/modules/staff/commands/leave-requests.js.map +3 -3
- package/dist/modules/staff/commands/team-members.js +21 -20
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/commands/timesheets-entries.js +409 -0
- package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
- package/dist/modules/staff/commands/timesheets-projects.js +618 -0
- package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
- package/dist/modules/staff/data/enrichers.js +104 -0
- package/dist/modules/staff/data/enrichers.js.map +7 -0
- package/dist/modules/staff/data/entities.js +226 -1
- package/dist/modules/staff/data/entities.js.map +2 -2
- package/dist/modules/staff/data/validators.js +113 -1
- package/dist/modules/staff/data/validators.js.map +2 -2
- package/dist/modules/staff/events.js +13 -1
- package/dist/modules/staff/events.js.map +2 -2
- package/dist/modules/staff/lib/crud.js +7 -1
- package/dist/modules/staff/lib/crud.js.map +2 -2
- package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
- package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
- package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
- package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
- package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
- package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
- package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
- package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
- package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
- package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
- package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
- package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
- package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
- package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
- package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
- package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
- package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
- package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
- package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
- package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
- package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
- package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
- package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
- package/dist/modules/staff/search.js +35 -0
- package/dist/modules/staff/search.js.map +2 -2
- package/dist/modules/staff/setup.js +15 -1
- package/dist/modules/staff/setup.js.map +2 -2
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
- package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
- package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
- package/dist/modules/staff/widgets/injection-table.js +12 -0
- package/dist/modules/staff/widgets/injection-table.js.map +7 -0
- package/dist/modules/sync_excel/api/import/route.js +19 -17
- package/dist/modules/sync_excel/api/import/route.js.map +2 -2
- package/dist/modules/translations/commands/translations.js +22 -19
- package/dist/modules/translations/commands/translations.js.map +2 -2
- package/generated/entities/staff_time_entry/index.ts +17 -0
- package/generated/entities/staff_time_entry_segment/index.ts +10 -0
- package/generated/entities/staff_time_project/index.ts +16 -0
- package/generated/entities/staff_time_project_member/index.ts +13 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +64 -0
- package/package.json +7 -7
- package/src/helpers/integration/timesheetFixtures.ts +61 -0
- package/src/modules/attachments/api/library/[id]/route.ts +24 -17
- package/src/modules/attachments/api/route.ts +20 -14
- package/src/modules/auth/api/roles/acl/route.ts +11 -5
- package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
- package/src/modules/auth/api/users/acl/route.ts +17 -12
- package/src/modules/auth/commands/users.ts +96 -80
- package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
- package/src/modules/catalog/commands/categories.ts +61 -12
- package/src/modules/catalog/commands/products.ts +93 -60
- package/src/modules/catalog/commands/variants.ts +29 -16
- package/src/modules/currencies/commands/currencies.ts +27 -14
- package/src/modules/customer_accounts/api/admin/users.ts +31 -26
- package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
- package/src/modules/customers/commands/addresses.ts +35 -23
- package/src/modules/customers/commands/companies.ts +166 -165
- package/src/modules/customers/commands/deals.ts +2 -4
- package/src/modules/customers/commands/interactions.ts +20 -26
- package/src/modules/customers/commands/people.ts +18 -15
- package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
- package/src/modules/customers/commands/pipeline-stages.ts +31 -27
- package/src/modules/customers/commands/pipelines.ts +29 -23
- package/src/modules/customers/commands/tags.ts +13 -5
- package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
- package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
- package/src/modules/data_sync/lib/sync-engine.ts +4 -5
- package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
- package/src/modules/directory/commands/organizations.ts +203 -166
- package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
- package/src/modules/messages/commands/messages.ts +82 -80
- package/src/modules/messages/commands/shared.ts +138 -133
- package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
- package/src/modules/resources/commands/resources.ts +127 -117
- package/src/modules/resources/commands/tags.ts +7 -3
- package/src/modules/sales/api/quotes/send/route.ts +17 -12
- package/src/modules/sales/commands/documents.ts +673 -481
- package/src/modules/sales/commands/payments.ts +158 -152
- package/src/modules/sales/commands/returns.ts +74 -63
- package/src/modules/staff/acl.ts +11 -0
- package/src/modules/staff/analytics.ts +30 -0
- package/src/modules/staff/api/guards.ts +59 -0
- package/src/modules/staff/api/interceptors.ts +122 -0
- package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
- package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
- package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
- package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
- package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
- package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
- package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
- package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
- package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
- package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
- package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
- package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
- package/src/modules/staff/cli.ts +40 -1
- package/src/modules/staff/commands/index.ts +2 -0
- package/src/modules/staff/commands/leave-requests.ts +37 -29
- package/src/modules/staff/commands/team-members.ts +25 -20
- package/src/modules/staff/commands/timesheets-entries.ts +504 -0
- package/src/modules/staff/commands/timesheets-projects.ts +699 -0
- package/src/modules/staff/data/enrichers.ts +134 -0
- package/src/modules/staff/data/entities.ts +198 -0
- package/src/modules/staff/data/validators.ts +129 -0
- package/src/modules/staff/events.ts +13 -0
- package/src/modules/staff/i18n/de.json +209 -1
- package/src/modules/staff/i18n/en.json +209 -1
- package/src/modules/staff/i18n/es.json +209 -1
- package/src/modules/staff/i18n/pl.json +209 -1
- package/src/modules/staff/lib/crud.ts +8 -0
- package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
- package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
- package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
- package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
- package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
- package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
- package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
- package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
- package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
- package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
- package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
- package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
- package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
- package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
- package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
- package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
- package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
- package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
- package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
- package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
- package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
- package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
- package/src/modules/staff/search.ts +35 -0
- package/src/modules/staff/setup.ts +15 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
- package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
- package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
- package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
- package/src/modules/staff/widgets/injection-table.ts +10 -0
- package/src/modules/sync_excel/api/import/route.ts +23 -18
- package/src/modules/translations/commands/translations.ts +49 -41
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ApiInterceptor, InterceptorContext } from '@open-mercato/shared/lib/crud/api-interceptor'
|
|
2
|
+
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
3
|
+
import { getStaffMemberByUserId } from '../lib/staffMemberResolver'
|
|
4
|
+
|
|
5
|
+
type RbacServiceLike = {
|
|
6
|
+
userHasAllFeatures: (
|
|
7
|
+
userId: string,
|
|
8
|
+
required: string[],
|
|
9
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
10
|
+
) => Promise<boolean>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether the caller has manage_all rights, honoring wildcard ACL grants
|
|
15
|
+
* (`staff.*`, `*`) and the super-admin flag.
|
|
16
|
+
*
|
|
17
|
+
* `context.userFeatures` is populated only on CRUD-factory routes (factory.ts
|
|
18
|
+
* calls `rbacService.getGrantedFeatures`). Custom routes that wire interceptors
|
|
19
|
+
* manually (like `dashboards/widgets/data`) pass `auth.features` directly, and
|
|
20
|
+
* the JWT does NOT include features — so we must fall back to loading the ACL
|
|
21
|
+
* from `rbacService.userHasAllFeatures`, which is cached and wildcard-aware.
|
|
22
|
+
*/
|
|
23
|
+
async function hasManageAllFeature(context: InterceptorContext): Promise<boolean> {
|
|
24
|
+
if (hasFeature(context.userFeatures, 'staff.timesheets.manage_all')) return true
|
|
25
|
+
try {
|
|
26
|
+
const rbac = context.container.resolve('rbacService') as RbacServiceLike | undefined
|
|
27
|
+
if (rbac?.userHasAllFeatures) {
|
|
28
|
+
return await rbac.userHasAllFeatures(
|
|
29
|
+
context.userId,
|
|
30
|
+
['staff.timesheets.manage_all'],
|
|
31
|
+
{ tenantId: context.tenantId ?? null, organizationId: context.organizationId ?? null },
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// rbacService not available — fall through to deny
|
|
36
|
+
}
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const interceptors: ApiInterceptor[] = [
|
|
41
|
+
{
|
|
42
|
+
id: 'staff.timesheets.self-scope-widget-data',
|
|
43
|
+
targetRoute: 'dashboards/widgets/data',
|
|
44
|
+
methods: ['POST'],
|
|
45
|
+
priority: 70,
|
|
46
|
+
async before(request, context) {
|
|
47
|
+
const entityType = request.body?.entityType
|
|
48
|
+
if (entityType !== 'staff:staff_time_entries') {
|
|
49
|
+
return { ok: true }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (await hasManageAllFeature(context)) {
|
|
53
|
+
return { ok: true }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const staffMember = await getStaffMemberByUserId(
|
|
57
|
+
context.em,
|
|
58
|
+
context.userId,
|
|
59
|
+
context.tenantId ?? null,
|
|
60
|
+
context.organizationId ?? null,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if (!staffMember) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
statusCode: 403,
|
|
67
|
+
message: 'User is not a staff member.',
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const existingFilters = Array.isArray(request.body?.filters) ? request.body.filters : []
|
|
72
|
+
const otherFilters = existingFilters.filter(
|
|
73
|
+
(f: Record<string, unknown>) => f.field !== 'staffMemberId',
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
body: {
|
|
79
|
+
...request.body,
|
|
80
|
+
filters: [
|
|
81
|
+
...otherFilters,
|
|
82
|
+
{ field: 'staffMemberId', operator: 'eq', value: staffMember.id },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'staff.timesheets.self-scope-time-entries',
|
|
90
|
+
targetRoute: 'staff/timesheets/time-entries',
|
|
91
|
+
methods: ['GET'],
|
|
92
|
+
priority: 70,
|
|
93
|
+
async before(request, context) {
|
|
94
|
+
if (await hasManageAllFeature(context)) {
|
|
95
|
+
return { ok: true }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const staffMember = await getStaffMemberByUserId(
|
|
99
|
+
context.em,
|
|
100
|
+
context.userId,
|
|
101
|
+
context.tenantId ?? null,
|
|
102
|
+
context.organizationId ?? null,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if (!staffMember) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
statusCode: 403,
|
|
109
|
+
message: 'User is not a staff member.',
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
query: {
|
|
116
|
+
...(request.query ?? {}),
|
|
117
|
+
staffMemberId: staffMember.id,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
]
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
8
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
10
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
11
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
+
import { StaffTimeProjectMember, StaffTeamMember } from '../../../../data/entities'
|
|
13
|
+
import { staffMyProjectVisibilityUpdateSchema } from '../../../../data/validators'
|
|
14
|
+
import {
|
|
15
|
+
resolveUserFeatures,
|
|
16
|
+
runStaffMutationGuardAfterSuccess,
|
|
17
|
+
runStaffMutationGuards,
|
|
18
|
+
} from '../../../guards'
|
|
19
|
+
|
|
20
|
+
export const metadata = {
|
|
21
|
+
PATCH: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractProjectIdFromUrl(req: Request): string | null {
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(req.url)
|
|
27
|
+
const match = url.pathname.match(/\/my-projects\/([^/]+)(?:\/|$)/)
|
|
28
|
+
return match?.[1] ?? null
|
|
29
|
+
} catch {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* PATCH /api/staff/timesheets/my-projects/{projectId}
|
|
36
|
+
*
|
|
37
|
+
* Self-service endpoint for the authenticated user to toggle visibility of a
|
|
38
|
+
* time project on their own My Timesheets grid. Does not require the admin-only
|
|
39
|
+
* `staff.timesheets.projects.manage` feature — only `staff.timesheets.manage_own`.
|
|
40
|
+
*
|
|
41
|
+
* Added 2026-04-13 to close a gap in the original UX enhancements spec:
|
|
42
|
+
* "+ Add row" and the new X remove button need to persist per-user grid membership.
|
|
43
|
+
*/
|
|
44
|
+
export async function PATCH(req: Request) {
|
|
45
|
+
try {
|
|
46
|
+
const container = await createRequestContainer()
|
|
47
|
+
const auth = await getAuthFromRequest(req)
|
|
48
|
+
const { translate } = await resolveTranslations()
|
|
49
|
+
if (!auth) {
|
|
50
|
+
throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const projectId = extractProjectIdFromUrl(req)
|
|
54
|
+
if (!projectId || !z.string().uuid().safeParse(projectId).success) {
|
|
55
|
+
throw new CrudHttpError(400, {
|
|
56
|
+
error: translate('staff.timesheets.errors.invalidProjectId', 'Invalid project id.'),
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
61
|
+
const tenantId = scope?.tenantId ?? auth.tenantId ?? null
|
|
62
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
63
|
+
if (!tenantId || !organizationId) {
|
|
64
|
+
throw new CrudHttpError(400, {
|
|
65
|
+
error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const rawBody = await readJsonSafe(req, {})
|
|
70
|
+
const parsed = staffMyProjectVisibilityUpdateSchema.safeParse(rawBody)
|
|
71
|
+
if (!parsed.success) {
|
|
72
|
+
throw new CrudHttpError(400, {
|
|
73
|
+
error: translate('staff.timesheets.errors.invalidBody', 'Invalid request body.'),
|
|
74
|
+
details: parsed.error.flatten(),
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
79
|
+
const scopeCtx = { tenantId, organizationId }
|
|
80
|
+
|
|
81
|
+
const staffMember = await findOneWithDecryption(
|
|
82
|
+
em,
|
|
83
|
+
StaffTeamMember,
|
|
84
|
+
{ userId: auth.sub, tenantId, organizationId, deletedAt: null },
|
|
85
|
+
{},
|
|
86
|
+
scopeCtx,
|
|
87
|
+
)
|
|
88
|
+
if (!staffMember) {
|
|
89
|
+
throw new CrudHttpError(403, {
|
|
90
|
+
error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.'),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const membership = await findOneWithDecryption(
|
|
95
|
+
em,
|
|
96
|
+
StaffTimeProjectMember,
|
|
97
|
+
{
|
|
98
|
+
timeProjectId: projectId,
|
|
99
|
+
staffMemberId: staffMember.id,
|
|
100
|
+
tenantId,
|
|
101
|
+
organizationId,
|
|
102
|
+
deletedAt: null,
|
|
103
|
+
status: 'active',
|
|
104
|
+
},
|
|
105
|
+
{},
|
|
106
|
+
scopeCtx,
|
|
107
|
+
)
|
|
108
|
+
if (!membership) {
|
|
109
|
+
throw new CrudHttpError(404, {
|
|
110
|
+
error: translate('staff.timesheets.errors.notAssigned', 'You are not assigned to this project.'),
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const guardResult = await runStaffMutationGuards(
|
|
115
|
+
container,
|
|
116
|
+
{
|
|
117
|
+
tenantId,
|
|
118
|
+
organizationId,
|
|
119
|
+
userId: auth.sub ?? '',
|
|
120
|
+
resourceKind: 'staff.timesheets.time_project_member',
|
|
121
|
+
resourceId: membership.id,
|
|
122
|
+
operation: 'update',
|
|
123
|
+
requestMethod: req.method,
|
|
124
|
+
requestHeaders: req.headers,
|
|
125
|
+
mutationPayload: parsed.data as unknown as Record<string, unknown>,
|
|
126
|
+
},
|
|
127
|
+
resolveUserFeatures(auth),
|
|
128
|
+
)
|
|
129
|
+
if (!guardResult.ok) {
|
|
130
|
+
return NextResponse.json(
|
|
131
|
+
guardResult.errorBody ?? { error: 'Operation blocked by guard' },
|
|
132
|
+
{ status: guardResult.errorStatus ?? 422 },
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
membership.showInGrid = parsed.data.showInGrid
|
|
137
|
+
await em.flush()
|
|
138
|
+
|
|
139
|
+
if (guardResult.afterSuccessCallbacks.length) {
|
|
140
|
+
await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
141
|
+
tenantId,
|
|
142
|
+
organizationId,
|
|
143
|
+
userId: auth.sub ?? '',
|
|
144
|
+
resourceKind: 'staff.timesheets.time_project_member',
|
|
145
|
+
resourceId: membership.id,
|
|
146
|
+
operation: 'update',
|
|
147
|
+
requestMethod: req.method,
|
|
148
|
+
requestHeaders: req.headers,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return NextResponse.json({ ok: true, showInGrid: membership.showInGrid }, { status: 200 })
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err instanceof CrudHttpError) {
|
|
155
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
156
|
+
}
|
|
157
|
+
console.error('staff.timesheets.my-projects.patch failed', err)
|
|
158
|
+
const { translate } = await resolveTranslations()
|
|
159
|
+
return NextResponse.json(
|
|
160
|
+
{ error: translate('staff.timesheets.errors.updateMyProject', 'Failed to update project visibility.') },
|
|
161
|
+
{ status: 400 },
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const openApi: OpenApiRouteDoc = {
|
|
167
|
+
tag: 'Staff',
|
|
168
|
+
summary: 'My project grid visibility',
|
|
169
|
+
methods: {
|
|
170
|
+
PATCH: {
|
|
171
|
+
summary: 'Toggle grid visibility for one of the caller\'s assigned time projects',
|
|
172
|
+
description:
|
|
173
|
+
'Self-service endpoint. Only the authenticated user can toggle `show_in_grid` on their own active membership for the given project. Does not require the admin-only `staff.timesheets.projects.manage` feature.',
|
|
174
|
+
requestBody: {
|
|
175
|
+
contentType: 'application/json',
|
|
176
|
+
schema: staffMyProjectVisibilityUpdateSchema,
|
|
177
|
+
},
|
|
178
|
+
responses: [
|
|
179
|
+
{
|
|
180
|
+
status: 200,
|
|
181
|
+
description: 'Visibility updated',
|
|
182
|
+
schema: z.object({ ok: z.boolean(), showInGrid: z.boolean() }),
|
|
183
|
+
},
|
|
184
|
+
{ status: 400, description: 'Invalid input', schema: z.object({ error: z.string() }) },
|
|
185
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
186
|
+
{ status: 403, description: 'No staff member linked', schema: z.object({ error: z.string() }) },
|
|
187
|
+
{ status: 404, description: 'Project membership not found', schema: z.object({ error: z.string() }) },
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
8
|
+
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
10
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
|
+
import { StaffTimeProjectMember, StaffTeamMember } from '../../../data/entities'
|
|
12
|
+
|
|
13
|
+
export const metadata = {
|
|
14
|
+
GET: { requireAuth: true, requireFeatures: ['staff.timesheets.view'] },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* GET /api/staff/timesheets/my-projects
|
|
19
|
+
*
|
|
20
|
+
* Spec N+1 Mitigation — Query 1:
|
|
21
|
+
* Returns staff_time_project_members for the authenticated staff member.
|
|
22
|
+
* Used by "My Timesheets" grid to resolve assigned project IDs.
|
|
23
|
+
*/
|
|
24
|
+
export async function GET(req: Request) {
|
|
25
|
+
try {
|
|
26
|
+
const container = await createRequestContainer()
|
|
27
|
+
const auth = await getAuthFromRequest(req)
|
|
28
|
+
const { translate } = await resolveTranslations()
|
|
29
|
+
if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })
|
|
30
|
+
|
|
31
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
32
|
+
const tenantId = scope?.tenantId ?? auth.tenantId ?? null
|
|
33
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
34
|
+
if (!tenantId || !organizationId) {
|
|
35
|
+
throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const em = (container.resolve('em') as EntityManager).fork()
|
|
39
|
+
const scopeCtx = { tenantId, organizationId }
|
|
40
|
+
|
|
41
|
+
const staffMember = await findOneWithDecryption(
|
|
42
|
+
em,
|
|
43
|
+
StaffTeamMember,
|
|
44
|
+
{ userId: auth.sub, tenantId, organizationId, deletedAt: null },
|
|
45
|
+
{},
|
|
46
|
+
scopeCtx,
|
|
47
|
+
)
|
|
48
|
+
if (!staffMember) {
|
|
49
|
+
throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const assignments = await findWithDecryption(
|
|
53
|
+
em,
|
|
54
|
+
StaffTimeProjectMember,
|
|
55
|
+
{ staffMemberId: staffMember.id, tenantId, organizationId, deletedAt: null, status: 'active' },
|
|
56
|
+
{},
|
|
57
|
+
scopeCtx,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const items = assignments.map((assignment) => ({
|
|
61
|
+
id: assignment.id,
|
|
62
|
+
time_project_id: assignment.timeProjectId,
|
|
63
|
+
staff_member_id: assignment.staffMemberId,
|
|
64
|
+
role: assignment.role ?? null,
|
|
65
|
+
status: assignment.status ?? null,
|
|
66
|
+
assigned_start_date: assignment.assignedStartDate ?? null,
|
|
67
|
+
assigned_end_date: assignment.assignedEndDate ?? null,
|
|
68
|
+
show_in_grid: assignment.showInGrid ?? false,
|
|
69
|
+
}))
|
|
70
|
+
|
|
71
|
+
return NextResponse.json({ items, total: items.length }, { status: 200 })
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err instanceof CrudHttpError) {
|
|
74
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
75
|
+
}
|
|
76
|
+
console.error('staff.timesheets.my-projects failed', err)
|
|
77
|
+
const { translate } = await resolveTranslations()
|
|
78
|
+
return NextResponse.json(
|
|
79
|
+
{ error: translate('staff.timesheets.errors.myProjects', 'Failed to load your projects.') },
|
|
80
|
+
{ status: 400 },
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const openApi: OpenApiRouteDoc = {
|
|
86
|
+
tag: 'Staff',
|
|
87
|
+
summary: 'My assigned time projects',
|
|
88
|
+
methods: {
|
|
89
|
+
GET: {
|
|
90
|
+
summary: 'List assigned time project memberships for the current user',
|
|
91
|
+
description: 'Returns staff_time_project_members where the authenticated user is an active member. Used by the My Timesheets grid to resolve assigned project IDs (spec N+1 mitigation query 1).',
|
|
92
|
+
responses: [
|
|
93
|
+
{
|
|
94
|
+
status: 200,
|
|
95
|
+
description: 'Assigned project memberships',
|
|
96
|
+
schema: z.object({
|
|
97
|
+
items: z.array(z.object({
|
|
98
|
+
id: z.string().uuid(),
|
|
99
|
+
time_project_id: z.string().uuid(),
|
|
100
|
+
staff_member_id: z.string().uuid(),
|
|
101
|
+
role: z.string().nullable(),
|
|
102
|
+
status: z.string().nullable(),
|
|
103
|
+
assigned_start_date: z.string().nullable(),
|
|
104
|
+
assigned_end_date: z.string().nullable(),
|
|
105
|
+
show_in_grid: z.boolean(),
|
|
106
|
+
})),
|
|
107
|
+
total: z.number(),
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
111
|
+
{ status: 403, description: 'No staff member linked', schema: z.object({ error: z.string() }) },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
7
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
8
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
10
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
12
|
+
import { StaffTeamMember } from '../../../../data/entities'
|
|
13
|
+
import {
|
|
14
|
+
computeCollabProjectsKpis,
|
|
15
|
+
computePmProjectsKpis,
|
|
16
|
+
} from '../../../../lib/timesheets-projects/computeProjectsKpis'
|
|
17
|
+
|
|
18
|
+
const VIEW_FEATURE = 'staff.timesheets.projects.view'
|
|
19
|
+
const MANAGE_FEATURE = 'staff.timesheets.projects.manage'
|
|
20
|
+
|
|
21
|
+
export const metadata = {
|
|
22
|
+
GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const deltaSchema = z.object({
|
|
26
|
+
current: z.number(),
|
|
27
|
+
previous: z.number(),
|
|
28
|
+
deltaPct: z.number().nullable(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const pmResponseSchema = z.object({
|
|
32
|
+
role: z.literal('pm'),
|
|
33
|
+
totals: z.object({
|
|
34
|
+
total: z.number().int(),
|
|
35
|
+
active: z.number().int(),
|
|
36
|
+
onHold: z.number().int(),
|
|
37
|
+
completed: z.number().int(),
|
|
38
|
+
}),
|
|
39
|
+
hoursWeek: deltaSchema,
|
|
40
|
+
hoursMonth: deltaSchema,
|
|
41
|
+
teamActive: z.object({ count: z.number().int() }),
|
|
42
|
+
assignedToMe: z.object({ total: z.number().int(), active: z.number().int() }),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const collabResponseSchema = z.object({
|
|
46
|
+
role: z.literal('collab'),
|
|
47
|
+
myProjects: z.object({
|
|
48
|
+
total: z.number().int(),
|
|
49
|
+
active: z.number().int(),
|
|
50
|
+
}),
|
|
51
|
+
myHoursWeek: deltaSchema,
|
|
52
|
+
myHoursMonth: deltaSchema,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export async function GET(req: Request) {
|
|
56
|
+
try {
|
|
57
|
+
const container = await createRequestContainer()
|
|
58
|
+
const auth = await getAuthFromRequest(req)
|
|
59
|
+
const { translate } = await resolveTranslations()
|
|
60
|
+
|
|
61
|
+
if (!auth) {
|
|
62
|
+
throw new CrudHttpError(401, {
|
|
63
|
+
error: translate('staff.errors.unauthorized', 'Unauthorized'),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
68
|
+
const tenantId = scope?.tenantId ?? auth.tenantId ?? null
|
|
69
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
70
|
+
if (!tenantId || !organizationId) {
|
|
71
|
+
throw new CrudHttpError(400, {
|
|
72
|
+
error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const em = container.resolve('em') as EntityManager
|
|
77
|
+
const rbac = container.resolve('rbacService') as RbacService
|
|
78
|
+
const isPm = await rbac.userHasAllFeatures(auth.sub, [MANAGE_FEATURE], {
|
|
79
|
+
tenantId,
|
|
80
|
+
organizationId,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const staffMember = await findOneWithDecryption(
|
|
84
|
+
em.fork(),
|
|
85
|
+
StaffTeamMember,
|
|
86
|
+
{ userId: auth.sub, tenantId, organizationId, deletedAt: null },
|
|
87
|
+
{},
|
|
88
|
+
{ tenantId, organizationId },
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if (isPm) {
|
|
92
|
+
const result = await computePmProjectsKpis({
|
|
93
|
+
em,
|
|
94
|
+
tenantId,
|
|
95
|
+
organizationId,
|
|
96
|
+
callerStaffMemberId: staffMember?.id ?? null,
|
|
97
|
+
})
|
|
98
|
+
return NextResponse.json(result, { status: 200 })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!staffMember) {
|
|
102
|
+
throw new CrudHttpError(403, {
|
|
103
|
+
error: translate(
|
|
104
|
+
'staff.timesheets.errors.noStaffMember',
|
|
105
|
+
'No staff member linked to your account.',
|
|
106
|
+
),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await computeCollabProjectsKpis({
|
|
111
|
+
em,
|
|
112
|
+
tenantId,
|
|
113
|
+
organizationId,
|
|
114
|
+
staffMemberId: staffMember.id,
|
|
115
|
+
})
|
|
116
|
+
return NextResponse.json(result, { status: 200 })
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (err instanceof CrudHttpError) {
|
|
119
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
120
|
+
}
|
|
121
|
+
console.error('staff.timesheets.projects.kpis failed', err)
|
|
122
|
+
const { translate } = await resolveTranslations()
|
|
123
|
+
return NextResponse.json(
|
|
124
|
+
{
|
|
125
|
+
error: translate(
|
|
126
|
+
'staff.timesheets.errors.projectsKpis',
|
|
127
|
+
'Failed to load project KPIs.',
|
|
128
|
+
),
|
|
129
|
+
},
|
|
130
|
+
{ status: 500 },
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const openApi: OpenApiRouteDoc = {
|
|
136
|
+
tag: 'Staff',
|
|
137
|
+
summary: 'Timesheet projects KPIs',
|
|
138
|
+
methods: {
|
|
139
|
+
GET: {
|
|
140
|
+
summary: 'Aggregate KPIs for the timesheets Projects page',
|
|
141
|
+
description:
|
|
142
|
+
'Returns a role-aware KPI payload. Users with `staff.timesheets.projects.manage` get the PM shape (portfolio totals, monthly team hours, active team member count). Other viewers get the Collaborator shape scoped to their own memberships and hours.',
|
|
143
|
+
responses: [
|
|
144
|
+
{
|
|
145
|
+
status: 200,
|
|
146
|
+
description: 'PM or Collaborator KPIs',
|
|
147
|
+
schema: z.union([pmResponseSchema, collabResponseSchema]),
|
|
148
|
+
},
|
|
149
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
150
|
+
{
|
|
151
|
+
status: 403,
|
|
152
|
+
description: 'Missing viewing feature or no staff member linked',
|
|
153
|
+
schema: z.object({ error: z.string() }),
|
|
154
|
+
},
|
|
155
|
+
{ status: 500, description: 'Aggregation failure', schema: z.object({ error: z.string() }) },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|