feat: enhance HW modal layout and Server list view columns
- 상세 모달 레이아웃 개선: 모델명과 메인보드 동일 행 배치, 중복 메인보드 필드 제거 - OS 컬럼 스키마 매핑 및 상세 모달 입력 폼 추가 - 모든 하드웨어(서버 포함)에서 HDD 1~4 노출되도록 pc-only 속성 제거 - 서버 리스트 뷰 레이아웃 개선: 자산유형(asset_type) 컬럼 추가 및 너비 조정 - 서버 리스트 모델/메인보드 통합 컬럼 노출 로직 개선 (model_name 우선 표시) - 자산코드 일괄 재부여 스크립트(batch_reformat_codes.js) 추가 및 유니크 제약조건 회피 로직 반영
This commit is contained in:
124
batch_reformat_codes.js
Normal file
124
batch_reformat_codes.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import mysql from 'mysql2/promise';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const TYPE_PREFIX_MAP = {
|
||||
'서버': 'SVR', '가상서버(VM)': 'VM', '워크스테이션': 'WKS', '서버PC': 'PC',
|
||||
'개인PC': 'PC', '공용PC': 'PC', '노트북': 'NBK', '태블릿': 'TAB',
|
||||
'NAS': 'NAS', 'DAS': 'DAS', '스토리지': 'STO', '스토리지 렉': 'STO',
|
||||
'스위치': 'NET', '방화벽': 'NET', '공유기': 'NET', '허브': 'NET', '네트워크': 'NET',
|
||||
'모니터': 'MNT', '프린터': 'PRT', '스캐너': 'SCN', '복합기': 'MFP', '빔프로젝터': 'PRJ', '화상회의장비': 'VCF', '업무지원장비': 'EQP',
|
||||
'CPU': 'CPU', 'HDD': 'HDD', 'RAM': 'RAM', 'GPU': 'GPU', 'SSD': 'SSD', '메인보드': 'MBD', '파워서플라이': 'PWR', '쿨러': 'CLR', '케이스': 'CAS', 'PC부품': 'PRT',
|
||||
'드론': 'DRO', '측량장비': 'SUR', '보조기기': 'SUR', '공간정보장비': 'SUR',
|
||||
'책상': 'FRN', '의자': 'FRN', '캐비닛': 'FRN', '사무가구': 'FRN',
|
||||
'구독SW': 'SW', '영구SW': 'SW', '외부': 'SW', '내부': 'INT',
|
||||
'선물': 'GFT', 'VIP': 'VIP'
|
||||
};
|
||||
|
||||
function formatPurchaseDate(date) {
|
||||
if (!date) return '000000';
|
||||
let s = String(date).replace(/[^0-9]/g, '');
|
||||
if (s.length >= 6) {
|
||||
return s.substring(0, 6);
|
||||
}
|
||||
return '000000';
|
||||
}
|
||||
|
||||
async function reformatAllCodes() {
|
||||
const connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
port: parseInt(process.env.DB_PORT || '3306')
|
||||
});
|
||||
|
||||
try {
|
||||
const tables = [
|
||||
'asset_pc', 'asset_server', 'asset_network', 'asset_storage',
|
||||
'asset_equipment', 'asset_survey', 'asset_pc_parts', 'asset_office_supplies',
|
||||
'asset_sw_external', 'asset_sw_internal', 'asset_vip'
|
||||
];
|
||||
|
||||
let allAssets = [];
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
const [rows] = await connection.query(`SELECT * FROM ${table}`);
|
||||
allAssets = allAssets.concat(rows.map(r => ({ ...r, sourceTable: table })));
|
||||
} catch (err) {
|
||||
if (err.code === 'ER_NO_SUCH_TABLE') {
|
||||
console.log(`Skipping missing table: ${table}`);
|
||||
} else {
|
||||
console.error(`Error querying ${table}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Total assets loaded: ${allAssets.length}`);
|
||||
|
||||
// Process each asset
|
||||
const processed = allAssets.map(a => {
|
||||
// 1. Determine prefix
|
||||
let prefix = 'AST';
|
||||
if (a.asset_type && TYPE_PREFIX_MAP[a.asset_type]) {
|
||||
prefix = TYPE_PREFIX_MAP[a.asset_type];
|
||||
} else if (a.category && TYPE_PREFIX_MAP[a.category]) {
|
||||
prefix = TYPE_PREFIX_MAP[a.category];
|
||||
} else if (a.sourceTable === 'asset_sw_external') prefix = 'SW';
|
||||
else if (a.sourceTable === 'asset_sw_internal') prefix = 'INT';
|
||||
|
||||
// 2. Determine YYYYMM
|
||||
const dateStr = a.purchase_date || a.start_date || ''; // start_date for SW
|
||||
const yyyymm = formatPurchaseDate(dateStr);
|
||||
|
||||
return { ...a, prefix, yyyymm };
|
||||
});
|
||||
|
||||
// Group by Prefix
|
||||
const groups = {};
|
||||
processed.forEach(a => {
|
||||
if (!groups[a.prefix]) groups[a.prefix] = [];
|
||||
groups[a.prefix].push(a);
|
||||
});
|
||||
|
||||
// Start renaming
|
||||
for (const prefix in groups) {
|
||||
const items = groups[prefix];
|
||||
|
||||
// Sort logic to maintain some order (by date then id)
|
||||
items.sort((a, b) => {
|
||||
if (a.yyyymm !== b.yyyymm) return a.yyyymm.localeCompare(b.yyyymm);
|
||||
return String(a.id).localeCompare(String(b.id));
|
||||
});
|
||||
|
||||
console.log(`Processing group ${prefix}: ${items.length} items`);
|
||||
|
||||
// Temporary rename to avoid UNIQUE constraint conflicts during sequential updates
|
||||
for (const item of items) {
|
||||
const tempCode = `TEMP-${Math.random().toString(36).substring(2, 10)}-${item.id}`;
|
||||
await connection.query(`UPDATE ${item.sourceTable} SET asset_code = ? WHERE id = ?`, [tempCode, item.id]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const serial = String(i + 1).padStart(4, '0'); // SVR-202209-0001
|
||||
|
||||
// Some formats might want 3 or 4 digits. Defaulting to 4.
|
||||
const newCode = `${prefix}-${item.yyyymm}-${serial}`;
|
||||
|
||||
await connection.query(`UPDATE ${item.sourceTable} SET asset_code = ? WHERE id = ?`, [newCode, item.id]);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Asset codes reformatted successfully.');
|
||||
|
||||
} catch (err) {
|
||||
console.error('❌ Reformatting failed:', err);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
reformatAllCodes();
|
||||
Reference in New Issue
Block a user