@open-mercato/core 0.4.2-canary-10c7a8bf2a → 0.4.2-canary-c84cff7ed5
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/dist/generated/entities/notification/index.js +57 -0
- package/dist/generated/entities/notification/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +5 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js +3 -2
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/api_keys/backend/api-keys/page.js +1 -1
- package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js +4 -0
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +2 -0
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +4 -3
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/profile/route.js +157 -0
- package/dist/modules/auth/api/profile/route.js.map +7 -0
- package/dist/modules/auth/api/reset/confirm.js +25 -2
- package/dist/modules/auth/api/reset/confirm.js.map +2 -2
- package/dist/modules/auth/api/reset.js +23 -0
- package/dist/modules/auth/api/reset.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +14 -9
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +4 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/auth/profile/page.js +141 -0
- package/dist/modules/auth/backend/auth/profile/page.js.map +7 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js +13 -0
- package/dist/modules/auth/backend/auth/profile/page.meta.js.map +7 -0
- package/dist/modules/auth/backend/roles/page.js +3 -3
- package/dist/modules/auth/backend/roles/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +14 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +15 -2
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +3 -3
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/cli.js +13 -0
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/commands/users.js +59 -2
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/validators.js +4 -2
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/frontend/reset/[token]/page.js +20 -10
- package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +3 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/notifications.js +112 -0
- package/dist/modules/auth/notifications.js.map +7 -0
- package/dist/modules/auth/services/authService.js +3 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/business_rules/backend/rules/page.js +4 -0
- package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
- package/dist/modules/business_rules/backend/sets/page.js +3 -0
- package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +28 -0
- package/dist/modules/business_rules/notifications.js.map +7 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js +37 -0
- package/dist/modules/business_rules/subscribers/rule-execution-failed-notification.js.map +7 -0
- package/dist/modules/catalog/analytics.js +27 -0
- package/dist/modules/catalog/analytics.js.map +7 -0
- package/dist/modules/catalog/components/PriceKindSettings.js +2 -0
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -2
- package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductsDataTable.js +2 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/notifications.js +28 -0
- package/dist/modules/catalog/notifications.js.map +7 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js +38 -0
- package/dist/modules/catalog/subscribers/low-stock-notification.js.map +7 -0
- package/dist/modules/configs/cli.js +6 -0
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +18 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +3 -0
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +2 -0
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/customers/analytics.js +50 -0
- package/dist/modules/customers/analytics.js.map +7 -0
- package/dist/modules/customers/backend/customers/companies/page.js +3 -0
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +3 -0
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +3 -0
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +31 -0
- package/dist/modules/customers/commands/deals.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/notifications.js +48 -0
- package/dist/modules/customers/notifications.js.map +7 -0
- package/dist/modules/dashboards/acl.js +2 -1
- package/dist/modules/dashboards/acl.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +187 -0
- package/dist/modules/dashboards/api/widgets/data/route.js.map +7 -0
- package/dist/modules/dashboards/cli.js +173 -1
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/di.js +11 -0
- package/dist/modules/dashboards/di.js.map +7 -0
- package/dist/modules/dashboards/lib/aggregations.js +162 -0
- package/dist/modules/dashboards/lib/aggregations.js.map +7 -0
- package/dist/modules/dashboards/lib/formatters.js +34 -0
- package/dist/modules/dashboards/lib/formatters.js.map +7 -0
- package/dist/modules/dashboards/lib/role-widgets.js +58 -0
- package/dist/modules/dashboards/lib/role-widgets.js.map +7 -0
- package/dist/modules/dashboards/seed/analytics.js +383 -0
- package/dist/modules/dashboards/seed/analytics.js.map +7 -0
- package/dist/modules/dashboards/services/analyticsRegistry.js +52 -0
- package/dist/modules/dashboards/services/analyticsRegistry.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +207 -0
- package/dist/modules/dashboards/services/widgetDataService.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +128 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +126 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +151 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +126 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js +16 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +123 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +128 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js +21 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +211 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js +19 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +131 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js +19 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +153 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/config.js +22 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +180 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +2 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/SystemEntitiesTable.js +1 -1
- package/dist/modules/entities/components/SystemEntitiesTable.js.map +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js +2 -2
- package/dist/modules/entities/components/UserEntitiesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +3 -3
- package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
- package/dist/modules/feature_toggles/components/OverridesTable.js +1 -1
- package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
- package/dist/modules/notifications/acl.js +11 -0
- package/dist/modules/notifications/acl.js.map +7 -0
- package/dist/modules/notifications/api/[id]/action/route.js +74 -0
- package/dist/modules/notifications/api/[id]/action/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js +15 -0
- package/dist/modules/notifications/api/[id]/dismiss/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/read/route.js +15 -0
- package/dist/modules/notifications/api/[id]/read/route.js.map +7 -0
- package/dist/modules/notifications/api/[id]/restore/route.js +53 -0
- package/dist/modules/notifications/api/[id]/restore/route.js.map +7 -0
- package/dist/modules/notifications/api/batch/route.js +17 -0
- package/dist/modules/notifications/api/batch/route.js.map +7 -0
- package/dist/modules/notifications/api/feature/route.js +17 -0
- package/dist/modules/notifications/api/feature/route.js.map +7 -0
- package/dist/modules/notifications/api/mark-all-read/route.js +35 -0
- package/dist/modules/notifications/api/mark-all-read/route.js.map +7 -0
- package/dist/modules/notifications/api/openapi.js +76 -0
- package/dist/modules/notifications/api/openapi.js.map +7 -0
- package/dist/modules/notifications/api/role/route.js +17 -0
- package/dist/modules/notifications/api/role/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +85 -0
- package/dist/modules/notifications/api/route.js.map +7 -0
- package/dist/modules/notifications/api/settings/route.js +155 -0
- package/dist/modules/notifications/api/settings/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +38 -0
- package/dist/modules/notifications/api/unread-count/route.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.js +10 -0
- package/dist/modules/notifications/backend/config/notifications/page.js.map +7 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js +24 -0
- package/dist/modules/notifications/backend/config/notifications/page.meta.js.map +7 -0
- package/dist/modules/notifications/cli.js +16 -0
- package/dist/modules/notifications/cli.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +112 -0
- package/dist/modules/notifications/data/entities.js.map +7 -0
- package/dist/modules/notifications/data/validators.js +94 -0
- package/dist/modules/notifications/data/validators.js.map +7 -0
- package/dist/modules/notifications/di.js +13 -0
- package/dist/modules/notifications/di.js.map +7 -0
- package/dist/modules/notifications/emails/NotificationEmail.js +58 -0
- package/dist/modules/notifications/emails/NotificationEmail.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js +44 -0
- package/dist/modules/notifications/frontend/NotificationInboxPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +219 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +7 -0
- package/dist/modules/notifications/index.js +14 -0
- package/dist/modules/notifications/index.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryConfig.js +105 -0
- package/dist/modules/notifications/lib/deliveryConfig.js.map +7 -0
- package/dist/modules/notifications/lib/events.js +12 -0
- package/dist/modules/notifications/lib/events.js.map +7 -0
- package/dist/modules/notifications/lib/notificationBuilder.js +66 -0
- package/dist/modules/notifications/lib/notificationBuilder.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +54 -0
- package/dist/modules/notifications/lib/notificationFactory.js.map +7 -0
- package/dist/modules/notifications/lib/notificationMapper.js +34 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +35 -0
- package/dist/modules/notifications/lib/notificationRecipients.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +279 -0
- package/dist/modules/notifications/lib/notificationService.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +101 -0
- package/dist/modules/notifications/lib/routeHelpers.js.map +7 -0
- package/dist/modules/notifications/lib/safeHref.js +24 -0
- package/dist/modules/notifications/lib/safeHref.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js +70 -0
- package/dist/modules/notifications/migrations/Migration20260123000001.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js +37 -0
- package/dist/modules/notifications/migrations/Migration20260126150000.js.map +7 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js +139 -0
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +7 -0
- package/dist/modules/notifications/workers/create-notification.worker.js +70 -0
- package/dist/modules/notifications/workers/create-notification.worker.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +7 -1
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js +2 -2
- package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js +2 -2
- package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
- package/dist/modules/sales/analytics.js +67 -0
- package/dist/modules/sales/analytics.js.map +7 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +53 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/payments.js +26 -0
- package/dist/modules/sales/commands/payments.js.map +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js +2 -2
- package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/TaxRatesSettings.js +2 -2
- package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +2 -0
- package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +2 -0
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/PaymentsSection.js +2 -1
- package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -0
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/notifications.client.js +51 -0
- package/dist/modules/sales/notifications.client.js.map +7 -0
- package/dist/modules/sales/notifications.js +88 -0
- package/dist/modules/sales/notifications.js.map +7 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js +38 -0
- package/dist/modules/sales/subscribers/quote-expiring-notification.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +137 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/index.js +7 -0
- package/dist/modules/sales/widgets/notifications/index.js.map +7 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js +60 -0
- package/dist/modules/sales/widgets/notifications/useSalesDocumentTotals.js.map +7 -0
- package/dist/modules/staff/backend/staff/team-members/page.js +1 -1
- package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js +2 -2
- package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js +2 -2
- package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
- package/dist/modules/staff/commands/leave-requests.js +79 -0
- package/dist/modules/staff/commands/leave-requests.js.map +2 -2
- package/dist/modules/staff/notifications.js +75 -0
- package/dist/modules/staff/notifications.js.map +7 -0
- package/dist/modules/workflows/backend/definitions/page.js +5 -0
- package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/page.js +3 -0
- package/dist/modules/workflows/backend/instances/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +3 -0
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/notifications.js +28 -0
- package/dist/modules/workflows/notifications.js.map +7 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js +38 -0
- package/dist/modules/workflows/subscribers/task-assigned-notification.js.map +7 -0
- package/generated/entities/notification/index.ts +27 -0
- package/generated/entities.ids.generated.ts +5 -1
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +3 -2
- package/src/modules/api_keys/backend/api-keys/page.tsx +1 -1
- package/src/modules/attachments/components/AttachmentLibrary.tsx +4 -0
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +2 -0
- package/src/modules/auth/api/admin/nav.ts +10 -6
- package/src/modules/auth/api/profile/route.ts +163 -0
- package/src/modules/auth/api/reset/confirm.ts +25 -2
- package/src/modules/auth/api/reset.ts +23 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +21 -12
- package/src/modules/auth/api/users/route.ts +5 -2
- package/src/modules/auth/backend/auth/profile/page.meta.ts +9 -0
- package/src/modules/auth/backend/auth/profile/page.tsx +174 -0
- package/src/modules/auth/backend/roles/page.tsx +3 -3
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +18 -2
- package/src/modules/auth/backend/users/create/page.tsx +19 -2
- package/src/modules/auth/backend/users/page.tsx +3 -3
- package/src/modules/auth/cli.ts +14 -0
- package/src/modules/auth/commands/users.ts +73 -2
- package/src/modules/auth/data/validators.ts +5 -2
- package/src/modules/auth/frontend/reset/[token]/page.tsx +24 -11
- package/src/modules/auth/i18n/de.json +43 -1
- package/src/modules/auth/i18n/en.json +43 -1
- package/src/modules/auth/i18n/es.json +43 -1
- package/src/modules/auth/i18n/pl.json +43 -1
- package/src/modules/auth/lib/setup-app.ts +3 -0
- package/src/modules/auth/notifications.ts +109 -0
- package/src/modules/auth/services/authService.ts +4 -4
- package/src/modules/business_rules/backend/rules/page.tsx +4 -0
- package/src/modules/business_rules/backend/sets/page.tsx +3 -0
- package/src/modules/business_rules/i18n/en.json +3 -1
- package/src/modules/business_rules/notifications.ts +25 -0
- package/src/modules/business_rules/subscribers/rule-execution-failed-notification.ts +50 -0
- package/src/modules/catalog/analytics.ts +24 -0
- package/src/modules/catalog/components/PriceKindSettings.tsx +2 -0
- package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +2 -2
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +2 -0
- package/src/modules/catalog/i18n/en.json +3 -1
- package/src/modules/catalog/notifications.ts +25 -0
- package/src/modules/catalog/subscribers/low-stock-notification.ts +52 -0
- package/src/modules/configs/cli.ts +6 -0
- package/src/modules/configs/lib/upgrade-actions.ts +18 -0
- package/src/modules/currencies/backend/currencies/page.tsx +3 -0
- package/src/modules/currencies/backend/exchange-rates/page.tsx +2 -0
- package/src/modules/customers/analytics.ts +47 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +3 -0
- package/src/modules/customers/backend/customers/deals/page.tsx +3 -0
- package/src/modules/customers/backend/customers/people/page.tsx +3 -0
- package/src/modules/customers/commands/deals.ts +39 -0
- package/src/modules/customers/components/CustomerTodosTable.tsx +1 -0
- package/src/modules/customers/i18n/en.json +5 -1
- package/src/modules/customers/notifications.ts +44 -0
- package/src/modules/dashboards/acl.ts +1 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +221 -0
- package/src/modules/dashboards/cli.ts +204 -1
- package/src/modules/dashboards/di.ts +9 -0
- package/src/modules/dashboards/i18n/de.json +115 -1
- package/src/modules/dashboards/i18n/en.json +115 -1
- package/src/modules/dashboards/i18n/es.json +115 -1
- package/src/modules/dashboards/i18n/pl.json +115 -1
- package/src/modules/dashboards/lib/__tests__/aggregations.test.ts +327 -0
- package/src/modules/dashboards/lib/__tests__/formatters.test.ts +128 -0
- package/src/modules/dashboards/lib/aggregations.ts +225 -0
- package/src/modules/dashboards/lib/formatters.ts +36 -0
- package/src/modules/dashboards/lib/role-widgets.ts +80 -0
- package/src/modules/dashboards/seed/analytics.ts +405 -0
- package/src/modules/dashboards/services/analyticsRegistry.ts +79 -0
- package/src/modules/dashboards/services/widgetDataService.ts +329 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +135 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +133 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +154 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +133 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +17 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +137 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +135 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/config.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +220 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/config.ts +21 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +131 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/config.ts +21 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +161 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/config.ts +27 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +181 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.ts +24 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +2 -0
- package/src/modules/directory/backend/directory/organizations/page.tsx +2 -2
- package/src/modules/directory/backend/directory/tenants/page.tsx +2 -2
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +2 -2
- package/src/modules/entities/components/SystemEntitiesTable.tsx +1 -1
- package/src/modules/entities/components/UserEntitiesTable.tsx +2 -2
- package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +3 -4
- package/src/modules/feature_toggles/components/OverridesTable.tsx +1 -1
- package/src/modules/notifications/acl.ts +7 -0
- package/src/modules/notifications/api/[id]/action/route.ts +75 -0
- package/src/modules/notifications/api/[id]/dismiss/route.ts +12 -0
- package/src/modules/notifications/api/[id]/read/route.ts +12 -0
- package/src/modules/notifications/api/[id]/restore/route.ts +53 -0
- package/src/modules/notifications/api/batch/route.ts +14 -0
- package/src/modules/notifications/api/feature/route.ts +14 -0
- package/src/modules/notifications/api/mark-all-read/route.ts +34 -0
- package/src/modules/notifications/api/openapi.ts +76 -0
- package/src/modules/notifications/api/role/route.ts +14 -0
- package/src/modules/notifications/api/route.ts +92 -0
- package/src/modules/notifications/api/settings/route.ts +157 -0
- package/src/modules/notifications/api/unread-count/route.ts +38 -0
- package/src/modules/notifications/backend/config/notifications/page.meta.ts +22 -0
- package/src/modules/notifications/backend/config/notifications/page.tsx +12 -0
- package/src/modules/notifications/cli.ts +18 -0
- package/src/modules/notifications/data/entities.ts +99 -0
- package/src/modules/notifications/data/validators.ts +110 -0
- package/src/modules/notifications/di.ts +11 -0
- package/src/modules/notifications/emails/NotificationEmail.tsx +98 -0
- package/src/modules/notifications/frontend/NotificationInboxPageClient.tsx +42 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +231 -0
- package/src/modules/notifications/i18n/de.json +50 -0
- package/src/modules/notifications/i18n/en.json +50 -0
- package/src/modules/notifications/i18n/es.json +50 -0
- package/src/modules/notifications/i18n/pl.json +50 -0
- package/src/modules/notifications/index.ts +12 -0
- package/src/modules/notifications/lib/deliveryConfig.ts +145 -0
- package/src/modules/notifications/lib/events.ts +48 -0
- package/src/modules/notifications/lib/notificationBuilder.ts +121 -0
- package/src/modules/notifications/lib/notificationFactory.ts +76 -0
- package/src/modules/notifications/lib/notificationMapper.ts +33 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +83 -0
- package/src/modules/notifications/lib/notificationService.ts +414 -0
- package/src/modules/notifications/lib/routeHelpers.ts +151 -0
- package/src/modules/notifications/lib/safeHref.ts +29 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +300 -0
- package/src/modules/notifications/migrations/Migration20260123000001.ts +73 -0
- package/src/modules/notifications/migrations/Migration20260126150000.ts +39 -0
- package/src/modules/notifications/subscribers/deliver-notification.ts +175 -0
- package/src/modules/notifications/workers/create-notification.worker.ts +122 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +2 -2
- package/src/modules/query_index/components/QueryIndexesTable.tsx +8 -2
- package/src/modules/resources/backend/resources/resource-types/page.tsx +2 -2
- package/src/modules/resources/backend/resources/resources/page.tsx +2 -2
- package/src/modules/sales/analytics.ts +64 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +2 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +2 -0
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/payments.ts +33 -0
- package/src/modules/sales/components/AdjustmentKindSettings.tsx +2 -2
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +2 -2
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +2 -2
- package/src/modules/sales/components/TaxRatesSettings.tsx +2 -2
- package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +2 -0
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +2 -0
- package/src/modules/sales/components/documents/PaymentsSection.tsx +2 -1
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -0
- package/src/modules/sales/i18n/de.json +20 -0
- package/src/modules/sales/i18n/en.json +25 -1
- package/src/modules/sales/i18n/es.json +20 -0
- package/src/modules/sales/i18n/pl.json +20 -0
- package/src/modules/sales/notifications.client.ts +65 -0
- package/src/modules/sales/notifications.ts +82 -0
- package/src/modules/sales/subscribers/quote-expiring-notification.ts +53 -0
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +156 -0
- package/src/modules/sales/widgets/notifications/index.ts +2 -0
- package/src/modules/sales/widgets/notifications/useSalesDocumentTotals.ts +81 -0
- package/src/modules/staff/backend/staff/team-members/page.tsx +1 -1
- package/src/modules/staff/backend/staff/team-roles/page.tsx +2 -2
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +2 -2
- package/src/modules/staff/backend/staff/teams/page.tsx +2 -2
- package/src/modules/staff/commands/leave-requests.ts +94 -0
- package/src/modules/staff/i18n/de.json +4 -0
- package/src/modules/staff/i18n/en.json +9 -1
- package/src/modules/staff/i18n/es.json +4 -0
- package/src/modules/staff/i18n/pl.json +4 -0
- package/src/modules/staff/notifications.ts +71 -0
- package/src/modules/workflows/backend/definitions/page.tsx +5 -0
- package/src/modules/workflows/backend/instances/page.tsx +4 -1
- package/src/modules/workflows/backend/tasks/page.tsx +4 -1
- package/src/modules/workflows/i18n/en.json +3 -1
- package/src/modules/workflows/notifications.ts +25 -0
- package/src/modules/workflows/subscribers/task-assigned-notification.ts +53 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
buildAggregateExpression,
|
|
6
|
+
buildDateTruncExpression,
|
|
7
|
+
buildJsonbFieldExpression,
|
|
8
|
+
buildAggregationQuery,
|
|
9
|
+
isValidGranularity,
|
|
10
|
+
isValidAggregate,
|
|
11
|
+
} from '../aggregations'
|
|
12
|
+
import { createAnalyticsRegistry } from '../../services/analyticsRegistry'
|
|
13
|
+
import { analyticsConfig as salesAnalyticsConfig } from '../../../sales/analytics'
|
|
14
|
+
import { analyticsConfig as customersAnalyticsConfig } from '../../../customers/analytics'
|
|
15
|
+
import { analyticsConfig as catalogAnalyticsConfig } from '../../../catalog/analytics'
|
|
16
|
+
|
|
17
|
+
const testRegistry = createAnalyticsRegistry([salesAnalyticsConfig, customersAnalyticsConfig, catalogAnalyticsConfig])
|
|
18
|
+
|
|
19
|
+
describe('aggregations', () => {
|
|
20
|
+
describe('isValidGranularity', () => {
|
|
21
|
+
it('returns true for valid granularities', () => {
|
|
22
|
+
expect(isValidGranularity('day')).toBe(true)
|
|
23
|
+
expect(isValidGranularity('week')).toBe(true)
|
|
24
|
+
expect(isValidGranularity('month')).toBe(true)
|
|
25
|
+
expect(isValidGranularity('quarter')).toBe(true)
|
|
26
|
+
expect(isValidGranularity('year')).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('returns false for invalid granularities', () => {
|
|
30
|
+
expect(isValidGranularity('invalid')).toBe(false)
|
|
31
|
+
expect(isValidGranularity('')).toBe(false)
|
|
32
|
+
expect(isValidGranularity(null)).toBe(false)
|
|
33
|
+
expect(isValidGranularity(undefined)).toBe(false)
|
|
34
|
+
expect(isValidGranularity(123)).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('isValidAggregate', () => {
|
|
39
|
+
it('returns true for valid aggregates', () => {
|
|
40
|
+
expect(isValidAggregate('count')).toBe(true)
|
|
41
|
+
expect(isValidAggregate('sum')).toBe(true)
|
|
42
|
+
expect(isValidAggregate('avg')).toBe(true)
|
|
43
|
+
expect(isValidAggregate('min')).toBe(true)
|
|
44
|
+
expect(isValidAggregate('max')).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('returns false for invalid aggregates', () => {
|
|
48
|
+
expect(isValidAggregate('invalid')).toBe(false)
|
|
49
|
+
expect(isValidAggregate('COUNT')).toBe(false) // case sensitive
|
|
50
|
+
expect(isValidAggregate('')).toBe(false)
|
|
51
|
+
expect(isValidAggregate(null)).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('isValidEntityType (via registry)', () => {
|
|
56
|
+
it('returns true for valid entity types', () => {
|
|
57
|
+
expect(testRegistry.isValidEntityType('sales:orders')).toBe(true)
|
|
58
|
+
expect(testRegistry.isValidEntityType('sales:order_lines')).toBe(true)
|
|
59
|
+
expect(testRegistry.isValidEntityType('customers:entities')).toBe(true)
|
|
60
|
+
expect(testRegistry.isValidEntityType('customers:deals')).toBe(true)
|
|
61
|
+
expect(testRegistry.isValidEntityType('catalog:products')).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('returns false for invalid entity types', () => {
|
|
65
|
+
expect(testRegistry.isValidEntityType('invalid')).toBe(false)
|
|
66
|
+
expect(testRegistry.isValidEntityType('sales:invalid')).toBe(false)
|
|
67
|
+
expect(testRegistry.isValidEntityType('')).toBe(false)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('getEntityTypeConfig (via registry)', () => {
|
|
72
|
+
it('returns config for valid entity types', () => {
|
|
73
|
+
const config = testRegistry.getEntityTypeConfig('sales:orders')
|
|
74
|
+
expect(config).not.toBeNull()
|
|
75
|
+
expect(config?.tableName).toBe('sales_orders')
|
|
76
|
+
expect(config?.dateField).toBe('placed_at')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('returns null for invalid entity types', () => {
|
|
80
|
+
expect(testRegistry.getEntityTypeConfig('invalid')).toBeNull()
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('getFieldMapping (via registry)', () => {
|
|
85
|
+
it('returns mapping for valid fields', () => {
|
|
86
|
+
const mapping = testRegistry.getFieldMapping('sales:orders', 'grandTotalGrossAmount')
|
|
87
|
+
expect(mapping).not.toBeNull()
|
|
88
|
+
expect(mapping?.dbColumn).toBe('grand_total_gross_amount')
|
|
89
|
+
expect(mapping?.type).toBe('numeric')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('returns null for invalid fields', () => {
|
|
93
|
+
expect(testRegistry.getFieldMapping('sales:orders', 'invalidField')).toBeNull()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns null for invalid entity types', () => {
|
|
97
|
+
expect(testRegistry.getFieldMapping('invalid', 'grandTotalGrossAmount')).toBeNull()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('buildAggregateExpression', () => {
|
|
102
|
+
it('builds COUNT(*) for count with id column', () => {
|
|
103
|
+
expect(buildAggregateExpression('count', 'id')).toBe('COUNT(*)')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('builds COUNT(column) for count with other columns', () => {
|
|
107
|
+
expect(buildAggregateExpression('count', 'status')).toBe('COUNT(status)')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('builds SUM with COALESCE', () => {
|
|
111
|
+
expect(buildAggregateExpression('sum', 'amount')).toBe('COALESCE(SUM(amount::numeric), 0)')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('builds AVG with COALESCE', () => {
|
|
115
|
+
expect(buildAggregateExpression('avg', 'amount')).toBe('COALESCE(AVG(amount::numeric), 0)')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('builds MIN', () => {
|
|
119
|
+
expect(buildAggregateExpression('min', 'amount')).toBe('MIN(amount::numeric)')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('builds MAX', () => {
|
|
123
|
+
expect(buildAggregateExpression('max', 'amount')).toBe('MAX(amount::numeric)')
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('buildDateTruncExpression', () => {
|
|
128
|
+
it('builds DATE_TRUNC for valid granularity', () => {
|
|
129
|
+
expect(buildDateTruncExpression('created_at', 'day')).toBe("DATE_TRUNC('day', created_at)")
|
|
130
|
+
expect(buildDateTruncExpression('created_at', 'month')).toBe("DATE_TRUNC('month', created_at)")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('throws for invalid granularity', () => {
|
|
134
|
+
expect(() => buildDateTruncExpression('created_at', 'invalid' as any)).toThrow('Invalid granularity')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
describe('buildJsonbFieldExpression', () => {
|
|
139
|
+
it('builds single-level JSONB access', () => {
|
|
140
|
+
expect(buildJsonbFieldExpression('data', 'name')).toBe("data->>'name'")
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('builds nested JSONB access', () => {
|
|
144
|
+
expect(buildJsonbFieldExpression('data', 'address.city')).toBe("data->'address'->>'city'")
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('builds deeply nested JSONB access', () => {
|
|
148
|
+
expect(buildJsonbFieldExpression('data', 'a.b.c')).toBe("data->'a'->'b'->>'c'")
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('throws for invalid path parts', () => {
|
|
152
|
+
expect(() => buildJsonbFieldExpression('data', 'invalid-path')).toThrow('Invalid JSONB path part')
|
|
153
|
+
expect(() => buildJsonbFieldExpression('data', '123invalid')).toThrow('Invalid JSONB path part')
|
|
154
|
+
expect(() => buildJsonbFieldExpression('data', 'valid.123invalid')).toThrow('Invalid JSONB path part')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('allows valid identifier characters', () => {
|
|
158
|
+
expect(buildJsonbFieldExpression('data', 'valid_name')).toBe("data->>'valid_name'")
|
|
159
|
+
expect(buildJsonbFieldExpression('data', '_private')).toBe("data->>'_private'")
|
|
160
|
+
expect(buildJsonbFieldExpression('data', 'CamelCase')).toBe("data->>'CamelCase'")
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('buildAggregationQuery', () => {
|
|
165
|
+
const baseOptions = {
|
|
166
|
+
entityType: 'sales:orders',
|
|
167
|
+
metric: { field: 'grandTotalGrossAmount', aggregate: 'sum' as const },
|
|
168
|
+
scope: { tenantId: 'tenant-123' },
|
|
169
|
+
registry: testRegistry,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
it('builds basic aggregation query', () => {
|
|
173
|
+
const result = buildAggregationQuery(baseOptions)
|
|
174
|
+
expect(result).not.toBeNull()
|
|
175
|
+
expect(result?.sql).toContain('SELECT')
|
|
176
|
+
expect(result?.sql).toContain('COALESCE(SUM(grand_total_gross_amount::numeric), 0)')
|
|
177
|
+
expect(result?.sql).toContain('FROM "sales_orders"')
|
|
178
|
+
expect(result?.sql).toContain('tenant_id = ?')
|
|
179
|
+
expect(result?.params).toContain('tenant-123')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('includes organization filter when provided', () => {
|
|
183
|
+
const result = buildAggregationQuery({
|
|
184
|
+
...baseOptions,
|
|
185
|
+
scope: { tenantId: 'tenant-123', organizationIds: ['org-1', 'org-2'] },
|
|
186
|
+
})
|
|
187
|
+
expect(result?.sql).toContain('organization_id = ANY(?::uuid[])')
|
|
188
|
+
expect(result?.params).toContain('{org-1,org-2}')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('includes date range filter', () => {
|
|
192
|
+
const start = new Date('2024-01-01')
|
|
193
|
+
const end = new Date('2024-01-31')
|
|
194
|
+
const result = buildAggregationQuery({
|
|
195
|
+
...baseOptions,
|
|
196
|
+
dateRange: { field: 'placedAt', start, end },
|
|
197
|
+
})
|
|
198
|
+
expect(result?.sql).toContain('placed_at >= ?')
|
|
199
|
+
expect(result?.sql).toContain('placed_at <= ?')
|
|
200
|
+
expect(result?.params).toContain(start)
|
|
201
|
+
expect(result?.params).toContain(end)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('includes groupBy clause', () => {
|
|
205
|
+
const result = buildAggregationQuery({
|
|
206
|
+
...baseOptions,
|
|
207
|
+
groupBy: { field: 'status' },
|
|
208
|
+
})
|
|
209
|
+
expect(result?.sql).toContain('GROUP BY status')
|
|
210
|
+
expect(result?.sql).toContain('status AS group_key')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('includes groupBy with granularity for timestamp fields', () => {
|
|
214
|
+
const result = buildAggregationQuery({
|
|
215
|
+
...baseOptions,
|
|
216
|
+
groupBy: { field: 'placedAt', granularity: 'month' },
|
|
217
|
+
})
|
|
218
|
+
expect(result?.sql).toContain("DATE_TRUNC('month', placed_at)")
|
|
219
|
+
expect(result?.sql).toContain('GROUP BY')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('includes LIMIT when groupBy has limit', () => {
|
|
223
|
+
const result = buildAggregationQuery({
|
|
224
|
+
...baseOptions,
|
|
225
|
+
groupBy: { field: 'status', limit: 10 },
|
|
226
|
+
})
|
|
227
|
+
expect(result?.sql).toContain('LIMIT 10')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('caps LIMIT at 100', () => {
|
|
231
|
+
const result = buildAggregationQuery({
|
|
232
|
+
...baseOptions,
|
|
233
|
+
groupBy: { field: 'status', limit: 200 },
|
|
234
|
+
})
|
|
235
|
+
expect(result?.sql).toContain('LIMIT 100')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('includes deleted_at IS NULL filter', () => {
|
|
239
|
+
const result = buildAggregationQuery(baseOptions)
|
|
240
|
+
expect(result?.sql).toContain('deleted_at IS NULL')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('handles various filter operators', () => {
|
|
244
|
+
const result = buildAggregationQuery({
|
|
245
|
+
...baseOptions,
|
|
246
|
+
filters: [
|
|
247
|
+
{ field: 'status', operator: 'eq', value: 'completed' },
|
|
248
|
+
{ field: 'grandTotalGrossAmount', operator: 'gte', value: 100 },
|
|
249
|
+
],
|
|
250
|
+
})
|
|
251
|
+
expect(result?.sql).toContain('status = ?')
|
|
252
|
+
expect(result?.sql).toContain('grand_total_gross_amount >= ?')
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('handles is_null and is_not_null operators without value', () => {
|
|
256
|
+
const result = buildAggregationQuery({
|
|
257
|
+
...baseOptions,
|
|
258
|
+
filters: [
|
|
259
|
+
{ field: 'customerEntityId', operator: 'is_null' },
|
|
260
|
+
{ field: 'channelId', operator: 'is_not_null' },
|
|
261
|
+
],
|
|
262
|
+
})
|
|
263
|
+
expect(result?.sql).toContain('customer_entity_id IS NULL')
|
|
264
|
+
expect(result?.sql).toContain('channel_id IS NOT NULL')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('returns null for invalid entity type', () => {
|
|
268
|
+
const result = buildAggregationQuery({
|
|
269
|
+
...baseOptions,
|
|
270
|
+
entityType: 'invalid',
|
|
271
|
+
})
|
|
272
|
+
expect(result).toBeNull()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('returns null for invalid metric field', () => {
|
|
276
|
+
const result = buildAggregationQuery({
|
|
277
|
+
...baseOptions,
|
|
278
|
+
metric: { field: 'invalidField', aggregate: 'sum' },
|
|
279
|
+
})
|
|
280
|
+
expect(result).toBeNull()
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
describe('entity type configs (via registry)', () => {
|
|
285
|
+
it('has all expected entity types', () => {
|
|
286
|
+
const entityIds = testRegistry.getAllEntityConfigs().map((c) => c.entityId)
|
|
287
|
+
expect(entityIds).toEqual(
|
|
288
|
+
expect.arrayContaining([
|
|
289
|
+
'sales:orders',
|
|
290
|
+
'sales:order_lines',
|
|
291
|
+
'customers:entities',
|
|
292
|
+
'customers:deals',
|
|
293
|
+
'catalog:products',
|
|
294
|
+
]),
|
|
295
|
+
)
|
|
296
|
+
expect(entityIds).toHaveLength(5)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('each config has required fields', () => {
|
|
300
|
+
testRegistry.getAllEntityConfigs().forEach((entityConfig) => {
|
|
301
|
+
expect(entityConfig.entityConfig.tableName).toBeDefined()
|
|
302
|
+
expect(entityConfig.entityConfig.dateField).toBeDefined()
|
|
303
|
+
expect(entityConfig.entityConfig.defaultScopeFields).toBeDefined()
|
|
304
|
+
expect(Array.isArray(entityConfig.entityConfig.defaultScopeFields)).toBe(true)
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
describe('field mappings (via registry)', () => {
|
|
310
|
+
it('has mappings for all entity types', () => {
|
|
311
|
+
testRegistry.getAllEntityConfigs().forEach((entityConfig) => {
|
|
312
|
+
const mappings = testRegistry.getAllFieldMappings(entityConfig.entityId)
|
|
313
|
+
expect(mappings).toBeDefined()
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('sales:orders has expected fields', () => {
|
|
318
|
+
const mappings = testRegistry.getAllFieldMappings('sales:orders')
|
|
319
|
+
expect(mappings).not.toBeNull()
|
|
320
|
+
const fields = Object.keys(mappings!)
|
|
321
|
+
expect(fields).toContain('id')
|
|
322
|
+
expect(fields).toContain('grandTotalGrossAmount')
|
|
323
|
+
expect(fields).toContain('status')
|
|
324
|
+
expect(fields).toContain('placedAt')
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
formatCurrency,
|
|
6
|
+
formatCurrencyWithDecimals,
|
|
7
|
+
formatCurrencyCompact,
|
|
8
|
+
formatCurrencySafe,
|
|
9
|
+
} from '../formatters'
|
|
10
|
+
|
|
11
|
+
describe('formatters', () => {
|
|
12
|
+
describe('formatCurrency', () => {
|
|
13
|
+
it('formats positive numbers as currency', () => {
|
|
14
|
+
const result = formatCurrency(1234)
|
|
15
|
+
expect(result).toMatch(/\$?1,?234/)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('formats zero', () => {
|
|
19
|
+
const result = formatCurrency(0)
|
|
20
|
+
expect(result).toMatch(/\$?0/)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('formats negative numbers', () => {
|
|
24
|
+
const result = formatCurrency(-500)
|
|
25
|
+
expect(result).toMatch(/-?\$?500/)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('uses custom currency', () => {
|
|
29
|
+
const result = formatCurrency(100, { currency: 'EUR' })
|
|
30
|
+
expect(result).toMatch(/€|EUR/)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('respects minimumFractionDigits', () => {
|
|
34
|
+
const result = formatCurrency(100, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
35
|
+
expect(result).toMatch(/100\.00|100,00/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('respects maximumFractionDigits', () => {
|
|
39
|
+
const result = formatCurrency(100.999, { maximumFractionDigits: 2 })
|
|
40
|
+
expect(result).toMatch(/101/)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('formatCurrencyWithDecimals', () => {
|
|
45
|
+
it('formats with 2 decimal places by default', () => {
|
|
46
|
+
const result = formatCurrencyWithDecimals(100)
|
|
47
|
+
expect(result).toMatch(/100\.00|100,00/)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('rounds to 2 decimal places', () => {
|
|
51
|
+
const result = formatCurrencyWithDecimals(99.999)
|
|
52
|
+
expect(result).toMatch(/100\.00|100,00/)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('shows trailing zeros', () => {
|
|
56
|
+
const result = formatCurrencyWithDecimals(50)
|
|
57
|
+
expect(result).toMatch(/50\.00|50,00/)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('formatCurrencyCompact', () => {
|
|
62
|
+
it('formats millions with M suffix', () => {
|
|
63
|
+
expect(formatCurrencyCompact(1000000)).toBe('$1.0M')
|
|
64
|
+
expect(formatCurrencyCompact(2500000)).toBe('$2.5M')
|
|
65
|
+
expect(formatCurrencyCompact(10000000)).toBe('$10.0M')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('formats thousands with K suffix', () => {
|
|
69
|
+
expect(formatCurrencyCompact(1000)).toBe('$1.0K')
|
|
70
|
+
expect(formatCurrencyCompact(5500)).toBe('$5.5K')
|
|
71
|
+
expect(formatCurrencyCompact(999000)).toBe('$999.0K')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('formats small numbers without suffix', () => {
|
|
75
|
+
expect(formatCurrencyCompact(500)).toBe('$500')
|
|
76
|
+
expect(formatCurrencyCompact(0)).toBe('$0')
|
|
77
|
+
expect(formatCurrencyCompact(999)).toBe('$999')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('handles negative values', () => {
|
|
81
|
+
expect(formatCurrencyCompact(-1000000)).toBe('$-1.0M')
|
|
82
|
+
expect(formatCurrencyCompact(-5000)).toBe('$-5.0K')
|
|
83
|
+
expect(formatCurrencyCompact(-500)).toBe('$-500')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('uses custom currency symbol', () => {
|
|
87
|
+
expect(formatCurrencyCompact(1000000, '€')).toBe('€1.0M')
|
|
88
|
+
expect(formatCurrencyCompact(5000, '£')).toBe('£5.0K')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('formatCurrencySafe', () => {
|
|
93
|
+
it('formats valid numbers', () => {
|
|
94
|
+
const result = formatCurrencySafe(1234)
|
|
95
|
+
expect(result).toMatch(/\$?1,?234/)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('returns fallback for null', () => {
|
|
99
|
+
expect(formatCurrencySafe(null)).toBe('--')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('returns fallback for undefined', () => {
|
|
103
|
+
expect(formatCurrencySafe(undefined)).toBe('--')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('returns fallback for NaN', () => {
|
|
107
|
+
expect(formatCurrencySafe(NaN)).toBe('--')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('returns fallback for Infinity', () => {
|
|
111
|
+
expect(formatCurrencySafe(Infinity)).toBe('--')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('returns fallback for non-numeric strings', () => {
|
|
115
|
+
expect(formatCurrencySafe('not a number')).toBe('--')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('converts numeric strings to numbers', () => {
|
|
119
|
+
const result = formatCurrencySafe('1234')
|
|
120
|
+
expect(result).toMatch(/\$?1,?234/)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('uses custom fallback', () => {
|
|
124
|
+
expect(formatCurrencySafe(null, 'N/A')).toBe('N/A')
|
|
125
|
+
expect(formatCurrencySafe(undefined, '-')).toBe('-')
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
})
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { AnalyticsRegistry } from '../services/analyticsRegistry'
|
|
2
|
+
import type { AnalyticsEntityTypeConfig, AnalyticsFieldMapping } from '@open-mercato/shared/modules/analytics'
|
|
3
|
+
|
|
4
|
+
export type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'
|
|
5
|
+
export type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year'
|
|
6
|
+
|
|
7
|
+
const VALID_GRANULARITIES: readonly DateGranularity[] = ['day', 'week', 'month', 'quarter', 'year']
|
|
8
|
+
const VALID_AGGREGATES: readonly AggregateFunction[] = ['count', 'sum', 'avg', 'min', 'max']
|
|
9
|
+
const SAFE_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
|
10
|
+
|
|
11
|
+
export function isValidGranularity(value: unknown): value is DateGranularity {
|
|
12
|
+
return typeof value === 'string' && VALID_GRANULARITIES.includes(value as DateGranularity)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isValidAggregate(value: unknown): value is AggregateFunction {
|
|
16
|
+
return typeof value === 'string' && VALID_AGGREGATES.includes(value as AggregateFunction)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isSafeIdentifier(value: string): boolean {
|
|
20
|
+
return SAFE_IDENTIFIER_PATTERN.test(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Re-export types from shared module for convenience
|
|
24
|
+
export type EntityTypeConfig = AnalyticsEntityTypeConfig
|
|
25
|
+
export type FieldMapping = AnalyticsFieldMapping
|
|
26
|
+
|
|
27
|
+
export function buildAggregateExpression(aggregate: AggregateFunction, column: string): string {
|
|
28
|
+
switch (aggregate) {
|
|
29
|
+
case 'count':
|
|
30
|
+
return column === 'id' ? 'COUNT(*)' : `COUNT(${column})`
|
|
31
|
+
case 'sum':
|
|
32
|
+
return `COALESCE(SUM(${column}::numeric), 0)`
|
|
33
|
+
case 'avg':
|
|
34
|
+
return `COALESCE(AVG(${column}::numeric), 0)`
|
|
35
|
+
case 'min':
|
|
36
|
+
return `MIN(${column}::numeric)`
|
|
37
|
+
case 'max':
|
|
38
|
+
return `MAX(${column}::numeric)`
|
|
39
|
+
default:
|
|
40
|
+
return `COUNT(*)`
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildDateTruncExpression(column: string, granularity: DateGranularity): string {
|
|
45
|
+
if (!isValidGranularity(granularity)) {
|
|
46
|
+
throw new Error(`Invalid granularity: ${granularity}`)
|
|
47
|
+
}
|
|
48
|
+
return `DATE_TRUNC('${granularity}', ${column})`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildJsonbFieldExpression(column: string, path: string): string {
|
|
52
|
+
const parts = path.split('.')
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
if (!isSafeIdentifier(part)) {
|
|
55
|
+
throw new Error(`Invalid JSONB path part: ${part}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (parts.length === 1) {
|
|
59
|
+
return `${column}->>'${parts[0]}'`
|
|
60
|
+
}
|
|
61
|
+
const intermediate = parts.slice(0, -1).map((p) => `'${p}'`).join('->')
|
|
62
|
+
const lastPart = parts[parts.length - 1]
|
|
63
|
+
return `${column}->${intermediate}->>'${lastPart}'`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AggregationQuery = {
|
|
67
|
+
sql: string
|
|
68
|
+
params: unknown[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type BuildAggregationQueryOptions = {
|
|
72
|
+
entityType: string
|
|
73
|
+
metric: {
|
|
74
|
+
field: string
|
|
75
|
+
aggregate: AggregateFunction
|
|
76
|
+
}
|
|
77
|
+
groupBy?: {
|
|
78
|
+
field: string
|
|
79
|
+
granularity?: DateGranularity
|
|
80
|
+
limit?: number
|
|
81
|
+
}
|
|
82
|
+
dateRange?: {
|
|
83
|
+
field: string
|
|
84
|
+
start: Date
|
|
85
|
+
end: Date
|
|
86
|
+
}
|
|
87
|
+
filters?: Array<{
|
|
88
|
+
field: string
|
|
89
|
+
operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'not_in' | 'is_null' | 'is_not_null'
|
|
90
|
+
value?: unknown
|
|
91
|
+
}>
|
|
92
|
+
scope: {
|
|
93
|
+
tenantId: string
|
|
94
|
+
organizationIds?: string[]
|
|
95
|
+
}
|
|
96
|
+
/** Analytics registry for resolving entity and field configurations */
|
|
97
|
+
registry: AnalyticsRegistry
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function buildAggregationQuery(options: BuildAggregationQueryOptions): AggregationQuery | null {
|
|
101
|
+
const { registry } = options
|
|
102
|
+
const config = registry.getEntityTypeConfig(options.entityType)
|
|
103
|
+
if (!config) return null
|
|
104
|
+
|
|
105
|
+
const metricMapping = registry.getFieldMapping(options.entityType, options.metric.field)
|
|
106
|
+
if (!metricMapping) return null
|
|
107
|
+
|
|
108
|
+
const params: unknown[] = []
|
|
109
|
+
|
|
110
|
+
const tableName = config.schema ? `"${config.schema}"."${config.tableName}"` : `"${config.tableName}"`
|
|
111
|
+
const aggregateExpr = buildAggregateExpression(options.metric.aggregate, metricMapping.dbColumn)
|
|
112
|
+
|
|
113
|
+
let selectClause = `SELECT ${aggregateExpr} AS value`
|
|
114
|
+
let groupByClause = ''
|
|
115
|
+
let orderByClause = ''
|
|
116
|
+
let limitClause = ''
|
|
117
|
+
|
|
118
|
+
if (options.groupBy) {
|
|
119
|
+
let groupMapping = registry.getFieldMapping(options.entityType, options.groupBy.field)
|
|
120
|
+
let groupExpr: string | null = null
|
|
121
|
+
|
|
122
|
+
// Handle JSONB path notation (e.g., shippingAddressSnapshot.region)
|
|
123
|
+
if (!groupMapping && options.groupBy.field.includes('.')) {
|
|
124
|
+
const [baseField, ...pathParts] = options.groupBy.field.split('.')
|
|
125
|
+
const baseMapping = registry.getFieldMapping(options.entityType, baseField)
|
|
126
|
+
if (baseMapping?.type === 'jsonb') {
|
|
127
|
+
groupExpr = buildJsonbFieldExpression(baseMapping.dbColumn, pathParts.join('.'))
|
|
128
|
+
}
|
|
129
|
+
} else if (groupMapping) {
|
|
130
|
+
if (groupMapping.type === 'timestamp' && options.groupBy.granularity) {
|
|
131
|
+
groupExpr = buildDateTruncExpression(groupMapping.dbColumn, options.groupBy.granularity)
|
|
132
|
+
} else {
|
|
133
|
+
groupExpr = groupMapping.dbColumn
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (groupExpr) {
|
|
138
|
+
selectClause = `SELECT ${groupExpr} AS group_key, ${aggregateExpr} AS value`
|
|
139
|
+
groupByClause = `GROUP BY ${groupExpr}`
|
|
140
|
+
orderByClause = `ORDER BY value DESC`
|
|
141
|
+
|
|
142
|
+
if (options.groupBy.limit && options.groupBy.limit > 0) {
|
|
143
|
+
limitClause = `LIMIT ${Math.min(options.groupBy.limit, 100)}`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const whereClauses: string[] = []
|
|
149
|
+
|
|
150
|
+
whereClauses.push(`tenant_id = ?`)
|
|
151
|
+
params.push(options.scope.tenantId)
|
|
152
|
+
|
|
153
|
+
if (options.scope.organizationIds && options.scope.organizationIds.length > 0) {
|
|
154
|
+
whereClauses.push(`organization_id = ANY(?::uuid[])`)
|
|
155
|
+
params.push(`{${options.scope.organizationIds.join(',')}}`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
whereClauses.push(`deleted_at IS NULL`)
|
|
159
|
+
|
|
160
|
+
if (options.dateRange) {
|
|
161
|
+
const dateMapping = registry.getFieldMapping(options.entityType, options.dateRange.field)
|
|
162
|
+
if (dateMapping) {
|
|
163
|
+
whereClauses.push(`${dateMapping.dbColumn} >= ?`)
|
|
164
|
+
params.push(options.dateRange.start)
|
|
165
|
+
whereClauses.push(`${dateMapping.dbColumn} <= ?`)
|
|
166
|
+
params.push(options.dateRange.end)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (options.filters) {
|
|
171
|
+
for (const filter of options.filters) {
|
|
172
|
+
const filterMapping = registry.getFieldMapping(options.entityType, filter.field)
|
|
173
|
+
if (!filterMapping) continue
|
|
174
|
+
|
|
175
|
+
switch (filter.operator) {
|
|
176
|
+
case 'eq':
|
|
177
|
+
whereClauses.push(`${filterMapping.dbColumn} = ?`)
|
|
178
|
+
params.push(filter.value)
|
|
179
|
+
break
|
|
180
|
+
case 'neq':
|
|
181
|
+
whereClauses.push(`${filterMapping.dbColumn} != ?`)
|
|
182
|
+
params.push(filter.value)
|
|
183
|
+
break
|
|
184
|
+
case 'gt':
|
|
185
|
+
whereClauses.push(`${filterMapping.dbColumn} > ?`)
|
|
186
|
+
params.push(filter.value)
|
|
187
|
+
break
|
|
188
|
+
case 'gte':
|
|
189
|
+
whereClauses.push(`${filterMapping.dbColumn} >= ?`)
|
|
190
|
+
params.push(filter.value)
|
|
191
|
+
break
|
|
192
|
+
case 'lt':
|
|
193
|
+
whereClauses.push(`${filterMapping.dbColumn} < ?`)
|
|
194
|
+
params.push(filter.value)
|
|
195
|
+
break
|
|
196
|
+
case 'lte':
|
|
197
|
+
whereClauses.push(`${filterMapping.dbColumn} <= ?`)
|
|
198
|
+
params.push(filter.value)
|
|
199
|
+
break
|
|
200
|
+
case 'in':
|
|
201
|
+
whereClauses.push(`${filterMapping.dbColumn} = ANY(?)`)
|
|
202
|
+
params.push(filter.value)
|
|
203
|
+
break
|
|
204
|
+
case 'not_in':
|
|
205
|
+
whereClauses.push(`${filterMapping.dbColumn} != ALL(?)`)
|
|
206
|
+
params.push(filter.value)
|
|
207
|
+
break
|
|
208
|
+
case 'is_null':
|
|
209
|
+
whereClauses.push(`${filterMapping.dbColumn} IS NULL`)
|
|
210
|
+
break
|
|
211
|
+
case 'is_not_null':
|
|
212
|
+
whereClauses.push(`${filterMapping.dbColumn} IS NOT NULL`)
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : ''
|
|
219
|
+
|
|
220
|
+
const sql = [selectClause, `FROM ${tableName}`, whereClause, groupByClause, orderByClause, limitClause]
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
.join(' ')
|
|
223
|
+
|
|
224
|
+
return { sql, params }
|
|
225
|
+
}
|