import mysql from 'mysql2/promise'; import XLSX from 'xlsx'; import dotenv from 'dotenv'; dotenv.config(); const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env; async function run() { const pool = mysql.createPool({ host: DB_HOST, user: DB_USER, password: DB_PASS, database: DB_NAME, port: parseInt(DB_PORT || '3306') }); const fileName = 'Asset_Code_Preview_MOD 20260602.xlsx'; console.log(`📖 Reading modified Excel: ${fileName}`); const workbook = XLSX.readFile(fileName); // 1. Fetch current DB state for matching console.log('📡 Fetching current DB state...'); const [dbPcs] = await pool.query('SELECT * FROM asset_pc'); const [dbServers] = await pool.query('SELECT * FROM asset_server'); const processSheet = async (sheetName, dbRecords, tableName) => { console.log(`🔍 Processing sheet: ${sheetName}`); const sheet = workbook.Sheets[sheetName]; if (!sheet) { console.warn(`⚠️ Sheet ${sheetName} not found.`); return; } const data = XLSX.utils.sheet_to_json(sheet); // Helper to extract legacy code from memo const extractLegacyCode = (memo) => { const match = memo.match(/\[Legacy:\s*([^\]]+)\]/); return match ? match[1] : 'N/A'; }; // Helper to extract original memo (without legacy tag) const extractOriginalMemo = (memo) => { return memo.replace(/\[Legacy:\s*[^\]]+\]\s*/, '').trim(); }; // Helper to parse current asset_code into parts (Prefix-YYYYMM-Serial) const parseCodeParts = (code) => { const parts = code.split('-'); if (parts.length >= 3) { return { prefix: parts[0], yyyymm: parts[1], yyyy: parts[1].substring(0, 4), // Extract Year serial: parts[parts.length - 1] }; } return { prefix: 'ETC', yyyymm: '000000', yyyy: '0000', serial: '0000' }; }; // 2. Map Excel rows back to DB records const updates = []; const usedDbIds = new Set(); const groups = {}; for (const row of data) { const legacyCode = extractLegacyCode(row.memo || ''); const originalMemo = extractOriginalMemo(row.memo || ''); let match = null; if (legacyCode !== 'N/A') { match = dbRecords.find(db => !usedDbIds.has(db.id) && (db.asset_code || 'N/A') === legacyCode); } if (!match) { match = dbRecords.find(db => !usedDbIds.has(db.id) && db.asset_type === row.asset_type && (db.asset_purpose || '') === row.asset_purpose); } if (!match) { match = dbRecords.find(db => !usedDbIds.has(db.id) && db.asset_type === row.asset_type); } if (match) { usedDbIds.add(match.id); const parts = parseCodeParts(row.asset_code); // Group by Prefix and Year (YYYY) const groupKey = `${parts.prefix}-${parts.yyyy}`; if (!groups[groupKey]) groups[groupKey] = []; groups[groupKey].push({ dbId: match.id, originalRow: row, yyyymm: parts.yyyymm, // Keep the specific month for the code requestedSerial: parts.serial }); } } // 3. Resolve Duplicates and Re-sequence BY YEAR console.log(`⚖️ Resolving duplicates for ${sheetName} (Yearly Sequencing)...`); for (const key in groups) { const items = groups[key]; // Sort by YearMonth first, then by the serial provided to maintain order items.sort((a, b) => { const monthCompare = a.yyyymm.localeCompare(b.yyyymm); if (monthCompare !== 0) return monthCompare; return a.requestedSerial.localeCompare(b.requestedSerial); }); items.forEach((item, index) => { const newSerial = String(index + 1).padStart(4, '0'); // Format remains Prefix-YYYYMM-Serial const finalCode = `${item.originalRow.asset_code.split('-').slice(0, 2).join('-')}-${newSerial}`; updates.push({ id: item.dbId, asset_code: finalCode, memo: extractOriginalMemo(item.originalRow.memo || '') }); }); } // 4. Perform DB Updates console.log(`💾 Updating ${tableName} in DB...`); const connection = await pool.getConnection(); try { await connection.beginTransaction(); // IMPORTANT: To avoid UNIQUE constraint errors during the update process, // we first set all asset_codes to a temporary unique value. console.log(`🔄 Clearing existing codes in ${tableName} for safe update...`); await connection.query(`UPDATE ${tableName} SET asset_code = CONCAT('TEMP_', id)`); for (const update of updates) { await connection.query( `UPDATE ${tableName} SET asset_code = ?, memo = ? WHERE id = ?`, [update.asset_code, update.memo, update.id] ); } await connection.commit(); console.log(`✅ Updated ${updates.length} records in ${tableName}.`); } catch (err) { await connection.rollback(); console.error(`❌ Failed to update ${tableName}:`, err); } finally { connection.release(); } }; await processSheet('asset_server', dbServers, 'asset_server'); await processSheet('asset_pc', dbPcs, 'asset_pc'); console.log('🏁 All updates completed.'); await pool.end(); } run().catch(console.error);