diff --git a/PC_사양_적정성_분석_기획서.html b/PC_사양_적정성_분석_기획서.html
deleted file mode 100644
index 60a01be..0000000
--- a/PC_사양_적정성_분석_기획서.html
+++ /dev/null
@@ -1,429 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- 1개요 — 100점 만점 감점형 성능 점수 체계
-
- v3.0부터 PC 사양 점수는 100점 만점 기준 감점제로 산출됩니다.
- 누적 합산 방식 대신, 최상급 부품 조합을 100점 만점으로 고정하고 사양이 저하되거나 연식이 노후화됨에 따라
- 성능 및 효율성 하락 폭을 감점하는 방식입니다. 이는 실제 업무 환경에서 PC 노후도에 따른
- 체감 생산성 저하를 훨씬 직관적이고 현실적으로 드러냅니다.
-
-
-
-
① 기본 100점 만점
-
→
-
② CPU 등급/세대 감점
-
→
-
③ RAM 용량 감점
-
→
-
④ GPU 등급 감점
-
→
-
⑤ 연식 노후 감점
-
→
-
⑥ 최종 실질 성능 점수
-
-
-
-
-totalScore = max(10, 100 - (cpuDeduction + genDeduction + ramDeduction + gpuDeduction + ageDeduction))
-
-
-
-
-
- 2CPU 사양 감점 기준
- CPU 감점은 등급 감점(최대 -30점)과 세대 노후 감점(최대 -15점)의 합산입니다.
-
-
-
-i9 / Ryzen 9 → 0점 감점
-i7 / Ryzen 7 → -5점 감점
-i5 / Ryzen 5 → -15점 감점
-i3 / Ryzen 3 → -25점 감점
-기타 → -30점 감점
-
-
-최신 세대 (Intel 12~14세대, Ryzen 5000~7000시리즈 이상) → 0점 감점
-과도기 세대 (Intel 10~11세대, Ryzen 3000시리즈) → -5점 감점
-구형 세대 (Intel 8~9세대, Ryzen 1000~2000시리즈) → -10점 감점
-노후 세대 (Intel 7세대 이하, 구형 AMD) → -15점 감점
-
-
- CPU 조합별 감점 예시
-
-
- | 모델 | 세대 구분 | 등급감점 | 세대감점 | CPU 감점 합계 |
-
- | i9-13900K | 최신 세대 | 0 | 0 | 0점 (감점 없음) |
- | i7-14700K | 최신 세대 | -5 | 0 | -5점 |
- | i7-1360P | 최신 세대 (노트북) | -5 | 0 | -5점 |
- | i5-12400 | 최신 세대 | -15 | 0 | -15점 |
- | i7-9700 | 구형 세대 | -5 | -10 | -15점 |
- | i5-8500 | 구형 세대 | -15 | -10 | -25점 |
- | i7-7700 | 노후 세대 | -5 | -15 | -20점 |
-
-
-
-
-
-
-
- 3RAM 용량 감점 기준
- 메모리 용량 부족에 따른 멀티태스킹 제약 및 병목 현상을 반영해 최대 -25점까지 감점합니다.
-
-
- | RAM 용량 | 감점 점수 | 영향도 | 평가 |
-
- | 32GB 이상 | 0점 (감점 없음) | 대용량 3D 및 개발 작업 원활 | 최적 |
- | 16GB | -10점 감점 | 일반 사무용 및 가벼운 멀티태스킹 적합 | 보통 |
- | 8GB | -20점 감점 | 브라우저 탭 다수 실행 시 물리 메모리 부족 | 주의 |
- | 8GB 미만 | -25점 감점 | 기본 OS 구동 외 심각한 메모리 병목 | 부족 |
-
-
-
-
-
-
-
- 4GPU 성능 감점 기준
-
- 3D 렌더링 및 고급 연산 처리 능력을 기준으로 외장 및 내장 GPU를 분류해 최대 -25점까지 감점합니다.
- GPU 정보가 감지되지 않거나 없는 경우 기본적으로 내장 그래픽 수준인 -25점을 감점합니다.
-
-
-
-
- | 등급 | 제품군 구분 | 대표 모델 | 감점 점수 | 적합 작업 |
-
- | S | 최상위 외장 GPU | RTX 4070~4090, RTX A4000~A6000 | 0점 (감점 없음) | 3D 그래픽, AI 연산, VR |
- | A | 메인스트림 외장 GPU | RTX 3060~3070, RTX 2060, RTX A2000 | -5점 감점 | 중급 개발, CAD 설계 |
- | B | 엔트리 외장 GPU | GTX 1660, GTX 1060, RX 6600 | -15점 감점 | 기본 CAD, 그래픽 보조 |
- | C | 내장 그래픽 및 기타 | Intel Iris Xe, UHD Graphics, Vega, GPU 없음 | -25점 감점 | 오피스 사무, 문서 작업 |
-
-
-
-
-
-
-
- 5감점법 종합 점수 계산 실사례
-
-
-
- | 모델명 | CPU 사양 (감점) | RAM 사양 (감점) | GPU 사양 (감점) | 연식 (감점) | 감점 총합 | 최종 점수 |
-
-
-
- | HP ZBook Fury 16 | Ryzen 9 7900X (0) | 64GB (0) | NVIDIA RTX A2000 (-5) | 2년차 (-6) | -11 | 89점 |
-
-
- | Dell Precision 5680 | i9-13900K (0) | 64GB (0) | NVIDIA RTX 4070 (0) | 2년차 (-6) | -6 | 94점 |
-
-
- | LG Gram 17 Pro | i7-14700K (-5) | 32GB (0) | NVIDIA RTX 4060 (-5) | 1년차 (-3) | -13 | 87점 |
-
-
- | LG Gram 16 | i7-1360P (-5) | 16GB (-10) | Intel Iris Xe (-25) | 3년차 (-9) | -49 | 51점 |
-
-
- | Samsung Galaxy Book 3 | i5-1340P (-15) | 16GB (-10) | Intel Iris Xe (-25) | 3년차 (-9) | -59 | 41점 |
-
-
- | HP EliteBook 840 | Ryzen 5 5600X (-15) | 16GB (-10) | AMD Radeon Vega (-25) | 4년차 (-12) | -62 | 38점 |
-
-
- | HP ProDesk 400 G5 | i3-8100 (-35) | 8GB (-20) | Intel UHD 630 (-25) | 5년 이상 (-15) | -95 | 10점(보존) |
-
-
-
-
-
-
-
-
- 6직무별 평균 및 권장 점수 기준 (100점 만점 감점형)
- 100점 만점 감점형 점수 체계를 실제 PC 데이터에 대입하여 산출된 각 직무별 평균 및 권장 목표 점수 기준선입니다.
-
-
-
- | 정렬 | 직무 | 실제 데이터 평균 (감점 반영) | 기본 권장 점수 (목표) | 규칙 |
-
-
- | 1 | AI 개발자 | 88.0점 | 95점 | 최고 |
- | 2 | 편집 디자이너 | 80.2점 | 75점 | 최고 |
- | 3 | 3D 디자이너 | 78.4점 | 90점 | 최고 |
- | 4 | UXUI 디자이너 | 72.7점 | 70점 | 고성능 |
- | 5 | 3D 개발자 | 67.8점 | 90점 | 최고 |
- | 6 | 프로그램 개발자 | 67.3점 | 80점 | 고성능 |
- | 7 | BIM모델러 | 62.1점 | 75점 | 최고 |
- | 8 | 엔지니어 | 42.9점 | 60점 | 고성능 |
- | 9 | 웹 개발자 | 39.2점 | 75점 | 고성능 |
- | 10 | 기획자 | 38.6점 | 50점 | 중간 |
- | 11 | 감리원 | - | 40점 | 기본 |
-
-
-
-
-
📌 대소 관계 조건 충족 확인
- AI 개발자(88.0) > 편집 디자이너(80.2) > 3D 디자이너(78.4) > UXUI 디자이너(72.7) > 3D 개발자(67.8) > 프로그램 개발자(67.3) > BIM모델러(62.1) > 엔지니어(42.9) > 웹 개발자(39.2) > 기획자(38.6) ✅
-
-
-
-
-
- 7적정성 판별 기준
- 직무 내 실제 평균 점수를 기준으로 편차율을 산출하여 3단계로 판별합니다.
-
-avgScore = 해당 직무 소속 PC 점수들의 산술 평균
-
-IF 개인 실질 점수 < avgScore × 0.80 → "사양 부족" (직무 평균 20% 이상 미달)
-IF 개인 실질 점수 > avgScore × 1.30 → "오버스펙" (직무 평균 30% 이상 초과)
-ELSE → "적정"
-
-
-
- | 판별 결과 | 조건 | 권장 조치 |
-
- | 사양 부족 | 실질 점수 < 직무 평균 × 0.8 | 교체 또는 성능 업그레이드 우선 검토 |
- | 적정 | 직무 평균 × 0.8 ≤ 실질 점수 ≤ 직무 평균 × 1.3 | 현행 업무 효율 유지 |
- | 오버스펙 | 실질 점수 > 직무 평균 × 1.3 | 과스펙 장비 회수 또는 필요 부서 재배치 |
-
-
-
-
-
-
-
- 8점수 신뢰도 및 한계 분석
-
- ✅ 신뢰 가능한 부분
-
-
- - 3요소 합산으로 실제 성능 근접도 향상: CPU·RAM·GPU를 모두 반영함으로써 단순 CPU 점수 대비 실체감 성능과의 상관관계가 크게 개선되었습니다.
- - GPU 티어 방향성 일치: RTX 4090 > 4080 > 4070 … 순의 점수 순서는 실제 벤치마크(3DMark, PassMark GPU)와 일치합니다.
- - 내장/외장 구분 명확: 내장 그래픽(5~15점)과 독립 GPU(18점~)의 점수 구간이 명확히 분리되어 사양 격차를 직관적으로 반영합니다.
- - 직무별 상대 비교 합리성 유지: GPU 점수 추가 후에도 직무 내 평균 기준 편차율 판별 방식이 그대로 유지됩니다.
-
-
-
- ⚠️ 여전히 남아있는 한계점
-
-
- | 한계 항목 | 내용 | 영향도 |
-
-
- | 노트북 TDP 미반영 |
- i7-1360P (노트북 28W)와 i7-13700K (데스크탑 125W)는 같은 세대지만 실제 성능 차이가 큽니다. 현재는 동일 점수가 부여됩니다. |
- 중간 |
-
-
- | SSD 유형 미반영 |
- NVMe SSD와 HDD의 체감 속도 차이는 크지만 점수에 포함되지 않습니다. |
- 중간 |
-
-
- | GPU 세부 파생 모델 한계 |
- RTX 4060 Laptop과 RTX 4060 Desktop은 성능 차이가 있으나 동일 점수(50점)를 받습니다. |
- 중간 |
-
-
- | GPU 세대 보정 미적용 |
- CPU와 달리 GPU는 세대 보정 없이 모델명 매핑 방식만 사용됩니다. 향후 세대별 보정을 검토할 수 있습니다. |
- 낮음 |
-
-
- | 실측 벤치마크 미연동 |
- 3DMark / PassMark GPU 실측값이 아닌 모델명 파싱 추정치입니다. |
- 중간 |
-
-
-
-
-
-
-
💡 종합 신뢰도 평가
- GPU 점수 반영 후
특히 디자이너·개발자와 같은 그래픽 집약적 직무의 적정성 판별 정확도가 대폭 향상되었습니다.
- 다만 노트북 TDP, SSD 유형 등 추가 변수를 향후 보완하면 신뢰도를 더 끌어올릴 수 있습니다.
- 현 시점에서 본 점수 체계는
"절대적 성능 수치"가 아닌 "조직 내 직무별 상대 비교 도구"로 활용하는 것이 가장 적합합니다.
-
-
-
-
-
- 9향후 개선 로드맵
-
-
- | 우선순위 | 항목 | 기대 효과 | 난이도 |
-
- | 완료 | GPU 점수 반영 (v2.0) | 그래픽 직무 신뢰도 대폭 향상 | 중 |
- | 권장 | SSD 유형별 점수 추가 (NVMe/SATA/HDD) | 실체감 체감 속도 반영 | 하 |
- | 권장 | 노트북/데스크탑 TDP 보정 | 모바일 CPU 과대평가 방지 | 중 |
- | 선택 | PassMark / 3DMark 실측 DB 내장 연동 | 추정치 → 실측값 전환 | 상 |
- | 선택 | 직무별 항목 가중치 커스터마이징 | 조직 특성 맞춤 정밀 점수화 | 중 |
- | 선택 | RMM 에이전트 실시간 자원 점유율 연동 | 실사용 기반 교체 우선순위 추천 | 상 |
-
-
-
-
-
-
-
-
-
-
diff --git a/README.md b/README.md
index 21a71f3..cad0b0b 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,14 @@
5. **DB 삭제 및 초기화 절대 엄금 (Strict DB Deletion Policy)**:
- 어떠한 경우에도 `DELETE`, `DROP`, `TRUNCATE` 등 데이터를 삭제하거나 테이블을 초기화하는 작업은 사전에 사용자에게 상세 사유를 보고하고 **명시적 승인**을 얻은 후에만 시행한다.
- 기존 데이터의 가치를 최우선으로 하며, 작업 전 백업 여부를 반드시 확인한다.
+6. **RED–GREEN–Refactor 개발 원칙**:
+ - 모든 기능 개발과 버그 수정은 **RED → GREEN → Refactor** 순서로 진행한다.
+ - **RED**: 요구사항을 명확히 표현하는 테스트를 먼저 작성하고, 해당 테스트가 기능 미구현 또는 결함으로 인해 실패하는지 확인한다.
+ - **GREEN**: 실패한 테스트를 통과시키는 데 필요한 최소한의 코드만 구현하며, 불필요한 기능 추가나 구조 변경을 하지 않는다.
+ - **Refactor**: 관련 테스트와 기존 테스트가 모두 통과하는 상태에서만 중복 제거, 명칭 개선, 책임 분리 등 코드 구조를 개선하며 동작은 변경하지 않는다.
+ - 각 단계가 끝날 때마다 관련 테스트와 기존 기능의 회귀 여부를 검증한다.
+ - 테스트 작성이 현실적으로 불가능한 경우에는 그 사유와 대체 검증 방법을 먼저 보고하고 승인을 받은 후 진행한다.
+ - 본 원칙을 적용할 때에도 기존의 **선보고 후승인** 및 **외과 수술식 수정** 규칙을 준수한다.
---
@@ -31,29 +39,8 @@
### 🎨 ITAM 시스템 디자인 가이드 (Design Guide)
-1. **디자인 철학 (Design Philosophy)**
- * **Minimalist & Border-based**: 불필요한 박스(Card) 사용을 최소화하고, 정보의 구분은 간결한 라인(Border/Divider)을 활용하여 시각적 피로도를 낮춥니다.
- * **Professional Achromatic**: 무채색(Black, White, Grey)을 기본으로 하여 정돈된 업무 환경을 제공합니다.
- * **Green Accent**: 블루 대신 짙은 그린(`#1E5149`)을 포인트 컬러로 사용하여 차분한 전문성을 강조합니다.
+디자인 일관성 및 시각적 원칙에 관한 상세 내용은 아래 문서를 참조하십시오.
-2. **타이포그래피 (Typography)**
- * **Font Family**: `Pretendard` (전역 적용)
- * **Letter Spacing**: `-0.02em` (약 -2%) 적용. 자간을 좁게 설정하여 밀도 있고 세련된 가독성을 확보합니다.
- * **Weights**: 400(Regular), 500(Medium), 600(SemiBold), 700(Bold).
+👉 **[디자인 가이드 바로가기 (design_rule.md)](./design_rule.md)**
-3. **컬러 팔레트 (Color Palette)**
- * **Point Color**: `#1E5149` (Deep Green) - 강조, 활성화 상태, 주요 액션 버튼.
- * **Text**: Main(`#111827` - Near Black), Muted(`#6B7280` - Grey).
- * **Border/Divider**: `#E5E7EB` (Light Grey) - 정보 구분을 위한 얇은 실선.
- * **Background**: `#FFFFFF` (White) / `#F9FAFB` (Off White).
-
-4. **레이아웃 및 컴포넌트 규칙 (Layout Rules)**
- * **Box-less Design**: 꼭 필요한 정보 묶음(데이터 그룹화 등)이 아니면 박스 형태의 테두리나 배경 사용을 지양합니다.
- * **Line-based Division**: 섹션 간의 구분은 1px 두께의 얇은 실선(Border)을 통해 명확히 합니다.
- * **Table**: 배경색이나 화려한 효과 없이 행(Row) 간의 얇은 구분선만 사용하여 데이터 본연에 집중하게 합니다.
- * **Input/Button**: 입력 필드와 버튼은 최소한의 보더와 포인트 컬러만 사용하여 정갈하게 표현합니다.
- * **Modal (모달 공통 규칙)**:
- * **Header**: 짙은 그린(`#1E5149`) 배경에 화이트 텍스트를 사용하며, 우측 상단에 명확한 'X' 닫기 버튼을 배치합니다.
- * **Interaction**: 사용자의 오입력(실수로 바깥을 클릭하여 입력 내용이 날아가는 현상)을 방지하기 위해 **모달 바깥 영역(Overlay) 클릭 시 모달이 닫히지 않도록** 설정합니다. 닫기는 오직 'ESC' 키 또는 명시적인 'X' 및 '닫기' 버튼을 통해서만 가능합니다.
- * **Layout**: `detail.png` 기준의 2열 그리드 시스템을 권장하며, 하단 우측에 액션 버튼(닫기, 저장 등)을 배치합니다.
diff --git a/SampleData_PC.xlsx b/SampleData_PC.xlsx
deleted file mode 100644
index 0c52785..0000000
Binary files a/SampleData_PC.xlsx and /dev/null differ
diff --git a/SampleData_SVR.xlsx b/SampleData_SVR.xlsx
deleted file mode 100644
index e137431..0000000
Binary files a/SampleData_SVR.xlsx and /dev/null differ
diff --git a/backupDB_20260602.xlsx b/backupDB_20260602.xlsx
deleted file mode 100644
index 3914bc9..0000000
Binary files a/backupDB_20260602.xlsx and /dev/null differ
diff --git a/backup_db.js b/backup_db.js
deleted file mode 100644
index 2687b14..0000000
--- a/backup_db.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import mysql from 'mysql2/promise';
-import dotenv from 'dotenv';
-import * as xlsx from 'xlsx';
-import fs from 'fs';
-
-dotenv.config();
-
-const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
-
-async function backup() {
- const connection = await mysql.createConnection({
- host: DB_HOST,
- user: DB_USER,
- password: DB_PASS,
- database: DB_NAME,
- port: parseInt(DB_PORT || '3306')
- });
-
- console.log('🚀 Starting Database Backup Process...');
-
- const tables = [
- 'asset_pc', 'asset_server', 'asset_storage', 'asset_remote',
- 'asset_equipment', 'asset_office_supplies', 'asset_survey', 'asset_vip'
- ];
-
- const wb = xlsx.utils.book_new();
-
- for (const table of tables) {
- try {
- // 1. Create table backup
- await connection.query(`DROP TABLE IF EXISTS ${table}_backup`);
- await connection.query(`CREATE TABLE ${table}_backup AS SELECT * FROM ${table}`);
- console.log(`✅ Table backup created: ${table} -> ${table}_backup`);
-
- // 2. Fetch data for Excel
- const [rows] = await connection.query(`SELECT * FROM ${table}`);
- if (rows.length > 0) {
- const ws = xlsx.utils.json_to_sheet(rows);
- // Sheet names max length is 31 chars
- const sheetName = table.substring(0, 31);
- xlsx.utils.book_append_sheet(wb, ws, sheetName);
- }
- } catch (e) {
- console.warn(`⚠️ Skipped ${table}: ${e.message}`);
- }
- }
-
- // 3. Write Excel file
- const fileName = 'backupDB_20260608.xlsx';
- xlsx.writeFile(wb, fileName);
- console.log(`✅ Excel data exported successfully to ${fileName}`);
-
- await connection.end();
-}
-
-backup().catch(err => {
- console.error('❌ Backup Failed:', err);
- process.exit(1);
-});
diff --git a/check_logs.js b/check_logs.js
deleted file mode 100644
index d14cdb7..0000000
--- a/check_logs.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import mysql from 'mysql2/promise';
-import dotenv from 'dotenv';
-
-dotenv.config();
-
-const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
-
-async function checkRecentLogs() {
- const connection = await mysql.createConnection({
- host: DB_HOST,
- user: DB_USER,
- password: DB_PASS,
- database: DB_NAME,
- port: parseInt(DB_PORT || '3306')
- });
-
- console.log('--- Recent History Logs ---');
- const [rows] = await connection.query('SELECT * FROM asset_history ORDER BY created_at DESC LIMIT 5');
- console.log(JSON.stringify(rows, null, 2));
-
- console.log('\n--- Recent Core Data (to check current_dept) ---');
- const [coreRows] = await connection.query('SELECT id, asset_code, current_dept, previous_dept FROM asset_core ORDER BY updated_at DESC LIMIT 5');
- console.log(JSON.stringify(coreRows, null, 2));
-
- await connection.end();
-}
-
-checkRecentLogs().catch(console.error);
diff --git a/check_network.js b/check_network.js
deleted file mode 100644
index 85228d4..0000000
--- a/check_network.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import mysql from 'mysql2/promise';
-import dotenv from 'dotenv';
-
-dotenv.config();
-
-const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
-
-async function checkRemote() {
- const connection = await mysql.createConnection({
- host: DB_HOST,
- user: DB_USER,
- password: DB_PASS,
- database: DB_NAME,
- port: parseInt(DB_PORT || '3306')
- });
-
- console.log('--- Checking asset_remote table ---');
-
- const [columns] = await connection.query('DESCRIBE asset_remote');
- const cols = columns.map(c => c.Field);
- console.log('Columns in asset_remote:', cols.join(', '));
-
- const [count] = await connection.query('SELECT COUNT(*) as count FROM asset_remote WHERE remote_tool IS NOT NULL OR remote_id IS NOT NULL');
- console.log(`Rows with remote info (tool or id): ${count[0].count}`);
-
- await connection.end();
-}
-
-checkRemote().catch(console.error);
diff --git a/db_init.js b/db_init.js
deleted file mode 100644
index df98336..0000000
--- a/db_init.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import mysql from 'mysql2/promise';
-import dotenv from 'dotenv';
-
-dotenv.config();
-
-const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
-
-async function initDB() {
- const connection = await mysql.createConnection({
- host: DB_HOST,
- user: DB_USER,
- password: DB_PASS,
- database: DB_NAME,
- port: parseInt(DB_PORT || '3306'),
- multipleStatements: true
- });
-
- console.log('🔄 DB 초기화 시작 (영문 표준 스키마 적용)...');
-
- const tablesToDrop = [
- 'pc_assets', 'server_assets', 'storage_assets', 'equip_assets', 'mobile_assets',
- 'sw_sub_assets', 'sw_perm_assets', 'cloud_assets', 'sw_users', 'asset_logs'
- ];
- for (const table of tablesToDrop) {
- await connection.query(`DROP TABLE IF EXISTS ${table}`);
- }
-
- const createHardwareTable = (tableName, comment) => `
- CREATE TABLE ${tableName} (
- id VARCHAR(50) PRIMARY KEY,
- corp VARCHAR(100),
- asset_code VARCHAR(100),
- purchase_date VARCHAR(50),
- type VARCHAR(50),
- detail_purpose VARCHAR(50),
- purpose VARCHAR(255),
- details TEXT,
- current_org VARCHAR(255),
- prev_org VARCHAR(255),
- location VARCHAR(255),
- manager_main VARCHAR(100),
- manager_sub VARCHAR(100),
- ip_address VARCHAR(100),
- remote_tool VARCHAR(100),
- server_id VARCHAR(100),
- server_pw VARCHAR(100),
- model_name VARCHAR(255),
- mainboard VARCHAR(255) COMMENT '메인보드',
- os VARCHAR(100),
- cpu VARCHAR(255),
- ram VARCHAR(100),
- gpu VARCHAR(100),
- storage1 VARCHAR(255),
- storage2 VARCHAR(255),
- storage3 VARCHAR(255),
- monitoring VARCHAR(100),
- price VARCHAR(100),
- remarks TEXT,
- storage_location VARCHAR(255),
- status VARCHAR(50),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `;
-
- await connection.query(createHardwareTable('pc_assets', 'PC'));
- await connection.query(createHardwareTable('server_assets', 'Server'));
- await connection.query(createHardwareTable('storage_assets', 'Storage'));
- await connection.query(createHardwareTable('equip_assets', 'Equipment'));
- await connection.query(createHardwareTable('mobile_assets', 'Mobile'));
-
- await connection.query(`
- CREATE TABLE sw_sub_assets (
- id VARCHAR(50) PRIMARY KEY,
- corp VARCHAR(100) COMMENT '구매법인',
- category VARCHAR(100) COMMENT '분야',
- dept VARCHAR(100) COMMENT '부서',
- product_name VARCHAR(255) COMMENT '제품명',
- license_type VARCHAR(100) COMMENT '라이선스 유형',
- quantity INT COMMENT '수량',
- price VARCHAR(100) COMMENT '금액',
- purchase_date VARCHAR(50) COMMENT '구매일',
- start_date VARCHAR(50) COMMENT '시작일',
- expiry_date VARCHAR(50) COMMENT '만료일',
- vendor VARCHAR(255) COMMENT '구매업체',
- remarks TEXT COMMENT '비고',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- await connection.query(`
- CREATE TABLE sw_perm_assets (
- id VARCHAR(50) PRIMARY KEY,
- corp VARCHAR(100) COMMENT '구매법인',
- category VARCHAR(100) COMMENT '분야',
- dept VARCHAR(100) COMMENT '부서',
- product_name VARCHAR(255) COMMENT '제품명',
- license_key VARCHAR(255) COMMENT '라이선스 키',
- quantity INT COMMENT '수량',
- price VARCHAR(100) COMMENT '금액',
- purchase_date VARCHAR(50) COMMENT '구매일',
- start_date VARCHAR(50) COMMENT '시작일',
- expiry_date VARCHAR(50) COMMENT '만료일',
- vendor VARCHAR(255) COMMENT '구매업체',
- remarks TEXT COMMENT '비고',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- await connection.query(`
- CREATE TABLE cloud_assets (
- id VARCHAR(50) PRIMARY KEY,
- platform_name VARCHAR(100),
- corp VARCHAR(100),
- dept VARCHAR(100),
- product_name VARCHAR(255),
- account_name VARCHAR(255),
- pay_method VARCHAR(100),
- pay_day VARCHAR(50),
- card_num VARCHAR(100),
- monthly_fee VARCHAR(100),
- remarks TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- await connection.query(`
- CREATE TABLE sw_users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- sw_id VARCHAR(50),
- corp VARCHAR(100),
- dept VARCHAR(100),
- position VARCHAR(50),
- user_name VARCHAR(100),
- usage_period VARCHAR(100),
- doc_name VARCHAR(255),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- await connection.query(`
- CREATE TABLE asset_logs (
- id INT AUTO_INCREMENT PRIMARY KEY,
- asset_id VARCHAR(50),
- log_date VARCHAR(50),
- log_user VARCHAR(100),
- details TEXT,
- cost DECIMAL(15,2) DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- await connection.query(`
- CREATE TABLE ops_domain_assets (
- id VARCHAR(50) PRIMARY KEY,
- type VARCHAR(50) COMMENT '유형',
- corp VARCHAR(100) COMMENT '법인',
- service_name VARCHAR(255) COMMENT '서비스명',
- domain_name VARCHAR(255) COMMENT '관리도메인',
- start_date VARCHAR(50) COMMENT '시작일',
- expiry_date VARCHAR(50) COMMENT '만료일',
- price VARCHAR(100) COMMENT '금액',
- manager_main VARCHAR(100) COMMENT '담당자',
- manager_sub VARCHAR(100) COMMENT '담당자(부)',
- remarks TEXT COMMENT '비고',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- `);
-
- console.log('✅ 모든 테이블이 영문 표준 스키마로 재생성되었습니다.');
- await connection.end();
-}
-
-initDB().catch(err => {
- console.error('❌ DB 초기화 실패:', err);
- process.exit(1);
-});
diff --git a/ITAM_RMM_Integration_Plan.md b/docs/plans/ITAM_RMM_Integration_Plan.md
similarity index 100%
rename from ITAM_RMM_Integration_Plan.md
rename to docs/plans/ITAM_RMM_Integration_Plan.md
diff --git a/IT_Asset_RMM_System_Report_Detailed.md b/docs/plans/IT_Asset_RMM_System_Report_Detailed.md
similarity index 100%
rename from IT_Asset_RMM_System_Report_Detailed.md
rename to docs/plans/IT_Asset_RMM_System_Report_Detailed.md
diff --git a/PC_사양_개선_기획서.html b/docs/plans/PC_사양_개선_기획서.html
similarity index 100%
rename from PC_사양_개선_기획서.html
rename to docs/plans/PC_사양_개선_기획서.html
diff --git a/PLAN_ASSET_HISTORY.md b/docs/plans/PLAN_ASSET_HISTORY.md
similarity index 100%
rename from PLAN_ASSET_HISTORY.md
rename to docs/plans/PLAN_ASSET_HISTORY.md
diff --git a/PROJECT_GUIDE.md b/docs/plans/PROJECT_GUIDE.md
similarity index 100%
rename from PROJECT_GUIDE.md
rename to docs/plans/PROJECT_GUIDE.md
diff --git a/docs/plans/design_rule.md b/docs/plans/design_rule.md
new file mode 100644
index 0000000..dbc3313
--- /dev/null
+++ b/docs/plans/design_rule.md
@@ -0,0 +1,48 @@
+# 🎨 ITAM 시스템 디자인 가이드 (Design Guide)
+
+본 문서는 ITAM(IT Asset Management System)의 시각적 일관성과 사용자 경험을 유지하기 위한 핵심 디자인 원칙을 정의합니다.
+
+---
+
+### 1. 디자인 철학 (Design Philosophy)
+* **Minimalist & Stark**: Vercel 스타일의 극도로 간결하고 현대적인 디자인을 지향합니다.
+* **Achromatic Base**: 블랙(#171717)과 화이트를 기본으로 하며, 정보의 구분은 얇은 헤어라인(#ebebeb)을 사용합니다.
+* **Fluid & Responsive**: 고정된 픽셀 대신 화면 크기에 비례하여 UI 밀도가 변하는 유동적 스케일링 시스템을 적용합니다.
+
+### 2. 타이포그래피 및 자간 (Typography & Letter-spacing)
+* **Font Family**: `Pretendard` 단일 폰트를 사용합니다.
+* **Letter-spacing**: 모든 텍스트에 `-0.02em` (-2%) 자간을 적용하여 밀도 있는 가독성을 확보합니다.
+* **Typography Scale**:
+ * **XS**: `clamp(10px, 1.2vmin + 0.2vw, 15px)` - 보조 텍스트
+ * **SM**: `clamp(12px, 1.4vmin + 0.3vw, 18px)` - 필터, 일반 라벨, 테이블 헤더
+ * **Base**: `clamp(14px, 1.6vmin + 0.4vw, 22px)` - 본문, 테이블 데이터
+ * **MD**: `clamp(18px, 2.5vmin + 0.5vw, 30px)` - 섹션 소제목
+ * **LG**: `clamp(24px, 4vmin + 0.6vw, 48px)` - 페이지 대제목
+ * **XL**: `clamp(32px, 6vmin + 0.8vw, 72px)` - 핵심 통계 지표
+* **Layout Units**:
+ * **Header Height**: `clamp(50px, 8vmin, 90px)`
+ * **Base Spacing**: `clamp(0.75rem, 3vmin, 3rem)`
+ * **Radius**: `clamp(6px, 1.5vmin, 16px)`
+
+### 3. 컬러 팔레트 (Vercel Stark Palette)
+* **Primary**: `#171717` (Stark Black) - 텍스트, 주요 버튼, 강조 요소.
+* **Secondary**: `#888888` (Mute) - 보조 텍스트, 비활성 아이콘.
+* **Border**: `#ebebeb` (Hairline) - 정보 구분선.
+* **Background**: `#ffffff` (Canvas), `#fafafa` (Soft), `#f5f5f5` (Soft 2).
+* **Accents**: Blue(`#0070f3`), Orange(`#f5a623`), Danger(`#ee0000`).
+
+### 4. 컴포넌트 및 레이아웃 규칙 (Component Rules)
+* **Header & Navigation**:
+ * 상단 1열 통합 바 형태를 유지하며, GNB와 LNB를 동일 라인에 배치하여 공간을 효율적으로 사용합니다.
+* **Unified Filter Bar**:
+ * 검색창과 필터는 상단 타이틀 바로 아래(기존 액션 버튼 라인)까지 올려서 배치합니다.
+ * **Action Group**: '자산 추가', '부품 마스터' 등의 주요 액션 버튼은 검색창과 같은 라인의 최우측에 정렬합니다.
+* **Dashboard**:
+ * **Single-Screen View**: 1920*1080(또는 1920*919) 해상도에서 스크롤 없이 한 화면에 핵심 정보가 모두 보이도록 최적화합니다.
+ * **Fixed Charts**: 차트 내부 숫자나 요소에 애니메이션(`animation: false`) 및 플로팅 레이블을 배제하여 정적인 안정성을 확보합니다.
+* **Footer**:
+ * 화면 최하단에 위치하며, 텍스트는 **우측 정렬(Right-aligned)**합니다.
+ * 상단에 1px 헤어라인 구분선을 가집니다.
+* **Security & UX**:
+ * **Text Selection**: 사용자의 실수에 의한 UI 드래그 방지를 위해 입력창(`input`, `textarea`)을 제외한 전체 영역의 텍스트 선택을 차단합니다.
+ * **View Toggle**: '서버' 탭 등 특정 탭에서만 '목록보기' 체크박스를 통해 뷰를 전환하며, 그 외 화면은 리스트 중심의 UI를 제공합니다.
diff --git a/implementation_plan.md b/docs/plans/implementation_plan.md
similarity index 100%
rename from implementation_plan.md
rename to docs/plans/implementation_plan.md
diff --git a/implementation_plan2 b/docs/plans/implementation_plan2
similarity index 100%
rename from implementation_plan2
rename to docs/plans/implementation_plan2
diff --git a/implementation_plan3 b/docs/plans/implementation_plan3
similarity index 100%
rename from implementation_plan3
rename to docs/plans/implementation_plan3
diff --git a/기능명세서.md b/docs/plans/기능명세서.md
similarity index 100%
rename from 기능명세서.md
rename to docs/plans/기능명세서.md
diff --git a/drop_legacy.js b/drop_legacy.js
deleted file mode 100644
index c4b6ad5..0000000
--- a/drop_legacy.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import mysql from 'mysql2/promise';
-import dotenv from 'dotenv';
-
-dotenv.config();
-
-const { DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT } = process.env;
-
-async function dropLegacyTables() {
- const connection = await mysql.createConnection({
- host: DB_HOST,
- user: DB_USER,
- password: DB_PASS,
- database: DB_NAME,
- port: parseInt(DB_PORT || '3306')
- });
-
- console.log('🧹 Starting cleanup of obsolete legacy backup tables...');
-
- const tablesToDrop = [
- 'asset_pc', 'asset_pc_backup',
- 'asset_server', 'asset_server_backup',
- 'asset_storage', 'asset_storage_backup',
- 'asset_remote_backup', // IMPORTANT: DO NOT drop asset_remote!
- 'asset_equipment', 'asset_equipment_backup',
- 'asset_office_supplies', 'asset_office_supplies_backup',
- 'asset_survey', 'asset_survey_backup',
- 'asset_vip', 'asset_vip_backup',
- 'asset_pc_parts'
- ];
-
- for (const table of tablesToDrop) {
- try {
- await connection.query(`DROP TABLE IF EXISTS ${table}`);
- console.log(`✅ Dropped table: ${table}`);
- } catch (err) {
- console.warn(`⚠️ Failed to drop table ${table}: ${err.message}`);
- }
- }
-
- console.log('🎉 Cleanup complete. Database is now lean and mean.');
- await connection.end();
-}
-
-dropLegacyTables().catch(console.error);
diff --git a/image 92.png b/image 92.png
deleted file mode 100644
index 5a36e8f..0000000
Binary files a/image 92.png and /dev/null differ
diff --git a/img/location_photo/IDC/서관202.png b/img/location_photo/IDC/서관202.png
deleted file mode 100644
index feae6be..0000000
Binary files a/img/location_photo/IDC/서관202.png and /dev/null differ
diff --git a/index.html b/index.html
index 9b2c65a..5a85b8d 100644
--- a/index.html
+++ b/index.html
@@ -5,15 +5,9 @@