From 3486c6dcc386857db9ce95deb455d09b51ce1173 Mon Sep 17 00:00:00 2001 From: NAS-Admin Date: Mon, 1 Jun 2026 10:23:05 +0900 Subject: [PATCH] feat(laptops): implement Excel/CSV bulk import with client-side interactive mapping and encoding detection --- api.php | 135 +++++++++++++++++++ laptops.php | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 506 insertions(+) diff --git a/api.php b/api.php index ba32148..e3e5416 100644 --- a/api.php +++ b/api.php @@ -363,6 +363,141 @@ WHERE a.current_user_id = ?"; echo json_encode(['success' => true]); break; + case 'download_laptop_template': + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename="FKI_Laptop_Import_Template.csv"'); + + // UTF-8 BOM to prevent Excel encoding issues + echo "\xEF\xBB\xBF"; + + $headers = ['자산관리번호', '시리얼번호', 'IP주소', '상태', '비고', '취득일', '정격입출력', '옵션', 'CPU', 'NPU', 'RAM', 'HDD0모델', 'HDD0용량', 'HDD1모델', 'HDD1용량', '임시배정명', '노트북모델명', '배정사용자명']; + $sample = ['0020010000245', '5CD0221MH9', '192.168.1.100', 'stock', '신규 구매 노트북', '2026-06-01', '20V / 3.25A', '지문인식, 백라이트', 'i5-1335U', '없음', '16GB', 'Imation NVMe M.2 1TB', '1TB', '슬롯없음', '슬롯없음', '업무용(TEMP_44666b)', 'HP 노트북 15S-FQ1005TU', '김건희']; + + echo implode(',', $headers) . "\n"; + echo implode(',', $sample) . "\n"; + exit; + + case 'bulk_add_laptop_assets': + $data = json_decode(file_get_contents('php://input'), true); + $assets = $data['assets'] ?? []; + + if (empty($assets)) { + echo json_encode(['success' => false, 'error' => '등록할 자산 데이터가 없습니다.']); + break; + } + + $db->beginTransaction(); + try { + $check_stmt = $db->prepare("SELECT id FROM laptop_assets WHERE asset_tag = ?"); + + $insert_stmt = $db->prepare("INSERT INTO laptop_assets ( + asset_tag, model_id, current_user_id, status, serial_number, purchase_date, ip_address, + cpu, npu, hdd0_model, hdd0_capacity, hdd1_model, hdd1_capacity, ram, + asset_status, assigned_user_name, fixed_ip, options, power_rating, manufacturer, vendor, product_name, remarks + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + $log_stmt = $db->prepare("INSERT INTO asset_history (asset_id, log_type, user_name, action_date, note) VALUES (?, 'assignment', ?, ?, '대량 가져오기로 배정됨')"); + + $inserted_count = 0; + $duplicates = []; + + foreach ($assets as $asset) { + $asset_tag = trim($asset['asset_tag'] ?? ''); + if (empty($asset_tag)) continue; + + // Check duplicate + $check_stmt->execute([$asset_tag]); + if ($check_stmt->fetch()) { + $duplicates[] = $asset_tag; + continue; + } + + // Get model details if model_id is specified + $manufacturer = ''; + $vendor = ''; + $product_name = ''; + $model_id = $asset['model_id'] ?: null; + if ($model_id) { + $m_stmt = $db->prepare("SELECT manufacturer, vendor, product_name FROM laptop_models WHERE id = ?"); + $m_stmt->execute([$model_id]); + $m_data = $m_stmt->fetch(); + if ($m_data) { + $manufacturer = $m_data['manufacturer'] ?? ''; + $vendor = $m_data['vendor'] ?? ''; + $product_name = $m_data['product_name'] ?? ''; + } + } + + // Get user name if current_user_id is specified + $assigned_user_name = $asset['assigned_user_name'] ?? ''; + $current_user_id = $asset['current_user_id'] ?: null; + if ($current_user_id) { + $u_stmt = $db->prepare("SELECT name FROM users WHERE id = ?"); + $u_stmt->execute([$current_user_id]); + $assigned_user_name = $u_stmt->fetchColumn() ?: ''; + } + + $status = ($asset['status'] ?? '') ?: ($current_user_id ? 'assigned' : 'stock'); + if ($status === 'stock' && empty($assigned_user_name)) { + $assigned_user_name = '업무용(TEMP_44666b)'; + } + + $insert_stmt->execute([ + $asset_tag, + $model_id, + $current_user_id, + $status, + $asset['serial_number'] ?? '', + $asset['purchase_date'] ?? '', + $asset['ip_address'] ?? null, + $asset['cpu'] ?? '', + $asset['npu'] ?? '', + $asset['hdd0_model'] ?? '', + $asset['hdd0_capacity'] ?? '', + $asset['hdd1_model'] ?? '', + $asset['hdd1_capacity'] ?? '', + $asset['ram'] ?? '', + $asset['asset_status'] ?? '', + $assigned_user_name, + $asset['fixed_ip'] ?? '', + $asset['options'] ?? '', + $asset['power_rating'] ?? '', + $manufacturer, + $vendor, + $product_name, + $asset['remarks'] ?? '' + ]); + + $new_id = $db->lastInsertId(); + + if ($assigned_user_name && $status === 'assigned') { + $log_stmt->execute([$new_id, $assigned_user_name, date('Y-m-d')]); + } + + $inserted_count++; + } + + if ($inserted_count === 0 && !empty($duplicates)) { + $db->rollBack(); + echo json_encode(['success' => false, 'error' => '중복된 자산 번호만 포함되어 있어 등록되지 않았습니다: ' . implode(', ', $duplicates)]); + break; + } + + $db->commit(); + + $msg = "총 {$inserted_count}대의 노트북 자산이 일괄 등록되었습니다."; + if (!empty($duplicates)) { + $msg .= " (중복 자산 제외: " . implode(', ', $duplicates) . ")"; + } + + echo json_encode(['success' => true, 'message' => $msg]); + + } catch (Exception $e) { + $db->rollBack(); + echo json_encode(['success' => false, 'error' => '자산 일괄 등록 중 오류가 발생했습니다: ' . $e->getMessage()]); + } + break; + case 'update_asset_remarks': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE laptop_assets SET remarks = ? WHERE id = ?"); diff --git a/laptops.php b/laptops.php index d0acc88..f4b8c5d 100644 --- a/laptops.php +++ b/laptops.php @@ -58,6 +58,14 @@ 엑셀 내보내기 + + + + +
+
+
+

01. 표준 포맷 다운로드

+

대량 등록용 표준 CSV 한글 양식을 다운로드하여 작성해 주세요.

+
+ + 양식 파일 받기 (.CSV) + +
+ +
+
+

02. 파일 인코딩 선택

+

업로드한 파일에서 한글이 깨진다면 인코딩 방식을 맞춤 조정해 주세요.

+
+ +
+ +
+
+

03. CSV 파일 가져오기

+

준비된 노트북 자산 목록 파일을 첨부해 주세요.

+
+
+ + + +
+
+
+ + +
+
+

가져온 데이터 실시간 매핑 테이블

+
+ 총 대 자산 대기 중 +
+
+ + + + +
+ + +
+ + +
+ + +