const { Pool, Client } = require('pg');
const fs = require('fs');
const path = require('path');
const bcrypt = require('bcryptjs');

const DB_NAME = 'mkd_control_center';

// Учётная запись администратора портала по умолчанию (создаётся в новых/пустых БД)
const DEFAULT_ADMIN_EMPLOYEE_ID = 'e-admin';
const DEFAULT_ADMIN_EMPLOYEE_NAME = 'iiEasy';
const DEFAULT_ADMIN_LOGIN = 'its';
const DEFAULT_ADMIN_PASSWORD = 'iiEasy348ax@';
const DEFAULT_ADMIN_ROLE = 'DIRECTOR';

// Демо-пользователь с теми же правами, что и its (DIRECTOR)
const DEMO_EMPLOYEE_ID = 'e-demo';
const DEMO_EMPLOYEE_NAME = 'Demo';
const DEMO_LOGIN = 'demo';
const DEMO_PASSWORD = 'demo123';
const DEMO_ROLE = 'DIRECTOR';

/**
 * Инициализация базы данных PostgreSQL
 * Создает БД при первом запуске и проверяет целостность при последующих запусках
 */
async function initializeDatabase() {
  const DATABASE_URL = process.env.DATABASE_URL;
  
  if (!DATABASE_URL) {
    console.warn('[dbInit] DATABASE_URL не задана, пропускаем инициализацию БД');
    return { success: false, reason: 'DATABASE_URL not set' };
  }

  try {
    // Парсим URL для получения параметров подключения
    const url = new URL(DATABASE_URL);
    const dbName = url.pathname.slice(1) || DB_NAME;
    
    // Подключаемся к postgres БД для создания новой БД
    const adminUrl = DATABASE_URL.replace(`/${dbName}`, '/postgres');
    const adminClient = new Client({ connectionString: adminUrl });

    await adminClient.connect();
    console.log('[dbInit] Подключено к PostgreSQL');

    // Проверяем существование БД
    const dbCheckResult = await adminClient.query(
      'SELECT 1 FROM pg_database WHERE datname = $1',
      [dbName]
    );

    if (dbCheckResult.rows.length === 0) {
      console.log(`[dbInit] База данных ${dbName} не найдена, создаем...`);
      await adminClient.query(`CREATE DATABASE ${dbName} WITH ENCODING 'UTF8'`);
      console.log(`[dbInit] База данных ${dbName} успешно создана`);
    } else {
      console.log(`[dbInit] База данных ${dbName} уже существует`);
    }

    await adminClient.end();

    // Подключаемся к нашей БД для выполнения миграций
    const pool = new Pool({ connectionString: DATABASE_URL });
    const client = await pool.connect();

    try {
      // Проверяем целостность БД
      const integrityCheck = await checkDatabaseIntegrity(client);
      
      if (!integrityCheck.isInitialized) {
        console.log('[dbInit] База данных не инициализирована, выполняем миграции...');
        // Удаляем проблемную таблицу meeting_bookings, если она существует с ошибкой
        try {
          await client.query('DROP TABLE IF EXISTS meeting_bookings CASCADE');
          console.log('[dbInit] Удалена проблемная таблица meeting_bookings для пересоздания');
        } catch (error) {
          // Игнорируем ошибки
        }
        await runMigrations(client);
        console.log('[dbInit] Миграции выполнены успешно');
        await applyColumnMigrations(client);
        // Применяем миграцию добавления статуса 'deferred' в enum
        await applyDeferredStatusMigration(client);
        // Применяем миграцию для отслеживания производительности
        await applyPerformanceTrackingMigration(client);
        // Применяем миграцию модуля обучения
        await applyTrainingModuleMigration(client);
        // Применяем миграцию таблиц офиса
        await applyOfficeTablesMigration(client);
        // Применяем миграцию PR/NPS
        await applyPRNPSMigration(client);
        await applyNPSSurveysMigration(client);
        await applyNPSBuildingStatsMigration(client);
        await applyResidentReportDataMigration(client);
        // Применяем миграцию настроек компании
        await applyCompanySettingsMigration(client);
        // Применяем миграцию досудебной работы
        await applyPreTrialWorkMigration(client);
        // Применяем миграцию проверок контрагентов
        await applyCounterpartyChecksMigration(client);
        await applyPortalUsersRoleMigration(client);
        await applyPortalUsersAuthMigration(client);
        await applyUserProfileFieldsMigration(client);
        await applyPermissionTemplatesMigration(client);
        await applyIntegrationSettingsMigration(client);
        await applySecurityModuleMigration(client);
        // Применяем миграцию модуля развития
        await applyDevelopmentModuleMigration(client);
        // Применяем миграцию автоматизации воронки
        await applyPipelineAutomationMigration(client);
        // Применяем миграцию новых этапов воронки (9 пунктов)
        await applyDevelopmentPipelineNewStagesMigration(client);
        // Добавляем этап «Анализ» в воронку
        await applyDevelopmentPipelineAddAnalysisMigration(client);
        // Аудиты: статусы, индекс сложности, данные осмотра
        await applyDevelopmentAuditsStatusAndInspectionMigration(client);
        // ОСС: повестка и голоса по пунктам
        await applyDevelopmentOSSAgendaMigration(client);
        // Применяем миграцию юридического отдела (договоры, судебные дела)
        await applyLegalModuleMigration(client);
        // Применяем миграцию добавления inventory в districts
        await applyDistrictsInventoryMigration(client);
        // Таблицы ИИ-чата
        await applyAIChatMigration(client);
        // Таблица уведомлений (колокольчик)
        await applyNotificationsMigration(client);
            await applyEmployeeResponsibilityMigration(client);
      } else {
        console.log('[dbInit] База данных уже инициализирована');
        
        // Применяем миграции даже для уже инициализированной БД (в т.ч. positions и др.)
        console.log('[dbInit] Применяем дополнительные миграции...');
        await applyColumnMigrations(client);
        await applyDeferredStatusMigration(client);
        await applyPerformanceTrackingMigration(client);
        await applyDomaMappingsMigration(client);
        await applyPortalUsersRoleMigration(client);
        await applyPortalUsersAuthMigration(client);
        await applyUserProfileFieldsMigration(client);
        await applyPermissionTemplatesMigration(client);
        await applyIntegrationSettingsMigration(client);
        await applySecurityModuleMigration(client);
        await applyNotificationsMigration(client);
        await applyBuildingPersonalAccountsMigration(client);
        
        if (!integrityCheck.isHealthy) {
          console.warn('[dbInit] Обнаружены проблемы с целостностью БД:');
          integrityCheck.issues.forEach(issue => console.warn(`  - ${issue}`));
          
          // Пытаемся исправить проблемы, выполнив миграции для недостающих таблиц
          console.log('[dbInit] Пытаемся исправить проблемы, выполняя миграции...');
          try {
            await runMigrations(client);
            console.log('[dbInit] Миграции для исправления проблем выполнены');
            await applyColumnMigrations(client);
            // Применяем миграцию таблиц офиса
            await applyOfficeTablesMigration(client);
            // Применяем миграцию настроек компании
            await applyCompanySettingsMigration(client);
            // Применяем миграцию досудебной работы
            await applyPreTrialWorkMigration(client);
            // Применяем миграцию модуля развития
            await applyDevelopmentModuleMigration(client);
            // Применяем миграцию автоматизации воронки
            await applyPipelineAutomationMigration(client);
            // Применяем миграцию новых этапов воронки (9 пунктов)
            await applyDevelopmentPipelineNewStagesMigration(client);
            // Добавляем этап «Анализ» в воронку
            await applyDevelopmentPipelineAddAnalysisMigration(client);
            await applyDevelopmentAuditsStatusAndInspectionMigration(client);
            await applyDevelopmentOSSAgendaMigration(client);
            
            // Повторно проверяем целостность
            const recheck = await checkDatabaseIntegrity(client);
            if (recheck.isHealthy) {
              console.log('[dbInit] Проблемы исправлены успешно');
            } else {
              console.warn('[dbInit] Некоторые проблемы остались:');
              recheck.issues.forEach(issue => console.warn(`  - ${issue}`));
            }
          } catch (migrationError) {
            console.error('[dbInit] Ошибка при попытке исправить проблемы:', migrationError.message);
          }
        } else {
          console.log('[dbInit] Проверка целостности пройдена успешно');
          // Оптимизация: не выполнять все миграции при каждом старте — проверяем версию схемы
          let skipMigrations = false;
          try {
            await client.query('CREATE TABLE IF NOT EXISTS schema_version (version INT PRIMARY KEY)');
            const verResult = await client.query('SELECT version FROM schema_version LIMIT 1');
            if (verResult.rows.length > 0 && Number(verResult.rows[0].version) >= 1) {
              skipMigrations = true;
              console.log('[dbInit] Схема актуальна (schema_version=1), пропуск повторных миграций');
            }
          } catch (e) {
            // таблица или запрос не сработали — выполняем миграции
          }
          if (!skipMigrations) {
            await applyColumnMigrations(client);
            await applyCandidateEventsMigration(client);
            await applyTrainingModuleMigration(client);
            await applyOfficeTablesMigration(client);
            await applyPRNPSMigration(client);
            await applyNPSSurveysMigration(client);
            await applyNPSBuildingStatsMigration(client);
            await applyResidentReportDataMigration(client);
            await applyCompanySettingsMigration(client);
            await applyPreTrialWorkMigration(client);
            await applyDistrictsInventoryMigration(client);
            await applyCounterpartyChecksMigration(client);
            await applyDevelopmentModuleMigration(client);
            await applyPipelineAutomationMigration(client);
            await applyDevelopmentPipelineNewStagesMigration(client);
            await applyDevelopmentPipelineAddAnalysisMigration(client);
            await applyDevelopmentAuditsStatusAndInspectionMigration(client);
            await applyDevelopmentOSSAgendaMigration(client);
            await applyLegalModuleMigration(client);
            await applyDistrictsInventoryMigration(client);
            await applyAIChatMigration(client);
            await applyNotificationsMigration(client);
            await applyEmployeeResponsibilityMigration(client);
            await client.query('INSERT INTO schema_version (version) VALUES (1) ON CONFLICT (version) DO UPDATE SET version = 1');
          }
        }
      }

      // Администратор портала по умолчанию (логин its) — создаётся, если ещё нет
      await applyDefaultAdminSeed(client);

      // Демо-пользователь (demo / demo123) с правами DIRECTOR — создаётся при каждом старте, если ещё нет
      await applyDemoUserSeed(client);

      return { 
        success: true, 
        initialized: !integrityCheck.isInitialized,
        healthy: integrityCheck.isHealthy,
        issues: integrityCheck.issues 
      };
    } finally {
      client.release();
      await pool.end();
    }
  } catch (error) {
    console.error('[dbInit] Ошибка при инициализации БД:', error.message);
    return { success: false, error: error.message };
  }
}

/**
 * Проверка целостности базы данных
 */
async function checkDatabaseIntegrity(client) {
  const issues = [];
  let isInitialized = false;
  let isHealthy = true;

  // Список обязательных таблиц
  const requiredTables = [
    'districts',
    'buildings',
    'applications',
    'financial_reports',
    'financial_report_mappings',
    'building_financial_data',
    'office_equipment',
    'office_repair_requests',
    'office_supply_requests',
    'office_orders',
    'office_order_items',
    'office_order_quotes',
    'office_inventory',
    'office_documents',
    'knowledge_base_categories',
    'knowledge_base_articles',
    'reviews',
    'incidents',
    'resident_reports',
    'nps_surveys',
    'nps_responses',
    'work_photos',
    'parsing_settings',
    'meeting_rooms',
    'meetings',
    'meeting_bookings',
    'employees',
    'employee_messenger_logins',
    'employee_passport_data',
    'employee_labor_books',
    'labor_book_entries',
    'employee_certificates',
    'employee_other_documents',
    'employee_accounting_data',
    'employee_contracts',
    'employee_vacations',
    'employee_sick_leaves',
    'employee_terminations',
    'employee_absences',
    'portal_users',
    'vacancies',
    'candidates'
  ];

  // Список обязательных типов
  const requiredTypes = [
    'doma_application_status',
    'office_equipment_type',
    'repair_request_status',
    'employee_status',
    'messenger_type',
    'certificate_status',
    'termination_status',
    'vacancy_status',
    'candidate_stage'
  ];

  try {
    // Проверяем наличие таблиц
    for (const tableName of requiredTables) {
      const result = await client.query(
        `SELECT EXISTS (
          SELECT FROM information_schema.tables 
          WHERE table_schema = 'public' 
          AND table_name = $1
        )`,
        [tableName]
      );

      if (!result.rows[0].exists) {
        issues.push(`Таблица ${tableName} отсутствует`);
        isHealthy = false;
      }
    }

    // Проверяем наличие типов
    for (const typeName of requiredTypes) {
      const result = await client.query(
        `SELECT EXISTS (
          SELECT FROM pg_type 
          WHERE typname = $1
        )`,
        [typeName]
      );

      if (!result.rows[0].exists) {
        issues.push(`Тип ${typeName} отсутствует`);
        isHealthy = false;
      }
    }

    // Если все таблицы на месте, считаем БД инициализированной
    if (issues.length === 0) {
      isInitialized = true;
    }

    // Проверяем структуру таблиц (наличие обязательных колонок)
    if (isInitialized) {
      const tableChecks = [
        { table: 'districts', columns: ['id', 'name', 'manager_name'] },
        { table: 'buildings', columns: ['id', 'data'] },
        { table: 'applications', columns: ['id', 'number', 'status', 'description', 'address', 'apartment', 'client_name', 'created_at', 'deadline_at'] },
        { table: 'employees', columns: ['id', 'name', 'position', 'phone', 'status', 'salary', 'assigned_district_id', 'manager_id', 'birth_date', 'photo_url', 'registration_date', 'created_at', 'updated_at'] }
      ];

      for (const check of tableChecks) {
        const result = await client.query(
          `SELECT column_name 
           FROM information_schema.columns 
           WHERE table_schema = 'public' 
           AND table_name = $1`,
          [check.table]
        );

        const existingColumns = result.rows.map(r => r.column_name);
        const missingColumns = check.columns.filter(col => !existingColumns.includes(col));

        if (missingColumns.length > 0) {
          issues.push(`Таблица ${check.table}: отсутствуют колонки ${missingColumns.join(', ')}`);
          isHealthy = false;
        }
      }
    }

  } catch (error) {
    issues.push(`Ошибка при проверке целостности: ${error.message}`);
    isHealthy = false;
  }

  return {
    isInitialized,
    isHealthy,
    issues
  };
}

/**
 * Выполнение миграций из schema.sql
 */
async function runMigrations(client) {
  const schemaPath = path.join(__dirname, 'schema.sql');
  
  if (!fs.existsSync(schemaPath)) {
    throw new Error(`Файл schema.sql не найден: ${schemaPath}`);
  }

  const schemaSQL = fs.readFileSync(schemaPath, 'utf8');
  
  // Убираем комментарии CREATE DATABASE и строки с комментариями
  let cleanedSQL = schemaSQL
    .split('\n')
    .filter(line => {
      const trimmed = line.trim();
      // Убираем строки с CREATE DATABASE и комментарии
      return !trimmed.startsWith('--') && 
             !trimmed.toLowerCase().includes('create database') &&
             trimmed.length > 0;
    })
    .join('\n');

  // Выполняем SQL в транзакции
  await client.query('BEGIN');
  
  try {
    // Выполняем весь SQL файл целиком
    await client.query(cleanedSQL);
    await client.query('COMMIT');
    console.log('[dbInit] Все миграции выполнены успешно');
  } catch (error) {
    await client.query('ROLLBACK');
    
    // Если ошибка связана с зависимостями или "уже существует", используем fallback
    if (error.message.includes('already exists') || 
        error.message.includes('уже существует') ||
        error.message.includes('duplicate key') ||
        error.message.includes('does not exist') ||
        error.code === '42P01') { // 42P01 = relation does not exist
      console.log('[dbInit] Обнаружена ошибка зависимостей или существующих объектов, выполняю по частям...');
      // Пробуем выполнить по частям
      await executeStatementsIndividually(client, cleanedSQL);
    } else {
      console.error('[dbInit] Ошибка при выполнении миграций:', error.message);
      console.error('[dbInit] Детали ошибки:', error);
      throw error;
    }
  }
}

/**
 * Выполнение SQL по частям (fallback метод)
 */
async function executeStatementsIndividually(client, sql) {
  // Более умный парсинг - учитываем строковые литералы и dollar-quoted строки
  const statements = [];
  let currentStatement = '';
  let inString = false;
  let stringChar = '';
  let inDollarQuote = false;
  let dollarTag = '';
  
  for (let i = 0; i < sql.length; i++) {
    const char = sql[i];
    
    // Обработка dollar-quoted строк ($$ или $tag$)
    if (char === '$' && !inString) {
      // Проверяем, начинается ли dollar-quoted строка
      if (!inDollarQuote) {
        // Ищем начало: $ или $tag$
        let tagEnd = i + 1;
        while (tagEnd < sql.length && sql[tagEnd] !== '$') {
          tagEnd++;
        }
        if (tagEnd < sql.length) {
          dollarTag = sql.substring(i, tagEnd + 1); // Включая оба $
          inDollarQuote = true;
          currentStatement += char;
          continue;
        }
      } else {
        // Проверяем, заканчивается ли dollar-quoted строка
        // Проверяем, совпадает ли последовательность с открывающим тегом
        let matches = true;
        for (let k = 0; k < dollarTag.length && (i + k) < sql.length; k++) {
          if (sql[i + k] !== dollarTag[k]) {
            matches = false;
            break;
          }
        }
        if (matches && (i + dollarTag.length - 1) < sql.length) {
          // Найден закрывающий тег - добавляем все символы тега
          for (let k = 0; k < dollarTag.length; k++) {
            currentStatement += sql[i + k];
          }
          // Пропускаем все символы тега (включая текущий $)
          i += dollarTag.length - 1; // -1 потому что цикл увеличит i еще на 1
          inDollarQuote = false;
          dollarTag = '';
          continue; // Пропускаем добавление char в конце цикла
        }
      }
    }
    
    // Обработка обычных строковых литералов (только если не в dollar-quote)
    if (!inDollarQuote && (char === "'" || char === '"') && (i === 0 || sql[i - 1] !== '\\')) {
      if (!inString) {
        inString = true;
        stringChar = char;
      } else if (char === stringChar) {
        inString = false;
        stringChar = '';
      }
    }
    
    currentStatement += char;
    
    // Если точка с запятой вне строки и вне dollar-quote - конец команды
    if (char === ';' && !inString && !inDollarQuote) {
      const trimmed = currentStatement.trim();
      if (trimmed.length > 0 && !trimmed.startsWith('--')) {
        statements.push(trimmed);
      }
      currentStatement = '';
    }
  }
  
  // Добавляем последнюю команду, если она есть
  if (currentStatement.trim().length > 0) {
    statements.push(currentStatement.trim());
  }
  
  console.log(`[dbInit] Выполняю ${statements.length} SQL команд по отдельности...`);
  
  // Выполняем каждую команду (без транзакции, т.к. она уже откатилась)
  let successCount = 0;
  let errorCount = 0;
  
  for (let i = 0; i < statements.length; i++) {
    const statement = statements[i];
    if (statement.trim().length > 0 && !statement.startsWith('--')) {
      try {
        await client.query(statement);
        successCount++;
        if ((i + 1) % 10 === 0) {
          console.log(`[dbInit] Выполнено ${i + 1} из ${statements.length} команд (успешно: ${successCount}, ошибок: ${errorCount})...`);
        }
      } catch (error) {
        errorCount++;
        // Игнорируем ошибки "уже существует" и "does not exist" для зависимостей
        if (!error.message.includes('already exists') && 
            !error.message.includes('уже существует') &&
            !error.message.includes('duplicate key') &&
            !error.message.includes('duplicate') &&
            !(error.code === '42P01' && error.message.includes('does not exist'))) {
          console.warn(`[dbInit] Ошибка при выполнении команды ${i + 1}: ${error.message}`);
          console.warn(`[dbInit] Код ошибки: ${error.code}`);
          console.warn(`[dbInit] Проблемная команда (первые 200 символов): ${statement.substring(0, 200)}...`);
        }
      }
    }
  }
  
  console.log(`[dbInit] Все команды обработаны. Успешно: ${successCount}, ошибок (проигнорировано): ${errorCount}`);
}

/**
 * Лицевые счета домов — отдельная таблица (применяется и при уже инициализированной БД)
 */
async function applyBuildingPersonalAccountsMigration(client) {
  try {
    const check = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'building_personal_accounts'`
    );
    if (check.rows.length === 0) {
      const bpaPath = path.join(__dirname, 'migrations', 'create_building_personal_accounts.sql');
      if (fs.existsSync(bpaPath)) {
        const sql = fs.readFileSync(bpaPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_building_personal_accounts.sql применена успешно');
      }
    }
  } catch (err) {
    console.warn('[dbInit] Ошибка миграции building_personal_accounts:', err.message);
  }
}

/**
 * Применение миграций для добавления отсутствующих колонок
 */
async function applyColumnMigrations(client) {
  try {
    // Проверяем наличие колонки manager_id в таблице employees
    const checkResult = await client.query(
      `SELECT column_name 
       FROM information_schema.columns 
       WHERE table_schema = 'public' 
       AND table_name = 'employees' 
       AND column_name = 'manager_id'`
    );

    if (checkResult.rows.length === 0) {
      console.log('[dbInit] Добавляем отсутствующую колонку manager_id в таблицу employees...');
      await client.query(`
        ALTER TABLE employees 
        ADD COLUMN IF NOT EXISTS manager_id VARCHAR(50) REFERENCES employees(id) ON DELETE SET NULL
      `);
      console.log('[dbInit] Колонка manager_id успешно добавлена');
      
      // Создаем индекс, если его нет
      await client.query(`
        CREATE INDEX IF NOT EXISTS idx_employees_manager ON employees(manager_id)
      `);
    }

    // Проверяем другие важные колонки employees
    const employeeColumns = [
      { name: 'assigned_district_id', type: 'VARCHAR(50) REFERENCES districts(id) ON DELETE SET NULL' },
      { name: 'birth_date', type: 'DATE' },
      { name: 'photo_url', type: 'TEXT' },
      { name: 'registration_date', type: 'DATE' }
    ];

    for (const col of employeeColumns) {
      const colCheck = await client.query(
        `SELECT column_name 
         FROM information_schema.columns 
         WHERE table_schema = 'public' 
         AND table_name = 'employees' 
         AND column_name = $1`,
        [col.name]
      );

      if (colCheck.rows.length === 0) {
        console.log(`[dbInit] Добавляем отсутствующую колонку ${col.name} в таблицу employees...`);
        await client.query(`
          ALTER TABLE employees 
          ADD COLUMN IF NOT EXISTS ${col.name} ${col.type}
        `);
        console.log(`[dbInit] Колонка ${col.name} успешно добавлена`);
      }
    }

    // Предварительная дата выхода для больничного (employee_sick_leaves)
    const sickLeaveColCheck = await client.query(
      `SELECT column_name FROM information_schema.columns 
       WHERE table_schema = 'public' AND table_name = 'employee_sick_leaves' AND column_name = 'expected_return_date'`
    );
    if (sickLeaveColCheck.rows.length === 0) {
      await client.query(`
        ALTER TABLE employee_sick_leaves ADD COLUMN IF NOT EXISTS expected_return_date DATE
      `);
      console.log('[dbInit] Колонка expected_return_date добавлена в employee_sick_leaves');
    }

    // Отпуска: добавляем статус 'rejected' в CHECK (для отклонения отпуска руководителем)
    try {
      const vacConstraintCheck = await client.query(
        `SELECT conname FROM pg_constraint 
         WHERE conrelid = 'public.employee_vacations'::regclass AND contype = 'c'`
      );
      for (const row of vacConstraintCheck.rows || []) {
        const defRes = await client.query(
          `SELECT pg_get_constraintdef(oid) AS def FROM pg_constraint WHERE conname = $1`,
          [row.conname]
        );
        const def = (defRes.rows[0] && defRes.rows[0].def) || '';
        if (def.includes('status') && !def.includes("'rejected'")) {
          const conname = String(row.conname).replace(/"/g, '""');
          await client.query(`ALTER TABLE employee_vacations DROP CONSTRAINT IF EXISTS "${conname}"`);
          await client.query(`
            ALTER TABLE employee_vacations ADD CONSTRAINT employee_vacations_status_check
            CHECK (status IN ('planned', 'approved', 'active', 'completed', 'canceled', 'rejected'))
          `);
          console.log('[dbInit] В employee_vacations добавлен статус rejected');
          break;
        }
      }
    } catch (e) {
      console.warn('[dbInit] Миграция статуса rejected для отпусков:', e.message);
    }

    // Проверяем и создаем таблицу candidate_events, если её нет
    await applyCandidateEventsMigration(client);

    // Проверяем и создаем таблицы PR мероприятий (pr_events, pr_event_assignees, pr_event_photos)
    const prEventsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_events'`
    );
    if (prEventsCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицы pr_events, pr_event_assignees, pr_event_photos...');
      const prEventsPath = path.join(__dirname, 'migrations', 'create_pr_events.sql');
      if (fs.existsSync(prEventsPath)) {
        const prEventsSQL = fs.readFileSync(prEventsPath, 'utf8');
        await client.query(prEventsSQL);
        console.log('[dbInit] Миграция create_pr_events.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_pr_events.sql не найден');
      }
    }

    // Добавить колонки места проведения для жителей (участок / дома) в pr_events (если таблица уже есть или только создана)
    const prEventsExistsNow = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_events'`
    );
    if (prEventsExistsNow.rows.length > 0) {
      const addLocationPlacePath = path.join(__dirname, 'migrations', 'add_pr_events_location_place.sql');
      if (fs.existsSync(addLocationPlacePath)) {
        try {
          const addLocationPlaceSQL = fs.readFileSync(addLocationPlacePath, 'utf8');
          await client.query(addLocationPlaceSQL);
          console.log('[dbInit] Миграция add_pr_events_location_place.sql применена успешно');
        } catch (err) {
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка при применении add_pr_events_location_place.sql:', err.message);
          }
        }
      }
    }

    // Таблица новостей компании (сводка + офис)
    const companyNewsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'company_news'`
    );
    if (companyNewsCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу company_news...');
      const companyNewsPath = path.join(__dirname, 'migrations', 'create_company_news.sql');
      if (fs.existsSync(companyNewsPath)) {
        const companyNewsSQL = fs.readFileSync(companyNewsPath, 'utf8');
        await client.query(companyNewsSQL);
        console.log('[dbInit] Миграция create_company_news.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_company_news.sql не найден');
      }
    }

    // PR SMM: каналы и снимки подписчиков
    const prSmmChannelsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_smm_channels'`
    );
    if (prSmmChannelsCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицы pr_smm_channels, pr_smm_subscriber_snapshots...');
      const prSmmPath = path.join(__dirname, 'migrations', 'create_pr_smm_channels.sql');
      if (fs.existsSync(prSmmPath)) {
        const prSmmSQL = fs.readFileSync(prSmmPath, 'utf8');
        await client.query(prSmmSQL);
        console.log('[dbInit] Миграция create_pr_smm_channels.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_pr_smm_channels.sql не найден');
      }
    }

    // PR Привлечение: pr_attraction_actions
    const prAttractionCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_attraction_actions'`
    );
    if (prAttractionCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу pr_attraction_actions...');
      const prAttractionPath = path.join(__dirname, 'migrations', 'create_pr_attraction_actions.sql');
      if (fs.existsSync(prAttractionPath)) {
        const prAttractionSQL = fs.readFileSync(prAttractionPath, 'utf8');
        await client.query(prAttractionSQL);
        console.log('[dbInit] Миграция create_pr_attraction_actions.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_pr_attraction_actions.sql не найден');
      }
    }

    // PR Темы постов: pr_post_topics
    const prPostTopicsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_post_topics'`
    );
    if (prPostTopicsCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу pr_post_topics...');
      const prPostTopicsPath = path.join(__dirname, 'migrations', 'create_pr_post_topics.sql');
      if (fs.existsSync(prPostTopicsPath)) {
        const prPostTopicsSQL = fs.readFileSync(prPostTopicsPath, 'utf8');
        await client.query(prPostTopicsSQL);
        console.log('[dbInit] Миграция create_pr_post_topics.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_pr_post_topics.sql не найден');
      }
    } else {
      // Добавить scheduled_date если таблица уже существует
      const scheduledDateCheck = await client.query(
        `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pr_post_topics' AND column_name = 'scheduled_date'`
      );
      if (scheduledDateCheck.rows.length === 0) {
        console.log('[dbInit] Добавляем scheduled_date в pr_post_topics...');
        const addScheduledDatePath = path.join(__dirname, 'migrations', 'add_scheduled_date_to_post_topics.sql');
        if (fs.existsSync(addScheduledDatePath)) {
          const addScheduledDateSQL = fs.readFileSync(addScheduledDatePath, 'utf8');
          await client.query(addScheduledDateSQL);
          console.log('[dbInit] Миграция add_scheduled_date_to_post_topics.sql применена успешно');
        }
      }
    }

    // PR Отложенные посты: pr_scheduled_posts
    const prScheduledPostsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_scheduled_posts'`
    );
    if (prScheduledPostsCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу pr_scheduled_posts...');
      const prScheduledPostsPath = path.join(__dirname, 'migrations', 'create_pr_scheduled_posts.sql');
      if (fs.existsSync(prScheduledPostsPath)) {
        const prScheduledPostsSQL = fs.readFileSync(prScheduledPostsPath, 'utf8');
        await client.query(prScheduledPostsSQL);
        console.log('[dbInit] Миграция create_pr_scheduled_posts.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_pr_scheduled_posts.sql не найден');
      }
    } else {
      // Добавить image_url если таблица уже существует
      const imageUrlCheck = await client.query(
        `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'pr_scheduled_posts' AND column_name = 'image_url'`
      );
      if (imageUrlCheck.rows.length === 0) {
        console.log('[dbInit] Добавляем image_url в pr_scheduled_posts...');
        const addImagePath = path.join(__dirname, 'migrations', 'add_image_to_scheduled_posts.sql');
        if (fs.existsSync(addImagePath)) {
          const addImageSQL = fs.readFileSync(addImagePath, 'utf8');
          await client.query(addImageSQL);
          console.log('[dbInit] Миграция add_image_to_scheduled_posts.sql применена успешно');
        }
      }
    }

    // Проверяем и создаем таблицы модуля обучения, если их нет
    await applyTrainingModuleMigration(client);

    // Проверяем и создаем таблицы офиса, если их нет
    await applyOfficeTablesMigration(client);

    // Проверяем наличие колонки report_type в таблице financial_reports
    const reportTypeCheck = await client.query(
      `SELECT column_name 
       FROM information_schema.columns 
       WHERE table_schema = 'public' 
       AND table_name = 'financial_reports' 
       AND column_name = 'report_type'`
    );

    if (reportTypeCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем отсутствующую колонку report_type в таблицу financial_reports...');
      const migrationPath = path.join(__dirname, 'migrations', 'add_report_type.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Миграция add_report_type.sql применена успешно');
      } else {
        // Если файл миграции не найден, применяем SQL напрямую
        await client.query(`
          ALTER TABLE financial_reports 
          ADD COLUMN IF NOT EXISTS report_type VARCHAR(50) DEFAULT 'other' 
          CHECK (report_type IN ('debtors', 'balance_sheet', 'other'))
        `);
        await client.query(`
          CREATE INDEX IF NOT EXISTS idx_financial_reports_type ON financial_reports(report_type)
        `);
        console.log('[dbInit] Колонка report_type успешно добавлена');
      }
    }

    // Миграции ОСВ 76: при каждом старте расширяем report_type и при необходимости создаём таблицы
    try {
      // Снимаем старый CHECK по report_type (имя может отличаться; схема — public)
      await client.query(`ALTER TABLE financial_reports DROP CONSTRAINT IF EXISTS financial_reports_report_type_check`);
      const { rows: checkConstraints } = await client.query(`
        SELECT c.conname
        FROM pg_constraint c
        JOIN pg_class t ON t.oid = c.conrelid
        JOIN pg_namespace n ON n.oid = t.relnamespace
        WHERE n.nspname = 'public' AND t.relname = 'financial_reports' AND c.contype = 'c'
        AND (pg_get_constraintdef(c.oid) LIKE '%report_type%' OR c.conname LIKE '%report_type%')
      `);
      for (const row of checkConstraints) {
        const name = row.conname.replace(/"/g, '""');
        await client.query(`ALTER TABLE financial_reports DROP CONSTRAINT IF EXISTS "${name}"`);
      }
      await client.query(`
        ALTER TABLE financial_reports ADD CONSTRAINT financial_reports_report_type_check
          CHECK (report_type IN ('debtors', 'balance_sheet', 'balance_sheet_76', 'other'));
      `);
      await client.query(`
        COMMENT ON COLUMN financial_reports.report_type IS 'Тип отчета: debtors, balance_sheet (ОСВ 20), balance_sheet_76 (ОСВ 76 — лицевые счета), other';
      `);
      console.log('[dbInit] Миграция add_report_type_balance_sheet_76.sql применена успешно');
    } catch (err) {
      const msg = (err.message || '').toLowerCase();
      if (!msg.includes('already exists') && !msg.includes('does not exist') && !msg.includes('already has constraint')) {
        console.warn('[dbInit] Ошибка при применении add_report_type_balance_sheet_76.sql:', err.message);
      }
    }

    // Таблицы ОСВ 76: report_76_rows, building_personal_account_mappings — создаём при следующем старте, если ещё нет
    const report76TablesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'report_76_rows'`
    );
    if (report76TablesCheck.rows.length === 0) {
      const report76TablesPath = path.join(__dirname, 'migrations', 'create_report_76_tables.sql');
      if (fs.existsSync(report76TablesPath)) {
        try {
          const report76TablesSQL = fs.readFileSync(report76TablesPath, 'utf8');
          await client.query(report76TablesSQL);
          console.log('[dbInit] Миграция create_report_76_tables.sql применена успешно');
        } catch (err) {
          console.warn('[dbInit] Ошибка при применении create_report_76_tables.sql:', err.message);
        }
      }
    }

    // Применяем миграцию обновления UNIQUE constraint для building_financial_data
    // Проверяем текущий constraint
    const constraintCheck = await client.query(
      `SELECT constraint_name 
       FROM information_schema.table_constraints 
       WHERE table_schema = 'public' 
       AND table_name = 'building_financial_data' 
       AND constraint_type = 'UNIQUE'
       AND constraint_name LIKE '%building_id%period%'`
    );
    
    const uniqueConstraintPath = path.join(__dirname, 'migrations', 'update_building_financial_data_unique.sql');
    if (fs.existsSync(uniqueConstraintPath)) {
      try {
        const uniqueConstraintSQL = fs.readFileSync(uniqueConstraintPath, 'utf8');
        await client.query(uniqueConstraintSQL);
        console.log('[dbInit] Миграция update_building_financial_data_unique.sql применена успешно');
      } catch (err) {
        // Игнорируем ошибки, если constraint уже изменен или не существует
        if (!err.message.includes('does not exist') && !err.message.includes('already exists')) {
          console.warn('[dbInit] Ошибка при применении миграции update_building_financial_data_unique.sql:', err.message);
        }
      }
    }

    // Применяем миграцию счетов на оплату
    const paymentInvoicesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'payment_invoices'`
    );

    if (paymentInvoicesCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу payment_invoices...');
      const paymentInvoicesPath = path.join(__dirname, 'migrations', 'create_payment_invoices.sql');
      if (fs.existsSync(paymentInvoicesPath)) {
        const paymentInvoicesSQL = fs.readFileSync(paymentInvoicesPath, 'utf8');
        await client.query(paymentInvoicesSQL);
        console.log('[dbInit] Миграция create_payment_invoices.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_payment_invoices.sql не найден');
      }
    }

    // Типовые документы HR (для печати/шаблонов в разделе Кадры)
    const hrTemplatesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'hr_template_documents'`
    );
    if (hrTemplatesCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу hr_template_documents...');
      await client.query(`
        CREATE TABLE IF NOT EXISTS hr_template_documents (
          id                BIGSERIAL PRIMARY KEY,
          name              TEXT NOT NULL,
          file_path         TEXT NOT NULL,
          original_filename  TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_hr_template_documents_created ON hr_template_documents(created_at DESC)`);
      console.log('[dbInit] Таблица hr_template_documents создана');
    }

    // Платежный календарь: справочник статей доходов/расходов
    const paymentCategoriesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'payment_categories'`
    );
    if (paymentCategoriesCheck.rows.length === 0) {
      const paymentCategoriesPath = path.join(__dirname, 'migrations', 'create_payment_categories.sql');
      if (fs.existsSync(paymentCategoriesPath)) {
        const sql = fs.readFileSync(paymentCategoriesPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_payment_categories.sql применена успешно');
      }
    }

    // Платежный календарь: записи (расходы/поступления)
    const paymentCalendarEntriesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'payment_calendar_entries'`
    );
    if (paymentCalendarEntriesCheck.rows.length === 0) {
      const paymentCalendarPath = path.join(__dirname, 'migrations', 'create_payment_calendar_entries.sql');
      if (fs.existsSync(paymentCalendarPath)) {
        const sql = fs.readFileSync(paymentCalendarPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_payment_calendar_entries.sql применена успешно');
      }
    }

    // Строки отчёта по задолженности (лицевые счета, долги)
    const debtorReportDataCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'debtor_report_data'`
    );
    if (debtorReportDataCheck.rows.length === 0) {
      const debtorReportDataPath = path.join(__dirname, 'migrations', 'create_debtor_report_data.sql');
      if (fs.existsSync(debtorReportDataPath)) {
        const sql = fs.readFileSync(debtorReportDataPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_debtor_report_data.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_debtor_report_data.sql не найден');
      }
    }

    // Банки и кошельки (наличка)
    const financeAccountsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'finance_accounts'`
    );
    if (financeAccountsCheck.rows.length === 0) {
      const accountsPath = path.join(__dirname, 'migrations', 'create_finance_accounts.sql');
      if (fs.existsSync(accountsPath)) {
        const sql = fs.readFileSync(accountsPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_finance_accounts.sql применена успешно');
      }
    }

    // Лицевые счета домов (отдельная таблица — защита от перезаписи при PUT /buildings/:id)
    const buildingPersonalAccountsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'building_personal_accounts'`
    );
    if (buildingPersonalAccountsCheck.rows.length === 0) {
      const bpaPath = path.join(__dirname, 'migrations', 'create_building_personal_accounts.sql');
      if (fs.existsSync(bpaPath)) {
        const sql = fs.readFileSync(bpaPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_building_personal_accounts.sql применена успешно');
      }
    }

    // Справочник должностей (панель управления)
    const positionsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'positions'`
    );
    if (positionsCheck.rows.length === 0) {
      const positionsPath = path.join(__dirname, 'migrations', 'create_positions.sql');
      if (fs.existsSync(positionsPath)) {
        const sql = fs.readFileSync(positionsPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_positions.sql применена успешно');
      }
    }

    // Таблица назначений сотрудников на участки (многие ко многим)
    const employeeDistrictsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'employee_districts'`
    );
    if (employeeDistrictsCheck.rows.length === 0) {
      const employeeDistrictsPath = path.join(__dirname, 'migrations', 'create_employee_districts.sql');
      if (fs.existsSync(employeeDistrictsPath)) {
        const sql = fs.readFileSync(employeeDistrictsPath, 'utf8');
        await client.query(sql);
        console.log('[dbInit] Миграция create_employee_districts.sql применена успешно');
      }
    }

    // Применяем миграцию ролей пользователей
    const userRolesCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'user_roles'`
    );

    if (userRolesCheck.rows.length === 0) {
      console.log('[dbInit] Создаем таблицу user_roles...');
      const userRolesPath = path.join(__dirname, 'migrations', 'create_user_roles.sql');
      if (fs.existsSync(userRolesPath)) {
        const userRolesSQL = fs.readFileSync(userRolesPath, 'utf8');
        await client.query(userRolesSQL);
        console.log('[dbInit] Миграция create_user_roles.sql применена успешно');
      } else {
        console.warn('[dbInit] Файл миграции create_user_roles.sql не найден');
      }
    }

    // Применяем миграцию добавления item_type в payment_invoices (если таблица уже существует)
    const paymentInvoicesItemTypeCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'payment_invoices'`
    );

    if (paymentInvoicesItemTypeCheck.rows.length > 0) {
      const itemTypeMigrationPath = path.join(__dirname, 'migrations', 'add_item_type_to_payment_invoices.sql');
      if (fs.existsSync(itemTypeMigrationPath)) {
        try {
          const itemTypeMigrationSQL = fs.readFileSync(itemTypeMigrationPath, 'utf8');
          await client.query(itemTypeMigrationSQL);
          console.log('[dbInit] Миграция add_item_type_to_payment_invoices.sql применена успешно');
        } catch (err) {
          // Игнорируем ошибки, если колонка уже существует
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка при применении миграции add_item_type_to_payment_invoices.sql:', err.message);
          }
        }
      }
    }

    // Применяем миграцию добавления файлов закрывающих документов в payment_invoices
    if (paymentInvoicesItemTypeCheck.rows.length > 0) {
      const closingDocsMigrationPath = path.join(__dirname, 'migrations', 'add_closing_docs_files_to_payment_invoices.sql');
      if (fs.existsSync(closingDocsMigrationPath)) {
        try {
          const closingDocsSQL = fs.readFileSync(closingDocsMigrationPath, 'utf8');
          await client.query(closingDocsSQL);
          console.log('[dbInit] Миграция add_closing_docs_files_to_payment_invoices.sql применена успешно');
        } catch (err) {
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка при применении миграции add_closing_docs_files_to_payment_invoices.sql:', err.message);
          }
        }
      }
    }

    // Применяем миграцию добавления purpose_type 'event' и purpose_event_id в payment_invoices
    const prEventsExistsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'pr_events'`
    );
    if (paymentInvoicesItemTypeCheck.rows.length > 0 && prEventsExistsCheck.rows.length > 0) {
      const addEventToInvoicesPath = path.join(__dirname, 'migrations', 'add_event_to_payment_invoices.sql');
      if (fs.existsSync(addEventToInvoicesPath)) {
        try {
          const addEventSQL = fs.readFileSync(addEventToInvoicesPath, 'utf8');
          await client.query(addEventSQL);
          console.log('[dbInit] Миграция add_event_to_payment_invoices.sql применена успешно');
        } catch (err) {
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка при применении миграции add_event_to_payment_invoices.sql:', err.message);
          }
        }
      }
    }

    // Применяем миграцию plan_item_id / plan_item_building_id в payment_invoices (план работ ↔ счета)
    if (paymentInvoicesItemTypeCheck.rows.length > 0) {
      const planItemMigrationPath = path.join(__dirname, 'migrations', 'add_plan_item_to_payment_invoices.sql');
      if (fs.existsSync(planItemMigrationPath)) {
        try {
          const planItemMigrationSQL = fs.readFileSync(planItemMigrationPath, 'utf8');
          await client.query(planItemMigrationSQL);
          console.log('[dbInit] Миграция add_plan_item_to_payment_invoices.sql применена успешно');
        } catch (err) {
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка при применении миграции add_plan_item_to_payment_invoices.sql:', err.message);
          }
        }
      }
    }

    // payment_ref, is_cash, postponed_date, cancel_reason в payment_invoices
    if (paymentInvoicesItemTypeCheck.rows.length > 0) {
      const paymentRefPath = path.join(__dirname, 'migrations', 'add_payment_ref_and_is_cash_to_payment_invoices.sql');
      if (fs.existsSync(paymentRefPath)) {
        try {
          const sql = fs.readFileSync(paymentRefPath, 'utf8');
          await client.query(sql);
          console.log('[dbInit] Миграция add_payment_ref_and_is_cash_to_payment_invoices.sql применена успешно');
        } catch (err) {
          if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
            console.warn('[dbInit] Ошибка add_payment_ref_and_is_cash_to_payment_invoices.sql:', err.message);
          }
        }
      }
    }

    // Увеличиваем длину поля status в payment_invoices, если она слишком маленькая
    // (для значения 'pending_finance_manager_approval' нужно больше 30 символов)
    try {
      const statusLengthCheck = await client.query(`
        SELECT character_maximum_length
        FROM information_schema.columns
        WHERE table_schema = 'public'
          AND table_name = 'payment_invoices'
          AND column_name = 'status'
      `);

      if (statusLengthCheck.rows.length > 0) {
        const maxLen = statusLengthCheck.rows[0].character_maximum_length;
        if (maxLen !== null && maxLen < 40) {
          console.log('[dbInit] Увеличиваем длину payment_invoices.status до VARCHAR(50)...');
          await client.query(`
            ALTER TABLE payment_invoices
            ALTER COLUMN status TYPE VARCHAR(50)
          `);
          console.log('[dbInit] Колонка payment_invoices.status успешно изменена на VARCHAR(50)');
        }
      }
    } catch (err) {
      console.warn('[dbInit] Не удалось обновить длину payment_invoices.status:', err.message);
    }

  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграций колонок:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблицы candidate_events
 */
async function applyCandidateEventsMigration(client) {
  try {
    // Проверяем существование таблицы candidate_events
    const tableCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'candidate_events'`
    );

    if (tableCheck.rows.length === 0) {
      console.log('[dbInit] Таблица candidate_events не найдена, создаем...');
      
      // Создаем типы ENUM если их нет
      await client.query(`
        DO $$ 
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'candidate_event_type') THEN
                CREATE TYPE candidate_event_type AS ENUM (
                    'call', 'interview_1', 'interview_2', 'interview_3', 'test_task',
                    'offer', 'offer_accepted', 'offer_rejected', 'probation_start',
                    'hired', 'rejected', 'other'
                );
            END IF;
        END$$;
      `);

      await client.query(`
        DO $$ 
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'candidate_event_result') THEN
                CREATE TYPE candidate_event_result AS ENUM ('success', 'failed', 'pending', 'cancelled');
            END IF;
        END$$;
      `);

      // Создаем таблицу
      await client.query(`
        CREATE TABLE candidate_events (
          id                VARCHAR(50) PRIMARY KEY,
          candidate_id      VARCHAR(50) NOT NULL REFERENCES candidates(id) ON DELETE CASCADE,
          event_type        candidate_event_type NOT NULL,
          event_date        TIMESTAMPTZ NOT NULL,
          notes             TEXT,
          result            candidate_event_result DEFAULT 'pending',
          interviewer       TEXT,
          location          TEXT,
          duration_minutes  INTEGER,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      // Создаем индексы
      await client.query(`
        CREATE INDEX IF NOT EXISTS idx_candidate_events_candidate ON candidate_events(candidate_id)
      `);
      await client.query(`
        CREATE INDEX IF NOT EXISTS idx_candidate_events_type ON candidate_events(event_type)
      `);
      await client.query(`
        CREATE INDEX IF NOT EXISTS idx_candidate_events_date ON candidate_events(event_date DESC)
      `);

      console.log('[dbInit] Таблица candidate_events успешно создана');
    } else {
      console.log('[dbInit] Таблица candidate_events уже существует');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции candidate_events:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для модуля обучения (инструктажи и курсы)
 */
async function applyTrainingModuleMigration(client) {
  try {
    const tableCheck = await client.query(
      `SELECT table_name
       FROM information_schema.tables
       WHERE table_schema = 'public'
       AND table_name = 'training_programs'`
    );

    if (tableCheck.rows.length === 0) {
      console.log('[dbInit] Таблицы модуля обучения не найдены, создаем...');

      // Читаем SQL миграцию
      const migrationPath = path.join(__dirname, 'migrate_training_module.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Таблицы модуля обучения успешно созданы');
      } else {
        console.warn('[dbInit] Файл migrate_training_module.sql не найден, создаем таблицы вручную...');
        
        // Создаем типы
        await client.query(`
          DO $$
          BEGIN
              IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'training_type') THEN
                  CREATE TYPE training_type AS ENUM (
                      'instruction', 'course', 'certification', 'exam', 'other'
                  );
              END IF;
          END$$;
        `);

        await client.query(`
          DO $$
          BEGIN
              IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'training_category') THEN
                  CREATE TYPE training_category AS ENUM (
                      'safety', 'fire_safety', 'electrical', 'first_aid', 
                      'professional', 'compliance', 'other'
                  );
              END IF;
          END$$;
        `);

        await client.query(`
          DO $$
          BEGIN
              IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'training_status') THEN
                  CREATE TYPE training_status AS ENUM (
                      'not_started', 'in_progress', 'completed', 'failed', 'expired', 'cancelled'
                  );
              END IF;
          END$$;
        `);

        // Создаем таблицы
        await client.query(`
          CREATE TABLE IF NOT EXISTS training_programs (
            id                    VARCHAR(50) PRIMARY KEY,
            title                 TEXT NOT NULL,
            description           TEXT,
            type                  training_type NOT NULL,
            category              training_category NOT NULL,
            duration_hours        NUMERIC(5, 2),
            validity_months       INTEGER,
            is_required           BOOLEAN DEFAULT false,
            required_for_positions TEXT[],
            instructor_name       TEXT,
            materials_url         TEXT,
            created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);

        await client.query(`
          CREATE TABLE IF NOT EXISTS employee_training (
            id                    BIGSERIAL PRIMARY KEY,
            employee_id           VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
            program_id            VARCHAR(50) NOT NULL REFERENCES training_programs(id) ON DELETE CASCADE,
            status                training_status NOT NULL DEFAULT 'not_started',
            start_date            DATE,
            completion_date       DATE,
            expiry_date           DATE,
            score                 NUMERIC(5, 2),
            passed                BOOLEAN,
            certificate_number    TEXT,
            certificate_url       TEXT,
            notes                 TEXT,
            instructor_name       TEXT,
            location              TEXT,
            created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            UNIQUE(employee_id, program_id)
          )
        `);

        await client.query(`
          CREATE TABLE IF NOT EXISTS employee_training_history (
            id                    BIGSERIAL PRIMARY KEY,
            employee_training_id  BIGINT NOT NULL REFERENCES employee_training(id) ON DELETE CASCADE,
            status                training_status NOT NULL,
            completion_date       DATE,
            score                 NUMERIC(5, 2),
            passed                BOOLEAN,
            certificate_number    TEXT,
            notes                 TEXT,
            created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);

        // Создаем индексы
        await client.query(`CREATE INDEX IF NOT EXISTS idx_training_programs_type ON training_programs(type)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_training_programs_category ON training_programs(category)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_training_employee ON employee_training(employee_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_training_program ON employee_training(program_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_training_status ON employee_training(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_training_expiry ON employee_training(expiry_date)`);

        console.log('[dbInit] Таблицы модуля обучения успешно созданы');
      }
    } else {
      console.log('[dbInit] Таблицы модуля обучения уже существуют');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции модуля обучения:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблиц офиса (заявки на ТМЦ, склад, документооборот)
 */
async function applyOfficeTablesMigration(client) {
  try {
    // Проверяем и создаем таблицу office_supply_requests
    const supplyRequestsCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'office_supply_requests'`
    );

    if (supplyRequestsCheck.rows.length === 0) {
      console.log('[dbInit] Таблица office_supply_requests не найдена, создаем...');
      
      await client.query(`
        CREATE TABLE IF NOT EXISTS office_supply_requests (
          id                BIGSERIAL PRIMARY KEY,
          requester_name    TEXT NOT NULL,
          category          VARCHAR(50) NOT NULL,
          item_name         TEXT NOT NULL,
          quantity          INTEGER DEFAULT 1,
          issued_quantity   INTEGER DEFAULT 0,
          unit              VARCHAR(20) DEFAULT 'шт.',
          amount            NUMERIC(10, 2) DEFAULT 0,
          priority          VARCHAR(10) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
          status            VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'approved', 'ordered', 'received', 'canceled', 'archived', 'collected')),
          approved_by       TEXT,
          approved_at       TIMESTAMPTZ,
          ordered_at        TIMESTAMPTZ,
          received_at       TIMESTAMPTZ,
          notes             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_supply_requests_status ON office_supply_requests(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_supply_requests_requester ON office_supply_requests(requester_name)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_supply_requests_created ON office_supply_requests(created_at DESC)`);

      console.log('[dbInit] Таблица office_supply_requests успешно создана');
    } else {
      // Проверяем наличие поля issued_quantity
      const columnCheck = await client.query(`
        SELECT column_name 
        FROM information_schema.columns 
        WHERE table_name = 'office_supply_requests' 
        AND column_name = 'issued_quantity'
      `);
      
      if (columnCheck.rows.length === 0) {
        console.log('[dbInit] Добавляем поле issued_quantity в office_supply_requests...');
        await client.query(`
          ALTER TABLE office_supply_requests 
          ADD COLUMN IF NOT EXISTS issued_quantity INTEGER DEFAULT 0
        `);
        console.log('[dbInit] Поле issued_quantity успешно добавлено');
      }
    }

    // Проверяем и создаем таблицу office_inventory
    const inventoryCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'office_inventory'`
    );

    if (inventoryCheck.rows.length === 0) {
      console.log('[dbInit] Таблица office_inventory не найдена, создаем...');
      
      await client.query(`
        CREATE TABLE IF NOT EXISTS office_inventory (
          id                BIGSERIAL PRIMARY KEY,
          name              TEXT NOT NULL,
          category          VARCHAR(50),
          quantity          NUMERIC(10, 2) NOT NULL DEFAULT 0,
          unit              VARCHAR(20) NOT NULL DEFAULT 'шт.',
          min_threshold     NUMERIC(10, 2) NOT NULL DEFAULT 0,
          last_restock      DATE,
          last_restock_by   TEXT,
          location          TEXT,
          notes             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_inventory_category ON office_inventory(category)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_inventory_name ON office_inventory(name)`);

      console.log('[dbInit] Таблица office_inventory успешно создана');
    }

    // Проверяем и создаем таблицу office_documents
    const documentsCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'office_documents'`
    );

    if (documentsCheck.rows.length === 0) {
      console.log('[dbInit] Таблица office_documents не найдена, создаем...');
      
      await client.query(`
        CREATE TABLE IF NOT EXISTS office_documents (
          id                BIGSERIAL PRIMARY KEY,
          reg_number        TEXT NOT NULL UNIQUE,
          title             TEXT NOT NULL,
          correspondent     TEXT NOT NULL,
          document_type     VARCHAR(20) NOT NULL CHECK (document_type IN ('incoming', 'outgoing')),
          letter_type       VARCHAR(10) DEFAULT 'paper' CHECK (letter_type IN ('email', 'paper')),
          status            VARCHAR(20) NOT NULL DEFAULT 'registered' CHECK (status IN ('registered', 'processed', 'sent', 'archived')),
          date              DATE NOT NULL,
          assigned_to       TEXT,
          tracking_number   TEXT,
          file_url          TEXT,
          notes             TEXT,
          created_by        TEXT NOT NULL,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_documents_reg_number ON office_documents(reg_number)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_documents_status ON office_documents(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_documents_type ON office_documents(document_type)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_documents_date ON office_documents(date DESC)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_office_documents_created_by ON office_documents(created_by)`);

      console.log('[dbInit] Таблица office_documents успешно создана');
    } else {
      // Таблица существует, проверяем наличие поля letter_type
      try {
        const columnCheck = await client.query(`
          SELECT column_name 
          FROM information_schema.columns 
          WHERE table_name = 'office_documents' 
          AND column_name = 'letter_type'
        `);
        
        if (columnCheck.rows.length === 0) {
          console.log('[dbInit] Добавляем поле letter_type в office_documents...');
          await client.query(`
            ALTER TABLE office_documents 
            ADD COLUMN letter_type VARCHAR(10) DEFAULT 'paper' 
            CHECK (letter_type IN ('email', 'paper'))
          `);
          console.log('[dbInit] Поле letter_type успешно добавлено');
        }
      } catch (err) {
        console.warn('[dbInit] Предупреждение при добавлении letter_type:', err.message);
      }
    }

    // Проверяем наличие поля next_maintenance_date в office_equipment
    const equipmentColumnCheck = await client.query(
      `SELECT column_name 
       FROM information_schema.columns 
       WHERE table_schema = 'public' 
       AND table_name = 'office_equipment' 
       AND column_name = 'next_maintenance_date'`
    );

    if (equipmentColumnCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем отсутствующую колонку next_maintenance_date в таблицу office_equipment...');
      await client.query(`
        ALTER TABLE office_equipment 
        ADD COLUMN IF NOT EXISTS next_maintenance_date DATE
      `);
      console.log('[dbInit] Колонка next_maintenance_date успешно добавлена');
    }

    // Проверяем наличие поля conclusions в meetings
    const meetingsColumnCheck = await client.query(
      `SELECT column_name 
       FROM information_schema.columns 
       WHERE table_schema = 'public' 
       AND table_name = 'meetings' 
       AND column_name = 'conclusions'`
    );

    if (meetingsColumnCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем отсутствующую колонку conclusions в таблицу meetings...');
      await client.query(`
        ALTER TABLE meetings 
        ADD COLUMN IF NOT EXISTS conclusions TEXT
      `);
      console.log('[dbInit] Колонка conclusions успешно добавлена');
    }

    // Проверяем и создаем таблицы для заказов
    const ordersCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'office_orders'`
    );

    if (ordersCheck.rows.length === 0) {
      console.log('[dbInit] Таблицы заказов не найдены, создаем...');
      
      await client.query(`
        CREATE TABLE IF NOT EXISTS office_orders (
          id                BIGSERIAL PRIMARY KEY,
          order_number      TEXT UNIQUE NOT NULL,
          title             TEXT NOT NULL,
          description       TEXT,
          status            VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'waiting_quote', 'quotes_received', 'approved', 'ordered', 'received', 'canceled')),
          total_amount      NUMERIC(10, 2) DEFAULT 0,
          supplier_name     TEXT,
          supplier_contact  TEXT,
          invoice_id        BIGINT,
          invoice_url       TEXT,
          expected_date     DATE,
          received_date     DATE,
          notes             TEXT,
          created_by        TEXT NOT NULL,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`
        CREATE TABLE IF NOT EXISTS office_order_items (
          id                BIGSERIAL PRIMARY KEY,
          order_id          BIGINT NOT NULL REFERENCES office_orders(id) ON DELETE CASCADE,
          request_id        BIGINT NOT NULL REFERENCES office_supply_requests(id) ON DELETE CASCADE,
          quantity          INTEGER DEFAULT 1,
          unit_price        NUMERIC(10, 2) DEFAULT 0,
          total_price       NUMERIC(10, 2) DEFAULT 0,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(order_id, request_id)
        )
      `);

      await client.query(`
        CREATE TABLE IF NOT EXISTS office_order_quotes (
          id                BIGSERIAL PRIMARY KEY,
          order_id          BIGINT NOT NULL REFERENCES office_orders(id) ON DELETE CASCADE,
          supplier_name     TEXT NOT NULL,
          supplier_contact  TEXT,
          total_amount      NUMERIC(10, 2) NOT NULL,
          quote_file_url    TEXT,
          notes             TEXT,
          is_selected       BOOLEAN DEFAULT FALSE,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_orders_status ON office_orders(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_orders_created ON office_orders(created_at DESC)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_order_items_order ON office_order_items(order_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_order_items_request ON office_order_items(request_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_order_quotes_order ON office_order_quotes(order_id)`);

      console.log('[dbInit] Таблицы заказов успешно созданы');
    }

    // Проверяем и добавляем поля для платного ремонта в office_repair_requests
    const repairRequestsCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'office_repair_requests'`
    );

    if (repairRequestsCheck.rows.length > 0) {
      // Таблица существует, проверяем наличие полей для платного ремонта
      const costColumnCheck = await client.query(`
        SELECT column_name 
        FROM information_schema.columns 
        WHERE table_name = 'office_repair_requests' 
        AND column_name = 'cost'
      `);
      
      if (costColumnCheck.rows.length === 0) {
        console.log('[dbInit] Добавляем поля для платного ремонта в office_repair_requests...');
        await client.query(`
          ALTER TABLE office_repair_requests 
          ADD COLUMN IF NOT EXISTS is_paid BOOLEAN DEFAULT FALSE,
          ADD COLUMN IF NOT EXISTS cost NUMERIC(10, 2) DEFAULT 0,
          ADD COLUMN IF NOT EXISTS cost_estimated BOOLEAN DEFAULT FALSE,
          ADD COLUMN IF NOT EXISTS invoice_id BIGINT,
          ADD COLUMN IF NOT EXISTS invoice_url TEXT
        `);
        console.log('[dbInit] Поля для платного ремонта успешно добавлены');
      }

      // Поля для статусов: ожидание поставки, увезли на ремонт, договорились с подрядчиком
      const statusFieldsCheck = await client.query(`
        SELECT column_name FROM information_schema.columns 
        WHERE table_name = 'office_repair_requests' AND column_name = 'taken_for_repair_deadline'
      `);
      if (statusFieldsCheck.rows.length === 0) {
        console.log('[dbInit] Добавляем поля статусов ремонта в office_repair_requests...');
        await client.query(`ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS waiting_delivery_deadline TEXT`);
        await client.query(`ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS waiting_delivery_contacts TEXT`);
        await client.query(`ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS taken_for_repair_deadline TEXT`);
        await client.query(`ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS taken_for_repair_contacts TEXT`);
        await client.query(`ALTER TABLE office_repair_requests ADD COLUMN IF NOT EXISTS agreed_contractor_price NUMERIC(10, 2)`);
        console.log('[dbInit] Поля статусов ремонта успешно добавлены');
      }

      // Добавляем недостающие значения в enum repair_request_status (если БД создана по старой схеме)
      const repairStatusEnumValues = ['agreed_with_contractor', 'search_contractor', 'waiting_delivery', 'taken_for_repair', 'self_repair'];
      for (const enumVal of repairStatusEnumValues) {
        try {
          await client.query(`ALTER TYPE repair_request_status ADD VALUE IF NOT EXISTS '${enumVal}'`);
        } catch (err) {
          if (!err.message || !err.message.includes('already exists')) {
            console.warn(`[dbInit] Предупреждение при добавлении значения ${enumVal} в repair_request_status:`, err.message);
          }
        }
      }

      // Таблица истории оборудования: создаём, если не существует
      const historyTableCheck = await client.query(
        `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'office_equipment_history'`
      );
      if (historyTableCheck.rows.length === 0) {
        console.log('[dbInit] Создаём таблицу office_equipment_history...');
        const typeCheck = await client.query(
          `SELECT 1 FROM pg_type WHERE typname = 'office_equipment_history_type'`
        );
        if (typeCheck.rows.length === 0) {
          await client.query(`
            CREATE TYPE office_equipment_history_type AS ENUM ('purchase', 'issue', 'transfer', 'repair', 'write_off')
          `);
        }
        await client.query(`
          CREATE TABLE IF NOT EXISTS office_equipment_history (
            id                BIGSERIAL PRIMARY KEY,
            equipment_id      BIGINT NOT NULL REFERENCES office_equipment(id) ON DELETE CASCADE,
            event_type        office_equipment_history_type NOT NULL,
            event_date        DATE NOT NULL,
            assigned_to       TEXT,
            assigned_from     TEXT,
            reason            TEXT,
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_equipment_history_equipment ON office_equipment_history(equipment_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_equipment_history_date ON office_equipment_history(event_date DESC)`);
        console.log('[dbInit] Таблица office_equipment_history создана');
      } else {
        const historyCols = await client.query(
          `SELECT column_name FROM information_schema.columns WHERE table_name = 'office_equipment_history' AND column_name IN ('assigned_from', 'assigned_to')`
        );
        const hasFrom = historyCols.rows.some(r => r.column_name === 'assigned_from');
        const hasTo = historyCols.rows.some(r => r.column_name === 'assigned_to');
        if (!hasFrom) {
          await client.query(`ALTER TABLE office_equipment_history ADD COLUMN IF NOT EXISTS assigned_from TEXT`);
          console.log('[dbInit] Добавлена колонка assigned_from в office_equipment_history');
        }
        if (!hasTo) {
          await client.query(`ALTER TABLE office_equipment_history ADD COLUMN IF NOT EXISTS assigned_to TEXT`);
          console.log('[dbInit] Добавлена колонка assigned_to в office_equipment_history');
        }
      }
    }

    // Создать office_equipment_history при наличии office_equipment (если ещё не создали выше)
    const officeEquipmentCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'office_equipment'`
    );
    if (officeEquipmentCheck.rows.length > 0) {
      const historyExists = await client.query(
        `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'office_equipment_history'`
      );
      if (historyExists.rows.length === 0) {
        console.log('[dbInit] Создаём таблицу office_equipment_history (при наличии office_equipment)...');
        try {
          const typeCheck = await client.query(`SELECT 1 FROM pg_type WHERE typname = 'office_equipment_history_type'`);
          if (typeCheck.rows.length === 0) {
            await client.query(`CREATE TYPE office_equipment_history_type AS ENUM ('purchase', 'issue', 'transfer', 'repair', 'write_off')`);
          }
          await client.query(`
            CREATE TABLE IF NOT EXISTS office_equipment_history (
              id                BIGSERIAL PRIMARY KEY,
              equipment_id      BIGINT NOT NULL REFERENCES office_equipment(id) ON DELETE CASCADE,
              event_type        office_equipment_history_type NOT NULL,
              event_date        DATE NOT NULL,
              assigned_to       TEXT,
              assigned_from     TEXT,
              reason            TEXT,
              created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
            )
          `);
          await client.query(`CREATE INDEX IF NOT EXISTS idx_equipment_history_equipment ON office_equipment_history(equipment_id)`);
          await client.query(`CREATE INDEX IF NOT EXISTS idx_equipment_history_date ON office_equipment_history(event_date DESC)`);
          console.log('[dbInit] Таблица office_equipment_history создана');
        } catch (histErr) {
          console.warn('[dbInit] Ошибка создания office_equipment_history:', histErr.message);
        }
      }
    }

  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции таблиц офиса:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции PR и NPS модуля
 */
async function applyPRNPSMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_pr_nps.sql');
    
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию PR/NPS из migrate_pr_nps.sql...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция PR/NPS успешно применена');
    } else {
      console.warn('[dbInit] Файл migrate_pr_nps.sql не найден, создаем таблицы вручную...');
      
      // Создаем таблицы PR/NPS вручную
      await client.query(`
        CREATE TABLE IF NOT EXISTS reviews (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          source            VARCHAR(20) NOT NULL CHECK (source IN ('yandex_maps', '2gis', 'internal', 'other')),
          source_url        TEXT,
          author_name       TEXT,
          text              TEXT NOT NULL,
          rating            INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 10),
          date              DATE NOT NULL,
          status            VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'processed', 'archived')),
          processed_at      TIMESTAMPTZ,
          processed_by      TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_reviews_building ON reviews(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_reviews_source ON reviews(source)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_reviews_status ON reviews(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_reviews_date ON reviews(date DESC)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS incidents (
          id                BIGSERIAL PRIMARY KEY,
          review_id         BIGINT REFERENCES reviews(id) ON DELETE SET NULL,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          type              VARCHAR(50) NOT NULL CHECK (type IN ('property_damage', 'debtor_complaint', 'service_quality', 'other')),
          title             TEXT NOT NULL,
          description       TEXT NOT NULL,
          status            VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'in_progress', 'resolved', 'closed')),
          priority          VARCHAR(10) NOT NULL DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
          assigned_to       TEXT,
          created_by        TEXT NOT NULL,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          resolved_at       TIMESTAMPTZ,
          resolution_notes  TEXT,
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_incidents_building ON incidents(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_incidents_status ON incidents(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_incidents_type ON incidents(type)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_incidents_assigned ON incidents(assigned_to)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_incidents_review ON incidents(review_id)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS resident_reports (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          month             VARCHAR(20) NOT NULL,
          period_start      DATE NOT NULL,
          period_end        DATE NOT NULL,
          status            VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published')),
          published_at      TIMESTAMPTZ,
          content           JSONB,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_resident_reports_building ON resident_reports(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_resident_reports_status ON resident_reports(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_resident_reports_period ON resident_reports(period_start)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS work_photos (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          resident_report_id BIGINT REFERENCES resident_reports(id) ON DELETE SET NULL,
          task_id           VARCHAR(50),
          work_name         TEXT NOT NULL,
          work_date         DATE NOT NULL,
          description       TEXT,
          photo_before_url  TEXT,
          photo_after_url   TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_work_photos_building ON work_photos(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_work_photos_report ON work_photos(resident_report_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_work_photos_date ON work_photos(work_date DESC)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_work_photos_task ON work_photos(task_id)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS parsing_settings (
          id                    BIGSERIAL PRIMARY KEY,
          source                VARCHAR(20) NOT NULL UNIQUE CHECK (source IN ('yandex_maps', '2gis')),
          enabled               BOOLEAN NOT NULL DEFAULT TRUE,
          url_template          TEXT,
          api_key               TEXT,
          parsing_interval_hours INTEGER DEFAULT 24,
          last_parsed_at        TIMESTAMPTZ,
          settings              JSONB,
          created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_parsing_settings_source ON parsing_settings(source)`);

      // Инициализируем настройки парсинга, если их нет
      const existingSettings = await client.query('SELECT COUNT(*) as count FROM parsing_settings');
      if (parseInt(existingSettings.rows[0].count) === 0) {
        await client.query(`
          INSERT INTO parsing_settings (source, enabled, url_template, parsing_interval_hours)
          VALUES 
            ('yandex_maps', FALSE, '', 24),
            ('2gis', FALSE, '', 24)
        `);
        console.log('[dbInit] Настройки парсинга инициализированы');
      }

      console.log('[dbInit] Таблицы PR/NPS успешно созданы');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции PR/NPS:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблиц NPS опросов
 */
async function applyNPSSurveysMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_nps_surveys.sql');
    
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию NPS опросов из migrate_nps_surveys.sql...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция NPS опросов успешно применена');
    } else {
      console.warn('[dbInit] Файл migrate_nps_surveys.sql не найден, создаем таблицы вручную...');
      
      // Создаем таблицы NPS опросов вручную
      await client.query(`
        CREATE TABLE IF NOT EXISTS nps_surveys (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          title             TEXT NOT NULL DEFAULT 'Опрос удовлетворенности',
          description       TEXT,
          status            VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'closed')),
          access_key        TEXT NOT NULL UNIQUE,
          published_at      TIMESTAMPTZ,
          expires_at        TIMESTAMPTZ,
          created_by        TEXT NOT NULL,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_surveys_building ON nps_surveys(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_surveys_status ON nps_surveys(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_surveys_access_key ON nps_surveys(access_key)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS nps_responses (
          id                BIGSERIAL PRIMARY KEY,
          survey_id         BIGINT NOT NULL REFERENCES nps_surveys(id) ON DELETE CASCADE,
          building_id       VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          score             INTEGER NOT NULL CHECK (score >= 0 AND score <= 10),
          comment           TEXT,
          respondent_name   TEXT,
          apartment         TEXT,
          phone             TEXT,
          email             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_responses_survey ON nps_responses(survey_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_responses_building ON nps_responses(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_responses_score ON nps_responses(score)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_responses_created ON nps_responses(created_at DESC)`);

      console.log('[dbInit] Таблицы NPS опросов успешно созданы');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции NPS опросов:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблицы агрегированных показателей NPS по дому
 */
async function applyNPSBuildingStatsMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_nps_building_stats.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию nps_building_stats...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Таблица nps_building_stats успешно создана');
    } else {
      await client.query(`
        CREATE TABLE IF NOT EXISTS nps_building_stats (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          period_start      DATE NOT NULL,
          period_end        DATE NOT NULL,
          total_responses   INTEGER NOT NULL DEFAULT 0,
          nps_score         INTEGER NOT NULL DEFAULT 0,
          avg_score         NUMERIC(4,2) NOT NULL DEFAULT 0,
          promoters         INTEGER NOT NULL DEFAULT 0,
          passives          INTEGER NOT NULL DEFAULT 0,
          detractors        INTEGER NOT NULL DEFAULT 0,
          promoter_percent  NUMERIC(5,2) DEFAULT 0,
          detractor_percent NUMERIC(5,2) DEFAULT 0,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(building_id, period_start, period_end)
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_building_stats_building ON nps_building_stats(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_nps_building_stats_period ON nps_building_stats(period_start, period_end)`);
      console.log('[dbInit] Таблица nps_building_stats успешно создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции nps_building_stats:', error.message);
  }
}

/**
 * Применение миграции для таблицы данных отчетов для жителей (дом + период)
 */
async function applyResidentReportDataMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_resident_report_data.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию resident_report_data...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Таблица resident_report_data успешно создана');
    } else {
      await client.query(`
        CREATE TABLE IF NOT EXISTS resident_report_data (
          id                    BIGSERIAL PRIMARY KEY,
          building_id           VARCHAR(50) NOT NULL REFERENCES buildings(id) ON DELETE CASCADE,
          period_start          DATE NOT NULL,
          period_end            DATE NOT NULL,
          period_month          INTEGER NOT NULL,
          period_year           INTEGER NOT NULL,
          nps_score             INTEGER NOT NULL DEFAULT 0,
          nps_total_responses   INTEGER NOT NULL DEFAULT 0,
          nps_avg_score         NUMERIC(4,2) DEFAULT 0,
          nps_promoters         INTEGER NOT NULL DEFAULT 0,
          nps_passives          INTEGER NOT NULL DEFAULT 0,
          nps_detractors        INTEGER NOT NULL DEFAULT 0,
          apps_total            INTEGER NOT NULL DEFAULT 0,
          apps_completed        INTEGER NOT NULL DEFAULT 0,
          apps_quality          INTEGER NOT NULL DEFAULT 0,
          tasks_total           INTEGER NOT NULL DEFAULT 0,
          tasks_completed       INTEGER NOT NULL DEFAULT 0,
          funds_collected       NUMERIC(14,2) NOT NULL DEFAULT 0,
          funds_spent           NUMERIC(14,2) NOT NULL DEFAULT 0,
          funds_balance         NUMERIC(14,2) NOT NULL DEFAULT 0,
          debt_cases_won        INTEGER NOT NULL DEFAULT 0,
          debt_collected        NUMERIC(14,2) NOT NULL DEFAULT 0,
          expenses_total        NUMERIC(14,2) NOT NULL DEFAULT 0,
          expenses_by_category  JSONB,
          events                JSONB,
          work_photos           JSONB,
          plan_items            JSONB,
          snapshot              JSONB,
          created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(building_id, period_start, period_end)
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_resident_report_data_building ON resident_report_data(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_resident_report_data_period ON resident_report_data(period_year, period_month)`);
      console.log('[dbInit] Таблица resident_report_data успешно создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции resident_report_data:', error.message);
  }
}

/**
 * Применение миграции для настроек компании
 */
async function applyCompanySettingsMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_company_settings.sql');
    
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию настроек компании из migrate_company_settings.sql...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция настроек компании успешно применена');
    } else {
      console.warn('[dbInit] Файл migrate_company_settings.sql не найден, создаем таблицу вручную...');
      
      await client.query(`
        CREATE TABLE IF NOT EXISTS company_settings (
          id                BIGSERIAL PRIMARY KEY,
          name              TEXT NOT NULL DEFAULT 'Управляющая компания',
          full_name         TEXT,
          address           TEXT,
          phone             TEXT,
          email             TEXT,
          website           TEXT,
          license_number    TEXT,
          license_valid_until DATE,
          logo_url          TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      // Вставляем дефолтные данные
      await client.query(`
        INSERT INTO company_settings (id, name, full_name, address, phone, email, license_number)
        VALUES (1, 'Управляющая компания "Дружба"', 'ООО "Управляющая компания Дружба"', 'г. Уфа, ул. Ленина, 1', '+7 (347) 123-45-67', 'info@uk-druzhba.ru', 'Лицензия №12345')
        ON CONFLICT (id) DO NOTHING
      `);

      console.log('[dbInit] Таблица company_settings успешно создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции настроек компании:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблиц досудебной работы
 */
async function applyPreTrialWorkMigration(client) {
  try {
    // Проверяем существование таблицы legal_debtors
    const debtorsCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'legal_debtors'`
    );

    if (debtorsCheck.rows.length === 0) {
      console.log('[dbInit] Таблицы досудебной работы не найдены, создаем...');
      
      // Читаем SQL миграцию
      const migrationPath = path.join(__dirname, 'migrate_pre_trial_work.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Таблицы досудебной работы успешно созданы');
      } else {
        console.warn('[dbInit] Файл migrate_pre_trial_work.sql не найден, создаем таблицы вручную...');
        
        // Создаем таблицы вручную, если файл не найден
        await client.query(`
          CREATE TABLE IF NOT EXISTS legal_debtors (
            id                BIGSERIAL PRIMARY KEY,
            building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL,
            apartment         TEXT NOT NULL,
            debtor_name       TEXT,
            phone             TEXT,
            email             TEXT,
            address           TEXT NOT NULL,
            debt_amount       NUMERIC(10, 2) NOT NULL,
            debt_months       INTEGER NOT NULL,
            status            VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'in_progress', 'promised_payment', 'transferred_to_court', 'resolved')),
            transferred_from_finance BOOLEAN DEFAULT FALSE,
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_debtors_building ON legal_debtors(building_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_debtors_status ON legal_debtors(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_debtors_apartment ON legal_debtors(apartment)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_debtors_created ON legal_debtors(created_at DESC)`);
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS pre_trial_work (
            id                        BIGSERIAL PRIMARY KEY,
            debtor_id                 BIGINT NOT NULL REFERENCES legal_debtors(id) ON DELETE CASCADE,
            assigned_to              TEXT,
            status                   VARCHAR(20) NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'in_progress', 'promised_payment', 'transferred_to_court', 'resolved')),
            promised_payment_date   DATE,
            promised_payment_amount  NUMERIC(10, 2),
            transferred_to_court    BOOLEAN DEFAULT FALSE,
            court_case_id            VARCHAR(50),
            notes                    TEXT,
            created_at               TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at               TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_work_debtor ON pre_trial_work(debtor_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_work_status ON pre_trial_work(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_work_assigned ON pre_trial_work(assigned_to)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_work_court_case ON pre_trial_work(court_case_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_work_promised_date ON pre_trial_work(promised_payment_date)`);
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS pre_trial_actions (
            id                BIGSERIAL PRIMARY KEY,
            work_id           BIGINT NOT NULL REFERENCES pre_trial_work(id) ON DELETE CASCADE,
            action_type       VARCHAR(20) NOT NULL CHECK (action_type IN ('call', 'letter', 'visit')),
            action_date       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            performed_by      TEXT NOT NULL,
            result            TEXT,
            notes             TEXT,
            attachments      TEXT[],
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_actions_work ON pre_trial_actions(work_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_actions_type ON pre_trial_actions(action_type)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_actions_date ON pre_trial_actions(action_date DESC)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_pre_trial_actions_performed ON pre_trial_actions(performed_by)`);
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS promised_payments (
            id                    BIGSERIAL PRIMARY KEY,
            work_id               BIGINT NOT NULL REFERENCES pre_trial_work(id) ON DELETE CASCADE,
            promised_date         DATE NOT NULL,
            promised_amount       NUMERIC(10, 2) NOT NULL,
            actual_payment_date   DATE,
            actual_payment_amount NUMERIC(10, 2),
            is_paid               BOOLEAN DEFAULT FALSE,
            reminder_sent         BOOLEAN DEFAULT FALSE,
            reminder_date         DATE,
            notes                 TEXT,
            created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_promised_payments_work ON promised_payments(work_id)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_promised_payments_date ON promised_payments(promised_date)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_promised_payments_paid ON promised_payments(is_paid)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_promised_payments_reminder ON promised_payments(reminder_sent, reminder_date)`);
        
        console.log('[dbInit] Таблицы досудебной работы успешно созданы вручную');
      }
    } else {
      console.log('[dbInit] Таблицы досудебной работы уже существуют');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции досудебной работы:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для таблиц проверок контрагентов
 */
async function applyCounterpartyChecksMigration(client) {
  try {
    // Проверяем существование таблицы counterparty_checks
    const checksCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'counterparty_checks'`
    );

    if (checksCheck.rows.length === 0) {
      console.log('[dbInit] Таблицы проверок контрагентов не найдены, создаем...');
      
      // Читаем SQL миграцию
      const migrationPath = path.join(__dirname, 'migrate_counterparty_checks.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Таблицы проверок контрагентов успешно созданы');
      } else {
        console.warn('[dbInit] Файл migrate_counterparty_checks.sql не найден, создаем таблицы вручную...');
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS counterparty_checks (
            id                BIGSERIAL PRIMARY KEY,
            inn               TEXT NOT NULL,
            kpp               TEXT,
            ogrn              TEXT,
            name              TEXT NOT NULL,
            short_name        TEXT,
            type              VARCHAR(20) CHECK (type IN ('LEGAL', 'INDIVIDUAL')),
            status            VARCHAR(20),
            registration_date DATE,
            liquidation_date  DATE,
            address           TEXT,
            okved             TEXT,
            okveds            JSONB,
            management_name   TEXT,
            management_post   TEXT,
            finance_data      JSONB,
            authorities_data  JSONB,
            phones            JSONB,
            emails            JSONB,
            employee_count    INTEGER,
            risk_level        VARCHAR(10) NOT NULL CHECK (risk_level IN ('low', 'medium', 'high')),
            risk_reasons      TEXT[],
            raw_data          JSONB,
            checked_by        TEXT,
            checked_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            notes             TEXT,
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_counterparty_checks_inn ON counterparty_checks(inn)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_counterparty_checks_risk_level ON counterparty_checks(risk_level)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_counterparty_checks_checked_at ON counterparty_checks(checked_at DESC)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_counterparty_checks_name ON counterparty_checks(name)`);
        
        console.log('[dbInit] Таблицы проверок контрагентов успешно созданы вручную');
      }
    } else {
      console.log('[dbInit] Таблицы проверок контрагентов уже существуют');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции проверок контрагентов:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции: колонка role в portal_users (роль пользователя портала)
 */
async function applyPortalUsersRoleMigration(client) {
  try {
    const colCheck = await client.query(
      `SELECT column_name FROM information_schema.columns 
       WHERE table_schema = 'public' AND table_name = 'portal_users' AND column_name = 'role'`
    );
    if (colCheck.rows.length === 0) {
      await client.query(
        `ALTER TABLE portal_users ADD COLUMN role VARCHAR(30) NOT NULL DEFAULT 'ENGINEER'`
      );
      console.log('[dbInit] В portal_users добавлена колонка role');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции portal_users.role:', error.message);
  }
}

/**
 * Миграция: password_hash и is_active в portal_users для авторизации
 */
async function applyPortalUsersAuthMigration(client) {
  try {
    const cols = await client.query(
      `SELECT column_name FROM information_schema.columns 
       WHERE table_schema = 'public' AND table_name = 'portal_users' AND column_name IN ('password_hash', 'is_active')`
    );
    const names = cols.rows.map(r => r.column_name);
    if (!names.includes('password_hash')) {
      await client.query(`ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS password_hash TEXT`);
      console.log('[dbInit] В portal_users добавлена колонка password_hash');
    }
    if (!names.includes('is_active')) {
      await client.query(`ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT true`);
      console.log('[dbInit] В portal_users добавлена колонка is_active');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции portal_users auth:', error.message);
  }
}

/**
 * Миграция: поля профиля пользователя и таблица user_preferences
 */
async function applyUserProfileFieldsMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrations', 'add_user_profile_fields.sql');
    if (fs.existsSync(migrationPath)) {
      const sql = fs.readFileSync(migrationPath, 'utf8');
      await client.query(sql);
      console.log('[dbInit] Миграция add_user_profile_fields.sql применена успешно');
    }
  } catch (error) {
    if (!error.message?.includes('already exists') && !error.message?.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции add_user_profile_fields.sql:', error.message);
    }
  }
}

async function applyPermissionTemplatesMigration(client) {
  try {
    const tableCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'permission_templates'`
    );
    if (tableCheck.rows.length === 0) {
      await client.query(`
        CREATE TABLE IF NOT EXISTS permission_templates (
          id         BIGSERIAL PRIMARY KEY,
          name       TEXT NOT NULL,
          description TEXT,
          permissions JSONB NOT NULL DEFAULT '[]',
          created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      console.log('[dbInit] Таблица permission_templates создана');
    }
    const colCheck = await client.query(
      `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'portal_users' AND column_name = 'permissions'`
    );
    if (colCheck.rows.length === 0) {
      await client.query(`ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS permissions JSONB`);
      console.log('[dbInit] В portal_users добавлена колонка permissions');
    }
    // scope и for_position в permission_templates; scope в portal_users
    const ptCols = await client.query(
      `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'permission_templates'`
    );
    const ptColNames = (ptCols.rows || []).map((r) => r.column_name);
    if (!ptColNames.includes('scope')) {
      await client.query(`ALTER TABLE permission_templates ADD COLUMN IF NOT EXISTS scope VARCHAR(20) NOT NULL DEFAULT 'all'`);
      console.log('[dbInit] В permission_templates добавлена колонка scope');
    }
    if (!ptColNames.includes('for_position')) {
      await client.query(`ALTER TABLE permission_templates ADD COLUMN IF NOT EXISTS for_position TEXT`);
      console.log('[dbInit] В permission_templates добавлена колонка for_position');
    }
    if (!ptColNames.includes('suggested_role')) {
      await client.query(`ALTER TABLE permission_templates ADD COLUMN IF NOT EXISTS suggested_role VARCHAR(30)`);
      console.log('[dbInit] В permission_templates добавлена колонка suggested_role');
    }
    const puScopeCheck = await client.query(
      `SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'portal_users' AND column_name = 'scope'`
    );
    if (puScopeCheck.rows.length === 0) {
      await client.query(`ALTER TABLE portal_users ADD COLUMN IF NOT EXISTS scope VARCHAR(20) NOT NULL DEFAULT 'all'`);
      console.log('[dbInit] В portal_users добавлена колонка scope');
    }
    // Сид: шаблон «Мастер участка (расширенный)» — участки, заявки, сводка (производство), финансы (счета :own, календарь), офис (закупки, ремонты), PR (мероприятия, фото)
    const masterExtendedName = 'Мастер участка (расширенный)';
    const existingMasterExtended = await client.query(
      `SELECT id FROM permission_templates WHERE name = $1`,
      [masterExtendedName]
    );
    if (existingMasterExtended.rows.length === 0) {
      const masterExtendedPermissions = [
        'objects',
        'requests',
        'dashboard_production',
        'finance_invoices:own',
        'finance_calendar',
        'office_supply',
        'office_repair',
        'pr_events',
        'pr_photos',
      ];
      await client.query(
        `INSERT INTO permission_templates (name, description, permissions, scope, for_position, suggested_role)
         VALUES ($1, $2, $3, 'own_district', $4, 'MASTER')`,
        [
          masterExtendedName,
          'Мастер: участки и заявки по своим участкам; сводка (производство); реестр счетов (только свои), календарь оплат; закупки ТМЦ и ремонт техники; мероприятия и фото отчёты по своим домам.',
          JSON.stringify(masterExtendedPermissions),
          'Мастер участка',
        ]
      );
      console.log('[dbInit] Добавлен шаблон прав: Мастер участка (расширенный)');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции permission_templates:', error.message);
  }
}

/**
 * Миграция: зоны ответственности — привязка сотрудников к подразделам модулей (для уведомлений и эффективности).
 */
async function applyEmployeeResponsibilityMigration(client) {
  try {
    const check = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'employee_responsibility'`
    );
    if (check.rows.length === 0) {
      await client.query(`
        CREATE TABLE employee_responsibility (
          id          BIGSERIAL PRIMARY KEY,
          employee_id VARCHAR(50) NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
          section     VARCHAR(50) NOT NULL,
          sub_id      VARCHAR(50) NOT NULL,
          created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(employee_id, section, sub_id)
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_responsibility_employee ON employee_responsibility(employee_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_employee_responsibility_zone ON employee_responsibility(section, sub_id)`);
      console.log('[dbInit] Таблица employee_responsibility создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции employee_responsibility:', error.message);
  }
}

/**
 * Создание администратора портала по умолчанию (сотрудник iiEasy, логин its).
 * Выполняется при каждой инициализации: если пользователя с логином 'its' нет — создаётся сотрудник и пользователь.
 */
async function applyDefaultAdminSeed(client) {
  try {
    const existingUser = await client.query(
      'SELECT id FROM portal_users WHERE login = $1',
      [DEFAULT_ADMIN_LOGIN]
    );
    if (existingUser.rows.length > 0) {
      return;
    }
    const employeeExists = await client.query(
      'SELECT id FROM employees WHERE id = $1',
      [DEFAULT_ADMIN_EMPLOYEE_ID]
    );
    if (employeeExists.rows.length === 0) {
      await client.query(
        `INSERT INTO employees (id, name, position, phone, status, salary, assigned_district_id)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         ON CONFLICT (id) DO NOTHING`,
        [
          DEFAULT_ADMIN_EMPLOYEE_ID,
          DEFAULT_ADMIN_EMPLOYEE_NAME,
          'Администратор портала',
          '+70000000000',
          'active',
          0,
          null,
        ]
      );
      console.log('[dbInit] Создан сотрудник по умолчанию:', DEFAULT_ADMIN_EMPLOYEE_NAME);
    }
    const passwordHash = await bcrypt.hash(DEFAULT_ADMIN_PASSWORD, 10);
    await client.query(
      `INSERT INTO portal_users (employee_id, login, role, password_hash, is_active)
       VALUES ($1, $2, $3, $4, true)
       ON CONFLICT (login) DO NOTHING`,
      [DEFAULT_ADMIN_EMPLOYEE_ID, DEFAULT_ADMIN_LOGIN, DEFAULT_ADMIN_ROLE, passwordHash]
    );
    const check = await client.query('SELECT id FROM portal_users WHERE login = $1', [DEFAULT_ADMIN_LOGIN]);
    if (check.rows.length > 0) {
      console.log('[dbInit] Создан администратор портала по умолчанию: логин', DEFAULT_ADMIN_LOGIN);
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при создании администратора по умолчанию:', error.message);
  }
}

/**
 * Демо-пользователь портала (логин demo, пароль demo123) с правами DIRECTOR, как у its.
 */
async function applyDemoUserSeed(client) {
  try {
    const existingUser = await client.query(
      'SELECT id FROM portal_users WHERE login = $1',
      [DEMO_LOGIN]
    );
    if (existingUser.rows.length > 0) {
      return;
    }
    const employeeExists = await client.query(
      'SELECT id FROM employees WHERE id = $1',
      [DEMO_EMPLOYEE_ID]
    );
    if (employeeExists.rows.length === 0) {
      await client.query(
        `INSERT INTO employees (id, name, position, phone, status, salary, assigned_district_id)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         ON CONFLICT (id) DO NOTHING`,
        [
          DEMO_EMPLOYEE_ID,
          DEMO_EMPLOYEE_NAME,
          'Демо-пользователь',
          '+70000000001',
          'active',
          0,
          null,
        ]
      );
      console.log('[dbInit] Создан сотрудник для демо:', DEMO_EMPLOYEE_NAME);
    }
    const passwordHash = await bcrypt.hash(DEMO_PASSWORD, 10);
    await client.query(
      `INSERT INTO portal_users (employee_id, login, role, password_hash, is_active)
       VALUES ($1, $2, $3, $4, true)
       ON CONFLICT (login) DO NOTHING`,
      [DEMO_EMPLOYEE_ID, DEMO_LOGIN, DEMO_ROLE, passwordHash]
    );
    const check = await client.query('SELECT id FROM portal_users WHERE login = $1', [DEMO_LOGIN]);
    if (check.rows.length > 0) {
      console.log('[dbInit] Создан демо-пользователь портала: логин', DEMO_LOGIN, ', роль', DEMO_ROLE);
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при создании демо-пользователя:', error.message);
  }
}

/**
 * Таблица настроек интеграций (DaData и др.)
 */
async function applyIntegrationSettingsMigration(client) {
  try {
    const tableCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'integration_settings'`
    );
    if (tableCheck.rows.length === 0) {
      await client.query(`
        CREATE TABLE IF NOT EXISTS integration_settings (
          id         BIGSERIAL PRIMARY KEY,
          key        VARCHAR(50) NOT NULL UNIQUE,
          name       TEXT,
          enabled    BOOLEAN NOT NULL DEFAULT true,
          config     JSONB NOT NULL DEFAULT '{}',
          created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_integration_settings_key ON integration_settings(key)`);
      await client.query(`
        INSERT INTO integration_settings (key, name, enabled, config)
        VALUES ('dadata', 'DaData (проверка контрагентов по ИНН)', true, '{"apiKey":"","secret":""}')
        ON CONFLICT (key) DO NOTHING
      `);
      await client.query(`
        INSERT INTO integration_settings (key, name, enabled, config)
        VALUES ('ai_chat', 'ИИ-помощник (чат)', false, '{"url":"","apiKey":""}')
        ON CONFLICT (key) DO NOTHING
      `);
      await client.query(`
        INSERT INTO integration_settings (key, name, enabled, config)
        VALUES ('doma', 'Дома.АИ (заявки, API)', true, '{"apiUrl":"","token":""}')
        ON CONFLICT (key) DO NOTHING
      `);
      console.log('[dbInit] Таблица integration_settings создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции integration_settings:', error.message);
  }
}

/**
 * Миграция: таблицы безопасности (логи входа, чёрный список)
 */
async function applySecurityModuleMigration(client) {
  try {
    const authLogsCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'auth_logs'`
    );
    if (authLogsCheck.rows.length === 0) {
      await client.query(`
        CREATE TABLE IF NOT EXISTS auth_logs (
          id         BIGSERIAL PRIMARY KEY,
          ip         TEXT NOT NULL,
          login_masked TEXT NOT NULL,
          success    BOOLEAN NOT NULL,
          created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_auth_logs_created_at ON auth_logs(created_at DESC)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_auth_logs_success ON auth_logs(success)`);
      console.log('[dbInit] Таблица auth_logs создана');
    }
    const blacklistCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'security_blacklist'`
    );
    if (blacklistCheck.rows.length === 0) {
      await client.query(`
        CREATE TABLE IF NOT EXISTS security_blacklist (
          id         BIGSERIAL PRIMARY KEY,
          type       TEXT NOT NULL CHECK (type IN ('ip', 'login')),
          value      TEXT NOT NULL,
          reason     TEXT,
          created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      await client.query(`CREATE UNIQUE INDEX IF NOT EXISTS idx_security_blacklist_type_value ON security_blacklist(type, value)`);
      console.log('[dbInit] Таблица security_blacklist создана');
    }
    const captchaCheck = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'security_captcha'`
    );
    if (captchaCheck.rows.length === 0) {
      await client.query(`
        CREATE TABLE IF NOT EXISTS security_captcha (
          id         SERIAL PRIMARY KEY,
          site_key   TEXT,
          secret_key TEXT,
          updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);
      await client.query(`INSERT INTO security_captcha (id, site_key, secret_key) VALUES (1, NULL, NULL) ON CONFLICT (id) DO NOTHING`);
      console.log('[dbInit] Таблица security_captcha создана');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции security:', error.message);
  }
}

/**
 * Применение миграции для модуля развития
 */
async function applyDevelopmentModuleMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_development_module.sql');
    
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию модуля развития из migrate_development_module.sql...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция модуля развития успешно применена');
    } else {
      console.warn('[dbInit] Файл migrate_development_module.sql не найден, создаем таблицы вручную...');
      
      // Создаем таблицы модуля развития вручную
      await client.query(`
        CREATE TABLE IF NOT EXISTS development_pipeline (
          id                VARCHAR(50) PRIMARY KEY,
          address           TEXT NOT NULL,
          type              VARCHAR(10) NOT NULL CHECK (type IN ('old', 'new')),
          floors            INTEGER NOT NULL,
          area              NUMERIC(10, 2) NOT NULL,
          apartments        INTEGER NOT NULL,
          status            VARCHAR(30) NOT NULL CHECK (status IN ('incoming', 'analysis', 'agenda_approval', 'in_person', 'absentee', 'protocol_formation', 'protocol_to_gzhi', 'gzhi_order', 'success', 'failure')),
          probability       INTEGER NOT NULL CHECK (probability >= 0 AND probability <= 100),
          expected_revenue  NUMERIC(15, 2) NOT NULL DEFAULT 0,
          manager           TEXT NOT NULL,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL,
          notes             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_pipeline_status ON development_pipeline(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_pipeline_building ON development_pipeline(building_id)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_oss_sessions (
          id                VARCHAR(50) PRIMARY KEY,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          address           TEXT NOT NULL,
          start_date        DATE NOT NULL,
          end_date          DATE NOT NULL,
          quorum_current    NUMERIC(10, 2) NOT NULL DEFAULT 0,
          quorum_total      NUMERIC(10, 2) NOT NULL,
          status            VARCHAR(20) NOT NULL CHECK (status IN ('active', 'planned', 'completed')),
          type              VARCHAR(20) NOT NULL CHECK (type IN ('annual', 'extraordinary')),
          description       TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_oss_building ON development_oss_sessions(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_oss_status ON development_oss_sessions(status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_oss_dates ON development_oss_sessions(start_date, end_date)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_audits (
          id                  VARCHAR(50) PRIMARY KEY,
          building_id         VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          address             TEXT NOT NULL,
          wear_percent        INTEGER NOT NULL CHECK (wear_percent >= 0 AND wear_percent <= 100),
          roof_condition      VARCHAR(10) NOT NULL CHECK (roof_condition IN ('good', 'poor', 'fair')),
          basement_condition  VARCHAR(10) NOT NULL CHECK (basement_condition IN ('good', 'poor', 'fair')),
          calculated_tariff  NUMERIC(10, 2) NOT NULL,
          projected_margin   NUMERIC(5, 2) NOT NULL,
          audit_date         DATE NOT NULL DEFAULT CURRENT_DATE,
          auditor_name       TEXT,
          defect_list_url    TEXT,
          notes              TEXT,
          created_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at         TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_audits_building ON development_audits(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_audits_date ON development_audits(audit_date DESC)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_marketing_activities (
          id                VARCHAR(50) PRIMARY KEY,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          address           TEXT NOT NULL,
          activists_count   INTEGER NOT NULL DEFAULT 0,
          meetings_held     INTEGER NOT NULL DEFAULT 0,
          ads_distributed   INTEGER NOT NULL DEFAULT 0,
          competitor        TEXT,
          status            VARCHAR(20) NOT NULL CHECK (status IN ('voting', 'my_house', 'competitor_house')),
          notes             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_marketing_building ON development_marketing_activities(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_marketing_status ON development_marketing_activities(status)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_building_locations (
          id                BIGSERIAL PRIMARY KEY,
          building_id       VARCHAR(50) REFERENCES buildings(id) ON DELETE CASCADE,
          address           TEXT NOT NULL,
          latitude          NUMERIC(10, 7),
          longitude         NUMERIC(10, 7),
          status            VARCHAR(20) NOT NULL CHECK (status IN ('ours', 'voting', 'competitor')),
          competitor_name   TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(building_id)
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_locations_building ON development_building_locations(building_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_locations_status ON development_building_locations(status)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_oss_registry (
          id                BIGSERIAL PRIMARY KEY,
          oss_session_id   VARCHAR(50) NOT NULL REFERENCES development_oss_sessions(id) ON DELETE CASCADE,
          apartment        TEXT NOT NULL,
          owner_name        TEXT,
          area              NUMERIC(10, 2) NOT NULL,
          ballot_submitted  BOOLEAN DEFAULT FALSE,
          ballot_date       DATE,
          vote_result       VARCHAR(20) CHECK (vote_result IN ('for', 'against', 'abstain')),
          notes             TEXT,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_dev_oss_registry_session ON development_oss_registry(oss_session_id)`);

      // Создаем функцию и триггеры для updated_at
      await client.query(`
        CREATE OR REPLACE FUNCTION update_updated_at_column()
        RETURNS TRIGGER AS $$
        BEGIN
            NEW.updated_at = NOW();
            RETURN NEW;
        END;
        $$ language 'plpgsql';
      `);

      // Создаем триггеры для каждой таблицы
      const tables = [
        'development_pipeline',
        'development_oss_sessions',
        'development_audits',
        'development_marketing_activities',
        'development_building_locations',
        'development_oss_registry'
      ];

      for (const table of tables) {
        await client.query(`
          DROP TRIGGER IF EXISTS update_${table}_updated_at ON ${table};
          CREATE TRIGGER update_${table}_updated_at BEFORE UPDATE ON ${table}
              FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
        `);
      }

      console.log('[dbInit] Таблицы модуля развития успешно созданы вручную');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции модуля развития:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для автоматизации воронки
 */
async function applyPipelineAutomationMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrate_pipeline_automation.sql');
    
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию автоматизации воронки из migrate_pipeline_automation.sql...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция автоматизации воронки успешно применена');
    } else {
      console.warn('[dbInit] Файл migrate_pipeline_automation.sql не найден, создаем таблицы вручную...');
      
      // Создаем таблицы автоматизации вручную
      await client.query(`
        CREATE TABLE IF NOT EXISTS development_pipeline_history (
          id                BIGSERIAL PRIMARY KEY,
          pipeline_id       VARCHAR(50) NOT NULL REFERENCES development_pipeline(id) ON DELETE CASCADE,
          from_status       VARCHAR(20),
          to_status         VARCHAR(20) NOT NULL,
          reason            TEXT,
          triggered_by      VARCHAR(50) NOT NULL DEFAULT 'manual',
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_pipeline_history_pipeline ON development_pipeline_history(pipeline_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_pipeline_history_status ON development_pipeline_history(to_status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_pipeline_history_date ON development_pipeline_history(created_at DESC)`);

      await client.query(`
        CREATE TABLE IF NOT EXISTS development_automation_rules (
          id                BIGSERIAL PRIMARY KEY,
          rule_name         VARCHAR(100) NOT NULL UNIQUE,
          from_status       VARCHAR(20),
          to_status         VARCHAR(20) NOT NULL,
          conditions        JSONB,
          enabled           BOOLEAN DEFAULT TRUE,
          priority          INTEGER DEFAULT 0,
          created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_automation_rules_status ON development_automation_rules(from_status, to_status)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_automation_rules_enabled ON development_automation_rules(enabled)`);

      // Добавляем поле last_auto_check если его нет
      await client.query(`
        DO $$ 
        BEGIN 
          IF NOT EXISTS (SELECT 1 FROM information_schema.columns 
                         WHERE table_name = 'development_pipeline' AND column_name = 'last_auto_check') THEN
            ALTER TABLE development_pipeline ADD COLUMN last_auto_check TIMESTAMPTZ;
          END IF;
        END $$;
      `);

      await client.query(`CREATE INDEX IF NOT EXISTS idx_pipeline_last_check ON development_pipeline(last_auto_check)`);

      console.log('[dbInit] Таблицы автоматизации воронки успешно созданы вручную');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции автоматизации воронки:', error.message);
  }
}

/**
 * Применение миграции новых этапов воронки развития (9 пунктов)
 * Обновляет CHECK по status и мигрирует старые статусы в новые.
 */
async function applyDevelopmentPipelineNewStagesMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrations', 'development_pipeline_new_stages.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию новых этапов воронки развития...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция новых этапов воронки успешно применена');
    } else {
      // Если файла нет — выполняем логику напрямую (для уже существующей БД со старым CHECK)
      const tableCheck = await client.query(
        `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'development_pipeline'`
      );
      if (tableCheck.rows.length > 0) {
        await client.query(`ALTER TABLE development_pipeline DROP CONSTRAINT IF EXISTS development_pipeline_status_check`);
        await client.query(`UPDATE development_pipeline SET status = 'incoming' WHERE status = 'analysis'`);
        await client.query(`UPDATE development_pipeline SET status = 'agenda_approval' WHERE status = 'negotiation'`);
        await client.query(`UPDATE development_pipeline SET status = 'in_person' WHERE status = 'preparation'`);
        await client.query(`UPDATE development_pipeline SET status = 'absentee' WHERE status = 'voting'`);
        await client.query(`UPDATE development_pipeline SET status = 'success' WHERE status = 'transfer'`);
        await client.query(`
          ALTER TABLE development_pipeline ADD CONSTRAINT development_pipeline_status_check
          CHECK (status IN ('incoming', 'analysis', 'agenda_approval', 'in_person', 'absentee', 'protocol_formation', 'protocol_to_gzhi', 'gzhi_order', 'success', 'failure'))
        `);
        console.log('[dbInit] Новые этапы воронки применены (inline)');
      }
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции новых этапов воронки:', error.message);
  }
}

/**
 * Добавление этапа «Анализ» (analysis) в воронку развития
 */
async function applyDevelopmentPipelineAddAnalysisMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrations', 'development_pipeline_add_analysis.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию: этап «Анализ» в воронке развития...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Этап «Анализ» успешно добавлен в воронку');
    } else {
      const tableCheck = await client.query(
        `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'development_pipeline'`
      );
      if (tableCheck.rows.length > 0) {
        await client.query(`ALTER TABLE development_pipeline DROP CONSTRAINT IF EXISTS development_pipeline_status_check`);
        await client.query(`
          ALTER TABLE development_pipeline ADD CONSTRAINT development_pipeline_status_check
          CHECK (status IN ('incoming', 'analysis', 'agenda_approval', 'in_person', 'absentee', 'protocol_formation', 'protocol_to_gzhi', 'gzhi_order', 'success', 'failure'))
        `);
        console.log('[dbInit] Этап «Анализ» применён (inline)');
      }
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции этапа «Анализ»:', error.message);
  }
}

/**
 * Миграция аудитов: статусы, индекс сложности, inspection_data
 */
async function applyDevelopmentAuditsStatusAndInspectionMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrations', 'development_audits_status_and_inspection.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию аудитов (статусы, осмотр)...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция аудитов успешно применена');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции аудитов:', error.message);
  }
}

/**
 * Миграция ОСС: повестка (agenda_items) и голоса по пунктам (votes_by_item)
 */
async function applyDevelopmentOSSAgendaMigration(client) {
  try {
    const migrationPath = path.join(__dirname, 'migrations', 'development_oss_agenda_and_votes.sql');
    if (fs.existsSync(migrationPath)) {
      console.log('[dbInit] Применяем миграцию ОСС (повестка, голоса по пунктам)...');
      const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция ОСС успешно применена');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции ОСС:', error.message);
  }
}

/**
 * Применение миграции для юридического отдела (договоры, судебные дела)
 */
async function applyLegalModuleMigration(client) {
  try {
    // Проверяем существование таблицы legal_contracts
    const contractsCheck = await client.query(
      `SELECT table_name 
       FROM information_schema.tables 
       WHERE table_schema = 'public' 
       AND table_name = 'legal_contracts'`
    );

    if (contractsCheck.rows.length === 0) {
      console.log('[dbInit] Таблицы юридического отдела не найдены, создаем...');
      
      // Читаем SQL миграцию
      const migrationPath = path.join(__dirname, 'migrate_legal_module.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Таблицы юридического отдела успешно созданы');
      } else {
        console.warn('[dbInit] Файл migrate_legal_module.sql не найден, создаем таблицы вручную...');
        
        // Создаем таблицы вручную
        await client.query(`
          CREATE TABLE IF NOT EXISTS legal_contracts (
            id                VARCHAR(50) PRIMARY KEY,
            number            TEXT NOT NULL UNIQUE,
            type              TEXT NOT NULL,
            counterparty      TEXT NOT NULL,
            counterparty_inn  TEXT,
            amount            NUMERIC(15, 2) NOT NULL,
            status            VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'finance_approval', 'counterparty_approval', 'signing', 'active', 'archived')),
            start_date        DATE NOT NULL,
            end_date          DATE NOT NULL,
            auto_prolongation BOOLEAN DEFAULT FALSE,
            manager           TEXT NOT NULL,
            has_disagreements BOOLEAN DEFAULT FALSE,
            contract_file_url TEXT,
            notes             TEXT,
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_contracts_status ON legal_contracts(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_contracts_counterparty ON legal_contracts(counterparty)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_contracts_number ON legal_contracts(number)`);
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS legal_court_cases (
            id                    VARCHAR(50) PRIMARY KEY,
            case_number           TEXT NOT NULL UNIQUE,
            type                  VARCHAR(20) NOT NULL CHECK (type IN ('arbitration', 'civil', 'debt_recovery')),
            role                  VARCHAR(20) NOT NULL CHECK (role IN ('plaintiff', 'defendant')),
            subject               TEXT NOT NULL,
            debtor_name           TEXT,
            address               TEXT,
            amount                NUMERIC(15, 2) NOT NULL,
            recovered_amount      NUMERIC(15, 2) DEFAULT 0,
            amount_at_bailiffs    NUMERIC(15, 2) DEFAULT 0,
            status                VARCHAR(20) NOT NULL DEFAULT 'pre_trial' CHECK (status IN ('pre_trial', 'litigation', 'decision_received', 'enforcement', 'closed')),
            fssp_status           TEXT,
            bailiff_name          TEXT,
            fssp_last_action_date DATE,
            next_hearing_date     DATE,
            judge                 TEXT,
            court_name            TEXT,
            case_file_url         TEXT,
            notes                 TEXT,
            created_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at            TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_court_cases_status ON legal_court_cases(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_court_cases_type ON legal_court_cases(type)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_court_cases_number ON legal_court_cases(case_number)`);
        
        await client.query(`
          CREATE TABLE IF NOT EXISTS legal_powers_of_attorney (
            id                VARCHAR(50) PRIMARY KEY,
            number            TEXT NOT NULL UNIQUE,
            issued_to         TEXT NOT NULL,
            issue_date        DATE NOT NULL,
            expiry_date       DATE NOT NULL,
            status            VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'revoked')),
            authority         TEXT NOT NULL,
            document_file_url TEXT,
            notes             TEXT,
            created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
            updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
          )
        `);
        
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_poa_status ON legal_powers_of_attorney(status)`);
        await client.query(`CREATE INDEX IF NOT EXISTS idx_legal_poa_expiry ON legal_powers_of_attorney(expiry_date)`);
        
        console.log('[dbInit] Таблицы юридического отдела успешно созданы вручную');
      }
    } else {
      console.log('[dbInit] Таблицы юридического отдела уже существуют');
    }

    // Миграция: этапы ФССП и ИП (доп. поля legal_court_cases)
    const fsspMigrationPath = path.join(__dirname, 'migrate_legal_fssp_enforcement.sql');
    if (fs.existsSync(fsspMigrationPath)) {
      try {
        const fsspSQL = fs.readFileSync(fsspMigrationPath, 'utf8');
        await client.query(fsspSQL);
        console.log('[dbInit] Миграция ФССП/ИП применена');
      } catch (fsspErr) {
        console.warn('[dbInit] Ошибка миграции ФССП (колонки могут уже существовать):', fsspErr.message);
      }
    }

    const penaltiesMigrationPath = path.join(__dirname, 'migrate_legal_penalties.sql');
    if (fs.existsSync(penaltiesMigrationPath)) {
      try {
        const penaltiesSQL = fs.readFileSync(penaltiesMigrationPath, 'utf8');
        await client.query(penaltiesSQL);
        console.log('[dbInit] Миграция пени применена');
      } catch (penErr) {
        console.warn('[dbInit] Ошибка миграции пени:', penErr.message);
      }
    }

    const caseDocsMigrationPath = path.join(__dirname, 'migrate_legal_case_documents.sql');
    if (fs.existsSync(caseDocsMigrationPath)) {
      try {
        const caseDocsSQL = fs.readFileSync(caseDocsMigrationPath, 'utf8');
        await client.query(caseDocsSQL);
        console.log('[dbInit] Миграция документов по делам применена');
      } catch (docErr) {
        console.warn('[dbInit] Ошибка миграции документов по делам:', docErr.message);
      }
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции юридического отдела:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для добавления статуса 'deferred' в enum doma_application_status
 */
async function applyDeferredStatusMigration(client) {
  try {
    // Проверяем, существует ли уже значение 'deferred' в enum
    const enumCheck = await client.query(`
      SELECT 1 FROM pg_enum 
      WHERE enumlabel = 'deferred' 
      AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'doma_application_status')
    `);

    if (enumCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем статус "deferred" в enum doma_application_status...');
      
      try {
        // Пробуем добавить значение (может не работать в транзакции в некоторых версиях PostgreSQL)
        await client.query(`ALTER TYPE doma_application_status ADD VALUE 'deferred'`);
        console.log('[dbInit] Статус "deferred" успешно добавлен в enum doma_application_status');
      } catch (error) {
        // Если ошибка "already exists" или "duplicate" - это нормально
        if (error.message.includes('already exists') || 
            error.message.includes('duplicate') || 
            error.message.includes('уже существует')) {
          console.log('[dbInit] Статус "deferred" уже существует в enum doma_application_status');
        } else if (error.message.includes('cannot be executed inside a transaction block') ||
                   error.message.includes('не может быть выполнена внутри блока транзакции')) {
          // Если не может быть выполнено в транзакции, используем отдельное подключение
          console.log('[dbInit] ALTER TYPE не может быть выполнен в транзакции, используем отдельное подключение...');
          const { Pool } = require('pg');
          const tempPool = new Pool({ connectionString: process.env.DATABASE_URL });
          try {
            const tempClient = await tempPool.connect();
            await tempClient.query(`ALTER TYPE doma_application_status ADD VALUE 'deferred'`);
            tempClient.release();
            console.log('[dbInit] Статус "deferred" успешно добавлен через отдельное подключение');
          } catch (tempError) {
            if (tempError.message.includes('already exists') || 
                tempError.message.includes('duplicate') ||
                tempError.message.includes('уже существует')) {
              console.log('[dbInit] Статус "deferred" уже существует');
            } else {
              console.warn('[dbInit] Ошибка при добавлении статуса через отдельное подключение:', tempError.message);
            }
          } finally {
            await tempPool.end();
          }
        } else {
          console.warn('[dbInit] Неожиданная ошибка при добавлении статуса "deferred":', error.message);
        }
      }
    } else {
      console.log('[dbInit] Статус "deferred" уже существует в enum doma_application_status');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции добавления статуса "deferred":', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для отслеживания производительности (поля в applications и таблицы статистики)
 */
async function applyDomaMappingsMigration(client) {
  try {
    console.log('[dbInit] Применяем миграцию для сопоставлений Doma.AI...');
    
    const migrationSql = fs.readFileSync(
      path.join(__dirname, 'migrate_doma_mappings.sql'),
      'utf8'
    );
    
    await client.query(migrationSql);
    
    // Создаём функции нормализации, если их нет
    await client.query(`
      CREATE OR REPLACE FUNCTION normalize_address(addr TEXT) RETURNS TEXT AS $$
      BEGIN
        RETURN lower(trim(regexp_replace(regexp_replace(regexp_replace(regexp_replace(
          regexp_replace(addr, 'г\\.', 'г ', 'g'),
          'ул\\.', 'ул ', 'g'),
          'д\\.', 'д ', 'g'),
          '[.,]', '', 'g'),
          '\\s+', ' ', 'g')));
      END;
      $$ LANGUAGE plpgsql IMMUTABLE;
    `);
    
    await client.query(`
      CREATE OR REPLACE FUNCTION normalize_name(n TEXT) RETURNS TEXT AS $$
      BEGIN
        RETURN lower(trim(regexp_replace(n, '\\s+', ' ', 'g')));
      END;
      $$ LANGUAGE plpgsql IMMUTABLE;
    `);
    
    console.log('[dbInit] Миграция сопоставлений Doma.AI применена успешно');
  } catch (err) {
    if (err.message.includes('already exists') || err.message.includes('duplicate')) {
      console.log('[dbInit] Таблицы сопоставлений Doma.AI уже существуют');
    } else {
      console.error('[dbInit] Ошибка при применении миграции сопоставлений Doma.AI:', err.message);
    }
  }
}

async function applyPerformanceTrackingMigration(client) {
  try {
    // Проверяем наличие колонки building_id
    const buildingIdCheck = await client.query(`
      SELECT column_name 
      FROM information_schema.columns 
      WHERE table_name = 'applications' AND column_name = 'building_id'
    `);

    if (buildingIdCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем поля для отслеживания производительности в таблицу applications...');
      
      await client.query(`
        ALTER TABLE applications 
        ADD COLUMN IF NOT EXISTS building_id VARCHAR(50) REFERENCES buildings(id) ON DELETE SET NULL
      `);
      
      await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_building ON applications(building_id)`);
      console.log('[dbInit] Поле building_id добавлено');
    }

    // Проверяем наличие колонки employee_id
    const employeeIdCheck = await client.query(`
      SELECT column_name 
      FROM information_schema.columns 
      WHERE table_name = 'applications' AND column_name = 'employee_id'
    `);

    if (employeeIdCheck.rows.length === 0) {
      await client.query(`
        ALTER TABLE applications 
        ADD COLUMN IF NOT EXISTS employee_id VARCHAR(50) REFERENCES employees(id) ON DELETE SET NULL
      `);
      
      await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_employee ON applications(employee_id)`);
      console.log('[dbInit] Поле employee_id добавлено');
    }

    // Проверяем наличие колонки is_overdue
    const overdueCheck = await client.query(`
      SELECT column_name 
      FROM information_schema.columns 
      WHERE table_name = 'applications' AND column_name = 'is_overdue'
    `);

    if (overdueCheck.rows.length === 0) {
      await client.query(`
        ALTER TABLE applications 
        ADD COLUMN IF NOT EXISTS is_overdue BOOLEAN NOT NULL DEFAULT FALSE
      `);
      
      await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_overdue ON applications(is_overdue)`);
      console.log('[dbInit] Поле is_overdue добавлено');
    }

    // Проверяем наличие колонки updated_at
    const updatedAtCheck = await client.query(`
      SELECT column_name 
      FROM information_schema.columns 
      WHERE table_name = 'applications' AND column_name = 'updated_at'
    `);

    if (updatedAtCheck.rows.length === 0) {
      await client.query(`
        ALTER TABLE applications 
        ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
      `);
      console.log('[dbInit] Поле updated_at добавлено');
    }

    // Создаём дополнительные индексы
    await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_performer ON applications(performer_name)`);
    await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_status ON applications(status)`);
    await client.query(`CREATE INDEX IF NOT EXISTS idx_applications_deadline ON applications(deadline_at)`);

    // Создаём таблицы для статистики производительности
    const employeeStatsCheck = await client.query(`
      SELECT table_name 
      FROM information_schema.tables 
      WHERE table_schema = 'public' AND table_name = 'employee_performance_stats'
    `);

    if (employeeStatsCheck.rows.length === 0) {
      console.log('[dbInit] Создаём таблицу employee_performance_stats...');
      await client.query(`
        CREATE TABLE employee_performance_stats (
          id                BIGSERIAL PRIMARY KEY,
          employee_name     TEXT NOT NULL,
          period_start      DATE NOT NULL,
          period_end        DATE NOT NULL,
          total_assigned    INTEGER NOT NULL DEFAULT 0,
          total_completed   INTEGER NOT NULL DEFAULT 0,
          total_overdue     INTEGER NOT NULL DEFAULT 0,
          total_in_progress INTEGER NOT NULL DEFAULT 0,
          total_deferred    INTEGER NOT NULL DEFAULT 0,
          completion_rate   NUMERIC(5, 2) NOT NULL DEFAULT 0,
          overdue_rate      NUMERIC(5, 2) NOT NULL DEFAULT 0,
          performance_score NUMERIC(5, 2) NOT NULL DEFAULT 0,
          district_id       VARCHAR(50) REFERENCES districts(id) ON DELETE SET NULL,
          calculated_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(employee_name, period_start, period_end)
        )
      `);
      
      await client.query(`CREATE INDEX IF NOT EXISTS idx_performance_stats_employee ON employee_performance_stats(employee_name)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_performance_stats_district ON employee_performance_stats(district_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_performance_stats_period ON employee_performance_stats(period_start, period_end)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_performance_stats_score ON employee_performance_stats(performance_score DESC)`);
      console.log('[dbInit] Таблица employee_performance_stats создана');
    }

    const districtStatsCheck = await client.query(`
      SELECT table_name 
      FROM information_schema.tables 
      WHERE table_schema = 'public' AND table_name = 'district_performance_stats'
    `);

    if (districtStatsCheck.rows.length === 0) {
      console.log('[dbInit] Создаём таблицу district_performance_stats...');
      await client.query(`
        CREATE TABLE district_performance_stats (
          id                BIGSERIAL PRIMARY KEY,
          district_id       VARCHAR(50) NOT NULL REFERENCES districts(id) ON DELETE CASCADE,
          period_start      DATE NOT NULL,
          period_end        DATE NOT NULL,
          total_applications INTEGER NOT NULL DEFAULT 0,
          total_completed    INTEGER NOT NULL DEFAULT 0,
          total_overdue      INTEGER NOT NULL DEFAULT 0,
          total_in_progress  INTEGER NOT NULL DEFAULT 0,
          completion_rate    NUMERIC(5, 2) NOT NULL DEFAULT 0,
          overdue_rate       NUMERIC(5, 2) NOT NULL DEFAULT 0,
          average_score      NUMERIC(5, 2) NOT NULL DEFAULT 0,
          calculated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          updated_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          UNIQUE(district_id, period_start, period_end)
        )
      `);
      
      await client.query(`CREATE INDEX IF NOT EXISTS idx_district_stats_district ON district_performance_stats(district_id)`);
      await client.query(`CREATE INDEX IF NOT EXISTS idx_district_stats_period ON district_performance_stats(period_start, period_end)`);
      console.log('[dbInit] Таблица district_performance_stats создана');
    }

    console.log('[dbInit] Миграция отслеживания производительности применена успешно');
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции отслеживания производительности:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }
}

/**
 * Применение миграции для добавления поля inventory в таблицу districts
 */
async function applyDistrictsInventoryMigration(client) {
  try {
    // Проверяем наличие колонки inventory в таблице districts
    const columnCheck = await client.query(
      `SELECT column_name 
       FROM information_schema.columns 
       WHERE table_schema = 'public' 
       AND table_name = 'districts' 
       AND column_name = 'inventory'`
    );

    if (columnCheck.rows.length === 0) {
      console.log('[dbInit] Добавляем поле inventory в таблицу districts...');
      
      // Читаем SQL миграцию
      const migrationPath = path.join(__dirname, 'migrations', 'add_inventory_to_districts.sql');
      if (fs.existsSync(migrationPath)) {
        const migrationSQL = fs.readFileSync(migrationPath, 'utf8');
        await client.query(migrationSQL);
        console.log('[dbInit] Поле inventory успешно добавлено в districts');
      } else {
        // Создаем поле вручную, если файл не найден
        await client.query(`
          ALTER TABLE districts 
          ADD COLUMN IF NOT EXISTS inventory JSONB DEFAULT '[]'::jsonb
        `);
        console.log('[dbInit] Поле inventory добавлено в districts (вручную)');
      }
    } else {
      console.log('[dbInit] Поле inventory уже существует в districts');
    }
  } catch (error) {
    console.warn('[dbInit] Ошибка при применении миграции inventory для districts:', error.message);
    // Не прерываем выполнение, т.к. это не критично
  }

  // Миграция: task_id в work_photos (привязка фото отчёта до/после к задаче)
  try {
    const taskIdMigrationPath = path.join(__dirname, 'migrations', 'add_task_id_to_work_photos.sql');
    if (fs.existsSync(taskIdMigrationPath)) {
      const migrationSQL = fs.readFileSync(taskIdMigrationPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция add_task_id_to_work_photos.sql применена успешно');
    }
  } catch (err) {
    if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции add_task_id_to_work_photos.sql:', err.message);
    }
  }

  // Миграция: source и поля для ручной карточки заявки (диспетчерская)
  try {
    const appManualPath = path.join(__dirname, 'migrations', 'add_applications_manual_fields.sql');
    if (fs.existsSync(appManualPath)) {
      const migrationSQL = fs.readFileSync(appManualPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция add_applications_manual_fields.sql применена успешно');
    }
  } catch (err) {
    if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции add_applications_manual_fields.sql:', err.message);
    }
  }

  // Миграция: журнал отключений (outages)
  try {
    const outagesPath = path.join(__dirname, 'migrations', 'create_outages.sql');
    if (fs.existsSync(outagesPath)) {
      const migrationSQL = fs.readFileSync(outagesPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция create_outages.sql применена успешно');
    }
  } catch (err) {
    if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции create_outages.sql:', err.message);
    }
  }

  // Миграция: доп. поля отключений (категория, тип работ, resident_message)
  try {
    const addOutagePath = path.join(__dirname, 'migrations', 'add_outage_fields.sql');
    if (fs.existsSync(addOutagePath)) {
      const migrationSQL = fs.readFileSync(addOutagePath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция add_outage_fields.sql применена успешно');
    }
  } catch (err) {
    if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции add_outage_fields.sql:', err.message);
    }
  }

  // Миграция: комментарии и история заявок
  try {
    const commentsHistoryPath = path.join(__dirname, 'migrations', 'create_application_comments_history.sql');
    if (fs.existsSync(commentsHistoryPath)) {
      const migrationSQL = fs.readFileSync(commentsHistoryPath, 'utf8');
      await client.query(migrationSQL);
      console.log('[dbInit] Миграция create_application_comments_history.sql применена успешно');
    }
  } catch (err) {
    if (!err.message.includes('already exists') && !err.message.includes('duplicate')) {
      console.warn('[dbInit] Ошибка при применении миграции create_application_comments_history.sql:', err.message);
    }
  }
}

/**
 * Миграция: таблицы для ИИ-чата (диалоги и сообщения по пользователям)
 */
async function applyAIChatMigration(client) {
  try {
    const tableCheck = await client.query(`
      SELECT table_name FROM information_schema.tables
      WHERE table_schema = 'public' AND table_name = 'ai_conversations'
    `);
    if (tableCheck.rows.length > 0) {
      console.log('[dbInit] Таблицы ИИ-чата уже существуют');
      return;
    }
    console.log('[dbInit] Создаём таблицы ИИ-чата...');
    await client.query(`
      CREATE TABLE ai_conversations (
        id          BIGSERIAL PRIMARY KEY,
        user_id     BIGINT NOT NULL REFERENCES portal_users(id) ON DELETE CASCADE,
        title       TEXT,
        created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
      );
      CREATE INDEX IF NOT EXISTS idx_ai_conversations_user ON ai_conversations(user_id);

      CREATE TABLE ai_messages (
        id               BIGSERIAL PRIMARY KEY,
        conversation_id  BIGINT NOT NULL REFERENCES ai_conversations(id) ON DELETE CASCADE,
        role             TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
        content          TEXT NOT NULL DEFAULT '',
        tool_calls_json  JSONB,
        created_at       TIMESTAMPTZ NOT NULL DEFAULT NOW()
      );
      CREATE INDEX IF NOT EXISTS idx_ai_messages_conversation ON ai_messages(conversation_id);
    `);
    console.log('[dbInit] Таблицы ИИ-чата созданы успешно');
  } catch (err) {
    if (err.message.includes('already exists') || err.message.includes('duplicate')) {
      console.log('[dbInit] Таблицы ИИ-чата уже существуют');
    } else {
      console.error('[dbInit] Ошибка миграции ИИ-чата:', err.message);
      throw err;
    }
  }
}

/**
 * Таблица уведомлений для колокольчика (только для затронутых пользователей)
 */
async function applyNotificationsMigration(client) {
  try {
    const check = await client.query(
      `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'notifications'`
    );
    if (check.rows.length > 0) {
      console.log('[dbInit] Таблица notifications уже существует');
      return;
    }
    await client.query(`
      CREATE TABLE notifications (
        id          BIGSERIAL PRIMARY KEY,
        user_id     BIGINT NOT NULL REFERENCES portal_users(id) ON DELETE CASCADE,
        type        VARCHAR(80) NOT NULL,
        title       TEXT NOT NULL,
        body        TEXT,
        entity_type VARCHAR(80),
        entity_id   TEXT,
        payload     JSONB,
        read_at     TIMESTAMPTZ,
        created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
      )
    `);
    await client.query(`CREATE INDEX IF NOT EXISTS idx_notifications_user_read_created ON notifications(user_id, read_at, created_at DESC)`);
    await client.query(`CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id)`);
    console.log('[dbInit] Таблица notifications создана');
  } catch (err) {
    console.warn('[dbInit] Ошибка миграции notifications:', err.message);
  }
}

module.exports = { initializeDatabase, checkDatabaseIntegrity };
