sales fix

This commit is contained in:
arys
2026-05-09 09:16:06 +05:00
parent d617b6908c
commit f53af1f7dc
35 changed files with 7495 additions and 6 deletions
+102
View File
@@ -0,0 +1,102 @@
from __future__ import annotations
import math
import struct
from array import array
try:
import audioop as _audioop
except ModuleNotFoundError:
try:
import audioop_lts as _audioop # type: ignore[import-not-found]
except ModuleNotFoundError:
_audioop = None
def _native_is_little_endian() -> bool:
return struct.pack("=h", 1) == struct.pack("<h", 1)
def _read_int16_samples(fragment: bytes, width: int) -> array:
if width != 2:
raise NotImplementedError("audioop_compat fallback currently supports only 16-bit PCM")
samples = array("h")
samples.frombytes(fragment)
if not _native_is_little_endian():
samples.byteswap()
return samples
def _write_int16_samples(samples: array) -> bytes:
output = array("h", samples)
if not _native_is_little_endian():
output.byteswap()
return output.tobytes()
def _clip_int16(value: float) -> int:
return max(-32768, min(32767, int(round(value))))
class _AudioopFallback:
@staticmethod
def rms(fragment: bytes, width: int) -> int:
samples = _read_int16_samples(fragment, width)
if not samples:
return 0
mean_square = sum(sample * sample for sample in samples) / len(samples)
return int(math.sqrt(mean_square))
@staticmethod
def tomono(fragment: bytes, width: int, lfactor: float, rfactor: float) -> bytes:
samples = _read_int16_samples(fragment, width)
if len(samples) % 2 != 0:
raise ValueError("Stereo PCM must contain an even number of samples")
mono = array("h")
for index in range(0, len(samples), 2):
left = samples[index]
right = samples[index + 1]
mono.append(_clip_int16((left * lfactor) + (right * rfactor)))
return _write_int16_samples(mono)
@staticmethod
def ratecv(
fragment: bytes,
width: int,
nchannels: int,
inrate: int,
outrate: int,
state,
weightA: int = 1,
weightB: int = 0,
) -> tuple[bytes, None]:
if nchannels <= 0:
raise ValueError("nchannels must be positive")
if inrate <= 0 or outrate <= 0:
raise ValueError("Sample rates must be positive")
samples = _read_int16_samples(fragment, width)
if not samples or inrate == outrate:
return fragment, None
if len(samples) % nchannels != 0:
raise ValueError("PCM fragment size does not match channel count")
frame_count = len(samples) // nchannels
output_frame_count = max(1, int(round(frame_count * outrate / inrate)))
output = array("h")
for out_index in range(output_frame_count):
position = out_index * inrate / outrate
left_index = min(int(position), frame_count - 1)
right_index = min(left_index + 1, frame_count - 1)
fraction = max(0.0, min(1.0, position - left_index))
for channel_index in range(nchannels):
left_sample = samples[(left_index * nchannels) + channel_index]
right_sample = samples[(right_index * nchannels) + channel_index]
interpolated = left_sample + ((right_sample - left_sample) * fraction)
output.append(_clip_int16(interpolated))
return _write_int16_samples(output), None
audioop = _audioop or _AudioopFallback()
__all__ = ["audioop"]
+685
View File
@@ -0,0 +1,685 @@
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
SalesLeadTemperature = Literal["warm", "hot"]
SalesChannelType = Literal["text", "voice"]
SalesPreferredChannel = Literal["telegram", "whatsapp", "webchat", "email", "voice"]
SalesDirection = Literal["inbound", "outbound"]
SalesAgentType = Literal["text_ai", "voice_ai", "human"]
SalesLeadStatus = Literal["new_qualified_lead", "warm_lead", "hot_lead", "enrichment_required", "converted", "lost"]
SalesDealStatus = Literal["active", "won", "lost", "postponed", "closed"]
SalesScenarioType = Literal[
"quick_sale",
"consultative_sale",
"quotation_based_sale",
"booking_based_sale",
"subscription_sale",
"custom_human_escalation",
]
SalesOfferType = Literal["offer", "quotation", "estimate", "order", "booking", "appointment", "subscription_plan"]
SalesOfferStatus = Literal["draft", "sent", "viewed", "accepted", "rejected"]
SalesTranscriptStatus = Literal["pending", "ready"]
SalesDocumentType = Literal[
"contract",
"offer_acceptance",
"appendix",
"specification",
"invoice_basis_doc",
"order_confirmation",
"booking_confirmation",
"appointment_confirmation",
"additional_agreement",
]
SalesDocumentStatus = Literal["draft", "sent", "under_review", "confirmed", "signed"]
SalesInvoiceStatus = Literal["draft", "issued", "sent", "partially_paid", "paid", "overdue", "canceled"]
SalesPaymentStatus = Literal["pending", "success", "failed", "canceled", "partial"]
SalesEscalationSeverity = Literal["low", "medium", "high", "critical"]
SalesEscalationStatus = Literal["open", "in_progress", "resolved"]
SalesAutomationStatus = Literal["pending", "running", "completed", "failed", "canceled"]
class SalesLeadCreate(BaseModel):
source_type: str = Field(default="crm")
source_channel: str = Field(default="webchat")
source_campaign_id: str | None = None
full_name: str = Field(min_length=2)
company_name: str | None = None
phone: str | None = None
email: str | None = None
messenger_handles: dict = Field(default_factory=dict)
lead_temperature: SalesLeadTemperature = "warm"
lead_score: float = Field(default=50.0, ge=0, le=100)
customer_type: str | None = None
segment_type: str | None = None
initial_need_summary: str | None = None
preferred_channel: SalesPreferredChannel = "telegram"
assigned_agent_type: SalesAgentType = "text_ai"
status: SalesLeadStatus = "new_qualified_lead"
priority: int = Field(default=3, ge=1, le=5)
title: str | None = None
estimated_amount: float | None = Field(default=None, ge=0)
currency: str = "KZT"
class SalesLeadUpdate(BaseModel):
full_name: str | None = Field(default=None, min_length=2)
company_name: str | None = None
phone: str | None = None
email: str | None = None
messenger_handles: dict | None = None
lead_temperature: SalesLeadTemperature | None = None
lead_score: float | None = Field(default=None, ge=0, le=100)
customer_type: str | None = None
segment_type: str | None = None
initial_need_summary: str | None = None
preferred_channel: SalesPreferredChannel | None = None
assigned_agent_type: SalesAgentType | None = None
status: SalesLeadStatus | None = None
class SalesLeadEnrichIn(BaseModel):
need_summary: str | None = None
product_context: dict = Field(default_factory=dict)
customer_type: str | None = None
preferred_channel: SalesPreferredChannel | None = None
scenario_type: SalesScenarioType | None = None
required_fields: dict = Field(default_factory=dict)
class SalesLeadOut(BaseModel):
lead_id: str
tenant_id: str
source_type: str
source_channel: str
source_campaign_id: str | None = None
full_name: str
company_name: str | None = None
phone: str | None = None
email: str | None = None
messenger_handles: dict = Field(default_factory=dict)
lead_temperature: SalesLeadTemperature
lead_score: float
customer_type: str | None = None
segment_type: str | None = None
initial_need_summary: str | None = None
preferred_channel: SalesPreferredChannel
assigned_agent_type: SalesAgentType
status: SalesLeadStatus
crm_customer_id: str | None = None
created_at: str
updated_at: str
class SalesDealCreate(BaseModel):
lead_id: str | None = None
customer_id: str | None = None
stage_id: str = "new_qualified_lead"
scenario_type: SalesScenarioType = "quick_sale"
priority: int = Field(default=3, ge=1, le=5)
title: str = Field(min_length=3)
need_summary: str | None = None
product_context: dict = Field(default_factory=dict)
estimated_amount: float | None = Field(default=None, ge=0)
final_amount: float | None = Field(default=None, ge=0)
currency: str = "KZT"
payment_model: str | None = None
document_required: bool = True
payment_required: bool = True
preferred_channel: SalesPreferredChannel = "telegram"
current_channel: SalesPreferredChannel = "telegram"
class SalesDealUpdate(BaseModel):
title: str | None = Field(default=None, min_length=3)
need_summary: str | None = None
product_context: dict | None = None
estimated_amount: float | None = Field(default=None, ge=0)
final_amount: float | None = Field(default=None, ge=0)
currency: str | None = None
payment_model: str | None = None
document_required: bool | None = None
payment_required: bool | None = None
preferred_channel: SalesPreferredChannel | None = None
current_channel: SalesPreferredChannel | None = None
assigned_human_user_id: str | None = None
next_action_type: str | None = None
next_action_at: str | None = None
class SalesDealStageChangeIn(BaseModel):
stage_id: str = Field(min_length=2)
reason: str | None = None
class SalesDealScenarioIn(BaseModel):
scenario_type: SalesScenarioType
class SalesDealNextActionIn(BaseModel):
next_action_type: str = Field(min_length=2)
next_action_at: str | None = None
payload: dict = Field(default_factory=dict)
class SalesDealCloseIn(BaseModel):
status: Literal["won", "lost", "postponed"]
reason: str | None = None
class SalesDealOut(BaseModel):
deal_id: str
tenant_id: str
lead_id: str | None = None
customer_id: str | None = None
pipeline_id: str = "sales_default"
stage_id: str
scenario_type: SalesScenarioType
priority: int
title: str
need_summary: str | None = None
product_context: dict = Field(default_factory=dict)
estimated_amount: float | None = None
final_amount: float | None = None
currency: str
payment_model: str | None = None
document_required: bool = True
payment_required: bool = True
assigned_human_user_id: str | None = None
assigned_ai_orchestrator_id: str | None = None
preferred_channel: str
current_channel: str
status: SalesDealStatus
won_reason: str | None = None
lost_reason: str | None = None
close_reason: str | None = None
next_action_type: str | None = None
next_action_at: str | None = None
last_contact_at: str | None = None
closed_at: str | None = None
created_at: str
updated_at: str
class SalesCommunicationStartIn(BaseModel):
channel_type: SalesChannelType
direction: SalesDirection = "outbound"
agent_type: SalesAgentType | None = None
subject: str | None = None
summary: str | None = None
next_action_type: str | None = None
next_action_at: str | None = None
class SalesCommunicationSummaryIn(BaseModel):
summary: str = Field(min_length=2)
result_code: str | None = None
sentiment: str | None = None
next_action_type: str | None = None
next_action_at: str | None = None
class SalesCommunicationSwitchChannelIn(BaseModel):
to_channel: SalesPreferredChannel
reason_for_channel_switch: str = Field(min_length=3)
class SalesCommunicationBindExternalIn(BaseModel):
channel_provider: str | None = None
telegram_thread_id: str | None = None
telegram_chat_id: str | None = None
voice_session_id: str | None = None
external_call_id: str | None = None
metadata: dict = Field(default_factory=dict)
class SalesCommunicationOut(BaseModel):
communication_id: str
tenant_id: str
deal_id: str
lead_id: str | None = None
customer_id: str | None = None
channel_type: SalesChannelType
direction: SalesDirection
agent_type: SalesAgentType
started_at: str
ended_at: str | None = None
duration_sec: int | None = None
subject: str | None = None
status: str
summary: str | None = None
transcript_id: str | None = None
next_action_type: str | None = None
next_action_at: str | None = None
sentiment: str | None = None
result_code: str | None = None
metadata: dict = Field(default_factory=dict)
created_at: str
updated_at: str
class SalesMessageWebhookIn(BaseModel):
deal_id: str | None = None
lead_id: str | None = None
phone: str | None = None
channel_provider: str = "telegram"
external_message_id: str | None = None
sender_id: str | None = None
body: str = Field(min_length=1)
attachments: list[dict] = Field(default_factory=list)
metadata: dict = Field(default_factory=dict)
class SalesMessageSendIn(BaseModel):
deal_id: str
communication_id: str | None = None
channel_provider: str = "telegram"
sender_type: Literal["text_ai", "human"] = "text_ai"
sender_id: str | None = None
body: str = Field(min_length=1)
attachments: list[dict] = Field(default_factory=list)
metadata: dict = Field(default_factory=dict)
class SalesMessageOut(BaseModel):
message_id: str
deal_id: str
communication_id: str
sender_type: str
sender_id: str | None = None
channel_provider: str
external_message_id: str | None = None
body: str
attachments: list[dict] = Field(default_factory=list)
delivery_status: str | None = None
read_status: str | None = None
metadata: dict = Field(default_factory=dict)
sent_at: str
created_at: str
class SalesCallWebhookIn(BaseModel):
deal_id: str | None = None
lead_id: str | None = None
phone_number: str
provider: str = "asterisk"
external_call_id: str | None = None
subject: str | None = None
class SalesCallCompleteIn(BaseModel):
summary: str | None = None
recording_url: str | None = None
result_code: str | None = None
class SalesTranscriptIn(BaseModel):
language: str = "ru"
transcript_text: str = Field(min_length=1)
diarization: dict = Field(default_factory=dict)
extracted_entities: dict = Field(default_factory=dict)
class SalesTranscriptOut(BaseModel):
transcript_id: str
call_id: str
language: str
transcript_text: str
diarization: dict = Field(default_factory=dict)
extracted_entities: dict = Field(default_factory=dict)
created_at: str
updated_at: str
class SalesCallOut(BaseModel):
call_id: str
deal_id: str
communication_id: str
phone_number: str
direction: SalesDirection
provider: str
external_call_id: str | None = None
recording_url: str | None = None
transcript_status: SalesTranscriptStatus
transcript_id: str | None = None
call_status: str
started_at: str
ended_at: str | None = None
duration_sec: int | None = None
summary: str | None = None
created_at: str
updated_at: str
class SalesOfferCreate(BaseModel):
offer_type: SalesOfferType = "offer"
title: str = Field(min_length=3)
description: str | None = None
line_items: list[dict] = Field(default_factory=list)
pricing: dict = Field(default_factory=dict)
total_amount: float = Field(ge=0)
currency: str = "KZT"
validity_until: str | None = None
rendered_document_url: str | None = None
class SalesOfferOut(BaseModel):
offer_id: str
deal_id: str
offer_type: SalesOfferType
title: str
description: str | None = None
line_items: list[dict] = Field(default_factory=list)
pricing: dict = Field(default_factory=dict)
total_amount: float
currency: str
validity_until: str | None = None
status: SalesOfferStatus
rendered_document_url: str | None = None
created_by_type: str
created_at: str
updated_at: str
class SalesConditionUpsertIn(BaseModel):
product_name: str | None = None
service_name: str | None = None
quantity: float | None = Field(default=None, ge=0)
unit: str | None = None
delivery_mode: str | None = None
execution_date: str | None = None
start_date: str | None = None
end_date: str | None = None
payment_terms: str | None = None
custom_terms: dict = Field(default_factory=dict)
agreed_price: float | None = Field(default=None, ge=0)
currency: str = "KZT"
class SalesConditionOut(BaseModel):
condition_id: str
deal_id: str
product_name: str | None = None
service_name: str | None = None
quantity: float | None = None
unit: str | None = None
delivery_mode: str | None = None
execution_date: str | None = None
start_date: str | None = None
end_date: str | None = None
payment_terms: str | None = None
custom_terms: dict = Field(default_factory=dict)
agreed_price: float | None = None
currency: str
confirmed_at: str | None = None
created_at: str
updated_at: str
class SalesCounterpartyUpsertIn(BaseModel):
company_name: str | None = None
full_name: str | None = None
bin_iin: str | None = None
address: str | None = None
bank_details: dict = Field(default_factory=dict)
signer_name: str | None = None
signer_role: str | None = None
signer_basis: str | None = None
email_for_docs: str | None = None
phone_for_docs: str | None = None
class SalesCounterpartyOut(BaseModel):
counterparty_id: str
deal_id: str
customer_id: str | None = None
company_name: str | None = None
full_name: str | None = None
bin_iin: str | None = None
address: str | None = None
bank_details: dict = Field(default_factory=dict)
signer_name: str | None = None
signer_role: str | None = None
signer_basis: str | None = None
email_for_docs: str | None = None
phone_for_docs: str | None = None
completeness_status: str
created_at: str
updated_at: str
class SalesDocumentCreate(BaseModel):
document_type: SalesDocumentType
template_id: str | None = None
version: int = Field(default=1, ge=1)
rendered_payload: dict = Field(default_factory=dict)
file_url: str | None = None
class SalesDocumentOut(BaseModel):
document_id: str
deal_id: str
customer_id: str | None = None
document_type: SalesDocumentType
template_id: str | None = None
version: int
status: SalesDocumentStatus
file_url: str | None = None
rendered_payload: dict = Field(default_factory=dict)
external_sign_provider_id: str | None = None
signed_at: str | None = None
created_at: str
updated_at: str
class SalesInvoiceCreate(BaseModel):
basis_document_id: str | None = None
amount: float = Field(ge=0)
currency: str = "KZT"
due_date: str | None = None
payment_link: str | None = None
line_items: list[dict] = Field(default_factory=list)
metadata: dict = Field(default_factory=dict)
class SalesInvoiceOut(BaseModel):
invoice_id: str
deal_id: str
customer_id: str | None = None
invoice_number: str
basis_document_id: str | None = None
amount: float
currency: str
due_date: str | None = None
status: SalesInvoiceStatus
payment_link: str | None = None
line_items: list[dict] = Field(default_factory=list)
metadata: dict = Field(default_factory=dict)
issued_at: str | None = None
paid_at: str | None = None
created_at: str
updated_at: str
class SalesPaymentWebhookIn(BaseModel):
deal_id: str
invoice_id: str | None = None
payment_provider: str = "manual"
external_payment_id: str | None = None
amount: float = Field(ge=0)
currency: str = "KZT"
status: SalesPaymentStatus = "success"
paid_at: str | None = None
payment_method: str | None = None
failure_reason: str | None = None
metadata: dict = Field(default_factory=dict)
class SalesPaymentReconcileIn(BaseModel):
status: SalesPaymentStatus
failure_reason: str | None = None
metadata: dict = Field(default_factory=dict)
class SalesPaymentOut(BaseModel):
payment_id: str
deal_id: str
invoice_id: str | None = None
payment_provider: str | None = None
external_payment_id: str | None = None
amount: float
currency: str
status: SalesPaymentStatus
paid_at: str | None = None
payment_method: str | None = None
failure_reason: str | None = None
metadata: dict = Field(default_factory=dict)
created_at: str
updated_at: str
class SalesEscalationCreateIn(BaseModel):
escalation_type: str = Field(min_length=2)
reason: str = Field(min_length=3)
severity: SalesEscalationSeverity = "medium"
assigned_to_user_id: str | None = None
class SalesEscalationOut(BaseModel):
escalation_id: str
deal_id: str
escalation_type: str
reason: str
severity: SalesEscalationSeverity
status: SalesEscalationStatus
assigned_to_user_id: str | None = None
created_at: str
resolved_at: str | None = None
class SalesAutomationTaskOut(BaseModel):
task_id: str
deal_id: str
task_type: str
payload: dict = Field(default_factory=dict)
run_at: str
status: SalesAutomationStatus
retry_count: int = 0
last_error: str | None = None
created_at: str
updated_at: str
class SalesStageHistoryOut(BaseModel):
history_id: str
deal_id: str
from_stage_id: str | None = None
to_stage_id: str
changed_by_type: str
changed_by_id: str | None = None
reason: str | None = None
changed_at: str
class SalesChannelSwitchOut(BaseModel):
switch_id: str
deal_id: str
communication_id: str | None = None
from_channel: str
to_channel: str
reason_for_channel_switch: str
switched_at: str
class SalesTimelineEventOut(BaseModel):
ts: str
kind: str
title: str
body: str
meta: dict = Field(default_factory=dict)
class SalesWorkspaceOut(BaseModel):
lead: SalesLeadOut | None = None
deal: SalesDealOut
communications: list[SalesCommunicationOut] = Field(default_factory=list)
messages: list[SalesMessageOut] = Field(default_factory=list)
calls: list[SalesCallOut] = Field(default_factory=list)
offers: list[SalesOfferOut] = Field(default_factory=list)
conditions: list[SalesConditionOut] = Field(default_factory=list)
counterparty: SalesCounterpartyOut | None = None
documents: list[SalesDocumentOut] = Field(default_factory=list)
invoices: list[SalesInvoiceOut] = Field(default_factory=list)
payments: list[SalesPaymentOut] = Field(default_factory=list)
escalations: list[SalesEscalationOut] = Field(default_factory=list)
tasks: list[SalesAutomationTaskOut] = Field(default_factory=list)
stage_history: list[SalesStageHistoryOut] = Field(default_factory=list)
channel_switches: list[SalesChannelSwitchOut] = Field(default_factory=list)
timeline: list[SalesTimelineEventOut] = Field(default_factory=list)
class SalesDashboardStageOut(BaseModel):
stage_id: str
label: str
count: int = 0
amount: float = 0.0
class SalesDashboardSummaryOut(BaseModel):
leads_total: int = 0
deals_active: int = 0
deals_won: int = 0
deals_lost: int = 0
overdue_invoices: int = 0
payment_expected: float = 0.0
payment_received: float = 0.0
voice_sessions: int = 0
text_sessions: int = 0
channel_switches: int = 0
human_escalations: int = 0
stage_counts: list[SalesDashboardStageOut] = Field(default_factory=list)
hottest_deals: list[SalesDealOut] = Field(default_factory=list)
class SalesTelegramSyncIn(BaseModel):
thread_id: str = Field(min_length=1)
chat_id: str = Field(min_length=1)
interaction_id: str | None = None
customer_id: str | None = None
phone_number: str | None = None
display_name: str | None = None
queue_id: str | None = None
status: str | None = None
ai_state: str | None = None
ai_handoff_reason: str | None = None
message_id: str | None = None
external_message_id: str | None = None
text: str | None = None
direction: str = "inbound"
author_type: str | None = None
author_id: str | None = None
happened_at: str | None = None
metadata: dict = Field(default_factory=dict)
class SalesVoiceSyncIn(BaseModel):
call_id: str = Field(min_length=1)
interaction_id: str | None = None
queue_id: str | None = None
queue_code: str | None = None
caller_number: str | None = None
caller_name: str | None = None
voice_session_id: str | None = None
ai_session_id: str | None = None
ai_state: str | None = None
handoff_reason: str | None = None
telephony_status: str | None = None
call_status: str | None = None
started_at: str | None = None
ended_at: str | None = None
summary: str | None = None
transcript_text: str | None = None
metadata: dict = Field(default_factory=dict)
+445
View File
@@ -0,0 +1,445 @@
from __future__ import annotations
from sqlalchemy import Boolean, Float, Index, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from services.shared.sql_models import Base
class SalesLeadRow(Base):
__tablename__ = "sales_leads"
__table_args__ = (
Index("idx_sales_leads_tenant_status_score", "tenant_id", "status", "lead_score"),
Index("idx_sales_leads_phone_email", "phone", "email"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
lead_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
source_type: Mapped[str] = mapped_column(String(64), index=True)
source_channel: Mapped[str] = mapped_column(String(32), index=True)
source_campaign_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
full_name: Mapped[str] = mapped_column(String(256), index=True)
company_name: Mapped[str | None] = mapped_column(String(256), nullable=True, index=True)
phone: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
email: Mapped[str | None] = mapped_column(String(256), nullable=True, index=True)
messenger_handles_json: Mapped[str] = mapped_column(Text, default="{}")
lead_temperature: Mapped[str] = mapped_column(String(16), index=True)
lead_score: Mapped[float] = mapped_column(Float, default=50.0, index=True)
customer_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
segment_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
initial_need_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
preferred_channel: Mapped[str] = mapped_column(String(32), index=True)
assigned_agent_type: Mapped[str] = mapped_column(String(32), index=True)
status: Mapped[str] = mapped_column(String(32), index=True)
crm_customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesDealRow(Base):
__tablename__ = "sales_deals"
__table_args__ = (
Index("idx_sales_deals_tenant_stage_status", "tenant_id", "stage_id", "status"),
Index("idx_sales_deals_customer_channel", "customer_id", "current_channel"),
Index("idx_sales_deals_next_action", "next_action_at", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
deal_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
lead_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
pipeline_id: Mapped[str] = mapped_column(String(64), default="sales_default", index=True)
stage_id: Mapped[str] = mapped_column(String(64), index=True)
scenario_type: Mapped[str] = mapped_column(String(64), index=True)
priority: Mapped[int] = mapped_column(Integer, default=3, index=True)
title: Mapped[str] = mapped_column(String(512))
need_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
product_context_json: Mapped[str] = mapped_column(Text, default="{}")
estimated_amount: Mapped[float | None] = mapped_column(Float, nullable=True)
final_amount: Mapped[float | None] = mapped_column(Float, nullable=True)
currency: Mapped[str] = mapped_column(String(16), default="KZT")
payment_model: Mapped[str | None] = mapped_column(String(64), nullable=True)
document_required: Mapped[bool] = mapped_column(Boolean, default=True)
payment_required: Mapped[bool] = mapped_column(Boolean, default=True)
assigned_human_user_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
assigned_ai_orchestrator_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
preferred_channel: Mapped[str] = mapped_column(String(32), index=True)
current_channel: Mapped[str] = mapped_column(String(32), index=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="active")
won_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
lost_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
close_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
next_action_type: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
next_action_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
last_contact_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
closed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesCommunicationSessionRow(Base):
__tablename__ = "sales_communication_sessions"
__table_args__ = (
Index("idx_sales_comm_deal_started", "deal_id", "started_at"),
Index("idx_sales_comm_channel_status", "channel_type", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
communication_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
lead_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
channel_type: Mapped[str] = mapped_column(String(16), index=True)
direction: Mapped[str] = mapped_column(String(16), index=True)
agent_type: Mapped[str] = mapped_column(String(32), index=True)
started_at: Mapped[str] = mapped_column(String(64), index=True)
ended_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
duration_sec: Mapped[int | None] = mapped_column(Integer, nullable=True)
subject: Mapped[str | None] = mapped_column(String(512), nullable=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="active")
summary: Mapped[str | None] = mapped_column(Text, nullable=True)
transcript_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
next_action_type: Mapped[str | None] = mapped_column(String(128), nullable=True)
next_action_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
sentiment: Mapped[str | None] = mapped_column(String(32), nullable=True)
result_code: Mapped[str | None] = mapped_column(String(64), nullable=True)
metadata_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesMessageRow(Base):
__tablename__ = "sales_messages"
__table_args__ = (
Index("idx_sales_messages_deal_sent", "deal_id", "sent_at"),
Index("idx_sales_messages_ext_id", "channel_provider", "external_message_id"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
message_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
communication_id: Mapped[str] = mapped_column(String(64), index=True)
sender_type: Mapped[str] = mapped_column(String(32), index=True)
sender_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
channel_provider: Mapped[str] = mapped_column(String(32), index=True)
external_message_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
body: Mapped[str] = mapped_column(Text)
attachments_json: Mapped[str] = mapped_column(Text, default="[]")
delivery_status: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
read_status: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
message_metadata_json: Mapped[str] = mapped_column(Text, default="{}")
sent_at: Mapped[str] = mapped_column(String(64), index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesCallRow(Base):
__tablename__ = "sales_calls"
__table_args__ = (
Index("idx_sales_calls_deal_started", "deal_id", "started_at"),
Index("idx_sales_calls_ext_provider", "provider", "external_call_id"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
call_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
communication_id: Mapped[str] = mapped_column(String(64), index=True)
phone_number: Mapped[str] = mapped_column(String(64), index=True)
direction: Mapped[str] = mapped_column(String(16), index=True)
provider: Mapped[str] = mapped_column(String(64), index=True)
external_call_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
recording_url: Mapped[str | None] = mapped_column(Text, nullable=True)
transcript_status: Mapped[str] = mapped_column(String(32), index=True, default="pending")
transcript_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
call_status: Mapped[str] = mapped_column(String(32), index=True, default="started")
summary: Mapped[str | None] = mapped_column(Text, nullable=True)
started_at: Mapped[str] = mapped_column(String(64), index=True)
ended_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
duration_sec: Mapped[int | None] = mapped_column(Integer, nullable=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesTranscriptRow(Base):
__tablename__ = "sales_transcripts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
transcript_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
call_id: Mapped[str] = mapped_column(String(64), index=True)
language: Mapped[str] = mapped_column(String(16), index=True, default="ru")
transcript_text: Mapped[str] = mapped_column(Text)
diarization_json: Mapped[str] = mapped_column(Text, default="{}")
extracted_entities_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesNoteRow(Base):
__tablename__ = "sales_notes"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
note_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
author_type: Mapped[str] = mapped_column(String(32), index=True)
author_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
note_type: Mapped[str] = mapped_column(String(64), index=True)
content: Mapped[str] = mapped_column(Text)
created_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesOfferRow(Base):
__tablename__ = "sales_offers"
__table_args__ = (
Index("idx_sales_offers_deal_status", "deal_id", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
offer_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
offer_type: Mapped[str] = mapped_column(String(32), index=True)
title: Mapped[str] = mapped_column(String(256))
description: Mapped[str | None] = mapped_column(Text, nullable=True)
line_items_json: Mapped[str] = mapped_column(Text, default="[]")
pricing_json: Mapped[str] = mapped_column(Text, default="{}")
total_amount: Mapped[float] = mapped_column(Float, default=0)
currency: Mapped[str] = mapped_column(String(16), default="KZT")
validity_until: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="draft")
rendered_document_url: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by_type: Mapped[str] = mapped_column(String(32), default="text_ai")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesConditionRow(Base):
__tablename__ = "sales_conditions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
condition_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
product_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
service_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
quantity: Mapped[float | None] = mapped_column(Float, nullable=True)
unit: Mapped[str | None] = mapped_column(String(32), nullable=True)
delivery_mode: Mapped[str | None] = mapped_column(String(64), nullable=True)
execution_date: Mapped[str | None] = mapped_column(String(64), nullable=True)
start_date: Mapped[str | None] = mapped_column(String(64), nullable=True)
end_date: Mapped[str | None] = mapped_column(String(64), nullable=True)
payment_terms: Mapped[str | None] = mapped_column(Text, nullable=True)
custom_terms_json: Mapped[str] = mapped_column(Text, default="{}")
agreed_price: Mapped[float | None] = mapped_column(Float, nullable=True)
currency: Mapped[str] = mapped_column(String(16), default="KZT")
confirmed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesCounterpartyRow(Base):
__tablename__ = "sales_counterparties"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
counterparty_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
company_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
full_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
bin_iin: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
address: Mapped[str | None] = mapped_column(Text, nullable=True)
bank_details_json: Mapped[str] = mapped_column(Text, default="{}")
signer_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
signer_role: Mapped[str | None] = mapped_column(String(128), nullable=True)
signer_basis: Mapped[str | None] = mapped_column(String(256), nullable=True)
email_for_docs: Mapped[str | None] = mapped_column(String(256), nullable=True)
phone_for_docs: Mapped[str | None] = mapped_column(String(64), nullable=True)
completeness_status: Mapped[str] = mapped_column(String(32), index=True, default="draft")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesDocumentRow(Base):
__tablename__ = "sales_documents"
__table_args__ = (
Index("idx_sales_documents_deal_status", "deal_id", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
document_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
document_type: Mapped[str] = mapped_column(String(64), index=True)
template_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
version: Mapped[int] = mapped_column(Integer, default=1)
status: Mapped[str] = mapped_column(String(32), index=True, default="draft")
file_url: Mapped[str | None] = mapped_column(Text, nullable=True)
rendered_payload_json: Mapped[str] = mapped_column(Text, default="{}")
external_sign_provider_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
signed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesInvoiceRow(Base):
__tablename__ = "sales_invoices"
__table_args__ = (
Index("idx_sales_invoices_deal_status_due", "deal_id", "status", "due_date"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
invoice_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
invoice_number: Mapped[str] = mapped_column(String(64), unique=True, index=True)
basis_document_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
amount: Mapped[float] = mapped_column(Float, default=0)
currency: Mapped[str] = mapped_column(String(16), default="KZT")
due_date: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="draft")
payment_link: Mapped[str | None] = mapped_column(Text, nullable=True)
line_items_json: Mapped[str] = mapped_column(Text, default="[]")
metadata_json: Mapped[str] = mapped_column(Text, default="{}")
issued_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
paid_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesPaymentRow(Base):
__tablename__ = "sales_payments"
__table_args__ = (
Index("idx_sales_payments_deal_status", "deal_id", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
payment_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
invoice_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
payment_provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
external_payment_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
amount: Mapped[float] = mapped_column(Float, default=0)
currency: Mapped[str] = mapped_column(String(16), default="KZT")
status: Mapped[str] = mapped_column(String(32), index=True, default="pending")
paid_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
payment_method: Mapped[str | None] = mapped_column(String(64), nullable=True)
failure_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
metadata_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesStageHistoryRow(Base):
__tablename__ = "sales_stage_history"
__table_args__ = (
Index("idx_sales_stage_history_deal_changed", "deal_id", "changed_at"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
history_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
from_stage_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
to_stage_id: Mapped[str] = mapped_column(String(64), index=True)
changed_by_type: Mapped[str] = mapped_column(String(32), index=True)
changed_by_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
changed_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesEscalationRow(Base):
__tablename__ = "sales_escalations"
__table_args__ = (
Index("idx_sales_escalations_deal_status", "deal_id", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
escalation_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
escalation_type: Mapped[str] = mapped_column(String(64), index=True)
reason: Mapped[str] = mapped_column(Text)
severity: Mapped[str] = mapped_column(String(16), index=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="open")
assigned_to_user_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
resolved_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
class SalesAutomationTaskRow(Base):
__tablename__ = "sales_automation_tasks"
__table_args__ = (
Index("idx_sales_tasks_run_status", "run_at", "status"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
task_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
task_type: Mapped[str] = mapped_column(String(64), index=True)
payload_json: Mapped[str] = mapped_column(Text, default="{}")
run_at: Mapped[str] = mapped_column(String(64), index=True)
status: Mapped[str] = mapped_column(String(32), index=True, default="pending")
retry_count: Mapped[int] = mapped_column(Integer, default=0)
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesChannelSwitchRow(Base):
__tablename__ = "sales_channel_switches"
__table_args__ = (
Index("idx_sales_channel_switches_deal_switched", "deal_id", "switched_at"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
switch_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
communication_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
from_channel: Mapped[str] = mapped_column(String(32), index=True)
to_channel: Mapped[str] = mapped_column(String(32), index=True)
reason_for_channel_switch: Mapped[str] = mapped_column(Text)
switched_at: Mapped[str] = mapped_column(String(64), index=True)
class SalesExternalLinkRow(Base):
__tablename__ = "sales_external_links"
__table_args__ = (
Index("idx_sales_external_links_deal_sync", "deal_id", "last_sync_at"),
Index("idx_sales_external_links_thread", "external_thread_id"),
Index("idx_sales_external_links_voice_session", "voice_session_id"),
Index("idx_sales_external_links_external_call", "external_call_id"),
Index("idx_sales_external_links_interaction", "interaction_id"),
Index("idx_sales_external_links_customer_phone", "customer_id", "phone_number"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
link_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
deal_id: Mapped[str] = mapped_column(String(64), index=True)
communication_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
sales_call_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
channel_provider: Mapped[str] = mapped_column(String(32), index=True)
external_thread_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
external_chat_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
external_call_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
voice_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
ai_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
interaction_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
customer_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
phone_number: Mapped[str | None] = mapped_column(String(64), nullable=True)
external_status: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
link_metadata_json: Mapped[str] = mapped_column(Text, default="{}")
created_at: Mapped[str] = mapped_column(String(64), index=True)
updated_at: Mapped[str] = mapped_column(String(64), index=True)
last_sync_at: Mapped[str] = mapped_column(String(64), index=True)