from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text, UniqueConstraint from app import db from .base import SoftDeleteMixin, TimestampMixin, default_uuid class SyncMetadata(db.Model, TimestampMixin, SoftDeleteMixin): """Метаданные синхронизации (паспорт узла).""" __tablename__ = "sync_metadata" id = Column(String(36), primary_key=True, default=default_uuid) node_id = Column(String(36), nullable=False, unique=True) node_type = Column(String(20), nullable=False) # 'server' | 'client' node_name = Column(String(100), nullable=True) node_status = Column(String(20), default="active") last_heartbeat = Column(DateTime, nullable=True) last_sync = Column(DateTime, nullable=True) sync_status = Column(String(20), default="idle") is_enabled = Column(Boolean, default=True) class SyncClient(db.Model, TimestampMixin, SoftDeleteMixin): """Реестр клиентов синхронизации.""" __tablename__ = "sync_clients" id = Column(String(36), primary_key=True, default=default_uuid) node_id = Column(String(36), nullable=False, unique=True) client_name = Column(String(100), nullable=False) ip_address = Column(String(45), nullable=True) port = Column(Integer, default=80) status = Column(String(20), default="active") last_seen = Column(DateTime, nullable=True) total_syncs = Column(Integer, default=0) last_error = Column(Text, nullable=True) is_enabled = Column(Boolean, default=True) personal_snapshot_completed_at = Column(DateTime, nullable=True) personal_snapshot_cursor = Column(Integer, nullable=False, default=0) class SyncEngineState(db.Model, TimestampMixin, SoftDeleteMixin): """Durable flags/cursors for sync engine bootstrap.""" __tablename__ = "sync_engine_state" id = Column(Integer, primary_key=True, default=1) universal_bootstrap_completed_at = Column(DateTime, nullable=True) universal_bootstrap_cursor = Column(Integer, nullable=False, default=0) universal_bootstrap_last_error = Column(Text, nullable=True) class SyncClientDisplayName(db.Model): """Привязка клиента к отображаемому имени (для UI).""" __tablename__ = "sync_client_display_name" node_id = Column(String(36), primary_key=True) display_name = Column(String(100), nullable=False) updated_at = Column(DateTime, nullable=False) class SyncQueue(db.Model, TimestampMixin, SoftDeleteMixin): """Очередь задач синхронизации.""" __tablename__ = "sync_queue" __table_args__ = ( UniqueConstraint( "table_name", "record_id", "action", "target_node_id", name="uq_sync_queue_task", ), ) id = Column(String(36), primary_key=True, default=default_uuid) table_name = Column(String(50), nullable=False) record_id = Column(String(36), nullable=False) action = Column(String(20), nullable=False) # 'create' | 'update' | 'delete' status = Column(String(20), default="pending") # pending|processing|completed|failed target_node_id = Column(String(36), nullable=True) source_node_id = Column(String(36), nullable=True) priority = Column(Integer, default=1) retry_count = Column(Integer, default=0) max_retries = Column(Integer, default=3) error_message = Column(Text, nullable=True) created_at = Column(DateTime, nullable=False) processed_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) class SyncDelivery(db.Model, TimestampMixin, SoftDeleteMixin): """Фиксация доставки универсальных задач до клиентов.""" __tablename__ = "sync_delivery" id = Column(String(36), primary_key=True, default=default_uuid) client_id = Column(String(36), nullable=False, index=True) task_id = Column(String(36), nullable=False, index=True) delivered_at = Column(DateTime, nullable=False) class SyncConflict(db.Model, TimestampMixin, SoftDeleteMixin): """Конфликты синхронизации.""" __tablename__ = "sync_conflicts" id = Column(String(36), primary_key=True, default=default_uuid) table_name = Column(String(50), nullable=False) record_id = Column(String(36), nullable=False) conflict_type = Column(String(20), nullable=False) local_data = Column(Text, nullable=True) remote_data = Column(Text, nullable=True) resolution = Column(String(20), nullable=True) resolved_by = Column(String(50), nullable=True) resolved_at = Column(DateTime, nullable=True)