Files

11 KiB
Raw Permalink Blame History

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.

Команда:

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.