@open-mercato/core 0.6.5-develop.5337.1.534b781eac → 0.6.5
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/AGENTS.md +1 -1
- package/dist/bootstrap.js +46 -6
- package/dist/bootstrap.js.map +2 -2
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/crmFixtures.js +4 -0
- package/dist/helpers/integration/crmFixtures.js.map +2 -2
- package/dist/modules/attachments/api/library/route.js +2 -2
- package/dist/modules/attachments/api/library/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +2 -0
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +9 -5
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/access.js +18 -0
- package/dist/modules/attachments/lib/access.js.map +2 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +3 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/audit_logs/data/entities.js +2 -1
- package/dist/modules/audit_logs/data/entities.js.map +2 -2
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
- package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
- package/dist/modules/audit_logs/services/accessLogService.js +10 -0
- package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
- package/dist/modules/auth/api/admin/nav.js +9 -0
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/login.js +4 -13
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/commands/users.js +20 -14
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js +4 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +35 -2
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/consentIntegrity.js +3 -3
- package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260610120000.js +30 -0
- package/dist/modules/auth/migrations/Migration20260610120000.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
- package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
- package/dist/modules/auth/services/authService.js +5 -3
- package/dist/modules/auth/services/authService.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +3 -2
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/products-pack.js.map +1 -1
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +1 -1
- package/dist/modules/communication_channels/data/entities.js.map +1 -1
- package/dist/modules/communication_channels/encryption.js.map +1 -1
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +1 -1
- package/dist/modules/communication_channels/lib/thread-token.js.map +1 -1
- package/dist/modules/currencies/api/currencies/route.js +4 -3
- package/dist/modules/currencies/api/currencies/route.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles.js +2 -1
- package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
- package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
- package/dist/modules/customer_accounts/events.js +1 -1
- package/dist/modules/customer_accounts/events.js.map +1 -1
- package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +1 -1
- package/dist/modules/customers/acl.js +1 -1
- package/dist/modules/customers/acl.js.map +1 -1
- package/dist/modules/customers/ai-tools/companies-pack.js.map +1 -1
- package/dist/modules/customers/ai-tools/deals-pack.js.map +1 -1
- package/dist/modules/customers/ai-tools/people-pack.js.map +1 -1
- package/dist/modules/customers/api/companies/route.js +4 -4
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +43 -2
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/deals/summary/route.js +402 -0
- package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
- package/dist/modules/customers/api/people/route.js +4 -4
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +221 -56
- package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/cli.js +15 -9
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/addresses.js +5 -5
- package/dist/modules/customers/commands/addresses.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +5 -5
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +2 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/entity-roles.js +2 -1
- package/dist/modules/customers/commands/entity-roles.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +8 -5
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/commands/shared.js +21 -6
- package/dist/modules/customers/commands/shared.js.map +2 -2
- package/dist/modules/customers/commands/tags.js +3 -3
- package/dist/modules/customers/commands/tags.js.map +2 -2
- package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
- package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +100 -17
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +21 -8
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
- package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
- package/dist/modules/customers/lib/dealsMetrics.js +82 -0
- package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +1 -1
- package/dist/modules/data_sync/api/run.js +1 -1
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +214 -0
- package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
- package/dist/modules/directory/api/organizations/route.js +7 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/branding/page.js +214 -0
- package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
- package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
- package/dist/modules/directory/commands/organizations.js +8 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +3 -0
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +9 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
- package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
- package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +59 -27
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +2 -1
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/entities.js +7 -0
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +26 -15
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
- package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
- package/dist/modules/payment_gateways/api/transactions/route.js +2 -4
- package/dist/modules/payment_gateways/api/transactions/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +7 -2
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +1 -1
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/progress/lib/progressServiceImpl.js +8 -2
- package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
- package/dist/modules/query_index/data/entities.js +2 -1
- package/dist/modules/query_index/data/entities.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +4 -2
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
- package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
- package/dist/modules/resources/api/resources.js +2 -3
- package/dist/modules/resources/api/resources.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +2 -2
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +7 -5
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
- package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
- package/dist/modules/staff/api/team-members.js +9 -2
- package/dist/modules/staff/api/team-members.js.map +2 -2
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
- package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/team-members.js +1 -1
- package/dist/modules/staff/commands/team-members.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
- package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
- package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
- package/dist/modules/sync_excel/api/import/route.js +1 -1
- package/dist/modules/sync_excel/api/import/route.js.map +2 -2
- package/dist/modules/workflows/api/definitions/route.js +3 -2
- package/dist/modules/workflows/api/definitions/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +11 -12
- package/src/bootstrap.ts +65 -7
- package/src/helpers/integration/crmFixtures.ts +21 -1
- package/src/modules/attachments/AGENTS.md +79 -0
- package/src/modules/attachments/api/library/route.ts +2 -2
- package/src/modules/attachments/api/route.ts +2 -0
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +6 -6
- package/src/modules/attachments/lib/access.ts +36 -0
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +14 -2
- package/src/modules/audit_logs/data/entities.ts +1 -0
- package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
- package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
- package/src/modules/audit_logs/services/accessLogService.ts +15 -0
- package/src/modules/auth/api/admin/nav.ts +9 -0
- package/src/modules/auth/api/login.ts +13 -13
- package/src/modules/auth/commands/users.ts +32 -15
- package/src/modules/auth/data/entities.ts +13 -1
- package/src/modules/auth/i18n/de.json +0 -1
- package/src/modules/auth/i18n/en.json +0 -1
- package/src/modules/auth/i18n/es.json +0 -1
- package/src/modules/auth/i18n/pl.json +0 -1
- package/src/modules/auth/lib/backendChrome.tsx +37 -1
- package/src/modules/auth/lib/consentIntegrity.ts +6 -3
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -10
- package/src/modules/auth/migrations/Migration20260610120000.ts +53 -0
- package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
- package/src/modules/auth/services/authService.ts +24 -4
- package/src/modules/auth/services/rbacService.ts +11 -2
- package/src/modules/catalog/ai-tools/configuration-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/products-pack.ts +1 -1
- package/src/modules/catalog/ai-tools/variants-pack.ts +1 -1
- package/src/modules/communication_channels/data/entities.ts +2 -2
- package/src/modules/communication_channels/encryption.ts +1 -1
- package/src/modules/communication_channels/lib/adapter.ts +1 -1
- package/src/modules/communication_channels/lib/thread-matcher.ts +1 -1
- package/src/modules/communication_channels/lib/thread-token.ts +1 -1
- package/src/modules/currencies/api/currencies/route.ts +4 -3
- package/src/modules/customer_accounts/api/admin/roles.ts +2 -1
- package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
- package/src/modules/customer_accounts/events.ts +1 -1
- package/src/modules/customer_accounts/lib/resolveTenantContext.ts +2 -2
- package/src/modules/customers/acl.ts +1 -1
- package/src/modules/customers/ai-tools/companies-pack.ts +1 -1
- package/src/modules/customers/ai-tools/deals-pack.ts +1 -1
- package/src/modules/customers/ai-tools/people-pack.ts +1 -1
- package/src/modules/customers/api/companies/route.ts +4 -4
- package/src/modules/customers/api/deals/route.ts +51 -2
- package/src/modules/customers/api/deals/summary/route.ts +496 -0
- package/src/modules/customers/api/people/route.ts +4 -4
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
- package/src/modules/customers/cli.ts +15 -15
- package/src/modules/customers/commands/addresses.ts +5 -5
- package/src/modules/customers/commands/comments.ts +5 -5
- package/src/modules/customers/commands/deals.ts +2 -2
- package/src/modules/customers/commands/entity-roles.ts +2 -1
- package/src/modules/customers/commands/interactions.ts +8 -5
- package/src/modules/customers/commands/shared.ts +26 -4
- package/src/modules/customers/commands/tags.ts +3 -3
- package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
- package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
- package/src/modules/customers/components/detail/DealForm.tsx +121 -19
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
- package/src/modules/customers/components/detail/assignableStaff.ts +32 -8
- package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
- package/src/modules/customers/i18n/de.json +43 -0
- package/src/modules/customers/i18n/en.json +43 -0
- package/src/modules/customers/i18n/es.json +43 -0
- package/src/modules/customers/i18n/pl.json +43 -0
- package/src/modules/customers/lib/dealsMetrics.ts +159 -0
- package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +1 -1
- package/src/modules/data_sync/api/run.ts +1 -1
- package/src/modules/directory/api/organization-branding/route.ts +238 -0
- package/src/modules/directory/api/organizations/route.ts +7 -0
- package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
- package/src/modules/directory/commands/organizations.ts +9 -1
- package/src/modules/directory/data/entities.ts +3 -0
- package/src/modules/directory/data/validators.ts +12 -0
- package/src/modules/directory/i18n/de.json +21 -0
- package/src/modules/directory/i18n/en.json +21 -0
- package/src/modules/directory/i18n/es.json +21 -0
- package/src/modules/directory/i18n/pl.json +21 -0
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
- package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
- package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
- package/src/modules/directory/utils/organizationScope.ts +85 -30
- package/src/modules/entities/api/definitions.batch.ts +11 -7
- package/src/modules/entities/api/entities.ts +11 -0
- package/src/modules/entities/api/records.ts +46 -25
- package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
- package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
- package/src/modules/entities/i18n/de.json +1 -0
- package/src/modules/entities/i18n/en.json +1 -0
- package/src/modules/entities/i18n/es.json +1 -0
- package/src/modules/entities/i18n/pl.json +1 -0
- package/src/modules/payment_gateways/api/transactions/route.ts +2 -5
- package/src/modules/progress/api/jobs/[id]/route.ts +6 -1
- package/src/modules/progress/api/jobs/route.ts +1 -1
- package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
- package/src/modules/query_index/data/entities.ts +1 -0
- package/src/modules/query_index/lib/engine.ts +11 -5
- package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
- package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
- package/src/modules/resources/api/resources.ts +2 -3
- package/src/modules/sales/api/documents/factory.ts +2 -2
- package/src/modules/sales/commands/documents.ts +7 -5
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
- package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
- package/src/modules/staff/AGENTS.md +1 -1
- package/src/modules/staff/api/team-members.ts +9 -2
- package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
- package/src/modules/staff/commands/team-members.ts +5 -2
- package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
- package/src/modules/sync_excel/api/import/route.ts +1 -1
- package/src/modules/workflows/api/definitions/route.ts +3 -2
- package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
- package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
// #2934: User email uniqueness must be per-tenant, not global. The original
|
|
4
|
+
// `users_email_unique` constraint (unique on `email` across all tenants) contradicts the
|
|
5
|
+
// multi-tenant login flow — which resolves the same email across tenants via
|
|
6
|
+
// `findUsersByEmail` — and leaks cross-tenant account existence / enables registration
|
|
7
|
+
// squatting. Replace it with a partial unique index scoped per-tenant over live rows.
|
|
8
|
+
//
|
|
9
|
+
// The index is keyed on `email_hash`, NOT `email`: `email` is encrypted at rest with a
|
|
10
|
+
// per-row IV (auth/encryption.ts -> shared aes.ts), so its ciphertext is non-deterministic
|
|
11
|
+
// and a unique index on it would never detect duplicates under the default (encryption-on)
|
|
12
|
+
// configuration. `email_hash` is the deterministic lookup hash the application already
|
|
13
|
+
// de-dupes on, so the constraint is effective in both encryption-on and encryption-off
|
|
14
|
+
// modes. This mirrors `customer_users_tenant_email_hash_uniq` in customer_accounts.
|
|
15
|
+
//
|
|
16
|
+
// `WHERE deleted_at IS NULL` lets a soft-deleted user's email be reused (the old non-partial
|
|
17
|
+
// constraint blocked this); `AND email_hash IS NOT NULL` skips the rare legacy/bootstrap rows
|
|
18
|
+
// that predate hash population (encryption-off `setup-app` users) — those remain protected by
|
|
19
|
+
// the tenant-scoped application duplicate check.
|
|
20
|
+
//
|
|
21
|
+
// Before creating the index, soft-delete any pre-existing duplicate live rows per
|
|
22
|
+
// (tenant_id, email_hash), keeping the most-recently-updated one. Under encryption the old
|
|
23
|
+
// `email` constraint never fired, so same-tenant duplicates were only blocked by the
|
|
24
|
+
// application check and a historical race could have slipped one through; the dedupe makes
|
|
25
|
+
// the index creation safe on such data (no-op when there are none).
|
|
26
|
+
export class Migration20260610120000 extends Migration {
|
|
27
|
+
|
|
28
|
+
override up(): void | Promise<void> {
|
|
29
|
+
this.addSql(`
|
|
30
|
+
with ranked as (
|
|
31
|
+
select id,
|
|
32
|
+
row_number() over (
|
|
33
|
+
partition by tenant_id, email_hash
|
|
34
|
+
order by coalesce(updated_at, created_at) desc, created_at desc, id desc
|
|
35
|
+
) as rn
|
|
36
|
+
from users
|
|
37
|
+
where deleted_at is null and email_hash is not null
|
|
38
|
+
)
|
|
39
|
+
update users
|
|
40
|
+
set deleted_at = now()
|
|
41
|
+
from ranked
|
|
42
|
+
where users.id = ranked.id and ranked.rn > 1;
|
|
43
|
+
`);
|
|
44
|
+
this.addSql(`alter table "users" drop constraint if exists "users_email_unique";`);
|
|
45
|
+
this.addSql(`create unique index if not exists "users_tenant_email_hash_uniq" on "users" ("tenant_id", "email_hash") where "deleted_at" is null and "email_hash" is not null;`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override down(): void | Promise<void> {
|
|
49
|
+
this.addSql(`drop index if exists "users_tenant_email_hash_uniq";`);
|
|
50
|
+
this.addSql(`alter table "users" add constraint "users_email_unique" unique ("email");`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
// #2966: user_roles carries only its FK constraints and Postgres does not
|
|
4
|
+
// auto-index FK columns, so RBAC scans it sequentially by user_id on every
|
|
5
|
+
// ACL cache miss (rbacService super-admin check + ACL aggregation) and by
|
|
6
|
+
// role_id on user-list filtering and role rename/delete guards. Index both
|
|
7
|
+
// FK columns so these hot paths become index scans. The table is small
|
|
8
|
+
// relative to search_tokens, so a plain (transactional) build is safe.
|
|
9
|
+
export class Migration20260611103000 extends Migration {
|
|
10
|
+
|
|
11
|
+
override up(): void | Promise<void> {
|
|
12
|
+
this.addSql(`create index if not exists "user_roles_user_id_idx" on "user_roles" ("user_id");`);
|
|
13
|
+
this.addSql(`create index if not exists "user_roles_role_id_idx" on "user_roles" ("role_id");`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override down(): void | Promise<void> {
|
|
17
|
+
this.addSql(`drop index if exists "user_roles_user_id_idx";`);
|
|
18
|
+
this.addSql(`drop index if exists "user_roles_role_id_idx";`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -5,6 +5,13 @@ import { emailHashLookupValues } from '@open-mercato/core/modules/auth/lib/email
|
|
|
5
5
|
import { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'
|
|
6
6
|
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
7
|
|
|
8
|
+
// A fixed, valid bcrypt hash (cost 10) of a throwaway value no real password
|
|
9
|
+
// can match. verifyPassword compares against it whenever the user is missing or
|
|
10
|
+
// has no password hash, so a failed login spends the same bcrypt CPU time
|
|
11
|
+
// regardless of whether the account exists — closing the timing side channel
|
|
12
|
+
// for account enumeration (issue #2242).
|
|
13
|
+
const TIMING_EQUALIZER_PASSWORD_HASH = '$2b$10$OcZrhmZpIzJOjkfwUrk7d.Nl0eHNzOvalBcBlt5Ran.4lj8R3HZg6'
|
|
14
|
+
|
|
8
15
|
export class AuthService {
|
|
9
16
|
constructor(private em: EntityManager) {}
|
|
10
17
|
|
|
@@ -48,9 +55,13 @@ export class AuthService {
|
|
|
48
55
|
)
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
async verifyPassword(user: User, password: string) {
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
async verifyPassword(user: User | null, password: string) {
|
|
59
|
+
const storedHash = user?.passwordHash ?? null
|
|
60
|
+
// Always run a bcrypt comparison — against a fixed dummy hash when the user
|
|
61
|
+
// is absent or has no password — so login latency does not reveal whether
|
|
62
|
+
// the account exists (timing-based enumeration, issue #2242).
|
|
63
|
+
const matched = await compare(password, storedHash ?? TIMING_EQUALIZER_PASSWORD_HASH)
|
|
64
|
+
return storedHash !== null && matched
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
async updateLastLoginAt(user: User) {
|
|
@@ -70,7 +81,16 @@ export class AuthService {
|
|
|
70
81
|
{ populate: ['role'] },
|
|
71
82
|
{ tenantId: resolvedTenantId, organizationId: user.organizationId ?? null },
|
|
72
83
|
)
|
|
73
|
-
|
|
84
|
+
// A populated `role` can still be null when the link points at a soft-deleted
|
|
85
|
+
// role (the Role soft-delete filter suppresses hydration), e.g. an admin link
|
|
86
|
+
// orphaned by a re-seed during interrupted-provisioning recovery. Dropping such
|
|
87
|
+
// links keeps role resolution from throwing on the login / session-refresh hot
|
|
88
|
+
// path, mirroring resolveCanonicalStaffAuthContext in lib/sessionIntegrity.ts.
|
|
89
|
+
return links
|
|
90
|
+
.map((l) => l.role)
|
|
91
|
+
.filter((role): role is Role => !!role)
|
|
92
|
+
.map((role) => role.name)
|
|
93
|
+
.filter((name): name is string => typeof name === 'string' && name.trim().length > 0)
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
|
|
@@ -4,6 +4,7 @@ import { getCurrentCacheTenant, runWithCacheTenant } from '@open-mercato/cache'
|
|
|
4
4
|
import { UserAcl, RoleAcl, User, UserRole } from '@open-mercato/core/modules/auth/data/entities'
|
|
5
5
|
import { ApiKey } from '@open-mercato/core/modules/api_keys/data/entities'
|
|
6
6
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { buildOrgScopeUserCacheTag, buildOrgScopeTenantCacheTag } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
7
8
|
import { matchFeature as sharedMatchFeature, hasAllFeatures as sharedHasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'
|
|
8
9
|
import { filterGrantsByEnabledModules, getOwningModuleId, getEnabledModuleIds } from '@open-mercato/shared/security/enabledModulesRegistry'
|
|
9
10
|
|
|
@@ -130,7 +131,12 @@ export class RbacService {
|
|
|
130
131
|
*/
|
|
131
132
|
async invalidateUserCache(userId: string): Promise<void> {
|
|
132
133
|
this.globalSuperAdminCache.delete(userId)
|
|
133
|
-
|
|
134
|
+
// Also drop the directory OrganizationScope cache for this user. That scope's
|
|
135
|
+
// accessible-org set is derived from this user's ACL/role grants, so any
|
|
136
|
+
// permission change that invalidates the RBAC cache must invalidate the
|
|
137
|
+
// resolved scope too. This is the missing `org-scope:user:*` caller required
|
|
138
|
+
// before the cross-request scope TTL can be safely enabled (issue #2259).
|
|
139
|
+
await this.deleteCacheByTags([this.getUserTag(userId), buildOrgScopeUserCacheTag(userId)])
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
/**
|
|
@@ -142,7 +148,10 @@ export class RbacService {
|
|
|
142
148
|
*/
|
|
143
149
|
async invalidateTenantCache(tenantId: string): Promise<void> {
|
|
144
150
|
this.globalSuperAdminCache.clear()
|
|
145
|
-
|
|
151
|
+
// Role ACL changes invalidate every user in the tenant; the resolved
|
|
152
|
+
// OrganizationScope for those users derives from the same grants, so drop
|
|
153
|
+
// the tenant-tagged scope entries alongside the RBAC ones (issue #2259).
|
|
154
|
+
await this.deleteCacheByTags([this.getTenantTag(tenantId), buildOrgScopeTenantCacheTag(tenantId)], [tenantId])
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Product-configuration surface: option schemas (variant axes) and unit
|
|
6
6
|
* conversions (UoM factors).
|
|
7
7
|
*
|
|
8
|
-
* Phase 3c of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
8
|
+
* Phase 3c of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
9
9
|
* both tools are now API-backed wrappers over the documented CRUD list
|
|
10
10
|
* routes (`GET /api/catalog/option-schemas` and
|
|
11
11
|
* `GET /api/catalog/product-unit-conversions`). Tool names, schemas,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* names available so the D18 tool can layer merchandising-specific shape
|
|
12
12
|
* over the base enumerator.
|
|
13
13
|
*
|
|
14
|
-
* Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
14
|
+
* Phase 3b of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
15
15
|
* `catalog.list_prices` and `catalog.list_offers` are now API-backed wrappers
|
|
16
16
|
* over `GET /api/catalog/prices` and `GET /api/catalog/offers`. Tool names,
|
|
17
17
|
* schemas, requiredFeatures, and output shapes are unchanged. The offers
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Read-only tools scoped to `ctx.tenantId` + `ctx.organizationId`. Mutation
|
|
5
5
|
* tools are deferred to Step 5.14 under the pending-action contract.
|
|
6
6
|
*
|
|
7
|
-
* Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
7
|
+
* Phase 3b of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
8
8
|
* `catalog.list_products` is now an API-backed wrapper over
|
|
9
9
|
* `GET /api/catalog/products`. Tool name, schema, requiredFeatures, and
|
|
10
10
|
* output shape are unchanged.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Enumerate variants for a single product with option values + media refs.
|
|
5
5
|
*
|
|
6
|
-
* Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
6
|
+
* Phase 3b of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
7
7
|
* `catalog.list_variants` is now an API-backed wrapper over
|
|
8
8
|
* `GET /api/catalog/variants`. Tool name, schema, requiredFeatures, and
|
|
9
9
|
* output shape are unchanged.
|
|
@@ -440,7 +440,7 @@ export class MessageReaction {
|
|
|
440
440
|
* forged inbound messages: tokens that don't HMAC-verify never reach the DB
|
|
441
441
|
* lookup.
|
|
442
442
|
*
|
|
443
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
443
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
444
444
|
*/
|
|
445
445
|
@Entity({ tableName: 'channel_thread_tokens' })
|
|
446
446
|
// One token row per (tenant, thread): the matcher resolves every reply to the
|
|
@@ -495,7 +495,7 @@ export class ChannelThreadToken {
|
|
|
495
495
|
* `raw_body` is encrypted at rest via the module's `encryption.ts`
|
|
496
496
|
* `defaultEncryptionMaps` entry (MIME bodies may contain PII).
|
|
497
497
|
*
|
|
498
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
498
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
499
499
|
* (§ 3 Data Model).
|
|
500
500
|
*/
|
|
501
501
|
@Entity({ tableName: 'channel_ingest_dead_letters' })
|
|
@@ -31,7 +31,7 @@ import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryptio
|
|
|
31
31
|
* contact resolution looks addresses up by value (see the address blind-index
|
|
32
32
|
* follow-up in customers/lib/findPeopleByAddresses.ts).
|
|
33
33
|
*
|
|
34
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
34
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
35
35
|
* (§ 3 Encryption posture).
|
|
36
36
|
*/
|
|
37
37
|
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
@@ -445,7 +445,7 @@ export interface ExchangeOAuthCodeResult {
|
|
|
445
445
|
* `RefreshCredentialsInput`. Adapters without OAuth refresh (IMAP, WhatsApp
|
|
446
446
|
* Business API) ignore it.
|
|
447
447
|
*
|
|
448
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
448
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
449
449
|
*/
|
|
450
450
|
export interface OAuthClientConfig {
|
|
451
451
|
clientId: string
|
|
@@ -19,7 +19,7 @@ import { extractTokenFromBody, extractTokenFromHeaders } from './thread-token'
|
|
|
19
19
|
* `UPDATE` that bumps a matched token's `last_seen_at` (a future-GC hint),
|
|
20
20
|
* which does not touch the caller's pending entities.
|
|
21
21
|
*
|
|
22
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
22
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`
|
|
23
23
|
* § 4 Threading Algorithm.
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -17,7 +17,7 @@ import { isUniqueViolation } from './pg-errors'
|
|
|
17
17
|
* `(tenantId, token)` so that even if the HMAC key leaked, tenant
|
|
18
18
|
* isolation still holds at the DB layer.
|
|
19
19
|
*
|
|
20
|
-
* See `.ai/specs/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
20
|
+
* See `.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md`.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
const TOKEN_PREFIX = 'om_'
|
|
@@ -6,6 +6,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
6
6
|
import type { FilterQuery } from '@mikro-orm/core'
|
|
7
7
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
8
8
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
9
|
+
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
9
10
|
import { currencyCreateSchema, currencyUpdateSchema } from '../../data/validators'
|
|
10
11
|
import {
|
|
11
12
|
createCurrenciesCrudOpenApi,
|
|
@@ -148,9 +149,9 @@ export async function GET(req: Request) {
|
|
|
148
149
|
if (code) filter.code = code
|
|
149
150
|
if (search) {
|
|
150
151
|
filter.$or = [
|
|
151
|
-
{ code: { $ilike: `%${search}%` } },
|
|
152
|
-
{ name: { $ilike: `%${search}%` } },
|
|
153
|
-
{ symbol: { $ilike: `%${search}%` } },
|
|
152
|
+
{ code: { $ilike: `%${escapeLikePattern(search)}%` } },
|
|
153
|
+
{ name: { $ilike: `%${escapeLikePattern(search)}%` } },
|
|
154
|
+
{ symbol: { $ilike: `%${escapeLikePattern(search)}%` } },
|
|
154
155
|
]
|
|
155
156
|
}
|
|
156
157
|
if (isBase === 'true') filter.isBase = true
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
|
|
3
3
|
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
4
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
5
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
6
7
|
import { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
8
|
import { CustomerRole, CustomerRoleAcl } from '@open-mercato/core/modules/customer_accounts/data/entities'
|
|
8
9
|
import { createRoleSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
|
|
@@ -37,7 +38,7 @@ export async function GET(req: Request) {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
if (search) {
|
|
40
|
-
const escapedSearch = search
|
|
41
|
+
const escapedSearch = escapeLikePattern(search)
|
|
41
42
|
where.$or = [
|
|
42
43
|
{ name: { $ilike: `%${escapedSearch}%` } },
|
|
43
44
|
{ slug: { $ilike: `%${escapedSearch}%` } },
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { AlertTriangle } from 'lucide-react'
|
|
5
4
|
import { Alert, AlertTitle, AlertDescription } from '@open-mercato/ui/primitives/alert'
|
|
6
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
6
|
import type { DomainMappingRow } from './types'
|
|
@@ -15,7 +14,6 @@ export function DnsDiagnostics({ mapping }: DiagnosticsProps) {
|
|
|
15
14
|
if (mapping.status !== 'dns_failed') return null
|
|
16
15
|
return (
|
|
17
16
|
<Alert variant="destructive">
|
|
18
|
-
<AlertTriangle className="h-4 w-4" aria-hidden />
|
|
19
17
|
<AlertTitle>
|
|
20
18
|
{t('customer_accounts.domainMapping.dns.diagnostics.title', 'DNS configuration issue')}
|
|
21
19
|
</AlertTitle>
|
|
@@ -41,7 +39,6 @@ export function TlsDiagnostics({ mapping }: DiagnosticsProps) {
|
|
|
41
39
|
if (mapping.status !== 'tls_failed') return null
|
|
42
40
|
return (
|
|
43
41
|
<Alert variant="warning">
|
|
44
|
-
<AlertTriangle className="h-4 w-4" aria-hidden />
|
|
45
42
|
<AlertTitle>
|
|
46
43
|
{t('customer_accounts.domainMapping.tls.diagnostics.title', 'SSL certificate issue')}
|
|
47
44
|
</AlertTitle>
|
|
@@ -18,7 +18,7 @@ const events = [
|
|
|
18
18
|
{ id: 'customer_accounts.role.deleted', label: 'Customer Role Deleted', entity: 'role', category: 'crud' },
|
|
19
19
|
{ id: 'customer_accounts.invitation.accepted', label: 'Customer Invitation Accepted', category: 'lifecycle', clientBroadcast: true },
|
|
20
20
|
{ id: 'customer_accounts.password_reset.requested', label: 'Customer Password Reset Requested', category: 'lifecycle' },
|
|
21
|
-
// Custom domain mapping lifecycle (see .ai/specs/2026-04-08-portal-custom-domain-routing.md)
|
|
21
|
+
// Custom domain mapping lifecycle (see .ai/specs/implemented/2026-04-08-portal-custom-domain-routing.md)
|
|
22
22
|
{ id: 'customer_accounts.domain_mapping.created', label: 'Custom Domain Registered', entity: 'domain_mapping', category: 'crud', clientBroadcast: true },
|
|
23
23
|
{ id: 'customer_accounts.domain_mapping.verified', label: 'Custom Domain DNS Verified', entity: 'domain_mapping', category: 'lifecycle', clientBroadcast: true },
|
|
24
24
|
{ id: 'customer_accounts.domain_mapping.activated', label: 'Custom Domain Active', entity: 'domain_mapping', category: 'lifecycle', clientBroadcast: true },
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* signup, magic-link, password-reset) so they all behave consistently when
|
|
13
13
|
* the request arrives on a tenant's branded URL.
|
|
14
14
|
*
|
|
15
|
-
* See `.ai/specs/2026-04-08-portal-custom-domain-routing.md` Phase 1.5 and
|
|
16
|
-
* `.ai/specs/2026-06-05-tenant-ownership-and-module-acl-authorization.md` § C.
|
|
15
|
+
* See `.ai/specs/implemented/2026-04-08-portal-custom-domain-routing.md` Phase 1.5 and
|
|
16
|
+
* `.ai/specs/implemented/2026-06-05-tenant-ownership-and-module-acl-authorization.md` § C.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { tryNormalizeHostname } from '@open-mercato/core/modules/customer_accounts/lib/hostname'
|
|
@@ -89,7 +89,7 @@ export const features = [
|
|
|
89
89
|
// privacy model is strict owner-only with NO admin bypass, so this feature is
|
|
90
90
|
// declared but INERT — granting it does not unlock other users' private emails
|
|
91
91
|
// (the visibility filter and the visibility-change gate ignore it). See
|
|
92
|
-
// .ai/specs/2026-05-27-crm-email-integration.md (v1 strict owner-only).
|
|
92
|
+
// .ai/specs/implemented/2026-05-27-crm-email-integration.md (v1 strict owner-only).
|
|
93
93
|
{
|
|
94
94
|
id: 'customers.email.view_private',
|
|
95
95
|
title: 'View other users\' private emails (reserved — inert in v1)',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `customers.list_companies` + `customers.get_company` (Phase 1 WS-C, Step 3.9).
|
|
3
3
|
*
|
|
4
|
-
* Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
4
|
+
* Phase 3a of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
5
5
|
* `customers.list_companies` is now an API-backed wrapper over
|
|
6
6
|
* `GET /api/customers/companies`. Tool name, schema, requiredFeatures, and
|
|
7
7
|
* output shape are unchanged.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `customers.list_deals` + `customers.get_deal` (Phase 1 WS-C, Step 3.9).
|
|
3
3
|
* `customers.update_deal_stage` mutation tool (Phase 3 WS-C, Step 5.13).
|
|
4
4
|
*
|
|
5
|
-
* Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
5
|
+
* Phase 3a of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
6
6
|
* `customers.list_deals` is now an API-backed wrapper over
|
|
7
7
|
* `GET /api/customers/deals`. Tool name, schema, requiredFeatures, and output
|
|
8
8
|
* shape are unchanged.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the existing customers query engine + encryption helpers. Mutation tools
|
|
6
6
|
* are deferred to Step 5.13+ under the pending-action contract.
|
|
7
7
|
*
|
|
8
|
-
* Phase 3a of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
8
|
+
* Phase 3a of `.ai/specs/implemented/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
9
9
|
* `customers.list_people` is now an API-backed wrapper over
|
|
10
10
|
* `GET /api/customers/people`. The `companyId` AI input has no inclusion
|
|
11
11
|
* equivalent on the route (the route exposes `excludeLinkedCompanyId` only)
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
extractAllCustomFieldEntries,
|
|
24
24
|
splitCustomFieldPayload,
|
|
25
25
|
} from '@open-mercato/shared/lib/crud/custom-fields'
|
|
26
|
-
import {
|
|
26
|
+
import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
|
|
27
27
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
28
28
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
29
29
|
import { consumeAdvancedFilterState, mergeAdvancedFilterTree } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
@@ -178,7 +178,7 @@ const crud = makeCrudRoute({
|
|
|
178
178
|
if (matchingIds !== null && matchingIds.length > 0) {
|
|
179
179
|
applyEntityIdRestriction(filters, matchingIds)
|
|
180
180
|
} else {
|
|
181
|
-
const searchPattern =
|
|
181
|
+
const searchPattern = buildIlikeTerm(query.search)
|
|
182
182
|
filters.$or = [
|
|
183
183
|
{ display_name: { $ilike: searchPattern } },
|
|
184
184
|
{ primary_email: { $ilike: searchPattern } },
|
|
@@ -276,9 +276,9 @@ const crud = makeCrudRoute({
|
|
|
276
276
|
if (email) {
|
|
277
277
|
filters.primary_email = { $eq: email }
|
|
278
278
|
} else if (emailStartsWith) {
|
|
279
|
-
filters.primary_email = { $ilike:
|
|
279
|
+
filters.primary_email = { $ilike: buildIlikeTerm(emailStartsWith, 'startsWith') }
|
|
280
280
|
} else if (emailContains) {
|
|
281
|
-
filters.primary_email = { $ilike:
|
|
281
|
+
filters.primary_email = { $ilike: buildIlikeTerm(emailContains) }
|
|
282
282
|
}
|
|
283
283
|
const hasEmail = parseBooleanToken(query.hasEmail)
|
|
284
284
|
if (!email && !emailStartsWith && !emailContains && hasEmail !== null) {
|
|
@@ -27,6 +27,7 @@ import { fetchStuckDealIds } from '../../lib/stuckDeals'
|
|
|
27
27
|
const rawBodySchema = z.object({}).passthrough()
|
|
28
28
|
|
|
29
29
|
const stringOrStringArray = z.union([z.string(), z.array(z.string())])
|
|
30
|
+
const OPEN_DEAL_STATUSES = ['open', 'in_progress'] as const
|
|
30
31
|
const booleanQueryParam = z.preprocess((value) => {
|
|
31
32
|
const parsed = parseBooleanFromUnknown(value)
|
|
32
33
|
return parsed === null ? value : parsed
|
|
@@ -47,6 +48,7 @@ export const dealListQuerySchema = z
|
|
|
47
48
|
expectedCloseAtTo: z.string().optional(),
|
|
48
49
|
isStuck: booleanQueryParam,
|
|
49
50
|
isOverdue: booleanQueryParam,
|
|
51
|
+
needsAttention: booleanQueryParam,
|
|
50
52
|
valueCurrency: stringOrStringArray.optional(),
|
|
51
53
|
sortField: z.string().optional(),
|
|
52
54
|
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
@@ -156,6 +158,43 @@ async function fetchDealIdsMatchingAssociations(
|
|
|
156
158
|
return rows.map((row) => row.id)
|
|
157
159
|
}
|
|
158
160
|
|
|
161
|
+
async function fetchNeedAttentionDealIds(
|
|
162
|
+
em: EntityManager,
|
|
163
|
+
organizationId: string,
|
|
164
|
+
tenantId: string,
|
|
165
|
+
): Promise<string[]> {
|
|
166
|
+
const connection = em.getConnection()
|
|
167
|
+
const overdueRows = await connection.execute<Array<{ id: string }>>(
|
|
168
|
+
`SELECT id FROM customer_deals
|
|
169
|
+
WHERE organization_id = ?
|
|
170
|
+
AND tenant_id = ?
|
|
171
|
+
AND deleted_at IS NULL
|
|
172
|
+
AND status = 'open'
|
|
173
|
+
AND expected_close_at IS NOT NULL
|
|
174
|
+
AND expected_close_at < CURRENT_DATE`,
|
|
175
|
+
[organizationId, tenantId],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const attentionIds = new Set(overdueRows.map((row) => row.id))
|
|
179
|
+
const stuckIds = await fetchStuckDealIds(em, organizationId, tenantId)
|
|
180
|
+
if (stuckIds.length > 0) {
|
|
181
|
+
const idPlaceholders = stuckIds.map(() => '?').join(',')
|
|
182
|
+
const statusPlaceholders = OPEN_DEAL_STATUSES.map(() => '?').join(',')
|
|
183
|
+
const openStuckRows = await connection.execute<Array<{ id: string }>>(
|
|
184
|
+
`SELECT id FROM customer_deals
|
|
185
|
+
WHERE organization_id = ?
|
|
186
|
+
AND tenant_id = ?
|
|
187
|
+
AND deleted_at IS NULL
|
|
188
|
+
AND status IN (${statusPlaceholders})
|
|
189
|
+
AND id IN (${idPlaceholders})`,
|
|
190
|
+
[organizationId, tenantId, ...OPEN_DEAL_STATUSES, ...stuckIds],
|
|
191
|
+
)
|
|
192
|
+
for (const row of openStuckRows) attentionIds.add(row.id)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Array.from(attentionIds)
|
|
196
|
+
}
|
|
197
|
+
|
|
159
198
|
function normalizeCurrencyList(value: unknown): string[] {
|
|
160
199
|
const set = new Set<string>()
|
|
161
200
|
const visit = (entry: unknown) => {
|
|
@@ -302,7 +341,7 @@ export async function buildDealListFilters(query: DealListQuery, ctx?: import('@
|
|
|
302
341
|
filters.expected_close_at = range
|
|
303
342
|
}
|
|
304
343
|
|
|
305
|
-
if (query.isOverdue) {
|
|
344
|
+
if (query.isOverdue && !query.needsAttention) {
|
|
306
345
|
const today = new Date()
|
|
307
346
|
today.setHours(0, 0, 0, 0)
|
|
308
347
|
if (statusList.length === 0) {
|
|
@@ -316,7 +355,7 @@ export async function buildDealListFilters(query: DealListQuery, ctx?: import('@
|
|
|
316
355
|
filters.expected_close_at = existingRange
|
|
317
356
|
}
|
|
318
357
|
|
|
319
|
-
if (query.isStuck && ctx) {
|
|
358
|
+
if (query.isStuck && !query.needsAttention && ctx) {
|
|
320
359
|
const tenantId = ctx.auth?.tenantId
|
|
321
360
|
// CrudCtx.auth carries `orgId` (not `organizationId`). The previous code referenced
|
|
322
361
|
// `organizationId` which is always `undefined`, so the typeof check below silently
|
|
@@ -329,6 +368,16 @@ export async function buildDealListFilters(query: DealListQuery, ctx?: import('@
|
|
|
329
368
|
}
|
|
330
369
|
}
|
|
331
370
|
|
|
371
|
+
if (query.needsAttention && ctx) {
|
|
372
|
+
const tenantId = ctx.auth?.tenantId
|
|
373
|
+
const organizationId = ctx.auth?.orgId
|
|
374
|
+
if (typeof tenantId === 'string' && typeof organizationId === 'string') {
|
|
375
|
+
const em = ctx.container.resolve<EntityManager>('em')
|
|
376
|
+
const attentionIds = await fetchNeedAttentionDealIds(em, organizationId, tenantId)
|
|
377
|
+
intersectIds(attentionIds)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
332
381
|
// Pre-pagination association filter. Must run on the FULL dataset (before pagination),
|
|
333
382
|
// otherwise matching deals on later pages disappear and `total` would be wrong. Read the
|
|
334
383
|
// raw URL too so legacy `?personEntityId=` / `?companyEntityId=` keep working alongside the
|