diff --git a/app/services/backup_service.py b/app/services/backup_service.py index 8d638442..643d697e 100644 --- a/app/services/backup_service.py +++ b/app/services/backup_service.py @@ -819,7 +819,7 @@ class BackupService: try: if clear_existing: logger.warning("🗑️ Очищаем существующие данные...") - await self._clear_database_tables(db) + await self._clear_database_tables(db, backup_data) models_for_restore = self._get_models_for_backup(True) models_by_table = { @@ -888,6 +888,9 @@ class BackupService: restored_tables += 1 logger.info("✅ Таблица %s восстановлена", table_name) + # Flush все изменения перед обновлением реферальных связей + await db.flush() + await self._update_user_referrals(db, backup_data) assoc_tables, assoc_records = await self._restore_association_tables( @@ -1201,10 +1204,29 @@ class BackupService: ) -> int: restored_count = 0 + # Кешируем существующие tariff_id для проверки FK + existing_tariff_ids = set() + if table_name == "subscriptions": + try: + result = await db.execute(select(Tariff.id)) + existing_tariff_ids = {row[0] for row in result.fetchall()} + logger.info(f"📋 Найдено {len(existing_tariff_ids)} существующих тарифов для валидации FK") + except Exception as e: + logger.warning(f"⚠️ Не удалось получить список тарифов: {e}") + for record_data in records: try: processed_data = self._process_record_data(record_data, model, table_name) + # Валидация FK для subscriptions.tariff_id + if table_name == "subscriptions" and "tariff_id" in processed_data: + tariff_id = processed_data.get("tariff_id") + if tariff_id is not None and tariff_id not in existing_tariff_ids: + logger.warning( + f"⚠️ Тариф {tariff_id} не найден, устанавливаем tariff_id=NULL для подписки" + ) + processed_data["tariff_id"] = None + primary_key_col = self._get_primary_key_column(model) if primary_key_col and primary_key_col in processed_data: @@ -1236,7 +1258,7 @@ class BackupService: return restored_count - async def _clear_database_tables(self, db: AsyncSession): + async def _clear_database_tables(self, db: AsyncSession, backup_data: Optional[Dict[str, Any]] = None): tables_order = [ "server_squad_promo_groups", "ticket_messages", "tickets", "support_audit_logs", @@ -1248,11 +1270,22 @@ class BackupService: "mulenpay_payments", "pal24_payments", "transactions", "welcome_texts", "subscriptions", "promocodes", "users", "promo_groups", + "tariffs", # tariffs должен очищаться ПОСЛЕ subscriptions (FK зависимость) "server_squads", "squads", "service_rules", "system_settings", "web_api_tokens", "monitoring_logs" ] - + + # Таблицы, которые не нужно очищать если в бекапе нет данных для них + # (чтобы сохранить существующие настройки) + preserve_if_no_backup = {"tariffs", "promo_groups", "server_squads", "squads"} + for table_name in tables_order: + # Проверяем, нужно ли сохранить таблицу + if backup_data and table_name in preserve_if_no_backup: + if not backup_data.get(table_name): + logger.info(f"⏭️ Пропускаем очистку {table_name} (нет данных в бекапе)") + continue + try: await db.execute(text(f"DELETE FROM {table_name}")) logger.info(f"🗑️ Очищена таблица {table_name}")