Implement sales tenant pipeline events
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# sales_service Step 1 Audit
|
||||
|
||||
Дата аудита: 2026-05-10.
|
||||
|
||||
Цель: зафиксировать фактическое состояние `sales_service` перед проектированием MVP sales workflow engine.
|
||||
|
||||
## Артефакты
|
||||
|
||||
- `sales_service_audit.md` - основное резюме: тесты, API, ER, gaps, риски.
|
||||
- `sales_service_api_routes.md` - фактический список FastAPI routes.
|
||||
- `sales_service_openapi.json` - выгруженный OpenAPI из `services.sales_service.app`.
|
||||
- `sales_service_routes.json` - машинный список routes.
|
||||
- `sales_service_er_map.md` - ER-карта sales-таблиц из SQLAlchemy metadata.
|
||||
- `sales_service_er_schema.json` - машинная ER-схема.
|
||||
- `sales_service_er_db_sqlite.md` - ER-карта фактической SQLite-схемы после migration-runner.
|
||||
- `sales_service_er_db_sqlite.json` - машинная фактическая SQLite-схема после migration-runner.
|
||||
- `sales_service_migrations.md` - список sales-миграций и результат migration smoke check.
|
||||
- `sales_service_risk_points.md` - точки риска в коде: tenant, stages, automation, payment webhook.
|
||||
- `sales_service_test_results.txt` - сохраненный вывод `pytest tests/test_sales_service.py -q`.
|
||||
- `sales_service_migration_check.txt` - сохраненный вывод migration smoke check на чистой SQLite DB.
|
||||
|
||||
## Команды для воспроизведения
|
||||
|
||||
```bash
|
||||
cd call-center
|
||||
python3.11 -m venv .venv
|
||||
.venv/bin/python -m pip install --upgrade pip setuptools wheel
|
||||
.venv/bin/python -m pip install -r requirements.txt
|
||||
.venv/bin/python -m pytest tests/test_sales_service.py -q
|
||||
```
|
||||
|
||||
Важно: `python3` на этой машине указывает на Python 3.9.6, а проект требует Python 3.10+ (`docs/runbooks/local-setup.md`). На Python 3.9 тесты падают еще на collection из-за `str | None` annotations.
|
||||
@@ -0,0 +1,71 @@
|
||||
# sales_service API routes
|
||||
|
||||
Total routes: 67
|
||||
|
||||
- `POST` `/api/v1/calls/inbound-webhook` -> `inbound_call`
|
||||
- `POST` `/api/v1/calls/outbound` -> `outbound_call`
|
||||
- `POST` `/api/v1/calls/{call_id}/complete` -> `complete_call`
|
||||
- `POST` `/api/v1/calls/{call_id}/transcript` -> `attach_transcript`
|
||||
- `POST` `/api/v1/communications/{communication_id}/bind-external` -> `bind_external_communication`
|
||||
- `POST` `/api/v1/communications/{communication_id}/summary` -> `summarize_communication`
|
||||
- `POST` `/api/v1/communications/{communication_id}/switch-channel` -> `switch_channel`
|
||||
- `GET` `/api/v1/dashboard` -> `sales_dashboard`
|
||||
- `GET` `/api/v1/deals` -> `list_deals`
|
||||
- `POST` `/api/v1/deals` -> `create_deal`
|
||||
- `GET` `/api/v1/deals/{deal_id}` -> `get_deal`
|
||||
- `PATCH` `/api/v1/deals/{deal_id}` -> `update_deal`
|
||||
- `GET` `/api/v1/deals/{deal_id}/calls` -> `list_calls`
|
||||
- `POST` `/api/v1/deals/{deal_id}/change-stage` -> `change_stage`
|
||||
- `POST` `/api/v1/deals/{deal_id}/close` -> `close_deal`
|
||||
- `GET` `/api/v1/deals/{deal_id}/communications` -> `list_communications`
|
||||
- `POST` `/api/v1/deals/{deal_id}/communications/text` -> `start_text_communication`
|
||||
- `POST` `/api/v1/deals/{deal_id}/communications/voice` -> `start_voice_communication`
|
||||
- `POST` `/api/v1/deals/{deal_id}/conditions` -> `upsert_conditions`
|
||||
- `GET` `/api/v1/deals/{deal_id}/counterparty` -> `get_counterparty`
|
||||
- `PATCH` `/api/v1/deals/{deal_id}/counterparty` -> `patch_counterparty`
|
||||
- `POST` `/api/v1/deals/{deal_id}/counterparty` -> `create_counterparty`
|
||||
- `POST` `/api/v1/deals/{deal_id}/documents` -> `create_document`
|
||||
- `POST` `/api/v1/deals/{deal_id}/escalate` -> `escalate_deal`
|
||||
- `POST` `/api/v1/deals/{deal_id}/invoices` -> `create_invoice`
|
||||
- `GET` `/api/v1/deals/{deal_id}/messages` -> `list_messages`
|
||||
- `POST` `/api/v1/deals/{deal_id}/offers` -> `create_offer`
|
||||
- `GET` `/api/v1/deals/{deal_id}/payments` -> `list_payments`
|
||||
- `POST` `/api/v1/deals/{deal_id}/schedule-next-action` -> `schedule_next_action`
|
||||
- `POST` `/api/v1/deals/{deal_id}/select-scenario` -> `select_scenario`
|
||||
- `GET` `/api/v1/deals/{deal_id}/workspace` -> `get_workspace`
|
||||
- `GET` `/api/v1/documents/{document_id}` -> `get_document`
|
||||
- `POST` `/api/v1/documents/{document_id}/confirm` -> `confirm_document`
|
||||
- `POST` `/api/v1/documents/{document_id}/send` -> `send_document`
|
||||
- `POST` `/api/v1/documents/{document_id}/sign-status-webhook` -> `sign_document`
|
||||
- `GET` `/api/v1/invoices/{invoice_id}` -> `get_invoice`
|
||||
- `POST` `/api/v1/invoices/{invoice_id}/mark-overdue` -> `mark_invoice_overdue`
|
||||
- `POST` `/api/v1/invoices/{invoice_id}/send` -> `send_invoice`
|
||||
- `GET` `/api/v1/leads` -> `list_leads`
|
||||
- `POST` `/api/v1/leads` -> `create_lead`
|
||||
- `GET` `/api/v1/leads/{lead_id}` -> `get_lead`
|
||||
- `PATCH` `/api/v1/leads/{lead_id}` -> `update_lead`
|
||||
- `POST` `/api/v1/leads/{lead_id}/convert-to-deal` -> `convert_lead_to_deal`
|
||||
- `POST` `/api/v1/leads/{lead_id}/enrich` -> `enrich_lead`
|
||||
- `POST` `/api/v1/messages/inbound-webhook` -> `inbound_message`
|
||||
- `POST` `/api/v1/messages/outbound` -> `outbound_message`
|
||||
- `GET` `/api/v1/offers/{offer_id}` -> `get_offer`
|
||||
- `POST` `/api/v1/offers/{offer_id}/accept` -> `accept_offer`
|
||||
- `POST` `/api/v1/offers/{offer_id}/reject` -> `reject_offer`
|
||||
- `POST` `/api/v1/offers/{offer_id}/send` -> `send_offer`
|
||||
- `POST` `/api/v1/payments/webhook` -> `payment_webhook`
|
||||
- `POST` `/api/v1/payments/{payment_id}/reconcile` -> `reconcile_payment`
|
||||
- `PATCH` `/api/v1/pipeline-stages/{stage_id}` -> `update_pipeline_stage`
|
||||
- `GET` `/api/v1/pipelines` -> `list_pipelines`
|
||||
- `POST` `/api/v1/pipelines` -> `create_pipeline`
|
||||
- `GET` `/api/v1/pipelines/{pipeline_id}` -> `get_pipeline`
|
||||
- `PATCH` `/api/v1/pipelines/{pipeline_id}` -> `update_pipeline`
|
||||
- `POST` `/api/v1/pipelines/{pipeline_id}/set-default` -> `set_default_pipeline`
|
||||
- `GET` `/api/v1/pipelines/{pipeline_id}/stages` -> `list_pipeline_stages`
|
||||
- `POST` `/api/v1/pipelines/{pipeline_id}/stages` -> `create_pipeline_stage`
|
||||
- `GET` `/docs` -> `swagger_ui_html`
|
||||
- `GET` `/docs/oauth2-redirect` -> `swagger_ui_redirect`
|
||||
- `GET` `/health` -> `health`
|
||||
- `POST` `/internal/sales-sync/telegram` -> `sync_telegram_thread`
|
||||
- `POST` `/internal/sales-sync/voice` -> `sync_voice_session`
|
||||
- `GET` `/openapi.json` -> `openapi`
|
||||
- `GET` `/redoc` -> `redoc_html`
|
||||
@@ -0,0 +1,184 @@
|
||||
# sales_service Step 1 Audit
|
||||
|
||||
## Executive Summary
|
||||
|
||||
`sales_service` уже содержит широкий MVP data/API layer для продаж: Lead, Deal, CommunicationSession, Message, Call, Transcript, Offer, DealCondition, Counterparty, Document, Invoice, Payment, StageHistory, Escalation, AutomationTask, ChannelSwitch и ExternalLink.
|
||||
|
||||
Это пока не зрелый sales workflow engine. После шагов 2-4 уже добавлены MVP tenant boundary, configurable pipeline/stages и публикация sales events в `event_outbox`. Основные оставшиеся ограничения: нет state machine для допустимых переходов, automation tasks не исполняются worker-ом, webhook-и не проверяют подпись провайдера, `sales_notes` существует только как таблица/модель без API.
|
||||
|
||||
## Step 4 Delta: Sales Events
|
||||
|
||||
После шагов 2-4 состояние изменилось:
|
||||
|
||||
- `sales_service` пишет sales domain events в общий `event_outbox` через `SalesEventPublisher`.
|
||||
- Registry событий находится в `services/sales_service/sales_events.py`; текущая версия событий `1`.
|
||||
- События создаются в той же SQLAlchemy session/transaction, что и бизнес-действие, до `session.commit()`.
|
||||
- `tenant_id`, `aggregate_type`, `aggregate_id`, actor metadata и causation/correlation metadata передаются в `payload` event envelope; общий `event_outbox` schema не расширялся.
|
||||
- Consumer-ы для sales events пока не реализованы: на шаге 4 добавлена только публикация в outbox.
|
||||
|
||||
Публикуемые MVP events:
|
||||
|
||||
- Lead/Deal: `lead.entered_crm`, `lead.enrichment_completed`, `deal.stage_changed`, `deal.scenario_selected`, `deal.next_action_scheduled`, `deal.closed`, `deal.lost`, `deal.won`.
|
||||
- Communications: `communication.started`, `message.received`, `message.sent`, `call.received`, `call.completed`, `communication.summary_created`.
|
||||
- Commercial: `offer.created`, `offer.sent`, `offer.accepted`, `offer.rejected`, `deal.conditions_confirmed`.
|
||||
- Counterparty/Documents: `counterparty.completed`, `document.created`, `document.sent`, `document.confirmed`, `document.signed`.
|
||||
- Invoice/Payment: `invoice.created`, `invoice.sent`, `invoice.overdue`, `payment.received`, `invoice.paid`.
|
||||
|
||||
Step 4 verification:
|
||||
|
||||
- `tests/test_sales_events.py` covers outbox writes for lead entry, stage changes, inbound message/call, call completion, offer/invoice actions, payment, invoice paid, deal won, tenant scope and failed-action rollback behavior.
|
||||
- Current focused run: `40 passed / 2 xfailed` for `tests/test_sales_events.py`, `tests/test_sales_service.py`, `tests/test_sales_tenant_isolation.py`, `tests/test_sales_pipeline_stages.py`, `tests/test_schema_migrations.py`.
|
||||
|
||||
## Test Environment
|
||||
|
||||
Фактический baseline:
|
||||
|
||||
- `python3` на машине: Python 3.9.6.
|
||||
- `python3.11`: Python 3.11.15.
|
||||
- Проектный runbook требует Python 3.10+.
|
||||
- Зависимости из `requirements.txt` успешно установлены в локальный venv на Python 3.11.
|
||||
- `pytest` установлен через `requirements.txt`.
|
||||
- Тестовая DB настроена в `tests/conftest.py`: SQLite в `call-center/.testdata/mvp_cc_test.db`.
|
||||
- `conftest.py` чистит таблицы перед тестами через SQLAlchemy metadata.
|
||||
|
||||
Команда:
|
||||
|
||||
```bash
|
||||
cd call-center
|
||||
python3.11 -m venv .venv
|
||||
.venv/bin/python -m pip install -r requirements.txt
|
||||
.venv/bin/python -m pytest tests/test_sales_service.py -q
|
||||
```
|
||||
|
||||
Результат `tests/test_sales_service.py`:
|
||||
|
||||
- 6 тестов собрались и запустились.
|
||||
- 4 passed.
|
||||
- 2 failed.
|
||||
|
||||
Падающие тесты фиксируют рассинхрон API/workspace-контракта:
|
||||
|
||||
- `test_sales_internal_telegram_sync_auto_creates_workspace`: `workspace["communications"][0]["channel_provider"]` отсутствует. Сейчас provider лежит в `communication.metadata`, а не на верхнем уровне `SalesCommunicationOut`.
|
||||
- `test_sales_internal_voice_sync_creates_call_and_transcript`: `workspace["transcripts"]` отсутствует. При этом transcript создается и привязывается к call/communication через `transcript_id`.
|
||||
|
||||
Полный вывод сохранен в `sales_service_test_results.txt`.
|
||||
|
||||
## API Snapshot
|
||||
|
||||
OpenAPI выгружен в `sales_service_openapi.json`.
|
||||
|
||||
Фактически найдено 67 service routes без `/docs`, `/openapi.json`, `/redoc`.
|
||||
|
||||
По блокам:
|
||||
|
||||
- Leads: create/list/get/update/enrich/convert-to-deal.
|
||||
- Deals: create/list/get/update/workspace/change-stage/select-scenario/schedule-next-action/close/escalate.
|
||||
- Communications: start text/start voice/list/summary/switch-channel/bind-external.
|
||||
- Messages: inbound webhook/outbound/list by deal.
|
||||
- Calls: inbound webhook/outbound/complete/list by deal/attach transcript.
|
||||
- Offers: create/get/send/accept/reject.
|
||||
- Conditions: upsert by deal.
|
||||
- Counterparty: create/patch/get by deal.
|
||||
- Documents: create/get/send/confirm/sign-status-webhook.
|
||||
- Invoices: create/get/send/mark-overdue.
|
||||
- Payments: payment webhook/list by deal/reconcile.
|
||||
- Internal sync: Telegram and Voice sync into sales workspace.
|
||||
- Dashboard: sales summary.
|
||||
- Pipelines/Stages: list/get/create/update/set-default pipelines, list/create/update stages.
|
||||
|
||||
Нет отдельных API для:
|
||||
|
||||
- `Tenant`.
|
||||
- `AutomationTask` CRUD/list/execute/retry.
|
||||
- `DealNote` create/list/update.
|
||||
- Escalation resolve/reassign.
|
||||
- Event outbox inspection scoped to sales.
|
||||
|
||||
## ER Snapshot
|
||||
|
||||
ER-карта из SQLAlchemy metadata сохранена в `sales_service_er_map.md`, машинная схема - в `sales_service_er_schema.json`.
|
||||
|
||||
Дополнительно сгенерирована ER-карта фактической SQLite-схемы после `scripts/migrate_core_db.py`: `sales_service_er_db_sqlite.md` и `sales_service_er_db_sqlite.json`.
|
||||
|
||||
Ключевые факты:
|
||||
|
||||
- Таблица из ТЗ `sales_deal_conditions` отсутствует; текущая реализация использует `sales_conditions`.
|
||||
- Все sales-связи хранятся строковыми id (`deal_id`, `lead_id`, `customer_id`, `communication_id`, `invoice_id`, etc.).
|
||||
- Физических foreign keys в sales-таблицах нет.
|
||||
- Unique есть в основном на surrogate business ids (`lead_id`, `deal_id`, `message_id`, etc.).
|
||||
- `sales_counterparties.deal_id` unique, то есть один counterparty record на сделку.
|
||||
- `sales_payments.external_payment_id` индексирован; после tenant hardening добавлен partial unique на `(tenant_id, payment_provider, external_payment_id)` для ненулевых external ids.
|
||||
- `sales_messages(channel_provider, external_message_id)` индексирован, но не unique.
|
||||
- `sales_calls(provider, external_call_id)` индексирован, но не unique.
|
||||
|
||||
Важное отличие metadata от миграций:
|
||||
|
||||
- SQLAlchemy metadata содержит много `index=True` на отдельных колонках.
|
||||
- Проверка чистой SQLite DB после migration-runner показывает, что миграции создают только явно прописанные индексы. Например, `sales_deals` после миграций имеет 3 индекса, а не все single-column индексы из metadata.
|
||||
- Для Postgres это означает, что performance-план нужно сверять именно с SQL migrations, а не только с ORM-моделью.
|
||||
|
||||
Критичные nullable-связи:
|
||||
|
||||
- `sales_deals.lead_id` и `sales_deals.customer_id` nullable: сделка может быть без лида или без клиента.
|
||||
- `sales_communication_sessions.lead_id/customer_id/transcript_id` nullable: контекст сделки не всегда содержит клиента/транскрипт.
|
||||
- `sales_invoices.customer_id/basis_document_id` nullable: счет может быть без клиента и без документа-основания.
|
||||
- `sales_payments.invoice_id` nullable: платеж может быть привязан только к сделке.
|
||||
- Внешние ids (`external_message_id`, `external_call_id`, `external_payment_id`) nullable и не unique.
|
||||
|
||||
Таблицы, которые есть:
|
||||
|
||||
- `sales_leads`
|
||||
- `sales_deals`
|
||||
- `sales_communication_sessions`
|
||||
- `sales_messages`
|
||||
- `sales_calls`
|
||||
- `sales_transcripts`
|
||||
- `sales_notes`
|
||||
- `sales_offers`
|
||||
- `sales_conditions`
|
||||
- `sales_counterparties`
|
||||
- `sales_documents`
|
||||
- `sales_invoices`
|
||||
- `sales_payments`
|
||||
- `sales_stage_history`
|
||||
- `sales_escalations`
|
||||
- `sales_automation_tasks`
|
||||
- `sales_channel_switches`
|
||||
- `sales_external_links`
|
||||
|
||||
## Migrations
|
||||
|
||||
Sales-миграции:
|
||||
|
||||
- `migrations/sql/0023_sales_sqlite.sql`
|
||||
- `migrations/sql/0023_sales_postgres.sql`
|
||||
- `migrations/sql/0024_sales_external_links_sqlite.sql`
|
||||
- `migrations/sql/0024_sales_external_links_postgres.sql`
|
||||
- `migrations/sql/0025_tenants_sqlite.sql`
|
||||
- `migrations/sql/0025_tenants_postgres.sql`
|
||||
- `migrations/sql/0026_sales_pipelines_sqlite.sql`
|
||||
- `migrations/sql/0026_sales_pipelines_postgres.sql`
|
||||
|
||||
Migration smoke check на чистой SQLite DB прошел:
|
||||
|
||||
- `scripts/migrate_core_db.py` применил все SQLite migrations до `0026_sales_pipelines_sqlite.sql`.
|
||||
- `missing_migration_versions()` вернул пустой список.
|
||||
- `services.sales_service.app` импортируется при `SCHEMA_MANAGEMENT_MODE=migrations`.
|
||||
|
||||
Полный вывод сохранен в `sales_service_migration_check.txt`.
|
||||
|
||||
## Architecture Gaps
|
||||
|
||||
| Area | Status | Gap |
|
||||
|---|---|---|
|
||||
| Tenant | MVP подключен | Есть `Tenant`, tenant settings/integrations и tenant-scoped sales API; полная subscription/billing модель еще не реализована. |
|
||||
| Stages | MVP подключен | Есть tenant `Pipeline`/`PipelineStage`, default pipeline/stages и real `pip_*`/`pst_*`; нет state machine для transition rules. |
|
||||
| Events | MVP подключен | Sales domain events пишутся в общий `event_outbox`; consumer-ы и обработчики пока не реализованы. |
|
||||
| Automation | частично | `sales_automation_tasks` создаются, но worker/dispatcher для pending tasks не найден. |
|
||||
| Payments | частично | Webhook идемпотентен по external payment id и двигает Invoice/Deal, но нет provider signature verification и сверки суммы платежей с invoice total. |
|
||||
| Notes | частично | `sales_notes` и `SalesNoteRow` есть, но API/model output/workspace integration отсутствуют. |
|
||||
| Escalation | частично | Escalation create есть, но нет resolve/reassign lifecycle и SLA/queue integration. |
|
||||
|
||||
## Current Baseline Verdict
|
||||
|
||||
Шаг 1 завершен как audit baseline: API, ER, миграции, тестовый статус и основные точки риска зафиксированы. Перед шагом проектирования MVP workflow engine стоит отдельно решить, что делаем первым: tenant boundary, configurable stages/state machine, event contract/outbox, automation executor или payment hardening.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,522 @@
|
||||
# sales_service ER map from migrated SQLite DB
|
||||
|
||||
Generated from a clean DB after `scripts/migrate_core_db.py`.
|
||||
|
||||
- `sales_leads`: 22 columns, 2 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_deals`: 32 columns, 3 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_communication_sessions`: 23 columns, 2 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_messages`: 16 columns, 2 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_calls`: 19 columns, 2 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_transcripts`: 10 columns, 0 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_notes`: 9 columns, 0 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_offers`: 17 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_deal_conditions`: missing
|
||||
- `sales_conditions`: 19 columns, 0 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_counterparties`: 18 columns, 0 indexes, 2 unique constraints, 0 foreign keys
|
||||
- `sales_documents`: 15 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_invoices`: 18 columns, 1 indexes, 2 unique constraints, 0 foreign keys
|
||||
- `sales_payments`: 16 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_stage_history`: 10 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_escalations`: 11 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_automation_tasks`: 12 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_channel_switches`: 9 columns, 1 indexes, 1 unique constraints, 0 foreign keys
|
||||
- `sales_external_links`: 20 columns, 6 indexes, 1 unique constraints, 0 foreign keys
|
||||
|
||||
## `sales_leads`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `lead_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `source_type` | `TEXT` | no | `` | no |
|
||||
| `source_channel` | `TEXT` | no | `` | no |
|
||||
| `source_campaign_id` | `TEXT` | yes | `` | no |
|
||||
| `full_name` | `TEXT` | no | `` | no |
|
||||
| `company_name` | `TEXT` | yes | `` | no |
|
||||
| `phone` | `TEXT` | yes | `` | no |
|
||||
| `email` | `TEXT` | yes | `` | no |
|
||||
| `messenger_handles_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `lead_temperature` | `TEXT` | no | `` | no |
|
||||
| `lead_score` | `REAL` | no | `50` | no |
|
||||
| `customer_type` | `TEXT` | yes | `` | no |
|
||||
| `segment_type` | `TEXT` | yes | `` | no |
|
||||
| `initial_need_summary` | `TEXT` | yes | `` | no |
|
||||
| `preferred_channel` | `TEXT` | no | `` | no |
|
||||
| `assigned_agent_type` | `TEXT` | no | `` | no |
|
||||
| `status` | `TEXT` | no | `` | no |
|
||||
| `crm_customer_id` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_leads_phone_email`: `phone`, `email`
|
||||
- `idx_sales_leads_tenant_status_score`: `tenant_id`, `status`, `lead_score`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `lead_id`
|
||||
|
||||
## `sales_deals`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `lead_id` | `TEXT` | yes | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `pipeline_id` | `TEXT` | no | `'sales_default'` | no |
|
||||
| `stage_id` | `TEXT` | no | `` | no |
|
||||
| `scenario_type` | `TEXT` | no | `` | no |
|
||||
| `priority` | `INTEGER` | no | `3` | no |
|
||||
| `title` | `TEXT` | no | `` | no |
|
||||
| `need_summary` | `TEXT` | yes | `` | no |
|
||||
| `product_context_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `estimated_amount` | `REAL` | yes | `` | no |
|
||||
| `final_amount` | `REAL` | yes | `` | no |
|
||||
| `currency` | `TEXT` | no | `'KZT'` | no |
|
||||
| `payment_model` | `TEXT` | yes | `` | no |
|
||||
| `document_required` | `INTEGER` | no | `1` | no |
|
||||
| `payment_required` | `INTEGER` | no | `1` | no |
|
||||
| `assigned_human_user_id` | `TEXT` | yes | `` | no |
|
||||
| `assigned_ai_orchestrator_id` | `TEXT` | yes | `` | no |
|
||||
| `preferred_channel` | `TEXT` | no | `` | no |
|
||||
| `current_channel` | `TEXT` | no | `` | no |
|
||||
| `status` | `TEXT` | no | `'active'` | no |
|
||||
| `won_reason` | `TEXT` | yes | `` | no |
|
||||
| `lost_reason` | `TEXT` | yes | `` | no |
|
||||
| `close_reason` | `TEXT` | yes | `` | no |
|
||||
| `next_action_type` | `TEXT` | yes | `` | no |
|
||||
| `next_action_at` | `TEXT` | yes | `` | no |
|
||||
| `last_contact_at` | `TEXT` | yes | `` | no |
|
||||
| `closed_at` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_deals_customer_channel`: `customer_id`, `current_channel`
|
||||
- `idx_sales_deals_next_action`: `next_action_at`, `status`
|
||||
- `idx_sales_deals_tenant_stage_status`: `tenant_id`, `stage_id`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `deal_id`
|
||||
|
||||
## `sales_communication_sessions`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `communication_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `lead_id` | `TEXT` | yes | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `channel_type` | `TEXT` | no | `` | no |
|
||||
| `direction` | `TEXT` | no | `` | no |
|
||||
| `agent_type` | `TEXT` | no | `` | no |
|
||||
| `started_at` | `TEXT` | no | `` | no |
|
||||
| `ended_at` | `TEXT` | yes | `` | no |
|
||||
| `duration_sec` | `INTEGER` | yes | `` | no |
|
||||
| `subject` | `TEXT` | yes | `` | no |
|
||||
| `status` | `TEXT` | no | `'active'` | no |
|
||||
| `summary` | `TEXT` | yes | `` | no |
|
||||
| `transcript_id` | `TEXT` | yes | `` | no |
|
||||
| `next_action_type` | `TEXT` | yes | `` | no |
|
||||
| `next_action_at` | `TEXT` | yes | `` | no |
|
||||
| `sentiment` | `TEXT` | yes | `` | no |
|
||||
| `result_code` | `TEXT` | yes | `` | no |
|
||||
| `metadata_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_comm_channel_status`: `channel_type`, `status`
|
||||
- `idx_sales_comm_deal_started`: `deal_id`, `started_at`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `communication_id`
|
||||
|
||||
## `sales_messages`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `message_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `communication_id` | `TEXT` | no | `` | no |
|
||||
| `sender_type` | `TEXT` | no | `` | no |
|
||||
| `sender_id` | `TEXT` | yes | `` | no |
|
||||
| `channel_provider` | `TEXT` | no | `` | no |
|
||||
| `external_message_id` | `TEXT` | yes | `` | no |
|
||||
| `body` | `TEXT` | no | `` | no |
|
||||
| `attachments_json` | `TEXT` | no | `'[]'` | no |
|
||||
| `delivery_status` | `TEXT` | yes | `` | no |
|
||||
| `read_status` | `TEXT` | yes | `` | no |
|
||||
| `message_metadata_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `sent_at` | `TEXT` | no | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_messages_deal_sent`: `deal_id`, `sent_at`
|
||||
- `idx_sales_messages_ext_id`: `channel_provider`, `external_message_id`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `message_id`
|
||||
|
||||
## `sales_calls`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `call_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `communication_id` | `TEXT` | no | `` | no |
|
||||
| `phone_number` | `TEXT` | no | `` | no |
|
||||
| `direction` | `TEXT` | no | `` | no |
|
||||
| `provider` | `TEXT` | no | `` | no |
|
||||
| `external_call_id` | `TEXT` | yes | `` | no |
|
||||
| `recording_url` | `TEXT` | yes | `` | no |
|
||||
| `transcript_status` | `TEXT` | no | `'pending'` | no |
|
||||
| `transcript_id` | `TEXT` | yes | `` | no |
|
||||
| `call_status` | `TEXT` | no | `'started'` | no |
|
||||
| `summary` | `TEXT` | yes | `` | no |
|
||||
| `started_at` | `TEXT` | no | `` | no |
|
||||
| `ended_at` | `TEXT` | yes | `` | no |
|
||||
| `duration_sec` | `INTEGER` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_calls_deal_started`: `deal_id`, `started_at`
|
||||
- `idx_sales_calls_ext_provider`: `provider`, `external_call_id`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `call_id`
|
||||
|
||||
## `sales_transcripts`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `transcript_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `call_id` | `TEXT` | no | `` | no |
|
||||
| `language` | `TEXT` | no | `'ru'` | no |
|
||||
| `transcript_text` | `TEXT` | no | `` | no |
|
||||
| `diarization_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `extracted_entities_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `transcript_id`
|
||||
|
||||
## `sales_notes`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `note_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `author_type` | `TEXT` | no | `` | no |
|
||||
| `author_id` | `TEXT` | yes | `` | no |
|
||||
| `note_type` | `TEXT` | no | `` | no |
|
||||
| `content` | `TEXT` | no | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `note_id`
|
||||
|
||||
## `sales_offers`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `offer_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `offer_type` | `TEXT` | no | `` | no |
|
||||
| `title` | `TEXT` | no | `` | no |
|
||||
| `description` | `TEXT` | yes | `` | no |
|
||||
| `line_items_json` | `TEXT` | no | `'[]'` | no |
|
||||
| `pricing_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `total_amount` | `REAL` | no | `0` | no |
|
||||
| `currency` | `TEXT` | no | `'KZT'` | no |
|
||||
| `validity_until` | `TEXT` | yes | `` | no |
|
||||
| `status` | `TEXT` | no | `'draft'` | no |
|
||||
| `rendered_document_url` | `TEXT` | yes | `` | no |
|
||||
| `created_by_type` | `TEXT` | no | `'text_ai'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_offers_deal_status`: `deal_id`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `offer_id`
|
||||
|
||||
## `sales_deal_conditions`
|
||||
|
||||
Not present in migrated DB.
|
||||
|
||||
## `sales_conditions`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `condition_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `product_name` | `TEXT` | yes | `` | no |
|
||||
| `service_name` | `TEXT` | yes | `` | no |
|
||||
| `quantity` | `REAL` | yes | `` | no |
|
||||
| `unit` | `TEXT` | yes | `` | no |
|
||||
| `delivery_mode` | `TEXT` | yes | `` | no |
|
||||
| `execution_date` | `TEXT` | yes | `` | no |
|
||||
| `start_date` | `TEXT` | yes | `` | no |
|
||||
| `end_date` | `TEXT` | yes | `` | no |
|
||||
| `payment_terms` | `TEXT` | yes | `` | no |
|
||||
| `custom_terms_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `agreed_price` | `REAL` | yes | `` | no |
|
||||
| `currency` | `TEXT` | no | `'KZT'` | no |
|
||||
| `confirmed_at` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `condition_id`
|
||||
|
||||
## `sales_counterparties`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `counterparty_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `company_name` | `TEXT` | yes | `` | no |
|
||||
| `full_name` | `TEXT` | yes | `` | no |
|
||||
| `bin_iin` | `TEXT` | yes | `` | no |
|
||||
| `address` | `TEXT` | yes | `` | no |
|
||||
| `bank_details_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `signer_name` | `TEXT` | yes | `` | no |
|
||||
| `signer_role` | `TEXT` | yes | `` | no |
|
||||
| `signer_basis` | `TEXT` | yes | `` | no |
|
||||
| `email_for_docs` | `TEXT` | yes | `` | no |
|
||||
| `phone_for_docs` | `TEXT` | yes | `` | no |
|
||||
| `completeness_status` | `TEXT` | no | `'draft'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `counterparty_id`
|
||||
- `None`: `deal_id`
|
||||
|
||||
## `sales_documents`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `document_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `document_type` | `TEXT` | no | `` | no |
|
||||
| `template_id` | `TEXT` | yes | `` | no |
|
||||
| `version` | `INTEGER` | no | `1` | no |
|
||||
| `status` | `TEXT` | no | `'draft'` | no |
|
||||
| `file_url` | `TEXT` | yes | `` | no |
|
||||
| `rendered_payload_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `external_sign_provider_id` | `TEXT` | yes | `` | no |
|
||||
| `signed_at` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_documents_deal_status`: `deal_id`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `document_id`
|
||||
|
||||
## `sales_invoices`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `invoice_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `invoice_number` | `TEXT` | no | `` | no |
|
||||
| `basis_document_id` | `TEXT` | yes | `` | no |
|
||||
| `amount` | `REAL` | no | `0` | no |
|
||||
| `currency` | `TEXT` | no | `'KZT'` | no |
|
||||
| `due_date` | `TEXT` | yes | `` | no |
|
||||
| `status` | `TEXT` | no | `'draft'` | no |
|
||||
| `payment_link` | `TEXT` | yes | `` | no |
|
||||
| `line_items_json` | `TEXT` | no | `'[]'` | no |
|
||||
| `metadata_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `issued_at` | `TEXT` | yes | `` | no |
|
||||
| `paid_at` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_invoices_deal_status_due`: `deal_id`, `status`, `due_date`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `invoice_id`
|
||||
- `None`: `invoice_number`
|
||||
|
||||
## `sales_payments`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `payment_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `invoice_id` | `TEXT` | yes | `` | no |
|
||||
| `payment_provider` | `TEXT` | yes | `` | no |
|
||||
| `external_payment_id` | `TEXT` | yes | `` | no |
|
||||
| `amount` | `REAL` | no | `0` | no |
|
||||
| `currency` | `TEXT` | no | `'KZT'` | no |
|
||||
| `status` | `TEXT` | no | `'pending'` | no |
|
||||
| `paid_at` | `TEXT` | yes | `` | no |
|
||||
| `payment_method` | `TEXT` | yes | `` | no |
|
||||
| `failure_reason` | `TEXT` | yes | `` | no |
|
||||
| `metadata_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_payments_deal_status`: `deal_id`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `payment_id`
|
||||
|
||||
## `sales_stage_history`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `history_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `from_stage_id` | `TEXT` | yes | `` | no |
|
||||
| `to_stage_id` | `TEXT` | no | `` | no |
|
||||
| `changed_by_type` | `TEXT` | no | `` | no |
|
||||
| `changed_by_id` | `TEXT` | yes | `` | no |
|
||||
| `reason` | `TEXT` | yes | `` | no |
|
||||
| `changed_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_stage_history_deal_changed`: `deal_id`, `changed_at`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `history_id`
|
||||
|
||||
## `sales_escalations`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `escalation_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `escalation_type` | `TEXT` | no | `` | no |
|
||||
| `reason` | `TEXT` | no | `` | no |
|
||||
| `severity` | `TEXT` | no | `` | no |
|
||||
| `status` | `TEXT` | no | `'open'` | no |
|
||||
| `assigned_to_user_id` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `resolved_at` | `TEXT` | yes | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_escalations_deal_status`: `deal_id`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `escalation_id`
|
||||
|
||||
## `sales_automation_tasks`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `task_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `task_type` | `TEXT` | no | `` | no |
|
||||
| `payload_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `run_at` | `TEXT` | no | `` | no |
|
||||
| `status` | `TEXT` | no | `'pending'` | no |
|
||||
| `retry_count` | `INTEGER` | no | `0` | no |
|
||||
| `last_error` | `TEXT` | yes | `` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_tasks_run_status`: `run_at`, `status`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `task_id`
|
||||
|
||||
## `sales_channel_switches`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `switch_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `communication_id` | `TEXT` | yes | `` | no |
|
||||
| `from_channel` | `TEXT` | no | `` | no |
|
||||
| `to_channel` | `TEXT` | no | `` | no |
|
||||
| `reason_for_channel_switch` | `TEXT` | no | `` | no |
|
||||
| `switched_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_channel_switches_deal_switched`: `deal_id`, `switched_at`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `switch_id`
|
||||
|
||||
## `sales_external_links`
|
||||
|
||||
| Column | Type | Null | Default | PK |
|
||||
|---|---|---:|---|---:|
|
||||
| `id` | `INTEGER` | yes | `` | yes |
|
||||
| `link_id` | `TEXT` | no | `` | no |
|
||||
| `tenant_id` | `TEXT` | no | `` | no |
|
||||
| `deal_id` | `TEXT` | no | `` | no |
|
||||
| `communication_id` | `TEXT` | yes | `` | no |
|
||||
| `sales_call_id` | `TEXT` | yes | `` | no |
|
||||
| `channel_provider` | `TEXT` | no | `` | no |
|
||||
| `external_thread_id` | `TEXT` | yes | `` | no |
|
||||
| `external_chat_id` | `TEXT` | yes | `` | no |
|
||||
| `external_call_id` | `TEXT` | yes | `` | no |
|
||||
| `voice_session_id` | `TEXT` | yes | `` | no |
|
||||
| `ai_session_id` | `TEXT` | yes | `` | no |
|
||||
| `interaction_id` | `TEXT` | yes | `` | no |
|
||||
| `customer_id` | `TEXT` | yes | `` | no |
|
||||
| `phone_number` | `TEXT` | yes | `` | no |
|
||||
| `external_status` | `TEXT` | yes | `` | no |
|
||||
| `link_metadata_json` | `TEXT` | no | `'{}'` | no |
|
||||
| `created_at` | `TEXT` | no | `` | no |
|
||||
| `updated_at` | `TEXT` | no | `` | no |
|
||||
| `last_sync_at` | `TEXT` | no | `` | no |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_external_links_customer_phone`: `customer_id`, `phone_number`
|
||||
- `idx_sales_external_links_deal_sync`: `deal_id`, `last_sync_at`
|
||||
- `idx_sales_external_links_external_call`: `external_call_id`
|
||||
- `idx_sales_external_links_interaction`: `interaction_id`
|
||||
- `idx_sales_external_links_thread`: `external_thread_id`
|
||||
- `idx_sales_external_links_voice_session`: `voice_session_id`
|
||||
|
||||
Unique constraints:
|
||||
- `None`: `link_id`
|
||||
@@ -0,0 +1,696 @@
|
||||
# sales_service ER map
|
||||
|
||||
Generated from SQLAlchemy metadata in `services.shared.sales_sql_models`.
|
||||
|
||||
## Summary
|
||||
- `sales_leads`: 22 columns, 21 indexes, 0 foreign keys
|
||||
- `sales_deals`: 32 columns, 21 indexes, 0 foreign keys
|
||||
- `sales_communication_sessions`: 23 columns, 17 indexes, 0 foreign keys
|
||||
- `sales_messages`: 16 columns, 14 indexes, 0 foreign keys
|
||||
- `sales_calls`: 19 columns, 17 indexes, 0 foreign keys
|
||||
- `sales_transcripts`: 10 columns, 6 indexes, 0 foreign keys
|
||||
- `sales_offers`: 17 columns, 9 indexes, 0 foreign keys
|
||||
- `sales_deal_conditions`: missing
|
||||
- `sales_conditions`: 19 columns, 6 indexes, 0 foreign keys
|
||||
- `sales_counterparties`: 18 columns, 8 indexes, 0 foreign keys
|
||||
- `sales_documents`: 15 columns, 10 indexes, 0 foreign keys
|
||||
- `sales_invoices`: 18 columns, 13 indexes, 0 foreign keys
|
||||
- `sales_payments`: 16 columns, 10 indexes, 0 foreign keys
|
||||
- `sales_stage_history`: 10 columns, 8 indexes, 0 foreign keys
|
||||
- `sales_escalations`: 11 columns, 10 indexes, 0 foreign keys
|
||||
- `sales_automation_tasks`: 12 columns, 9 indexes, 0 foreign keys
|
||||
- `sales_notes`: 9 columns, 7 indexes, 0 foreign keys
|
||||
- `sales_channel_switches`: 9 columns, 8 indexes, 0 foreign keys
|
||||
- `sales_external_links`: 20 columns, 16 indexes, 0 foreign keys
|
||||
|
||||
## `sales_leads`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `lead_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `source_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `source_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `source_campaign_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `full_name` | `VARCHAR(256)` | no | no | no | yes | `` |
|
||||
| `company_name` | `VARCHAR(256)` | yes | no | no | yes | `` |
|
||||
| `phone` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `email` | `VARCHAR(256)` | yes | no | no | yes | `` |
|
||||
| `messenger_handles_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `lead_temperature` | `VARCHAR(16)` | no | no | no | yes | `` |
|
||||
| `lead_score` | `FLOAT` | no | no | no | yes | `50.0` |
|
||||
| `customer_type` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `segment_type` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `initial_need_summary` | `TEXT` | yes | no | no | no | `` |
|
||||
| `preferred_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `assigned_agent_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `crm_customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_leads_phone_email`: `phone`, `email`
|
||||
- `idx_sales_leads_tenant_status_score`: `tenant_id`, `status`, `lead_score`
|
||||
- `ix_sales_leads_assigned_agent_type`: `assigned_agent_type`
|
||||
- `ix_sales_leads_company_name`: `company_name`
|
||||
- `ix_sales_leads_created_at`: `created_at`
|
||||
- `ix_sales_leads_crm_customer_id`: `crm_customer_id`
|
||||
- `ix_sales_leads_customer_type`: `customer_type`
|
||||
- `ix_sales_leads_email`: `email`
|
||||
- `ix_sales_leads_full_name`: `full_name`
|
||||
- `ix_sales_leads_lead_id` unique: `lead_id`
|
||||
- `ix_sales_leads_lead_score`: `lead_score`
|
||||
- `ix_sales_leads_lead_temperature`: `lead_temperature`
|
||||
- `ix_sales_leads_phone`: `phone`
|
||||
- `ix_sales_leads_preferred_channel`: `preferred_channel`
|
||||
- `ix_sales_leads_segment_type`: `segment_type`
|
||||
- `ix_sales_leads_source_campaign_id`: `source_campaign_id`
|
||||
- `ix_sales_leads_source_channel`: `source_channel`
|
||||
- `ix_sales_leads_source_type`: `source_type`
|
||||
- `ix_sales_leads_status`: `status`
|
||||
- `ix_sales_leads_tenant_id`: `tenant_id`
|
||||
- `ix_sales_leads_updated_at`: `updated_at`
|
||||
|
||||
## `sales_deals`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `lead_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `pipeline_id` | `VARCHAR(64)` | no | no | no | yes | `sales_default` |
|
||||
| `stage_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `scenario_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `priority` | `INTEGER` | no | no | no | yes | `3` |
|
||||
| `title` | `VARCHAR(512)` | no | no | no | no | `` |
|
||||
| `need_summary` | `TEXT` | yes | no | no | no | `` |
|
||||
| `product_context_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `estimated_amount` | `FLOAT` | yes | no | no | no | `` |
|
||||
| `final_amount` | `FLOAT` | yes | no | no | no | `` |
|
||||
| `currency` | `VARCHAR(16)` | no | no | no | no | `KZT` |
|
||||
| `payment_model` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `document_required` | `BOOLEAN` | no | no | no | no | `True` |
|
||||
| `payment_required` | `BOOLEAN` | no | no | no | no | `True` |
|
||||
| `assigned_human_user_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `assigned_ai_orchestrator_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `preferred_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `current_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `active` |
|
||||
| `won_reason` | `TEXT` | yes | no | no | no | `` |
|
||||
| `lost_reason` | `TEXT` | yes | no | no | no | `` |
|
||||
| `close_reason` | `TEXT` | yes | no | no | no | `` |
|
||||
| `next_action_type` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `next_action_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `last_contact_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `closed_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_deals_customer_channel`: `customer_id`, `current_channel`
|
||||
- `idx_sales_deals_next_action`: `next_action_at`, `status`
|
||||
- `idx_sales_deals_tenant_stage_status`: `tenant_id`, `stage_id`, `status`
|
||||
- `ix_sales_deals_assigned_human_user_id`: `assigned_human_user_id`
|
||||
- `ix_sales_deals_closed_at`: `closed_at`
|
||||
- `ix_sales_deals_created_at`: `created_at`
|
||||
- `ix_sales_deals_current_channel`: `current_channel`
|
||||
- `ix_sales_deals_customer_id`: `customer_id`
|
||||
- `ix_sales_deals_deal_id` unique: `deal_id`
|
||||
- `ix_sales_deals_last_contact_at`: `last_contact_at`
|
||||
- `ix_sales_deals_lead_id`: `lead_id`
|
||||
- `ix_sales_deals_next_action_at`: `next_action_at`
|
||||
- `ix_sales_deals_next_action_type`: `next_action_type`
|
||||
- `ix_sales_deals_pipeline_id`: `pipeline_id`
|
||||
- `ix_sales_deals_preferred_channel`: `preferred_channel`
|
||||
- `ix_sales_deals_priority`: `priority`
|
||||
- `ix_sales_deals_scenario_type`: `scenario_type`
|
||||
- `ix_sales_deals_stage_id`: `stage_id`
|
||||
- `ix_sales_deals_status`: `status`
|
||||
- `ix_sales_deals_tenant_id`: `tenant_id`
|
||||
- `ix_sales_deals_updated_at`: `updated_at`
|
||||
|
||||
## `sales_communication_sessions`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `communication_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `lead_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `channel_type` | `VARCHAR(16)` | no | no | no | yes | `` |
|
||||
| `direction` | `VARCHAR(16)` | no | no | no | yes | `` |
|
||||
| `agent_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `started_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `ended_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `duration_sec` | `INTEGER` | yes | no | no | no | `` |
|
||||
| `subject` | `VARCHAR(512)` | yes | no | no | no | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `active` |
|
||||
| `summary` | `TEXT` | yes | no | no | no | `` |
|
||||
| `transcript_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `next_action_type` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `next_action_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `sentiment` | `VARCHAR(32)` | yes | no | no | no | `` |
|
||||
| `result_code` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `metadata_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_comm_channel_status`: `channel_type`, `status`
|
||||
- `idx_sales_comm_deal_started`: `deal_id`, `started_at`
|
||||
- `ix_sales_communication_sessions_agent_type`: `agent_type`
|
||||
- `ix_sales_communication_sessions_channel_type`: `channel_type`
|
||||
- `ix_sales_communication_sessions_communication_id` unique: `communication_id`
|
||||
- `ix_sales_communication_sessions_created_at`: `created_at`
|
||||
- `ix_sales_communication_sessions_customer_id`: `customer_id`
|
||||
- `ix_sales_communication_sessions_deal_id`: `deal_id`
|
||||
- `ix_sales_communication_sessions_direction`: `direction`
|
||||
- `ix_sales_communication_sessions_ended_at`: `ended_at`
|
||||
- `ix_sales_communication_sessions_lead_id`: `lead_id`
|
||||
- `ix_sales_communication_sessions_next_action_at`: `next_action_at`
|
||||
- `ix_sales_communication_sessions_started_at`: `started_at`
|
||||
- `ix_sales_communication_sessions_status`: `status`
|
||||
- `ix_sales_communication_sessions_tenant_id`: `tenant_id`
|
||||
- `ix_sales_communication_sessions_transcript_id`: `transcript_id`
|
||||
- `ix_sales_communication_sessions_updated_at`: `updated_at`
|
||||
|
||||
## `sales_messages`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `message_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `communication_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `sender_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `sender_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `channel_provider` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `external_message_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `body` | `TEXT` | no | no | no | no | `` |
|
||||
| `attachments_json` | `TEXT` | no | no | no | no | `[]` |
|
||||
| `delivery_status` | `VARCHAR(32)` | yes | no | no | yes | `` |
|
||||
| `read_status` | `VARCHAR(32)` | yes | no | no | yes | `` |
|
||||
| `message_metadata_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `sent_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_messages_deal_sent`: `deal_id`, `sent_at`
|
||||
- `idx_sales_messages_ext_id`: `channel_provider`, `external_message_id`
|
||||
- `ix_sales_messages_channel_provider`: `channel_provider`
|
||||
- `ix_sales_messages_communication_id`: `communication_id`
|
||||
- `ix_sales_messages_created_at`: `created_at`
|
||||
- `ix_sales_messages_deal_id`: `deal_id`
|
||||
- `ix_sales_messages_delivery_status`: `delivery_status`
|
||||
- `ix_sales_messages_external_message_id`: `external_message_id`
|
||||
- `ix_sales_messages_message_id` unique: `message_id`
|
||||
- `ix_sales_messages_read_status`: `read_status`
|
||||
- `ix_sales_messages_sender_id`: `sender_id`
|
||||
- `ix_sales_messages_sender_type`: `sender_type`
|
||||
- `ix_sales_messages_sent_at`: `sent_at`
|
||||
- `ix_sales_messages_tenant_id`: `tenant_id`
|
||||
|
||||
## `sales_calls`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `call_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `communication_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `phone_number` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `direction` | `VARCHAR(16)` | no | no | no | yes | `` |
|
||||
| `provider` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `external_call_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `recording_url` | `TEXT` | yes | no | no | no | `` |
|
||||
| `transcript_status` | `VARCHAR(32)` | no | no | no | yes | `pending` |
|
||||
| `transcript_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `call_status` | `VARCHAR(32)` | no | no | no | yes | `started` |
|
||||
| `summary` | `TEXT` | yes | no | no | no | `` |
|
||||
| `started_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `ended_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `duration_sec` | `INTEGER` | yes | no | no | no | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_calls_deal_started`: `deal_id`, `started_at`
|
||||
- `idx_sales_calls_ext_provider`: `provider`, `external_call_id`
|
||||
- `ix_sales_calls_call_id` unique: `call_id`
|
||||
- `ix_sales_calls_call_status`: `call_status`
|
||||
- `ix_sales_calls_communication_id`: `communication_id`
|
||||
- `ix_sales_calls_created_at`: `created_at`
|
||||
- `ix_sales_calls_deal_id`: `deal_id`
|
||||
- `ix_sales_calls_direction`: `direction`
|
||||
- `ix_sales_calls_ended_at`: `ended_at`
|
||||
- `ix_sales_calls_external_call_id`: `external_call_id`
|
||||
- `ix_sales_calls_phone_number`: `phone_number`
|
||||
- `ix_sales_calls_provider`: `provider`
|
||||
- `ix_sales_calls_started_at`: `started_at`
|
||||
- `ix_sales_calls_tenant_id`: `tenant_id`
|
||||
- `ix_sales_calls_transcript_id`: `transcript_id`
|
||||
- `ix_sales_calls_transcript_status`: `transcript_status`
|
||||
- `ix_sales_calls_updated_at`: `updated_at`
|
||||
|
||||
## `sales_transcripts`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `transcript_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `call_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `language` | `VARCHAR(16)` | no | no | no | yes | `ru` |
|
||||
| `transcript_text` | `TEXT` | no | no | no | no | `` |
|
||||
| `diarization_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `extracted_entities_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `ix_sales_transcripts_call_id`: `call_id`
|
||||
- `ix_sales_transcripts_created_at`: `created_at`
|
||||
- `ix_sales_transcripts_language`: `language`
|
||||
- `ix_sales_transcripts_tenant_id`: `tenant_id`
|
||||
- `ix_sales_transcripts_transcript_id` unique: `transcript_id`
|
||||
- `ix_sales_transcripts_updated_at`: `updated_at`
|
||||
|
||||
## `sales_offers`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `offer_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `offer_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `title` | `VARCHAR(256)` | no | no | no | no | `` |
|
||||
| `description` | `TEXT` | yes | no | no | no | `` |
|
||||
| `line_items_json` | `TEXT` | no | no | no | no | `[]` |
|
||||
| `pricing_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `total_amount` | `FLOAT` | no | no | no | no | `0` |
|
||||
| `currency` | `VARCHAR(16)` | no | no | no | no | `KZT` |
|
||||
| `validity_until` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `draft` |
|
||||
| `rendered_document_url` | `TEXT` | yes | no | no | no | `` |
|
||||
| `created_by_type` | `VARCHAR(32)` | no | no | no | no | `text_ai` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_offers_deal_status`: `deal_id`, `status`
|
||||
- `ix_sales_offers_created_at`: `created_at`
|
||||
- `ix_sales_offers_deal_id`: `deal_id`
|
||||
- `ix_sales_offers_offer_id` unique: `offer_id`
|
||||
- `ix_sales_offers_offer_type`: `offer_type`
|
||||
- `ix_sales_offers_status`: `status`
|
||||
- `ix_sales_offers_tenant_id`: `tenant_id`
|
||||
- `ix_sales_offers_updated_at`: `updated_at`
|
||||
- `ix_sales_offers_validity_until`: `validity_until`
|
||||
|
||||
## `sales_deal_conditions`
|
||||
|
||||
Not present in SQLAlchemy metadata / migrations. Current implementation uses `sales_conditions` for deal conditions.
|
||||
|
||||
## `sales_conditions`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `condition_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `product_name` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `service_name` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `quantity` | `FLOAT` | yes | no | no | no | `` |
|
||||
| `unit` | `VARCHAR(32)` | yes | no | no | no | `` |
|
||||
| `delivery_mode` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `execution_date` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `start_date` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `end_date` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `payment_terms` | `TEXT` | yes | no | no | no | `` |
|
||||
| `custom_terms_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `agreed_price` | `FLOAT` | yes | no | no | no | `` |
|
||||
| `currency` | `VARCHAR(16)` | no | no | no | no | `KZT` |
|
||||
| `confirmed_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `ix_sales_conditions_condition_id` unique: `condition_id`
|
||||
- `ix_sales_conditions_confirmed_at`: `confirmed_at`
|
||||
- `ix_sales_conditions_created_at`: `created_at`
|
||||
- `ix_sales_conditions_deal_id`: `deal_id`
|
||||
- `ix_sales_conditions_tenant_id`: `tenant_id`
|
||||
- `ix_sales_conditions_updated_at`: `updated_at`
|
||||
|
||||
## `sales_counterparties`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `counterparty_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `company_name` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `full_name` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `bin_iin` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `address` | `TEXT` | yes | no | no | no | `` |
|
||||
| `bank_details_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `signer_name` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `signer_role` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `signer_basis` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `email_for_docs` | `VARCHAR(256)` | yes | no | no | no | `` |
|
||||
| `phone_for_docs` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `completeness_status` | `VARCHAR(32)` | no | no | no | yes | `draft` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `ix_sales_counterparties_bin_iin`: `bin_iin`
|
||||
- `ix_sales_counterparties_completeness_status`: `completeness_status`
|
||||
- `ix_sales_counterparties_counterparty_id` unique: `counterparty_id`
|
||||
- `ix_sales_counterparties_created_at`: `created_at`
|
||||
- `ix_sales_counterparties_customer_id`: `customer_id`
|
||||
- `ix_sales_counterparties_deal_id` unique: `deal_id`
|
||||
- `ix_sales_counterparties_tenant_id`: `tenant_id`
|
||||
- `ix_sales_counterparties_updated_at`: `updated_at`
|
||||
|
||||
## `sales_documents`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `document_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `document_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `template_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `version` | `INTEGER` | no | no | no | no | `1` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `draft` |
|
||||
| `file_url` | `TEXT` | yes | no | no | no | `` |
|
||||
| `rendered_payload_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `external_sign_provider_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `signed_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_documents_deal_status`: `deal_id`, `status`
|
||||
- `ix_sales_documents_created_at`: `created_at`
|
||||
- `ix_sales_documents_customer_id`: `customer_id`
|
||||
- `ix_sales_documents_deal_id`: `deal_id`
|
||||
- `ix_sales_documents_document_id` unique: `document_id`
|
||||
- `ix_sales_documents_document_type`: `document_type`
|
||||
- `ix_sales_documents_signed_at`: `signed_at`
|
||||
- `ix_sales_documents_status`: `status`
|
||||
- `ix_sales_documents_tenant_id`: `tenant_id`
|
||||
- `ix_sales_documents_updated_at`: `updated_at`
|
||||
|
||||
## `sales_invoices`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `invoice_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `invoice_number` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `basis_document_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `amount` | `FLOAT` | no | no | no | no | `0` |
|
||||
| `currency` | `VARCHAR(16)` | no | no | no | no | `KZT` |
|
||||
| `due_date` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `draft` |
|
||||
| `payment_link` | `TEXT` | yes | no | no | no | `` |
|
||||
| `line_items_json` | `TEXT` | no | no | no | no | `[]` |
|
||||
| `metadata_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `issued_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `paid_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_invoices_deal_status_due`: `deal_id`, `status`, `due_date`
|
||||
- `ix_sales_invoices_basis_document_id`: `basis_document_id`
|
||||
- `ix_sales_invoices_created_at`: `created_at`
|
||||
- `ix_sales_invoices_customer_id`: `customer_id`
|
||||
- `ix_sales_invoices_deal_id`: `deal_id`
|
||||
- `ix_sales_invoices_due_date`: `due_date`
|
||||
- `ix_sales_invoices_invoice_id` unique: `invoice_id`
|
||||
- `ix_sales_invoices_invoice_number` unique: `invoice_number`
|
||||
- `ix_sales_invoices_issued_at`: `issued_at`
|
||||
- `ix_sales_invoices_paid_at`: `paid_at`
|
||||
- `ix_sales_invoices_status`: `status`
|
||||
- `ix_sales_invoices_tenant_id`: `tenant_id`
|
||||
- `ix_sales_invoices_updated_at`: `updated_at`
|
||||
|
||||
## `sales_payments`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `payment_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `invoice_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `payment_provider` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `external_payment_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `amount` | `FLOAT` | no | no | no | no | `0` |
|
||||
| `currency` | `VARCHAR(16)` | no | no | no | no | `KZT` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `pending` |
|
||||
| `paid_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `payment_method` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `failure_reason` | `TEXT` | yes | no | no | no | `` |
|
||||
| `metadata_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_payments_deal_status`: `deal_id`, `status`
|
||||
- `ix_sales_payments_created_at`: `created_at`
|
||||
- `ix_sales_payments_deal_id`: `deal_id`
|
||||
- `ix_sales_payments_external_payment_id`: `external_payment_id`
|
||||
- `ix_sales_payments_invoice_id`: `invoice_id`
|
||||
- `ix_sales_payments_paid_at`: `paid_at`
|
||||
- `ix_sales_payments_payment_id` unique: `payment_id`
|
||||
- `ix_sales_payments_status`: `status`
|
||||
- `ix_sales_payments_tenant_id`: `tenant_id`
|
||||
- `ix_sales_payments_updated_at`: `updated_at`
|
||||
|
||||
## `sales_stage_history`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `history_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `from_stage_id` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `to_stage_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `changed_by_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `changed_by_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `reason` | `TEXT` | yes | no | no | no | `` |
|
||||
| `changed_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_stage_history_deal_changed`: `deal_id`, `changed_at`
|
||||
- `ix_sales_stage_history_changed_at`: `changed_at`
|
||||
- `ix_sales_stage_history_changed_by_id`: `changed_by_id`
|
||||
- `ix_sales_stage_history_changed_by_type`: `changed_by_type`
|
||||
- `ix_sales_stage_history_deal_id`: `deal_id`
|
||||
- `ix_sales_stage_history_history_id` unique: `history_id`
|
||||
- `ix_sales_stage_history_tenant_id`: `tenant_id`
|
||||
- `ix_sales_stage_history_to_stage_id`: `to_stage_id`
|
||||
|
||||
## `sales_escalations`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `escalation_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `escalation_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `reason` | `TEXT` | no | no | no | no | `` |
|
||||
| `severity` | `VARCHAR(16)` | no | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `open` |
|
||||
| `assigned_to_user_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `resolved_at` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_escalations_deal_status`: `deal_id`, `status`
|
||||
- `ix_sales_escalations_assigned_to_user_id`: `assigned_to_user_id`
|
||||
- `ix_sales_escalations_created_at`: `created_at`
|
||||
- `ix_sales_escalations_deal_id`: `deal_id`
|
||||
- `ix_sales_escalations_escalation_id` unique: `escalation_id`
|
||||
- `ix_sales_escalations_escalation_type`: `escalation_type`
|
||||
- `ix_sales_escalations_resolved_at`: `resolved_at`
|
||||
- `ix_sales_escalations_severity`: `severity`
|
||||
- `ix_sales_escalations_status`: `status`
|
||||
- `ix_sales_escalations_tenant_id`: `tenant_id`
|
||||
|
||||
## `sales_automation_tasks`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `task_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `task_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `payload_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `run_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `status` | `VARCHAR(32)` | no | no | no | yes | `pending` |
|
||||
| `retry_count` | `INTEGER` | no | no | no | no | `0` |
|
||||
| `last_error` | `TEXT` | yes | no | no | no | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_tasks_run_status`: `run_at`, `status`
|
||||
- `ix_sales_automation_tasks_created_at`: `created_at`
|
||||
- `ix_sales_automation_tasks_deal_id`: `deal_id`
|
||||
- `ix_sales_automation_tasks_run_at`: `run_at`
|
||||
- `ix_sales_automation_tasks_status`: `status`
|
||||
- `ix_sales_automation_tasks_task_id` unique: `task_id`
|
||||
- `ix_sales_automation_tasks_task_type`: `task_type`
|
||||
- `ix_sales_automation_tasks_tenant_id`: `tenant_id`
|
||||
- `ix_sales_automation_tasks_updated_at`: `updated_at`
|
||||
|
||||
## `sales_notes`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `note_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `author_type` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `author_id` | `VARCHAR(128)` | yes | no | no | yes | `` |
|
||||
| `note_type` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `content` | `TEXT` | no | no | no | no | `` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `ix_sales_notes_author_id`: `author_id`
|
||||
- `ix_sales_notes_author_type`: `author_type`
|
||||
- `ix_sales_notes_created_at`: `created_at`
|
||||
- `ix_sales_notes_deal_id`: `deal_id`
|
||||
- `ix_sales_notes_note_id` unique: `note_id`
|
||||
- `ix_sales_notes_note_type`: `note_type`
|
||||
- `ix_sales_notes_tenant_id`: `tenant_id`
|
||||
|
||||
## `sales_channel_switches`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `switch_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `communication_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `from_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `to_channel` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `reason_for_channel_switch` | `TEXT` | no | no | no | no | `` |
|
||||
| `switched_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_channel_switches_deal_switched`: `deal_id`, `switched_at`
|
||||
- `ix_sales_channel_switches_communication_id`: `communication_id`
|
||||
- `ix_sales_channel_switches_deal_id`: `deal_id`
|
||||
- `ix_sales_channel_switches_from_channel`: `from_channel`
|
||||
- `ix_sales_channel_switches_switch_id` unique: `switch_id`
|
||||
- `ix_sales_channel_switches_switched_at`: `switched_at`
|
||||
- `ix_sales_channel_switches_tenant_id`: `tenant_id`
|
||||
- `ix_sales_channel_switches_to_channel`: `to_channel`
|
||||
|
||||
## `sales_external_links`
|
||||
|
||||
Physical foreign keys: none. Relations are stored as string ids and enforced only by service code.
|
||||
|
||||
| Column | Type | Null | PK | Unique | Indexed | Default |
|
||||
|---|---|---:|---:|---:|---:|---|
|
||||
| `id` | `INTEGER` | no | yes | no | no | `` |
|
||||
| `link_id` | `VARCHAR(64)` | no | no | yes | yes | `` |
|
||||
| `tenant_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `deal_id` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `communication_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `sales_call_id` | `VARCHAR(64)` | yes | no | no | yes | `` |
|
||||
| `channel_provider` | `VARCHAR(32)` | no | no | no | yes | `` |
|
||||
| `external_thread_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `external_chat_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `external_call_id` | `VARCHAR(128)` | yes | no | no | no | `` |
|
||||
| `voice_session_id` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `ai_session_id` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `interaction_id` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `customer_id` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `phone_number` | `VARCHAR(64)` | yes | no | no | no | `` |
|
||||
| `external_status` | `VARCHAR(32)` | yes | no | no | yes | `` |
|
||||
| `link_metadata_json` | `TEXT` | no | no | no | no | `{}` |
|
||||
| `created_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `updated_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
| `last_sync_at` | `VARCHAR(64)` | no | no | no | yes | `` |
|
||||
|
||||
Indexes:
|
||||
- `idx_sales_external_links_customer_phone`: `customer_id`, `phone_number`
|
||||
- `idx_sales_external_links_deal_sync`: `deal_id`, `last_sync_at`
|
||||
- `idx_sales_external_links_external_call`: `external_call_id`
|
||||
- `idx_sales_external_links_interaction`: `interaction_id`
|
||||
- `idx_sales_external_links_thread`: `external_thread_id`
|
||||
- `idx_sales_external_links_voice_session`: `voice_session_id`
|
||||
- `ix_sales_external_links_channel_provider`: `channel_provider`
|
||||
- `ix_sales_external_links_communication_id`: `communication_id`
|
||||
- `ix_sales_external_links_created_at`: `created_at`
|
||||
- `ix_sales_external_links_deal_id`: `deal_id`
|
||||
- `ix_sales_external_links_external_status`: `external_status`
|
||||
- `ix_sales_external_links_last_sync_at`: `last_sync_at`
|
||||
- `ix_sales_external_links_link_id` unique: `link_id`
|
||||
- `ix_sales_external_links_sales_call_id`: `sales_call_id`
|
||||
- `ix_sales_external_links_tenant_id`: `tenant_id`
|
||||
- `ix_sales_external_links_updated_at`: `updated_at`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
Dialect: sqlite
|
||||
Applied migrations:
|
||||
- 0001_core_sqlite.sql
|
||||
- 0002_stage3_sqlite.sql
|
||||
- 0003_wave2_auth_sqlite.sql
|
||||
- 0004_webchat_sqlite.sql
|
||||
- 0005_email_sqlite.sql
|
||||
- 0006_recordings_sqlite.sql
|
||||
- 0007_ivr_sqlite.sql
|
||||
- 0008_reporting_expansion_sqlite.sql
|
||||
- 0009_event_bus_sqlite.sql
|
||||
- 0010_asterisk_bridge_sqlite.sql
|
||||
- 0011_track11_voice_control_sqlite.sql
|
||||
- 0012_telegram_chat_sqlite.sql
|
||||
- 0013_asterisk_bridge_idempotency_sqlite.sql
|
||||
- 0013_telegram_ai_sqlite.sql
|
||||
- 0014_voice_ai_sqlite.sql
|
||||
- 0015_voice_ai_media_sqlite.sql
|
||||
- 0016_ivr_queue_code_sqlite.sql
|
||||
- 0017_kb_localization_sqlite.sql
|
||||
- 0018_postgres_readiness_sqlite.sql
|
||||
- 0019_reporting_kpi_facts_sqlite.sql
|
||||
- 0020_reporting_saved_views_sqlite.sql
|
||||
- 0021_voice_start_identity_sqlite.sql
|
||||
- 0022_voice_name_collection_config_sqlite.sql
|
||||
- 0023_sales_sqlite.sql
|
||||
- 0024_sales_external_links_sqlite.sql
|
||||
|
||||
MIGRATE_EXIT_STATUS=0
|
||||
MISSING_MIGRATIONS=[]
|
||||
SALES_APP_ROUTES=59
|
||||
VALIDATE_EXIT_STATUS=0
|
||||
@@ -0,0 +1,56 @@
|
||||
# sales_service Migrations Inventory
|
||||
|
||||
## Migration Files
|
||||
|
||||
Sales-specific migrations:
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `migrations/sql/0023_sales_sqlite.sql` | Creates main sales tables for SQLite. |
|
||||
| `migrations/sql/0023_sales_postgres.sql` | Creates main sales tables for Postgres. |
|
||||
| `migrations/sql/0024_sales_external_links_sqlite.sql` | Adds `sales_external_links` for SQLite. |
|
||||
| `migrations/sql/0024_sales_external_links_postgres.sql` | Adds `sales_external_links` for Postgres. |
|
||||
|
||||
## Migration Runner
|
||||
|
||||
Runner: `scripts/migrate_core_db.py`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Detects dialect from `DATABASE_URL`.
|
||||
- Selects all `*_{dialect}.sql` files in `migrations/sql`.
|
||||
- Applies them in sorted filename order.
|
||||
- Tracks applied files in `schema_migrations`.
|
||||
|
||||
Runtime schema mode:
|
||||
|
||||
- SQLite defaults to `SCHEMA_MANAGEMENT_MODE=legacy`, which calls `Base.metadata.create_all`.
|
||||
- Non-SQLite requires `SCHEMA_MANAGEMENT_MODE=migrations`.
|
||||
- `SCHEMA_MANAGEMENT_MODE=migrations` validates that all checked-in migration files were applied.
|
||||
|
||||
## Smoke Check
|
||||
|
||||
Command used:
|
||||
|
||||
```bash
|
||||
DATABASE_URL="sqlite:///.../migration_check.db" \
|
||||
SCHEMA_MANAGEMENT_MODE=migrations \
|
||||
.venv/bin/python scripts/migrate_core_db.py
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
- Exit status: 0.
|
||||
- Applied migrations include `0023_sales_sqlite.sql` and `0024_sales_external_links_sqlite.sql`.
|
||||
- `missing_migration_versions()` returned `[]`.
|
||||
- `services.sales_service.app` imports successfully after migrations.
|
||||
|
||||
Full output: `sales_service_migration_check.txt`.
|
||||
|
||||
## Observations
|
||||
|
||||
- `migrations/README.md` is stale: it still lists only `0001` and `0002`, while the repo now has migrations through `0024`.
|
||||
- Sales migrations create no physical foreign keys between sales tables.
|
||||
- Several external ids are indexed but not unique: messages, calls, payments.
|
||||
- There is no migration for `Tenant`, configurable `Pipeline`, or configurable `Stage`.
|
||||
- ORM metadata has more `index=True` declarations than the explicit SQL migrations create. Use `sales_service_er_db_sqlite.md` for the migrated DB baseline and `sales_service_er_map.md` for ORM intent.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,202 @@
|
||||
# sales_service Risk Points
|
||||
|
||||
## Step 4 Event Outbox Status
|
||||
|
||||
Current state after Step 4:
|
||||
|
||||
- `sales_service` publishes domain events to the shared `event_outbox`.
|
||||
- Publisher: `services/sales_service/event_publisher.py` (`SalesEventPublisher`).
|
||||
- Registry: `services/sales_service/sales_events.py`.
|
||||
- Events are appended before `session.commit()`, in the same transaction as the business change.
|
||||
- `tenant_id` is included in event envelope payload for every sales event.
|
||||
|
||||
Published MVP events:
|
||||
|
||||
- `lead.entered_crm`
|
||||
- `lead.enrichment_completed`
|
||||
- `deal.stage_changed`
|
||||
- `deal.scenario_selected`
|
||||
- `deal.next_action_scheduled`
|
||||
- `deal.closed`
|
||||
- `deal.lost`
|
||||
- `deal.won`
|
||||
- `communication.started`
|
||||
- `communication.summary_created`
|
||||
- `message.received`
|
||||
- `message.sent`
|
||||
- `call.received`
|
||||
- `call.completed`
|
||||
- `offer.created`
|
||||
- `offer.sent`
|
||||
- `offer.accepted`
|
||||
- `offer.rejected`
|
||||
- `deal.conditions_confirmed`
|
||||
- `counterparty.completed`
|
||||
- `document.created`
|
||||
- `document.sent`
|
||||
- `document.confirmed`
|
||||
- `document.signed`
|
||||
- `invoice.created`
|
||||
- `invoice.sent`
|
||||
- `invoice.overdue`
|
||||
- `payment.received`
|
||||
- `invoice.paid`
|
||||
|
||||
Remaining risks:
|
||||
|
||||
- No sales-specific consumers/handlers yet; outbox rows are ready for the shared dispatcher, but sales automation/analytics/post-sale reactions are future steps.
|
||||
- The shared `event_outbox` schema has no dedicated `tenant_id`, `actor_type`, `actor_id`, or `causation_id` columns. These values are stored inside `payload_json.payload`.
|
||||
- Event payload schemas are registry constants plus tests, not yet formal JSON Schema files.
|
||||
|
||||
## DEFAULT_TENANT_ID
|
||||
|
||||
Current state after Step 2:
|
||||
|
||||
- Production sales API flow no longer uses `DEFAULT_TENANT_ID`.
|
||||
- `_tenant_id(actor)` requires tenant context from JWT/legacy dev header.
|
||||
- Webhooks resolve tenant from auth/header or `tenant_integrations` provider mapping.
|
||||
- Sales reads/writes are tenant-scoped.
|
||||
|
||||
Remaining risk:
|
||||
|
||||
- Tenant entity/settings are MVP-level, not full subscription/billing model.
|
||||
- Webhook provider signature verification is still separate from tenant resolution.
|
||||
|
||||
## stage_id Changes
|
||||
|
||||
Current state after Step 3:
|
||||
|
||||
- `sales_pipelines` and `sales_pipeline_stages` exist.
|
||||
- `Deal.pipeline_id` stores real `pip_*` public id.
|
||||
- `Deal.stage_id` stores real `pst_*` public id.
|
||||
- `STAGE_LABELS` is no longer the source of truth.
|
||||
- `_apply_stage(...)` resolves stable stage code to tenant/pipeline stage id and writes `SalesStageHistoryRow`.
|
||||
- Step 4 also publishes `deal.stage_changed` to `event_outbox`.
|
||||
|
||||
Initial stage writes:
|
||||
|
||||
- `_create_lead_and_deal_for_contact` resolves default stage code `new_qualified_lead` to real `pst_*` id and records history.
|
||||
- `_resolve_deal_for_inbound` creates inbound lead/deal, resolves real stage id and records history.
|
||||
- `create_lead` creates a lead and auto-deal, then records history with reason `lead.entered_crm`.
|
||||
- `create_deal` resolves stage code/id input to real `pst_*` id and records history with reason `deal.created`.
|
||||
|
||||
Endpoint-driven stage changes:
|
||||
|
||||
- `POST /api/v1/leads/{lead_id}/enrich`
|
||||
- `POST /api/v1/deals/{deal_id}/change-stage`
|
||||
- `POST /api/v1/deals/{deal_id}/schedule-next-action`
|
||||
- `POST /api/v1/deals/{deal_id}/close`
|
||||
- `POST /api/v1/deals/{deal_id}/escalate`
|
||||
- `POST /api/v1/deals/{deal_id}/communications/text`
|
||||
- `POST /api/v1/deals/{deal_id}/communications/voice`
|
||||
- `POST /api/v1/communications/{communication_id}/switch-channel`
|
||||
- `POST /api/v1/communications/{communication_id}/bind-external`
|
||||
- Offer actions: create/send/accept/reject.
|
||||
- Conditions upsert.
|
||||
- Counterparty create/patch.
|
||||
- Document create/send/confirm.
|
||||
- Invoice create/send/mark-overdue.
|
||||
- Payment webhook success/partial.
|
||||
|
||||
Risk:
|
||||
|
||||
- No transition validation/state machine yet.
|
||||
- Stage customization is API/MVP-level; no UI/admin migration workflow for moving active deals between stages.
|
||||
|
||||
## AutomationTask Creation
|
||||
|
||||
Central helper:
|
||||
|
||||
- `_schedule_task(session, deal, task_type, run_at, payload)` creates `SalesAutomationTaskRow`.
|
||||
|
||||
Creation points:
|
||||
|
||||
| Code area | task_type |
|
||||
|---|---|
|
||||
| `_start_communication` | `payload.next_action_type` |
|
||||
| `POST /api/v1/deals/{deal_id}/schedule-next-action` | `payload.next_action_type` |
|
||||
| `POST /api/v1/communications/{communication_id}/summary` | `payload.next_action_type` |
|
||||
| `POST /api/v1/communications/{communication_id}/switch-channel` | `switch_to_{to_channel}` |
|
||||
| `POST /api/v1/invoices/{invoice_id}/send` | `invoice_follow_up` |
|
||||
|
||||
Task fields:
|
||||
|
||||
- `status` defaults to `pending`.
|
||||
- `retry_count` defaults to `0`.
|
||||
- `last_error` exists.
|
||||
- Index exists on `(run_at, status)`.
|
||||
|
||||
Risk:
|
||||
|
||||
- No worker/dispatcher found for pending tasks.
|
||||
- No API found to list/update/execute/cancel/retry automation tasks.
|
||||
- No overdue task handling found.
|
||||
- Status values allow `running`, `completed`, `failed`, `canceled`, but code only creates `pending`.
|
||||
|
||||
## Payment Webhook
|
||||
|
||||
Endpoint:
|
||||
|
||||
- `POST /api/v1/payments/webhook`
|
||||
|
||||
Input:
|
||||
|
||||
- Required `deal_id`.
|
||||
- Optional `invoice_id`.
|
||||
- Optional `external_payment_id`.
|
||||
- `payment_provider`, `amount`, `currency`, `status`, `paid_at`, `payment_method`, `failure_reason`, `metadata`.
|
||||
|
||||
Current flow:
|
||||
|
||||
1. Load deal by `payload.deal_id`.
|
||||
2. If `invoice_id` is present, load invoice by id.
|
||||
3. Create or update `SalesPaymentRow` idempotently by `(tenant_id, payment_provider, external_payment_id)` when external id is provided.
|
||||
4. If invoice exists and status is `success`:
|
||||
- invoice -> `paid`
|
||||
- invoice.paid_at set
|
||||
- deal.status -> `won`
|
||||
- deal.closed_at set
|
||||
- deal.final_amount set to payment amount
|
||||
- stage -> `won`
|
||||
- ensure CRM customer
|
||||
5. If invoice exists and status is `partial`:
|
||||
- invoice -> `partially_paid`
|
||||
- stage -> `partially_paid`
|
||||
6. If invoice exists and status is `failed`:
|
||||
- only invoice.updated_at changes.
|
||||
|
||||
Risk:
|
||||
|
||||
- No provider signature verification.
|
||||
- Payment idempotency depends on provider sending `external_payment_id`.
|
||||
- Payment totals are not reconciled against invoice amount before marking paid.
|
||||
- Endpoint still lacks provider-specific webhook signature authentication.
|
||||
|
||||
## Notes
|
||||
|
||||
Current state:
|
||||
|
||||
- `SalesNoteRow` and `sales_notes` exist.
|
||||
- No `SalesNoteIn/Out` schemas found.
|
||||
- No notes API routes found.
|
||||
- `SalesWorkspaceOut` does not include notes.
|
||||
|
||||
Risk:
|
||||
|
||||
- Notes are table-only, not product-ready.
|
||||
- System/user notes cannot be created or retrieved through current sales API.
|
||||
|
||||
## Escalation
|
||||
|
||||
Current state:
|
||||
|
||||
- `POST /api/v1/deals/{deal_id}/escalate` creates `SalesEscalationRow`.
|
||||
- It sets `deal.assigned_human_user_id`.
|
||||
- It sets `deal.scenario_type = "custom_human_escalation"`.
|
||||
- It moves stage to `transferred_to_support`.
|
||||
|
||||
Risk:
|
||||
|
||||
- No resolve/reassign API found.
|
||||
- No integration with queue/routing SLA in sales-service.
|
||||
- No status transition beyond initial `open`.
|
||||
@@ -0,0 +1,337 @@
|
||||
[
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/calls/inbound-webhook",
|
||||
"name": "inbound_call"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/calls/outbound",
|
||||
"name": "outbound_call"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/calls/{call_id}/complete",
|
||||
"name": "complete_call"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/calls/{call_id}/transcript",
|
||||
"name": "attach_transcript"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/communications/{communication_id}/bind-external",
|
||||
"name": "bind_external_communication"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/communications/{communication_id}/summary",
|
||||
"name": "summarize_communication"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/communications/{communication_id}/switch-channel",
|
||||
"name": "switch_channel"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/dashboard",
|
||||
"name": "sales_dashboard"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals",
|
||||
"name": "list_deals"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals",
|
||||
"name": "create_deal"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}",
|
||||
"name": "get_deal"
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/v1/deals/{deal_id}",
|
||||
"name": "update_deal"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/calls",
|
||||
"name": "list_calls"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/change-stage",
|
||||
"name": "change_stage"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/close",
|
||||
"name": "close_deal"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/communications",
|
||||
"name": "list_communications"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/communications/text",
|
||||
"name": "start_text_communication"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/communications/voice",
|
||||
"name": "start_voice_communication"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/conditions",
|
||||
"name": "upsert_conditions"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/counterparty",
|
||||
"name": "get_counterparty"
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/v1/deals/{deal_id}/counterparty",
|
||||
"name": "patch_counterparty"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/counterparty",
|
||||
"name": "create_counterparty"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/documents",
|
||||
"name": "create_document"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/escalate",
|
||||
"name": "escalate_deal"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/invoices",
|
||||
"name": "create_invoice"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/messages",
|
||||
"name": "list_messages"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/offers",
|
||||
"name": "create_offer"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/payments",
|
||||
"name": "list_payments"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/schedule-next-action",
|
||||
"name": "schedule_next_action"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/deals/{deal_id}/select-scenario",
|
||||
"name": "select_scenario"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/deals/{deal_id}/workspace",
|
||||
"name": "get_workspace"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/documents/{document_id}",
|
||||
"name": "get_document"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/documents/{document_id}/confirm",
|
||||
"name": "confirm_document"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/documents/{document_id}/send",
|
||||
"name": "send_document"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/documents/{document_id}/sign-status-webhook",
|
||||
"name": "sign_document"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/invoices/{invoice_id}",
|
||||
"name": "get_invoice"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/invoices/{invoice_id}/mark-overdue",
|
||||
"name": "mark_invoice_overdue"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/invoices/{invoice_id}/send",
|
||||
"name": "send_invoice"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/leads",
|
||||
"name": "list_leads"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/leads",
|
||||
"name": "create_lead"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/leads/{lead_id}",
|
||||
"name": "get_lead"
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/v1/leads/{lead_id}",
|
||||
"name": "update_lead"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/leads/{lead_id}/convert-to-deal",
|
||||
"name": "convert_lead_to_deal"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/leads/{lead_id}/enrich",
|
||||
"name": "enrich_lead"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/messages/inbound-webhook",
|
||||
"name": "inbound_message"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/messages/outbound",
|
||||
"name": "outbound_message"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/offers/{offer_id}",
|
||||
"name": "get_offer"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/offers/{offer_id}/accept",
|
||||
"name": "accept_offer"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/offers/{offer_id}/reject",
|
||||
"name": "reject_offer"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/offers/{offer_id}/send",
|
||||
"name": "send_offer"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/payments/webhook",
|
||||
"name": "payment_webhook"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/payments/{payment_id}/reconcile",
|
||||
"name": "reconcile_payment"
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/v1/pipeline-stages/{stage_id}",
|
||||
"name": "update_pipeline_stage"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/pipelines",
|
||||
"name": "list_pipelines"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/pipelines",
|
||||
"name": "create_pipeline"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/pipelines/{pipeline_id}",
|
||||
"name": "get_pipeline"
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/api/v1/pipelines/{pipeline_id}",
|
||||
"name": "update_pipeline"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/pipelines/{pipeline_id}/set-default",
|
||||
"name": "set_default_pipeline"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/v1/pipelines/{pipeline_id}/stages",
|
||||
"name": "list_pipeline_stages"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/pipelines/{pipeline_id}/stages",
|
||||
"name": "create_pipeline_stage"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/docs",
|
||||
"name": "swagger_ui_html"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/docs/oauth2-redirect",
|
||||
"name": "swagger_ui_redirect"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/health",
|
||||
"name": "health"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/internal/sales-sync/telegram",
|
||||
"name": "sync_telegram_thread"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/internal/sales-sync/voice",
|
||||
"name": "sync_voice_session"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/openapi.json",
|
||||
"name": "openapi"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/redoc",
|
||||
"name": "redoc_html"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,136 @@
|
||||
...FF. [100%]
|
||||
=================================== FAILURES ===================================
|
||||
___________ test_sales_internal_telegram_sync_auto_creates_workspace ___________
|
||||
|
||||
def test_sales_internal_telegram_sync_auto_creates_workspace():
|
||||
client = TestClient(sales_module.app)
|
||||
|
||||
response = client.post(
|
||||
"/internal/sales-sync/telegram",
|
||||
json={
|
||||
"thread_id": "tg-thread-auto-01",
|
||||
"chat_id": "tg-chat-auto-01",
|
||||
"interaction_id": "int_tg_auto_01",
|
||||
"phone_number": "+77005550101",
|
||||
"display_name": "Telegram Prospect",
|
||||
"queue_id": "q_sales",
|
||||
"status": "new",
|
||||
"ai_state": "queued",
|
||||
"message_id": "msg_tg_auto_01",
|
||||
"external_message_id": "ext_tg_auto_01",
|
||||
"text": "Здравствуйте, нужен расчет и коммерческое предложение.",
|
||||
"direction": "inbound",
|
||||
"author_type": "customer",
|
||||
"author_id": "tg-user-01",
|
||||
"metadata": {"username": "sales_prospect"},
|
||||
},
|
||||
headers=_headers(),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
workspace = response.json()
|
||||
assert workspace["deal"]["current_channel"] == "telegram"
|
||||
assert workspace["deal"]["preferred_channel"] == "telegram"
|
||||
> assert workspace["communications"][0]["channel_provider"] == "telegram"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
E KeyError: 'channel_provider'
|
||||
|
||||
tests/test_sales_service.py:220: KeyError
|
||||
__________ test_sales_internal_voice_sync_creates_call_and_transcript __________
|
||||
|
||||
def test_sales_internal_voice_sync_creates_call_and_transcript():
|
||||
client = TestClient(sales_module.app)
|
||||
|
||||
response = client.post(
|
||||
"/internal/sales-sync/voice",
|
||||
json={
|
||||
"call_id": "call-auto-voice-01",
|
||||
"interaction_id": "int_voice_auto_01",
|
||||
"queue_id": "q_voice_sales",
|
||||
"queue_code": "voice_sales",
|
||||
"caller_number": "+77005550202",
|
||||
"caller_name": "Voice Prospect",
|
||||
"voice_session_id": "avs_auto_01",
|
||||
"ai_session_id": "ais_auto_01",
|
||||
"ai_state": "completed",
|
||||
"telephony_status": "ended",
|
||||
"call_status": "completed",
|
||||
"started_at": "2026-05-08T10:00:00Z",
|
||||
"ended_at": "2026-05-08T10:03:00Z",
|
||||
"summary": "Клиент запросил прайс и условия подключения.",
|
||||
"transcript_text": "customer: Добрый день, нужен прайс.\nassistant: Подготовлю информацию.",
|
||||
"metadata": {"agent_profile": "voice_sales"},
|
||||
},
|
||||
headers=_headers(),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
workspace = response.json()
|
||||
assert workspace["deal"]["current_channel"] == "voice"
|
||||
assert workspace["communications"][0]["channel_type"] == "voice"
|
||||
assert workspace["communications"][0]["metadata"]["voice_session_id"] == "avs_auto_01"
|
||||
assert workspace["calls"][0]["external_call_id"] == "call-auto-voice-01"
|
||||
> assert workspace["transcripts"][0]["transcript_text"].startswith("customer:")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
E KeyError: 'transcripts'
|
||||
|
||||
tests/test_sales_service.py:257: KeyError
|
||||
=============================== warnings summary ===============================
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/services/telegram_adapter_service/app.py:1493: DeprecationWarning:
|
||||
on_event is deprecated, use lifespan event handlers instead.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
|
||||
|
||||
@app.on_event("startup")
|
||||
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/.venv/lib/python3.11/site-packages/fastapi/applications.py:4495: DeprecationWarning:
|
||||
on_event is deprecated, use lifespan event handlers instead.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
|
||||
|
||||
return self.router.on_event(event_type)
|
||||
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/services/telegram_adapter_service/app.py:1498: DeprecationWarning:
|
||||
on_event is deprecated, use lifespan event handlers instead.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
|
||||
|
||||
@app.on_event("shutdown")
|
||||
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/services/asterisk_bridge_service/app.py:507: DeprecationWarning:
|
||||
on_event is deprecated, use lifespan event handlers instead.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
|
||||
|
||||
@app.on_event("startup")
|
||||
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/services/asterisk_bridge_service/app.py:512: DeprecationWarning:
|
||||
on_event is deprecated, use lifespan event handlers instead.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).
|
||||
|
||||
@app.on_event("shutdown")
|
||||
|
||||
tests/test_sales_service.py::test_sales_lead_creation_auto_creates_deal_and_workspace
|
||||
/Users/magzhanzhumabayev/Desktop/projects/kazakhtelecom/telecom-crm/call-center/services/shared/audioop_compat.py:8: DeprecationWarning: 'audioop' is deprecated and slated for removal in Python 3.13
|
||||
import audioop as _audioop
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
=========================== short test summary info ============================
|
||||
FAILED tests/test_sales_service.py::test_sales_internal_telegram_sync_auto_creates_workspace
|
||||
FAILED tests/test_sales_service.py::test_sales_internal_voice_sync_creates_call_and_transcript
|
||||
|
||||
EXIT_STATUS=1
|
||||
Reference in New Issue
Block a user