@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/commands/shared.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData, type RecipientStatus } from '../data/entities'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\n\nexport type MessageCommandExecuteResult = {\n id: string\n externalEmail: string | null\n recipientUserIds: string[]\n}\n\nexport type MessageScopeInput = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport type MessageRecipientSnapshot = {\n id: string\n messageId: string\n recipientUserId: string\n recipientType: 'to' | 'cc' | 'bcc'\n status: RecipientStatus\n readAt: string | null\n archivedAt: string | null\n deletedAt: string | null\n}\n\nexport type MessageObjectSnapshot = {\n id: string\n messageId: string\n entityModule: string\n entityType: string\n entityId: string\n actionRequired: boolean\n actionType: string | null\n actionLabel: string | null\n entitySnapshot: Record<string, unknown> | null\n}\n\nexport type MessageSnapshot = {\n id: string\n type: string\n visibility: 'public' | 'internal' | null\n sourceEntityType: string | null\n sourceEntityId: string | null\n externalEmail: string | null\n externalName: string | null\n threadId: string | null\n parentMessageId: string | null\n senderUserId: string\n subject: string\n body: string\n bodyFormat: 'text' | 'markdown'\n priority: 'low' | 'normal' | 'high' | 'urgent'\n status: 'draft' | 'sent'\n isDraft: boolean\n sentAt: string | null\n actionData: MessageActionData | null\n actionResult: Record<string, unknown> | null\n actionTaken: string | null\n actionTakenByUserId: string | null\n actionTakenAt: string | null\n sendViaEmail: boolean\n tenantId: string\n organizationId: string | null\n deletedAt: string | null\n}\n\nexport type MessageAggregateSnapshot = {\n message: MessageSnapshot\n recipients: MessageRecipientSnapshot[]\n objects: MessageObjectSnapshot[]\n attachmentIds: string[]\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction hasOrganizationAccess(scopeOrganizationId: string | null, messageOrganizationId: string | null | undefined): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport function assertOrganizationAccess(scope: MessageScopeInput, message: Message): void {\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n throw new Error('Access denied')\n }\n}\n\nexport async function getAttachmentIdsForMessage(\n em: EntityManager,\n messageId: string,\n scope: { tenantId: string; organizationId: string | null },\n): Promise<string[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return attachments.map((item) => item.id)\n}\n\nexport async function loadMessageAggregateSnapshot(\n em: EntityManager,\n messageId: string,\n scope?: { tenantId: string; organizationId: string | null },\n): Promise<MessageAggregateSnapshot | null> {\n const where: Record<string, unknown> = { id: messageId }\n if (scope) {\n where.tenantId = scope.tenantId\n where.organizationId = scope.organizationId\n }\n const message = await em.findOne(Message, where)\n if (!message) return null\n const recipients = await em.find(MessageRecipient, { messageId })\n const objects = await em.find(MessageObject, { messageId })\n const attachmentIds = await getAttachmentIdsForMessage(em, messageId, {\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n })\n\n return {\n message: {\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n threadId: message.threadId ?? null,\n parentMessageId: message.parentMessageId ?? null,\n senderUserId: message.senderUserId,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n status: message.status,\n isDraft: message.isDraft,\n sentAt: toIso(message.sentAt),\n actionData: (message.actionData as MessageActionData | null) ?? null,\n actionResult: message.actionResult ?? null,\n actionTaken: message.actionTaken ?? null,\n actionTakenByUserId: message.actionTakenByUserId ?? null,\n actionTakenAt: toIso(message.actionTakenAt),\n sendViaEmail: message.sendViaEmail,\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n deletedAt: toIso(message.deletedAt),\n },\n recipients: recipients.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n recipientUserId: item.recipientUserId,\n recipientType: item.recipientType,\n status: item.status,\n readAt: toIso(item.readAt),\n archivedAt: toIso(item.archivedAt),\n deletedAt: toIso(item.deletedAt),\n })),\n objects: objects.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType ?? null,\n actionLabel: item.actionLabel ?? null,\n entitySnapshot: item.entitySnapshot ?? null,\n })),\n attachmentIds,\n }\n}\n\nexport async function restoreMessageAggregateSnapshot(\n em: EntityManager,\n snapshot: MessageAggregateSnapshot,\n): Promise<void> {\n const existingMessage = await em.findOne(Message, { id: snapshot.message.id })\n if (!existingMessage) {\n const created = em.create(Message, {\n id: snapshot.message.id,\n type: snapshot.message.type,\n visibility: snapshot.message.visibility,\n sourceEntityType: snapshot.message.sourceEntityType,\n sourceEntityId: snapshot.message.sourceEntityId,\n externalEmail: snapshot.message.externalEmail,\n externalName: snapshot.message.externalName,\n threadId: snapshot.message.threadId,\n parentMessageId: snapshot.message.parentMessageId,\n senderUserId: snapshot.message.senderUserId,\n subject: snapshot.message.subject,\n body: snapshot.message.body,\n bodyFormat: snapshot.message.bodyFormat,\n priority: snapshot.message.priority,\n status: snapshot.message.status,\n isDraft: snapshot.message.isDraft,\n sentAt: toDate(snapshot.message.sentAt),\n actionData: snapshot.message.actionData as MessageActionData,\n actionResult: snapshot.message.actionResult,\n actionTaken: snapshot.message.actionTaken,\n actionTakenByUserId: snapshot.message.actionTakenByUserId,\n actionTakenAt: toDate(snapshot.message.actionTakenAt),\n sendViaEmail: snapshot.message.sendViaEmail,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n deletedAt: toDate(snapshot.message.deletedAt),\n })\n em.persist(created)\n } else {\n existingMessage.type = snapshot.message.type\n existingMessage.visibility = snapshot.message.visibility\n existingMessage.sourceEntityType = snapshot.message.sourceEntityType\n existingMessage.sourceEntityId = snapshot.message.sourceEntityId\n existingMessage.externalEmail = snapshot.message.externalEmail\n existingMessage.externalName = snapshot.message.externalName\n existingMessage.threadId = snapshot.message.threadId\n existingMessage.parentMessageId = snapshot.message.parentMessageId\n existingMessage.senderUserId = snapshot.message.senderUserId\n existingMessage.subject = snapshot.message.subject\n existingMessage.body = snapshot.message.body\n existingMessage.bodyFormat = snapshot.message.bodyFormat\n existingMessage.priority = snapshot.message.priority\n existingMessage.status = snapshot.message.status\n existingMessage.isDraft = snapshot.message.isDraft\n existingMessage.sentAt = toDate(snapshot.message.sentAt)\n existingMessage.actionData = snapshot.message.actionData as MessageActionData\n existingMessage.actionResult = snapshot.message.actionResult\n existingMessage.actionTaken = snapshot.message.actionTaken\n existingMessage.actionTakenByUserId = snapshot.message.actionTakenByUserId\n existingMessage.actionTakenAt = toDate(snapshot.message.actionTakenAt)\n existingMessage.sendViaEmail = snapshot.message.sendViaEmail\n existingMessage.tenantId = snapshot.message.tenantId\n existingMessage.organizationId = snapshot.message.organizationId\n existingMessage.deletedAt = toDate(snapshot.message.deletedAt)\n }\n\n const existingRecipients = await em.find(MessageRecipient, { messageId: snapshot.message.id })\n const recipientById = new Map(existingRecipients.map((item) => [item.id, item]))\n const snapshotRecipientIds = new Set(snapshot.recipients.map((item) => item.id))\n for (const current of existingRecipients) {\n if (!snapshotRecipientIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const recipient of snapshot.recipients) {\n const existing = recipientById.get(recipient.id)\n if (!existing) {\n em.persist(em.create(MessageRecipient, {\n id: recipient.id,\n messageId: recipient.messageId,\n recipientUserId: recipient.recipientUserId,\n recipientType: recipient.recipientType,\n status: recipient.status,\n readAt: toDate(recipient.readAt),\n archivedAt: toDate(recipient.archivedAt),\n deletedAt: toDate(recipient.deletedAt),\n }))\n continue\n }\n existing.messageId = recipient.messageId\n existing.recipientUserId = recipient.recipientUserId\n existing.recipientType = recipient.recipientType\n existing.status = recipient.status\n existing.readAt = toDate(recipient.readAt)\n existing.archivedAt = toDate(recipient.archivedAt)\n existing.deletedAt = toDate(recipient.deletedAt)\n }\n\n const existingObjects = await em.find(MessageObject, { messageId: snapshot.message.id })\n const objectById = new Map(existingObjects.map((item) => [item.id, item]))\n const snapshotObjectIds = new Set(snapshot.objects.map((item) => item.id))\n for (const current of existingObjects) {\n if (!snapshotObjectIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const object of snapshot.objects) {\n const existing = objectById.get(object.id)\n if (!existing) {\n em.persist(em.create(MessageObject, {\n id: object.id,\n messageId: object.messageId,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n entitySnapshot: object.entitySnapshot,\n }))\n continue\n }\n existing.messageId = object.messageId\n existing.entityModule = object.entityModule\n existing.entityType = object.entityType\n existing.entityId = object.entityId\n existing.actionRequired = object.actionRequired\n existing.actionType = object.actionType\n existing.actionLabel = object.actionLabel\n existing.entitySnapshot = object.entitySnapshot\n }\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: snapshot.message.id,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n id: { $nin: snapshot.attachmentIds.length > 0 ? snapshot.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },\n })\n if (snapshot.attachmentIds.length > 0) {\n const attachments = await em.find(Attachment, {\n id: { $in: snapshot.attachmentIds },\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n })\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = snapshot.message.id\n }\n }\n\n await em.flush()\n}\n\nexport function buildCommandLogBase(\n actionLabel: string,\n resourceId: string,\n snapshot: { tenantId: string; organizationId: string | null },\n) {\n return {\n actionLabel,\n resourceKind: 'messages.message',\n resourceId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { Message, MessageObject, MessageRecipient, type MessageActionData, type RecipientStatus } from '../data/entities'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'\n\nexport type MessageCommandExecuteResult = {\n id: string\n externalEmail: string | null\n recipientUserIds: string[]\n}\n\nexport type MessageScopeInput = {\n tenantId: string\n organizationId: string | null\n userId: string\n}\n\nexport type MessageRecipientSnapshot = {\n id: string\n messageId: string\n recipientUserId: string\n recipientType: 'to' | 'cc' | 'bcc'\n status: RecipientStatus\n readAt: string | null\n archivedAt: string | null\n deletedAt: string | null\n}\n\nexport type MessageObjectSnapshot = {\n id: string\n messageId: string\n entityModule: string\n entityType: string\n entityId: string\n actionRequired: boolean\n actionType: string | null\n actionLabel: string | null\n entitySnapshot: Record<string, unknown> | null\n}\n\nexport type MessageSnapshot = {\n id: string\n type: string\n visibility: 'public' | 'internal' | null\n sourceEntityType: string | null\n sourceEntityId: string | null\n externalEmail: string | null\n externalName: string | null\n threadId: string | null\n parentMessageId: string | null\n senderUserId: string\n subject: string\n body: string\n bodyFormat: 'text' | 'markdown'\n priority: 'low' | 'normal' | 'high' | 'urgent'\n status: 'draft' | 'sent'\n isDraft: boolean\n sentAt: string | null\n actionData: MessageActionData | null\n actionResult: Record<string, unknown> | null\n actionTaken: string | null\n actionTakenByUserId: string | null\n actionTakenAt: string | null\n sendViaEmail: boolean\n tenantId: string\n organizationId: string | null\n deletedAt: string | null\n}\n\nexport type MessageAggregateSnapshot = {\n message: MessageSnapshot\n recipients: MessageRecipientSnapshot[]\n objects: MessageObjectSnapshot[]\n attachmentIds: string[]\n}\n\nfunction toIso(value: Date | null | undefined): string | null {\n return value ? value.toISOString() : null\n}\n\nfunction toDate(value: string | null | undefined): Date | null {\n if (!value) return null\n return new Date(value)\n}\n\nfunction hasOrganizationAccess(scopeOrganizationId: string | null, messageOrganizationId: string | null | undefined): boolean {\n if (scopeOrganizationId) {\n return messageOrganizationId === scopeOrganizationId\n }\n return messageOrganizationId == null\n}\n\nexport function assertOrganizationAccess(scope: MessageScopeInput, message: Message): void {\n if (!hasOrganizationAccess(scope.organizationId, message.organizationId)) {\n throw new Error('Access denied')\n }\n}\n\nexport async function getAttachmentIdsForMessage(\n em: EntityManager,\n messageId: string,\n scope: { tenantId: string; organizationId: string | null },\n): Promise<string[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n return attachments.map((item) => item.id)\n}\n\nexport async function loadMessageAggregateSnapshot(\n em: EntityManager,\n messageId: string,\n scope?: { tenantId: string; organizationId: string | null },\n): Promise<MessageAggregateSnapshot | null> {\n const where: Record<string, unknown> = { id: messageId }\n if (scope) {\n where.tenantId = scope.tenantId\n where.organizationId = scope.organizationId\n }\n const message = await em.findOne(Message, where)\n if (!message) return null\n const recipients = await em.find(MessageRecipient, { messageId })\n const objects = await em.find(MessageObject, { messageId })\n const attachmentIds = await getAttachmentIdsForMessage(em, messageId, {\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n })\n\n return {\n message: {\n id: message.id,\n type: message.type,\n visibility: message.visibility ?? null,\n sourceEntityType: message.sourceEntityType ?? null,\n sourceEntityId: message.sourceEntityId ?? null,\n externalEmail: message.externalEmail ?? null,\n externalName: message.externalName ?? null,\n threadId: message.threadId ?? null,\n parentMessageId: message.parentMessageId ?? null,\n senderUserId: message.senderUserId,\n subject: message.subject,\n body: message.body,\n bodyFormat: message.bodyFormat,\n priority: message.priority,\n status: message.status,\n isDraft: message.isDraft,\n sentAt: toIso(message.sentAt),\n actionData: (message.actionData as MessageActionData | null) ?? null,\n actionResult: message.actionResult ?? null,\n actionTaken: message.actionTaken ?? null,\n actionTakenByUserId: message.actionTakenByUserId ?? null,\n actionTakenAt: toIso(message.actionTakenAt),\n sendViaEmail: message.sendViaEmail,\n tenantId: message.tenantId,\n organizationId: message.organizationId ?? null,\n deletedAt: toIso(message.deletedAt),\n },\n recipients: recipients.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n recipientUserId: item.recipientUserId,\n recipientType: item.recipientType,\n status: item.status,\n readAt: toIso(item.readAt),\n archivedAt: toIso(item.archivedAt),\n deletedAt: toIso(item.deletedAt),\n })),\n objects: objects.map((item) => ({\n id: item.id,\n messageId: item.messageId,\n entityModule: item.entityModule,\n entityType: item.entityType,\n entityId: item.entityId,\n actionRequired: item.actionRequired,\n actionType: item.actionType ?? null,\n actionLabel: item.actionLabel ?? null,\n entitySnapshot: item.entitySnapshot ?? null,\n })),\n attachmentIds,\n }\n}\n\nexport async function restoreMessageAggregateSnapshot(\n em: EntityManager,\n snapshot: MessageAggregateSnapshot,\n): Promise<void> {\n await withAtomicFlush(em, [async () => {\n // Resolve every read before mutating: a query issued between a scalar\n // mutation and the flush can silently reset the Unit of Work (SPEC-018\n // Problem 1). withAtomicFlush flushes once at the end, so ordering is ours.\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const existingMessage = await em.findOne(Message, { id: snapshot.message.id })\n const existingRecipients = await em.find(MessageRecipient, { messageId: snapshot.message.id })\n const existingObjects = await em.find(MessageObject, { messageId: snapshot.message.id })\n const attachmentsToRelink = snapshot.attachmentIds.length > 0\n ? await em.find(Attachment, {\n id: { $in: snapshot.attachmentIds },\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n })\n : []\n\n if (!existingMessage) {\n const created = em.create(Message, {\n id: snapshot.message.id,\n type: snapshot.message.type,\n visibility: snapshot.message.visibility,\n sourceEntityType: snapshot.message.sourceEntityType,\n sourceEntityId: snapshot.message.sourceEntityId,\n externalEmail: snapshot.message.externalEmail,\n externalName: snapshot.message.externalName,\n threadId: snapshot.message.threadId,\n parentMessageId: snapshot.message.parentMessageId,\n senderUserId: snapshot.message.senderUserId,\n subject: snapshot.message.subject,\n body: snapshot.message.body,\n bodyFormat: snapshot.message.bodyFormat,\n priority: snapshot.message.priority,\n status: snapshot.message.status,\n isDraft: snapshot.message.isDraft,\n sentAt: toDate(snapshot.message.sentAt),\n actionData: snapshot.message.actionData as MessageActionData,\n actionResult: snapshot.message.actionResult,\n actionTaken: snapshot.message.actionTaken,\n actionTakenByUserId: snapshot.message.actionTakenByUserId,\n actionTakenAt: toDate(snapshot.message.actionTakenAt),\n sendViaEmail: snapshot.message.sendViaEmail,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n deletedAt: toDate(snapshot.message.deletedAt),\n })\n em.persist(created)\n } else {\n existingMessage.type = snapshot.message.type\n existingMessage.visibility = snapshot.message.visibility\n existingMessage.sourceEntityType = snapshot.message.sourceEntityType\n existingMessage.sourceEntityId = snapshot.message.sourceEntityId\n existingMessage.externalEmail = snapshot.message.externalEmail\n existingMessage.externalName = snapshot.message.externalName\n existingMessage.threadId = snapshot.message.threadId\n existingMessage.parentMessageId = snapshot.message.parentMessageId\n existingMessage.senderUserId = snapshot.message.senderUserId\n existingMessage.subject = snapshot.message.subject\n existingMessage.body = snapshot.message.body\n existingMessage.bodyFormat = snapshot.message.bodyFormat\n existingMessage.priority = snapshot.message.priority\n existingMessage.status = snapshot.message.status\n existingMessage.isDraft = snapshot.message.isDraft\n existingMessage.sentAt = toDate(snapshot.message.sentAt)\n existingMessage.actionData = snapshot.message.actionData as MessageActionData\n existingMessage.actionResult = snapshot.message.actionResult\n existingMessage.actionTaken = snapshot.message.actionTaken\n existingMessage.actionTakenByUserId = snapshot.message.actionTakenByUserId\n existingMessage.actionTakenAt = toDate(snapshot.message.actionTakenAt)\n existingMessage.sendViaEmail = snapshot.message.sendViaEmail\n existingMessage.tenantId = snapshot.message.tenantId\n existingMessage.organizationId = snapshot.message.organizationId\n existingMessage.deletedAt = toDate(snapshot.message.deletedAt)\n }\n\n const recipientById = new Map(existingRecipients.map((item) => [item.id, item]))\n const snapshotRecipientIds = new Set(snapshot.recipients.map((item) => item.id))\n for (const current of existingRecipients) {\n if (!snapshotRecipientIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const recipient of snapshot.recipients) {\n const existing = recipientById.get(recipient.id)\n if (!existing) {\n em.persist(em.create(MessageRecipient, {\n id: recipient.id,\n messageId: recipient.messageId,\n recipientUserId: recipient.recipientUserId,\n recipientType: recipient.recipientType,\n status: recipient.status,\n readAt: toDate(recipient.readAt),\n archivedAt: toDate(recipient.archivedAt),\n deletedAt: toDate(recipient.deletedAt),\n }))\n continue\n }\n existing.messageId = recipient.messageId\n existing.recipientUserId = recipient.recipientUserId\n existing.recipientType = recipient.recipientType\n existing.status = recipient.status\n existing.readAt = toDate(recipient.readAt)\n existing.archivedAt = toDate(recipient.archivedAt)\n existing.deletedAt = toDate(recipient.deletedAt)\n }\n\n const objectById = new Map(existingObjects.map((item) => [item.id, item]))\n const snapshotObjectIds = new Set(snapshot.objects.map((item) => item.id))\n for (const current of existingObjects) {\n if (!snapshotObjectIds.has(current.id)) {\n em.remove(current)\n }\n }\n for (const object of snapshot.objects) {\n const existing = objectById.get(object.id)\n if (!existing) {\n em.persist(em.create(MessageObject, {\n id: object.id,\n messageId: object.messageId,\n entityModule: object.entityModule,\n entityType: object.entityType,\n entityId: object.entityId,\n actionRequired: object.actionRequired,\n actionType: object.actionType,\n actionLabel: object.actionLabel,\n entitySnapshot: object.entitySnapshot,\n }))\n continue\n }\n existing.messageId = object.messageId\n existing.entityModule = object.entityModule\n existing.entityType = object.entityType\n existing.entityId = object.entityId\n existing.actionRequired = object.actionRequired\n existing.actionType = object.actionType\n existing.actionLabel = object.actionLabel\n existing.entitySnapshot = object.entitySnapshot\n }\n\n await em.nativeDelete(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: snapshot.message.id,\n tenantId: snapshot.message.tenantId,\n organizationId: snapshot.message.organizationId,\n id: { $nin: snapshot.attachmentIds.length > 0 ? snapshot.attachmentIds : ['00000000-0000-0000-0000-000000000000'] },\n })\n for (const attachment of attachmentsToRelink) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = snapshot.message.id\n }\n }], { transaction: true })\n}\n\nexport function buildCommandLogBase(\n actionLabel: string,\n resourceId: string,\n snapshot: { tenantId: string; organizationId: string | null },\n) {\n return {\n actionLabel,\n resourceKind: 'messages.message',\n resourceId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
5
6
|
import { perspectiveSaveSchema } from "@open-mercato/core/modules/perspectives/data/validators";
|
|
6
7
|
import {
|
|
7
8
|
loadPerspectivesState,
|
|
@@ -122,15 +123,10 @@ async function POST(req, ctx) {
|
|
|
122
123
|
})();
|
|
123
124
|
const rbac = container.resolve("rbacService");
|
|
124
125
|
const scope = buildScope(auth);
|
|
125
|
-
const saved = await saveUserPerspective(em, cache, {
|
|
126
|
-
scope,
|
|
127
|
-
tableId,
|
|
128
|
-
input: parsed.data
|
|
129
|
-
});
|
|
130
126
|
const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0);
|
|
131
127
|
const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0);
|
|
132
|
-
|
|
133
|
-
if (
|
|
128
|
+
const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0;
|
|
129
|
+
if (hasRoleOps) {
|
|
134
130
|
const canApplyToRoles = await rbac.userHasAllFeatures?.(
|
|
135
131
|
auth.sub,
|
|
136
132
|
["perspectives.role_defaults"],
|
|
@@ -151,28 +147,43 @@ async function POST(req, ctx) {
|
|
|
151
147
|
if (missing.length) {
|
|
152
148
|
return NextResponse.json({ error: "Invalid roles", missing }, { status: 400 });
|
|
153
149
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
name: parsed.data.name,
|
|
162
|
-
settings: parsed.data.settings,
|
|
163
|
-
setDefault: parsed.data.setRoleDefault ?? false
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
if (clearRoleIds.length) {
|
|
168
|
-
await clearRolePerspectives(em, cache, {
|
|
150
|
+
}
|
|
151
|
+
let saved = null;
|
|
152
|
+
let updatedRolePerspectives = null;
|
|
153
|
+
await withAtomicFlush(em, [
|
|
154
|
+
async () => {
|
|
155
|
+
saved = await saveUserPerspective(em, cache, {
|
|
156
|
+
scope,
|
|
169
157
|
tableId,
|
|
170
|
-
|
|
171
|
-
organizationId: auth.orgId ?? null,
|
|
172
|
-
roleIds: clearRoleIds
|
|
158
|
+
input: parsed.data
|
|
173
159
|
});
|
|
160
|
+
},
|
|
161
|
+
async () => {
|
|
162
|
+
if (applyToRoles.length) {
|
|
163
|
+
updatedRolePerspectives = await saveRolePerspectives(em, cache, {
|
|
164
|
+
tableId,
|
|
165
|
+
tenantId: auth.tenantId ?? null,
|
|
166
|
+
organizationId: auth.orgId ?? null,
|
|
167
|
+
input: {
|
|
168
|
+
roleIds: applyToRoles,
|
|
169
|
+
name: parsed.data.name,
|
|
170
|
+
settings: parsed.data.settings,
|
|
171
|
+
setDefault: parsed.data.setRoleDefault ?? false
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
async () => {
|
|
177
|
+
if (clearRoleIds.length) {
|
|
178
|
+
await clearRolePerspectives(em, cache, {
|
|
179
|
+
tableId,
|
|
180
|
+
tenantId: auth.tenantId ?? null,
|
|
181
|
+
organizationId: auth.orgId ?? null,
|
|
182
|
+
roleIds: clearRoleIds
|
|
183
|
+
});
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
|
-
}
|
|
186
|
+
], { transaction: true });
|
|
176
187
|
return NextResponse.json({
|
|
177
188
|
perspective: saved,
|
|
178
189
|
rolePerspectives: updatedRolePerspectives ?? [],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/perspectives/api/%5BtableId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n const saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n })\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n\n if (applyToRoles.length > 0 || clearRoleIds.length > 0) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n\n if (clearRoleIds.length) {\n await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n }\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0\n\n if (hasRoleOps) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n\n await withAtomicFlush(em, [\n async () => {\n saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n })\n },\n async () => {\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n },\n async () => {\n if (clearRoleIds.length) {\n await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n },\n ], { transaction: true })\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@ import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
|
2
2
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
3
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
4
|
import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, parseWithCustomFields, setCustomFieldsIfAny } from "@open-mercato/shared/lib/commands/helpers";
|
|
5
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
5
6
|
import { buildCustomFieldResetMap, diffCustomFieldChanges, loadCustomFieldSnapshot } from "@open-mercato/shared/lib/commands/customFieldSnapshots";
|
|
6
7
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
8
|
import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
|
|
@@ -172,8 +173,18 @@ const createResourceCommand = {
|
|
|
172
173
|
createdAt: now,
|
|
173
174
|
updatedAt: now
|
|
174
175
|
});
|
|
175
|
-
em
|
|
176
|
-
|
|
176
|
+
await withAtomicFlush(em, [
|
|
177
|
+
async () => {
|
|
178
|
+
em.persist(record);
|
|
179
|
+
await em.flush();
|
|
180
|
+
},
|
|
181
|
+
() => syncResourcesResourceTags(em, {
|
|
182
|
+
resourceId: record.id,
|
|
183
|
+
organizationId: record.organizationId,
|
|
184
|
+
tenantId: record.tenantId,
|
|
185
|
+
tagIds: parsed.tags
|
|
186
|
+
})
|
|
187
|
+
], { transaction: true });
|
|
177
188
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
178
189
|
await setCustomFieldsIfAny({
|
|
179
190
|
dataEngine,
|
|
@@ -183,13 +194,6 @@ const createResourceCommand = {
|
|
|
183
194
|
organizationId: record.organizationId,
|
|
184
195
|
values: custom
|
|
185
196
|
});
|
|
186
|
-
await syncResourcesResourceTags(em, {
|
|
187
|
-
resourceId: record.id,
|
|
188
|
-
organizationId: record.organizationId,
|
|
189
|
-
tenantId: record.tenantId,
|
|
190
|
-
tagIds: parsed.tags
|
|
191
|
-
});
|
|
192
|
-
await em.flush();
|
|
193
197
|
await emitCrudSideEffects({
|
|
194
198
|
dataEngine,
|
|
195
199
|
action: "created",
|
|
@@ -282,42 +286,40 @@ const updateResourceCommand = {
|
|
|
282
286
|
if (!record) throw new CrudHttpError(404, { error: "Resources resource not found." });
|
|
283
287
|
ensureTenantScope(ctx, record.tenantId);
|
|
284
288
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
289
|
+
let capacityUnit;
|
|
285
290
|
if (parsed.capacityUnitValue !== void 0) {
|
|
286
291
|
const unitValue = typeof parsed.capacityUnitValue === "string" ? parsed.capacityUnitValue.trim() : "";
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
record.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
} else {
|
|
293
|
-
const unitSnapshot = await resolveCapacityUnit(
|
|
294
|
-
em,
|
|
295
|
-
{ tenantId: record.tenantId, organizationId: record.organizationId },
|
|
296
|
-
unitValue
|
|
297
|
-
);
|
|
298
|
-
record.capacityUnitValue = unitSnapshot.value;
|
|
299
|
-
record.capacityUnitName = unitSnapshot.name;
|
|
300
|
-
record.capacityUnitColor = unitSnapshot.color;
|
|
301
|
-
record.capacityUnitIcon = unitSnapshot.icon;
|
|
302
|
-
}
|
|
292
|
+
capacityUnit = unitValue ? await resolveCapacityUnit(
|
|
293
|
+
em,
|
|
294
|
+
{ tenantId: record.tenantId, organizationId: record.organizationId },
|
|
295
|
+
unitValue
|
|
296
|
+
) : null;
|
|
303
297
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
298
|
+
await withAtomicFlush(em, [
|
|
299
|
+
() => {
|
|
300
|
+
if (parsed.capacityUnitValue !== void 0) {
|
|
301
|
+
record.capacityUnitValue = capacityUnit?.value ?? null;
|
|
302
|
+
record.capacityUnitName = capacityUnit?.name ?? null;
|
|
303
|
+
record.capacityUnitColor = capacityUnit?.color ?? null;
|
|
304
|
+
record.capacityUnitIcon = capacityUnit?.icon ?? null;
|
|
305
|
+
}
|
|
306
|
+
if (parsed.name !== void 0) record.name = parsed.name;
|
|
307
|
+
if (parsed.description !== void 0) record.description = parsed.description ?? null;
|
|
308
|
+
if (parsed.resourceTypeId !== void 0) record.resourceTypeId = parsed.resourceTypeId ?? null;
|
|
309
|
+
if (parsed.capacity !== void 0) record.capacity = parsed.capacity ?? null;
|
|
310
|
+
if (parsed.appearanceIcon !== void 0) record.appearanceIcon = parsed.appearanceIcon ?? null;
|
|
311
|
+
if (parsed.appearanceColor !== void 0) record.appearanceColor = parsed.appearanceColor ?? null;
|
|
312
|
+
if (parsed.availabilityRuleSetId !== void 0) record.availabilityRuleSetId = parsed.availabilityRuleSetId ?? null;
|
|
313
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
314
|
+
if (parsed.isActive !== void 0) record.isActive = parsed.isActive;
|
|
315
|
+
},
|
|
316
|
+
() => syncResourcesResourceTags(em, {
|
|
317
|
+
resourceId: record.id,
|
|
318
|
+
organizationId: record.organizationId,
|
|
319
|
+
tenantId: record.tenantId,
|
|
320
|
+
tagIds: parsed.tags
|
|
321
|
+
})
|
|
322
|
+
], { transaction: true });
|
|
321
323
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
322
324
|
await setCustomFieldsIfAny({
|
|
323
325
|
dataEngine,
|
|
@@ -402,28 +404,30 @@ const updateResourceCommand = {
|
|
|
402
404
|
const em = ctx.container.resolve("em").fork();
|
|
403
405
|
const record = await em.findOne(ResourcesResource, { id: before.id });
|
|
404
406
|
if (!record) return;
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
407
|
+
await withAtomicFlush(em, [
|
|
408
|
+
() => {
|
|
409
|
+
record.name = before.name;
|
|
410
|
+
record.description = before.description ?? null;
|
|
411
|
+
record.resourceTypeId = before.resourceTypeId ?? null;
|
|
412
|
+
record.capacity = before.capacity ?? null;
|
|
413
|
+
record.capacityUnitValue = before.capacityUnitValue ?? null;
|
|
414
|
+
record.capacityUnitName = before.capacityUnitName ?? null;
|
|
415
|
+
record.capacityUnitColor = before.capacityUnitColor ?? null;
|
|
416
|
+
record.capacityUnitIcon = before.capacityUnitIcon ?? null;
|
|
417
|
+
record.appearanceIcon = before.appearanceIcon ?? null;
|
|
418
|
+
record.appearanceColor = before.appearanceColor ?? null;
|
|
419
|
+
record.isActive = before.isActive;
|
|
420
|
+
record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
|
|
421
|
+
record.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null;
|
|
422
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
423
|
+
},
|
|
424
|
+
() => syncResourcesResourceTags(em, {
|
|
425
|
+
resourceId: record.id,
|
|
426
|
+
organizationId: record.organizationId,
|
|
427
|
+
tenantId: record.tenantId,
|
|
428
|
+
tagIds: before.tags
|
|
429
|
+
})
|
|
430
|
+
], { transaction: true });
|
|
427
431
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
428
432
|
const customBefore = payload.customBefore ?? fallbackCustomBefore ?? void 0;
|
|
429
433
|
const customAfter = payload.customAfter ?? fallbackCustomAfter ?? void 0;
|
|
@@ -523,52 +527,56 @@ const deleteResourceCommand = {
|
|
|
523
527
|
const fallbackCustomBefore = before.customFields ?? null;
|
|
524
528
|
const em = ctx.container.resolve("em").fork();
|
|
525
529
|
let record = await em.findOne(ResourcesResource, { id: before.id });
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
530
|
+
await withAtomicFlush(em, [
|
|
531
|
+
async () => {
|
|
532
|
+
if (!record) {
|
|
533
|
+
record = em.create(ResourcesResource, {
|
|
534
|
+
id: before.id,
|
|
535
|
+
tenantId: before.tenantId,
|
|
536
|
+
organizationId: before.organizationId,
|
|
537
|
+
name: before.name,
|
|
538
|
+
description: before.description ?? null,
|
|
539
|
+
resourceTypeId: before.resourceTypeId ?? null,
|
|
540
|
+
capacity: before.capacity ?? null,
|
|
541
|
+
capacityUnitValue: before.capacityUnitValue ?? null,
|
|
542
|
+
capacityUnitName: before.capacityUnitName ?? null,
|
|
543
|
+
capacityUnitColor: before.capacityUnitColor ?? null,
|
|
544
|
+
capacityUnitIcon: before.capacityUnitIcon ?? null,
|
|
545
|
+
appearanceIcon: before.appearanceIcon ?? null,
|
|
546
|
+
appearanceColor: before.appearanceColor ?? null,
|
|
547
|
+
isActive: before.isActive,
|
|
548
|
+
availabilityRuleSetId: before.availabilityRuleSetId ?? null,
|
|
549
|
+
deletedAt: null,
|
|
550
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
551
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
552
|
+
});
|
|
553
|
+
em.persist(record);
|
|
554
|
+
} else {
|
|
555
|
+
record.name = before.name;
|
|
556
|
+
record.description = before.description ?? null;
|
|
557
|
+
record.resourceTypeId = before.resourceTypeId ?? null;
|
|
558
|
+
record.capacity = before.capacity ?? null;
|
|
559
|
+
record.capacityUnitValue = before.capacityUnitValue ?? null;
|
|
560
|
+
record.capacityUnitName = before.capacityUnitName ?? null;
|
|
561
|
+
record.capacityUnitColor = before.capacityUnitColor ?? null;
|
|
562
|
+
record.capacityUnitIcon = before.capacityUnitIcon ?? null;
|
|
563
|
+
record.appearanceIcon = before.appearanceIcon ?? null;
|
|
564
|
+
record.appearanceColor = before.appearanceColor ?? null;
|
|
565
|
+
record.isActive = before.isActive;
|
|
566
|
+
record.availabilityRuleSetId = before.availabilityRuleSetId ?? null;
|
|
567
|
+
record.deletedAt = null;
|
|
568
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
569
|
+
}
|
|
570
|
+
await em.flush();
|
|
571
|
+
},
|
|
572
|
+
() => syncResourcesResourceTags(em, {
|
|
573
|
+
resourceId: record.id,
|
|
574
|
+
organizationId: record.organizationId,
|
|
575
|
+
tenantId: record.tenantId,
|
|
576
|
+
tagIds: before.tags
|
|
577
|
+
})
|
|
578
|
+
], { transaction: true });
|
|
579
|
+
const resolvedRecord = record;
|
|
572
580
|
const dataEngine = ctx.container.resolve("dataEngine");
|
|
573
581
|
const customBefore = payload.customBefore ?? fallbackCustomBefore ?? void 0;
|
|
574
582
|
if (customBefore) {
|
|
@@ -576,20 +584,20 @@ const deleteResourceCommand = {
|
|
|
576
584
|
await setCustomFieldsIfAny({
|
|
577
585
|
dataEngine,
|
|
578
586
|
entityId: E.resources.resources_resource,
|
|
579
|
-
recordId:
|
|
580
|
-
tenantId:
|
|
581
|
-
organizationId:
|
|
587
|
+
recordId: resolvedRecord.id,
|
|
588
|
+
tenantId: resolvedRecord.tenantId,
|
|
589
|
+
organizationId: resolvedRecord.organizationId,
|
|
582
590
|
values: reset
|
|
583
591
|
});
|
|
584
592
|
}
|
|
585
593
|
await emitCrudUndoSideEffects({
|
|
586
594
|
dataEngine,
|
|
587
595
|
action: "created",
|
|
588
|
-
entity:
|
|
596
|
+
entity: resolvedRecord,
|
|
589
597
|
identifiers: {
|
|
590
|
-
id:
|
|
591
|
-
organizationId:
|
|
592
|
-
tenantId:
|
|
598
|
+
id: resolvedRecord.id,
|
|
599
|
+
organizationId: resolvedRecord.organizationId,
|
|
600
|
+
tenantId: resolvedRecord.tenantId
|
|
593
601
|
},
|
|
594
602
|
events: resourcesResourceCrudEvents,
|
|
595
603
|
indexer: resourceCrudIndexer
|