Implement sales CRM workflow foundation
This commit is contained in:
@@ -38,9 +38,10 @@ SalesDocumentStatus = Literal["draft", "sent", "under_review", "confirmed", "sig
|
||||
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"]
|
||||
SalesEscalationStatus = Literal["open", "assigned", "in_progress", "resolved", "canceled"]
|
||||
SalesAutomationStatus = Literal["pending", "running", "completed", "failed", "canceled"]
|
||||
SalesPipelineStageCategory = Literal["entry", "communication", "commercial", "paperwork", "finance", "closing"]
|
||||
SalesSwitchChannel = Literal["text", "voice"]
|
||||
|
||||
|
||||
class SalesStageRefOut(BaseModel):
|
||||
@@ -222,7 +223,11 @@ class SalesDealUpdate(BaseModel):
|
||||
class SalesDealStageChangeIn(BaseModel):
|
||||
stage_id: str | None = Field(default=None, min_length=2)
|
||||
stage_code: str | None = Field(default=None, min_length=2)
|
||||
target_stage_id: str | None = Field(default=None, min_length=2)
|
||||
target_stage_code: str | None = Field(default=None, min_length=2)
|
||||
reason: str | None = None
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
force: bool = False
|
||||
|
||||
|
||||
class SalesDealScenarioIn(BaseModel):
|
||||
@@ -295,8 +300,16 @@ class SalesCommunicationSummaryIn(BaseModel):
|
||||
|
||||
|
||||
class SalesCommunicationSwitchChannelIn(BaseModel):
|
||||
to_channel: SalesPreferredChannel
|
||||
reason_for_channel_switch: str = Field(min_length=3)
|
||||
to_channel: SalesSwitchChannel
|
||||
reason_code: str = "human_decision"
|
||||
reason_text: str | None = None
|
||||
reason_for_channel_switch: str | None = None
|
||||
create_session: bool = False
|
||||
scheduled_at: str | None = None
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
source_type: str | None = None
|
||||
source_id: str | None = None
|
||||
recommended_by_task_id: str | None = None
|
||||
|
||||
|
||||
class SalesCommunicationBindExternalIn(BaseModel):
|
||||
@@ -315,6 +328,7 @@ class SalesCommunicationOut(BaseModel):
|
||||
lead_id: str | None = None
|
||||
customer_id: str | None = None
|
||||
channel_type: SalesChannelType
|
||||
channel_provider: str | None = None
|
||||
direction: SalesDirection
|
||||
agent_type: SalesAgentType
|
||||
started_at: str
|
||||
@@ -580,7 +594,11 @@ class SalesPaymentWebhookIn(BaseModel):
|
||||
deal_id: str
|
||||
invoice_id: str | None = None
|
||||
payment_provider: str = "manual"
|
||||
provider_account_id: str | None = None
|
||||
external_event_id: str | None = None
|
||||
external_payment_id: str | None = None
|
||||
event_type: str | None = None
|
||||
provider_status: str | None = None
|
||||
amount: float = Field(ge=0)
|
||||
currency: str = "KZT"
|
||||
status: SalesPaymentStatus = "success"
|
||||
@@ -618,6 +636,10 @@ class SalesEscalationCreateIn(BaseModel):
|
||||
reason: str = Field(min_length=3)
|
||||
severity: SalesEscalationSeverity = "medium"
|
||||
assigned_to_user_id: str | None = None
|
||||
source_channel: str | None = None
|
||||
source_communication_session_id: str | None = None
|
||||
source_automation_task_id: str | None = None
|
||||
sla_due_at: str | None = None
|
||||
|
||||
|
||||
class SalesEscalationOut(BaseModel):
|
||||
@@ -628,8 +650,35 @@ class SalesEscalationOut(BaseModel):
|
||||
severity: SalesEscalationSeverity
|
||||
status: SalesEscalationStatus
|
||||
assigned_to_user_id: str | None = None
|
||||
assigned_at: str | None = None
|
||||
started_at: str | None = None
|
||||
created_at: str
|
||||
resolved_at: str | None = None
|
||||
canceled_at: str | None = None
|
||||
resolution_code: str | None = None
|
||||
resolution_summary: str | None = None
|
||||
sla_due_at: str | None = None
|
||||
source_channel: str | None = None
|
||||
source_communication_session_id: str | None = None
|
||||
source_automation_task_id: str | None = None
|
||||
updated_at: str | None = None
|
||||
|
||||
|
||||
class SalesEscalationAssignIn(BaseModel):
|
||||
assigned_to_user_id: str = Field(min_length=1)
|
||||
|
||||
|
||||
class SalesEscalationResolveIn(BaseModel):
|
||||
resolution_code: str = Field(min_length=2)
|
||||
resolution_summary: str | None = None
|
||||
target_stage_code: str | None = None
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SalesEscalationCancelIn(BaseModel):
|
||||
resolution_code: str = "canceled"
|
||||
resolution_summary: str | None = None
|
||||
metadata: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SalesAutomationTaskOut(BaseModel):
|
||||
@@ -640,6 +689,11 @@ class SalesAutomationTaskOut(BaseModel):
|
||||
run_at: str
|
||||
status: SalesAutomationStatus
|
||||
retry_count: int = 0
|
||||
max_retries: int = 3
|
||||
locked_at: str | None = None
|
||||
locked_by: str | None = None
|
||||
completed_at: str | None = None
|
||||
failed_at: str | None = None
|
||||
last_error: str | None = None
|
||||
created_at: str
|
||||
updated_at: str
|
||||
@@ -662,10 +716,50 @@ class SalesChannelSwitchOut(BaseModel):
|
||||
switch_id: str
|
||||
deal_id: str
|
||||
communication_id: str | None = None
|
||||
communication_session_id: str | None = None
|
||||
previous_communication_session_id: str | None = None
|
||||
new_communication_session_id: str | None = None
|
||||
from_channel: str
|
||||
to_channel: str
|
||||
reason_code: str
|
||||
reason_text: str | None = None
|
||||
reason_for_channel_switch: str
|
||||
initiated_by_type: str
|
||||
initiated_by_id: str | None = None
|
||||
source_type: str | None = None
|
||||
source_id: str | None = None
|
||||
recommended_by_task_id: str | None = None
|
||||
switched_at: str
|
||||
created_at: str
|
||||
new_communication: SalesCommunicationOut | None = None
|
||||
|
||||
|
||||
class SalesNoteCreateIn(BaseModel):
|
||||
note_type: str = "general"
|
||||
content: str = Field(min_length=1)
|
||||
source_type: str | None = None
|
||||
source_id: str | None = None
|
||||
|
||||
|
||||
class SalesNoteUpdateIn(BaseModel):
|
||||
note_type: str | None = None
|
||||
content: str | None = Field(default=None, min_length=1)
|
||||
source_type: str | None = None
|
||||
source_id: str | None = None
|
||||
|
||||
|
||||
class SalesNoteOut(BaseModel):
|
||||
note_id: str
|
||||
deal_id: str
|
||||
author_type: str
|
||||
author_id: str | None = None
|
||||
note_type: str
|
||||
content: str
|
||||
source_type: str | None = None
|
||||
source_id: str | None = None
|
||||
created_at: str
|
||||
updated_at: str | None = None
|
||||
archived_at: str | None = None
|
||||
|
||||
|
||||
class SalesTimelineEventOut(BaseModel):
|
||||
@@ -684,6 +778,8 @@ class SalesWorkspaceOut(BaseModel):
|
||||
communications: list[SalesCommunicationOut] = Field(default_factory=list)
|
||||
messages: list[SalesMessageOut] = Field(default_factory=list)
|
||||
calls: list[SalesCallOut] = Field(default_factory=list)
|
||||
transcripts: list[SalesTranscriptOut] = Field(default_factory=list)
|
||||
notes: list[SalesNoteOut] = Field(default_factory=list)
|
||||
offers: list[SalesOfferOut] = Field(default_factory=list)
|
||||
conditions: list[SalesConditionOut] = Field(default_factory=list)
|
||||
counterparty: SalesCounterpartyOut | None = None
|
||||
@@ -691,9 +787,11 @@ class SalesWorkspaceOut(BaseModel):
|
||||
invoices: list[SalesInvoiceOut] = Field(default_factory=list)
|
||||
payments: list[SalesPaymentOut] = Field(default_factory=list)
|
||||
escalations: list[SalesEscalationOut] = Field(default_factory=list)
|
||||
active_escalation: SalesEscalationOut | None = None
|
||||
tasks: list[SalesAutomationTaskOut] = Field(default_factory=list)
|
||||
stage_history: list[SalesStageHistoryOut] = Field(default_factory=list)
|
||||
channel_switches: list[SalesChannelSwitchOut] = Field(default_factory=list)
|
||||
recommended_channel_switch: dict | None = None
|
||||
timeline: list[SalesTimelineEventOut] = Field(default_factory=list)
|
||||
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ class SalesCommunicationSessionRow(Base):
|
||||
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)
|
||||
channel_provider: Mapped[str | None] = mapped_column(String(32), nullable=True, 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)
|
||||
@@ -275,7 +276,11 @@ class SalesNoteRow(Base):
|
||||
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)
|
||||
source_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
source_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
created_at: Mapped[str] = mapped_column(String(64), index=True)
|
||||
updated_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
archived_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
|
||||
|
||||
class SalesOfferRow(Base):
|
||||
@@ -403,6 +408,15 @@ class SalesPaymentRow(Base):
|
||||
__tablename__ = "sales_payments"
|
||||
__table_args__ = (
|
||||
Index("idx_sales_payments_deal_status", "deal_id", "status"),
|
||||
Index(
|
||||
"idx_sales_payments_tenant_external_payment_unique",
|
||||
"tenant_id",
|
||||
"payment_provider",
|
||||
"external_payment_id",
|
||||
unique=True,
|
||||
sqlite_where=text("external_payment_id IS NOT NULL"),
|
||||
postgresql_where=text("external_payment_id IS NOT NULL"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
@@ -423,6 +437,41 @@ class SalesPaymentRow(Base):
|
||||
updated_at: Mapped[str] = mapped_column(String(64), index=True)
|
||||
|
||||
|
||||
class SalesPaymentWebhookEventRow(Base):
|
||||
__tablename__ = "sales_payment_webhook_events"
|
||||
__table_args__ = (
|
||||
Index("idx_sales_payment_webhook_events_received", "tenant_id", "received_at"),
|
||||
Index("idx_sales_payment_webhook_events_payment", "tenant_id", "payment_provider", "external_payment_id"),
|
||||
Index(
|
||||
"idx_sales_payment_webhook_events_external_event_unique",
|
||||
"tenant_id",
|
||||
"payment_provider",
|
||||
"external_event_id",
|
||||
unique=True,
|
||||
sqlite_where=text("external_event_id IS NOT NULL"),
|
||||
postgresql_where=text("external_event_id IS NOT NULL"),
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
tenant_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
payment_provider: Mapped[str] = mapped_column(String(64), index=True)
|
||||
provider_account_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
external_event_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
external_payment_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
event_type: Mapped[str] = mapped_column(String(128), index=True)
|
||||
event_status: Mapped[str] = mapped_column(String(32), index=True, default="received")
|
||||
signature_status: Mapped[str] = mapped_column(String(32), index=True, default="unchecked")
|
||||
raw_payload_hash: Mapped[str] = mapped_column(String(64), index=True)
|
||||
normalized_payload_json: Mapped[str] = mapped_column(Text, default="{}")
|
||||
headers_json: Mapped[str] = mapped_column(Text, default="{}")
|
||||
received_at: Mapped[str] = mapped_column(String(64), index=True)
|
||||
processed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
error_message: 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 SalesStageHistoryRow(Base):
|
||||
__tablename__ = "sales_stage_history"
|
||||
__table_args__ = (
|
||||
@@ -456,14 +505,25 @@ class SalesEscalationRow(Base):
|
||||
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)
|
||||
assigned_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
started_at: Mapped[str | None] = mapped_column(String(64), 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)
|
||||
canceled_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
resolution_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
resolution_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
sla_due_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
source_channel: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
|
||||
source_communication_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
source_automation_task_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
updated_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"),
|
||||
Index("idx_sales_tasks_lock", "status", "locked_at", "locked_by"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
@@ -475,6 +535,11 @@ class SalesAutomationTaskRow(Base):
|
||||
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)
|
||||
max_retries: Mapped[int] = mapped_column(Integer, default=3)
|
||||
locked_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
locked_by: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
completed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
failed_at: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
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)
|
||||
@@ -491,10 +556,21 @@ class SalesChannelSwitchRow(Base):
|
||||
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)
|
||||
communication_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
previous_communication_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
new_communication_session_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_code: Mapped[str] = mapped_column(String(64), default="human_decision", index=True)
|
||||
reason_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
reason_for_channel_switch: Mapped[str] = mapped_column(Text)
|
||||
initiated_by_type: Mapped[str] = mapped_column(String(32), default="human", index=True)
|
||||
initiated_by_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
source_type: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
source_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
recommended_by_task_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
switched_at: Mapped[str] = mapped_column(String(64), index=True)
|
||||
created_at: Mapped[str] = mapped_column(String(64), index=True)
|
||||
|
||||
|
||||
class SalesExternalLinkRow(Base):
|
||||
|
||||
@@ -601,6 +601,145 @@ def _apply_runtime_schema_compatibility() -> None:
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_automation_tasks" in table_names:
|
||||
columns = _table_columns(inspector, "sales_automation_tasks")
|
||||
_add_column_if_missing(conn, columns, "sales_automation_tasks", "max_retries", "INTEGER DEFAULT 3")
|
||||
_add_column_if_missing(conn, columns, "sales_automation_tasks", "locked_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_automation_tasks", "locked_by", "VARCHAR(128)")
|
||||
_add_column_if_missing(conn, columns, "sales_automation_tasks", "completed_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_automation_tasks", "failed_at", "VARCHAR(64)")
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE sales_automation_tasks
|
||||
SET max_retries = 3
|
||||
WHERE max_retries IS NULL
|
||||
"""
|
||||
)
|
||||
)
|
||||
indexes = _table_indexes(inspector, "sales_automation_tasks")
|
||||
if "idx_sales_tasks_lock" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE INDEX IF NOT EXISTS idx_sales_tasks_lock "
|
||||
"ON sales_automation_tasks(status, locked_at, locked_by)"
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_communication_sessions" in table_names:
|
||||
columns = _table_columns(inspector, "sales_communication_sessions")
|
||||
_add_column_if_missing(conn, columns, "sales_communication_sessions", "channel_provider", "VARCHAR(32)")
|
||||
indexes = _table_indexes(inspector, "sales_communication_sessions")
|
||||
if "idx_sales_comm_channel_provider" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE INDEX IF NOT EXISTS idx_sales_comm_channel_provider "
|
||||
"ON sales_communication_sessions(channel_provider)"
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_notes" in table_names:
|
||||
columns = _table_columns(inspector, "sales_notes")
|
||||
_add_column_if_missing(conn, columns, "sales_notes", "source_type", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_notes", "source_id", "VARCHAR(128)")
|
||||
_add_column_if_missing(conn, columns, "sales_notes", "updated_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_notes", "archived_at", "VARCHAR(64)")
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE sales_notes
|
||||
SET updated_at = created_at
|
||||
WHERE updated_at IS NULL OR TRIM(updated_at) = ''
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_escalations" in table_names:
|
||||
columns = _table_columns(inspector, "sales_escalations")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "assigned_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "started_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "canceled_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "resolution_code", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "resolution_summary", "TEXT")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "sla_due_at", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "source_channel", "VARCHAR(32)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "source_communication_session_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "source_automation_task_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_escalations", "updated_at", "VARCHAR(64)")
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE sales_escalations
|
||||
SET updated_at = COALESCE(updated_at, resolved_at, created_at)
|
||||
WHERE updated_at IS NULL OR TRIM(updated_at) = ''
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_channel_switches" in table_names:
|
||||
columns = _table_columns(inspector, "sales_channel_switches")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "communication_session_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "previous_communication_session_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "new_communication_session_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "reason_code", "VARCHAR(64) DEFAULT 'human_decision'")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "reason_text", "TEXT")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "initiated_by_type", "VARCHAR(32) DEFAULT 'human'")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "initiated_by_id", "VARCHAR(128)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "source_type", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "source_id", "VARCHAR(128)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "recommended_by_task_id", "VARCHAR(64)")
|
||||
_add_column_if_missing(conn, columns, "sales_channel_switches", "created_at", "VARCHAR(64)")
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE sales_channel_switches
|
||||
SET communication_session_id = COALESCE(communication_session_id, communication_id),
|
||||
previous_communication_session_id = COALESCE(previous_communication_session_id, communication_id),
|
||||
reason_code = COALESCE(reason_code, 'human_decision'),
|
||||
reason_text = COALESCE(reason_text, reason_for_channel_switch),
|
||||
initiated_by_type = COALESCE(initiated_by_type, 'human'),
|
||||
created_at = COALESCE(created_at, switched_at)
|
||||
WHERE created_at IS NULL OR TRIM(created_at) = ''
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_payment_webhook_events" in table_names:
|
||||
indexes = _table_indexes(inspector, "sales_payment_webhook_events")
|
||||
if "idx_sales_payment_webhook_events_received" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE INDEX IF NOT EXISTS idx_sales_payment_webhook_events_received "
|
||||
"ON sales_payment_webhook_events(tenant_id, received_at)"
|
||||
)
|
||||
)
|
||||
if "idx_sales_payment_webhook_events_payment" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE INDEX IF NOT EXISTS idx_sales_payment_webhook_events_payment "
|
||||
"ON sales_payment_webhook_events(tenant_id, payment_provider, external_payment_id)"
|
||||
)
|
||||
)
|
||||
if "idx_sales_payment_webhook_events_external_event_unique" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_sales_payment_webhook_events_external_event_unique "
|
||||
"ON sales_payment_webhook_events(tenant_id, payment_provider, external_event_id) "
|
||||
"WHERE external_event_id IS NOT NULL"
|
||||
)
|
||||
)
|
||||
|
||||
if "sales_payments" in table_names:
|
||||
indexes = _table_indexes(inspector, "sales_payments")
|
||||
if "idx_sales_payments_tenant_external_payment_unique" not in indexes:
|
||||
conn.execute(
|
||||
text(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_sales_payments_tenant_external_payment_unique "
|
||||
"ON sales_payments(tenant_id, payment_provider, external_payment_id) "
|
||||
"WHERE external_payment_id IS NOT NULL"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def init_sql_schema() -> None:
|
||||
global _BASE_SCHEMA_INITIALIZED
|
||||
|
||||
Reference in New Issue
Block a user