Files
ITAM/src/core/excelHandler.ts

278 lines
10 KiB
TypeScript

import * as XLSX from 'xlsx';
import { ASSET_SCHEMA } from './schema';
/**
* ITAM 엑셀 핸들러 (Database Synchronized Edition)
* 데이터베이스 실제 스키마 컬럼과 엑셀 헤더를 1:1로 일치시킵니다.
*/
export interface HardwareAsset {
[key: string]: any;
id: string;
}
export interface SoftwareAsset {
[key: string]: any;
id: string;
}
export interface SWUser {
id: string;
sw_id: string;
user_name: string;
dept: string;
corp: string;
[key: string]: any;
}
export interface HardwareLog {
id: string;
assetId?: string;
asset_id?: string;
date?: string;
log_date?: string;
created_at?: string;
details: string;
user?: string;
log_user?: string;
event_type?: string;
}
export interface MasterAssetData {
pc: HardwareAsset[];
server: HardwareAsset[];
storage: HardwareAsset[];
network: HardwareAsset[];
equipment: HardwareAsset[];
survey: HardwareAsset[];
pcParts: HardwareAsset[];
swInternal: SoftwareAsset[];
swExternal: SoftwareAsset[];
cloud: SoftwareAsset[];
domain: any[];
vip: HardwareAsset[];
officeSupplies: HardwareAsset[];
cost: any[];
swUsers: SWUser[];
logs: HardwareLog[];
[key: string]: any;
}
/**
* DB 컬럼 순서 및 구성 정의 (실제 DB 스키마 dump 기준)
*/
const DB_MAPPING: Record<string, (keyof typeof ASSET_SCHEMA)[]> = {
pc: [
'ASSET_TYPE', 'HW_STATUS', 'CURRENT_DEPT', 'PREV_DEPT', 'USER_POSITION',
'EMP_NO', 'CURRENT_USER',
'CPU', 'RAM', 'GPU', 'SSD1', 'SSD2', 'HDD1', 'HDD2', 'HDD3', 'HDD4', 'MAC_ADDR',
'MANAGER_MAIN', 'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
'PURCHASE_VENDOR', 'MEMO', 'MAINBOARD'
],
server: [
'ASSET_TYPE', 'MODEL_NAME', 'ASSET_PURPOSE', 'HW_STATUS',
'CURRENT_DEPT', 'CPU', 'RAM', 'GPU', 'SSD1', 'SSD2', 'HDD1', 'HDD2', 'IP_ADDR',
'REMOTE_TOOL', 'REMOTE_ID', 'REMOTE_PW', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
'MEMO', 'PREV_DEPT', 'MANAGER_SUB', 'IP_ADDR2', 'MONITORING', 'HDD3', 'HDD4', 'EMP_NO'
],
storage: [
'ASSET_TYPE', 'HW_STATUS', 'VOLUME', 'MODEL_NAME',
'EMP_NO', 'CURRENT_USER',
'SERIAL_NUM', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
'MEMO', 'CURRENT_DEPT', 'PREV_DEPT'
],
network: [
'PURCHASE_CORP', 'HW_STATUS', 'CURRENT_DEPT', 'PREV_DEPT',
'EMP_NO', 'CURRENT_USER',
'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
'MANAGER_SUB', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR', 'MEMO'
],
survey: [ // asset_survey (공간정보장비)
'HW_STATUS', 'ASSET_NAME', 'LOCATION', 'LOC_DETAIL',
'EMP_NO', 'CURRENT_USER',
'MANAGER_MAIN', 'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
'PURCHASE_VENDOR', 'MEMO'
],
pcParts: [
'HW_STATUS', 'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME', 'VOLUME',
'EMP_NO', 'CURRENT_USER',
'MONITOR_INCH', 'LOCATION', 'LOC_DETAIL', 'PURCHASE_CORP', 'PURCHASE_DATE',
'PURCHASE_AMOUNT', 'PURCHASE_VENDOR', 'MEMO'
],
equipment: [
'HW_STATUS', 'ASSET_STATUS', 'ASSET_TYPE', 'ASSET_MFR',
'EMP_NO', 'CURRENT_USER',
'MODEL_NAME', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
'MEMO'
],
officeSupplies: [ // asset_office_supplies (시설자산)
'HW_STATUS', 'ASSET_TYPE', 'ASSET_MFR', 'MODEL_NAME',
'EMP_NO', 'CURRENT_USER',
'ASSET_COUNT', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN', 'MANAGER_SUB',
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
'MEMO'
],
swInternal: [
'SW_FIELD', 'DEV_OBJ', 'SW_STATUS', 'SW_TYPE', 'MANAGER_MAIN',
'DEV_MGR', 'PLANNING_MGR', 'SALES_MGR', 'PURCHASE_CORP', 'MEMO'
],
swExternal: [
'PRODUCT_NAME', 'SW_TYPE', 'SW_STATUS', 'SW_FIELD', 'CURRENT_DEPT',
'PREV_DEPT', 'MANAGER_MAIN', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT',
'PURCHASE_VENDOR', 'EMAIL_ACCOUNT', 'MEMO', 'EMP_NO', 'CURRENT_USER'
],
cloud: [
'ASSET_PURPOSE', 'PURCHASE_METHOD', 'PURCHASE_VENDOR', 'PURCHASE_CORP',
'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'MANAGER_MAIN', 'MANAGER_SUB',
'MEMO', 'SW_ID', 'SW_PW'
],
domain: [
'DOMAIN_ADDR', 'ASSET_PURPOSE', 'PURCHASE_VENDOR', 'ASSET_TYPE',
'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'MANAGER_MAIN', 'MANAGER_SUB',
'MEMO'
],
cost: [
'ASSET_TYPE', 'ASSET_PURPOSE', 'LOCATION', 'LOC_DETAIL', 'MANAGER_MAIN',
'MANAGER_SUB', 'PURCHASE_CORP', 'PURCHASE_DATE', 'PURCHASE_AMOUNT', 'PURCHASE_VENDOR',
'EMAIL_ACCOUNT', 'EMAIL_PW', 'MEMO', 'EMP_NO', 'CURRENT_USER'
],
vip: [ // asset_vip (선물)
'ASSET_NAME', 'MODEL_NAME', 'LOCATION', 'LOC_DETAIL',
'PURCHASE_CORP', 'PURCHASE_DATE', 'EXPIRED_DATE', 'PURCHASE_VENDOR', 'MEMO'
]
};
export function downloadTemplate() {
const wb = XLSX.utils.book_new();
const tabConfigs = [
{ name: 'PC', key: 'pc' },
{ name: '서버', key: 'server' },
{ name: '스토리지', key: 'storage' },
{ name: '공간정보장비', key: 'survey' },
{ name: 'PC부품', key: 'pcParts' },
{ name: '네트워크', key: 'network' },
{ name: '업무지원장비', key: 'equipment' },
{ name: '내부SW', key: 'swInternal' },
{ name: '외부SW', key: 'swExternal' },
{ name: '클라우드', key: 'cloud' },
{ name: '도메인', key: 'domain' },
{ name: '비용관리', key: 'cost' },
{ name: '선물', key: 'vip' },
{ name: '시설자산', key: 'officeSupplies' }
];
tabConfigs.forEach(config => {
const keys = DB_MAPPING[config.key];
const headers = keys.map(k => ASSET_SCHEMA[k].ui);
const ws = XLSX.utils.aoa_to_sheet([headers]);
ws['!cols'] = Array(headers.length).fill({ wch: 20 });
XLSX.utils.book_append_sheet(wb, ws, config.name);
});
XLSX.writeFile(wb, 'itam_template_db_aligned.xlsx');
}
export function exportToExcel(masterData: MasterAssetData) {
const wb = XLSX.utils.book_new();
const exportConfigs = [
{ name: 'PC', list: masterData.pc, key: 'pc' },
{ name: '서버', list: masterData.server, key: 'server' },
{ name: '스토리지', list: masterData.storage, key: 'storage' },
{ name: '공간정보장비', list: masterData.survey || [], key: 'survey' },
{ name: 'PC부품', list: masterData.pcParts || [], key: 'pcParts' },
{ name: '네트워크', list: masterData.network || [], key: 'network' },
{ name: '업무지원장비', list: masterData.equipment || [], key: 'equipment' },
{ name: '내부SW', list: masterData.swInternal, key: 'swInternal' },
{ name: '외부SW', list: masterData.swExternal, key: 'swExternal' },
{ name: '클라우드', list: masterData.cloud || [], key: 'cloud' },
{ name: '도메인', list: masterData.domain || [], key: 'domain' },
{ name: '비용관리', list: masterData.cost || [], key: 'cost' },
{ name: '선물', list: masterData.vip || [], key: 'vip' },
{ name: '시설자산', list: masterData.officeSupplies || [], key: 'officeSupplies' }
];
exportConfigs.forEach(config => {
const schemaKeys = DB_MAPPING[config.key];
const headers = schemaKeys.map(k => ASSET_SCHEMA[k].ui);
const rows = config.list.map(asset =>
schemaKeys.map(k => {
const dbField = ASSET_SCHEMA[k].db;
return asset[dbField] || asset[ASSET_SCHEMA[k].key] || '';
})
);
const ws = XLSX.utils.aoa_to_sheet([headers, ...rows]);
XLSX.utils.book_append_sheet(wb, ws, config.name);
});
XLSX.writeFile(wb, `itam_export_${new Date().toISOString().split('T')[0]}.xlsx`);
}
export function formatExcelDate(val: any): string {
if (!val) return '';
if (typeof val === 'number') {
const date = new Date(Math.round((val - 25569) * 86400 * 1000));
return date.toISOString().split('T')[0];
}
if (typeof val === 'string') {
return val.replace(/\./g, '-').trim();
}
return String(val);
}
export async function parseExcel(file: File): Promise<any> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const workbook = XLSX.read(e.target?.result, { type: 'array' });
const parsedData: any = {};
workbook.SheetNames.forEach(sheetName => {
const ws = workbook.Sheets[sheetName];
const rows = XLSX.utils.sheet_to_json(ws, { defval: "" }) as any[];
const list: any[] = [];
rows.forEach(r => {
const data: any = { id: Math.random().toString(36).substring(2, 9) };
// Set default category based on sheet name
data['category'] = sheetName;
Object.keys(r).forEach(label => {
const schemaEntry = Object.values(ASSET_SCHEMA).find(s => s.ui === label);
const key = schemaEntry ? schemaEntry.db : label;
let val = r[label];
if (label.includes('일자') || label.includes('연월') || label.includes('만료일') || label.includes('시작일')) {
val = formatExcelDate(val);
}
data[key] = val;
});
list.push(data);
});
// Sheet Name Mapping back to state keys
const nameMap: Record<string, string> = {
'PC': 'pc', '서버': 'server', '스토리지': 'storage', '공간정보장비': 'survey',
'PC부품': 'pcParts', '네트워크': 'network', '업무지원장비': 'equipment',
'내부SW': 'swInternal', '외부SW': 'swExternal', '클라우드': 'cloud',
'도메인': 'domain', '비용관리': 'cost', '선물': 'vip', '시설자산': 'officeSupplies'
};
const stateKey = nameMap[sheetName] || sheetName;
if (list.length > 0) parsedData[stateKey] = list;
});
resolve(parsedData);
} catch (err) { reject(err); }
};
reader.readAsArrayBuffer(file);
});
}