Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)

This commit is contained in:
mi
2026-08-13 18:52:42 +03:00
parent 5100ba9fc3
commit 99605b1c77
144 changed files with 15295 additions and 1120 deletions
+78 -3
View File
@@ -34,6 +34,10 @@ class Base(DeclarativeBase):
type_annotation_map = {dict[str, Any]: JSON}
class BitrixBase(DeclarativeBase):
"""Models owned by bitrix-sync, excluded from han_app create_all."""
class Common:
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
record_status: Mapped[str] = mapped_column(String(1), default="A", server_default="A")
@@ -142,6 +146,9 @@ class Message(Common, Base):
content_kind: Mapped[str] = mapped_column(String(16))
text: Mapped[str] = mapped_column(Text, default="")
safety_status: Mapped[str] = mapped_column(String(16))
safety_processing_mode: Mapped[str | None] = mapped_column(String(16))
safety_config_version: Mapped[int | None] = mapped_column(BigInteger)
safety_rules_version: Mapped[str | None] = mapped_column(String(128))
delivery_status: Mapped[str] = mapped_column(String(16))
external_message_id: Mapped[str | None] = mapped_column(String(255))
client_idempotency_key: Mapped[str | None] = mapped_column(String(128))
@@ -152,7 +159,7 @@ class MessageAttachment(Common, Base):
__tablename__ = "message_attachments"
__table_args__ = (
CheckConstraint("direction IN ('client_upload','company_inbound')"),
CheckConstraint("scan_status IN ('pending','clean','infected','failed')"),
CheckConstraint("scan_status IN ('pending','clean','bypassed','infected','failed')"),
CheckConstraint("size_bytes > 0"),
{"schema": SCHEMA},
)
@@ -171,6 +178,8 @@ class MessageAttachment(Common, Base):
storage_bucket: Mapped[str] = mapped_column(String(255))
object_key: Mapped[str] = mapped_column(String(1024))
quarantine_object_key: Mapped[str | None] = mapped_column(String(1024))
quarantine_version_id: Mapped[str | None] = mapped_column(String(1024))
quarantine_etag: Mapped[str | None] = mapped_column(String(1024))
upload_expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
@@ -193,10 +202,15 @@ class SafetyTask(Base):
__table_args__ = ({"schema": SCHEMA},)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
task_id: Mapped[str] = mapped_column(String(255), unique=True)
poll_location: Mapped[str] = mapped_column(String(1024))
message_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(f"{SCHEMA}.messages.id"), unique=True)
attachment_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
quarantine_object_key: Mapped[str | None] = mapped_column(String(1024))
status: Mapped[str] = mapped_column(String(16))
processing_mode: Mapped[str | None] = mapped_column(String(16))
config_version: Mapped[int | None] = mapped_column(BigInteger)
rules_version: Mapped[str | None] = mapped_column(String(128))
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
deadline_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
next_poll_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
@@ -321,18 +335,32 @@ class PopularQuestion(Common, Base):
class SyncQueue(Base):
__tablename__ = "sync_queue"
__table_args__ = ({"schema": SCHEMA},)
__table_args__ = (
CheckConstraint(
"status IN ('pending','leased','processed','retry_wait','dead_letter','cancelled')"
),
Index("ix_sync_queue_claim", "status", "next_attempt_at", "created_at"),
Index("ix_sync_queue_entity_history", "entity_type", "entity_id", "created_at"),
{"schema": SCHEMA},
)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
task_type: Mapped[str] = mapped_column(String(64))
entity_type: Mapped[str] = mapped_column(String(64))
entity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
dedup_key: Mapped[str] = mapped_column(String(255), unique=True)
dedup_key: Mapped[str] = mapped_column(String(255))
payload_json: Mapped[dict[str, Any]] = mapped_column(JSON)
status: Mapped[str] = mapped_column(String(16), default="pending")
attempt_count: Mapped[int] = mapped_column(Integer, default=0)
next_attempt_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
locked_by: Mapped[str | None] = mapped_column(String(128))
locked_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
lease_token: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
last_error_code: Mapped[str | None] = mapped_column(String(64))
last_error_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
cancel_reason: Mapped[str | None] = mapped_column(String(255))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
@@ -347,6 +375,53 @@ class EntityExternalMapping(Base):
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
class BitrixEntityExternalMapping(BitrixBase):
__tablename__ = "entity_external_mapping"
__table_args__ = ({"schema": "bitrix_sync"},)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
entity_type: Mapped[str] = mapped_column(String(64))
entity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True))
external_system: Mapped[str] = mapped_column(String(32), default="bitrix24")
external_entity_type: Mapped[str] = mapped_column(String(32), default="contact")
external_id: Mapped[str] = mapped_column(String(128))
status: Mapped[str] = mapped_column(String(16), default="active")
opened_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
close_reason: Mapped[str | None] = mapped_column(String(64))
workflow_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
Index(
"uq_sync_queue_active_dedup",
SyncQueue.dedup_key,
unique=True,
postgresql_where=SyncQueue.status.in_(["pending", "leased", "retry_wait"]),
)
Index(
"ix_sync_queue_expired_lease",
SyncQueue.locked_until,
postgresql_where=SyncQueue.status == "leased",
)
Index(
"uq_external_mapping_active_entity",
BitrixEntityExternalMapping.external_system,
BitrixEntityExternalMapping.entity_type,
BitrixEntityExternalMapping.entity_id,
unique=True,
postgresql_where=BitrixEntityExternalMapping.status == "active",
)
Index(
"uq_external_mapping_active_external",
BitrixEntityExternalMapping.external_system,
BitrixEntityExternalMapping.external_entity_type,
BitrixEntityExternalMapping.external_id,
unique=True,
postgresql_where=BitrixEntityExternalMapping.status == "active",
)
class Database:
def __init__(self, url: str) -> None:
self.engine: AsyncEngine = create_postgres_engine(url, pool_pre_ping=True)