Files
ITAM/scratch/db_migrate.cjs

190 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const mysql = require('mysql2/promise');
const fs = require('fs');
require('dotenv').config();
function getCleanMapKey(path) {
let clean = path.replace('img/location_photo/', '').replace('.png', '');
clean = clean.replace('서관', 'W').replace('동관', 'E');
clean = clean.replace('한맥빌딩/MDF실/MDF_', 'HAN-MDF-');
clean = clean.replace('기술개발센터/서버실/서버실_', 'DEV-SVR-');
clean = clean.replace(/\//g, '-');
return clean;
}
function getLocationName(path) {
if (path.includes('IDC')) return 'IDC';
if (path.includes('한맥빌딩')) return '한맥빌딩';
if (path.includes('기술개발센터')) return '기술개발센터';
return '기타';
}
function getLocationDetail(path, idx) {
let clean = path.replace('img/location_photo/', '').replace('.png', '');
let parts = clean.split('/');
let lastPart = parts[parts.length - 1]; // e.g. "서관205", "MDF_1", "서버실_1"
return `${lastPart} 구역 자리 #${idx + 1}`;
}
async function main() {
console.log('🏁 Starting DB migration...');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
port: process.env.DB_PORT
});
const connection = await pool.getConnection();
try {
// 1. Create physical_locations table
console.log('⏳ Creating physical_locations table...');
await connection.query(`
CREATE TABLE IF NOT EXISTS physical_locations (
location_code VARCHAR(50) NOT NULL COMMENT '위치 식별 코드 (예: LOC-IDC-W205-001)',
location_name VARCHAR(100) NOT NULL COMMENT '물리 위치 대분류 (예: IDC 서관)',
location_detail VARCHAR(100) NOT NULL COMMENT '상세 위치/랙 번호 (예: 205호 1번 랙)',
map_image VARCHAR(150) NOT NULL COMMENT '해당 도면 파일 경로 (예: img/location_photo/IDC/서관205.png)',
map_x DECIMAL(5,2) NOT NULL COMMENT '도면 내 X 백분율 좌표',
map_y DECIMAL(5,2) NOT NULL COMMENT '도면 내 Y 백분율 좌표',
map_w DECIMAL(5,2) NOT NULL DEFAULT 4.00 COMMENT '도면 내 박스 너비(%)',
map_h DECIMAL(5,2) NOT NULL DEFAULT 4.00 COMMENT '도면 내 박스 높이(%)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (location_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
`);
console.log('✅ physical_locations table ready.');
// 2. Create asset_audit_pending table
console.log('⏳ Creating asset_audit_pending table...');
await connection.query(`
CREATE TABLE IF NOT EXISTS asset_audit_pending (
id INT AUTO_INCREMENT PRIMARY KEY,
asset_code VARCHAR(50) NOT NULL COMMENT '스캔된 자산 고유번호 (예: server_1779761946023_14)',
physical_location_code VARCHAR(50) NOT NULL COMMENT '스캔된 위치 마스터 코드 (예: LOC-IDC-W205-001)',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '상태: PENDING(대기), APPROVED(승인), REJECTED(반려)',
scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL COMMENT '승인/반려 처리 일시',
processed_by VARCHAR(50) NULL COMMENT '처리한 관리자',
CONSTRAINT fk_audit_physical FOREIGN KEY (physical_location_code) REFERENCES physical_locations(location_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
`);
console.log('✅ asset_audit_pending table ready.');
// 3. Add physical_location_code to asset_location
console.log('⏳ Checking physical_location_code column in asset_location...');
const [cols] = await connection.query('DESCRIBE asset_location');
const hasCol = cols.some(c => c.Field === 'physical_location_code');
if (!hasCol) {
await connection.query(`
ALTER TABLE asset_location
ADD COLUMN physical_location_code VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'physical_locations의 location_code FK'
`);
console.log('✅ physical_location_code column added with utf8mb4_unicode_ci collation.');
} else {
console.log(' physical_location_code column already exists. Enforcing collation...');
await connection.query(`
ALTER TABLE asset_location
MODIFY COLUMN physical_location_code VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'physical_locations의 location_code FK'
`);
console.log('✅ physical_location_code column collation enforced.');
}
// Add constraint if not exists
console.log('⏳ Checking foreign key constraint fk_asset_loc_physical...');
const [constraints] = await connection.query(`
SELECT CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_NAME = 'asset_location'
AND CONSTRAINT_NAME = 'fk_asset_loc_physical'
AND TABLE_SCHEMA = DATABASE()
`);
if (constraints.length === 0) {
console.log('⏳ Adding foreign key constraint...');
await connection.query(`
ALTER TABLE asset_location
ADD CONSTRAINT fk_asset_loc_physical
FOREIGN KEY (physical_location_code) REFERENCES physical_locations(location_code)
`);
console.log('✅ Foreign key constraint added.');
} else {
console.log(' Foreign key constraint already exists.');
}
// 4. Load map_config.json and migrate
console.log('⏳ Migrating map_config.json data to physical_locations...');
if (fs.existsSync('map_config.json')) {
const mapConfig = JSON.parse(fs.readFileSync('map_config.json', 'utf8') || '{}');
let insertCount = 0;
let syncCount = 0;
for (const [mapPath, boxes] of Object.entries(mapConfig)) {
const cleanKey = getCleanMapKey(mapPath);
const locName = getLocationName(mapPath);
for (let i = 0; i < boxes.length; i++) {
const box = boxes[i];
const padIdx = String(i + 1).padStart(3, '0');
const locCode = `LOC-${cleanKey}-${padIdx}`;
const locDetail = getLocationDetail(mapPath, i);
const bx = parseFloat(box.x);
const by = parseFloat(box.y);
const bw = parseFloat(box.w || 4.00);
const bh = parseFloat(box.h || 4.00);
// Insert into physical_locations (ignore if duplicate)
await connection.query(`
INSERT INTO physical_locations
(location_code, location_name, location_detail, map_image, map_x, map_y, map_w, map_h)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
location_name = VALUES(location_name),
location_detail = VALUES(location_detail),
map_image = VALUES(map_image),
map_x = VALUES(map_x),
map_y = VALUES(map_y),
map_w = VALUES(map_w),
map_h = VALUES(map_h)
`, [locCode, locName, locDetail, mapPath, bx, by, bw, bh]);
insertCount++;
// Sync database asset if box.asset_id exists
if (box.asset_id) {
const [rows] = await connection.query(
'SELECT id FROM asset_location WHERE asset_id = ? AND is_active = 1',
[box.asset_id]
);
if (rows.length > 0) {
await connection.query(
'UPDATE asset_location SET physical_location_code = ? WHERE asset_id = ? AND is_active = 1',
[locCode, box.asset_id]
);
syncCount++;
}
}
}
}
console.log(`✅ Migrated ${insertCount} physical locations and synced ${syncCount} existing assets.`);
} else {
console.log('⚠️ map_config.json not found, skipping initial migration.');
}
console.log('🎉 DB Migration successfully completed!');
} catch (err) {
console.error('❌ Migration failed:', err);
throw err;
} finally {
connection.release();
await pool.end();
}
}
main().catch(err => {
process.exit(1);
});