import pkg from 'xlsx'; import * as fs from 'fs'; import * as path from 'path'; const { readFile, utils } = pkg; // 임시 ID 생성 및 도우미 함수 const randomId = () => Math.random().toString(36).substring(2, 9); const CORPS = ['한맥', '삼안', '장헌', '장헌산업', 'PTC', '바론', '한라']; function cleanValue(val) { if (val === undefined || val === null) return '-'; const str = String(val).trim(); return str === '' ? '-' : str; } try { const workbook = readFile('c:/Project/HM ITAM/SampleData_PC.xlsx'); const sheet = workbook.Sheets[workbook.SheetNames[0]]; // header: 1로 읽어 2차원 배열을 획득 const rawRows = utils.sheet_to_json(sheet, { header: 1 }); // 첫 번째 행은 헤더이므로 제외 const dataRows = rawRows.slice(1); const parsedPCs = []; let pcIndex = 0; let designKihuckCount = 0; for (const row of dataRows) { // 빈 행 건너뛰기 (성명, 부서, 팀명 모두 비어있으면 데이터가 없는 행으로 판단) if (!row[0] && !row[1] && !row[2] && !row[3] && !row[4]) { continue; } const deptRaw = cleanValue(row[0]); const teamRaw = cleanValue(row[1]); const corpRaw = cleanValue(row[2]); // C열: 소속 (NEW) const jobRaw = cleanValue(row[3]); // D열: 직무 (밀림) const nameRaw = cleanValue(row[4]); // E열: 성명 (밀림) // 특정 사용자 제외 필터 if (nameRaw === '한치영' || nameRaw === '공용') { continue; } const posRaw = cleanValue(row[5]); // F열: 직급 (밀림) const mainboardRaw = cleanValue(row[6]); // G열: 메인보드 (밀림) const cpuRaw = cleanValue(row[7]); // H열: CPU (밀림) const cpuYearRaw = row[8]; // I열: CPU 출시연도 (밀림) const gpuRaw = cleanValue(row[9]); // J열: GPU (밀림) const gpuYearRaw = row[10]; // K열: GPU 출시연도 (밀림) const ramRaw = cleanValue(row[11]); // L열: RAM (밀림) const ssd1Raw = cleanValue(row[12]);// M열: SDD1 (밀림) const ssd2Raw = cleanValue(row[13]);// N열: SDD2 (밀림) const hdd1Raw = cleanValue(row[14]);// O열: HDD1 (밀림) const hdd2Raw = cleanValue(row[15]);// P열: HDD2 (밀림) const hdd3Raw = cleanValue(row[16]);// Q열: HDD3 (밀림) const hdd4Raw = cleanValue(row[17]);// R열: HDD4 (밀림) // W열(22번째 인덱스) -> 구매일자 const dateRaw = cleanValue(row[22]); // X열(23번째 인덱스) -> 비고 const memoRaw = cleanValue(row[23]); // 1. 법인 매핑 (엑셀 C열의 실제 소속 우선 사용, 없을 시 순환 지정) const purchase_corp = corpRaw !== '-' ? corpRaw : CORPS[pcIndex % CORPS.length]; // 2. 재고PC 판단 및 상태 설정 const isStock = teamRaw === '재고PC'; const hw_status = isStock ? '창고보관' : '운영중'; // 3. 성명 정제 let user_current = nameRaw; if (isStock) { // 재고PC인 경우 직무 컬럼(row[3])에 성명이 들어가 있음 user_current = jobRaw !== '-' ? jobRaw : '재고장비'; } // 4. 직무 정제 let user_position = jobRaw; if (isStock) { user_position = '재고PC'; } else if (user_position === '-' || user_position === 'undefined' || !user_position || ['안용주', '김민수', '심영표', '이수창A', '조병철', '윤진호', '김대영', '박정웅', '김유식'].includes(user_position)) { // 직무가 유효하지 않거나 이름인 경우 정제 if (nameRaw === '장종찬' || posRaw === '사장') { user_position = '기획자'; } else if (nameRaw === '노트북' || nameRaw === '공용') { user_position = '기획자'; } else { // 팀명/부서 기준 매핑 const combined = (deptRaw + ' ' + teamRaw).toUpperCase(); if (combined.includes('개발') || combined.includes('SOLUTION') || combined.includes('WEB') || combined.includes('ERP')) { user_position = '개발자'; } else if (combined.includes('BIM') || combined.includes('구조') || combined.includes('설계') || combined.includes('터널') || combined.includes('상하수도') || combined.includes('수자원') || combined.includes('건설') || combined.includes('CM')) { user_position = '엔지니어'; } else if (combined.includes('디자인') || combined.includes('GRAPHICS')) { user_position = '디자이너'; } else { user_position = '기획자'; } } } // 만약 직무가 'BIM모델러' 인 경우, 그대로 유지 if (jobRaw === 'BIM모델러') { user_position = 'BIM모델러'; } // 개발자/디자이너 세부 직무 분리 로직 적용 if (user_position === '개발자') { const nameUpper = nameRaw.trim(); const teamUpper = teamRaw.toUpperCase(); if (nameUpper === '조찬영' || nameUpper === '김용연') { user_position = 'AI 개발자'; } else if ( teamUpper.includes('그래픽스') || teamUpper.includes('MODELER') || teamUpper.includes('HMEG') || teamUpper.includes('EG-BIM') || teamUpper.includes('GSIM') || teamUpper.includes('STRANA') ) { user_position = '3D 개발자'; } else if ( teamUpper.includes('WEB') || teamUpper.includes('솔루션개발') || teamUpper.includes('ERP') || teamUpper.includes('전산') ) { user_position = '웹 개발자'; } else { user_position = '프로그램 개발자'; } } else if (user_position === '디자이너') { const teamUpper = teamRaw.toUpperCase(); if (teamUpper.includes('디자인셀')) { user_position = 'UXUI 디자이너'; } else if (teamUpper.includes('디자인기획')) { // 디자인기획팀 소속 중 약 40%는 3D 디자이너, 60%는 편집 디자이너 if (designKihuckCount % 10 < 4) { user_position = '3D 디자이너'; } else { user_position = '편집 디자이너'; } designKihuckCount++; } else { user_position = '편집 디자이너'; } } // 5. 구매일자 포맷 가공 (YYYY-MM) let purchase_date = '2022-01'; // 기본값 if (dateRaw !== '-') { if (dateRaw.length === 6 && !isNaN(dateRaw)) { purchase_date = `${dateRaw.substring(0, 4)}-${dateRaw.substring(4, 6)}`; } else if (dateRaw.length === 4 && !isNaN(dateRaw)) { purchase_date = `${dateRaw}-01`; } else { purchase_date = dateRaw; } } else if (cpuYearRaw && !isNaN(cpuYearRaw)) { purchase_date = `${cpuYearRaw}-01`; } // 6. 도입 금액(purchase_amount) 책정 let purchase_amount = '1500000'; const cpuUpper = cpuRaw.toUpperCase(); const gpuUpper = gpuRaw.toUpperCase(); if (cpuUpper.includes('I9') || cpuUpper.includes('RYZEN 9') || cpuUpper.includes('RYZEN9') || gpuUpper.includes('4080') || gpuUpper.includes('4090')) { purchase_amount = '3500000'; } else if (cpuUpper.includes('I7') || cpuUpper.includes('RYZEN 7') || cpuUpper.includes('RYZEN7') || gpuUpper.includes('3070') || gpuUpper.includes('4070') || gpuUpper.includes('A2000')) { purchase_amount = '2200000'; } else if (cpuUpper.includes('I5') || cpuUpper.includes('RYZEN 5') || cpuUpper.includes('RYZEN5') || gpuUpper.includes('3060') || gpuUpper.includes('2060')) { purchase_amount = '1500000'; } else if (cpuYearRaw && parseInt(cpuYearRaw) < 2020) { purchase_amount = '800000'; } else { purchase_amount = '950000'; } // 7. MAC 주소 생성 (16진수 포맷) const mac_address = `00:1A:2B:3C:4D:${pcIndex.toString(16).toUpperCase().padStart(2, '0')}`; parsedPCs.push({ id: randomId(), asset_type: '개인PC', purchase_corp, asset_code: 'PC-24' + String(pcIndex).padStart(3, '0'), purchase_date, user_current, user_position, current_dept: teamRaw !== '-' ? teamRaw : deptRaw, previous_dept: pcIndex % 8 === 0 ? '기획팀' : '-', location: '서울본사 7층', manager_primary: '김IT', manager_secondary: '이IT', model_name: mainboardRaw !== '-' ? mainboardRaw : '사내 표준 데스크톱', os: 'Windows 11 Pro', cpu: cpuRaw, gpu: gpuRaw, ram: ramRaw, ssd_1: ssd1Raw, ssd_2: ssd2Raw, ssd_3: '-', hdd_1: hdd1Raw, hdd_2: hdd2Raw, hdd_3: hdd3Raw, hdd_4: hdd4Raw, mainboard: mainboardRaw, ip_address: '192.168.0.' + (10 + (pcIndex % 240)), purchase_amount, purchase_vendor: 'LG전자/삼성전자/HP', approval_document: '2024_상반기_PC구매_' + pcIndex, memo: memoRaw !== '-' ? memoRaw : (isStock ? '재고 보유 분' : '임직원 지급용'), asset_name: `개인PC ${pcIndex + 1}`, mac_address, hw_status }); pcIndex++; } console.log(`Successfully parsed ${parsedPCs.length} PCs from excel file.`); // dummyData.ts 의 나머지 데이터(dummyServers 등)를 포함하여 전체 파일을 새로 씁니다. const newDummyDataFileContent = `import { HardwareAsset, SoftwareAsset, SWUser, HardwareLog } from './excelHandler'; // 유틸리티: 랜덤 문자열 const randomId = () => Math.random().toString(36).substring(2, 9); // 유틸리티: 랜덤 년월 (YYYY-MM) (최근 10년) const randomPurchaseYM = () => { const currentYear = new Date().getFullYear(); const year = currentYear - Math.floor(Math.random() * 10); const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0'); return \`\${year}-\${month}\`; }; // 유틸리티: 랜덤 YYYY-MM-DD const randomDateStr = (maxYearsAgo = 10) => { const currentYear = new Date().getFullYear(); const year = currentYear - Math.floor(Math.random() * maxYearsAgo); const month = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0'); const day = String(Math.floor(Math.random() * 28) + 1).padStart(2, '0'); return \`\${year}-\${month}-\${day}\`; }; const CORPS = ['한맥', '삼안', '장헌', '장헌산업', 'PTC', '바론', '한라']; const getRandomCorp = () => CORPS[Math.floor(Math.random() * CORPS.length)]; // ──────────────────────────────────────────────────────── // 1. SampleData_PC.xlsx 에서 파싱된 PC 데이터 주입 // ──────────────────────────────────────────────────────── export const dummyPCs: any[] = ${JSON.stringify(parsedPCs, null, 2)}; // ──────────────────────────────────────────────────────── // 2. 기타 자산 더미 데이터 (서버, 스토리지, 소프트웨어 등) // ──────────────────────────────────────────────────────── export const dummyServers: any[] = Array.from({ length: 15 }).map((_, i) => ({ id: randomId(), asset_type: '서버', type2: i % 2 === 0 ? '물리' : '가상', purchase_corp: getRandomCorp(), asset_code: \`SRV-24\${String(i).padStart(3, '0')}\`, purchase_date: randomPurchaseYM(), asset_purpose: i % 2 === 0 ? '운영 웹 서버' : '사내망 DB 서버', current_dept: '인프라팀', previous_dept: '-', location: 'IDC 센터 1-A', manager_primary: '박서버', manager_secondary: '최백업', ip_address: \`10.0.0.\${10 + i}\`, ip_address_2: \`192.168.100.\${10 + i}\`, remote_tool: 'RDP / SSH', remote_id: \`admin_\${i}\`, remote_pw: '********', model_name: 'Dell PowerEdge R750', os: 'Ubuntu 22.04 LTS', cpu: 'Intel Xeon Gold 6330', ram: '128GB', gpu: i % 3 === 0 ? 'NVIDIA A100' : '-', ssd_1: '1TB NVMe', ssd_2: '1TB NVMe', hdd_1: '4TB HDD', monitoring: 'Zabbix Agent', purchase_amount: '8500000', purchase_vendor: '델테크놀로지스', approval_document: \`2024_IDC_확장품의_\sign\${i}\`, memo: '서버 랙 3번 위치', asset_name: \`운영 서버 \${i+1}\`, mac_address: \`00:1A:2B:3C:4E:\${String(i).padStart(2, '0')}\`, hw_status: '운영중' })); export const dummyStorages: any[] = Array.from({ length: 8 }).map((_, i) => ({ id: randomId(), asset_type: '스토리지', purchase_corp: getRandomCorp(), asset_code: \`STR-24\${String(i).padStart(3, '0')}\`, asset_name: \`공용 스토리지 \${i+1}\`, location: 'IDC 센터 1-A', model_name: 'Synology RS4021xs+', volume: '100TB', manager_primary: '박서버', manager_secondary: '최백업', ip_address: \`10.0.0.\${50 + i}\`, mac_address: \`00:1A:2B:3C:4F:\${String(i).padStart(2, '0')}\`, purchase_date: randomPurchaseYM(), purchase_amount: '12000000', purchase_vendor: '시놀로지코리아', approval_document: \`2024_스토리지구매_\${i}\`, memo: '부서별 백업본 저장용', os: 'Synology DSM', asset_purpose: '데이터 백업', hw_status: '운영중' })); export const dummyEquips: any[] = Array.from({ length: 12 }).map((_, i) => ({ id: randomId(), asset_type: '전산비품', purchase_corp: getRandomCorp(), asset_code: \`EQ-24\${String(i).padStart(3, '0')}\`, asset_name: \`네트워크 스위치 \${i+1}\`, location: '전산실 랙 1', manager_primary: '네트워크담당자', ip_address: \`192.168.10.\${200 + i}\`, mac_address: \`00:1A:2B:3C:51:\${String(i).padStart(2, '0')}\`, os: 'Cisco IOS', purchase_date: randomPurchaseYM(), purchase_amount: '150000', purchase_vendor: '다나와', approval_document: \`2024_비품구매_\${i}\`, memo: '사내망 확장용', asset_purpose: '네트워크 분배' })); export const dummyMobiles: any[] = Array.from({ length: 15 }).map((_, i) => ({ id: randomId(), asset_type: '모바일기기', purchase_corp: getRandomCorp(), asset_code: \`MOB-24\${String(i).padStart(3, '0')}\`, asset_name: \`테스트용 단말기 \${i+1}\`, location: '개발2팀', manager_primary: '테스터', os: i % 2 === 0 ? 'Android 14' : 'iOS 17', purchase_date: randomPurchaseYM(), purchase_amount: '900000', purchase_vendor: '삼성전자/애플', approval_document: \`2024_모바일구매_\${i}\`, memo: '앱 호환성 테스트 전용', asset_purpose: 'QA 테스트', ip_address: \`192.168.1.\${10 + i}\`, mac_address: \`00:1A:2B:3C:50:\${String(i).padStart(2, '0')}\` })); export const dummySubSw: any[] = Array.from({ length: 10 }).map((_, i) => ({ id: randomId(), sw_type: '구독SW', sw_field: '업무용/협업', purchase_corp: getRandomCorp(), current_dept: '전사', product_name: \`Microsoft 365 E\${3 + (i%2)}\`, purchase_date: randomDateStr(3), start_date: randomDateStr(1), expired_date: randomDateStr(0), purchase_amount: '150000', asset_count: 50 + i * 5, email_account: \`admin\${i}@hmcorp.com\`, purchase_vendor: '소프트웨어인라이프', memo: '연간 계약 갱신 필요' })); export const dummyPermSw: any[] = Array.from({ length: 5 }).map((_, i) => ({ id: randomId(), sw_type: '영구SW', sw_field: '디자인/설계', purchase_corp: getRandomCorp(), current_dept: '디자인팀', product_name: \`AutoCAD 202\${i%4}\`, purchase_date: randomDateStr(5), start_date: randomDateStr(5), expired_date: '2099-12-31', purchase_amount: '3000000', asset_count: 2, email_account: \`design\${i}@hmcorp.com\`, purchase_vendor: '오토데스크 파트너', memo: 'USB 동글키 보관중' })); export const dummyCloud: any[] = Array.from({ length: 5 }).map((_, i) => ({ id: randomId(), sw_type: '클라우드', asset_mfr: i % 2 === 0 ? 'AWS' : 'GCP', purchase_corp: getRandomCorp(), current_dept: '개발팀', product_name: \`컴퓨팅 인스턴스 Type \${i}\`, email_account: \`awsadmin\${i}@hmcorp.com\`, purchase_method: '법인카드(신한 1234)', purchase_amount: \`\${500000 + i * 100000}\`, asset_count: 1, purchase_vendor: 'AWS/GCP', memo: '환율 변동에 따라 매월 상이함' })); export const dummyDomain: any[] = Array.from({ length: 5 }).map((_, i) => ({ id: randomId(), asset_type: '도메인', purchase_corp: getRandomCorp(), product_name: \`사내 운영 서비스 \${i+1}\`, domain_address: \`service\${i+1}.hmcorp.com\`, start_date: randomDateStr(4), expired_date: randomDateStr(0), purchase_amount: '22000', manager_primary: '인프라팀장', manager_secondary: '인프라담당자', memo: '가비아 자동갱신 설정 완료' })); export const dummySwUsers: any[] = Array.from({ length: 15 }).map((_, i) => ({ id: randomId(), sw_id: dummySubSw[0]?.id || randomId(), purchase_corp: getRandomCorp(), current_dept: '경영지원팀', user_current: \`홍길동\${i}\`, memo: \`SW신청서_2400\${i}\` })); export const dummyLogs: any[] = Array.from({ length: 10 }).map((_, i) => ({ id: randomId(), assetId: dummyPCs[0]?.id || randomId(), date: randomDateStr(1), details: i % 2 === 0 ? '메모리 추가 증설 (16GB -> 32GB)' : '디스플레이 파손 수리', user: 'IT지원팀', cost: i % 2 === 0 ? 80000 : 150000, })); `; fs.writeFileSync('c:/Project/HM ITAM/src/core/dummyData.ts', newDummyDataFileContent, 'utf-8'); console.log('✅ dummyData.ts file updated successfully.'); } catch (e) { console.error('❌ Failed to update dummy data:', e); }