Files
call-center/services/shared/sales_models.py
T
2026-05-09 09:16:06 +05:00

686 lines
21 KiB
Python

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)