@open-mercato/core 0.7.0 → 0.7.1-develop.7102.1.b41f7e3e51
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 +1 -1
- package/dist/helpers/integration/api.js +13 -1
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +19 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +1 -1
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/page.js +3 -1
- package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
- package/dist/modules/api_keys/di.js +9 -0
- package/dist/modules/api_keys/di.js.map +7 -0
- package/dist/modules/api_keys/services/principalService.js +31 -0
- package/dist/modules/api_keys/services/principalService.js.map +7 -0
- package/dist/modules/attachments/api/route.js +2 -2
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js +2 -0
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/di.js +11 -1
- package/dist/modules/attachments/di.js.map +2 -2
- package/dist/modules/attachments/index.js.map +1 -1
- package/dist/modules/attachments/lib/attachment-service.js +313 -0
- package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
- package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/upload-limits.js +6 -2
- package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +6 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +19 -11
- package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
- package/dist/modules/audit_logs/services/actionLogService.js +57 -12
- package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
- package/dist/modules/auth/acl.js +30 -5
- package/dist/modules/auth/acl.js.map +2 -2
- package/dist/modules/auth/api/feature-check.js +8 -2
- package/dist/modules/auth/api/feature-check.js.map +2 -2
- package/dist/modules/auth/api/login.js +4 -1
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/api/reset/validate.js +55 -0
- package/dist/modules/auth/api/reset/validate.js.map +7 -0
- package/dist/modules/auth/api/roles/acl/route.js +28 -33
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/session/refresh.js +25 -1
- package/dist/modules/auth/api/session/refresh.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +96 -51
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +13 -5
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/backend/roles/page.js +3 -1
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js +5 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +2 -1
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/commands/acl.js +254 -0
- package/dist/modules/auth/commands/acl.js.map +7 -0
- package/dist/modules/auth/components/AclEditor.js +39 -30
- package/dist/modules/auth/components/AclEditor.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/data/validators.js +5 -1
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/di.js +35 -9
- package/dist/modules/auth/di.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +46 -0
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/lib/grantChecks.js.map +2 -2
- package/dist/modules/auth/lib/userIdFilter.js +16 -0
- package/dist/modules/auth/lib/userIdFilter.js.map +7 -0
- package/dist/modules/auth/services/authService.js +16 -2
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/auth/services/principalService.js +200 -0
- package/dist/modules/auth/services/principalService.js.map +7 -0
- package/dist/modules/auth/services/rbacService.js +67 -24
- package/dist/modules/auth/services/rbacService.js.map +3 -3
- package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
- package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
- package/dist/modules/business_rules/backend/logs/page.js +3 -1
- package/dist/modules/business_rules/backend/logs/page.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/page.js +3 -1
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +3 -1
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/business_rules/lib/expression-evaluator.js +9 -9
- package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-evaluator.js +10 -9
- package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
- package/dist/modules/catalog/ai-agents.js +1 -1
- package/dist/modules/catalog/ai-agents.js.map +1 -1
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +2 -2
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +5 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -0
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +7 -1
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +2 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/test-seed/route.js +95 -10
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +3 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +14 -1
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/communication_channels/di.js +7 -1
- package/dist/modules/communication_channels/di.js.map +2 -2
- package/dist/modules/communication_channels/lib/email-capabilities.js +4 -1
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +2 -2
- package/dist/modules/communication_channels/lib/outbound-recipient.js +33 -0
- package/dist/modules/communication_channels/lib/outbound-recipient.js.map +7 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js +51 -0
- package/dist/modules/communication_channels/lib/resolve-channel-type.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +41 -2
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/configs/lib/system-status.js +9 -0
- package/dist/modules/configs/lib/system-status.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +3 -1
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +3 -1
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/services/rateFetchingService.js +6 -1
- package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js +9 -0
- package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +3 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +3 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
- package/dist/modules/customer_accounts/services/domainMappingService.js +10 -0
- package/dist/modules/customer_accounts/services/domainMappingService.js.map +2 -2
- package/dist/modules/customers/ai-tools/companies-pack.js +4 -1
- package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +13 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/deals-pack.js +52 -6
- package/dist/modules/customers/ai-tools/deals-pack.js.map +2 -2
- package/dist/modules/customers/ai-tools/people-pack.js +4 -1
- package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +48 -6
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/people/route.js +20 -64
- package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +17 -94
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +8 -5
- package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/companies/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/people/route.js +1 -1
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +8 -25
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/stats/route.js +3 -22
- package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
- package/dist/modules/customers/api/deals/aggregate/route.js +11 -4
- package/dist/modules/customers/api/deals/aggregate/route.js.map +2 -2
- package/dist/modules/customers/api/deals/map/route.js +2 -1
- package/dist/modules/customers/api/deals/map/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +38 -1
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/entity-roles-factory.js +4 -4
- package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -1
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +27 -4
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +4 -3
- package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/context.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +1 -1
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/route.js +11 -10
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/people/check-phone/route.js +51 -9
- package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
- package/dist/modules/customers/api/todos/route.js +30 -5
- package/dist/modules/customers/api/todos/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +4 -2
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +5 -2
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +4 -13
- package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +85 -14
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js +18 -0
- package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js.map +7 -0
- package/dist/modules/customers/backend/customers/people/page.js +4 -2
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js +9 -3
- package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +21 -34
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js.map +1 -1
- package/dist/modules/customers/commands/personCompanyLinks.js +131 -2
- package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
- package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
- package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
- package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
- package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +1 -1
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventBlock.js +1 -1
- package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +3 -2
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +40 -5
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +2 -1
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
- package/dist/modules/customers/components/calendar/types.js +1 -1
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
- package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -1
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +19 -7
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +6 -5
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +35 -11
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +3 -1
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +19 -13
- package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -4
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +3 -3
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/dist/modules/customers/components/detail/utils.js +5 -5
- package/dist/modules/customers/components/detail/utils.js.map +2 -2
- package/dist/modules/customers/data/entities.js.map +1 -1
- package/dist/modules/customers/data/validators.js +32 -3
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/events.js +5 -0
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +8 -2
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +3 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/mapItem.js +8 -3
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/participantIdentity.js +9 -0
- package/dist/modules/customers/lib/calendar/participantIdentity.js.map +7 -0
- package/dist/modules/customers/lib/closureStage.js +45 -0
- package/dist/modules/customers/lib/closureStage.js.map +7 -0
- package/dist/modules/customers/lib/dealStatus.js +30 -0
- package/dist/modules/customers/lib/dealStatus.js.map +2 -2
- package/dist/modules/customers/lib/detailReadAccess.js +12 -0
- package/dist/modules/customers/lib/detailReadAccess.js.map +7 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
- package/dist/modules/customers/lib/personCompanies.js +100 -2
- package/dist/modules/customers/lib/personCompanies.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +12 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +6 -1
- package/dist/modules/dashboards/lib/widgetScope.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/lib/abandoned-run.js +19 -0
- package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
- package/dist/modules/data_sync/lib/sync-engine.js +72 -6
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-export.js +5 -1
- package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
- package/dist/modules/data_sync/workers/sync-import.js +5 -1
- package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
- package/dist/modules/devices/acl.js +9 -1
- package/dist/modules/devices/acl.js.map +2 -2
- package/dist/modules/devices/api/admin/devices/[id]/route.js +5 -32
- package/dist/modules/devices/api/admin/devices/[id]/route.js.map +2 -2
- package/dist/modules/devices/api/admin/devices/route.js +14 -1
- package/dist/modules/devices/api/admin/devices/route.js.map +2 -2
- package/dist/modules/devices/api/deviceList.js +99 -12
- package/dist/modules/devices/api/deviceList.js.map +2 -2
- package/dist/modules/devices/api/deviceSerialization.js +46 -0
- package/dist/modules/devices/api/deviceSerialization.js.map +7 -0
- package/dist/modules/devices/api/openapi.js +3 -1
- package/dist/modules/devices/api/openapi.js.map +2 -2
- package/dist/modules/devices/api/route.js +9 -1
- package/dist/modules/devices/api/route.js.map +2 -2
- package/dist/modules/devices/backend/devices/[id]/page.js +10 -26
- package/dist/modules/devices/backend/devices/[id]/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/create/page.js +17 -1
- package/dist/modules/devices/backend/devices/create/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/page.js +28 -34
- package/dist/modules/devices/backend/devices/page.js.map +2 -2
- package/dist/modules/devices/backend/devices/useDeviceUserLabels.js +34 -0
- package/dist/modules/devices/backend/devices/useDeviceUserLabels.js.map +7 -0
- package/dist/modules/devices/backend/devices/userOptions.js +52 -0
- package/dist/modules/devices/backend/devices/userOptions.js.map +7 -0
- package/dist/modules/devices/setup.js +6 -2
- package/dist/modules/devices/setup.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +2 -1
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +2 -1
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/directory/di.js +9 -0
- package/dist/modules/directory/di.js.map +2 -2
- package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
- package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
- package/dist/modules/directory/services/organizationScopeService.js +56 -0
- package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +4 -2
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +3 -1
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/eudr/api/plots/route.js +5 -1
- package/dist/modules/eudr/api/plots/route.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/plots/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/plots/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +2 -2
- package/dist/modules/eudr/backend/eudr/statements/page.js +3 -0
- package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
- package/dist/modules/eudr/commands/statements.js +0 -3
- package/dist/modules/eudr/commands/statements.js.map +2 -2
- package/dist/modules/eudr/data/validators.js +4 -1
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +1 -0
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/OverridesTable.js +1 -0
- package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +0 -8
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +3 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js +3 -0
- package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js +1 -12
- package/dist/modules/inbox_ops/lib/htmlToPlainText.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +6 -1
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +38 -29
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +36 -9
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/credential-secret-fields.js +55 -0
- package/dist/modules/integrations/backend/integrations/credential-secret-fields.js.map +7 -0
- package/dist/modules/integrations/data/validators.js +11 -3
- package/dist/modules/integrations/data/validators.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-masking.js +6 -2
- package/dist/modules/integrations/lib/credentials-masking.js.map +2 -2
- package/dist/modules/messages/api/[id]/route.js +29 -1
- package/dist/modules/messages/api/[id]/route.js.map +2 -2
- package/dist/modules/messages/api/openapi.js +17 -2
- package/dist/modules/messages/api/openapi.js.map +2 -2
- package/dist/modules/messages/api/route.js +17 -2
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +7 -14
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/components/inboxFilters.js +15 -0
- package/dist/modules/messages/components/inboxFilters.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +11 -1
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +3 -1
- package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +2 -2
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js +2 -1
- package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
- package/dist/modules/messages/components/messageListLabels.js +14 -2
- package/dist/modules/messages/components/messageListLabels.js.map +2 -2
- package/dist/modules/messages/data/validators.js +18 -4
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/messages/lib/channel-sender-identity.js +26 -0
- package/dist/modules/messages/lib/channel-sender-identity.js.map +7 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js +41 -0
- package/dist/modules/messages/lib/composeSourceChannelType.js.map +7 -0
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +4 -6
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +3 -1
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/payment_gateways/lib/webhook-processor.js +7 -0
- package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +3 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/push_notifications/backend/push_notifications/page.js +3 -1
- package/dist/modules/push_notifications/backend/push_notifications/page.js.map +2 -2
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js +1 -1
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +150 -51
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/search-entity-policy.js +14 -0
- package/dist/modules/query_index/lib/search-entity-policy.js.map +7 -0
- package/dist/modules/query_index/lib/search-tokens.js +79 -3
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js +3 -1
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +3 -1
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +3 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +3 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +79 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
- package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +3 -0
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +51 -22
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +83 -18
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +23 -9
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
- package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +68 -14
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +6 -5
- package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemShipmentLock.js +54 -0
- package/dist/modules/sales/components/documents/lineItemShipmentLock.js.map +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/sales/data/entities.js +8 -2
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/extension-points.js +1 -0
- package/dist/modules/sales/extension-points.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js +33 -0
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
- package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +10 -2
- package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +2 -2
- package/dist/modules/sales/widgets/injection-table.js +4 -7
- package/dist/modules/sales/widgets/injection-table.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/page.js +3 -0
- package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/page.js +3 -0
- package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/page.js +3 -0
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js +3 -0
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +3 -0
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js +3 -0
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
- package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js +3 -0
- package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
- package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/dist/modules/warranty_claims/backend/page.js +3 -0
- package/dist/modules/warranty_claims/backend/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +2 -2
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +3 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +2 -2
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
- package/dist/modules/wms/api/warehouseSearch.js +21 -0
- package/dist/modules/wms/api/warehouseSearch.js.map +7 -0
- package/dist/modules/wms/api/warehouses/route.js +13 -9
- package/dist/modules/wms/api/warehouses/route.js.map +2 -2
- package/dist/modules/wms/api/zones/route.js +15 -2
- package/dist/modules/wms/api/zones/route.js.map +2 -2
- package/dist/modules/wms/commands/configuration.js +121 -9
- package/dist/modules/wms/commands/configuration.js.map +2 -2
- package/dist/modules/wms/commands/inventory-actions.js +3 -0
- package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
- package/dist/modules/wms/commands/shared.js +9 -1
- package/dist/modules/wms/commands/shared.js.map +2 -2
- package/dist/modules/wms/components/backend/WarehouseEditDialog.js +158 -0
- package/dist/modules/wms/components/backend/WarehouseEditDialog.js.map +7 -0
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js +76 -111
- package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +3 -3
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +1 -0
- package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
- package/dist/modules/wms/components/backend/WmsLotsListPage.js +1 -0
- package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +27 -20
- package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
- package/dist/modules/wms/components/backend/warehouseFormOptions.js +66 -0
- package/dist/modules/wms/components/backend/warehouseFormOptions.js.map +7 -0
- package/dist/modules/workflows/backend/definitions/page.js +3 -1
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/page.js +3 -1
- package/dist/modules/workflows/backend/events/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/page.js +3 -1
- package/dist/modules/workflows/backend/instances/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +3 -1
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +4 -0
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +9 -10
- package/src/helpers/integration/api.ts +33 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +69 -1
- package/src/modules/api_keys/api/keys/route.ts +3 -1
- package/src/modules/api_keys/backend/api-keys/page.tsx +4 -1
- package/src/modules/api_keys/di.ts +7 -0
- package/src/modules/api_keys/services/principalService.ts +33 -0
- package/src/modules/attachments/AGENTS.md +15 -0
- package/src/modules/attachments/api/route.ts +2 -2
- package/src/modules/attachments/components/AttachmentLibrary.tsx +3 -0
- package/src/modules/attachments/di.ts +16 -0
- package/src/modules/attachments/i18n/de.json +5 -0
- package/src/modules/attachments/i18n/en.json +5 -0
- package/src/modules/attachments/i18n/es.json +5 -0
- package/src/modules/attachments/i18n/ko.json +5 -0
- package/src/modules/attachments/i18n/pl.json +5 -0
- package/src/modules/attachments/index.ts +10 -0
- package/src/modules/attachments/lib/attachment-service.ts +468 -0
- package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
- package/src/modules/attachments/lib/upload-limits.ts +6 -2
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +8 -0
- package/src/modules/audit_logs/lib/display-helpers.tsx +42 -32
- package/src/modules/audit_logs/services/actionLogService.ts +74 -11
- package/src/modules/auth/acl.ts +30 -6
- package/src/modules/auth/api/feature-check.ts +10 -3
- package/src/modules/auth/api/login.ts +9 -1
- package/src/modules/auth/api/reset/validate.ts +57 -0
- package/src/modules/auth/api/roles/acl/route.ts +42 -39
- package/src/modules/auth/api/session/refresh.ts +32 -1
- package/src/modules/auth/api/users/acl/route.ts +177 -53
- package/src/modules/auth/api/users/route.ts +18 -5
- package/src/modules/auth/backend/roles/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/backend/roles/page.tsx +4 -2
- package/src/modules/auth/backend/users/[id]/edit/page.meta.ts +5 -2
- package/src/modules/auth/backend/users/page.tsx +3 -2
- package/src/modules/auth/commands/acl.ts +495 -0
- package/src/modules/auth/components/AclEditor.tsx +63 -33
- package/src/modules/auth/data/entities.ts +4 -1
- package/src/modules/auth/data/validators.ts +5 -0
- package/src/modules/auth/di.ts +46 -16
- package/src/modules/auth/frontend/reset/[token]/page.tsx +74 -0
- package/src/modules/auth/i18n/de.json +389 -0
- package/src/modules/auth/i18n/en.json +389 -0
- package/src/modules/auth/i18n/es.json +389 -0
- package/src/modules/auth/i18n/ko.json +389 -0
- package/src/modules/auth/i18n/pl.json +391 -2
- package/src/modules/auth/lib/grantChecks.ts +6 -0
- package/src/modules/auth/lib/userIdFilter.ts +31 -0
- package/src/modules/auth/services/authService.ts +18 -2
- package/src/modules/auth/services/principalService.ts +266 -0
- package/src/modules/auth/services/rbacService.ts +110 -24
- package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
- package/src/modules/business_rules/backend/logs/page.tsx +4 -1
- package/src/modules/business_rules/backend/rules/page.tsx +4 -1
- package/src/modules/business_rules/backend/sets/page.tsx +4 -1
- package/src/modules/business_rules/lib/expression-evaluator.ts +9 -9
- package/src/modules/business_rules/lib/rule-evaluator.ts +10 -9
- package/src/modules/catalog/ai-agents.ts +1 -1
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +2 -2
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +12 -5
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +3 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +7 -0
- package/src/modules/catalog/i18n/de.json +1 -0
- package/src/modules/catalog/i18n/en.json +1 -0
- package/src/modules/catalog/i18n/es.json +1 -0
- package/src/modules/catalog/i18n/ko.json +1 -0
- package/src/modules/catalog/i18n/pl.json +1 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +4 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +17 -1
- package/src/modules/communication_channels/api/post/test-seed/route.ts +155 -16
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +4 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +38 -1
- package/src/modules/communication_channels/di.ts +7 -0
- package/src/modules/communication_channels/lib/adapter.ts +14 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +4 -0
- package/src/modules/communication_channels/lib/outbound-recipient.ts +71 -0
- package/src/modules/communication_channels/lib/resolve-channel-type.ts +99 -0
- package/src/modules/communication_channels/lib/test-seed.ts +64 -4
- package/src/modules/configs/i18n/de.json +2 -0
- package/src/modules/configs/i18n/en.json +2 -0
- package/src/modules/configs/i18n/es.json +2 -0
- package/src/modules/configs/i18n/ko.json +2 -0
- package/src/modules/configs/i18n/pl.json +2 -0
- package/src/modules/configs/lib/system-status.ts +9 -0
- package/src/modules/currencies/AGENTS.md +3 -0
- package/src/modules/currencies/backend/currencies/page.tsx +4 -1
- package/src/modules/currencies/backend/exchange-rates/page.tsx +4 -1
- package/src/modules/currencies/services/README.md +45 -0
- package/src/modules/currencies/services/rateFetchingService.ts +6 -1
- package/src/modules/customer_accounts/api/admin/domain-mappings.ts +7 -0
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +4 -1
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +4 -1
- package/src/modules/customer_accounts/services/domainMappingService.ts +10 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +4 -1
- package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +17 -2
- package/src/modules/customers/ai-tools/deals-pack.ts +66 -1
- package/src/modules/customers/ai-tools/people-pack.ts +4 -1
- package/src/modules/customers/api/activities/route.ts +45 -0
- package/src/modules/customers/api/companies/[id]/people/route.ts +24 -73
- package/src/modules/customers/api/companies/[id]/route.ts +24 -107
- package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +16 -8
- package/src/modules/customers/api/deals/[id]/companies/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/people/route.ts +4 -1
- package/src/modules/customers/api/deals/[id]/route.ts +17 -28
- package/src/modules/customers/api/deals/[id]/stats/route.ts +10 -25
- package/src/modules/customers/api/deals/aggregate/route.ts +14 -4
- package/src/modules/customers/api/deals/map/route.ts +1 -0
- package/src/modules/customers/api/deals/route.ts +50 -1
- package/src/modules/customers/api/entity-roles-factory.ts +8 -4
- package/src/modules/customers/api/interactions/route.ts +8 -2
- package/src/modules/customers/api/interactions/tasks/route.ts +24 -1
- package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +12 -4
- package/src/modules/customers/api/people/[id]/companies/context.ts +4 -1
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +4 -1
- package/src/modules/customers/api/people/[id]/route.ts +16 -11
- package/src/modules/customers/api/people/check-phone/route.ts +75 -10
- package/src/modules/customers/api/todos/route.ts +27 -2
- package/src/modules/customers/backend/customers/companies/page.tsx +5 -2
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +8 -2
- package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +5 -14
- package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +109 -27
- package/src/modules/customers/backend/customers/deals/pipeline/components/toneClasses.ts +21 -0
- package/src/modules/customers/backend/customers/people/page.tsx +5 -2
- package/src/modules/customers/backend/hooks/useEmailDuplicateCheck.ts +12 -4
- package/src/modules/customers/commands/deals.ts +34 -59
- package/src/modules/customers/commands/interactions.ts +1 -1
- package/src/modules/customers/commands/personCompanyLinks.ts +224 -8
- package/src/modules/customers/components/CustomerTodosTable.tsx +2 -0
- package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
- package/src/modules/customers/components/calendar/AgendaList.tsx +3 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +1 -1
- package/src/modules/customers/components/calendar/EventBlock.tsx +2 -2
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +4 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +61 -5
- package/src/modules/customers/components/calendar/editor/hooks.ts +7 -1
- package/src/modules/customers/components/calendar/types.ts +2 -2
- package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -1
- package/src/modules/customers/components/detail/DealForm.tsx +22 -2
- package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +58 -12
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +8 -1
- package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +23 -14
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +5 -5
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +5 -5
- package/src/modules/customers/components/detail/types.ts +1 -1
- package/src/modules/customers/components/detail/utils.ts +15 -5
- package/src/modules/customers/data/entities.ts +1 -1
- package/src/modules/customers/data/validators.ts +50 -9
- package/src/modules/customers/events.ts +5 -0
- package/src/modules/customers/i18n/de.json +9 -1
- package/src/modules/customers/i18n/en.json +9 -1
- package/src/modules/customers/i18n/es.json +9 -1
- package/src/modules/customers/i18n/ko.json +9 -1
- package/src/modules/customers/i18n/pl.json +9 -1
- package/src/modules/customers/lib/calendar/conflicts.ts +10 -2
- package/src/modules/customers/lib/calendar/editorPayload.ts +4 -3
- package/src/modules/customers/lib/calendar/mapItem.ts +8 -3
- package/src/modules/customers/lib/calendar/participantIdentity.ts +17 -0
- package/src/modules/customers/lib/closureStage.ts +76 -0
- package/src/modules/customers/lib/dealStatus.ts +42 -0
- package/src/modules/customers/lib/detailReadAccess.ts +35 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
- package/src/modules/customers/lib/personCompanies.ts +136 -1
- package/src/modules/customers/lib/todoCompatibility.ts +19 -1
- package/src/modules/dashboards/i18n/es.json +4 -4
- package/src/modules/dashboards/i18n/pl.json +7 -7
- package/src/modules/dashboards/lib/widgetScope.ts +6 -0
- package/src/modules/data_sync/AGENTS.md +1 -1
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -1
- package/src/modules/data_sync/lib/abandoned-run.ts +56 -0
- package/src/modules/data_sync/lib/adapter.ts +36 -0
- package/src/modules/data_sync/lib/sync-engine.ts +113 -5
- package/src/modules/data_sync/workers/sync-export.ts +4 -0
- package/src/modules/data_sync/workers/sync-import.ts +4 -0
- package/src/modules/devices/AGENTS.md +20 -1
- package/src/modules/devices/acl.ts +9 -1
- package/src/modules/devices/api/admin/devices/[id]/route.ts +5 -34
- package/src/modules/devices/api/admin/devices/route.ts +14 -1
- package/src/modules/devices/api/deviceList.ts +135 -11
- package/src/modules/devices/api/deviceSerialization.ts +50 -0
- package/src/modules/devices/api/openapi.ts +9 -1
- package/src/modules/devices/api/route.ts +9 -1
- package/src/modules/devices/backend/devices/[id]/page.tsx +18 -32
- package/src/modules/devices/backend/devices/create/page.tsx +17 -1
- package/src/modules/devices/backend/devices/page.tsx +44 -45
- package/src/modules/devices/backend/devices/useDeviceUserLabels.ts +45 -0
- package/src/modules/devices/backend/devices/userOptions.ts +99 -0
- package/src/modules/devices/i18n/de.json +3 -2
- package/src/modules/devices/i18n/en.json +3 -2
- package/src/modules/devices/i18n/es.json +3 -2
- package/src/modules/devices/i18n/ko.json +3 -2
- package/src/modules/devices/i18n/pl.json +3 -2
- package/src/modules/devices/setup.ts +6 -2
- package/src/modules/directory/backend/directory/organizations/page.tsx +3 -1
- package/src/modules/directory/backend/directory/tenants/page.tsx +3 -1
- package/src/modules/directory/di.ts +14 -2
- package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
- package/src/modules/directory/services/organizationScopeService.ts +85 -0
- package/src/modules/directory/utils/organizationScope.ts +4 -15
- package/src/modules/entities/api/records.ts +2 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +4 -1
- package/src/modules/eudr/api/plots/route.ts +4 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/plots/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +4 -0
- package/src/modules/eudr/backend/eudr/statements/page.tsx +4 -0
- package/src/modules/eudr/commands/statements.ts +0 -3
- package/src/modules/eudr/data/validators.ts +16 -1
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
- package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
- package/src/modules/feature_toggles/defaults.json +0 -8
- package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +4 -0
- package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +4 -0
- package/src/modules/inbox_ops/lib/htmlToPlainText.ts +1 -17
- package/src/modules/integrations/api/[id]/credentials/route.ts +6 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +61 -31
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +44 -9
- package/src/modules/integrations/backend/integrations/credential-secret-fields.ts +84 -0
- package/src/modules/integrations/data/validators.ts +12 -2
- package/src/modules/integrations/i18n/de.json +1 -0
- package/src/modules/integrations/i18n/en.json +1 -0
- package/src/modules/integrations/i18n/es.json +1 -0
- package/src/modules/integrations/i18n/ko.json +1 -0
- package/src/modules/integrations/i18n/pl.json +1 -0
- package/src/modules/integrations/lib/credentials-masking.ts +10 -6
- package/src/modules/messages/api/[id]/route.ts +55 -1
- package/src/modules/messages/api/openapi.ts +20 -0
- package/src/modules/messages/api/route.ts +31 -2
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +9 -16
- package/src/modules/messages/components/inboxFilters.ts +36 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +14 -1
- package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +3 -1
- package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +2 -1
- package/src/modules/messages/components/message-detail/types.ts +3 -0
- package/src/modules/messages/components/messageListLabels.ts +52 -3
- package/src/modules/messages/data/validators.ts +43 -4
- package/src/modules/messages/lib/channel-sender-identity.ts +55 -0
- package/src/modules/messages/lib/composeSourceChannelType.ts +95 -0
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +4 -6
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +4 -1
- package/src/modules/payment_gateways/lib/webhook-processor.ts +11 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +4 -0
- package/src/modules/push_notifications/backend/push_notifications/page.tsx +4 -1
- package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +4 -4
- package/src/modules/query_index/lib/engine.ts +249 -61
- package/src/modules/query_index/lib/search-entity-policy.ts +46 -0
- package/src/modules/query_index/lib/search-tokens.ts +135 -3
- package/src/modules/resources/backend/resources/resource-types/page.tsx +4 -1
- package/src/modules/resources/backend/resources/resources/page.tsx +4 -1
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +4 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
- package/src/modules/sales/commands/documents.ts +93 -0
- package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +4 -0
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/ItemsSection.tsx +56 -18
- package/src/modules/sales/components/documents/LineItemDialog.tsx +137 -20
- package/src/modules/sales/components/documents/PaymentsSection.tsx +20 -9
- package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +106 -14
- package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +6 -5
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
- package/src/modules/sales/components/documents/lineItemShipmentLock.ts +74 -0
- package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
- package/src/modules/sales/data/entities.ts +6 -0
- package/src/modules/sales/extension-points.ts +1 -0
- package/src/modules/sales/i18n/de.json +8 -0
- package/src/modules/sales/i18n/en.json +8 -0
- package/src/modules/sales/i18n/es.json +8 -0
- package/src/modules/sales/i18n/ko.json +8 -0
- package/src/modules/sales/i18n/pl.json +8 -0
- package/src/modules/sales/lib/salesChannelsToggleSeed.ts +42 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
- package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
- package/src/modules/sales/setup.ts +2 -0
- package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +9 -1
- package/src/modules/sales/widgets/injection-table.ts +4 -7
- package/src/modules/staff/backend/staff/leave-requests/page.tsx +4 -0
- package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +4 -0
- package/src/modules/staff/backend/staff/team-members/page.tsx +6 -0
- package/src/modules/staff/backend/staff/team-roles/page.tsx +4 -0
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +4 -0
- package/src/modules/staff/backend/staff/teams/page.tsx +4 -0
- package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
- package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +4 -0
- package/src/modules/sync_excel/lib/adapters/customers.ts +5 -0
- package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
- package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
- package/src/modules/warranty_claims/backend/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +4 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +4 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +4 -0
- package/src/modules/wms/api/warehouseSearch.ts +23 -0
- package/src/modules/wms/api/warehouses/route.ts +13 -9
- package/src/modules/wms/api/zones/route.ts +16 -0
- package/src/modules/wms/commands/configuration.ts +159 -10
- package/src/modules/wms/commands/inventory-actions.ts +3 -0
- package/src/modules/wms/commands/shared.ts +17 -0
- package/src/modules/wms/components/backend/WarehouseEditDialog.tsx +216 -0
- package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +95 -141
- package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +2 -0
- package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -0
- package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +36 -20
- package/src/modules/wms/components/backend/warehouseFormOptions.ts +72 -0
- package/src/modules/wms/i18n/de.json +2 -0
- package/src/modules/wms/i18n/en.json +2 -0
- package/src/modules/wms/i18n/es.json +2 -0
- package/src/modules/wms/i18n/ko.json +2 -0
- package/src/modules/wms/i18n/pl.json +2 -0
- package/src/modules/workflows/backend/definitions/page.tsx +4 -1
- package/src/modules/workflows/backend/events/page.tsx +4 -1
- package/src/modules/workflows/backend/instances/page.tsx +4 -1
- package/src/modules/workflows/backend/tasks/page.tsx +4 -1
- package/src/modules/workflows/lib/activity-executor.ts +9 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/deals/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerDeal,\n CustomerDealPersonLink,\n CustomerDealCompanyLink,\n CustomerDealStageTransition,\n CustomerDictionaryEntry,\n CustomerEntity,\n CustomerPipeline,\n CustomerPipelineStage,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { E } from '#generated/entities.ids.generated'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n expandResourceAliases,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isMissingDealStageTransitionTable, warnMissingDealStageTransitionTable } from '../../../lib/dealStageTransitionTable'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction forbidden(message: string) {\n return NextResponse.json({ error: message }, { status: 403 })\n}\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n isPrimary?: boolean\n}\n\nfunction normalizePersonAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const email =\n typeof entity.primaryEmail === 'string' && entity.primaryEmail.trim().length\n ? entity.primaryEmail.trim()\n : null\n const phone =\n typeof entity.primaryPhone === 'string' && entity.primaryPhone.trim().length\n ? entity.primaryPhone.trim()\n : null\n const jobTitle =\n entity.personProfile &&\n typeof (entity.personProfile as { jobTitle?: string | null })?.jobTitle === 'string' &&\n (entity.personProfile as { jobTitle?: string | null }).jobTitle?.trim().length\n ? ((entity.personProfile as { jobTitle?: string | null }).jobTitle as string).trim()\n : null\n const subtitle = jobTitle ?? email ?? phone ?? null\n const label = displayName.length ? displayName : email ?? phone ?? entity.id\n return { label, subtitle }\n}\n\nfunction normalizeCompanyAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const domain =\n entity.companyProfile &&\n typeof (entity.companyProfile as { domain?: string | null })?.domain === 'string' &&\n (entity.companyProfile as { domain?: string | null }).domain?.trim().length\n ? ((entity.companyProfile as { domain?: string | null }).domain as string).trim()\n : null\n const website =\n entity.companyProfile &&\n typeof (entity.companyProfile as { websiteUrl?: string | null })?.websiteUrl === 'string' &&\n (entity.companyProfile as { websiteUrl?: string | null }).websiteUrl?.trim().length\n ? ((entity.companyProfile as { websiteUrl?: string | null }).websiteUrl as string).trim()\n : null\n const subtitle = domain ?? website ?? null\n const label = displayName.length ? displayName : domain ?? website ?? entity.id\n return { label, subtitle }\n}\n\nfunction readIncludeFlags(request: Request): Set<string> {\n const flags = new Set<string>()\n const url = new URL(request.url)\n for (const rawValue of url.searchParams.getAll('include')) {\n rawValue\n .split(',')\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean)\n .forEach((value) => flags.add(value))\n }\n return flags\n}\n\nfunction readViewMode(request: Request): 'full' | 'lite' {\n const raw = new URL(request.url).searchParams.get('view')\n return raw === 'lite' || raw === 'detail-lite' ? 'lite' : 'full'\n}\n\nfunction normalizeStageLabel(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim().toLowerCase() : ''\n}\n\ntype StageTransitionPayload = {\n stageId: string\n stageLabel: string\n stageOrder: number\n transitionedAt: string\n}\n\ntype DealSnapshotStageInfo = {\n pipelineId: string | null\n stageId: string | null\n stageLabel: string | null\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null\n}\n\nfunction readRecordString(record: Record<string, unknown> | null, ...keys: string[]): string | null {\n if (!record) return null\n for (const key of keys) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction readSnapshotDealRecord(snapshot: unknown): Record<string, unknown> | null {\n const root = asObject(snapshot)\n if (!root) return null\n return asObject(root.deal) ?? root\n}\n\nfunction readSnapshotStageInfo(snapshot: unknown): DealSnapshotStageInfo {\n const dealRecord = readSnapshotDealRecord(snapshot)\n return {\n pipelineId: readRecordString(dealRecord, 'pipelineId', 'pipeline_id'),\n stageId: readRecordString(dealRecord, 'pipelineStageId', 'pipeline_stage_id'),\n stageLabel: readRecordString(dealRecord, 'pipelineStage', 'pipeline_stage'),\n }\n}\n\nasync function loadAuditStageTransitionsFallback({\n container,\n deal,\n pipelineStages,\n}: {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n deal: CustomerDeal\n pipelineStages: CustomerPipelineStage[]\n}): Promise<StageTransitionPayload[]> {\n if (!deal.tenantId || !deal.organizationId || !pipelineStages.length) return []\n\n let actionLogs: ActionLogService | null = null\n try {\n actionLogs = container.resolve('actionLogService') as ActionLogService\n } catch {\n return []\n }\n if (!actionLogs || typeof actionLogs.list !== 'function') return []\n const stageOrderById = new Map(pipelineStages.map((stage) => [stage.id, stage.order]))\n const stageLabelById = new Map(pipelineStages.map((stage) => [stage.id, stage.label]))\n const transitionsByStageId = new Map<string, StageTransitionPayload>()\n const logsResult = await actionLogs.list({\n tenantId: deal.tenantId,\n organizationId: deal.organizationId,\n resourceKind: 'customers.deal',\n resourceId: deal.id,\n limit: 200,\n offset: 0,\n sortField: 'createdAt',\n sortDir: 'asc',\n }).catch(() => null)\n const logs = logsResult?.items ?? []\n\n let previousStageId: string | null = null\n for (const log of logs) {\n if (log.executionState === 'failed' || log.executionState === 'undone') continue\n\n const before = readSnapshotStageInfo(log.snapshotBefore)\n const after = readSnapshotStageInfo(log.snapshotAfter)\n const nextStageId = after.stageId\n if (!nextStageId) continue\n\n const stageOrder = stageOrderById.get(nextStageId)\n if (typeof stageOrder !== 'number') {\n previousStageId = nextStageId\n continue\n }\n\n const effectivePreviousStageId: string | null = before.stageId ?? previousStageId\n if (effectivePreviousStageId === nextStageId && transitionsByStageId.has(nextStageId)) {\n previousStageId = nextStageId\n continue\n }\n\n transitionsByStageId.set(nextStageId, {\n stageId: nextStageId,\n stageLabel: after.stageLabel ?? stageLabelById.get(nextStageId) ?? nextStageId,\n stageOrder,\n transitionedAt: log.createdAt.toISOString(),\n })\n previousStageId = nextStageId\n }\n\n return Array.from(transitionsByStageId.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nfunction mergeStageTransitions({\n persisted,\n recovered,\n currentStage,\n fallbackTimestamp,\n}: {\n persisted: StageTransitionPayload[]\n recovered: StageTransitionPayload[]\n currentStage: { id: string; label: string; order: number } | null\n fallbackTimestamp: string\n}): StageTransitionPayload[] {\n const merged = new Map<string, StageTransitionPayload>()\n for (const transition of persisted) {\n merged.set(transition.stageId, transition)\n }\n for (const transition of recovered) {\n if (!merged.has(transition.stageId)) {\n merged.set(transition.stageId, transition)\n }\n }\n if (currentStage && !merged.has(currentStage.id)) {\n merged.set(currentStage.id, {\n stageId: currentStage.id,\n stageLabel: currentStage.label,\n stageOrder: currentStage.order,\n transitionedAt: fallbackTimestamp,\n })\n }\n return Array.from(merged.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nasync function loadPipelineStageAppearanceMap(\n em: EntityManager,\n stages: CustomerPipelineStage[],\n organizationId: string,\n tenantId: string,\n): Promise<Map<string, CustomerDictionaryEntry>> {\n const normalizedValues = stages\n .map((stage) => stage.label.trim().toLowerCase())\n .filter((value) => value.length > 0)\n if (!normalizedValues.length) return new Map<string, CustomerDictionaryEntry>()\n const entries = await findWithDecryption(\n em,\n CustomerDictionaryEntry,\n {\n organizationId,\n tenantId,\n kind: 'pipeline_stage',\n normalizedValue: { $in: normalizedValues },\n },\n undefined,\n { tenantId, organizationId },\n )\n const map = new Map<string, CustomerDictionaryEntry>()\n entries.forEach((entry) => map.set(entry.normalizedValue, entry))\n return map\n}\n\nasync function resolveEffectivePipelineStage(\n em: EntityManager,\n deal: CustomerDeal,\n decryptionScope: { tenantId: string | null; organizationId: string | null },\n): Promise<CustomerPipelineStage | null> {\n if (deal.pipelineStageId) {\n const exactStage = await findOneWithDecryption(\n em,\n CustomerPipelineStage,\n {\n id: deal.pipelineStageId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n if (exactStage) return exactStage\n }\n\n const normalizedStageLabel = normalizeStageLabel(deal.pipelineStage)\n if (!normalizedStageLabel) return null\n\n const scopedStages = await findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n ...(deal.pipelineId ? { pipelineId: deal.pipelineId } : {}),\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n\n const matchingStages = scopedStages.filter((stage) => normalizeStageLabel(stage.label) === normalizedStageLabel)\n if (matchingStages.length === 1) return matchingStages[0] ?? null\n if (matchingStages.length > 1) {\n const distinctPipelineIds = new Set(matchingStages.map((stage) => stage.pipelineId))\n if (distinctPipelineIds.size === 1) return matchingStages[0] ?? null\n }\n return null\n}\n\nconst DEAL_DETAIL_CACHE_RESOURCE = 'customers.deal'\n// Every entity the detail payload reads, expressed with the same resourceKind\n// strings the customers commands declare. `expandResourceAliases` canonicalizes\n// them exactly the way `invalidateCrudCache` does on the write side (camelCase is\n// split, `_`/`-` become `.`), so `customers.pipelineStage` resolves to\n// `customers.pipeline.stage` and `customers.dictionary_entry` to\n// `customers.dictionary.entry`. That guarantees the collection tags stored here\n// match the ones the deal/pipeline/stage/dictionary commands flush.\nconst DEAL_DETAIL_CACHE_TAG_RESOURCES = [\n 'customers.pipeline',\n 'customers.pipelineStage',\n 'customers.dictionary_entry',\n]\nconst DEAL_DETAIL_CACHE_TTL_MS = 60_000\n\ntype DealDetailCacheAuth = {\n isSuperAdmin?: boolean | null\n orgId?: string | null\n}\n\n// Capture every input `isOrganizationReadAccessAllowed` consults, so two requests\n// that share a signature also share an authorization outcome for the same deal.\n// The lookup runs after the access check, so this is defense in depth plus the\n// per-scope cache partitioning the issue asks for.\nfunction buildDealDetailScopeSignature(\n auth: DealDetailCacheAuth,\n scope: Pick<OrganizationScope, 'allowedIds' | 'filterIds'> | null | undefined,\n): string {\n if (auth?.isSuperAdmin === true) return 'super'\n if (scope?.allowedIds === null) return 'all'\n const filterIds = Array.isArray(scope?.filterIds)\n ? scope!.filterIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : []\n if (filterIds.length) return `filter:${[...filterIds].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',')}`\n return `home:${auth?.orgId ?? 'null'}`\n}\n\nfunction buildDealDetailCacheKey(params: {\n tenantId: string | null\n organizationId: string | null\n dealId: string\n scopeSignature: string\n view: 'full' | 'lite'\n includeKey: string\n}): string {\n return [\n 'customers:deal:detail',\n `tenant=${params.tenantId ?? 'null'}`,\n `org=${params.organizationId ?? 'null'}`,\n `deal=${params.dealId}`,\n `scope=${params.scopeSignature}`,\n `view=${params.view}`,\n `include=${params.includeKey}`,\n ].join(':')\n}\n\nfunction buildDealDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {\n const tags = new Set<string>()\n for (const key of expandResourceAliases(DEAL_DETAIL_CACHE_RESOURCE, DEAL_DETAIL_CACHE_TAG_RESOURCES)) {\n for (const tag of buildCollectionTags(key, tenantId, [organizationId])) {\n tags.add(tag)\n }\n }\n return Array.from(tags)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound('Deal not found')\n }\n\n const includeFlags = readIncludeFlags(request)\n const viewMode = readViewMode(request)\n const liteView = viewMode === 'lite'\n const includeStages = includeFlags.has('stages')\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: 'Authentication required' }, { status: 401 })\n }\n\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n if (!rbac || !auth?.sub) {\n return forbidden('Access denied')\n }\n const hasFeature = await rbac.userHasAllFeatures(auth.sub, ['customers.deals.view'], {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n })\n if (!hasFeature) {\n return forbidden('Access denied')\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, deletedAt: null },\n {\n populate: ['people.person', 'people.person.personProfile', 'companies.company', 'companies.company.companyProfile'],\n },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound('Deal not found')\n }\n\n if (auth.tenantId && deal.tenantId && auth.tenantId !== deal.tenantId) {\n return notFound('Deal not found')\n }\n\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n return forbidden('Access denied')\n }\n\n const decryptionScope = {\n tenantId: deal.tenantId ?? auth.tenantId ?? null,\n organizationId: deal.organizationId ?? auth.orgId ?? null,\n }\n\n const viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n const resolveViewer = async (): Promise<{ name: string | null; email: string | null }> => {\n if (!viewerUserId) {\n return { name: null, email: auth.email ?? null }\n }\n const viewer = await findOneWithDecryption(\n em,\n User,\n { id: viewerUserId, tenantId: auth.tenantId ?? null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n return {\n name: viewer?.name ?? null,\n email: viewer?.email ?? auth.email ?? null,\n }\n }\n\n // Opt-in detail cache (ENABLE_CRUD_API_CACHE). The lookup runs after the deal is\n // resolved and the tenant + organization read-access checks pass, so the cache\n // only ever memoizes a payload this caller is already authorized to see; it then\n // skips the heavy association / custom-field / pipeline-stage / dictionary /\n // audit-fallback sweeps below. The viewer block is request-specific, so it is\n // excluded from the cached value and always resolved fresh.\n const cacheTenantId = deal.tenantId ?? auth.tenantId ?? null\n const cacheOrganizationId = deal.organizationId ?? null\n const detailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null\n const includeKey = includeFlags.size ? Array.from(includeFlags).sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',') : 'none'\n const detailCacheKey = detailCache\n ? buildDealDetailCacheKey({\n tenantId: cacheTenantId,\n organizationId: cacheOrganizationId,\n dealId: deal.id,\n scopeSignature: buildDealDetailScopeSignature(auth, scope),\n view: viewMode,\n includeKey,\n })\n : null\n\n if (detailCache && detailCacheKey) {\n try {\n const cached = await runWithCacheTenant(cacheTenantId, () => detailCache.get(detailCacheKey))\n if (cached && typeof cached === 'object') {\n const viewerInfo = await resolveViewer()\n return NextResponse.json({\n ...(cached as Record<string, unknown>),\n viewer: {\n userId: viewerUserId,\n name: viewerInfo.name,\n email: viewerInfo.email,\n },\n })\n }\n } catch (err) {\n // A cache-backend read error must degrade to a fresh DB read, never a 500.\n debugCrudCache('get', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n // Issue #3175: the reads below only depend on the already-resolved deal +\n // organization scope + decryption scope, so they are grouped into Promise.all\n // batches instead of being awaited one after another. True dependencies stay\n // ordered: preview/decrypt run after their link rows, pipeline reads after the\n // effective stage is known, and the appearance map / audit fallback after the\n // pipeline stages load. This mirrors the parallel-read pattern already used\n // across the customers list/interactions/activities routes.\n const resolveAssociations = async (): Promise<{\n people: DealAssociation[]\n companies: DealAssociation[]\n linkedPersonIds: string[]\n linkedCompanyIds: string[]\n }> => {\n if (liteView) {\n const [personLinkRows, companyLinkRows] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n ])\n\n const linkedPersonIds = Array.from(\n new Set(\n personLinkRows\n .map((link) => {\n const personRef = link.person\n if (!personRef) return null\n if (typeof personRef === 'string') return personRef\n const personIdValue = personRef.id\n return typeof personIdValue === 'string' ? personIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n const linkedCompanyIds = Array.from(\n new Set(\n companyLinkRows\n .map((link) => {\n const companyRef = link.company\n if (!companyRef) return null\n if (typeof companyRef === 'string') return companyRef\n const companyIdValue = companyRef.id\n return typeof companyIdValue === 'string' ? companyIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n\n const [previewPeople, previewCompanies] = await Promise.all([\n linkedPersonIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedPersonIds.slice(0, 3) } },\n { populate: ['personProfile'] },\n decryptionScope,\n )\n : [],\n linkedCompanyIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedCompanyIds.slice(0, 3) } },\n { populate: ['companyProfile'] },\n decryptionScope,\n )\n : [],\n ])\n const primaryPersonIds = new Set(\n personLinkRows\n .filter((link) => link.isPrimary === true)\n .map((link) => (typeof link.person === 'string' ? link.person : link.person?.id))\n .filter((value): value is string => typeof value === 'string'),\n )\n const previewPeopleMap = new Map(previewPeople.map((entity) => [entity.id, entity]))\n const previewCompaniesMap = new Map(previewCompanies.map((entity) => [entity.id, entity]))\n const people = linkedPersonIds.slice(0, 3).reduce<DealAssociation[]>((acc, personId) => {\n const entity = previewPeopleMap.get(personId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: primaryPersonIds.has(entity.id) })\n return acc\n }, [])\n const companies = linkedCompanyIds.slice(0, 3).reduce<DealAssociation[]>((acc, companyId) => {\n const entity = previewCompaniesMap.get(companyId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return { people, companies, linkedPersonIds, linkedCompanyIds }\n }\n\n const [personLinks, companyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { populate: ['person', 'person.personProfile'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { populate: ['company', 'company.companyProfile'] },\n decryptionScope,\n ),\n ])\n const fallbackTenantId = deal.tenantId ?? auth.tenantId ?? null\n const fallbackOrgId = deal.organizationId ?? auth.orgId ?? null\n await decryptEntitiesWithFallbackScope(personLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n await decryptEntitiesWithFallbackScope(companyLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n\n const people = personLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.person as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: link.isPrimary === true })\n return acc\n }, [])\n\n const companies = companyLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.company as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return {\n people,\n companies,\n linkedPersonIds: people.map((entry) => entry.id),\n linkedCompanyIds: companies.map((entry) => entry.id),\n }\n }\n\n const resolveOwner = async () =>\n deal.ownerUserId\n ? await findOneWithDecryption(\n em,\n User,\n { id: deal.ownerUserId, tenantId: deal.tenantId ?? auth.tenantId ?? null },\n {},\n decryptionScope,\n )\n : null\n\n const resolveStageTransitions = async (): Promise<CustomerDealStageTransition[]> => {\n if (!includeStages) return []\n try {\n return await findWithDecryption(\n em,\n CustomerDealStageTransition,\n { deal: deal.id, deletedAt: null },\n { orderBy: { stageOrder: 'ASC', transitionedAt: 'ASC' } },\n decryptionScope,\n )\n } catch (error) {\n if (!isMissingDealStageTransitionTable(error)) {\n throw error\n }\n warnMissingDealStageTransitionTable('customers.api.deals.detail.GET')\n return []\n }\n }\n\n const [associations, customFieldValues, viewerInfo, owner, effectiveStage, stageTransitions] = await Promise.all([\n resolveAssociations(),\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_deal,\n recordIds: [deal.id],\n tenantIdByRecord: { [deal.id]: deal.tenantId ?? null },\n organizationIdByRecord: { [deal.id]: deal.organizationId ?? null },\n tenantFallbacks: [deal.tenantId ?? auth.tenantId ?? null].filter((value): value is string => !!value),\n }),\n resolveViewer(),\n resolveOwner(),\n includeStages ? resolveEffectivePipelineStage(em, deal, decryptionScope) : Promise.resolve(null),\n resolveStageTransitions(),\n ])\n\n const { people, companies, linkedPersonIds, linkedCompanyIds } = associations\n const customFields = normalizeCustomFieldResponse(customFieldValues[deal.id]) ?? {}\n const viewerName = viewerInfo.name\n const viewerEmail = viewerInfo.email\n const ownerPayload = owner\n ? {\n id: owner.id,\n name: owner.name ?? owner.email ?? owner.id,\n email: owner.email ?? '',\n }\n : null\n\n const effectivePipelineId = deal.pipelineId ?? effectiveStage?.pipelineId ?? null\n const effectivePipelineStageId = deal.pipelineStageId ?? effectiveStage?.id ?? null\n const effectivePipelineStageLabel = deal.pipelineStage ?? effectiveStage?.label ?? null\n\n const [pipelineStages, pipeline] = await Promise.all([\n includeStages && effectivePipelineId\n ? findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n pipelineId: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n : [],\n effectivePipelineId\n ? findOneWithDecryption(\n em,\n CustomerPipeline,\n {\n id: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n : null,\n ])\n\n const persistedStageTransitions = stageTransitions.map((transition) => ({\n stageId: transition.stageId,\n stageLabel: transition.stageLabel,\n stageOrder: transition.stageOrder,\n transitionedAt: transition.transitionedAt.toISOString(),\n }))\n const [pipelineStageAppearanceMap, recoveredStageTransitions] = await Promise.all([\n pipelineStages.length\n ? loadPipelineStageAppearanceMap(em, pipelineStages, deal.organizationId, deal.tenantId)\n : new Map<string, CustomerDictionaryEntry>(),\n includeStages && persistedStageTransitions.length === 0\n ? loadAuditStageTransitionsFallback({ container, deal, pipelineStages })\n : [],\n ])\n const effectiveCurrentStage = (() => {\n if (!effectivePipelineStageId) return null\n const matchingStage = pipelineStages.find((stage) => stage.id === effectivePipelineStageId)\n if (matchingStage) {\n return {\n id: matchingStage.id,\n label: matchingStage.label,\n order: matchingStage.order,\n }\n }\n if (!effectivePipelineStageLabel) return null\n return {\n id: effectivePipelineStageId,\n label: effectivePipelineStageLabel,\n order: 0,\n }\n })()\n const stageTransitionPayload = mergeStageTransitions({\n persisted: persistedStageTransitions,\n recovered: recoveredStageTransitions,\n currentStage: effectiveCurrentStage,\n fallbackTimestamp: deal.createdAt.toISOString(),\n })\n\n const viewer = {\n userId: viewerUserId,\n name: viewerName,\n email: viewerEmail,\n }\n\n // Everything except the request-specific viewer block is shareable across\n // authorized callers, so it is what gets cached.\n const cacheableBody = {\n deal: {\n id: deal.id,\n title: deal.title,\n description: deal.description ?? null,\n status: deal.status ?? null,\n pipelineStage: effectivePipelineStageLabel,\n pipelineId: effectivePipelineId,\n pipelineStageId: effectivePipelineStageId,\n valueAmount: deal.valueAmount ?? null,\n valueCurrency: deal.valueCurrency ?? null,\n probability: deal.probability ?? null,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId ?? null,\n source: deal.source ?? null,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n organizationId: deal.organizationId ?? null,\n tenantId: deal.tenantId ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n },\n people,\n companies,\n linkedPersonIds,\n linkedCompanyIds,\n counts: {\n people: linkedPersonIds.length,\n companies: linkedCompanyIds.length,\n },\n customFields,\n pipelineStages: pipelineStages.map((stage) => {\n const appearance = pipelineStageAppearanceMap.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n label: stage.label,\n order: stage.order,\n color: appearance?.color ?? null,\n icon: appearance?.icon ?? null,\n }\n }),\n pipelineName: pipeline?.name ?? null,\n stageTransitions: stageTransitionPayload,\n owner: ownerPayload,\n }\n\n if (detailCache && detailCacheKey) {\n try {\n await runWithCacheTenant(cacheTenantId, () =>\n detailCache.set(detailCacheKey, cacheableBody, {\n ttl: DEAL_DETAIL_CACHE_TTL_MS,\n tags: buildDealDetailCacheTags(cacheTenantId, cacheOrganizationId),\n }),\n )\n } catch (err) {\n // A cache write must never break the request; log it for observability\n // instead of failing the response (matches the CRUD factory).\n debugCrudCache('store', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return NextResponse.json({ ...cacheableBody, viewer })\n}\n\nconst dealDetailQuerySchema = z.object({\n include: z.string().optional(),\n})\n\nconst pipelineStageInfoSchema = z.object({\n id: z.string().uuid(),\n label: z.string(),\n order: z.number().int(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n})\n\nconst stageTransitionInfoSchema = z.object({\n stageId: z.string().uuid(),\n stageLabel: z.string(),\n stageOrder: z.number().int(),\n transitionedAt: z.string(),\n})\n\nconst dealDetailResponseSchema = z.object({\n deal: z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n pipelineId: z.string().uuid().nullable().optional(),\n pipelineStageId: z.string().uuid().nullable().optional(),\n valueAmount: z.string().nullable().optional(),\n valueCurrency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expectedCloseAt: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n closureOutcome: z.enum(['won', 'lost']).nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n people: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('person'),\n isPrimary: z.boolean().optional(),\n }),\n ),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('company'),\n }),\n ),\n customFields: z.record(z.string(), z.unknown()),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n pipelineStages: z.array(pipelineStageInfoSchema),\n stageTransitions: z.array(stageTransitionInfoSchema),\n owner: z.object({\n id: z.string().uuid(),\n name: z.string(),\n email: z.string(),\n }).nullable(),\n})\n\nconst dealDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal detail',\n methods: {\n GET: {\n summary: 'Fetch deal with associations and pipeline context',\n description: 'Returns a deal with linked people, companies, closure fields, optional pipeline history, custom fields, and viewer context.',\n query: dealDetailQuerySchema,\n responses: [\n { status: 200, description: 'Deal detail payload', schema: dealDetailResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dealDetailErrorSchema },\n { status: 403, description: 'Forbidden for tenant/organization scope', schema: dealDetailErrorSchema },\n { status: 404, description: 'Deal not found', schema: dealDetailErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerDeal,\n CustomerDealPersonLink,\n CustomerDealCompanyLink,\n CustomerDealStageTransition,\n CustomerDictionaryEntry,\n CustomerEntity,\n CustomerPipeline,\n CustomerPipelineStage,\n} from '../../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { E } from '#generated/entities.ids.generated'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { denyCustomerDetailReadAsNotFound } from '../../../lib/detailReadAccess'\nimport { decryptEntitiesWithFallbackScope } from '@open-mercato/shared/lib/encryption/subscriber'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n expandResourceAliases,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { isMissingDealStageTransitionTable, warnMissingDealStageTransitionTable } from '../../../lib/dealStageTransitionTable'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\ntype DealAssociation = {\n id: string\n label: string\n subtitle: string | null\n kind: 'person' | 'company'\n isPrimary?: boolean\n}\n\nfunction normalizePersonAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const email =\n typeof entity.primaryEmail === 'string' && entity.primaryEmail.trim().length\n ? entity.primaryEmail.trim()\n : null\n const phone =\n typeof entity.primaryPhone === 'string' && entity.primaryPhone.trim().length\n ? entity.primaryPhone.trim()\n : null\n const jobTitle =\n entity.personProfile &&\n typeof (entity.personProfile as { jobTitle?: string | null })?.jobTitle === 'string' &&\n (entity.personProfile as { jobTitle?: string | null }).jobTitle?.trim().length\n ? ((entity.personProfile as { jobTitle?: string | null }).jobTitle as string).trim()\n : null\n const subtitle = jobTitle ?? email ?? phone ?? null\n const label = displayName.length ? displayName : email ?? phone ?? entity.id\n return { label, subtitle }\n}\n\nfunction normalizeCompanyAssociation(entity: CustomerEntity): { label: string; subtitle: string | null } {\n const displayName = typeof entity.displayName === 'string' ? entity.displayName.trim() : ''\n const domain =\n entity.companyProfile &&\n typeof (entity.companyProfile as { domain?: string | null })?.domain === 'string' &&\n (entity.companyProfile as { domain?: string | null }).domain?.trim().length\n ? ((entity.companyProfile as { domain?: string | null }).domain as string).trim()\n : null\n const website =\n entity.companyProfile &&\n typeof (entity.companyProfile as { websiteUrl?: string | null })?.websiteUrl === 'string' &&\n (entity.companyProfile as { websiteUrl?: string | null }).websiteUrl?.trim().length\n ? ((entity.companyProfile as { websiteUrl?: string | null }).websiteUrl as string).trim()\n : null\n const subtitle = domain ?? website ?? null\n const label = displayName.length ? displayName : domain ?? website ?? entity.id\n return { label, subtitle }\n}\n\nfunction readIncludeFlags(request: Request): Set<string> {\n const flags = new Set<string>()\n const url = new URL(request.url)\n for (const rawValue of url.searchParams.getAll('include')) {\n rawValue\n .split(',')\n .map((value) => value.trim().toLowerCase())\n .filter(Boolean)\n .forEach((value) => flags.add(value))\n }\n return flags\n}\n\nfunction readViewMode(request: Request): 'full' | 'lite' {\n const raw = new URL(request.url).searchParams.get('view')\n return raw === 'lite' || raw === 'detail-lite' ? 'lite' : 'full'\n}\n\nfunction normalizeStageLabel(value: string | null | undefined): string {\n return typeof value === 'string' ? value.trim().toLowerCase() : ''\n}\n\ntype StageTransitionPayload = {\n stageId: string\n stageLabel: string\n stageOrder: number\n transitionedAt: string\n}\n\ntype DealSnapshotStageInfo = {\n pipelineId: string | null\n stageId: string | null\n stageLabel: string | null\n}\n\nfunction asObject(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null\n}\n\nfunction readRecordString(record: Record<string, unknown> | null, ...keys: string[]): string | null {\n if (!record) return null\n for (const key of keys) {\n const value = record[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return value.trim()\n }\n }\n return null\n}\n\nfunction readSnapshotDealRecord(snapshot: unknown): Record<string, unknown> | null {\n const root = asObject(snapshot)\n if (!root) return null\n return asObject(root.deal) ?? root\n}\n\nfunction readSnapshotStageInfo(snapshot: unknown): DealSnapshotStageInfo {\n const dealRecord = readSnapshotDealRecord(snapshot)\n return {\n pipelineId: readRecordString(dealRecord, 'pipelineId', 'pipeline_id'),\n stageId: readRecordString(dealRecord, 'pipelineStageId', 'pipeline_stage_id'),\n stageLabel: readRecordString(dealRecord, 'pipelineStage', 'pipeline_stage'),\n }\n}\n\nasync function loadAuditStageTransitionsFallback({\n container,\n deal,\n pipelineStages,\n}: {\n container: Awaited<ReturnType<typeof createRequestContainer>>\n deal: CustomerDeal\n pipelineStages: CustomerPipelineStage[]\n}): Promise<StageTransitionPayload[]> {\n if (!deal.tenantId || !deal.organizationId || !pipelineStages.length) return []\n\n let actionLogs: ActionLogService | null = null\n try {\n actionLogs = container.resolve('actionLogService') as ActionLogService\n } catch {\n return []\n }\n if (!actionLogs || typeof actionLogs.list !== 'function') return []\n const stageOrderById = new Map(pipelineStages.map((stage) => [stage.id, stage.order]))\n const stageLabelById = new Map(pipelineStages.map((stage) => [stage.id, stage.label]))\n const transitionsByStageId = new Map<string, StageTransitionPayload>()\n const logsResult = await actionLogs.list({\n tenantId: deal.tenantId,\n organizationId: deal.organizationId,\n resourceKind: 'customers.deal',\n resourceId: deal.id,\n limit: 200,\n offset: 0,\n sortField: 'createdAt',\n sortDir: 'asc',\n }).catch(() => null)\n const logs = logsResult?.items ?? []\n\n let previousStageId: string | null = null\n for (const log of logs) {\n if (log.executionState === 'failed' || log.executionState === 'undone') continue\n\n const before = readSnapshotStageInfo(log.snapshotBefore)\n const after = readSnapshotStageInfo(log.snapshotAfter)\n const nextStageId = after.stageId\n if (!nextStageId) continue\n\n const stageOrder = stageOrderById.get(nextStageId)\n if (typeof stageOrder !== 'number') {\n previousStageId = nextStageId\n continue\n }\n\n const effectivePreviousStageId: string | null = before.stageId ?? previousStageId\n if (effectivePreviousStageId === nextStageId && transitionsByStageId.has(nextStageId)) {\n previousStageId = nextStageId\n continue\n }\n\n transitionsByStageId.set(nextStageId, {\n stageId: nextStageId,\n stageLabel: after.stageLabel ?? stageLabelById.get(nextStageId) ?? nextStageId,\n stageOrder,\n transitionedAt: log.createdAt.toISOString(),\n })\n previousStageId = nextStageId\n }\n\n return Array.from(transitionsByStageId.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nfunction mergeStageTransitions({\n persisted,\n recovered,\n currentStage,\n fallbackTimestamp,\n}: {\n persisted: StageTransitionPayload[]\n recovered: StageTransitionPayload[]\n currentStage: { id: string; label: string; order: number } | null\n fallbackTimestamp: string\n}): StageTransitionPayload[] {\n const merged = new Map<string, StageTransitionPayload>()\n for (const transition of persisted) {\n merged.set(transition.stageId, transition)\n }\n for (const transition of recovered) {\n if (!merged.has(transition.stageId)) {\n merged.set(transition.stageId, transition)\n }\n }\n if (currentStage && !merged.has(currentStage.id)) {\n merged.set(currentStage.id, {\n stageId: currentStage.id,\n stageLabel: currentStage.label,\n stageOrder: currentStage.order,\n transitionedAt: fallbackTimestamp,\n })\n }\n return Array.from(merged.values()).sort((left, right) => left.stageOrder - right.stageOrder)\n}\n\nasync function loadPipelineStageAppearanceMap(\n em: EntityManager,\n stages: CustomerPipelineStage[],\n organizationId: string,\n tenantId: string,\n): Promise<Map<string, CustomerDictionaryEntry>> {\n const normalizedValues = stages\n .map((stage) => stage.label.trim().toLowerCase())\n .filter((value) => value.length > 0)\n if (!normalizedValues.length) return new Map<string, CustomerDictionaryEntry>()\n const entries = await findWithDecryption(\n em,\n CustomerDictionaryEntry,\n {\n organizationId,\n tenantId,\n kind: 'pipeline_stage',\n normalizedValue: { $in: normalizedValues },\n },\n undefined,\n { tenantId, organizationId },\n )\n const map = new Map<string, CustomerDictionaryEntry>()\n entries.forEach((entry) => map.set(entry.normalizedValue, entry))\n return map\n}\n\nasync function resolveEffectivePipelineStage(\n em: EntityManager,\n deal: CustomerDeal,\n decryptionScope: { tenantId: string | null; organizationId: string | null },\n): Promise<CustomerPipelineStage | null> {\n if (deal.pipelineStageId) {\n const exactStage = await findOneWithDecryption(\n em,\n CustomerPipelineStage,\n {\n id: deal.pipelineStageId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n if (exactStage) return exactStage\n }\n\n const normalizedStageLabel = normalizeStageLabel(deal.pipelineStage)\n if (!normalizedStageLabel) return null\n\n const scopedStages = await findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n ...(deal.pipelineId ? { pipelineId: deal.pipelineId } : {}),\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n\n const matchingStages = scopedStages.filter((stage) => normalizeStageLabel(stage.label) === normalizedStageLabel)\n if (matchingStages.length === 1) return matchingStages[0] ?? null\n if (matchingStages.length > 1) {\n const distinctPipelineIds = new Set(matchingStages.map((stage) => stage.pipelineId))\n if (distinctPipelineIds.size === 1) return matchingStages[0] ?? null\n }\n return null\n}\n\nconst DEAL_DETAIL_CACHE_RESOURCE = 'customers.deal'\n// Every entity the detail payload reads, expressed with the same resourceKind\n// strings the customers commands declare. `expandResourceAliases` canonicalizes\n// them exactly the way `invalidateCrudCache` does on the write side (camelCase is\n// split, `_`/`-` become `.`), so `customers.pipelineStage` resolves to\n// `customers.pipeline.stage` and `customers.dictionary_entry` to\n// `customers.dictionary.entry`. That guarantees the collection tags stored here\n// match the ones the deal/pipeline/stage/dictionary commands flush.\nconst DEAL_DETAIL_CACHE_TAG_RESOURCES = [\n 'customers.pipeline',\n 'customers.pipelineStage',\n 'customers.dictionary_entry',\n]\nconst DEAL_DETAIL_CACHE_TTL_MS = 60_000\n\ntype DealDetailCacheAuth = {\n isSuperAdmin?: boolean | null\n orgId?: string | null\n}\n\n// Capture every input `isOrganizationReadAccessAllowed` consults, so two requests\n// that share a signature also share an authorization outcome for the same deal.\n// The lookup runs after the access check, so this is defense in depth plus the\n// per-scope cache partitioning the issue asks for.\nfunction buildDealDetailScopeSignature(\n auth: DealDetailCacheAuth,\n scope: Pick<OrganizationScope, 'allowedIds' | 'filterIds'> | null | undefined,\n): string {\n if (auth?.isSuperAdmin === true) return 'super'\n if (scope?.allowedIds === null) return 'all'\n const filterIds = Array.isArray(scope?.filterIds)\n ? scope!.filterIds.filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n : []\n if (filterIds.length) return `filter:${[...filterIds].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',')}`\n return `home:${auth?.orgId ?? 'null'}`\n}\n\nfunction buildDealDetailCacheKey(params: {\n tenantId: string | null\n organizationId: string | null\n dealId: string\n scopeSignature: string\n view: 'full' | 'lite'\n includeKey: string\n}): string {\n return [\n 'customers:deal:detail',\n `tenant=${params.tenantId ?? 'null'}`,\n `org=${params.organizationId ?? 'null'}`,\n `deal=${params.dealId}`,\n `scope=${params.scopeSignature}`,\n `view=${params.view}`,\n `include=${params.includeKey}`,\n ].join(':')\n}\n\nfunction buildDealDetailCacheTags(tenantId: string | null, organizationId: string | null): string[] {\n const tags = new Set<string>()\n for (const key of expandResourceAliases(DEAL_DETAIL_CACHE_RESOURCE, DEAL_DETAIL_CACHE_TAG_RESOURCES)) {\n for (const tag of buildCollectionTags(key, tenantId, [organizationId])) {\n tags.add(tag)\n }\n }\n return Array.from(tags)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound('Deal not found')\n }\n\n const includeFlags = readIncludeFlags(request)\n const viewMode = readViewMode(request)\n const liteView = viewMode === 'lite'\n const includeStages = includeFlags.has('stages')\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: 'Authentication required' }, { status: 401 })\n }\n\n // The API dispatcher already enforces `requireFeatures: ['customers.deals.view']`\n // against the caller's effective (selected) organization before this handler\n // runs, so no in-route feature re-check is needed. Re-checking here against the\n // token org (auth.orgId) would wrongly 403 org-switched callers (#5012).\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, deletedAt: null },\n {\n populate: ['people.person', 'people.person.personProfile', 'companies.company', 'companies.company.companyProfile'],\n },\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound('Deal not found')\n }\n\n if (auth.tenantId && deal.tenantId && auth.tenantId !== deal.tenantId) {\n return notFound('Deal not found')\n }\n\n // Existence oracle (issue #5504): a caller who holds customers.deals.view but\n // whose scope excludes the record's organization must get the SAME response as\n // for a non-existent id, so 403-when-present / 404-when-absent collapses to a\n // uniform 404 not-found. The dispatcher already returns a uniform 403 for\n // callers who lack the feature entirely.\n const organizationReadDenied = denyCustomerDetailReadAsNotFound(\n { scope, auth, organizationId: deal.organizationId },\n 'Deal not found',\n )\n if (organizationReadDenied) return organizationReadDenied\n\n const decryptionScope = {\n tenantId: deal.tenantId ?? auth.tenantId ?? null,\n organizationId: deal.organizationId ?? auth.orgId ?? null,\n }\n\n const viewerUserId = auth.isApiKey ? null : auth.sub ?? null\n const resolveViewer = async (): Promise<{ name: string | null; email: string | null }> => {\n if (!viewerUserId) {\n return { name: null, email: auth.email ?? null }\n }\n const viewer = await findOneWithDecryption(\n em,\n User,\n { id: viewerUserId, tenantId: auth.tenantId ?? null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n return {\n name: viewer?.name ?? null,\n email: viewer?.email ?? auth.email ?? null,\n }\n }\n\n // Opt-in detail cache (ENABLE_CRUD_API_CACHE). The lookup runs after the deal is\n // resolved and the tenant + organization read-access checks pass, so the cache\n // only ever memoizes a payload this caller is already authorized to see; it then\n // skips the heavy association / custom-field / pipeline-stage / dictionary /\n // audit-fallback sweeps below. The viewer block is request-specific, so it is\n // excluded from the cached value and always resolved fresh.\n const cacheTenantId = deal.tenantId ?? auth.tenantId ?? null\n const cacheOrganizationId = deal.organizationId ?? null\n const detailCache = isCrudCacheEnabled() ? resolveCrudCache(container) : null\n const includeKey = includeFlags.size ? Array.from(includeFlags).sort((left, right) => (left < right ? -1 : left > right ? 1 : 0)).join(',') : 'none'\n const detailCacheKey = detailCache\n ? buildDealDetailCacheKey({\n tenantId: cacheTenantId,\n organizationId: cacheOrganizationId,\n dealId: deal.id,\n scopeSignature: buildDealDetailScopeSignature(auth, scope),\n view: viewMode,\n includeKey,\n })\n : null\n\n if (detailCache && detailCacheKey) {\n try {\n const cached = await runWithCacheTenant(cacheTenantId, () => detailCache.get(detailCacheKey))\n if (cached && typeof cached === 'object') {\n const viewerInfo = await resolveViewer()\n return NextResponse.json({\n ...(cached as Record<string, unknown>),\n viewer: {\n userId: viewerUserId,\n name: viewerInfo.name,\n email: viewerInfo.email,\n },\n })\n }\n } catch (err) {\n // A cache-backend read error must degrade to a fresh DB read, never a 500.\n debugCrudCache('get', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n // Issue #3175: the reads below only depend on the already-resolved deal +\n // organization scope + decryption scope, so they are grouped into Promise.all\n // batches instead of being awaited one after another. True dependencies stay\n // ordered: preview/decrypt run after their link rows, pipeline reads after the\n // effective stage is known, and the appearance map / audit fallback after the\n // pipeline stages load. This mirrors the parallel-read pattern already used\n // across the customers list/interactions/activities routes.\n const resolveAssociations = async (): Promise<{\n people: DealAssociation[]\n companies: DealAssociation[]\n linkedPersonIds: string[]\n linkedCompanyIds: string[]\n }> => {\n if (liteView) {\n const [personLinkRows, companyLinkRows] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { orderBy: { createdAt: 'ASC' } },\n decryptionScope,\n ),\n ])\n\n const linkedPersonIds = Array.from(\n new Set(\n personLinkRows\n .map((link) => {\n const personRef = link.person\n if (!personRef) return null\n if (typeof personRef === 'string') return personRef\n const personIdValue = personRef.id\n return typeof personIdValue === 'string' ? personIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n const linkedCompanyIds = Array.from(\n new Set(\n companyLinkRows\n .map((link) => {\n const companyRef = link.company\n if (!companyRef) return null\n if (typeof companyRef === 'string') return companyRef\n const companyIdValue = companyRef.id\n return typeof companyIdValue === 'string' ? companyIdValue : null\n })\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0),\n ),\n )\n\n const [previewPeople, previewCompanies] = await Promise.all([\n linkedPersonIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedPersonIds.slice(0, 3) } },\n { populate: ['personProfile'] },\n decryptionScope,\n )\n : [],\n linkedCompanyIds.length\n ? findWithDecryption(\n em,\n CustomerEntity,\n { id: { $in: linkedCompanyIds.slice(0, 3) } },\n { populate: ['companyProfile'] },\n decryptionScope,\n )\n : [],\n ])\n const primaryPersonIds = new Set(\n personLinkRows\n .filter((link) => link.isPrimary === true)\n .map((link) => (typeof link.person === 'string' ? link.person : link.person?.id))\n .filter((value): value is string => typeof value === 'string'),\n )\n const previewPeopleMap = new Map(previewPeople.map((entity) => [entity.id, entity]))\n const previewCompaniesMap = new Map(previewCompanies.map((entity) => [entity.id, entity]))\n const people = linkedPersonIds.slice(0, 3).reduce<DealAssociation[]>((acc, personId) => {\n const entity = previewPeopleMap.get(personId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: primaryPersonIds.has(entity.id) })\n return acc\n }, [])\n const companies = linkedCompanyIds.slice(0, 3).reduce<DealAssociation[]>((acc, companyId) => {\n const entity = previewCompaniesMap.get(companyId) ?? null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return { people, companies, linkedPersonIds, linkedCompanyIds }\n }\n\n const [personLinks, companyLinks] = await Promise.all([\n findWithDecryption(\n em,\n CustomerDealPersonLink,\n { deal: deal.id },\n { populate: ['person', 'person.personProfile'] },\n decryptionScope,\n ),\n findWithDecryption(\n em,\n CustomerDealCompanyLink,\n { deal: deal.id },\n { populate: ['company', 'company.companyProfile'] },\n decryptionScope,\n ),\n ])\n const fallbackTenantId = deal.tenantId ?? auth.tenantId ?? null\n const fallbackOrgId = deal.organizationId ?? auth.orgId ?? null\n await decryptEntitiesWithFallbackScope(personLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n await decryptEntitiesWithFallbackScope(companyLinks, {\n em,\n tenantId: fallbackTenantId,\n organizationId: fallbackOrgId,\n })\n\n const people = personLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.person as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizePersonAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'person', isPrimary: link.isPrimary === true })\n return acc\n }, [])\n\n const companies = companyLinks.reduce<DealAssociation[]>((acc, link) => {\n const entity = link.company as CustomerEntity | null\n if (!entity || entity.deletedAt) return acc\n const { label, subtitle } = normalizeCompanyAssociation(entity)\n acc.push({ id: entity.id, label, subtitle, kind: 'company' })\n return acc\n }, [])\n return {\n people,\n companies,\n linkedPersonIds: people.map((entry) => entry.id),\n linkedCompanyIds: companies.map((entry) => entry.id),\n }\n }\n\n const resolveOwner = async () =>\n deal.ownerUserId\n ? await findOneWithDecryption(\n em,\n User,\n { id: deal.ownerUserId, tenantId: deal.tenantId ?? auth.tenantId ?? null },\n {},\n decryptionScope,\n )\n : null\n\n const resolveStageTransitions = async (): Promise<CustomerDealStageTransition[]> => {\n if (!includeStages) return []\n try {\n return await findWithDecryption(\n em,\n CustomerDealStageTransition,\n { deal: deal.id, deletedAt: null },\n { orderBy: { stageOrder: 'ASC', transitionedAt: 'ASC' } },\n decryptionScope,\n )\n } catch (error) {\n if (!isMissingDealStageTransitionTable(error)) {\n throw error\n }\n warnMissingDealStageTransitionTable('customers.api.deals.detail.GET')\n return []\n }\n }\n\n const [associations, customFieldValues, viewerInfo, owner, effectiveStage, stageTransitions] = await Promise.all([\n resolveAssociations(),\n loadCustomFieldValues({\n em,\n entityId: E.customers.customer_deal,\n recordIds: [deal.id],\n tenantIdByRecord: { [deal.id]: deal.tenantId ?? null },\n organizationIdByRecord: { [deal.id]: deal.organizationId ?? null },\n tenantFallbacks: [deal.tenantId ?? auth.tenantId ?? null].filter((value): value is string => !!value),\n }),\n resolveViewer(),\n resolveOwner(),\n includeStages ? resolveEffectivePipelineStage(em, deal, decryptionScope) : Promise.resolve(null),\n resolveStageTransitions(),\n ])\n\n const { people, companies, linkedPersonIds, linkedCompanyIds } = associations\n const customFields = normalizeCustomFieldResponse(customFieldValues[deal.id]) ?? {}\n const viewerName = viewerInfo.name\n const viewerEmail = viewerInfo.email\n const ownerPayload = owner\n ? {\n id: owner.id,\n name: owner.name ?? owner.email ?? owner.id,\n email: owner.email ?? '',\n }\n : null\n\n const effectivePipelineId = deal.pipelineId ?? effectiveStage?.pipelineId ?? null\n const effectivePipelineStageId = deal.pipelineStageId ?? effectiveStage?.id ?? null\n const effectivePipelineStageLabel = deal.pipelineStage ?? effectiveStage?.label ?? null\n\n const [pipelineStages, pipeline] = await Promise.all([\n includeStages && effectivePipelineId\n ? findWithDecryption(\n em,\n CustomerPipelineStage,\n {\n pipelineId: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { orderBy: { order: 'ASC' } },\n decryptionScope,\n )\n : [],\n effectivePipelineId\n ? findOneWithDecryption(\n em,\n CustomerPipeline,\n {\n id: effectivePipelineId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n {},\n decryptionScope,\n )\n : null,\n ])\n\n const persistedStageTransitions = stageTransitions.map((transition) => ({\n stageId: transition.stageId,\n stageLabel: transition.stageLabel,\n stageOrder: transition.stageOrder,\n transitionedAt: transition.transitionedAt.toISOString(),\n }))\n const [pipelineStageAppearanceMap, recoveredStageTransitions] = await Promise.all([\n pipelineStages.length\n ? loadPipelineStageAppearanceMap(em, pipelineStages, deal.organizationId, deal.tenantId)\n : new Map<string, CustomerDictionaryEntry>(),\n includeStages && persistedStageTransitions.length === 0\n ? loadAuditStageTransitionsFallback({ container, deal, pipelineStages })\n : [],\n ])\n const effectiveCurrentStage = (() => {\n if (!effectivePipelineStageId) return null\n const matchingStage = pipelineStages.find((stage) => stage.id === effectivePipelineStageId)\n if (matchingStage) {\n return {\n id: matchingStage.id,\n label: matchingStage.label,\n order: matchingStage.order,\n }\n }\n if (!effectivePipelineStageLabel) return null\n return {\n id: effectivePipelineStageId,\n label: effectivePipelineStageLabel,\n order: 0,\n }\n })()\n const stageTransitionPayload = mergeStageTransitions({\n persisted: persistedStageTransitions,\n recovered: recoveredStageTransitions,\n currentStage: effectiveCurrentStage,\n fallbackTimestamp: deal.createdAt.toISOString(),\n })\n\n const viewer = {\n userId: viewerUserId,\n name: viewerName,\n email: viewerEmail,\n }\n\n // Everything except the request-specific viewer block is shareable across\n // authorized callers, so it is what gets cached.\n const cacheableBody = {\n deal: {\n id: deal.id,\n title: deal.title,\n description: deal.description ?? null,\n status: deal.status ?? null,\n pipelineStage: effectivePipelineStageLabel,\n pipelineId: effectivePipelineId,\n pipelineStageId: effectivePipelineStageId,\n valueAmount: deal.valueAmount ?? null,\n valueCurrency: deal.valueCurrency ?? null,\n probability: deal.probability ?? null,\n expectedCloseAt: deal.expectedCloseAt ? deal.expectedCloseAt.toISOString() : null,\n ownerUserId: deal.ownerUserId ?? null,\n source: deal.source ?? null,\n closureOutcome: deal.closureOutcome ?? null,\n lossReasonId: deal.lossReasonId ?? null,\n lossNotes: deal.lossNotes ?? null,\n organizationId: deal.organizationId ?? null,\n tenantId: deal.tenantId ?? null,\n createdAt: deal.createdAt.toISOString(),\n updatedAt: deal.updatedAt.toISOString(),\n },\n people,\n companies,\n linkedPersonIds,\n linkedCompanyIds,\n counts: {\n people: linkedPersonIds.length,\n companies: linkedCompanyIds.length,\n },\n customFields,\n pipelineStages: pipelineStages.map((stage) => {\n const appearance = pipelineStageAppearanceMap.get(stage.label.trim().toLowerCase())\n return {\n id: stage.id,\n label: stage.label,\n order: stage.order,\n color: appearance?.color ?? null,\n icon: appearance?.icon ?? null,\n }\n }),\n pipelineName: pipeline?.name ?? null,\n stageTransitions: stageTransitionPayload,\n owner: ownerPayload,\n }\n\n if (detailCache && detailCacheKey) {\n try {\n await runWithCacheTenant(cacheTenantId, () =>\n detailCache.set(detailCacheKey, cacheableBody, {\n ttl: DEAL_DETAIL_CACHE_TTL_MS,\n tags: buildDealDetailCacheTags(cacheTenantId, cacheOrganizationId),\n }),\n )\n } catch (err) {\n // A cache write must never break the request; log it for observability\n // instead of failing the response (matches the CRUD factory).\n debugCrudCache('store', {\n resource: DEAL_DETAIL_CACHE_RESOURCE,\n key: detailCacheKey,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return NextResponse.json({ ...cacheableBody, viewer })\n}\n\nconst dealDetailQuerySchema = z.object({\n include: z.string().optional(),\n})\n\nconst pipelineStageInfoSchema = z.object({\n id: z.string().uuid(),\n label: z.string(),\n order: z.number().int(),\n color: z.string().nullable(),\n icon: z.string().nullable(),\n})\n\nconst stageTransitionInfoSchema = z.object({\n stageId: z.string().uuid(),\n stageLabel: z.string(),\n stageOrder: z.number().int(),\n transitionedAt: z.string(),\n})\n\nconst dealDetailResponseSchema = z.object({\n deal: z.object({\n id: z.string().uuid(),\n title: z.string().nullable().optional(),\n description: z.string().nullable().optional(),\n status: z.string().nullable().optional(),\n pipelineStage: z.string().nullable().optional(),\n pipelineId: z.string().uuid().nullable().optional(),\n pipelineStageId: z.string().uuid().nullable().optional(),\n valueAmount: z.string().nullable().optional(),\n valueCurrency: z.string().nullable().optional(),\n probability: z.number().nullable().optional(),\n expectedCloseAt: z.string().nullable().optional(),\n ownerUserId: z.string().uuid().nullable().optional(),\n source: z.string().nullable().optional(),\n closureOutcome: z.enum(['won', 'lost']).nullable().optional(),\n lossReasonId: z.string().uuid().nullable().optional(),\n lossNotes: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable().optional(),\n tenantId: z.string().uuid().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n people: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('person'),\n isPrimary: z.boolean().optional(),\n }),\n ),\n companies: z.array(\n z.object({\n id: z.string().uuid(),\n label: z.string(),\n subtitle: z.string().nullable().optional(),\n kind: z.literal('company'),\n }),\n ),\n customFields: z.record(z.string(), z.unknown()),\n viewer: z.object({\n userId: z.string().uuid().nullable(),\n name: z.string().nullable(),\n email: z.string().nullable(),\n }),\n pipelineStages: z.array(pipelineStageInfoSchema),\n stageTransitions: z.array(stageTransitionInfoSchema),\n owner: z.object({\n id: z.string().uuid(),\n name: z.string(),\n email: z.string(),\n }).nullable(),\n})\n\nconst dealDetailErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal detail',\n methods: {\n GET: {\n summary: 'Fetch deal with associations and pipeline context',\n description: 'Returns a deal with linked people, companies, closure fields, optional pipeline history, custom fields, and viewer context.',\n query: dealDetailQuerySchema,\n responses: [\n { status: 200, description: 'Deal detail payload', schema: dealDetailResponseSchema },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: dealDetailErrorSchema },\n { status: 403, description: 'Forbidden \u2014 caller lacks the required feature', schema: dealDetailErrorSchema },\n { status: 404, description: 'Deal not found, or its organization is not in the caller\u2019s scope', schema: dealDetailErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AAEnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY;AAErB,SAAS,6BAA6B;AACtC,SAAS,oCAAoC;AAC7C,SAAS,SAAS;AAElB,SAAS,oBAAoB,6BAA6B;AAC1D,SAAS,wCAAwC;AACjD,SAAS,wCAAwC;AACjD,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,mCAAmC,2CAA2C;AAEhF,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAED,SAAS,SAAS,SAAiB;AACjC,SAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9D;AAUA,SAAS,2BAA2B,QAAoE;AACtG,QAAM,cAAc,OAAO,OAAO,gBAAgB,WAAW,OAAO,YAAY,KAAK,IAAI;AACzF,QAAM,QACJ,OAAO,OAAO,iBAAiB,YAAY,OAAO,aAAa,KAAK,EAAE,SAClE,OAAO,aAAa,KAAK,IACzB;AACN,QAAM,QACJ,OAAO,OAAO,iBAAiB,YAAY,OAAO,aAAa,KAAK,EAAE,SAClE,OAAO,aAAa,KAAK,IACzB;AACN,QAAM,WACJ,OAAO,iBACP,OAAQ,OAAO,eAAgD,aAAa,YAC3E,OAAO,cAA+C,UAAU,KAAK,EAAE,SAClE,OAAO,cAA+C,SAAoB,KAAK,IACjF;AACN,QAAM,WAAW,YAAY,SAAS,SAAS;AAC/C,QAAM,QAAQ,YAAY,SAAS,cAAc,SAAS,SAAS,OAAO;AAC1E,SAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,SAAS,4BAA4B,QAAoE;AACvG,QAAM,cAAc,OAAO,OAAO,gBAAgB,WAAW,OAAO,YAAY,KAAK,IAAI;AACzF,QAAM,SACJ,OAAO,kBACP,OAAQ,OAAO,gBAA+C,WAAW,YACxE,OAAO,eAA8C,QAAQ,KAAK,EAAE,SAC/D,OAAO,eAA8C,OAAkB,KAAK,IAC9E;AACN,QAAM,UACJ,OAAO,kBACP,OAAQ,OAAO,gBAAmD,eAAe,YAChF,OAAO,eAAkD,YAAY,KAAK,EAAE,SACvE,OAAO,eAAkD,WAAsB,KAAK,IACtF;AACN,QAAM,WAAW,UAAU,WAAW;AACtC,QAAM,QAAQ,YAAY,SAAS,cAAc,UAAU,WAAW,OAAO;AAC7E,SAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,SAAS,iBAAiB,SAA+B;AACvD,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,aAAW,YAAY,IAAI,aAAa,OAAO,SAAS,GAAG;AACzD,aACG,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,CAAC,EACzC,OAAO,OAAO,EACd,QAAQ,CAAC,UAAU,MAAM,IAAI,KAAK,CAAC;AAAA,EACxC;AACA,SAAO;AACT;AAEA,SAAS,aAAa,SAAmC;AACvD,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,MAAM;AACxD,SAAO,QAAQ,UAAU,QAAQ,gBAAgB,SAAS;AAC5D;AAEA,SAAS,oBAAoB,OAA0C;AACrE,SAAO,OAAO,UAAU,WAAW,MAAM,KAAK,EAAE,YAAY,IAAI;AAClE;AAeA,SAAS,SAAS,OAAgD;AAChE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACtE,QACA;AACN;AAEA,SAAS,iBAAiB,WAA2C,MAA+B;AAClG,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,aAAO,MAAM,KAAK;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,UAAmD;AACjF,QAAM,OAAO,SAAS,QAAQ;AAC9B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,SAAS,KAAK,IAAI,KAAK;AAChC;AAEA,SAAS,sBAAsB,UAA0C;AACvE,QAAM,aAAa,uBAAuB,QAAQ;AAClD,SAAO;AAAA,IACL,YAAY,iBAAiB,YAAY,cAAc,aAAa;AAAA,IACpE,SAAS,iBAAiB,YAAY,mBAAmB,mBAAmB;AAAA,IAC5E,YAAY,iBAAiB,YAAY,iBAAiB,gBAAgB;AAAA,EAC5E;AACF;AAEA,eAAe,kCAAkC;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AACF,GAIsC;AACpC,MAAI,CAAC,KAAK,YAAY,CAAC,KAAK,kBAAkB,CAAC,eAAe,OAAQ,QAAO,CAAC;AAE9E,MAAI,aAAsC;AAC1C,MAAI;AACF,iBAAa,UAAU,QAAQ,kBAAkB;AAAA,EACnD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,CAAC,cAAc,OAAO,WAAW,SAAS,WAAY,QAAO,CAAC;AAClE,QAAM,iBAAiB,IAAI,IAAI,eAAe,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,KAAK,CAAC,CAAC;AACrF,QAAM,iBAAiB,IAAI,IAAI,eAAe,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,MAAM,KAAK,CAAC,CAAC;AACrF,QAAM,uBAAuB,oBAAI,IAAoC;AACrE,QAAM,aAAa,MAAM,WAAW,KAAK;AAAA,IACvC,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,IACrB,cAAc;AAAA,IACd,YAAY,KAAK;AAAA,IACjB,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,EACX,CAAC,EAAE,MAAM,MAAM,IAAI;AACnB,QAAM,OAAO,YAAY,SAAS,CAAC;AAEnC,MAAI,kBAAiC;AACrC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,mBAAmB,YAAY,IAAI,mBAAmB,SAAU;AAExE,UAAM,SAAS,sBAAsB,IAAI,cAAc;AACvD,UAAM,QAAQ,sBAAsB,IAAI,aAAa;AACrD,UAAM,cAAc,MAAM;AAC1B,QAAI,CAAC,YAAa;AAElB,UAAM,aAAa,eAAe,IAAI,WAAW;AACjD,QAAI,OAAO,eAAe,UAAU;AAClC,wBAAkB;AAClB;AAAA,IACF;AAEA,UAAM,2BAA0C,OAAO,WAAW;AAClE,QAAI,6BAA6B,eAAe,qBAAqB,IAAI,WAAW,GAAG;AACrF,wBAAkB;AAClB;AAAA,IACF;AAEA,yBAAqB,IAAI,aAAa;AAAA,MACpC,SAAS;AAAA,MACT,YAAY,MAAM,cAAc,eAAe,IAAI,WAAW,KAAK;AAAA,MACnE;AAAA,MACA,gBAAgB,IAAI,UAAU,YAAY;AAAA,IAC5C,CAAC;AACD,sBAAkB;AAAA,EACpB;AAEA,SAAO,MAAM,KAAK,qBAAqB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,aAAa,MAAM,UAAU;AAC3G;AAEA,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAK6B;AAC3B,QAAM,SAAS,oBAAI,IAAoC;AACvD,aAAW,cAAc,WAAW;AAClC,WAAO,IAAI,WAAW,SAAS,UAAU;AAAA,EAC3C;AACA,aAAW,cAAc,WAAW;AAClC,QAAI,CAAC,OAAO,IAAI,WAAW,OAAO,GAAG;AACnC,aAAO,IAAI,WAAW,SAAS,UAAU;AAAA,IAC3C;AAAA,EACF;AACA,MAAI,gBAAgB,CAAC,OAAO,IAAI,aAAa,EAAE,GAAG;AAChD,WAAO,IAAI,aAAa,IAAI;AAAA,MAC1B,SAAS,aAAa;AAAA,MACtB,YAAY,aAAa;AAAA,MACzB,YAAY,aAAa;AAAA,MACzB,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AACA,SAAO,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,aAAa,MAAM,UAAU;AAC7F;AAEA,eAAe,+BACb,IACA,QACA,gBACA,UAC+C;AAC/C,QAAM,mBAAmB,OACtB,IAAI,CAAC,UAAU,MAAM,MAAM,KAAK,EAAE,YAAY,CAAC,EAC/C,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACrC,MAAI,CAAC,iBAAiB,OAAQ,QAAO,oBAAI,IAAqC;AAC9E,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,iBAAiB,EAAE,KAAK,iBAAiB;AAAA,IAC3C;AAAA,IACA;AAAA,IACA,EAAE,UAAU,eAAe;AAAA,EAC7B;AACA,QAAM,MAAM,oBAAI,IAAqC;AACrD,UAAQ,QAAQ,CAAC,UAAU,IAAI,IAAI,MAAM,iBAAiB,KAAK,CAAC;AAChE,SAAO;AACT;AAEA,eAAe,8BACb,IACA,MACA,iBACuC;AACvC,MAAI,KAAK,iBAAiB;AACxB,UAAM,aAAa,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI,KAAK;AAAA,QACT,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,CAAC;AAAA,MACD;AAAA,IACF;AACA,QAAI,WAAY,QAAO;AAAA,EACzB;AAEA,QAAM,uBAAuB,oBAAoB,KAAK,aAAa;AACnE,MAAI,CAAC,qBAAsB,QAAO;AAElC,QAAM,eAAe,MAAM;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,MACE,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,MACf,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IAC3D;AAAA,IACA,EAAE,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,iBAAiB,aAAa,OAAO,CAAC,UAAU,oBAAoB,MAAM,KAAK,MAAM,oBAAoB;AAC/G,MAAI,eAAe,WAAW,EAAG,QAAO,eAAe,CAAC,KAAK;AAC7D,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,sBAAsB,IAAI,IAAI,eAAe,IAAI,CAAC,UAAU,MAAM,UAAU,CAAC;AACnF,QAAI,oBAAoB,SAAS,EAAG,QAAO,eAAe,CAAC,KAAK;AAAA,EAClE;AACA,SAAO;AACT;AAEA,MAAM,6BAA6B;AAQnC,MAAM,kCAAkC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,2BAA2B;AAWjC,SAAS,8BACP,MACA,OACQ;AACR,MAAI,MAAM,iBAAiB,KAAM,QAAO;AACxC,MAAI,OAAO,eAAe,KAAM,QAAO;AACvC,QAAM,YAAY,MAAM,QAAQ,OAAO,SAAS,IAC5C,MAAO,UAAU,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,KAAK,EAAE,SAAS,CAAC,IAC5F,CAAC;AACL,MAAI,UAAU,OAAQ,QAAO,UAAU,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,MAAM,UAAW,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,CAAE,EAAE,KAAK,GAAG,CAAC;AACjI,SAAO,QAAQ,MAAM,SAAS,MAAM;AACtC;AAEA,SAAS,wBAAwB,QAOtB;AACT,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO,YAAY,MAAM;AAAA,IACnC,OAAO,OAAO,kBAAkB,MAAM;AAAA,IACtC,QAAQ,OAAO,MAAM;AAAA,IACrB,SAAS,OAAO,cAAc;AAAA,IAC9B,QAAQ,OAAO,IAAI;AAAA,IACnB,WAAW,OAAO,UAAU;AAAA,EAC9B,EAAE,KAAK,GAAG;AACZ;AAEA,SAAS,yBAAyB,UAAyB,gBAAyC;AAClG,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,OAAO,sBAAsB,4BAA4B,+BAA+B,GAAG;AACpG,eAAW,OAAO,oBAAoB,KAAK,UAAU,CAAC,cAAc,CAAC,GAAG;AACtE,WAAK,IAAI,GAAG;AAAA,IACd;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,eAAsB,IAAI,SAAkB,SAA+C;AACzF,QAAM,eAAe,aAAa,UAAU,QAAQ,MAAM;AAC1D,MAAI,CAAC,aAAa,SAAS;AACzB,WAAO,SAAS,gBAAgB;AAAA,EAClC;AAEA,QAAM,eAAe,iBAAiB,OAAO;AAC7C,QAAM,WAAW,aAAa,OAAO;AACrC,QAAM,WAAW,aAAa;AAC9B,QAAM,gBAAgB,aAAa,IAAI,QAAQ;AAC/C,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,OAAO;AAC7C,MAAI,CAAC,MAAM,OAAO,CAAC,MAAM,UAAU;AACjC,WAAO,aAAa,KAAK,EAAE,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAChF;AAOA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,QAAQ,CAAC;AACnF,QAAM,KAAM,UAAU,QAAQ,IAAI;AAElC,QAAM,OAAO,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,aAAa,KAAK,IAAI,WAAW,KAAK;AAAA,IAC5C;AAAA,MACE,UAAU,CAAC,iBAAiB,+BAA+B,qBAAqB,kCAAkC;AAAA,IACpH;AAAA,IACA,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK;AAAA,EACxE;AACA,MAAI,CAAC,MAAM;AACT,WAAO,SAAS,gBAAgB;AAAA,EAClC;AAEA,MAAI,KAAK,YAAY,KAAK,YAAY,KAAK,aAAa,KAAK,UAAU;AACrE,WAAO,SAAS,gBAAgB;AAAA,EAClC;AAOA,QAAM,yBAAyB;AAAA,IAC7B,EAAE,OAAO,MAAM,gBAAgB,KAAK,eAAe;AAAA,IACnD;AAAA,EACF;AACA,MAAI,uBAAwB,QAAO;AAEnC,QAAM,kBAAkB;AAAA,IACtB,UAAU,KAAK,YAAY,KAAK,YAAY;AAAA,IAC5C,gBAAgB,KAAK,kBAAkB,KAAK,SAAS;AAAA,EACvD;AAEA,QAAM,eAAe,KAAK,WAAW,OAAO,KAAK,OAAO;AACxD,QAAM,gBAAgB,YAAoE;AACxF,QAAI,CAAC,cAAc;AACjB,aAAO,EAAE,MAAM,MAAM,OAAO,KAAK,SAAS,KAAK;AAAA,IACjD;AACA,UAAMA,UAAS,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,EAAE,IAAI,cAAc,UAAU,KAAK,YAAY,KAAK;AAAA,MACpD,CAAC;AAAA,MACD,EAAE,UAAU,KAAK,YAAY,MAAM,gBAAgB,KAAK,SAAS,KAAK;AAAA,IACxE;AACA,WAAO;AAAA,MACL,MAAMA,SAAQ,QAAQ;AAAA,MACtB,OAAOA,SAAQ,SAAS,KAAK,SAAS;AAAA,IACxC;AAAA,EACF;AAQA,QAAM,gBAAgB,KAAK,YAAY,KAAK,YAAY;AACxD,QAAM,sBAAsB,KAAK,kBAAkB;AACnD,QAAM,cAAc,mBAAmB,IAAI,iBAAiB,SAAS,IAAI;AACzE,QAAM,aAAa,aAAa,OAAO,MAAM,KAAK,YAAY,EAAE,KAAK,CAAC,MAAM,UAAW,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,CAAE,EAAE,KAAK,GAAG,IAAI;AAC9I,QAAM,iBAAiB,cACnB,wBAAwB;AAAA,IACtB,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,gBAAgB,8BAA8B,MAAM,KAAK;AAAA,IACzD,MAAM;AAAA,IACN;AAAA,EACF,CAAC,IACD;AAEJ,MAAI,eAAe,gBAAgB;AACjC,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,eAAe,MAAM,YAAY,IAAI,cAAc,CAAC;AAC5F,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAMC,cAAa,MAAM,cAAc;AACvC,eAAO,aAAa,KAAK;AAAA,UACvB,GAAI;AAAA,UACJ,QAAQ;AAAA,YACN,QAAQ;AAAA,YACR,MAAMA,YAAW;AAAA,YACjB,OAAOA,YAAW;AAAA,UACpB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AAEZ,qBAAe,OAAO;AAAA,QACpB,UAAU;AAAA,QACV,KAAK;AAAA,QACL,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAQA,QAAM,sBAAsB,YAKtB;AACJ,QAAI,UAAU;AACZ,YAAM,CAAC,gBAAgB,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC1D;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,MAAM,KAAK,GAAG;AAAA,UAChB,EAAE,SAAS,EAAE,WAAW,MAAM,EAAE;AAAA,UAChC;AAAA,QACF;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,MAAM,KAAK,GAAG;AAAA,UAChB,EAAE,SAAS,EAAE,WAAW,MAAM,EAAE;AAAA,UAChC;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAMC,mBAAkB,MAAM;AAAA,QAC5B,IAAI;AAAA,UACF,eACG,IAAI,CAAC,SAAS;AACb,kBAAM,YAAY,KAAK;AACvB,gBAAI,CAAC,UAAW,QAAO;AACvB,gBAAI,OAAO,cAAc,SAAU,QAAO;AAC1C,kBAAM,gBAAgB,UAAU;AAChC,mBAAO,OAAO,kBAAkB,WAAW,gBAAgB;AAAA,UAC7D,CAAC,EACA,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC;AAAA,QAC5F;AAAA,MACF;AACA,YAAMC,oBAAmB,MAAM;AAAA,QAC7B,IAAI;AAAA,UACF,gBACG,IAAI,CAAC,SAAS;AACb,kBAAM,aAAa,KAAK;AACxB,gBAAI,CAAC,WAAY,QAAO;AACxB,gBAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,kBAAM,iBAAiB,WAAW;AAClC,mBAAO,OAAO,mBAAmB,WAAW,iBAAiB;AAAA,UAC/D,CAAC,EACA,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,CAAC;AAAA,QAC5F;AAAA,MACF;AAEA,YAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC1DD,iBAAgB,SACZ;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,IAAI,EAAE,KAAKA,iBAAgB,MAAM,GAAG,CAAC,EAAE,EAAE;AAAA,UAC3C,EAAE,UAAU,CAAC,eAAe,EAAE;AAAA,UAC9B;AAAA,QACF,IACA,CAAC;AAAA,QACLC,kBAAiB,SACb;AAAA,UACE;AAAA,UACA;AAAA,UACA,EAAE,IAAI,EAAE,KAAKA,kBAAiB,MAAM,GAAG,CAAC,EAAE,EAAE;AAAA,UAC5C,EAAE,UAAU,CAAC,gBAAgB,EAAE;AAAA,UAC/B;AAAA,QACF,IACA,CAAC;AAAA,MACP,CAAC;AACD,YAAM,mBAAmB,IAAI;AAAA,QAC3B,eACG,OAAO,CAAC,SAAS,KAAK,cAAc,IAAI,EACxC,IAAI,CAAC,SAAU,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,KAAK,QAAQ,EAAG,EAC/E,OAAO,CAAC,UAA2B,OAAO,UAAU,QAAQ;AAAA,MACjE;AACA,YAAM,mBAAmB,IAAI,IAAI,cAAc,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;AACnF,YAAM,sBAAsB,IAAI,IAAI,iBAAiB,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC;AACzF,YAAMC,UAASF,iBAAgB,MAAM,GAAG,CAAC,EAAE,OAA0B,CAAC,KAAK,aAAa;AACtF,cAAM,SAAS,iBAAiB,IAAI,QAAQ,KAAK;AACjD,YAAI,CAAC,UAAU,OAAO,UAAW,QAAO;AACxC,cAAM,EAAE,OAAO,SAAS,IAAI,2BAA2B,MAAM;AAC7D,YAAI,KAAK,EAAE,IAAI,OAAO,IAAI,OAAO,UAAU,MAAM,UAAU,WAAW,iBAAiB,IAAI,OAAO,EAAE,EAAE,CAAC;AACvG,eAAO;AAAA,MACT,GAAG,CAAC,CAAC;AACL,YAAMG,aAAYF,kBAAiB,MAAM,GAAG,CAAC,EAAE,OAA0B,CAAC,KAAK,cAAc;AAC3F,cAAM,SAAS,oBAAoB,IAAI,SAAS,KAAK;AACrD,YAAI,CAAC,UAAU,OAAO,UAAW,QAAO;AACxC,cAAM,EAAE,OAAO,SAAS,IAAI,4BAA4B,MAAM;AAC9D,YAAI,KAAK,EAAE,IAAI,OAAO,IAAI,OAAO,UAAU,MAAM,UAAU,CAAC;AAC5D,eAAO;AAAA,MACT,GAAG,CAAC,CAAC;AACL,aAAO,EAAE,QAAAC,SAAQ,WAAAC,YAAW,iBAAAH,kBAAiB,kBAAAC,kBAAiB;AAAA,IAChE;AAEA,UAAM,CAAC,aAAa,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,MACpD;AAAA,QACE;AAAA,QACA;AAAA,QACA,EAAE,MAAM,KAAK,GAAG;AAAA,QAChB,EAAE,UAAU,CAAC,UAAU,sBAAsB,EAAE;AAAA,QAC/C;AAAA,MACF;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA,EAAE,MAAM,KAAK,GAAG;AAAA,QAChB,EAAE,UAAU,CAAC,WAAW,wBAAwB,EAAE;AAAA,QAClD;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,mBAAmB,KAAK,YAAY,KAAK,YAAY;AAC3D,UAAM,gBAAgB,KAAK,kBAAkB,KAAK,SAAS;AAC3D,UAAM,iCAAiC,aAAa;AAAA,MAClD;AAAA,MACA,UAAU;AAAA,MACV,gBAAgB;AAAA,IAClB,CAAC;AACD,UAAM,iCAAiC,cAAc;AAAA,MACnD;AAAA,MACA,UAAU;AAAA,MACV,gBAAgB;AAAA,IAClB,CAAC;AAED,UAAMC,UAAS,YAAY,OAA0B,CAAC,KAAK,SAAS;AAClE,YAAM,SAAS,KAAK;AACpB,UAAI,CAAC,UAAU,OAAO,UAAW,QAAO;AACxC,YAAM,EAAE,OAAO,SAAS,IAAI,2BAA2B,MAAM;AAC7D,UAAI,KAAK,EAAE,IAAI,OAAO,IAAI,OAAO,UAAU,MAAM,UAAU,WAAW,KAAK,cAAc,KAAK,CAAC;AAC/F,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAEL,UAAMC,aAAY,aAAa,OAA0B,CAAC,KAAK,SAAS;AACtE,YAAM,SAAS,KAAK;AACpB,UAAI,CAAC,UAAU,OAAO,UAAW,QAAO;AACxC,YAAM,EAAE,OAAO,SAAS,IAAI,4BAA4B,MAAM;AAC9D,UAAI,KAAK,EAAE,IAAI,OAAO,IAAI,OAAO,UAAU,MAAM,UAAU,CAAC;AAC5D,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AACL,WAAO;AAAA,MACL,QAAAD;AAAA,MACA,WAAAC;AAAA,MACA,iBAAiBD,QAAO,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,MAC/C,kBAAkBC,WAAU,IAAI,CAAC,UAAU,MAAM,EAAE;AAAA,IACrD;AAAA,EACF;AAEA,QAAM,eAAe,YACnB,KAAK,cACD,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,EAAE,IAAI,KAAK,aAAa,UAAU,KAAK,YAAY,KAAK,YAAY,KAAK;AAAA,IACzE,CAAC;AAAA,IACD;AAAA,EACF,IACE;AAEN,QAAM,0BAA0B,YAAoD;AAClF,QAAI,CAAC,cAAe,QAAO,CAAC;AAC5B,QAAI;AACF,aAAO,MAAM;AAAA,QACX;AAAA,QACA;AAAA,QACA,EAAE,MAAM,KAAK,IAAI,WAAW,KAAK;AAAA,QACjC,EAAE,SAAS,EAAE,YAAY,OAAO,gBAAgB,MAAM,EAAE;AAAA,QACxD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,UAAI,CAAC,kCAAkC,KAAK,GAAG;AAC7C,cAAM;AAAA,MACR;AACA,0CAAoC,gCAAgC;AACpE,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAEA,QAAM,CAAC,cAAc,mBAAmB,YAAY,OAAO,gBAAgB,gBAAgB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/G,oBAAoB;AAAA,IACpB,sBAAsB;AAAA,MACpB;AAAA,MACA,UAAU,EAAE,UAAU;AAAA,MACtB,WAAW,CAAC,KAAK,EAAE;AAAA,MACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,YAAY,KAAK;AAAA,MACrD,wBAAwB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,kBAAkB,KAAK;AAAA,MACjE,iBAAiB,CAAC,KAAK,YAAY,KAAK,YAAY,IAAI,EAAE,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,IACtG,CAAC;AAAA,IACD,cAAc;AAAA,IACd,aAAa;AAAA,IACb,gBAAgB,8BAA8B,IAAI,MAAM,eAAe,IAAI,QAAQ,QAAQ,IAAI;AAAA,IAC/F,wBAAwB;AAAA,EAC1B,CAAC;AAED,QAAM,EAAE,QAAQ,WAAW,iBAAiB,iBAAiB,IAAI;AACjE,QAAM,eAAe,6BAA6B,kBAAkB,KAAK,EAAE,CAAC,KAAK,CAAC;AAClF,QAAM,aAAa,WAAW;AAC9B,QAAM,cAAc,WAAW;AAC/B,QAAM,eAAe,QACjB;AAAA,IACA,IAAI,MAAM;AAAA,IACV,MAAM,MAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,IACzC,OAAO,MAAM,SAAS;AAAA,EACxB,IACE;AAEJ,QAAM,sBAAsB,KAAK,cAAc,gBAAgB,cAAc;AAC7E,QAAM,2BAA2B,KAAK,mBAAmB,gBAAgB,MAAM;AAC/E,QAAM,8BAA8B,KAAK,iBAAiB,gBAAgB,SAAS;AAEnF,QAAM,CAAC,gBAAgB,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IACnD,iBAAiB,sBACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,YAAY;AAAA,QACZ,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,EAAE,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,MAC5B;AAAA,IACF,IACE,CAAC;AAAA,IACL,sBACI;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,gBAAgB,KAAK;AAAA,QACrB,UAAU,KAAK;AAAA,MACjB;AAAA,MACA,CAAC;AAAA,MACD;AAAA,IACF,IACE;AAAA,EACN,CAAC;AAED,QAAM,4BAA4B,iBAAiB,IAAI,CAAC,gBAAgB;AAAA,IACtE,SAAS,WAAW;AAAA,IACpB,YAAY,WAAW;AAAA,IACvB,YAAY,WAAW;AAAA,IACvB,gBAAgB,WAAW,eAAe,YAAY;AAAA,EACxD,EAAE;AACF,QAAM,CAAC,4BAA4B,yBAAyB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChF,eAAe,SACX,+BAA+B,IAAI,gBAAgB,KAAK,gBAAgB,KAAK,QAAQ,IACrF,oBAAI,IAAqC;AAAA,IAC7C,iBAAiB,0BAA0B,WAAW,IAClD,kCAAkC,EAAE,WAAW,MAAM,eAAe,CAAC,IACrE,CAAC;AAAA,EACP,CAAC;AACD,QAAM,yBAAyB,MAAM;AACnC,QAAI,CAAC,yBAA0B,QAAO;AACtC,UAAM,gBAAgB,eAAe,KAAK,CAAC,UAAU,MAAM,OAAO,wBAAwB;AAC1F,QAAI,eAAe;AACjB,aAAO;AAAA,QACL,IAAI,cAAc;AAAA,QAClB,OAAO,cAAc;AAAA,QACrB,OAAO,cAAc;AAAA,MACvB;AAAA,IACF;AACA,QAAI,CAAC,4BAA6B,QAAO;AACzC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AAAA,EACF,GAAG;AACH,QAAM,yBAAyB,sBAAsB;AAAA,IACnD,WAAW;AAAA,IACX,WAAW;AAAA,IACX,cAAc;AAAA,IACd,mBAAmB,KAAK,UAAU,YAAY;AAAA,EAChD,CAAC;AAED,QAAM,SAAS;AAAA,IACb,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAIA,QAAM,gBAAgB;AAAA,IACpB,MAAM;AAAA,MACJ,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK,eAAe;AAAA,MACjC,QAAQ,KAAK,UAAU;AAAA,MACvB,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,aAAa,KAAK,eAAe;AAAA,MACjC,eAAe,KAAK,iBAAiB;AAAA,MACrC,aAAa,KAAK,eAAe;AAAA,MACjC,iBAAiB,KAAK,kBAAkB,KAAK,gBAAgB,YAAY,IAAI;AAAA,MAC7E,aAAa,KAAK,eAAe;AAAA,MACjC,QAAQ,KAAK,UAAU;AAAA,MACvB,gBAAgB,KAAK,kBAAkB;AAAA,MACvC,cAAc,KAAK,gBAAgB;AAAA,MACnC,WAAW,KAAK,aAAa;AAAA,MAC7B,gBAAgB,KAAK,kBAAkB;AAAA,MACvC,UAAU,KAAK,YAAY;AAAA,MAC3B,WAAW,KAAK,UAAU,YAAY;AAAA,MACtC,WAAW,KAAK,UAAU,YAAY;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ,gBAAgB;AAAA,MACxB,WAAW,iBAAiB;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,gBAAgB,eAAe,IAAI,CAAC,UAAU;AAC5C,YAAM,aAAa,2BAA2B,IAAI,MAAM,MAAM,KAAK,EAAE,YAAY,CAAC;AAClF,aAAO;AAAA,QACL,IAAI,MAAM;AAAA,QACV,OAAO,MAAM;AAAA,QACb,OAAO,MAAM;AAAA,QACb,OAAO,YAAY,SAAS;AAAA,QAC5B,MAAM,YAAY,QAAQ;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,IACD,cAAc,UAAU,QAAQ;AAAA,IAChC,kBAAkB;AAAA,IAClB,OAAO;AAAA,EACT;AAEA,MAAI,eAAe,gBAAgB;AACjC,QAAI;AACF,YAAM;AAAA,QAAmB;AAAA,QAAe,MACtC,YAAY,IAAI,gBAAgB,eAAe;AAAA,UAC7C,KAAK;AAAA,UACL,MAAM,yBAAyB,eAAe,mBAAmB;AAAA,QACnE,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AAGZ,qBAAe,SAAS;AAAA,QACtB,UAAU;AAAA,QACV,KAAK;AAAA,QACL,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO,aAAa,KAAK,EAAE,GAAG,eAAe,OAAO,CAAC;AACvD;AAEA,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,SAAS,EAAE,OAAO,EAAE,SAAS;AAC/B,CAAC;AAED,MAAM,0BAA0B,EAAE,OAAO;AAAA,EACvC,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO,EAAE,OAAO,EAAE,IAAI;AAAA,EACtB,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,SAAS;AAC5B,CAAC;AAED,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,OAAO,EAAE,KAAK;AAAA,EACzB,YAAY,EAAE,OAAO;AAAA,EACrB,YAAY,EAAE,OAAO,EAAE,IAAI;AAAA,EAC3B,gBAAgB,EAAE,OAAO;AAC3B,CAAC;AAED,MAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,MAAM,EAAE,OAAO;AAAA,IACb,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACtC,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACvC,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9C,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACvD,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAChD,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACvC,gBAAgB,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5D,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACtD,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IAChD,WAAW,EAAE,OAAO;AAAA,IACpB,WAAW,EAAE,OAAO;AAAA,EACtB,CAAC;AAAA,EACD,QAAQ,EAAE;AAAA,IACR,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,OAAO,EAAE,OAAO;AAAA,MAChB,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,MAAM,EAAE,QAAQ,QAAQ;AAAA,MACxB,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,CAAC;AAAA,EACH;AAAA,EACA,WAAW,EAAE;AAAA,IACX,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,MACpB,OAAO,EAAE,OAAO;AAAA,MAChB,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,MAAM,EAAE,QAAQ,SAAS;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EACA,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,EAC9C,QAAQ,EAAE,OAAO;AAAA,IACf,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACnC,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,IAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,CAAC;AAAA,EACD,gBAAgB,EAAE,MAAM,uBAAuB;AAAA,EAC/C,kBAAkB,EAAE,MAAM,yBAAyB;AAAA,EACnD,OAAO,EAAE,OAAO;AAAA,IACd,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO;AAAA,EAClB,CAAC,EAAE,SAAS;AACd,CAAC;AAED,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aAAa;AAAA,MACb,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,uBAAuB,QAAQ,yBAAyB;AAAA,MACtF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,sBAAsB;AAAA,QAC1E,EAAE,QAAQ,KAAK,aAAa,sDAAiD,QAAQ,sBAAsB;AAAA,QAC3G,EAAE,QAAQ,KAAK,aAAa,yEAAoE,QAAQ,sBAAsB;AAAA,MAChI;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": ["viewer", "viewerInfo", "linkedPersonIds", "linkedCompanyIds", "people", "companies"]
|
|
7
7
|
}
|
|
@@ -17,9 +17,6 @@ const paramsSchema = z.object({
|
|
|
17
17
|
function notFound(message) {
|
|
18
18
|
return NextResponse.json({ error: message }, { status: 404 });
|
|
19
19
|
}
|
|
20
|
-
function forbidden(message) {
|
|
21
|
-
return NextResponse.json({ error: message }, { status: 403 });
|
|
22
|
-
}
|
|
23
20
|
function badRequest(message, code) {
|
|
24
21
|
return NextResponse.json(
|
|
25
22
|
code ? { error: message, code } : { error: message },
|
|
@@ -53,22 +50,6 @@ async function GET(request, context) {
|
|
|
53
50
|
if (!auth?.sub && !auth?.isApiKey) {
|
|
54
51
|
return NextResponse.json({ error: translate("customers.errors.authentication_required", "Authentication required") }, { status: 401 });
|
|
55
52
|
}
|
|
56
|
-
let rbac = null;
|
|
57
|
-
try {
|
|
58
|
-
rbac = container.resolve("rbacService");
|
|
59
|
-
} catch {
|
|
60
|
-
rbac = null;
|
|
61
|
-
}
|
|
62
|
-
if (!rbac || !auth?.sub) {
|
|
63
|
-
return forbidden(translate("customers.errors.access_denied", "Access denied"));
|
|
64
|
-
}
|
|
65
|
-
const hasFeature = await rbac.userHasAllFeatures(auth.sub, ["customers.deals.view"], {
|
|
66
|
-
tenantId: auth.tenantId ?? null,
|
|
67
|
-
organizationId: auth.orgId ?? null
|
|
68
|
-
});
|
|
69
|
-
if (!hasFeature) {
|
|
70
|
-
return forbidden(translate("customers.errors.access_denied", "Access denied"));
|
|
71
|
-
}
|
|
72
53
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request });
|
|
73
54
|
const em = container.resolve("em");
|
|
74
55
|
const deal = await findOneWithDecryption(
|
|
@@ -82,7 +63,7 @@ async function GET(request, context) {
|
|
|
82
63
|
return notFound(translate("customers.errors.deal_not_found", "Deal not found"));
|
|
83
64
|
}
|
|
84
65
|
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {
|
|
85
|
-
return
|
|
66
|
+
return notFound(translate("customers.errors.deal_not_found", "Deal not found"));
|
|
86
67
|
}
|
|
87
68
|
if (!deal.closureOutcome) {
|
|
88
69
|
return badRequest(translate("customers.errors.deal_not_closed", "Deal is not closed"), "DEAL_NOT_CLOSED");
|
|
@@ -174,8 +155,8 @@ const openApi = {
|
|
|
174
155
|
errors: [
|
|
175
156
|
{ status: 400, description: "Deal is not closed", schema: dealStatsErrorSchema },
|
|
176
157
|
{ status: 401, description: "Unauthorized", schema: dealStatsErrorSchema },
|
|
177
|
-
{ status: 403, description: "Forbidden
|
|
178
|
-
{ status: 404, description: "Deal not found", schema: dealStatsErrorSchema }
|
|
158
|
+
{ status: 403, description: "Forbidden \u2014 caller lacks the required feature", schema: dealStatsErrorSchema },
|
|
159
|
+
{ status: 404, description: "Deal not found, or its organization is not in the caller\u2019s scope", schema: dealStatsErrorSchema }
|
|
179
160
|
]
|
|
180
161
|
}
|
|
181
162
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/api/deals/%5Bid%5D/stats/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerDeal, CustomerPipeline } from '../../../../data/entities'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport type {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,2BAA2B;AAEpC,SAAS,cAAc,wBAAwB;AAC/C,SAAS,uBAAuB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerDeal, CustomerPipeline } from '../../../../data/entities'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst paramsSchema = z.object({\n id: z.string().uuid(),\n})\n\nfunction notFound(message: string) {\n return NextResponse.json({ error: message }, { status: 404 })\n}\n\nfunction badRequest(message: string, code?: string) {\n return NextResponse.json(\n code ? { error: message, code } : { error: message },\n { status: 400 },\n )\n}\n\nfunction startOfIsoWeek(date: Date): Date {\n const value = new Date(date)\n const day = value.getDay()\n const diff = day === 0 ? -6 : 1 - day\n value.setHours(0, 0, 0, 0)\n value.setDate(value.getDate() + diff)\n return value\n}\n\nfunction startOfQuarter(date: Date): Date {\n return new Date(date.getFullYear(), Math.floor(date.getMonth() / 3) * 3, 1)\n}\n\nfunction calculateSalesCycleDays(createdAt: Date, closedAt: Date): number {\n const diffMs = closedAt.getTime() - createdAt.getTime()\n if (diffMs <= 0) return 0\n return Math.floor(diffMs / 86400000)\n}\n\nexport async function GET(request: Request, context: { params?: Record<string, unknown> }) {\n const { translate } = await resolveTranslations()\n const parsedParams = paramsSchema.safeParse(context.params)\n if (!parsedParams.success) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n if (!auth?.sub && !auth?.isApiKey) {\n return NextResponse.json({ error: translate('customers.errors.authentication_required', 'Authentication required') }, { status: 401 })\n }\n\n // The API dispatcher already enforces `requireFeatures: ['customers.deals.view']`\n // against the caller's effective (selected) organization before this handler\n // runs, so no in-route feature re-check is needed. Re-checking against the token\n // org (auth.orgId) would wrongly 403 org-switched callers (#5012).\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const em = (container.resolve('em') as EntityManager)\n const deal = await findOneWithDecryption(\n em,\n CustomerDeal,\n { id: parsedParams.data.id, tenantId: auth.tenantId ?? null, deletedAt: null },\n {},\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n )\n if (!deal) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n // Existence oracle (#5504): deny a cross-org read as not-found \u2014 identical to\n // the deal-not-found above \u2014 so it cannot reveal that a deal exists in an\n // organization the caller cannot see.\n if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: deal.organizationId })) {\n return notFound(translate('customers.errors.deal_not_found', 'Deal not found'))\n }\n\n if (!deal.closureOutcome) {\n return badRequest(translate('customers.errors.deal_not_closed', 'Deal is not closed'), 'DEAL_NOT_CLOSED')\n }\n\n const now = new Date()\n const weekStart = startOfIsoWeek(now)\n const quarterStart = startOfQuarter(now)\n const dealsClosedThisPeriod = await em.count(CustomerDeal, {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n closureOutcome: deal.closureOutcome,\n deletedAt: null,\n updatedAt: { $gte: weekStart },\n })\n\n let dealRankInQuarter: number | null = null\n if (deal.closureOutcome === 'won' && deal.valueAmount !== null) {\n const higherValueDeals = await em.count(CustomerDeal, {\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n closureOutcome: 'won',\n deletedAt: null,\n updatedAt: { $gte: quarterStart },\n valueAmount: { $gt: deal.valueAmount },\n })\n dealRankInQuarter = higherValueDeals + 1\n }\n\n const pipeline = deal.pipelineId\n ? await findOneWithDecryption(\n em,\n CustomerPipeline,\n { id: deal.pipelineId, tenantId: deal.tenantId, organizationId: deal.organizationId },\n {},\n { tenantId: deal.tenantId, organizationId: deal.organizationId },\n )\n : null\n\n let lossReasonLabel: string | null = null\n if (deal.lossReasonId) {\n const dictionaryEntry = await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: deal.lossReasonId,\n organizationId: deal.organizationId,\n tenantId: deal.tenantId,\n },\n { populate: ['dictionary'] },\n { tenantId: deal.tenantId, organizationId: deal.organizationId },\n )\n const dictionaryKey =\n dictionaryEntry?.dictionary &&\n typeof (dictionaryEntry.dictionary as { key?: unknown }).key === 'string'\n ? (dictionaryEntry.dictionary as { key: string }).key\n : null\n if (dictionaryKey === 'sales.deal_loss_reason') {\n lossReasonLabel = dictionaryEntry?.label ?? dictionaryEntry?.value ?? null\n }\n }\n\n return NextResponse.json({\n dealValue: deal.valueAmount !== null ? Number(deal.valueAmount) : null,\n dealCurrency: deal.valueCurrency ?? null,\n closureOutcome: deal.closureOutcome,\n closedAt: deal.updatedAt.toISOString(),\n pipelineName: pipeline?.name ?? null,\n dealsClosedThisPeriod,\n salesCycleDays: calculateSalesCycleDays(deal.createdAt, deal.updatedAt),\n dealRankInQuarter,\n lossReason: lossReasonLabel,\n })\n}\n\nconst dealStatsResponseSchema = z.object({\n dealValue: z.number().nullable(),\n dealCurrency: z.string().nullable(),\n closureOutcome: z.enum(['won', 'lost']),\n closedAt: z.string(),\n pipelineName: z.string().nullable(),\n dealsClosedThisPeriod: z.number().int(),\n salesCycleDays: z.number().int().nullable(),\n dealRankInQuarter: z.number().int().nullable(),\n lossReason: z.string().nullable(),\n})\n\nconst dealStatsErrorSchema = z.object({\n error: z.string(),\n code: z.string().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Fetch deal closure stats',\n methods: {\n GET: {\n summary: 'Fetch analytics for a closed deal',\n description: 'Returns week-to-date closure counts, sales cycle length, quarter ranking, and loss reason context for a closed deal.',\n responses: [\n { status: 200, description: 'Deal closure stats payload', schema: dealStatsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Deal is not closed', schema: dealStatsErrorSchema },\n { status: 401, description: 'Unauthorized', schema: dealStatsErrorSchema },\n { status: 403, description: 'Forbidden \u2014 caller lacks the required feature', schema: dealStatsErrorSchema },\n { status: 404, description: 'Deal not found, or its organization is not in the caller\u2019s scope', schema: dealStatsErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,6 +9,7 @@ import { parseBooleanFromUnknown } from "@open-mercato/shared/lib/boolean";
|
|
|
9
9
|
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
10
10
|
import { isTenantDataEncryptionEnabled } from "@open-mercato/shared/lib/encryption/toggles";
|
|
11
11
|
import { fetchStuckDealIds } from "../../../lib/stuckDeals.js";
|
|
12
|
+
import { expandDealStatusAliases } from "../../../lib/dealStatus.js";
|
|
12
13
|
import { findMatchingEntityIdsBySearchTokensAcrossSources } from "../../utils.js";
|
|
13
14
|
import { E } from "../../../../../generated/entities.ids.generated.js";
|
|
14
15
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
@@ -19,7 +20,7 @@ const metadata = {
|
|
|
19
20
|
const querySchema = z.object({
|
|
20
21
|
pipelineId: z.string().uuid().optional(),
|
|
21
22
|
search: z.string().optional(),
|
|
22
|
-
status: z.array(z.
|
|
23
|
+
status: z.array(z.string().max(50)).max(20).optional(),
|
|
23
24
|
ownerUserId: z.array(z.string().uuid()).optional(),
|
|
24
25
|
personId: z.array(z.string().uuid()).optional(),
|
|
25
26
|
companyId: z.array(z.string().uuid()).optional(),
|
|
@@ -177,9 +178,10 @@ async function GET(req) {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
if (parsed.data.status && parsed.data.status.length) {
|
|
180
|
-
const
|
|
181
|
+
const expandedStatuses = expandDealStatusAliases(parsed.data.status);
|
|
182
|
+
const placeholders = expandedStatuses.map(() => "?").join(",");
|
|
181
183
|
where.push(`status IN (${placeholders})`);
|
|
182
|
-
values.push(...
|
|
184
|
+
values.push(...expandedStatuses);
|
|
183
185
|
}
|
|
184
186
|
if (parsed.data.ownerUserId && parsed.data.ownerUserId.length) {
|
|
185
187
|
const placeholders = parsed.data.ownerUserId.map(() => "?").join(",");
|
|
@@ -195,7 +197,12 @@ async function GET(req) {
|
|
|
195
197
|
values.push(parsed.data.expectedCloseAtTo);
|
|
196
198
|
}
|
|
197
199
|
if (parsed.data.isOverdue) {
|
|
198
|
-
|
|
200
|
+
const hasCallerStatus = !!parsed.data.status?.length;
|
|
201
|
+
if (hasCallerStatus) {
|
|
202
|
+
where.push("expected_close_at < CURRENT_DATE");
|
|
203
|
+
} else {
|
|
204
|
+
where.push("expected_close_at < CURRENT_DATE AND status = 'open'");
|
|
205
|
+
}
|
|
199
206
|
}
|
|
200
207
|
if (parsed.data.isStuck) {
|
|
201
208
|
const stuckIds = await fetchStuckDealIds(em, orgFilterIds[0], effectiveTenantId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/api/deals/aggregate/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager as CoreEntityManager } from '@mikro-orm/core'\nimport type { EntityManager as PgEntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveDealsOrganizationIds } from '../../../lib/dealsOrganizationScope'\nimport { resolveOptionalBaseCurrencyCode } from '../../../lib/optionalBaseCurrency'\nimport type { ExchangeRateService } from '@open-mercato/core/modules/currencies/services/exchangeRateService'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { fetchStuckDealIds } from '../../../lib/stuckDeals'\nimport { findMatchingEntityIdsBySearchTokensAcrossSources } from '../../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst querySchema = z.object({\n pipelineId: z.string().uuid().optional(),\n search: z.string().optional(),\n status: z.array(z.enum(['open', 'closed', 'win', 'loose'])).optional(),\n ownerUserId: z.array(z.string().uuid()).optional(),\n personId: z.array(z.string().uuid()).optional(),\n companyId: z.array(z.string().uuid()).optional(),\n isStuck: z.preprocess((value) => {\n const parsed = parseBooleanFromUnknown(value)\n return parsed === null ? value : parsed\n }, z.boolean()).optional(),\n isOverdue: z.preprocess((value) => {\n const parsed = parseBooleanFromUnknown(value)\n return parsed === null ? value : parsed\n }, z.boolean()).optional(),\n expectedCloseAtFrom: z.string().optional(),\n expectedCloseAtTo: z.string().optional(),\n})\n\ntype StageBreakdownByCurrency = {\n currency: string\n total: number\n count: number\n}\n\ntype StageAggregate = {\n stageId: string\n count: number\n openCount: number\n totalInBaseCurrency: number\n byCurrency: StageBreakdownByCurrency[]\n /**\n * `true` when every currency present in `byCurrency` was either the base currency or\n * had a usable exchange rate at request time. `false` when at least one currency in\n * `byCurrency` could not be resolved to base \u2014 the lane header should surface a\n * \"partial\" indicator in that case so the converted total isn't read as authoritative.\n */\n convertedAll: boolean\n /**\n * Currencies present in `byCurrency` that had NO exchange rate to the base currency.\n * Empty when conversion was complete or there is no base currency configured. The\n * client uses this to disclose which slice of the value is excluded from `totalInBaseCurrency`.\n */\n missingRateCurrencies: string[]\n}\n\ntype AggregateResponse = {\n baseCurrencyCode: string | null\n perStage: StageAggregate[]\n}\n\nconst stageBreakdownByCurrencySchema = z.object({\n currency: z.string(),\n total: z.number(),\n count: z.number(),\n})\n\nconst stageAggregateSchema = z.object({\n stageId: z.string(),\n count: z.number(),\n openCount: z.number(),\n totalInBaseCurrency: z.number(),\n byCurrency: z.array(stageBreakdownByCurrencySchema),\n convertedAll: z.boolean(),\n missingRateCurrencies: z.array(z.string()),\n})\n\nconst aggregateResponseSchema = z.object({\n baseCurrencyCode: z.string().nullable(),\n perStage: z.array(stageAggregateSchema),\n})\n\nconst aggregateErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Deals aggregate per pipeline stage',\n methods: {\n GET: {\n summary: 'Per-stage counts and currency totals for kanban lane headers',\n description:\n 'Returns per-stage counts and totals for deals, with values converted to the tenant base currency where rates are available. Used to power kanban lane headers without loading every deal.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Per-stage aggregate payload', schema: aggregateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: aggregateErrorSchema },\n { status: 401, description: 'Unauthorized', schema: aggregateErrorSchema },\n ],\n },\n },\n}\n\nfunction readArrayParam(searchParams: URLSearchParams, key: string): string[] | null {\n const all = searchParams.getAll(key)\n if (!all.length) return null\n const flat = all.flatMap((v) => v.split(','))\n const trimmed = flat.map((s) => s.trim()).filter(Boolean)\n return trimmed.length ? trimmed : null\n}\n\nfunction restrictToIds(where: string[], values: Array<string | number | null>, ids: string[]) {\n if (ids.length === 0) {\n where.push('id = ?')\n values.push('00000000-0000-0000-0000-000000000000')\n return\n }\n const placeholders = ids.map(() => '?').join(',')\n where.push(`id IN (${placeholders})`)\n values.push(...ids)\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const params = url.searchParams\n const parsed = querySchema.safeParse({\n pipelineId: params.get('pipelineId') ?? undefined,\n search: params.get('search') ?? undefined,\n status: readArrayParam(params, 'status') ?? undefined,\n ownerUserId: readArrayParam(params, 'ownerUserId') ?? undefined,\n personId: readArrayParam(params, 'personId') ?? undefined,\n companyId: readArrayParam(params, 'companyId') ?? undefined,\n isStuck: params.get('isStuck') ?? undefined,\n isOverdue: params.get('isOverdue') ?? undefined,\n expectedCloseAtFrom: params.get('expectedCloseAtFrom') ?? undefined,\n expectedCloseAtTo: params.get('expectedCloseAtTo') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<CoreEntityManager>('em')\n\n // Resolve organization scope from the request so multi-org operators can aggregate\n // deals for any org their RBAC scope permits (matching the deal detail route at\n // [id]/route.ts:360). Falls back to `auth.orgId` when no scope cookie is set or\n // when rbacService cannot be resolved (e.g. in unit tests with a minimal container).\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const effectiveTenantId = scope.tenantId ?? auth.tenantId\n if (!effectiveTenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const orgFilterIds = await resolveDealsOrganizationIds({ em, scope, auth, tenantId: effectiveTenantId })\n\n // Raw SQL is used here intentionally \u2014 the route only projects non-encrypted columns\n // (`pipeline_stage_id`, `value_amount`, `value_currency`, `status`, plus filters). It\n // avoids the per-row decryption cost that would be paid by `findWithDecryption` for an\n // aggregate that never reads `title`/`description`. The search path still relies on\n // the token index above to find matching deals when encrypted columns are involved.\n const baseCurrencyCode = await resolveOptionalBaseCurrencyCode(\n container,\n effectiveTenantId,\n orgFilterIds[0],\n )\n\n // Build WHERE clause shared between count + sum queries\n const orgPlaceholders = orgFilterIds.map(() => '?').join(',')\n const where: string[] = [\n 'tenant_id = ?',\n `organization_id IN (${orgPlaceholders})`,\n 'deleted_at IS NULL',\n ]\n const values: Array<string | number | null> = [effectiveTenantId, ...orgFilterIds]\n\n if (parsed.data.pipelineId) {\n where.push('pipeline_id = ?')\n values.push(parsed.data.pipelineId)\n }\n const search = parsed.data.search?.trim()\n if (search) {\n const searchCtx: CrudCtx = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: orgFilterIds[0],\n organizationIds: orgFilterIds,\n request: req,\n }\n const matchingIds = await findMatchingEntityIdsBySearchTokensAcrossSources({\n ctx: searchCtx,\n query: search,\n sources: [\n {\n entityType: E.customers.customer_deal,\n fields: [\n 'title',\n 'description',\n 'status',\n 'pipeline_stage',\n 'source',\n 'value_amount',\n 'value_currency',\n 'cf:competitive_risk',\n 'cf:implementation_complexity',\n ],\n },\n ],\n })\n if (matchingIds !== null && matchingIds.length > 0) {\n restrictToIds(where, values, matchingIds)\n } else if (isTenantDataEncryptionEnabled()) {\n // `customers:customer_deal.title` and `.description` are declared in\n // `encryption.ts` as encrypted columns. A raw `ILIKE` over the ciphertext would\n // silently match nothing on encrypted tenants and produce misleading lane totals,\n // so we collapse the result to zero rows instead \u2014 consistent with the list\n // endpoint's behavior when the token index has nothing to offer.\n restrictToIds(where, values, [])\n } else {\n const searchPattern = `%${escapeLikePattern(search)}%`\n where.push(\"(title ILIKE ? ESCAPE '\\\\' OR description ILIKE ? ESCAPE '\\\\')\")\n values.push(searchPattern, searchPattern)\n }\n }\n if (parsed.data.status && parsed.data.status.length) {\n const placeholders = parsed.data.status.map(() => '?').join(',')\n where.push(`status IN (${placeholders})`)\n values.push(...parsed.data.status)\n }\n if (parsed.data.ownerUserId && parsed.data.ownerUserId.length) {\n const placeholders = parsed.data.ownerUserId.map(() => '?').join(',')\n where.push(`owner_user_id IN (${placeholders})`)\n values.push(...parsed.data.ownerUserId)\n }\n if (parsed.data.expectedCloseAtFrom) {\n where.push('expected_close_at >= ?')\n values.push(parsed.data.expectedCloseAtFrom)\n }\n if (parsed.data.expectedCloseAtTo) {\n where.push('expected_close_at <= ?')\n values.push(parsed.data.expectedCloseAtTo)\n }\n if (parsed.data.isOverdue) {\n where.push(\"expected_close_at < CURRENT_DATE AND status = 'open'\")\n }\n if (parsed.data.isStuck) {\n // Reuse the list endpoint's stuck-deal lookup so kanban headers, lane counts, and the\n // cards rendered inside each lane agree on the same definition of \"stuck\". Empty result\n // \u2192 narrow to a sentinel UUID so the WHERE clause collapses to zero rows.\n const stuckIds = await fetchStuckDealIds(em as unknown as PgEntityManager, orgFilterIds[0], effectiveTenantId)\n restrictToIds(where, values, stuckIds)\n }\n if (parsed.data.personId && parsed.data.personId.length) {\n const placeholders = parsed.data.personId.map(() => '?').join(',')\n where.push(`EXISTS (SELECT 1 FROM customer_deal_people dp WHERE dp.deal_id = customer_deals.id AND dp.person_entity_id IN (${placeholders}))`)\n values.push(...parsed.data.personId)\n }\n if (parsed.data.companyId && parsed.data.companyId.length) {\n const placeholders = parsed.data.companyId.map(() => '?').join(',')\n where.push(`EXISTS (SELECT 1 FROM customer_deal_companies dc WHERE dc.deal_id = customer_deals.id AND dc.company_entity_id IN (${placeholders}))`)\n values.push(...parsed.data.companyId)\n }\n\n const whereSql = where.join(' AND ')\n\n // Aggregate by (stage, currency). Treat null stage as the literal `__unassigned`.\n const rows = await em.getConnection().execute<\n Array<{\n stage_id: string | null\n currency: string | null\n total: string | number | null\n count: string | number\n open_count: string | number\n }>\n >(\n `SELECT\n pipeline_stage_id AS stage_id,\n UPPER(COALESCE(value_currency, '')) AS currency,\n COALESCE(SUM(value_amount), 0) AS total,\n COUNT(*) AS count,\n COUNT(*) FILTER (WHERE status = 'open') AS open_count\n FROM customer_deals\n WHERE ${whereSql}\n GROUP BY pipeline_stage_id, UPPER(COALESCE(value_currency, ''))`,\n values,\n )\n\n // Reduce to per-stage aggregates\n const stageMap = new Map<string, StageAggregate>()\n for (const row of rows) {\n const stageId = row.stage_id ?? '__unassigned'\n const total = Number(row.total ?? 0)\n const count = Number(row.count ?? 0)\n const openCount = Number(row.open_count ?? 0)\n const currency = (row.currency ?? '').toString().trim()\n\n if (!stageMap.has(stageId)) {\n stageMap.set(stageId, {\n stageId,\n count: 0,\n openCount: 0,\n totalInBaseCurrency: 0,\n byCurrency: [],\n convertedAll: true,\n missingRateCurrencies: [],\n })\n }\n const agg = stageMap.get(stageId)!\n agg.count += count\n agg.openCount += openCount\n // Include any row that has a real currency code, even when its summed amount is\n // zero \u2014 `byCurrency` should agree with `count` so the per-currency breakdown\n // doesn't silently drop deals whose amount happens to be zero. Rows with no\n // currency code at all still get folded into the stage totals via `count`.\n if (currency.length > 0) {\n agg.byCurrency.push({ currency, total, count })\n }\n }\n\n // Convert to base currency where possible\n if (baseCurrencyCode) {\n const exchange = container.resolve('exchangeRateService') as ExchangeRateService | undefined\n const today = new Date()\n const distinctCurrencies = new Set<string>()\n for (const agg of stageMap.values()) {\n for (const row of agg.byCurrency) {\n if (row.currency && row.currency !== baseCurrencyCode) {\n distinctCurrencies.add(row.currency)\n }\n }\n }\n\n const rateCache = new Map<string, number>()\n if (exchange && distinctCurrencies.size > 0) {\n const pairs = Array.from(distinctCurrencies).map((c) => ({\n fromCurrencyCode: c,\n toCurrencyCode: baseCurrencyCode,\n }))\n try {\n const results = await exchange.getRates({\n pairs,\n date: today,\n scope: { tenantId: effectiveTenantId, organizationId: orgFilterIds[0] },\n options: { maxDaysBack: 60, autoFetch: false },\n })\n for (const [key, rateResult] of results) {\n if (rateResult.rates.length > 0) {\n // Pick the first matching rate (sources are equivalent for display purposes)\n const rate = Number(rateResult.rates[0].rate)\n if (Number.isFinite(rate) && rate > 0) {\n rateCache.set(key, rate)\n }\n }\n }\n } catch (err) {\n // Swallow \u2014 partial totals are still useful and we'll fall back to currency-native\n // sums. Logging at warn level so operators can correlate missing-rate disclosures in\n // the UI (`convertedAll: false` / `missingRateCurrencies`) with the underlying error.\n logger.warn('exchange-rate lookup failed; falling back to per-currency totals', { component: 'deals.aggregate', err })\n }\n }\n\n for (const agg of stageMap.values()) {\n let totalBase = 0\n let convertedAll = true\n const missingRateCurrencies: string[] = []\n for (const row of agg.byCurrency) {\n if (!row.currency) continue\n if (row.currency === baseCurrencyCode) {\n totalBase += row.total\n continue\n }\n const key = `${row.currency}/${baseCurrencyCode}`\n const rate = rateCache.get(key)\n if (rate !== undefined) {\n totalBase += row.total * rate\n } else {\n convertedAll = false\n if (!missingRateCurrencies.includes(row.currency)) {\n missingRateCurrencies.push(row.currency)\n }\n }\n }\n // Even when some rates are missing, totalInBaseCurrency reflects the converted slice.\n // `convertedAll`/`missingRateCurrencies` let the client distinguish a complete\n // conversion from a partial one and disclose which currencies were excluded.\n agg.totalInBaseCurrency = Math.round(totalBase)\n agg.convertedAll = convertedAll\n agg.missingRateCurrencies = missingRateCurrencies\n }\n } else {\n // No base currency configured for the tenant \u2014 surface this by marking every stage as\n // \"not converted\" with all currencies flagged as missing rates. This way the client UI\n // can fall back to a per-currency breakdown without trying to render a board total.\n for (const agg of stageMap.values()) {\n const present = agg.byCurrency.map((row) => row.currency).filter((c): c is string => !!c)\n if (present.length > 0) {\n agg.convertedAll = false\n agg.missingRateCurrencies = Array.from(new Set(present))\n }\n }\n }\n\n // Sort byCurrency rows by total descending for stable display\n for (const agg of stageMap.values()) {\n agg.byCurrency.sort((a, b) => b.total - a.total)\n }\n\n const response: AggregateResponse = {\n baseCurrencyCode,\n perStage: Array.from(stageMap.values()),\n }\n\n return NextResponse.json(response)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,0CAA0C;AACnD,SAAS,mCAAmC;AAC5C,SAAS,uCAAuC;AAEhD,SAAS,+BAA+B;AACxC,SAAS,yBAAyB;AAElC,SAAS,qCAAqC;AAE9C,SAAS,yBAAyB;AAClC,SAAS,wDAAwD;AACjE,SAAS,SAAS;AAClB,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAEhC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,sBAAsB,EAAE;AACtE;AAEA,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,QAAQ,EAAE,MAAM,EAAE,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager as CoreEntityManager } from '@mikro-orm/core'\nimport type { EntityManager as PgEntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveDealsOrganizationIds } from '../../../lib/dealsOrganizationScope'\nimport { resolveOptionalBaseCurrencyCode } from '../../../lib/optionalBaseCurrency'\nimport type { ExchangeRateService } from '@open-mercato/core/modules/currencies/services/exchangeRateService'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { fetchStuckDealIds } from '../../../lib/stuckDeals'\nimport { expandDealStatusAliases } from '../../../lib/dealStatus'\nimport { findMatchingEntityIdsBySearchTokensAcrossSources } from '../../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.deals.view'] },\n}\n\nconst querySchema = z.object({\n pipelineId: z.string().uuid().optional(),\n search: z.string().optional(),\n status: z.array(z.string().max(50)).max(20).optional(),\n ownerUserId: z.array(z.string().uuid()).optional(),\n personId: z.array(z.string().uuid()).optional(),\n companyId: z.array(z.string().uuid()).optional(),\n isStuck: z.preprocess((value) => {\n const parsed = parseBooleanFromUnknown(value)\n return parsed === null ? value : parsed\n }, z.boolean()).optional(),\n isOverdue: z.preprocess((value) => {\n const parsed = parseBooleanFromUnknown(value)\n return parsed === null ? value : parsed\n }, z.boolean()).optional(),\n expectedCloseAtFrom: z.string().optional(),\n expectedCloseAtTo: z.string().optional(),\n})\n\ntype StageBreakdownByCurrency = {\n currency: string\n total: number\n count: number\n}\n\ntype StageAggregate = {\n stageId: string\n count: number\n openCount: number\n totalInBaseCurrency: number\n byCurrency: StageBreakdownByCurrency[]\n /**\n * `true` when every currency present in `byCurrency` was either the base currency or\n * had a usable exchange rate at request time. `false` when at least one currency in\n * `byCurrency` could not be resolved to base \u2014 the lane header should surface a\n * \"partial\" indicator in that case so the converted total isn't read as authoritative.\n */\n convertedAll: boolean\n /**\n * Currencies present in `byCurrency` that had NO exchange rate to the base currency.\n * Empty when conversion was complete or there is no base currency configured. The\n * client uses this to disclose which slice of the value is excluded from `totalInBaseCurrency`.\n */\n missingRateCurrencies: string[]\n}\n\ntype AggregateResponse = {\n baseCurrencyCode: string | null\n perStage: StageAggregate[]\n}\n\nconst stageBreakdownByCurrencySchema = z.object({\n currency: z.string(),\n total: z.number(),\n count: z.number(),\n})\n\nconst stageAggregateSchema = z.object({\n stageId: z.string(),\n count: z.number(),\n openCount: z.number(),\n totalInBaseCurrency: z.number(),\n byCurrency: z.array(stageBreakdownByCurrencySchema),\n convertedAll: z.boolean(),\n missingRateCurrencies: z.array(z.string()),\n})\n\nconst aggregateResponseSchema = z.object({\n baseCurrencyCode: z.string().nullable(),\n perStage: z.array(stageAggregateSchema),\n})\n\nconst aggregateErrorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Customers',\n summary: 'Deals aggregate per pipeline stage',\n methods: {\n GET: {\n summary: 'Per-stage counts and currency totals for kanban lane headers',\n description:\n 'Returns per-stage counts and totals for deals, with values converted to the tenant base currency where rates are available. Used to power kanban lane headers without loading every deal.',\n query: querySchema,\n responses: [\n { status: 200, description: 'Per-stage aggregate payload', schema: aggregateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: aggregateErrorSchema },\n { status: 401, description: 'Unauthorized', schema: aggregateErrorSchema },\n ],\n },\n },\n}\n\nfunction readArrayParam(searchParams: URLSearchParams, key: string): string[] | null {\n const all = searchParams.getAll(key)\n if (!all.length) return null\n const flat = all.flatMap((v) => v.split(','))\n const trimmed = flat.map((s) => s.trim()).filter(Boolean)\n return trimmed.length ? trimmed : null\n}\n\nfunction restrictToIds(where: string[], values: Array<string | number | null>, ids: string[]) {\n if (ids.length === 0) {\n where.push('id = ?')\n values.push('00000000-0000-0000-0000-000000000000')\n return\n }\n const placeholders = ids.map(() => '?').join(',')\n where.push(`id IN (${placeholders})`)\n values.push(...ids)\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const params = url.searchParams\n const parsed = querySchema.safeParse({\n pipelineId: params.get('pipelineId') ?? undefined,\n search: params.get('search') ?? undefined,\n status: readArrayParam(params, 'status') ?? undefined,\n ownerUserId: readArrayParam(params, 'ownerUserId') ?? undefined,\n personId: readArrayParam(params, 'personId') ?? undefined,\n companyId: readArrayParam(params, 'companyId') ?? undefined,\n isStuck: params.get('isStuck') ?? undefined,\n isOverdue: params.get('isOverdue') ?? undefined,\n expectedCloseAtFrom: params.get('expectedCloseAtFrom') ?? undefined,\n expectedCloseAtTo: params.get('expectedCloseAtTo') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid query parameters' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve<CoreEntityManager>('em')\n\n // Resolve organization scope from the request so multi-org operators can aggregate\n // deals for any org their RBAC scope permits (matching the deal detail route at\n // [id]/route.ts:360). Falls back to `auth.orgId` when no scope cookie is set or\n // when rbacService cannot be resolved (e.g. in unit tests with a minimal container).\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const effectiveTenantId = scope.tenantId ?? auth.tenantId\n if (!effectiveTenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const orgFilterIds = await resolveDealsOrganizationIds({ em, scope, auth, tenantId: effectiveTenantId })\n\n // Raw SQL is used here intentionally \u2014 the route only projects non-encrypted columns\n // (`pipeline_stage_id`, `value_amount`, `value_currency`, `status`, plus filters). It\n // avoids the per-row decryption cost that would be paid by `findWithDecryption` for an\n // aggregate that never reads `title`/`description`. The search path still relies on\n // the token index above to find matching deals when encrypted columns are involved.\n const baseCurrencyCode = await resolveOptionalBaseCurrencyCode(\n container,\n effectiveTenantId,\n orgFilterIds[0],\n )\n\n // Build WHERE clause shared between count + sum queries\n const orgPlaceholders = orgFilterIds.map(() => '?').join(',')\n const where: string[] = [\n 'tenant_id = ?',\n `organization_id IN (${orgPlaceholders})`,\n 'deleted_at IS NULL',\n ]\n const values: Array<string | number | null> = [effectiveTenantId, ...orgFilterIds]\n\n if (parsed.data.pipelineId) {\n where.push('pipeline_id = ?')\n values.push(parsed.data.pipelineId)\n }\n const search = parsed.data.search?.trim()\n if (search) {\n const searchCtx: CrudCtx = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: orgFilterIds[0],\n organizationIds: orgFilterIds,\n request: req,\n }\n const matchingIds = await findMatchingEntityIdsBySearchTokensAcrossSources({\n ctx: searchCtx,\n query: search,\n sources: [\n {\n entityType: E.customers.customer_deal,\n fields: [\n 'title',\n 'description',\n 'status',\n 'pipeline_stage',\n 'source',\n 'value_amount',\n 'value_currency',\n 'cf:competitive_risk',\n 'cf:implementation_complexity',\n ],\n },\n ],\n })\n if (matchingIds !== null && matchingIds.length > 0) {\n restrictToIds(where, values, matchingIds)\n } else if (isTenantDataEncryptionEnabled()) {\n // `customers:customer_deal.title` and `.description` are declared in\n // `encryption.ts` as encrypted columns. A raw `ILIKE` over the ciphertext would\n // silently match nothing on encrypted tenants and produce misleading lane totals,\n // so we collapse the result to zero rows instead \u2014 consistent with the list\n // endpoint's behavior when the token index has nothing to offer.\n restrictToIds(where, values, [])\n } else {\n const searchPattern = `%${escapeLikePattern(search)}%`\n where.push(\"(title ILIKE ? ESCAPE '\\\\' OR description ILIKE ? ESCAPE '\\\\')\")\n values.push(searchPattern, searchPattern)\n }\n }\n if (parsed.data.status && parsed.data.status.length) {\n // Non-empty input always expands to a non-empty set, so this always narrows.\n const expandedStatuses = expandDealStatusAliases(parsed.data.status)\n const placeholders = expandedStatuses.map(() => '?').join(',')\n where.push(`status IN (${placeholders})`)\n values.push(...expandedStatuses)\n }\n if (parsed.data.ownerUserId && parsed.data.ownerUserId.length) {\n const placeholders = parsed.data.ownerUserId.map(() => '?').join(',')\n where.push(`owner_user_id IN (${placeholders})`)\n values.push(...parsed.data.ownerUserId)\n }\n if (parsed.data.expectedCloseAtFrom) {\n where.push('expected_close_at >= ?')\n values.push(parsed.data.expectedCloseAtFrom)\n }\n if (parsed.data.expectedCloseAtTo) {\n where.push('expected_close_at <= ?')\n values.push(parsed.data.expectedCloseAtTo)\n }\n if (parsed.data.isOverdue) {\n // Mirror the list route's precedence: the caller-supplied status filter wins, and\n // status='open' is injected only when none was provided.\n const hasCallerStatus = !!parsed.data.status?.length\n if (hasCallerStatus) {\n where.push('expected_close_at < CURRENT_DATE')\n } else {\n where.push(\"expected_close_at < CURRENT_DATE AND status = 'open'\")\n }\n }\n if (parsed.data.isStuck) {\n // Reuse the list endpoint's stuck-deal lookup so kanban headers, lane counts, and the\n // cards rendered inside each lane agree on the same definition of \"stuck\". Empty result\n // \u2192 narrow to a sentinel UUID so the WHERE clause collapses to zero rows.\n const stuckIds = await fetchStuckDealIds(em as unknown as PgEntityManager, orgFilterIds[0], effectiveTenantId)\n restrictToIds(where, values, stuckIds)\n }\n if (parsed.data.personId && parsed.data.personId.length) {\n const placeholders = parsed.data.personId.map(() => '?').join(',')\n where.push(`EXISTS (SELECT 1 FROM customer_deal_people dp WHERE dp.deal_id = customer_deals.id AND dp.person_entity_id IN (${placeholders}))`)\n values.push(...parsed.data.personId)\n }\n if (parsed.data.companyId && parsed.data.companyId.length) {\n const placeholders = parsed.data.companyId.map(() => '?').join(',')\n where.push(`EXISTS (SELECT 1 FROM customer_deal_companies dc WHERE dc.deal_id = customer_deals.id AND dc.company_entity_id IN (${placeholders}))`)\n values.push(...parsed.data.companyId)\n }\n\n const whereSql = where.join(' AND ')\n\n // Aggregate by (stage, currency). Treat null stage as the literal `__unassigned`.\n const rows = await em.getConnection().execute<\n Array<{\n stage_id: string | null\n currency: string | null\n total: string | number | null\n count: string | number\n open_count: string | number\n }>\n >(\n `SELECT\n pipeline_stage_id AS stage_id,\n UPPER(COALESCE(value_currency, '')) AS currency,\n COALESCE(SUM(value_amount), 0) AS total,\n COUNT(*) AS count,\n COUNT(*) FILTER (WHERE status = 'open') AS open_count\n FROM customer_deals\n WHERE ${whereSql}\n GROUP BY pipeline_stage_id, UPPER(COALESCE(value_currency, ''))`,\n values,\n )\n\n // Reduce to per-stage aggregates\n const stageMap = new Map<string, StageAggregate>()\n for (const row of rows) {\n const stageId = row.stage_id ?? '__unassigned'\n const total = Number(row.total ?? 0)\n const count = Number(row.count ?? 0)\n const openCount = Number(row.open_count ?? 0)\n const currency = (row.currency ?? '').toString().trim()\n\n if (!stageMap.has(stageId)) {\n stageMap.set(stageId, {\n stageId,\n count: 0,\n openCount: 0,\n totalInBaseCurrency: 0,\n byCurrency: [],\n convertedAll: true,\n missingRateCurrencies: [],\n })\n }\n const agg = stageMap.get(stageId)!\n agg.count += count\n agg.openCount += openCount\n // Include any row that has a real currency code, even when its summed amount is\n // zero \u2014 `byCurrency` should agree with `count` so the per-currency breakdown\n // doesn't silently drop deals whose amount happens to be zero. Rows with no\n // currency code at all still get folded into the stage totals via `count`.\n if (currency.length > 0) {\n agg.byCurrency.push({ currency, total, count })\n }\n }\n\n // Convert to base currency where possible\n if (baseCurrencyCode) {\n const exchange = container.resolve('exchangeRateService') as ExchangeRateService | undefined\n const today = new Date()\n const distinctCurrencies = new Set<string>()\n for (const agg of stageMap.values()) {\n for (const row of agg.byCurrency) {\n if (row.currency && row.currency !== baseCurrencyCode) {\n distinctCurrencies.add(row.currency)\n }\n }\n }\n\n const rateCache = new Map<string, number>()\n if (exchange && distinctCurrencies.size > 0) {\n const pairs = Array.from(distinctCurrencies).map((c) => ({\n fromCurrencyCode: c,\n toCurrencyCode: baseCurrencyCode,\n }))\n try {\n const results = await exchange.getRates({\n pairs,\n date: today,\n scope: { tenantId: effectiveTenantId, organizationId: orgFilterIds[0] },\n options: { maxDaysBack: 60, autoFetch: false },\n })\n for (const [key, rateResult] of results) {\n if (rateResult.rates.length > 0) {\n // Pick the first matching rate (sources are equivalent for display purposes)\n const rate = Number(rateResult.rates[0].rate)\n if (Number.isFinite(rate) && rate > 0) {\n rateCache.set(key, rate)\n }\n }\n }\n } catch (err) {\n // Swallow \u2014 partial totals are still useful and we'll fall back to currency-native\n // sums. Logging at warn level so operators can correlate missing-rate disclosures in\n // the UI (`convertedAll: false` / `missingRateCurrencies`) with the underlying error.\n logger.warn('exchange-rate lookup failed; falling back to per-currency totals', { component: 'deals.aggregate', err })\n }\n }\n\n for (const agg of stageMap.values()) {\n let totalBase = 0\n let convertedAll = true\n const missingRateCurrencies: string[] = []\n for (const row of agg.byCurrency) {\n if (!row.currency) continue\n if (row.currency === baseCurrencyCode) {\n totalBase += row.total\n continue\n }\n const key = `${row.currency}/${baseCurrencyCode}`\n const rate = rateCache.get(key)\n if (rate !== undefined) {\n totalBase += row.total * rate\n } else {\n convertedAll = false\n if (!missingRateCurrencies.includes(row.currency)) {\n missingRateCurrencies.push(row.currency)\n }\n }\n }\n // Even when some rates are missing, totalInBaseCurrency reflects the converted slice.\n // `convertedAll`/`missingRateCurrencies` let the client distinguish a complete\n // conversion from a partial one and disclose which currencies were excluded.\n agg.totalInBaseCurrency = Math.round(totalBase)\n agg.convertedAll = convertedAll\n agg.missingRateCurrencies = missingRateCurrencies\n }\n } else {\n // No base currency configured for the tenant \u2014 surface this by marking every stage as\n // \"not converted\" with all currencies flagged as missing rates. This way the client UI\n // can fall back to a per-currency breakdown without trying to render a board total.\n for (const agg of stageMap.values()) {\n const present = agg.byCurrency.map((row) => row.currency).filter((c): c is string => !!c)\n if (present.length > 0) {\n agg.convertedAll = false\n agg.missingRateCurrencies = Array.from(new Set(present))\n }\n }\n }\n\n // Sort byCurrency rows by total descending for stable display\n for (const agg of stageMap.values()) {\n agg.byCurrency.sort((a, b) => b.total - a.total)\n }\n\n const response: AggregateResponse = {\n baseCurrencyCode,\n perStage: Array.from(stageMap.values()),\n }\n\n return NextResponse.json(response)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -422,7 +422,8 @@ async function GET(req) {
|
|
|
422
422
|
total: res.total,
|
|
423
423
|
page: query.page,
|
|
424
424
|
pageSize: query.pageSize,
|
|
425
|
-
totalPages: Math.ceil(res.total / (query.pageSize || 1))
|
|
425
|
+
totalPages: Math.ceil(res.total / (query.pageSize || 1)),
|
|
426
|
+
...res.meta?.listCountCapWarning ? { totalIsCapped: true } : {}
|
|
426
427
|
});
|
|
427
428
|
}
|
|
428
429
|
export {
|