false, 'error' => 'Unauthorized']); exit; } try { switch ($action) { case 'login': $data = json_decode(file_get_contents('php://input'), true); $login_id = $data['login_id'] ?? ''; $password = $data['password'] ?? ''; $stmt = $db->prepare("SELECT * FROM administrators WHERE login_id = ?"); $stmt->execute([$login_id]); $admin = $stmt->fetch(); if ($admin && password_verify($password, $admin['password_hash'])) { $_SESSION['admin_id'] = $admin['id']; $_SESSION['admin_login_id'] = $admin['login_id']; $_SESSION['admin_name'] = $admin['admin_name']; echo json_encode(['success' => true]); } else { echo json_encode(['success' => false, 'error' => '아이디 또는 비밀번호가 일치하지 않습니다.']); } break; case 'logout': session_destroy(); echo json_encode(['success' => true]); break; case 'get_admins': $stmt = $db->query("SELECT id, login_id, admin_name, created_at FROM administrators ORDER BY id ASC"); echo json_encode($stmt->fetchAll()); break; case 'add_admin': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO administrators (login_id, password_hash, admin_name) VALUES (?, ?, ?)"); $stmt->execute([ $data['login_id'], password_hash($data['password'], PASSWORD_DEFAULT), $data['admin_name'] ]); echo json_encode(['success' => true]); break; case 'update_admin_password': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE administrators SET password_hash = ? WHERE id = ?"); $stmt->execute([ password_hash($data['password'], PASSWORD_DEFAULT), $data['id'] ]); echo json_encode(['success' => true]); break; case 'delete_admin': $data = json_decode(file_get_contents('php://input'), true); if ($data['id'] == $_SESSION['admin_id']) { echo json_encode(['success' => false, 'error' => '자기 자신은 삭제할 수 없습니다.']); break; } $stmt = $db->prepare("DELETE FROM administrators WHERE id = ?"); $stmt->execute([$data['id']]); echo json_encode(['success' => true]); break; case 'get_dashboard_stats': // Laptops $laptop_total = $db->query("SELECT COUNT(*) FROM laptop_assets WHERE status != 'disposed'")->fetchColumn(); $laptop_assigned = $db->query("SELECT COUNT(*) FROM laptop_assets WHERE status = 'assigned'")->fetchColumn(); $laptop_disposed = $db->query("SELECT COUNT(*) FROM laptop_assets WHERE status = 'disposed'")->fetchColumn(); // Cards $card_total = $db->query("SELECT COUNT(*) FROM access_cards")->fetchColumn(); $card_assigned = $db->query("SELECT COUNT(*) FROM access_cards WHERE status = 'assigned'")->fetchColumn(); // MFP $mfp_total = $db->query("SELECT COUNT(*) FROM mfp_accounts")->fetchColumn(); $mfp_active = $db->query("SELECT COUNT(*) FROM mfp_accounts WHERE status = 'active'")->fetchColumn(); // Users $users_general = $db->query("SELECT COUNT(*) FROM users WHERE accounting_type = '일반회계'")->fetchColumn(); $users_special = $db->query("SELECT COUNT(*) FROM users WHERE accounting_type = '특별회계'")->fetchColumn(); echo json_encode([ 'laptops' => ['total' => (int) $laptop_total, 'assigned' => (int) $laptop_assigned, 'disposed' => (int) $laptop_disposed], 'cards' => ['total' => (int) $card_total, 'assigned' => (int) $card_assigned], 'mfp' => ['total' => (int) $mfp_total, 'active' => (int) $mfp_active], 'users' => ['general' => (int) $users_general, 'special' => (int) $users_special] ]); break; case 'get_users': $query = "WITH RECURSIVE dept_path(id, path) AS ( SELECT id, name FROM departments WHERE parent_id IS NULL UNION ALL SELECT d.id, dp.path || ' > ' || d.name FROM departments d JOIN dept_path dp ON d.parent_id = dp.id ) SELECT u.*, dp.path as dept_name, (SELECT asset_tag FROM laptop_assets WHERE current_user_id = u.id ORDER BY id DESC LIMIT 1) as laptop_tag, (SELECT purchase_date FROM laptop_assets WHERE current_user_id = u.id ORDER BY id DESC LIMIT 1) as laptop_purchase_date, (SELECT m.model_name FROM laptop_assets a JOIN laptop_models m ON a.model_id = m.id WHERE a.current_user_id = u.id ORDER BY a.id DESC LIMIT 1) as laptop_model_name, (SELECT COUNT(*) FROM laptop_assets WHERE rental_user_id = u.id) as rental_count FROM users u LEFT JOIN dept_path dp ON u.department_id = dp.id"; $search = $_GET['search'] ?? ''; if ($search) { $query .= " WHERE u.name LIKE :search OR u.emp_id LIKE :search OR u.email LIKE :search"; $stmt = $db->prepare($query); $stmt->execute(['search' => "%$search%"]); } else { $stmt = $db->query($query); } echo json_encode($stmt->fetchAll()); break; case 'add_user': $data = json_decode(file_get_contents('php://input'), true); $status = $data['status'] ?? 'active'; $department_id = $data['department_id'] ?: null; $emp_id = $data['emp_id']; // Duplicate emp_id check $check = $db->prepare("SELECT id FROM users WHERE emp_id = ?"); $check->execute([$emp_id]); if ($check->fetch()) { echo json_encode(['success' => false, 'error' => "이미 존재하는 사번({$emp_id})입니다."]); break; } // Auto-relocate based on status if ($status === 'on_leave' || $status === 'retired') { $dept_name = ($status === 'on_leave') ? '휴직' : '퇴사'; $target_dept = $db->query("SELECT id FROM departments WHERE name = '$dept_name'")->fetchColumn(); if ($target_dept) $department_id = $target_dept; } try { $stmt = $db->prepare("INSERT INTO users (emp_id, name, department_id, position, email, phone, mobile, accounting_type, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([ $emp_id, $data['name'], $department_id, $data['position'], $data['email'], $data['phone'] ?? '', $data['mobile'], $data['accounting_type'], $status ]); echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'update_user': $data = json_decode(file_get_contents('php://input'), true); $status = $data['status']; $department_id = $data['department_id'] ?: null; $emp_id = $data['emp_id']; $user_id = $data['id']; // Duplicate emp_id check (excluding current user) $check = $db->prepare("SELECT id FROM users WHERE emp_id = ? AND id != ?"); $check->execute([$emp_id, $user_id]); if ($check->fetch()) { echo json_encode(['success' => false, 'error' => "이미 존재하는 사번({$emp_id})입니다."]); break; } // Auto-relocate based on status if ($status === 'on_leave' || $status === 'retired') { $dept_name = ($status === 'on_leave') ? '휴직' : '퇴사'; $target_dept = $db->query("SELECT id FROM departments WHERE name = '$dept_name'")->fetchColumn(); if ($target_dept) $department_id = $target_dept; } try { $stmt = $db->prepare("UPDATE users SET emp_id = ?, name = ?, department_id = ?, position = ?, email = ?, phone = ?, mobile = ?, accounting_type = ?, status = ? WHERE id = ?"); $stmt->execute([ $emp_id, $data['name'], $department_id, $data['position'], $data['email'], $data['phone'] ?? '', $data['mobile'], $data['accounting_type'], $status, $user_id ]); echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'get_laptops': $query = "SELECT a.*, m.model_name, m.manufacturer, m.specs, m.processor, m.ram, m.storage, u.name as user_name FROM laptop_assets a LEFT JOIN laptop_models m ON a.model_id = m.id LEFT JOIN users u ON a.current_user_id = u.id"; echo json_encode($db->query($query)->fetchAll()); break; case 'get_rental_laptops': $query = "SELECT a.*, m.model_name, m.manufacturer, m.product_name FROM laptop_assets a LEFT JOIN laptop_models m ON a.model_id = m.id WHERE (a.assigned_user_name LIKE '%업무용%' OR a.current_user_id IS NULL)"; echo json_encode($db->query($query)->fetchAll()); break; case 'update_rental': $data = json_decode(file_get_contents('php://input'), true); $startDate = $data['rental_start_date'] ?: date('Y-m-d'); $stmt = $db->prepare("UPDATE laptop_assets SET rental_user_id = ?, rental_user_name = ?, rental_start_date = ?, rental_end_scheduled = ?, rental_reason = ?, rental_peripherals = ?, remarks = ? WHERE id = ?"); $stmt->execute([ $data['rental_user_id'] ?? null, $data['rental_user_name'], $startDate, $data['rental_end_scheduled'] ?? null, $data['rental_reason'] ?? '', $data['rental_peripherals'] ?? '', $data['remarks'] ?? '', $data['id'] ]); // Log the rental action $peripherals = $data['rental_peripherals'] ? "\n부속품: " . $data['rental_peripherals'] : ""; $note = "사유: " . ($data['rental_reason'] ?? '없음') . $peripherals; $log_stmt = $db->prepare("INSERT INTO asset_history (asset_id, log_type, user_name, action_date, note) VALUES (?, 'rental', ?, ?, ?)"); $log_stmt->execute([$data['id'], $data['rental_user_name'], $startDate, $note]); echo json_encode(['success' => true]); break; case 'return_rental': $data = json_decode(file_get_contents('php://input'), true); $asset = $db->query("SELECT * FROM laptop_assets WHERE id = " . (int) $data['id'])->fetch(); if ($asset) { // 반납 처리: 임대 정보는 초기화하되 비고(remarks)는 유지 $stmt = $db->prepare("UPDATE laptop_assets SET rental_user_id = NULL, rental_user_name = NULL, rental_start_date = NULL, rental_end_scheduled = NULL, rental_reason = NULL, rental_peripherals = NULL WHERE id = ?"); $stmt->execute([$data['id']]); // Log return $log_stmt = $db->prepare("INSERT INTO asset_history (asset_id, log_type, user_name, action_date, note) VALUES (?, 'return', ?, ?, ?)"); $log_stmt->execute([$data['id'], $asset['rental_user_name'], date('Y-m-d'), '반납 완료']); } echo json_encode(['success' => true]); break; case 'get_user_rentals': $user_id = (int) $_GET['user_id']; $query = "SELECT a.*, m.model_name, m.manufacturer FROM laptop_assets a JOIN laptop_models m ON a.model_id = m.id WHERE a.rental_user_id = ?"; $stmt = $db->prepare($query); $stmt->execute([$user_id]); echo json_encode($stmt->fetchAll()); break; case 'update_asset_remarks': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE laptop_assets SET remarks = ? WHERE id = ?"); $stmt->execute([$data['remarks'] ?? '', $data['id']]); echo json_encode(['success' => true]); break; case 'get_asset_history': $asset_id = $_GET['asset_id']; $rental_logs = $db->query("SELECT * FROM asset_history WHERE asset_id = $asset_id AND log_type = 'rental' ORDER BY id DESC LIMIT 20")->fetchAll(); $assign_logs = $db->query("SELECT * FROM asset_history WHERE asset_id = $asset_id AND log_type = 'assignment' ORDER BY id DESC LIMIT 20")->fetchAll(); echo json_encode([ 'rental' => $rental_logs, 'assignment' => $assign_logs ]); break; case 'get_cards': echo json_encode($db->query("SELECT * FROM access_cards")->fetchAll()); break; case 'add_card': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO access_cards (card_number, card_type, nfc_id, status) VALUES (?, ?, ?, ?)"); $stmt->execute([$data['card_number'], $data['card_type'], $data['nfc_id'], 'available']); echo json_encode(['success' => true]); break; case 'update_card': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE access_cards SET card_number = ?, card_type = ?, nfc_id = ? WHERE id = ?"); $stmt->execute([$data['card_number'], $data['card_type'], $data['nfc_id'], $data['id']]); echo json_encode(['success' => true]); break; case 'get_mfp': echo json_encode($db->query("SELECT * FROM mfp_accounts")->fetchAll()); break; case 'add_mfp': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO mfp_accounts (account_id, account_pw, purpose, status) VALUES (?, ?, ?, ?)"); $stmt->execute([$data['account_id'], $data['account_pw'], $data['purpose'], 'active']); echo json_encode(['success' => true]); break; case 'update_mfp': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE mfp_accounts SET account_id = ?, account_pw = ?, purpose = ? WHERE id = ?"); $stmt->execute([$data['account_id'], $data['account_pw'], $data['purpose'], $data['id']]); echo json_encode(['success' => true]); break; case 'get_models': echo json_encode($db->query("SELECT * FROM laptop_models ORDER BY manufacturer, model_name")->fetchAll()); break; case 'add_model': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO laptop_models ( model_name, manufacturer, specs, cpu, npu, hdd0_model, hdd0_capacity, hdd1_model, hdd1_capacity, ram, asset_status, assigned_user, fixed_ip, remarks, options, power_rating, purchase_date, vendor, product_name ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([ $data['model_name'], $data['manufacturer'], $data['specs'] ?? '', $data['cpu'] ?? '', $data['npu'] ?? '', $data['hdd0_model'] ?? '', $data['hdd0_capacity'] ?? '', $data['hdd1_model'] ?? '', $data['hdd1_capacity'] ?? '', $data['ram'] ?? '', $data['asset_status'] ?? '', $data['assigned_user'] ?? '', $data['fixed_ip'] ?? '', $data['remarks'] ?? '', $data['options'] ?? '', $data['power_rating'] ?? '', $data['purchase_date'] ?? '', $data['vendor'] ?? '', $data['product_name'] ?? '' ]); echo json_encode(['success' => true]); break; case 'update_model': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE laptop_models SET model_name = ?, manufacturer = ?, specs = ?, cpu = ?, npu = ?, hdd0_model = ?, hdd0_capacity = ?, hdd1_model = ?, hdd1_capacity = ?, ram = ?, asset_status = ?, assigned_user = ?, fixed_ip = ?, remarks = ?, options = ?, power_rating = ?, purchase_date = ?, vendor = ?, product_name = ? WHERE id = ?"); $stmt->execute([ $data['model_name'], $data['manufacturer'], $data['specs'] ?? '', $data['cpu'] ?? '', $data['npu'] ?? '', $data['hdd0_model'] ?? '', $data['hdd0_capacity'] ?? '', $data['hdd1_model'] ?? '', $data['hdd1_capacity'] ?? '', $data['ram'] ?? '', $data['asset_status'] ?? '', $data['assigned_user'] ?? '', $data['fixed_ip'] ?? '', $data['remarks'] ?? '', $data['options'] ?? '', $data['power_rating'] ?? '', $data['purchase_date'] ?? '', $data['vendor'] ?? '', $data['product_name'] ?? '', $data['id'] ]); echo json_encode(['success' => true]); break; case 'get_departments': $sql = "WITH RECURSIVE dept_path(id, name, path, level, parent_id) AS ( SELECT id, name, name, level, parent_id FROM departments WHERE parent_id IS NULL UNION ALL SELECT d.id, d.name, dp.path || ' > ' || d.name, d.level, d.parent_id FROM departments d JOIN dept_path dp ON d.parent_id = dp.id ) SELECT dp.*, (SELECT COUNT(*) FROM users WHERE department_id = dp.id) as member_count FROM dept_path dp ORDER BY path"; echo json_encode($db->query($sql)->fetchAll()); break; case 'add_laptop_asset': $data = json_decode(file_get_contents('php://input'), true); $asset_tag = trim($data['asset_tag'] ?? ''); // Duplicate check $check = $db->prepare("SELECT id FROM laptop_assets WHERE asset_tag = ?"); $check->execute([$asset_tag]); if ($check->fetch()) { echo json_encode(['success' => false, 'error' => "이미 존재하는 자산번호({$asset_tag})입니다."]); break; } $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, last_confirmed_date, disposal_recipient, disposal_date, real_user, disposal_user_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([ $data['asset_tag'], $data['model_id'], $data['current_user_id'] ?: null, $data['status'] ?: ($data['current_user_id'] ? 'assigned' : 'stock'), $data['serial_number'], $data['purchase_date'], $data['ip_address'] ?? null, $data['cpu'] ?? '', $data['npu'] ?? '', $data['hdd0_model'] ?? '', $data['hdd0_capacity'] ?? '', $data['hdd1_model'] ?? '', $data['hdd1_capacity'] ?? '', $data['ram'] ?? '', $data['asset_status'] ?? '', $data['assigned_user_name'] ?? '', $data['fixed_ip'] ?? '', $data['options'] ?? '', $data['power_rating'] ?? '', $data['manufacturer'] ?? '', $data['vendor'] ?? '', $data['product_name'] ?? '', $data['remarks'] ?? '', $data['last_confirmed_date'] ?? null, $data['disposal_recipient'] ?? null, $data['disposal_date'] ?? null, $data['real_user'] ?? null, $data['disposal_user_id'] ?? null ]); $new_asset_id = $db->lastInsertId(); if ($data['assigned_user_name']) { $log_stmt = $db->prepare("INSERT INTO asset_history (asset_id, log_type, user_name, action_date) VALUES (?, 'assignment', ?, ?)"); $log_stmt->execute([$new_asset_id, $data['assigned_user_name'], date('Y-m-d')]); } echo json_encode(['success' => true]); break; case 'update_laptop_asset': $data = json_decode(file_get_contents('php://input'), true); // Get old data for logging comparison $old_stmt = $db->prepare("SELECT assigned_user_name FROM laptop_assets WHERE id = ?"); $old_stmt->execute([$data['id']]); $old_asset = $old_stmt->fetch(); // Auto-relocate based on status if needed (optional) // If assigned_user_name is missing but current_user_id exists, try to fill it for logging if (empty($data['assigned_user_name']) && !empty($data['current_user_id'])) { $u_stmt = $db->prepare("SELECT name FROM users WHERE id = ?"); $u_stmt->execute([$data['current_user_id']]); $data['assigned_user_name'] = $u_stmt->fetchColumn() ?: ''; } $asset_tag = trim($data['asset_tag'] ?? ''); $asset_id = $data['id']; // Duplicate check (excluding current asset) $check = $db->prepare("SELECT id FROM laptop_assets WHERE asset_tag = ? AND id != ?"); $check->execute([$asset_tag, $asset_id]); if ($check->fetch()) { echo json_encode(['success' => false, 'error' => "이미 존재하는 자산번호({$asset_tag})입니다."]); break; } $stmt = $db->prepare("UPDATE laptop_assets SET 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 = ?, last_confirmed_date = ?, disposal_recipient = ?, disposal_date = ?, real_user = ?, disposal_user_id = ? WHERE id = ?"); $stmt->execute([ $data['asset_tag'], $data['model_id'], $data['current_user_id'] ?: null, $data['status'] ?: ($data['current_user_id'] ? 'assigned' : 'stock'), $data['serial_number'], $data['purchase_date'], $data['ip_address'] ?? null, $data['cpu'] ?? '', $data['npu'] ?? '', $data['hdd0_model'] ?? '', $data['hdd0_capacity'] ?? '', $data['hdd1_model'] ?? '', $data['hdd1_capacity'] ?? '', $data['ram'] ?? '', $data['asset_status'] ?? '', $data['assigned_user_name'] ?? '', $data['fixed_ip'] ?? '', $data['options'] ?? '', $data['power_rating'] ?? '', $data['manufacturer'] ?? '', $data['vendor'] ?? '', $data['product_name'] ?? '', $data['remarks'] ?? '', $data['last_confirmed_date'] ?? null, $data['disposal_recipient'] ?? null, $data['disposal_date'] ?? null, $data['real_user'] ?? null, $data['disposal_user_id'] ?? null, $data['id'] ]); // Auto-log assignment change $new_name = $data['assigned_user_name'] ?: ''; $old_name = $old_asset['assigned_user_name'] ?? ''; if ($new_name !== $old_name) { $log_stmt = $db->prepare("INSERT INTO asset_history (asset_id, log_type, user_name, action_date) VALUES (?, 'assignment', ?, ?)"); $log_stmt->execute([$data['id'], $new_name ?: '재고(반납)', date('Y-m-d')]); } echo json_encode(['success' => true]); break; case 'add_dept': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO departments (name, parent_id, level) VALUES (?, ?, ?)"); $stmt->execute([$data['name'], $data['parent_id'] ?: null, $data['level'] ?: 1]); echo json_encode(['success' => true]); break; case 'update_dept': $data = json_decode(file_get_contents('php://input'), true); $new_name = $data['name']; $parent_id = $data['parent_id'] ?: null; $dept_id = $data['id']; $merge_target_id = $data['merge_target_id'] ?? null; if ($merge_target_id) { // Scenario: Merge members to an existing department with the same name $db->prepare("UPDATE users SET department_id = ? WHERE department_id = ?")->execute([$merge_target_id, $dept_id]); // Recursively move children? For now, we delete the source dept after merging members $db->prepare("DELETE FROM departments WHERE id = ?")->execute([$dept_id]); echo json_encode(['success' => true, 'merged' => true]); } else { // Check if a department with the same name exists under the same parent (excluding itself) $check_stmt = $db->prepare("SELECT id FROM departments WHERE name = ? AND parent_id IS ? AND id != ?"); $check_stmt->execute([$new_name, $parent_id, $dept_id]); $existing = $check_stmt->fetch(); if ($existing) { echo json_encode(['success' => false, 'conflict' => true, 'existing_id' => $existing['id']]); } else { $stmt = $db->prepare("UPDATE departments SET name = ?, parent_id = ? WHERE id = ?"); $stmt->execute([$new_name, $parent_id, $dept_id]); echo json_encode(['success' => true]); } } break; case 'delete_dept': $dept_id = $_GET['id']; // Find FKI Root $fki_root = $db->query("SELECT id FROM departments WHERE name = '한국경제인협회' AND parent_id IS NULL LIMIT 1")->fetch(); $fki_root_id = $fki_root ? $fki_root['id'] : null; // 1. Find the '미소속' department under FKI Root $miso_id = $db->query("SELECT id FROM departments WHERE name = '미소속' AND parent_id IS " . ($fki_root_id ?: 'NULL') . " LIMIT 1")->fetchColumn(); if (!$miso_id) { $db->prepare("INSERT INTO departments (name, parent_id, level) VALUES ('미소속', ?, 1)")->execute([$fki_root_id]); $miso_id = $db->lastInsertId(); } // 2. Get current dept info for re-parenting children $current_dept = $db->prepare("SELECT parent_id FROM departments WHERE id = ?"); $current_dept->execute([$dept_id]); $parent_info = $current_dept->fetch(); $target_new_parent = $parent_info ? $parent_info['parent_id'] : null; // 3. Move members to '미소속' $db->prepare("UPDATE users SET department_id = ? WHERE department_id = ?")->execute([$miso_id, $dept_id]); // 4. Move child departments to its grandparent (re-parenting) to avoid FK constraint violation $db->prepare("UPDATE departments SET parent_id = ? WHERE parent_id = ?")->execute([$target_new_parent, $dept_id]); // 5. Delete the department $db->prepare("DELETE FROM departments WHERE id = ?")->execute([$dept_id]); echo json_encode(['success' => true]); break; case 'get_dept_users': $dept_id = $_GET['dept_id']; $stmt = $db->prepare("SELECT id, name, emp_id, position FROM users WHERE department_id = ?"); $stmt->execute([$dept_id]); echo json_encode($stmt->fetchAll()); break; case 'reassign_user': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("UPDATE users SET department_id = ? WHERE id = ?"); foreach ($data['user_ids'] as $u_id) { $stmt->execute([$data['new_dept_id'], $u_id]); } echo json_encode(['success' => true]); break; case 'bulk_update_laptops': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; $newStatus = $data['status'] ?? null; $newModelId = $data['model_id'] ?? null; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } $updates = []; $params = []; $log_msg = ""; if ($newStatus) { $updates[] = "status = ?"; $params[] = $newStatus; if ($newStatus === 'stock') { $updates[] = "current_user_id = NULL"; $updates[] = "assigned_user_name = '업무용(TEMP_44666b)'"; $log_msg = "일괄 상태 변경: 재고 (업무용 지정)"; } elseif ($newStatus === 'disposed') { $updates[] = "current_user_id = NULL"; $updates[] = "assigned_user_name = NULL"; $updates[] = "disposal_date = '" . date('Y-m-d') . "'"; $log_msg = "일괄 상태 변경: 매각됨"; } elseif ($newStatus === 'assigned') { $log_msg = "일괄 상태 변경: 직원배정"; } } if ($newModelId) { $updates[] = "model_id = ?"; $params[] = $newModelId; $log_msg .= ($log_msg ? ", " : "") . "일괄 모델 변경(ID: $newModelId)"; } if (empty($updates)) { echo json_encode(['success' => false, 'error' => 'No updates specified']); break; } $placeholders = implode(",", array_fill(0, count($ids), "?")); $sql = "UPDATE laptop_assets SET " . implode(", ", $updates) . " WHERE id IN ($placeholders)"; $stmt = $db->prepare($sql); $stmt->execute(array_merge($params, $ids)); // 히스토리 일괄 기록 if ($log_msg) { $history_sql = "INSERT INTO asset_history (asset_id, log_type, user_name, action_date, note) VALUES (?, 'bulk_update', '시스템(일괄)', ?, ?)"; $h_stmt = $db->prepare($history_sql); foreach ($ids as $id) { $h_stmt->execute([$id, date('Y-m-d'), $log_msg]); } } echo json_encode(['success' => true]); break; case 'bulk_delete_laptops': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } $placeholders = implode(",", array_fill(0, count($ids), "?")); $db->prepare("DELETE FROM laptop_assets WHERE id IN ($placeholders)")->execute($ids); // 관련 히스토리도 삭제? 보통 자산 삭제시에는 히스토리도 함께 날리거나 남김. 여기선 DB 정합성을 위해 연쇄 삭제는 안하더라도 자산은 사라짐. echo json_encode(['success' => true]); break; case 'bulk_update_cards': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; $newStatus = $data['status'] ?? null; $newCardType = $data['card_type'] ?? null; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } $updates = []; $params = []; if ($newStatus) { $updates[] = "status = ?"; $params[] = $newStatus; } if ($newCardType) { $updates[] = "card_type = ?"; $params[] = $newCardType; } if (empty($updates)) { echo json_encode(['success' => false, 'error' => 'No updates specified']); break; } $sql = "UPDATE access_cards SET " . implode(", ", $updates) . " WHERE id IN (" . implode(",", array_fill( 0, count($ids), "?" )) . ")"; $stmt = $db->prepare($sql); $stmt->execute(array_merge($params, $ids)); echo json_encode(['success' => true]); break; case 'bulk_update_mfp': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; $newStatus = $data['status'] ?? null; $newPurpose = $data['purpose'] ?? null; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } $updates = []; $params = []; if ($newStatus) { $updates[] = "status = ?"; $params[] = $newStatus; } if ($newPurpose) { $updates[] = "purpose = ?"; $params[] = $newPurpose; } if (empty($updates)) { echo json_encode(['success' => false, 'error' => 'No updates specified']); break; } $sql = "UPDATE mfp_accounts SET " . implode(", ", $updates) . " WHERE id IN (" . implode(",", array_fill( 0, count($ids), "?" )) . ")"; $stmt = $db->prepare($sql); $stmt->execute(array_merge($params, $ids)); echo json_encode(['success' => true]); break; case 'bulk_update_models': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; $newManufacturer = $data['manufacturer'] ?? null; $newProductName = $data['product_name'] ?? null; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } $updates = []; $params = []; if ($newManufacturer) { $updates[] = "manufacturer = ?"; $params[] = $newManufacturer; } if ($newProductName) { $updates[] = "product_name = ?"; $params[] = $newProductName; } if (empty($updates)) { echo json_encode(['success' => false, 'error' => 'No updates specified']); break; } $sql = "UPDATE laptop_models SET " . implode(", ", $updates) . " WHERE id IN (" . implode(",", array_fill( 0, count($ids), "?" )) . ")"; $stmt = $db->prepare($sql); $stmt->execute(array_merge($params, $ids)); echo json_encode(['success' => true]); break; case 'bulk_delete_models': $data = json_decode(file_get_contents('php://input'), true); $ids = $data['ids']; if (empty($ids)) { echo json_encode(['success' => false, 'error' => 'No items selected']); break; } try { $db->beginTransaction(); $placeholders = implode(",", array_fill(0, count($ids), "?")); // 1. 해당 모델을 참조 중인 자산들의 연결 고리 해제 (ID 참조를 NULL로 변경) $stmt1 = $db->prepare("UPDATE laptop_assets SET model_id = NULL WHERE model_id IN ($placeholders)"); $stmt1->execute($ids); // 2. 노트북 모델 마스터 정보 삭제 $stmt2 = $db->prepare("DELETE FROM laptop_models WHERE id IN ($placeholders)"); $stmt2->execute($ids); $db->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { $db->rollBack(); echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'bulk_update_users': $data = json_decode(file_get_contents('php://input'), true); $userIds = $data['user_ids']; $newStatus = $data['status'] ?? null; $newDeptId = $data['department_id'] ?? null; if (empty($userIds)) { echo json_encode(['success' => false, 'error' => 'No users selected']); break; } // Enhanced logic: if status is changed to leave/retired, override dept if ($newStatus === 'on_leave' || $newStatus === 'retired') { $dept_name = ($newStatus === 'on_leave') ? '휴직' : '퇴사'; $target_dept = $db->query("SELECT id FROM departments WHERE name = '$dept_name'")->fetchColumn(); if ($target_dept) $newDeptId = $target_dept; } $sql = "UPDATE users SET "; $params = []; $updates = []; if ($newStatus) { $updates[] = "status = ?"; $params[] = $newStatus; } if ($newDeptId) { $updates[] = "department_id = ?"; $params[] = $newDeptId; } $newPosition = $data['position'] ?? null; if ($newPosition) { $updates[] = "position = ?"; $params[] = $newPosition; } if (empty($updates)) { echo json_encode(['success' => false, 'error' => 'No updates specified']); break; } $sql .= implode(", ", $updates) . " WHERE id IN (" . implode(",", array_fill(0, count($userIds), "?")) . ")"; $params = array_merge($params, $userIds); $stmt = $db->prepare($sql); $stmt->execute($params); echo json_encode(['success' => true]); break; case 'archive_users': $data = json_decode(file_get_contents('php://input'), true); $userIds = $data['user_ids']; if (empty($userIds)) { echo json_encode(['success' => false, 'error' => 'No users selected']); break; } $db->beginTransaction(); try { $placeholders = implode(',', array_fill(0, count($userIds), '?')); // Copy to archive $db->prepare("INSERT INTO users_archive (id, emp_id, name, department_id, position, email, phone, mobile, accounting_type, status) SELECT id, emp_id, name, department_id, position, email, phone, mobile, accounting_type, status FROM users WHERE id IN ($placeholders)") ->execute($userIds); // Delete from original $db->prepare("DELETE FROM users WHERE id IN ($placeholders)")->execute($userIds); $db->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { $db->rollBack(); echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'get_archived_users': $query = "SELECT u.*, dp.path as dept_name FROM users_archive u LEFT JOIN ( WITH RECURSIVE dept_path(id, path) AS ( SELECT id, name FROM departments WHERE parent_id IS NULL UNION ALL SELECT d.id, dp.path || ' > ' || d.name FROM departments d JOIN dept_path dp ON d.parent_id = dp.id ) SELECT * FROM dept_path ) dp ON u.department_id = dp.id ORDER BY u.archived_at DESC"; echo json_encode($db->query($query)->fetchAll()); break; case 'restore_users': $data = json_decode(file_get_contents('php://input'), true); $userIds = $data['user_ids']; if (empty($userIds)) { echo json_encode(['success' => false, 'error' => 'No users selected']); break; } $db->beginTransaction(); try { $placeholders = implode(',', array_fill(0, count($userIds), '?')); // Restore to users $db->prepare("INSERT INTO users (id, emp_id, name, department_id, position, email, phone, mobile, accounting_type, status) SELECT id, emp_id, name, department_id, position, email, phone, mobile, accounting_type, status FROM users_archive WHERE id IN ($placeholders)") ->execute($userIds); // Delete from archive $db->prepare("DELETE FROM users_archive WHERE id IN ($placeholders)")->execute($userIds); $db->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { $db->rollBack(); echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'bulk_delete_users': $data = json_decode(file_get_contents('php://input'), true); $userIds = $data['user_ids']; if (empty($userIds)) { echo json_encode(['success' => false, 'error' => 'No users selected']); break; } $placeholders = implode(',', array_fill(0, count($userIds), '?')); $db->prepare("DELETE FROM users WHERE id IN ($placeholders)")->execute($userIds); echo json_encode(['success' => true]); break; case 'export_users_csv': $data = json_decode(file_get_contents('php://input'), true); $userIds = $data['user_ids'] ?? []; $columns = $data['columns'] ?? []; // ['name' => '이름', 'emp_id' => '사번', ...] if (empty($columns)) { echo json_encode(['success' => false, 'error' => '내보낼 컬럼을 선택해주세요.']); break; } // 전체 유저 정보 조회 (기본 쿼리 재사용) $query = "WITH RECURSIVE dept_path(id, path) AS ( SELECT id, name FROM departments WHERE parent_id IS NULL UNION ALL SELECT d.id, dp.path || ' > ' || d.name FROM departments d JOIN dept_path dp ON d.parent_id = dp.id ) SELECT u.*, dp.path as dept_name, (SELECT asset_tag FROM laptop_assets WHERE current_user_id = u.id ORDER BY id DESC LIMIT 1) as laptop_tag FROM users u LEFT JOIN dept_path dp ON u.department_id = dp.id"; if (!empty($userIds)) { $placeholders = implode(',', array_fill(0, count($userIds), '?')); $query .= " WHERE u.id IN ($placeholders)"; $stmt = $db->prepare($query); $stmt->execute($userIds); } else { $stmt = $db->query($query); } $users = $stmt->fetchAll(); // CSV 스트림 생성 header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename=직원_마스터_리스트_' . date('Ymd_His') . '.csv'); $output = fopen('php://output', 'w'); // Zero-Mojibake: BOM (Byte Order Mark) 삽입 fwrite($output, "\xEF\xBB\xBF"); // 헤더 작성 fputcsv($output, array_values($columns)); // 데이터 작성 foreach ($users as $user) { $row = []; foreach (array_keys($columns) as $key) { $val = $user[$key] ?? ''; // 상태 값 한글 변환 if ($key === 'status') { $val = match ($val) { 'active' => '재직', 'retired' => '퇴사', 'on_leave' => '휴직', 'classification' => '분류', default => $val }; } $row[] = $val; } fputcsv($output, $row); } fclose($output); exit; case 'get_general_rentals': $query = "SELECT r.*, GROUP_CONCAT(i.item_name, ', ') as items FROM general_rentals r LEFT JOIN general_rental_items i ON r.id = i.rental_id GROUP BY r.id ORDER BY r.id DESC"; echo json_encode($db->query($query)->fetchAll()); break; case 'add_general_rental': $data = json_decode(file_get_contents('php://input'), true); $startDate = $data['rental_start_date'] ?: date('Y-m-d'); $db->beginTransaction(); try { $stmt = $db->prepare("INSERT INTO general_rentals (user_id, user_name, rental_start_date, rental_reason, status) VALUES (?, ?, ?, ?, 'rented')"); $stmt->execute([ $data['user_id'] ?? null, $data['user_name'], $startDate, $data['rental_reason'] ?? '' ]); $rentalId = $db->lastInsertId(); if (!empty($data['items'])) { $itemStmt = $db->prepare("INSERT INTO general_rental_items (rental_id, item_name) VALUES (?, ?)"); foreach ($data['items'] as $itemName) { if (trim($itemName)) { $itemStmt->execute([$rentalId, trim($itemName)]); } } } $db->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { $db->rollBack(); echo json_encode(['success' => false, 'error' => $e->getMessage()]); } break; case 'get_settings': $settings = $db->query("SELECT key_name, value FROM system_settings")->fetchAll(PDO::FETCH_KEY_PAIR); echo json_encode(['success' => true, 'settings' => $settings]); break; case 'update_settings': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT OR REPLACE INTO system_settings (key_name, value) VALUES (?, ?)"); foreach ($data['settings'] as $key => $value) { $stmt->execute([$key, (string) $value]); } echo json_encode(['success' => true]); break; case 'return_general_rental': $data = json_decode(file_get_contents('php://input'), true); $returnDate = date('Y-m-d'); $stmt = $db->prepare("UPDATE general_rentals SET status = 'returned', rental_return_date = ? WHERE id = ?"); $stmt->execute([$returnDate, $data['id']]); echo json_encode(['success' => true]); break; default: echo json_encode(['error' => 'Invalid action']); break; } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); }