Files
ITAM/update_db_from_excel.js
Taehoon a30f99f0ad feat: improve asset code generation and re-sequence assets by year
- Enhanced backend asset code generation logic to handle multiple tables
- Integrated asset code generation button in HWModal
- Included utility scripts for asset code migration and DB synchronization
- Resolved issues with missing purchase dates and duplicate asset codes
2026-06-02 14:40:06 +09:00

151 lines
5.2 KiB
JavaScript

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);