const mysql = require('mysql2/promise'); const path = require('path'); const fs = require('fs'); require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); const dbConfig = { host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, multipleStatements: true }; function outputProgress(data) { if (!data.status) { data = { status: 'running', ...data }; } console.log(JSON.stringify(data)); } // Explicitly define all metrics-related tables const METRICS_TABLES = [ 'brand_metrics', 'brand_time_metrics', 'category_forecasts', 'category_metrics', 'category_sales_metrics', 'category_time_metrics', 'product_metrics', 'product_time_aggregates', 'sales_forecasts', 'sales_seasonality', 'temp_purchase_metrics', 'temp_sales_metrics', 'vendor_metrics', //before vendor_details for foreign key 'vendor_time_metrics', //before vendor_details for foreign key 'vendor_details' ]; // Config tables that must exist const CONFIG_TABLES = [ 'stock_thresholds', 'lead_time_thresholds', 'sales_velocity_config', 'abc_classification_config', 'safety_stock_config', 'turnover_config' ]; // Core tables that must exist const REQUIRED_CORE_TABLES = [ 'products', 'orders', 'purchase_orders' ]; // Split SQL into individual statements function splitSQLStatements(sql) { sql = sql.replace(/\r\n/g, '\n'); let statements = []; let currentStatement = ''; let inString = false; let stringChar = ''; for (let i = 0; i < sql.length; i++) { const char = sql[i]; const nextChar = sql[i + 1] || ''; if ((char === "'" || char === '"') && sql[i - 1] !== '\\') { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar) { inString = false; } } if (!inString && char === '-' && nextChar === '-') { while (i < sql.length && sql[i] !== '\n') i++; continue; } if (!inString && char === '/' && nextChar === '*') { i += 2; while (i < sql.length && (sql[i] !== '*' || sql[i + 1] !== '/')) i++; i++; continue; } if (!inString && char === ';') { if (currentStatement.trim()) { statements.push(currentStatement.trim()); } currentStatement = ''; } else { currentStatement += char; } } if (currentStatement.trim()) { statements.push(currentStatement.trim()); } return statements; } async function resetMetrics() { let connection; try { outputProgress({ operation: 'Starting metrics reset', message: 'Connecting to database...' }); connection = await mysql.createConnection(dbConfig); await connection.beginTransaction(); // Verify required core tables exist outputProgress({ operation: 'Verifying core tables', message: 'Checking required tables exist...' }); const [tables] = await connection.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name IN (?) `, [REQUIRED_CORE_TABLES]); const existingCoreTables = tables.map(t => t.table_name); const missingCoreTables = REQUIRED_CORE_TABLES.filter(t => !existingCoreTables.includes(t)); if (missingCoreTables.length > 0) { throw new Error(`Required core tables missing: ${missingCoreTables.join(', ')}`); } // Verify config tables exist outputProgress({ operation: 'Verifying config tables', message: 'Checking configuration tables exist...' }); const [configTables] = await connection.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name IN (?) `, [CONFIG_TABLES]); const existingConfigTables = configTables.map(t => t.table_name); const missingConfigTables = CONFIG_TABLES.filter(t => !existingConfigTables.includes(t)); if (missingConfigTables.length > 0) { throw new Error(`Required config tables missing: ${missingConfigTables.join(', ')}`); } // Drop all metrics tables outputProgress({ operation: 'Dropping metrics tables', message: 'Removing existing metrics tables...' }); for (const table of METRICS_TABLES) { try { await connection.query(`DROP TABLE IF EXISTS ${table}`); outputProgress({ operation: 'Table dropped', message: `Successfully dropped table: ${table}` }); } catch (err) { outputProgress({ status: 'error', operation: 'Drop table error', message: `Error dropping table ${table}: ${err.message}` }); throw err; } } // Read metrics schema outputProgress({ operation: 'Reading schema', message: 'Loading metrics schema file...' }); const schemaPath = path.resolve(__dirname, '../db/metrics-schema.sql'); if (!fs.existsSync(schemaPath)) { throw new Error(`Schema file not found at: ${schemaPath}`); } const schemaSQL = fs.readFileSync(schemaPath, 'utf8'); const statements = splitSQLStatements(schemaSQL); outputProgress({ operation: 'Schema loaded', message: `Found ${statements.length} SQL statements to execute` }); // Execute schema statements for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; try { const [result] = await connection.query(stmt); // Check for warnings const [warnings] = await connection.query('SHOW WARNINGS'); if (warnings && warnings.length > 0) { outputProgress({ status: 'warning', operation: 'SQL Warning', message: warnings }); } outputProgress({ operation: 'SQL Progress', message: { statement: i + 1, total: statements.length, preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : ''), affectedRows: result.affectedRows } }); } catch (sqlError) { outputProgress({ status: 'error', operation: 'SQL Error', message: { error: sqlError.message, sqlState: sqlError.sqlState, errno: sqlError.errno, statement: stmt, statementNumber: i + 1 } }); throw sqlError; } } // Verify metrics tables were created outputProgress({ operation: 'Verifying metrics tables', message: 'Checking all metrics tables were created...' }); const [metricsTablesResult] = await connection.query(` SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name IN (?) `, [METRICS_TABLES]); const existingMetricsTables = metricsTablesResult.map(t => t.table_name); const missingMetricsTables = METRICS_TABLES.filter(t => !existingMetricsTables.includes(t)); if (missingMetricsTables.length > 0) { throw new Error(`Failed to create metrics tables: ${missingMetricsTables.join(', ')}`); } await connection.commit(); outputProgress({ status: 'complete', operation: 'Reset complete', message: 'All metrics tables have been reset successfully' }); } catch (error) { outputProgress({ status: 'error', operation: 'Reset failed', message: error.message, stack: error.stack }); if (connection) { await connection.rollback(); } throw error; } finally { if (connection) { await connection.end(); } } } // Export if required as a module if (typeof module !== 'undefined' && module.exports) { module.exports = resetMetrics; } // Run if called from command line if (require.main === module) { resetMetrics().catch(error => { console.error('Error:', error); process.exit(1); }); }