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
This commit is contained in:
150
update_db_from_excel.js
Normal file
150
update_db_from_excel.js
Normal file
@@ -0,0 +1,150 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user