diff --git a/sales_manager_scenario/api_handler.php b/sales_manager_scenario/api_handler.php
deleted file mode 100644
index e75a6db7..00000000
--- a/sales_manager_scenario/api_handler.php
+++ /dev/null
@@ -1,67 +0,0 @@
-prepare("SELECT step_id, checkpoint_index FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND is_checked = 1");
- $stmt->execute([':tenant_id' => $tenantId]);
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- $data = [];
- foreach ($rows as $row) {
- if (!isset($data[$row['step_id']])) {
- $data[$row['step_id']] = [];
- }
- $data[$row['step_id']][] = (int)$row['checkpoint_index'];
- }
-
- echo json_encode(['success' => true, 'data' => $data]);
- } catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => $e->getMessage()]);
- }
-}
-elseif ($method === 'POST') {
- $input = json_decode(file_get_contents('php://input'), true);
-
- if (!$input) {
- echo json_encode(['success' => false, 'message' => 'Invalid input']);
- exit;
- }
-
- $tenantId = $input['tenant_id'] ?? 'default_tenant';
- $stepId = $input['step_id'];
- $checkpointIndex = $input['checkpoint_index'];
- $isChecked = $input['is_checked'] ? 1 : 0;
-
- try {
- if ($isChecked) {
- // Insert or ignore (if already exists)
- $stmt = $pdo->prepare("INSERT IGNORE INTO manager_scenario_checklist (tenant_id, step_id, checkpoint_index, is_checked) VALUES (:tenant_id, :step_id, :idx, 1)");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
- } else {
- // Delete record if unchecked
- $stmt = $pdo->prepare("DELETE FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND checkpoint_index = :idx");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
- }
-
- // Return updated list for this step
- $stmt = $pdo->prepare("SELECT checkpoint_index FROM manager_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND is_checked = 1");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId]);
- $checkedIndices = $stmt->fetchAll(PDO::FETCH_COLUMN);
-
- echo json_encode(['success' => true, 'data' => array_map('intval', $checkedIndices)]);
- } catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => $e->getMessage()]);
- }
-}
-?>
diff --git a/sales_manager_scenario/delete_consultation.php b/sales_manager_scenario/delete_consultation.php
deleted file mode 100644
index 4aa070e0..00000000
--- a/sales_manager_scenario/delete_consultation.php
+++ /dev/null
@@ -1,172 +0,0 @@
- 'RS256', 'typ' => 'JWT']));
- $jwtClaim = base64_encode(json_encode([
- 'iss' => $serviceAccount['client_email'],
- 'scope' => 'https://www.googleapis.com/auth/devstorage.full_control',
- 'aud' => 'https://oauth2.googleapis.com/token',
- 'exp' => $now + 3600,
- 'iat' => $now
- ]));
-
- $privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
- if (!$privateKey) {
- error_log('GCS 삭제 실패: 개인 키 읽기 오류');
- return false;
- }
-
- openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
- openssl_free_key($privateKey);
-
- $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature);
-
- // OAuth 토큰 요청
- $tokenCh = curl_init('https://oauth2.googleapis.com/token');
- curl_setopt($tokenCh, CURLOPT_POST, true);
- curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([
- 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
- 'assertion' => $jwt
- ]));
- curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($tokenCh, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
-
- $tokenResponse = curl_exec($tokenCh);
- $tokenCode = curl_getinfo($tokenCh, CURLINFO_HTTP_CODE);
- curl_close($tokenCh);
-
- if ($tokenCode !== 200) {
- error_log('GCS 삭제 실패: OAuth 토큰 요청 실패 (HTTP ' . $tokenCode . ')');
- return false;
- }
-
- $tokenData = json_decode($tokenResponse, true);
- if (!isset($tokenData['access_token'])) {
- error_log('GCS 삭제 실패: OAuth 토큰 없음');
- return false;
- }
-
- $accessToken = $tokenData['access_token'];
-
- // GCS에서 파일 삭제
- $delete_url = 'https://storage.googleapis.com/storage/v1/b/' .
- urlencode($bucket_name) . '/o/' .
- urlencode($object_name);
-
- $ch = curl_init($delete_url);
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Authorization: Bearer ' . $accessToken
- ]);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $response = curl_exec($ch);
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
-
- // 204 No Content 또는 404 Not Found는 성공으로 간주
- if ($code === 204 || $code === 404) {
- return true;
- } else {
- error_log('GCS 삭제 실패 (HTTP ' . $code . '): ' . $response);
- return false;
- }
-}
-
-// 출력 버퍼 비우기
-ob_clean();
-
-header('Content-Type: application/json; charset=utf-8');
-
-// 권한 체크
-if (!isset($user_id) || $level > 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-// 업무협의 ID 확인
-$consultation_id = isset($_POST['id']) ? intval($_POST['id']) : 0;
-$manager_id = $user_id;
-
-if ($consultation_id <= 0) {
- echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
- exit;
-}
-
-try {
- $pdo = db_connect();
-
- // 녹음 파일 정보 조회
- $sql = "SELECT audio_file_path FROM manager_consultations WHERE id = ? AND manager_id = ?";
- $stmt = $pdo->prepare($sql);
- $stmt->execute([$consultation_id, $manager_id]);
- $consultation = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if (!$consultation) {
- echo json_encode(['success' => false, 'message' => '녹음 파일을 찾을 수 없습니다.']);
- exit;
- }
-
- // 1. 서버 파일 삭제
- if (!empty($consultation['audio_file_path'])) {
- // GCS URI가 아닌 경우 로컬 파일 삭제
- if (strpos($consultation['audio_file_path'], 'gs://') !== 0) {
- $file_path = $_SERVER['DOCUMENT_ROOT'] . $consultation['audio_file_path'];
- $file_path = str_replace('\\', '/', $file_path);
- $file_path = preg_replace('#/+#', '/', $file_path);
-
- if (file_exists($file_path)) {
- @unlink($file_path);
- }
- } else {
- // 2. GCS 파일 삭제 (GCS URI인 경우)
- $gcs_uri = $consultation['audio_file_path'];
- if (preg_match('#gs://([^/]+)/(.+)#', $gcs_uri, $matches)) {
- $bucket_name = $matches[1];
- $object_name = $matches[2];
-
- $gcs_deleted = deleteFromGCS($bucket_name, $object_name);
- if (!$gcs_deleted) {
- error_log('GCS 파일 삭제 실패: ' . $gcs_uri);
- }
- }
- }
- }
-
- // 3. DB에서 삭제
- $delete_sql = "DELETE FROM manager_consultations WHERE id = ? AND manager_id = ?";
- $delete_stmt = $pdo->prepare($delete_sql);
- $delete_stmt->execute([$consultation_id, $manager_id]);
-
- echo json_encode(['success' => true, 'message' => '녹음 파일이 삭제되었습니다.']);
-
-} catch (Exception $e) {
- error_log('녹음 파일 삭제 오류: ' . $e->getMessage());
- echo json_encode(['success' => false, 'message' => '삭제 중 오류가 발생했습니다: ' . $e->getMessage()]);
-}
-?>
-
diff --git a/sales_manager_scenario/download_consultation.php b/sales_manager_scenario/download_consultation.php
deleted file mode 100644
index 2703eb87..00000000
--- a/sales_manager_scenario/download_consultation.php
+++ /dev/null
@@ -1,113 +0,0 @@
- 5) {
- header('HTTP/1.0 403 Forbidden');
- die('접근 권한이 없습니다.');
-}
-
-// 녹음 파일 ID 확인
-$consultation_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
-$manager_id = $user_id;
-
-if ($consultation_id <= 0) {
- header('HTTP/1.0 400 Bad Request');
- die('잘못된 요청입니다.');
-}
-
-try {
- $pdo = db_connect();
-
- $sql = "SELECT audio_file_path, created_at
- FROM manager_consultations
- WHERE id = ? AND manager_id = ?";
- $stmt = $pdo->prepare($sql);
- $stmt->execute([$consultation_id, $manager_id]);
- $consultation = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if (!$consultation || empty($consultation['audio_file_path'])) {
- header('HTTP/1.0 404 Not Found');
- die('오디오 파일을 찾을 수 없습니다.');
- }
-
- // GCS URI인 경우 처리 불가 (직접 다운로드 불가)
- if (strpos($consultation['audio_file_path'], 'gs://') === 0) {
- header('HTTP/1.0 400 Bad Request');
- die('GCS에 저장된 파일은 직접 다운로드할 수 없습니다.');
- }
-
- // 파일 경로 구성
- $file_path = $_SERVER['DOCUMENT_ROOT'] . $consultation['audio_file_path'];
-
- // 경로 정규화
- $file_path = str_replace('\\', '/', $file_path);
- $file_path = preg_replace('#/+#', '/', $file_path);
-
- // 파일 존재 확인
- if (!file_exists($file_path)) {
- header('HTTP/1.0 404 Not Found');
- die('오디오 파일이 서버에 존재하지 않습니다.');
- }
-
- // 파일 확장자 확인
- $file_extension = pathinfo($file_path, PATHINFO_EXTENSION) ?: 'webm';
- $mime_types = [
- 'webm' => 'audio/webm',
- 'wav' => 'audio/wav',
- 'mp3' => 'audio/mpeg',
- 'ogg' => 'audio/ogg',
- 'm4a' => 'audio/mp4'
- ];
-
- $content_type = isset($mime_types[$file_extension])
- ? $mime_types[$file_extension]
- : 'audio/webm';
-
- // 다운로드 파일명 생성
- $date = date('Ymd_His', strtotime($consultation['created_at']));
- $download_filename = '상담녹음_' . $date . '.' . $file_extension;
-
- // 출력 버퍼 비우기
- ob_clean();
-
- $file_size = filesize($file_path);
- if ($file_size === false || $file_size == 0) {
- header('HTTP/1.0 500 Internal Server Error');
- die('파일을 읽을 수 없습니다.');
- }
-
- // 헤더 설정
- header('Content-Type: ' . $content_type);
- header('Content-Disposition: attachment; filename="' . $download_filename . '"');
- header('Content-Length: ' . $file_size);
- header('Content-Transfer-Encoding: binary');
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
- header('Pragma: public');
- header('Expires: 0');
-
- // 파일 출력
- $handle = @fopen($file_path, 'rb');
- if ($handle === false) {
- header('HTTP/1.0 500 Internal Server Error');
- die('파일을 열 수 없습니다.');
- }
-
- while (!feof($handle)) {
- $chunk = fread($handle, 8192);
- if ($chunk === false) break;
- echo $chunk;
- flush();
- }
- fclose($handle);
- exit;
-
-} catch (Exception $e) {
- header('HTTP/1.0 500 Internal Server Error');
- die('파일 다운로드 중 오류가 발생했습니다.');
-}
\ No newline at end of file
diff --git a/sales_manager_scenario/get_consultation_detail.php b/sales_manager_scenario/get_consultation_detail.php
deleted file mode 100644
index 1121fbef..00000000
--- a/sales_manager_scenario/get_consultation_detail.php
+++ /dev/null
@@ -1,61 +0,0 @@
- 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-$consultation_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
-$manager_id = $user_id;
-
-if ($consultation_id <= 0) {
- echo json_encode(['success' => false, 'message' => '잘못된 요청입니다.']);
- exit;
-}
-
-try {
- $pdo = db_connect();
-
- $sql = "SELECT id, manager_id, step_id, audio_file_path, transcript_text,
- file_expiry_date, created_at
- FROM manager_consultations
- WHERE id = ? AND manager_id = ?";
- $stmt = $pdo->prepare($sql);
- $stmt->execute([$consultation_id, $manager_id]);
- $consultation = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if (!$consultation) {
- echo json_encode(['success' => false, 'message' => '녹음 파일을 찾을 수 없습니다.']);
- exit;
- }
-
- // 날짜 포맷팅
- $consultation['created_at_formatted'] = date('Y-m-d H:i:s', strtotime($consultation['created_at']));
- $consultation['is_gcs'] = strpos($consultation['audio_file_path'], 'gs://') === 0;
-
- echo json_encode([
- 'success' => true,
- 'data' => $consultation
- ]);
-
-} catch (Exception $e) {
- error_log('조회 오류: ' . $e->getMessage());
- echo json_encode([
- 'success' => false,
- 'message' => '조회 실패: ' . $e->getMessage()
- ]);
-}
-?>
-
diff --git a/sales_manager_scenario/get_consultation_files.php b/sales_manager_scenario/get_consultation_files.php
deleted file mode 100644
index 1ff09a8e..00000000
--- a/sales_manager_scenario/get_consultation_files.php
+++ /dev/null
@@ -1,68 +0,0 @@
- 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-$manager_id = $user_id;
-$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 2;
-$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 20;
-
-try {
- $pdo = db_connect();
- if (!$pdo) {
- throw new Exception('데이터베이스 연결 실패');
- }
-
- // 저장된 첨부파일 목록 조회
- $sql = "SELECT id, manager_id, step_id, file_paths_json,
- file_expiry_date, created_at
- FROM manager_consultation_files
- WHERE manager_id = ? AND step_id = ?
- ORDER BY created_at DESC
- LIMIT ?";
- $stmt = $pdo->prepare($sql);
-
- if (!$stmt) {
- throw new Exception('SQL 준비 실패');
- }
-
- $stmt->execute([$manager_id, $step_id, $limit]);
- $files = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- // JSON 파싱 및 날짜 포맷팅
- foreach ($files as &$file) {
- $file['files'] = json_decode($file['file_paths_json'], true) ?: [];
- $file['created_at_formatted'] = date('Y-m-d H:i', strtotime($file['created_at']));
- $file['file_count'] = count($file['files']);
- }
-
- echo json_encode([
- 'success' => true,
- 'data' => $files,
- 'count' => count($files)
- ]);
-
-} catch (Exception $e) {
- error_log('조회 오류: ' . $e->getMessage());
- echo json_encode([
- 'success' => false,
- 'message' => '조회 실패: ' . $e->getMessage(),
- 'data' => []
- ]);
-}
-?>
-
diff --git a/sales_manager_scenario/get_consultations.php b/sales_manager_scenario/get_consultations.php
deleted file mode 100644
index 2faa1118..00000000
--- a/sales_manager_scenario/get_consultations.php
+++ /dev/null
@@ -1,67 +0,0 @@
- 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-$manager_id = $user_id;
-$step_id = isset($_GET['step_id']) ? intval($_GET['step_id']) : 2;
-$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 20;
-
-try {
- $pdo = db_connect();
- if (!$pdo) {
- throw new Exception('데이터베이스 연결 실패');
- }
-
- // 저장된 녹음 파일 목록 조회
- $sql = "SELECT id, manager_id, step_id, audio_file_path, transcript_text,
- file_expiry_date, created_at
- FROM manager_consultations
- WHERE manager_id = ? AND step_id = ?
- ORDER BY created_at DESC
- LIMIT ?";
- $stmt = $pdo->prepare($sql);
-
- if (!$stmt) {
- throw new Exception('SQL 준비 실패');
- }
-
- $stmt->execute([$manager_id, $step_id, $limit]);
- $consultations = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- // 날짜 포맷팅
- foreach ($consultations as &$consultation) {
- $consultation['created_at_formatted'] = date('Y-m-d H:i', strtotime($consultation['created_at']));
- $consultation['is_gcs'] = strpos($consultation['audio_file_path'], 'gs://') === 0;
- }
-
- echo json_encode([
- 'success' => true,
- 'data' => $consultations,
- 'count' => count($consultations)
- ]);
-
-} catch (Exception $e) {
- error_log('조회 오류: ' . $e->getMessage());
- echo json_encode([
- 'success' => false,
- 'message' => '조회 실패: ' . $e->getMessage(),
- 'data' => []
- ]);
-}
-?>
-
diff --git a/sales_manager_scenario/index.php b/sales_manager_scenario/index.php
deleted file mode 100644
index 3814a4ae..00000000
--- a/sales_manager_scenario/index.php
+++ /dev/null
@@ -1,1630 +0,0 @@
-
-
-
-
-
- SAM 매니저 시나리오 - CodeBridgeExy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sales_manager_scenario/save_consultation.php b/sales_manager_scenario/save_consultation.php
deleted file mode 100644
index 442e3546..00000000
--- a/sales_manager_scenario/save_consultation.php
+++ /dev/null
@@ -1,218 +0,0 @@
- 'RS256', 'typ' => 'JWT']));
- $jwtClaim = base64_encode(json_encode([
- 'iss' => $serviceAccount['client_email'],
- 'scope' => 'https://www.googleapis.com/auth/devstorage.full_control',
- 'aud' => 'https://oauth2.googleapis.com/token',
- 'exp' => $now + 3600,
- 'iat' => $now
- ]));
-
- $privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
- if (!$privateKey) {
- error_log('GCS 업로드 실패: 개인 키 읽기 오류');
- return false;
- }
-
- openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
- openssl_free_key($privateKey);
-
- $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature);
-
- // OAuth 토큰 요청
- $tokenCh = curl_init('https://oauth2.googleapis.com/token');
- curl_setopt($tokenCh, CURLOPT_POST, true);
- curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([
- 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
- 'assertion' => $jwt
- ]));
- curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($tokenCh, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
-
- $tokenResponse = curl_exec($tokenCh);
- $tokenCode = curl_getinfo($tokenCh, CURLINFO_HTTP_CODE);
- curl_close($tokenCh);
-
- if ($tokenCode !== 200) {
- error_log('GCS 업로드 실패: OAuth 토큰 요청 실패 (HTTP ' . $tokenCode . ')');
- return false;
- }
-
- $tokenData = json_decode($tokenResponse, true);
- if (!isset($tokenData['access_token'])) {
- error_log('GCS 업로드 실패: OAuth 토큰 없음');
- return false;
- }
-
- $accessToken = $tokenData['access_token'];
-
- // GCS에 파일 업로드
- $file_content = file_get_contents($file_path);
- $mime_type = mime_content_type($file_path) ?: 'audio/webm';
-
- $upload_url = 'https://storage.googleapis.com/upload/storage/v1/b/' .
- urlencode($bucket_name) . '/o?uploadType=media&name=' .
- urlencode($object_name);
-
- $ch = curl_init($upload_url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Authorization: Bearer ' . $accessToken,
- 'Content-Type: ' . $mime_type,
- 'Content-Length: ' . strlen($file_content)
- ]);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $response = curl_exec($ch);
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
-
- if ($code === 200) {
- return 'gs://' . $bucket_name . '/' . $object_name;
- } else {
- error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response);
- return false;
- }
-}
-
-// 출력 버퍼 비우기
-ob_clean();
-
-header('Content-Type: application/json; charset=utf-8');
-
-// 1. 권한 및 세션 체크
-if (!isset($user_id) || $level > 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-$manager_id = $user_id; // session.php에서 user_id를 manager_id로 사용
-$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/";
-
-// 2. 파일 업로드 처리
-if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true);
-
-if (!isset($_FILES['audio_file'])) {
- echo json_encode(['success' => false, 'message' => '오디오 파일이 없습니다.']);
- exit;
-}
-
-// 파일 크기 확인
-if ($_FILES['audio_file']['size'] == 0) {
- echo json_encode(['success' => false, 'message' => '오디오 파일이 비어있습니다.']);
- exit;
-}
-
-$file_name = date('Ymd_His') . "_" . uniqid() . ".webm";
-$file_path = $upload_dir . $file_name;
-
-if (!move_uploaded_file($_FILES['audio_file']['tmp_name'], $file_path)) {
- echo json_encode(['success' => false, 'message' => '파일 저장 실패']);
- exit;
-}
-
-// 3. GCS 업로드 (선택사항 - 파일이 큰 경우)
-$gcs_uri = null;
-$file_size = filesize($file_path);
-$max_local_size = 10 * 1024 * 1024; // 10MB
-
-if ($file_size > $max_local_size) {
- // GCS 설정 확인
- $gcs_config_file = $_SERVER['DOCUMENT_ROOT'] . '/apikey/gcs_config.txt';
- $bucket_name = null;
-
- if (file_exists($gcs_config_file)) {
- $gcs_config = parse_ini_file($gcs_config_file);
- $bucket_name = isset($gcs_config['bucket_name']) ? $gcs_config['bucket_name'] : null;
- }
-
- if ($bucket_name) {
- $googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
- $gcs_object_name = 'manager_consultations/' . $manager_id . '/' . basename($file_path);
- $gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile);
-
- if ($gcs_uri) {
- // GCS 업로드 성공 시 로컬 파일 삭제 (선택사항)
- // @unlink($file_path);
- }
- }
-}
-
-// 4. DB 저장
-$transcript = isset($_POST['transcript']) ? trim($_POST['transcript']) : '';
-$step_id = isset($_POST['step_id']) ? intval($_POST['step_id']) : 2;
-$web_path = "/uploads/manager_consultations/" . $manager_id . "/" . $file_name;
-$file_path_to_store = $gcs_uri ? $gcs_uri : $web_path;
-
-try {
- $pdo = db_connect();
- if (!$pdo) {
- throw new Exception('데이터베이스 연결 실패');
- }
-
- $expiry_date = date('Y-m-d H:i:s', strtotime('+30 days')); // 30일 보관
-
- // manager_consultations 테이블에 저장 (테이블이 없으면 생성 필요)
- $sql = "INSERT INTO manager_consultations
- (manager_id, step_id, audio_file_path, transcript_text, file_expiry_date, created_at)
- VALUES (?, ?, ?, ?, ?, NOW())";
- $stmt = $pdo->prepare($sql);
-
- if (!$stmt) {
- throw new Exception('SQL 준비 실패');
- }
-
- $executeResult = $stmt->execute([
- $manager_id,
- $step_id,
- $file_path_to_store,
- $transcript,
- $expiry_date
- ]);
-
- if (!$executeResult) {
- throw new Exception('SQL 실행 실패');
- }
-
- $insertId = $pdo->lastInsertId();
-
- echo json_encode([
- 'success' => true,
- 'message' => '녹음 파일이 저장되었습니다.',
- 'id' => $insertId,
- 'file_path' => $file_path_to_store,
- 'gcs_uri' => $gcs_uri
- ]);
-
-} catch (Exception $e) {
- error_log('DB 저장 오류: ' . $e->getMessage());
- echo json_encode([
- 'success' => false,
- 'message' => '데이터베이스 저장 실패: ' . $e->getMessage()
- ]);
-}
-?>
-
diff --git a/sales_manager_scenario/upload_consultation_files.php b/sales_manager_scenario/upload_consultation_files.php
deleted file mode 100644
index bb00897b..00000000
--- a/sales_manager_scenario/upload_consultation_files.php
+++ /dev/null
@@ -1,234 +0,0 @@
- 'RS256', 'typ' => 'JWT']));
- $jwtClaim = base64_encode(json_encode([
- 'iss' => $serviceAccount['client_email'],
- 'scope' => 'https://www.googleapis.com/auth/devstorage.full_control',
- 'aud' => 'https://oauth2.googleapis.com/token',
- 'exp' => $now + 3600,
- 'iat' => $now
- ]));
-
- $privateKey = openssl_pkey_get_private($serviceAccount['private_key']);
- if (!$privateKey) {
- error_log('GCS 업로드 실패: 개인 키 읽기 오류');
- return false;
- }
-
- openssl_sign($jwtHeader . '.' . $jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
- openssl_free_key($privateKey);
-
- $jwt = $jwtHeader . '.' . $jwtClaim . '.' . base64_encode($signature);
-
- // OAuth 토큰 요청
- $tokenCh = curl_init('https://oauth2.googleapis.com/token');
- curl_setopt($tokenCh, CURLOPT_POST, true);
- curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query([
- 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
- 'assertion' => $jwt
- ]));
- curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($tokenCh, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
-
- $tokenResponse = curl_exec($tokenCh);
- $tokenCode = curl_getinfo($tokenCh, CURLINFO_HTTP_CODE);
- curl_close($tokenCh);
-
- if ($tokenCode !== 200) {
- error_log('GCS 업로드 실패: OAuth 토큰 요청 실패 (HTTP ' . $tokenCode . ')');
- return false;
- }
-
- $tokenData = json_decode($tokenResponse, true);
- if (!isset($tokenData['access_token'])) {
- error_log('GCS 업로드 실패: OAuth 토큰 없음');
- return false;
- }
-
- $accessToken = $tokenData['access_token'];
-
- // GCS에 파일 업로드
- $file_content = file_get_contents($file_path);
- $mime_type = mime_content_type($file_path) ?: 'application/octet-stream';
-
- $upload_url = 'https://storage.googleapis.com/upload/storage/v1/b/' .
- urlencode($bucket_name) . '/o?uploadType=media&name=' .
- urlencode($object_name);
-
- $ch = curl_init($upload_url);
- curl_setopt($ch, CURLOPT_POST, true);
- curl_setopt($ch, CURLOPT_HTTPHEADER, [
- 'Authorization: Bearer ' . $accessToken,
- 'Content-Type: ' . $mime_type,
- 'Content-Length: ' . strlen($file_content)
- ]);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-
- $response = curl_exec($ch);
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- curl_close($ch);
-
- if ($code === 200) {
- return 'gs://' . $bucket_name . '/' . $object_name;
- } else {
- error_log('GCS 업로드 실패 (HTTP ' . $code . '): ' . $response);
- return false;
- }
-}
-
-// 출력 버퍼 비우기
-ob_clean();
-
-header('Content-Type: application/json; charset=utf-8');
-
-// 1. 권한 및 세션 체크
-if (!isset($user_id) || $level > 5) {
- echo json_encode(['success' => false, 'message' => '접근 권한이 없습니다.']);
- exit;
-}
-
-$manager_id = $user_id;
-$step_id = isset($_POST['step_id']) ? intval($_POST['step_id']) : 2;
-$upload_dir = $_SERVER['DOCUMENT_ROOT'] . "/uploads/manager_consultations/" . $manager_id . "/files/";
-
-// 2. 파일 업로드 처리
-if (!file_exists($upload_dir)) mkdir($upload_dir, 0777, true);
-
-// 업로드된 파일 확인
-$uploaded_files = [];
-$file_count = isset($_POST['file_count']) ? intval($_POST['file_count']) : 0;
-
-for ($i = 0; $i < $file_count; $i++) {
- $file_key = 'file_' . $i;
-
- if (!isset($_FILES[$file_key])) {
- continue;
- }
-
- $file = $_FILES[$file_key];
-
- // 파일 크기 확인 (50MB 제한)
- $max_file_size = 50 * 1024 * 1024;
- if ($file['size'] > $max_file_size) {
- echo json_encode([
- 'success' => false,
- 'message' => '파일 크기가 너무 큽니다. (최대 50MB): ' . $file['name']
- ]);
- exit;
- }
-
- // 파일명 생성
- $original_name = $file['name'];
- $file_extension = pathinfo($original_name, PATHINFO_EXTENSION);
- $file_name = date('Ymd_His') . "_" . uniqid() . "." . $file_extension;
- $file_path = $upload_dir . $file_name;
-
- if (!move_uploaded_file($file['tmp_name'], $file_path)) {
- echo json_encode(['success' => false, 'message' => '파일 저장 실패: ' . $original_name]);
- exit;
- }
-
- // GCS 업로드 (선택사항 - 큰 파일인 경우)
- $gcs_uri = null;
- $file_size = filesize($file_path);
- $max_local_size = 10 * 1024 * 1024; // 10MB
-
- if ($file_size > $max_local_size) {
- $gcs_config_file = $_SERVER['DOCUMENT_ROOT'] . '/apikey/gcs_config.txt';
- $bucket_name = null;
-
- if (file_exists($gcs_config_file)) {
- $gcs_config = parse_ini_file($gcs_config_file);
- $bucket_name = isset($gcs_config['bucket_name']) ? $gcs_config['bucket_name'] : null;
- }
-
- if ($bucket_name) {
- $googleServiceAccountFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/google_service_account.json';
- $gcs_object_name = 'manager_consultations/' . $manager_id . '/files/' . basename($file_path);
- $gcs_uri = uploadToGCS($file_path, $bucket_name, $gcs_object_name, $googleServiceAccountFile);
- }
- }
-
- $web_path = "/uploads/manager_consultations/" . $manager_id . "/files/" . $file_name;
- $file_path_to_store = $gcs_uri ? $gcs_uri : $web_path;
-
- $uploaded_files[] = [
- 'original_name' => $original_name,
- 'file_path' => $file_path_to_store,
- 'file_size' => $file_size,
- 'gcs_uri' => $gcs_uri
- ];
-}
-
-// 3. DB 저장
-try {
- $pdo = db_connect();
- if (!$pdo) {
- throw new Exception('데이터베이스 연결 실패');
- }
-
- $expiry_date = date('Y-m-d H:i:s', strtotime('+30 days'));
- $file_paths_json = json_encode($uploaded_files);
-
- // manager_consultation_files 테이블에 저장
- $sql = "INSERT INTO manager_consultation_files
- (manager_id, step_id, file_paths_json, file_expiry_date, created_at)
- VALUES (?, ?, ?, ?, NOW())";
- $stmt = $pdo->prepare($sql);
-
- if (!$stmt) {
- throw new Exception('SQL 준비 실패');
- }
-
- $executeResult = $stmt->execute([
- $manager_id,
- $step_id,
- $file_paths_json,
- $expiry_date
- ]);
-
- if (!$executeResult) {
- throw new Exception('SQL 실행 실패');
- }
-
- $insertId = $pdo->lastInsertId();
-
- echo json_encode([
- 'success' => true,
- 'message' => count($uploaded_files) . '개 파일이 업로드되었습니다.',
- 'id' => $insertId,
- 'files' => $uploaded_files,
- 'file_count' => count($uploaded_files)
- ]);
-
-} catch (Exception $e) {
- error_log('DB 저장 오류: ' . $e->getMessage());
- echo json_encode([
- 'success' => false,
- 'message' => '데이터베이스 저장 실패: ' . $e->getMessage()
- ]);
-}
-?>
-
diff --git a/sales_scenario/api_handler.php b/sales_scenario/api_handler.php
deleted file mode 100644
index 634b7ef3..00000000
--- a/sales_scenario/api_handler.php
+++ /dev/null
@@ -1,67 +0,0 @@
-prepare("SELECT step_id, checkpoint_index FROM sales_scenario_checklist WHERE tenant_id = :tenant_id AND is_checked = 1");
- $stmt->execute([':tenant_id' => $tenantId]);
- $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- $data = [];
- foreach ($rows as $row) {
- if (!isset($data[$row['step_id']])) {
- $data[$row['step_id']] = [];
- }
- $data[$row['step_id']][] = (int)$row['checkpoint_index'];
- }
-
- echo json_encode(['success' => true, 'data' => $data]);
- } catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => $e->getMessage()]);
- }
-}
-elseif ($method === 'POST') {
- $input = json_decode(file_get_contents('php://input'), true);
-
- if (!$input) {
- echo json_encode(['success' => false, 'message' => 'Invalid input']);
- exit;
- }
-
- $tenantId = $input['tenant_id'] ?? 'default_tenant';
- $stepId = $input['step_id'];
- $checkpointIndex = $input['checkpoint_index'];
- $isChecked = $input['is_checked'] ? 1 : 0;
-
- try {
- if ($isChecked) {
- // Insert or ignore (if already exists)
- $stmt = $pdo->prepare("INSERT IGNORE INTO sales_scenario_checklist (tenant_id, step_id, checkpoint_index, is_checked) VALUES (:tenant_id, :step_id, :idx, 1)");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
- } else {
- // Delete record if unchecked
- $stmt = $pdo->prepare("DELETE FROM sales_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND checkpoint_index = :idx");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId, ':idx' => $checkpointIndex]);
- }
-
- // Return updated list for this step
- $stmt = $pdo->prepare("SELECT checkpoint_index FROM sales_scenario_checklist WHERE tenant_id = :tenant_id AND step_id = :step_id AND is_checked = 1");
- $stmt->execute([':tenant_id' => $tenantId, ':step_id' => $stepId]);
- $checkedIndices = $stmt->fetchAll(PDO::FETCH_COLUMN);
-
- echo json_encode(['success' => true, 'data' => array_map('intval', $checkedIndices)]);
- } catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => $e->getMessage()]);
- }
-}
-?>
diff --git a/sales_scenario/index.php b/sales_scenario/index.php
deleted file mode 100644
index 67f7a853..00000000
--- a/sales_scenario/index.php
+++ /dev/null
@@ -1,752 +0,0 @@
-
-
-
-
-
- SAM 영업 시나리오 - CodeBridgeExy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sales_scenario/setup_db.php b/sales_scenario/setup_db.php
deleted file mode 100644
index 9f4444bf..00000000
--- a/sales_scenario/setup_db.php
+++ /dev/null
@@ -1,20 +0,0 @@
-exec($sql);
- echo "Table 'sales_scenario_checklist' created successfully.";
-} catch (PDOException $e) {
- echo "Error creating table: " . $e->getMessage();
-}
-?>
diff --git a/salesmanagement/api/company_info.php b/salesmanagement/api/company_info.php
deleted file mode 100644
index c9d89f25..00000000
--- a/salesmanagement/api/company_info.php
+++ /dev/null
@@ -1,550 +0,0 @@
- [
- 'sales_records' => [
- [
- "id" => "sale_001",
- "customer_name" => "대박 식당",
- "program_id" => "prog_pro",
- "contract_date" => "2024-10-15",
- "duration_months" => 84,
- "join_fee" => 2000000,
- "subscription_fee" => 100000,
- "total_amount" => 2000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-10-15",
- "join_fee" => "2024-10-16",
- "service_start" => "2024-11-01",
- "subscription_fee" => "2024-11-25",
- "product_modified" => "2024-10-15"
- ],
- "history" => [
- [
- "date" => "2024-10-15",
- "type" => "New Contract",
- "program_id" => "prog_pro",
- "description" => "Initial contract signed (Pro Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_002",
- "customer_name" => "강남 카페",
- "program_id" => "prog_premium",
- "contract_date" => "2024-11-05",
- "duration_months" => 84,
- "join_fee" => 3000000,
- "subscription_fee" => 150000,
- "total_amount" => 3000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-11-05",
- "join_fee" => "2024-11-06",
- "service_start" => "2024-12-01",
- "subscription_fee" => "2024-12-25",
- "product_modified" => "2024-11-05"
- ],
- "history" => [
- [
- "date" => "2024-11-05",
- "type" => "New Contract",
- "program_id" => "prog_premium",
- "description" => "Initial contract signed (Premium Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_003",
- "customer_name" => "성수 팩토리",
- "program_id" => "prog_basic",
- "contract_date" => "2024-11-20",
- "duration_months" => 84,
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "total_amount" => 1000000,
- "status" => "Pending",
- "dates" => [
- "contract" => "2024-11-20",
- "join_fee" => null,
- "service_start" => null,
- "subscription_fee" => null,
- "product_modified" => "2024-11-20"
- ],
- "history" => [
- [
- "date" => "2024-11-20",
- "type" => "New Contract",
- "program_id" => "prog_basic",
- "description" => "Contract drafted (Basic Plan)"
- ]
- ]
- ]
- ],
- 'current_user' => [
- "id" => "user_A",
- "name" => "김관리 팀장",
- "role" => "1차영업담당",
- "sub_managers" => [
- [
- "id" => "user_B",
- "name" => "이하위 대리",
- "role" => "Sub-Manager",
- "total_sales" => 50000000,
- "active_contracts" => 5,
- "performance_grade" => "A"
- ],
- [
- "id" => "user_C",
- "name" => "박신입 사원",
- "role" => "Seller",
- "total_sales" => 12000000,
- "active_contracts" => 2,
- "performance_grade" => "B"
- ],
- [
- "id" => "user_D",
- "name" => "최열정 인턴",
- "role" => "Seller",
- "total_sales" => 0,
- "active_contracts" => 0,
- "performance_grade" => "C"
- ]
- ]
- ]
- ],
- '2차영업담당' => [
- 'sales_records' => [
- [
- "id" => "sale_201",
- "customer_name" => "서초 IT센터",
- "program_id" => "prog_pro",
- "contract_date" => "2024-09-10",
- "duration_months" => 84,
- "join_fee" => 2000000,
- "subscription_fee" => 100000,
- "total_amount" => 2000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-09-10",
- "join_fee" => "2024-09-11",
- "service_start" => "2024-10-01",
- "subscription_fee" => "2024-10-25",
- "product_modified" => "2024-09-10"
- ],
- "history" => [
- [
- "date" => "2024-09-10",
- "type" => "New Contract",
- "program_id" => "prog_pro",
- "description" => "Initial contract signed (Pro Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_202",
- "customer_name" => "홍대 레스토랑",
- "program_id" => "prog_basic",
- "contract_date" => "2024-10-22",
- "duration_months" => 84,
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "total_amount" => 1000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-10-22",
- "join_fee" => "2024-10-23",
- "service_start" => "2024-11-01",
- "subscription_fee" => "2024-11-25",
- "product_modified" => "2024-10-22"
- ],
- "history" => [
- [
- "date" => "2024-10-22",
- "type" => "New Contract",
- "program_id" => "prog_basic",
- "description" => "Initial contract signed (Basic Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_203",
- "customer_name" => "판교 스타트업",
- "program_id" => "prog_premium",
- "contract_date" => "2024-11-18",
- "duration_months" => 84,
- "join_fee" => 3000000,
- "subscription_fee" => 150000,
- "total_amount" => 3000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-11-18",
- "join_fee" => "2024-11-19",
- "service_start" => "2024-12-01",
- "subscription_fee" => "2024-12-25",
- "product_modified" => "2024-11-18"
- ],
- "history" => [
- [
- "date" => "2024-11-18",
- "type" => "New Contract",
- "program_id" => "prog_premium",
- "description" => "Initial contract signed (Premium Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_204",
- "customer_name" => "이태원 바",
- "program_id" => "prog_basic",
- "contract_date" => "2024-11-25",
- "duration_months" => 84,
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "total_amount" => 1000000,
- "status" => "Pending",
- "dates" => [
- "contract" => "2024-11-25",
- "join_fee" => null,
- "service_start" => null,
- "subscription_fee" => null,
- "product_modified" => "2024-11-25"
- ],
- "history" => [
- [
- "date" => "2024-11-25",
- "type" => "New Contract",
- "program_id" => "prog_basic",
- "description" => "Contract drafted (Basic Plan)"
- ]
- ]
- ]
- ],
- 'current_user' => [
- "id" => "user_2A",
- "name" => "정영업 과장",
- "role" => "2차영업담당",
- "sub_managers" => [
- [
- "id" => "user_2B",
- "name" => "한성실 대리",
- "role" => "Sub-Manager",
- "total_sales" => 35000000,
- "active_contracts" => 4,
- "performance_grade" => "A"
- ],
- [
- "id" => "user_2C",
- "name" => "윤열심 주임",
- "role" => "Seller",
- "total_sales" => 18000000,
- "active_contracts" => 3,
- "performance_grade" => "B"
- ],
- [
- "id" => "user_2D",
- "name" => "강신규 사원",
- "role" => "Seller",
- "total_sales" => 5000000,
- "active_contracts" => 1,
- "performance_grade" => "C"
- ]
- ]
- ]
- ],
- '3차영업담당' => [
- 'sales_records' => [
- [
- "id" => "sale_301",
- "customer_name" => "잠실 마트",
- "program_id" => "prog_basic",
- "contract_date" => "2024-08-15",
- "duration_months" => 84,
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "total_amount" => 1000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-08-15",
- "join_fee" => "2024-08-16",
- "service_start" => "2024-09-01",
- "subscription_fee" => "2024-09-25",
- "product_modified" => "2024-08-15"
- ],
- "history" => [
- [
- "date" => "2024-08-15",
- "type" => "New Contract",
- "program_id" => "prog_basic",
- "description" => "Initial contract signed (Basic Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_302",
- "customer_name" => "송파 병원",
- "program_id" => "prog_pro",
- "contract_date" => "2024-10-08",
- "duration_months" => 84,
- "join_fee" => 2000000,
- "subscription_fee" => 100000,
- "total_amount" => 2000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-10-08",
- "join_fee" => "2024-10-09",
- "service_start" => "2024-11-01",
- "subscription_fee" => "2024-11-25",
- "product_modified" => "2024-10-08"
- ],
- "history" => [
- [
- "date" => "2024-10-08",
- "type" => "New Contract",
- "program_id" => "prog_pro",
- "description" => "Initial contract signed (Pro Plan)"
- ]
- ]
- ],
- [
- "id" => "sale_303",
- "customer_name" => "강동 학원",
- "program_id" => "prog_basic",
- "contract_date" => "2024-11-12",
- "duration_months" => 84,
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "total_amount" => 1000000,
- "status" => "Active",
- "dates" => [
- "contract" => "2024-11-12",
- "join_fee" => "2024-11-13",
- "service_start" => "2024-12-01",
- "subscription_fee" => "2024-12-25",
- "product_modified" => "2024-11-12"
- ],
- "history" => [
- [
- "date" => "2024-11-12",
- "type" => "New Contract",
- "program_id" => "prog_basic",
- "description" => "Initial contract signed (Basic Plan)"
- ]
- ]
- ]
- ],
- 'current_user' => [
- "id" => "user_3A",
- "name" => "오영업 대리",
- "role" => "3차영업담당",
- "sub_managers" => [
- [
- "id" => "user_3B",
- "name" => "신성실 주임",
- "role" => "Sub-Manager",
- "total_sales" => 25000000,
- "active_contracts" => 3,
- "performance_grade" => "A"
- ],
- [
- "id" => "user_3C",
- "name" => "조근면 사원",
- "role" => "Seller",
- "total_sales" => 8000000,
- "active_contracts" => 2,
- "performance_grade" => "B"
- ],
- [
- "id" => "user_3D",
- "name" => "임신입 인턴",
- "role" => "Seller",
- "total_sales" => 2000000,
- "active_contracts" => 1,
- "performance_grade" => "C"
- ]
- ]
- ]
- ]
-];
-
-// 기본 데이터 구조
-$response = [
- "company_info" => [
- "id" => "comp_001",
- "name" => "건축자재(주)",
- "logo_url" => "https://via.placeholder.com/150x50?text=Company+Logo", // Placeholder
- "currency" => "KRW"
- ],
- "sales_config" => [
- "programs" => [
- [
- "id" => "prog_basic",
- "name" => "Basic Plan",
- "join_fee" => 1000000,
- "subscription_fee" => 50000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "prog_pro",
- "name" => "Pro Plan",
- "join_fee" => 2000000,
- "subscription_fee" => 100000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "prog_premium",
- "name" => "Premium Plan",
- "join_fee" => 3000000,
- "subscription_fee" => 150000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ]
- ],
- "default_contract_period" => 84,
- "package_types" => [
- [
- "id" => "select_models",
- "name" => "선택모델",
- "type" => "checkbox",
- "models" => [
- [
- "id" => "model_qr",
- "name" => "QR코드",
- "sub_name" => "설비관리/장비 점검",
- "join_fee" => 10200000,
- "subscription_fee" => 50000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_photo",
- "name" => "사진 - 출하",
- "sub_name" => "사진 관리",
- "join_fee" => 19200000,
- "subscription_fee" => 100000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_inspection",
- "name" => "검사 / 토큰 적용",
- "sub_name" => "계산서 발행",
- "join_fee" => 10200000,
- "subscription_fee" => 50000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_account",
- "name" => "이카운트 / 거래처대장",
- "sub_name" => "서류관리",
- "join_fee" => 19200000,
- "subscription_fee" => 100000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_iso",
- "name" => "ISO, 인정서류 / 토큰 적용",
- "sub_name" => "서류관리",
- "join_fee" => 10200000,
- "subscription_fee" => 50000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_inventory",
- "name" => "적정 재고 표기",
- "sub_name" => "재고 관리",
- "join_fee" => 19200000,
- "subscription_fee" => 100000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "model_manufacturing",
- "name" => "제조 관리",
- "sub_name" => "제작지시, 발주",
- "join_fee" => 19200000,
- "subscription_fee" => 100000,
- "commission_rates" => [
- "seller" => ["join" => 0.2, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ]
- ]
- ],
- [
- "id" => "construction_management",
- "name" => "공사관리",
- "type" => "package",
- "join_fee" => 40000000,
- "subscription_fee" => 200000,
- "commission_rates" => [
- "seller" => ["join" => 0.25, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ],
- [
- "id" => "process_government",
- "name" => "공정/정부지원사업",
- "type" => "package",
- "join_fee" => 80000000,
- "subscription_fee" => 400000,
- "commission_rates" => [
- "seller" => ["join" => 0.25, "sub" => 0.5],
- "manager" => ["join" => 0.05, "sub" => 0.3],
- "educator" => ["join" => 0.03, "sub" => 0.2]
- ]
- ]
- ]
- ],
- "sales_records" => isset($roleData[$role]['sales_records']) ? $roleData[$role]['sales_records'] : [],
- "current_user" => isset($roleData[$role]['current_user']) ? $roleData[$role]['current_user'] : [
- "id" => "user_default",
- "name" => "기본 사용자",
- "role" => $role,
- "sub_managers" => []
- ]
-];
-
-echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
-?>
diff --git a/salesmanagement/api/package_pricing.php b/salesmanagement/api/package_pricing.php
deleted file mode 100644
index 95a1c679..00000000
--- a/salesmanagement/api/package_pricing.php
+++ /dev/null
@@ -1,162 +0,0 @@
-prepare("SELECT * FROM package_pricing WHERE is_active = 1 ORDER BY item_type, item_id");
- $stmt->execute();
- $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
- // JSON 필드 파싱
- foreach ($items as &$item) {
- if ($item['commission_rates']) {
- $item['commission_rates'] = json_decode($item['commission_rates'], true);
- }
- $item['join_fee'] = floatval($item['join_fee']);
- $item['subscription_fee'] = floatval($item['subscription_fee']);
- $item['total_amount'] = $item['total_amount'] ? floatval($item['total_amount']) : null;
- $item['allow_flexible_pricing'] = (bool)$item['allow_flexible_pricing'];
- }
-
- echo json_encode(['success' => true, 'data' => $items], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
- } elseif ($action === 'get') {
- // 단일 항목 조회
- $item_type = $_GET['item_type'] ?? '';
- $item_id = $_GET['item_id'] ?? '';
-
- if (!$item_type || !$item_id) {
- throw new Exception("item_type과 item_id가 필요합니다.");
- }
-
- $stmt = $pdo->prepare("SELECT * FROM package_pricing WHERE item_type = ? AND item_id = ? AND is_active = 1");
- $stmt->execute([$item_type, $item_id]);
- $item = $stmt->fetch(PDO::FETCH_ASSOC);
-
- if ($item) {
- if ($item['commission_rates']) {
- $item['commission_rates'] = json_decode($item['commission_rates'], true);
- }
- $item['join_fee'] = floatval($item['join_fee']);
- $item['subscription_fee'] = floatval($item['subscription_fee']);
- }
-
- echo json_encode(['success' => true, 'data' => $item], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
- } else {
- throw new Exception("잘못된 action입니다.");
- }
- break;
-
- case 'POST':
- // 새 항목 생성
- $data = json_decode(file_get_contents('php://input'), true);
-
- if (!isset($data['item_type']) || !isset($data['item_id']) || !isset($data['item_name'])) {
- throw new Exception("필수 필드가 누락되었습니다.");
- }
-
- $item_type = $data['item_type'];
- $item_id = $data['item_id'];
- $item_name = $data['item_name'];
- $sub_name = $data['sub_name'] ?? null;
- $join_fee = floatval($data['join_fee'] ?? 0);
- $subscription_fee = floatval($data['subscription_fee'] ?? 0);
- $commission_rates = isset($data['commission_rates']) ? json_encode($data['commission_rates'], JSON_UNESCAPED_UNICODE) : null;
-
- $stmt = $pdo->prepare("
- INSERT INTO package_pricing (item_type, item_id, item_name, sub_name, join_fee, subscription_fee, commission_rates)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- ");
- $stmt->execute([$item_type, $item_id, $item_name, $sub_name, $join_fee, $subscription_fee, $commission_rates]);
-
- echo json_encode(['success' => true, 'message' => '항목이 생성되었습니다.', 'id' => $pdo->lastInsertId()], JSON_UNESCAPED_UNICODE);
- break;
-
- case 'PUT':
- // 항목 수정
- $data = json_decode(file_get_contents('php://input'), true);
-
- if (!isset($data['item_type']) || !isset($data['item_id'])) {
- throw new Exception("item_type과 item_id가 필요합니다.");
- }
-
- $item_type = $data['item_type'];
- $item_id = $data['item_id'];
- $updates = [];
- $params = [];
-
- if (isset($data['join_fee'])) {
- $updates[] = "join_fee = ?";
- $params[] = floatval($data['join_fee']);
- }
- if (isset($data['subscription_fee'])) {
- $updates[] = "subscription_fee = ?";
- $params[] = floatval($data['subscription_fee']);
- }
- if (isset($data['total_amount'])) {
- $updates[] = "total_amount = ?";
- $params[] = $data['total_amount'] !== null ? floatval($data['total_amount']) : null;
- }
- if (isset($data['allow_flexible_pricing'])) {
- $updates[] = "allow_flexible_pricing = ?";
- $params[] = intval($data['allow_flexible_pricing']);
- }
- if (isset($data['commission_rates'])) {
- $updates[] = "commission_rates = ?";
- $params[] = json_encode($data['commission_rates'], JSON_UNESCAPED_UNICODE);
- }
- if (isset($data['item_name'])) {
- $updates[] = "item_name = ?";
- $params[] = $data['item_name'];
- }
- if (isset($data['sub_name'])) {
- $updates[] = "sub_name = ?";
- $params[] = $data['sub_name'];
- }
-
- if (empty($updates)) {
- throw new Exception("수정할 필드가 없습니다.");
- }
-
- $params[] = $item_type;
- $params[] = $item_id;
-
- $sql = "UPDATE package_pricing SET " . implode(", ", $updates) . " WHERE item_type = ? AND item_id = ?";
- $stmt = $pdo->prepare($sql);
- $stmt->execute($params);
-
- echo json_encode(['success' => true, 'message' => '항목이 수정되었습니다.'], JSON_UNESCAPED_UNICODE);
- break;
-
- case 'DELETE':
- // 항목 삭제 (soft delete)
- $item_type = $_GET['item_type'] ?? '';
- $item_id = $_GET['item_id'] ?? '';
-
- if (!$item_type || !$item_id) {
- throw new Exception("item_type과 item_id가 필요합니다.");
- }
-
- $stmt = $pdo->prepare("UPDATE package_pricing SET is_active = 0 WHERE item_type = ? AND item_id = ?");
- $stmt->execute([$item_type, $item_id]);
-
- echo json_encode(['success' => true, 'message' => '항목이 삭제되었습니다.'], JSON_UNESCAPED_UNICODE);
- break;
-
- default:
- throw new Exception("지원하지 않는 HTTP 메서드입니다.");
- }
-
-} catch (Exception $e) {
- http_response_code(400);
- echo json_encode(['success' => false, 'error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
-}
-
diff --git a/salesmanagement/index.php b/salesmanagement/index.php
deleted file mode 100644
index dd15a6dc..00000000
--- a/salesmanagement/index.php
+++ /dev/null
@@ -1,2813 +0,0 @@
-
-
-
-
-
- 영업 관리 시스템 - CodeBridgeExy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/salesmanagement/sales_commission_ui_plan.md b/salesmanagement/sales_commission_ui_plan.md
deleted file mode 100644
index 3beb13fa..00000000
--- a/salesmanagement/sales_commission_ui_plan.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# CodeBridgeExy 영업 수당 체계 UI 계획서
-
-## 1. 프로젝트 개요
-**프로젝트명**: CodeBridgeExy 영업 수당 관리 및 시뮬레이션 시스템
-**목적**: 엑셀로 관리되던 영업 수당 체계(`sample.xlsx`)를 웹 기반 UI로 전환하여, 영업 사원 및 관리자가 수당을 쉽게 계산하고 시뮬레이션하며, 실적을 관리할 수 있도록 함.
-
-## 2. 주요 기능 및 UI 구성
-
-### 2.1 대시보드 (Dashboard)
-* **개요**: 전체 영업 현황 및 수당 지급 현황을 한눈에 파악.
-* **주요 지표 (KPI)**:
- * 총 매출액 (Total Sales)
- * 총 지급 수당 (Total Commission Paid)
- * 이번 달 예상 수당 (Estimated Commission)
- * 프로그램별 판매 비중 (Pie Chart)
-* **디자인 컨셉**: Glassmorphism 카드 디자인, 다크/라이트 모드 지원, 동적 그래프 (Chart.js or Recharts).
-
-### 2.2 수당 시뮬레이터 (Commission Simulator)
-* **기능**: 프로그램 유형과 조건을 입력하면 예상 수당을 자동으로 계산하여 보여줌.
-* **입력 항목**:
- * 프로그램 선택 (QR코드, 사진 관리 등)
- * 계약 기간 (기본 7년/84개월)
- * 가입비 및 구독료 설정 (기본값 자동 로드, 수정 가능)
-* **출력 항목 (실시간 계산)**:
- * **판매자 수당 (Seller)**: 가입비의 20% + 구독료의 50%
- * **영업 관리자 수당 (Manager)**: 가입비의 5% + 구독료의 30%
- * **교육 지원자 수당 (Educator)**: 가입비의 3% + 구독료의 20%
- * **회사 마진 (Company Margin)**: 가입비의 70% 등
-* **UI 구성**: 좌측 입력 폼, 우측 결과 카드 (영수증 형태 또는 카드 형태).
-
-### 2.3 프로그램 및 수당 기준 관리 (Admin)
-* **기능**: `sample.xlsx`의 기준 데이터를 관리 (CRUD).
-* **데이터 테이블**:
- * 프로그램명
- * 단가
- * 구독료
- * 수당 배분율 (판매자, 관리자, 교육자)
-* **UI 구성**: 정렬 및 필터링이 가능한 데이터 그리드.
-
-## 3. 데이터 모델 (Data Model)
-`sample.xlsx` 분석 기반:
-
-| 필드명 | 설명 | 예시 데이터 |
-| :--- | :--- | :--- |
-| `program_type` | 프로그램 타입 | QR코드, 사진 관리 |
-| `unit_price` | 프로그램 단가 | 10,400,000 |
-| `subscription_fee` | 월 구독료 | 100,000 |
-| `duration_months` | 계약 기간 | 84 |
-| `join_fee` | 가입비 | 2,000,000 |
-| `commission_seller_join_rate` | 판매자 가입비 수당율 | 20% |
-| `commission_seller_sub_rate` | 판매자 구독료 수당율 | 50% |
-| `commission_manager_join_rate` | 관리자 가입비 수당율 | 5% |
-| `commission_manager_sub_rate` | 관리자 구독료 수당율 | 30% |
-
-## 4. 기술 스택 (제안)
-* **Frontend**: React (Next.js)
-* **Styling**: Tailwind CSS (Premium Design, Responsive)
-* **State Management**: Zustand or Context API
-* **Charts**: Recharts or Chart.js
-* **Icons**: Lucide React or Heroicons
-
-## 5. 디자인 가이드 (Aesthetics)
-* **Color Palette**: 신뢰감을 주는 Deep Blue & Purple Gradients.
-* **Typography**: Pretendard or Inter (가독성 최우선).
-* **Interaction**: 버튼 호버 효과, 모달 등장 애니메이션, 수치 카운트업 효과.
-
-## 6. 개발 단계
-1. **기획 및 디자인**: 본 계획서 확정 및 와이어프레임 작성.
-2. **프론트엔드 개발**: 컴포넌트 개발 및 시뮬레이션 로직 구현.
-3. **데이터 연동**: (백엔드 필요 시) API 연동 또는 로컬 목업 데이터 사용.
-4. **배포 및 테스트**.
diff --git a/salesmanagement/sales_management_api_plan.md b/salesmanagement/sales_management_api_plan.md
deleted file mode 100644
index f3a69d3e..00000000
--- a/salesmanagement/sales_management_api_plan.md
+++ /dev/null
@@ -1,41 +0,0 @@
-### 2.2 엔드포인트
-* **URL**: `/api/v1/company/sales-config` (예시)
-* **Method**: `GET`
-* **Headers**:
- * `X-Tenant-ID`: `{company_id}` (또는 세션/토큰 기반 인증)
-
-### 2.3 응답 데이터 구조 (JSON)
-```json
-{
- "company_info": {
- "id": "comp_12345",
- "name": "건축자재(주)",
-3. **Commission Simulator**:
- * 프로그램 선택 Dropdown (API에서 로드된 프로그램 목록).
- * 실시간 수당 계산기.
-4. **Sales List & Detail Modal** (New):
- * **Sales List**: API에서 가져온 `sales_records`를 테이블 형태로 표시.
- * **Detail Modal**:
- * **기본 정보**: 5가지 주요 일자(계약일, 가입비일, 구독료일, 시작일, 수정일) 표시.
- * **수당 상세**: 현재 상품 기준 수당 계산.
- * **히스토리**: 상품 변경 이력(타임라인) 표시.
-5. **Sub-Manager Management** (New):
- * **Hierarchy View**: 내 하위 관리자/영업사원 목록 표시.
- * **Performance**: 하위 관리자의 실적(매출, 계약 건수) 요약 표시.
- * **Detail Modal**: 하위 관리자 클릭 시, 상세 실적 요약(매출, 등급 등)을 모달로 표시.
-
-### 3.3 디자인 적용 (`tone.md` 준수)
-* **Background**: `bg-gray-50` (rgb(250, 250, 250))
-* **Font**: `font-sans` (Pretendard)
-* **Primary Color**: `text-blue-600`, `bg-blue-600` (버튼 등)
-* **Cards**: `bg-white rounded-xl shadow-sm p-6`
-
-## 4. 개발 단계
-1. **API Mocking**: `api_mock.json` 또는 PHP 파일로 간단한 Mock API 구현.
-2. **UI Skeleton**: `index.php`에 HTML 구조 및 Tailwind CSS 로드.
-3. **Data Fetching**: `fetch()`를 사용하여 API에서 회사 정보 로드.
-4. **Component Rendering**: 로드된 데이터를 기반으로 UI 렌더링.
-
-## 5. User Review Required
-* **API 방식**: 별도의 백엔드 프레임워크 없이 PHP로 간단한 JSON 응답을 주는 API를 `api/company_info.php`와 같이 만들어서 테스트하시겠습니까?
-* **Frontend 환경**: `index.php` 파일 하나에서 작업하려면 React CDN 방식이 간편합니다. Node.js 기반의 Next.js 프로젝트로 완전히 분리하시겠습니까? (현재 폴더 구조상 `index.php` 수정을 요청하셨으므로 **PHP + JS** 방식을 추천합니다.)
diff --git a/salesmanagement/tone.md b/salesmanagement/tone.md
deleted file mode 100644
index 9cfd4238..00000000
--- a/salesmanagement/tone.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# CodeBridgeExy Design Tone & Manner
-
-## 1. Overview
-This document defines the visual style and design tokens extracted from the CodeBridgeExy Dashboard (`https://dev.codebridge-x.com/dashboard`). The design aims for a clean, modern, and professional look suitable for an enterprise ERP system.
-
-**Reference Screenshot**:
-
-
-## 2. Color Palette
-
-### Backgrounds
-* **Page Background**: `rgb(250, 250, 250)` (Light Gray / Off-White) - Creates a clean canvas.
-* **Card/Container Background**: `White` (`#ffffff`) or very light transparency - Used for content areas to separate them from the page background.
-
-### Text Colors
-* **Headings**: `rgb(255, 255, 255)` (White) - Primarily used on dark sidebars or headers.
-* **Body Text**: `oklch(0.551 0.027 264.364)` (Dark Slate Blue/Gray) - High contrast for readability on light backgrounds.
-* **Muted Text**: Gray-400/500 (Inferred) - For secondary labels.
-
-### Brand Colors
-* **Primary Blue**: (Inferred from "경영분석" button) - Likely a standard corporate blue (e.g., Tailwind `blue-600` or `indigo-600`). Used for primary actions and active states.
-
-## 3. Typography
-
-### Font Family
-* **Primary**: `Pretendard`
-* **Fallback**: `-apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif`
-
-### Weights
-* **Regular**: 400 (Body text)
-* **Medium/Bold**: 500/700 (Headings, Buttons)
-
-## 4. UI Components & Layout
-
-### Cards
-* **Border Radius**: `12px` (Rounded-lg/xl) - Soft, modern feel.
-* **Shadow**: Subtle shadows (e.g., `shadow-sm` or `shadow-md`) to lift content off the background.
-* **Padding**: Spacious internal padding to avoid clutter.
-
-### Buttons
-* **Shape**: Rounded corners (matching cards, likely `8px` or `12px`).
-* **Style**: Solid background for primary, outlined/ghost for secondary.
-
-### Layout Density
-* **Spacious**: The design uses whitespace effectively to separate sections, typical of modern dashboards.
-
-## 5. Implementation Guide (Tailwind CSS)
-
-To match this tone in the new Sales Commission System:
-
-```js
-// tailwind.config.js
-module.exports = {
- theme: {
- extend: {
- colors: {
- background: 'rgb(250, 250, 250)',
- foreground: 'oklch(0.551 0.027 264.364)', // Adjust to closest Hex
- primary: {
- DEFAULT: '#2563eb', // Placeholder for Brand Blue
- foreground: '#ffffff',
- },
- },
- fontFamily: {
- sans: ['Pretendard', 'sans-serif'],
- },
- borderRadius: {
- 'card': '12px',
- }
- },
- },
-}
-```
diff --git a/salesmanagement/수당지급체계.md b/salesmanagement/수당지급체계.md
deleted file mode 100644
index 6e397396..00000000
--- a/salesmanagement/수당지급체계.md
+++ /dev/null
@@ -1,584 +0,0 @@
-# 영업관리 수당 지급 체계
-
-## 개요
-이 문서는 영업관리 시스템의 수당 지급 체계를 정의합니다.
-수당은 **가입비**에 대해서만 지급되며, 구독료에 대한 수당은 지급되지 않습니다.
-
----
-
-## 1. 기본 원칙
-
-### 1.1 수당 지급 대상
-- **가입비만 수당 지급 대상**
-- 구독료는 수당 계산에서 제외
-
-### 1.2 계층 구조
-```
-내 조직 (영업관리자)
-├── 내 직접 판매 (20%)
-├── 1차 하위 (직속) (5%)
-│ └── 2차 하위 (손자) (3%)
-└── 1차 하위 (직속) (5%)
-```
-
-### 1.3 핵심 개념
-- **직접 판매**: 내가 직접 판매한 계약
-- **1차 하위 (관리자 수당)**: 내가 초대한 영업관리가 판매한 계약
-- **2차 하위 (교육자 수당)**: 내 하위의 하위가 판매한 계약
-
----
-
-## 2. 수당 지급 비율
-
-| 구분 | 지급 비율 | 설명 |
-|------|-----------|------|
-| **직접 판매** | 가입비의 **20%** | 본인이 직접 판매한 계약의 가입비 |
-| **관리자 수당** | 가입비의 **5%** | 직속 하위(1차)가 판매한 계약의 가입비 |
-| **교육자 수당** | 가입비의 **3%** | 2차 하위(손자)가 판매한 계약의 가입비 |
-
-**총 수당 = 직접 판매(20%) + 관리자 수당(5%) + 교육자 수당(3%)**
-
----
-
-## 3. 계층 구조 상세 설명
-
-### 3.1 역할 정의
-
-#### 판매자 (Seller)
-- 직접 영업을 성사시킨 담당자
-- 자신의 판매에 대해 **20%** 수당
-
-#### 관리자 (Manager)
-- 판매자를 데려온 상위 담당자
-- 하위 1단계의 판매에 대해 **5%** 수당
-
-#### 교육자 (Educator)
-- 관리자를 데려온 상위 담당자
-- 하위 2단계의 판매에 대해 **3%** 수당
-
-### 3.2 계층 예시
-
-```
-A (영업관리자)
-├── A의 직접 판매: 1,000만원
-├── B (1차 하위)
-│ └── B의 판매: 2,000만원
-└── C (1차 하위)
- ├── C의 판매: 1,500만원
- └── D (2차 하위)
- └── D의 판매: 3,000만원
-```
-
-**A가 받는 수당:**
-- A의 직접 판매: 1,000만원 × 20% = **200만원**
-- B의 판매 (관리자 수당): 2,000만원 × 5% = **100만원**
-- C의 판매 (관리자 수당): 1,500만원 × 5% = **75만원**
-- D의 판매 (교육자 수당): 3,000만원 × 3% = **90만원**
-- **A의 총 수당: 465만원**
-
----
-
-## 4. 수당 계산 로직
-
-### 4.1 내 총 수당 계산 공식
-
-```
-내 총 수당 = (내 직접 판매 × 20%)
- + (1차 하위 전체 판매 × 5%)
- + (2차 하위 전체 판매 × 3%)
-```
-
-### 4.2 JavaScript 계산 코드
-
-```javascript
-// 내 직접 판매 수당 (20%)
-const myDirectSales = findDirectSales(myOrg);
-const sellerCommission = myDirectSales * 0.20;
-
-// 1차 하위 관리자 수당 (5%)
-const level1Sales = calculateLevel1TotalSales(myOrg);
-const managerCommission = level1Sales * 0.05;
-
-// 2차 하위 교육자 수당 (3%)
-const level2Sales = calculateLevel2TotalSales(myOrg);
-const educatorCommission = level2Sales * 0.03;
-
-// 총 수당
-const totalCommission = sellerCommission + managerCommission + educatorCommission;
-```
-
----
-
-## 5. 지급 일정
-
-### 5.1 계약일 정의
-- **계약일**: 가입비 완료일을 기준으로 함
-
-### 5.2 수당 지급일
-- **가입비 수당**: 가입비 완료 후 지급
-- 구독료 수당은 없음 (구독료는 수당 대상 아님)
-
----
-
-## 6. 회사 마진
-
-### 6.1 마진 계산
-```
-회사 마진 = 가입비 - 총 수당
- = 가입비 - (20% + 5% + 3%)
- = 가입비 × 72%
-```
-
-### 6.2 최대 수당 비율
-- 3단계 전체가 채워진 경우 최대 **28%** 수당
-- 나머지 **72%**는 회사 마진
-
----
-
-## 7. 특수 상황 처리
-
-### 7.1 하위가 없는 경우
-- 1차 하위가 없으면: 관리자 수당(5%) 없음
-- 2차 하위가 없으면: 교육자 수당(3%) 없음
-- 본인의 직접 판매(20%)만 받음
-
-### 7.2 계약 취소/해지
-- 이미 지급된 가입비 수당은 회수하지 않음
-- 향후 정책 보완 예정
-
----
-
-## 8. 대시보드 표시 구조
-
-### 8.1 전체 누적 실적
-- **총 가입비**: 전체 기간 누적 가입비
-- **총 수당**: 전체 기간 누적 수당
-- **전체 건수**: 전체 계약 건수
-
-### 8.2 기간별 실적 (당월 기본)
-- **판매자 수당 (20%)**: 기간 내 직접 판매 수당
-- **관리자 수당 (5%)**: 기간 내 1차 하위 관리 수당
-- **교육자 수당 (3%)**: 기간 내 2차 하위 교육 수당
-- **총 수당**: 세 가지 수당의 합계
-
-### 8.3 기간 선택 옵션
-- **당월**: 현재 년월 (기본값)
-- **기간 설정**: 시작 년월 ~ 종료 년월
-
-### 8.4 기간별 필터링
-- 각 계약의 `contractDate` 기준으로 필터링
-- 선택된 기간 내 계약만 집계
-- 조직 트리도 동일 기간으로 필터링
-
----
-
-## 9. 조직 구조 표시
-
-### 9.1 계층별 색상
-- **Depth 0** (내 조직): 파란색
-- **Depth 1** (1차 하위): 초록색
-- **Depth 2** (2차 하위): 보라색
-- **직접 판매**: 노란색
-
-### 9.2 표시 정보
-각 노드마다 표시:
-- 이름 및 역할
-- 총 매출 (가입비)
-- 계약 건수
-- **내 수당**: 해당 노드의 판매로부터 내가 받는 수당
-
-### 9.3 직접 판매 항목
-- 오직 **"내 조직"**만 직접 판매 항목을 가짐
-- 1차, 2차 영업관리는 자신의 판매가 자동으로 집계됨
-- 노란색 배경, 쇼핑카트 아이콘으로 구분
-
----
-
-## 10. 데이터 구조
-
-### 10.1 조직 노드 구조
-```javascript
-{
- id: 'unique-id',
- name: '김철수',
- depth: 1, // 0: 내 조직, 1: 1차 하위, 2: 2차 하위
- role: '영업관리',
- isDirect: false, // 직접 판매 항목 여부
- totalSales: 50000000, // 총 매출 (가입비)
- contractCount: 15,
- commission: 2500000, // 내가 받는 수당
- contracts: [ // 계약 목록 (날짜 포함)
- { id: 'c1', contractDate: '2024-11-15', amount: 25000000 },
- { id: 'c2', contractDate: '2024-12-01', amount: 25000000 }
- ],
- children: [ /* 하위 노드 */ ]
-}
-```
-
-### 10.2 직접 판매 노드
-```javascript
-{
- id: 'root-direct',
- name: '내 직접 판매',
- depth: 0,
- role: '직접 판매',
- isDirect: true,
- totalSales: 30000000,
- contractCount: 3,
- commission: 6000000, // 3천만원 × 20%
- contracts: [
- { id: 'c1', contractDate: '2024-12-01', amount: 10000000 },
- { id: 'c2', contractDate: '2024-12-15', amount: 20000000 }
- ],
- children: []
-}
-```
-
-### 10.3 계약 데이터 구조
-```javascript
-{
- id: 'contract-123',
- contractDate: '2024-12-01', // YYYY-MM-DD 형식
- amount: 25000000 // 가입비
-}
-```
-
----
-
-## 11. 기간별 필터링 로직
-
-### 11.1 필터링 알고리즘
-```javascript
-filterNodeByDate(node, startDate, endDate) {
- // 1. 해당 노드의 계약 중 기간 내 계약만 필터링
- const filteredContracts = node.contracts.filter(contract => {
- const contractDate = new Date(contract.contractDate);
- return contractDate >= startDate && contractDate <= endDate;
- });
-
- // 2. 하위 노드도 재귀적으로 필터링
- const filteredChildren = node.children
- .map(child => filterNodeByDate(child, startDate, endDate))
- .filter(c => c !== null);
-
- // 3. 매출 재계산
- const ownSales = filteredContracts.reduce((sum, c) => sum + c.amount, 0);
- const childrenSales = filteredChildren.reduce((sum, c) => sum + c.totalSales, 0);
- const totalSales = ownSales + childrenSales;
-
- // 4. 데이터가 없으면 null 반환 (표시 안함)
- if (totalSales === 0 && filteredChildren.length === 0) {
- return null;
- }
-
- // 5. 수당 재계산
- const commission = calculateCommission(node, ownSales, childrenSales);
-
- return { ...node, totalSales, contractCount, commission, contracts: filteredContracts, children: filteredChildren };
-}
-```
-
-### 11.2 기간 옵션
-- **당월**: `new Date(year, month, 1)` ~ `new Date(year, month+1, 0)`
-- **커스텀**: 사용자가 선택한 시작일 ~ 종료일
-
----
-
-## 12. 운영자 화면 - 영업담당 관리
-
-### 12.1 개요
-운영자는 모든 영업담당의 실적과 수당을 역할별로 구분하여 확인할 수 있습니다.
-
-### 12.2 통계 카드 (6개)
-
-| 카드 | 설명 | 계산 방식 |
-|------|------|-----------|
-| **총 건수** | 전체 계약 건수 | 모든 영업담당의 계약 건수 합계 |
-| **이번달 건수** | 이번달 신규 계약 | 당월 계약 건수 합계 |
-| **총 가입비** | 전체 누적 가입비 | 모든 계약의 가입비 합계 |
-| **총 수당 지급** | 전체 누적 수당 | 모든 영업담당에게 지급한 수당 합계 |
-| **이번달 수당** | 이번달 지급 예정 | 당월 계약에 대한 수당 합계 |
-| **지난달 수당** | 지난달 지급 완료 | 전월 계약에 대한 수당 합계 |
-
-### 12.3 영업담당 개별 카드 구조
-
-각 영업담당 카드에 표시:
-```
-김철수 [아이콘]
-├── 총 건수: 15건
-├── 이번달 건수: 3건
-├── 총 가입비: ₩75,000,000
-├── └ 직접 판매 (20%): ₩6,000,000
-├── └ 관리자 (5%): ₩2,000,000
-├── └ 교육자 (3%): ₩900,000
-├── 총 수당: ₩8,900,000
-├── 이번달 수당: ₩1,200,000
-└── 지난달 수당: ₩1,500,000
-```
-
-### 12.4 역할별 계약 데이터 구조
-
-```javascript
-{
- id: 'contract-123',
- customer: '고객사 A',
- contractDate: '2024-12-01',
- amount: 25000000,
- role: 'direct' // 'direct', 'manager', 'educator'
-}
-```
-
-**역할 타입:**
-- `direct`: 직접 판매 (20% 수당)
-- `manager`: 1차 하위 관리 (5% 수당)
-- `educator`: 2차 하위 교육 (3% 수당)
-
-### 12.5 세부 내역 화면
-
-영업담당 클릭 시 표시:
-
-#### **역할별 수당 요약 (3개 카드)**
-
-| 직접 판매 (20%) | 관리자 수당 (5%) | 교육자 수당 (3%) |
-|-----------------|-----------------|-----------------|
-| 🟢 ₩6,042,670 | 🟣 ₩2,362,340 | 🟠 ₩914,256 |
-| ₩30,213,350 × 20% | ₩47,246,800 × 5% | ₩30,475,200 × 3% |
-
-#### **계약 목록 테이블**
-
-| 번호 | 고객사 | 계약일 | 역할 | 가입비 | 수당 |
-|------|--------|--------|------|--------|------|
-| 1 | 고객사 A | 2024-11-15 | 🟢 직접 판매 (20%) | ₩25,000,000 | ₩5,000,000 |
-| 2 | 고객사 B | 2024-12-01 | 🟣 관리자 (5%) | ₩30,000,000 | ₩1,500,000 |
-| 3 | 고객사 C | 2024-10-20 | 🟠 교육자 (3%) | ₩20,000,000 | ₩600,000 |
-| ... | ... | ... | ... | ... | ... |
-| **합계** | | | | **₩107,689,150** | **₩9,477,266** |
-
-### 12.6 역할별 색상 구분
-
-- 🟢 **초록색**: 직접 판매 (20%)
-- 🟣 **보라색**: 관리자 (5%)
-- 🟠 **주황색**: 교육자 (3%)
-
-### 12.7 계약 생성 로직
-
-```javascript
-// 각 영업담당마다 역할별 계약 생성
-const directContracts = generateContracts(2~6건); // 직접 판매
-const managerContracts = generateContracts(3~10건); // 1차 하위
-const educatorContracts = generateContracts(1~5건); // 2차 하위
-
-// 역할 구분
-directContracts.forEach(c => c.role = 'direct');
-managerContracts.forEach(c => c.role = 'manager');
-educatorContracts.forEach(c => c.role = 'educator');
-
-// 수당 계산
-const directCommission = directSales × 0.20;
-const managerCommission = managerSales × 0.05;
-const educatorCommission = educatorSales × 0.03;
-const totalCommission = directCommission + managerCommission + educatorCommission;
-```
-
----
-
-## 12. 운영자 화면 - 영업담당 관리
-
-### 12.1 개요
-운영자는 모든 영업담당의 실적과 수당을 역할별로 구분하여 확인할 수 있습니다.
-
-### 12.2 통계 카드 (6개)
-
-| 카드 | 설명 | 계산 방식 |
-|------|------|-----------|
-| **총 건수** | 전체 계약 건수 | 모든 영업담당의 계약 건수 합계 |
-| **이번달 건수** | 이번달 신규 계약 | 당월 계약 건수 합계 |
-| **총 가입비** | 전체 누적 가입비 | 모든 계약의 가입비 합계 |
-| **총 수당 지급** | 전체 누적 수당 | 모든 영업담당에게 지급한 수당 합계 |
-| **이번달 수당** | 이번달 지급 예정 | 당월 계약에 대한 수당 합계 |
-| **지난달 수당** | 지난달 지급 완료 | 전월 계약에 대한 수당 합계 |
-
-### 12.3 영업담당 개별 카드 구조
-
-각 영업담당 카드에 표시:
-```
-김철수 [아이콘]
-├── 총 건수: 15건
-├── 이번달 건수: 3건
-├── 총 가입비: ₩75,000,000
-├── └ 직접 판매 (20%): ₩6,000,000
-├── └ 관리자 (5%): ₩2,000,000
-├── └ 교육자 (3%): ₩900,000
-├── 총 수당: ₩8,900,000
-├── 이번달 수당: ₩1,200,000
-└── 지난달 수당: ₩1,500,000
-```
-
-### 12.4 역할별 계약 데이터 구조
-
-```javascript
-{
- id: 'contract-123',
- customer: '고객사 A',
- contractDate: '2024-12-01',
- amount: 25000000,
- role: 'direct' // 'direct', 'manager', 'educator'
-}
-```
-
-**역할 타입:**
-- `direct`: 직접 판매 (20% 수당)
-- `manager`: 1차 하위 관리 (5% 수당)
-- `educator`: 2차 하위 교육 (3% 수당)
-
-### 12.5 세부 내역 화면
-
-영업담당 클릭 시 표시:
-
-#### **역할별 수당 요약 (3개 카드)**
-
-| 직접 판매 (20%) | 관리자 수당 (5%) | 교육자 수당 (3%) |
-|-----------------|-----------------|-----------------|
-| 🟢 ₩6,042,670 | 🟣 ₩2,362,340 | 🟠 ₩914,256 |
-| ₩30,213,350 × 20% | ₩47,246,800 × 5% | ₩30,475,200 × 3% |
-
-#### **계약 목록 테이블**
-
-| 번호 | 고객사 | 계약일 | 역할 | 가입비 | 수당 |
-|------|--------|--------|------|--------|------|
-| 1 | 고객사 A | 2024-11-15 | 🟢 직접 판매 (20%) | ₩25,000,000 | ₩5,000,000 |
-| 2 | 고객사 B | 2024-12-01 | 🟣 관리자 (5%) | ₩30,000,000 | ₩1,500,000 |
-| 3 | 고객사 C | 2024-10-20 | 🟠 교육자 (3%) | ₩20,000,000 | ₩600,000 |
-| ... | ... | ... | ... | ... | ... |
-| **합계** | | | | **₩107,689,150** | **₩9,477,266** |
-
-### 12.6 역할별 색상 구분
-
-- 🟢 **초록색**: 직접 판매 (20%)
-- 🟣 **보라색**: 관리자 (5%)
-- 🟠 **주황색**: 교육자 (3%)
-
-### 12.7 계약 생성 로직
-
-```javascript
-// 각 영업담당마다 역할별 계약 생성
-const directContracts = generateContracts(2~6건); // 직접 판매
-const managerContracts = generateContracts(3~10건); // 1차 하위
-const educatorContracts = generateContracts(1~5건); // 2차 하위
-
-// 역할 구분
-directContracts.forEach(c => c.role = 'direct');
-managerContracts.forEach(c => c.role = 'manager');
-educatorContracts.forEach(c => c.role = 'educator');
-
-// 수당 계산
-const directCommission = directSales × 0.20;
-const managerCommission = managerSales × 0.05;
-const educatorCommission = educatorSales × 0.03;
-const totalCommission = directCommission + managerCommission + educatorCommission;
-```
-
----
-
-## 13. API 연동 (향후 구현)
-
-### 13.1 필요한 API 엔드포인트
-```
-GET /api/sales/organization?startDate=2024-12-01&endDate=2024-12-31
-GET /api/operator/managers?startDate=2024-12-01&endDate=2024-12-31
-```
-
-### 13.2 응답 데이터 구조
-위의 "10. 데이터 구조"와 동일한 형식으로 반환
-
----
-
-## 14. 개발 노트
-
-### 14.1 구현 완료
-- ✅ 가입비 기반 수당 계산
-- ✅ 3단계 계층 구조 (직접/1차/2차)
-- ✅ 계약 날짜 기반 기간 필터링
-- ✅ 대시보드 통계 연동
-- ✅ 역할별 수당 상세 표시
-- ✅ 계약 날짜 데이터 구조 추가
-- ✅ 운영자 화면 영업담당 관리
-- ✅ 역할별 계약 구분 (직접/관리자/교육자)
-- ✅ 영업담당 세부 내역 역할별 표시
-- ✅ 모달창 계약 상세 내역 및 날짜 표시
-
-### 14.2 향후 개선 사항
-- [ ] 실제 DB 연동
-- [ ] 계약 취소/해지 처리 로직
-- [ ] 수당 지급 이력 관리
-- [ ] 월별 수당 지급 스케줄
-- [ ] 세금 공제 계산
-- [ ] 운영자 화면 실시간 데이터 연동
-- [ ] 영업담당별 성과 분석 리포트
-
-### 14.3 주의사항
-- 현재는 샘플 랜덤 데이터 사용 (최근 12개월 내 랜덤 날짜)
-- 실제 구현 시 DB의 계약 데이터 기반으로 변경 필요
-- PHP 7.3 호환성 유지
-- 운영자 화면은 각 영업담당의 역할별 계약을 모두 표시
-- 각 영업담당은 직접 판매, 관리자, 교육자 역할을 동시에 수행 가능
-
----
-
-## 15. 변경 이력
-
-| 날짜 | 버전 | 변경 내용 |
-|------|------|-----------|
-| 2024-12-02 | 1.0 | 초기 문서 작성 - 가입비 기반 수당 체계 정의 |
-| 2024-12-02 | 1.1 | 계약 날짜 구조 추가 - 기간별 필터링 로직 문서화 |
-| 2024-12-02 | 1.2 | 운영자 화면 추가 - 역할별 계약 구분 및 수당 관리 상세화 |
-
----
-
-## 16. 운영자 화면 계산 예시
-
-### 16.1 영업담당 "김철수"의 데이터
-
-**역할별 계약:**
-- 직접 판매: 3건, 총 ₩30,000,000
-- 관리자 역할: 8건, 총 ₩100,000,000 (1차 하위의 판매)
-- 교육자 역할: 5건, 총 ₩50,000,000 (2차 하위의 판매)
-
-**수당 계산:**
-```
-직접 판매 수당 = ₩30,000,000 × 20% = ₩6,000,000
-관리자 수당 = ₩100,000,000 × 5% = ₩5,000,000
-교육자 수당 = ₩50,000,000 × 3% = ₩1,500,000
-───────────────────────────────────────────────
-총 수당 = ₩12,500,000
-```
-
-**카드 표시:**
-```
-김철수
-├── 총 건수: 16건
-├── 이번달 건수: 3건
-├── 총 가입비: ₩180,000,000
-├── └ 직접 판매 (20%): ₩6,000,000
-├── └ 관리자 (5%): ₩5,000,000
-├── └ 교육자 (3%): ₩1,500,000
-└── 총 수당: ₩12,500,000
-```
-
-### 16.2 세부 내역 테이블 예시
-
-| 번호 | 고객사 | 계약일 | 역할 | 가입비 | 수당 |
-|------|--------|--------|------|--------|------|
-| 1 | 고객사 A | 2024-11-15 | 직접 판매 (20%) | ₩10,000,000 | ₩2,000,000 |
-| 2 | 고객사 B | 2024-12-01 | 직접 판매 (20%) | ₩20,000,000 | ₩4,000,000 |
-| 3 | 고객사 C | 2024-10-20 | 관리자 (5%) | ₩25,000,000 | ₩1,250,000 |
-| 4 | 고객사 D | 2024-11-03 | 관리자 (5%) | ₩35,000,000 | ₩1,750,000 |
-| 5 | 고객사 E | 2024-09-15 | 교육자 (3%) | ₩20,000,000 | ₩600,000 |
-| 6 | 고객사 F | 2024-12-10 | 교육자 (3%) | ₩30,000,000 | ₩900,000 |
-| **합계** | | | | **₩180,000,000** | **₩12,500,000** |
-
----
-
-## 문의 및 개선 제안
-개발 과정에서 수당 체계 관련 질문이나 개선 제안 사항이 있으면 이 문서를 업데이트하여 관리합니다.
-
diff --git a/salesmangement/api/company_info.php b/salesmangement/api/company_info.php
deleted file mode 100644
index a500e0fc..00000000
--- a/salesmangement/api/company_info.php
+++ /dev/null
@@ -1,47 +0,0 @@
- [
- "id" => "comp_001",
- "name" => "건축자재(주)",
- "logo_url" => "https://via.placeholder.com/150x50?text=Company+Logo", // Placeholder
- "currency" => "KRW"
- ],
- "sales_config" => [
- "programs" => [
- [
- "id" => "prog_qr",
- "name" => "QR코드 (설비관리/장비점검)",
- "unit_price" => 10400000,
- "subscription_fee" => 100000,
- "duration_months" => 84,
- "join_fee" => 2000000,
- "commission_rates" => [
- "seller" => ["join" => 0.20, "sub" => 0.50],
- "manager" => ["join" => 0.05, "sub" => 0.30],
- "educator" => ["join" => 0.03, "sub" => 0.20]
- ]
- ],
- [
- "id" => "prog_photo",
- "name" => "사진관리 (출하/검사/토큰적용)",
- "unit_price" => 20800000,
- "subscription_fee" => 200000,
- "duration_months" => 84,
- "join_fee" => 4000000,
- "commission_rates" => [
- "seller" => ["join" => 0.20, "sub" => 0.50],
- "manager" => ["join" => 0.05, "sub" => 0.30],
- "educator" => ["join" => 0.03, "sub" => 0.20]
- ]
- ]
- ],
- "default_contract_period" => 84
- ]
-];
-
-echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
-?>
diff --git a/salesmangement/index.php b/salesmangement/index.php
deleted file mode 100644
index dec98561..00000000
--- a/salesmangement/index.php
+++ /dev/null
@@ -1,288 +0,0 @@
-
-
-
-
-
- 영업 관리 시스템 - CodeBridgeExy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/salesmangement/sales_commission_ui_plan.md b/salesmangement/sales_commission_ui_plan.md
deleted file mode 100644
index 3beb13fa..00000000
--- a/salesmangement/sales_commission_ui_plan.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# CodeBridgeExy 영업 수당 체계 UI 계획서
-
-## 1. 프로젝트 개요
-**프로젝트명**: CodeBridgeExy 영업 수당 관리 및 시뮬레이션 시스템
-**목적**: 엑셀로 관리되던 영업 수당 체계(`sample.xlsx`)를 웹 기반 UI로 전환하여, 영업 사원 및 관리자가 수당을 쉽게 계산하고 시뮬레이션하며, 실적을 관리할 수 있도록 함.
-
-## 2. 주요 기능 및 UI 구성
-
-### 2.1 대시보드 (Dashboard)
-* **개요**: 전체 영업 현황 및 수당 지급 현황을 한눈에 파악.
-* **주요 지표 (KPI)**:
- * 총 매출액 (Total Sales)
- * 총 지급 수당 (Total Commission Paid)
- * 이번 달 예상 수당 (Estimated Commission)
- * 프로그램별 판매 비중 (Pie Chart)
-* **디자인 컨셉**: Glassmorphism 카드 디자인, 다크/라이트 모드 지원, 동적 그래프 (Chart.js or Recharts).
-
-### 2.2 수당 시뮬레이터 (Commission Simulator)
-* **기능**: 프로그램 유형과 조건을 입력하면 예상 수당을 자동으로 계산하여 보여줌.
-* **입력 항목**:
- * 프로그램 선택 (QR코드, 사진 관리 등)
- * 계약 기간 (기본 7년/84개월)
- * 가입비 및 구독료 설정 (기본값 자동 로드, 수정 가능)
-* **출력 항목 (실시간 계산)**:
- * **판매자 수당 (Seller)**: 가입비의 20% + 구독료의 50%
- * **영업 관리자 수당 (Manager)**: 가입비의 5% + 구독료의 30%
- * **교육 지원자 수당 (Educator)**: 가입비의 3% + 구독료의 20%
- * **회사 마진 (Company Margin)**: 가입비의 70% 등
-* **UI 구성**: 좌측 입력 폼, 우측 결과 카드 (영수증 형태 또는 카드 형태).
-
-### 2.3 프로그램 및 수당 기준 관리 (Admin)
-* **기능**: `sample.xlsx`의 기준 데이터를 관리 (CRUD).
-* **데이터 테이블**:
- * 프로그램명
- * 단가
- * 구독료
- * 수당 배분율 (판매자, 관리자, 교육자)
-* **UI 구성**: 정렬 및 필터링이 가능한 데이터 그리드.
-
-## 3. 데이터 모델 (Data Model)
-`sample.xlsx` 분석 기반:
-
-| 필드명 | 설명 | 예시 데이터 |
-| :--- | :--- | :--- |
-| `program_type` | 프로그램 타입 | QR코드, 사진 관리 |
-| `unit_price` | 프로그램 단가 | 10,400,000 |
-| `subscription_fee` | 월 구독료 | 100,000 |
-| `duration_months` | 계약 기간 | 84 |
-| `join_fee` | 가입비 | 2,000,000 |
-| `commission_seller_join_rate` | 판매자 가입비 수당율 | 20% |
-| `commission_seller_sub_rate` | 판매자 구독료 수당율 | 50% |
-| `commission_manager_join_rate` | 관리자 가입비 수당율 | 5% |
-| `commission_manager_sub_rate` | 관리자 구독료 수당율 | 30% |
-
-## 4. 기술 스택 (제안)
-* **Frontend**: React (Next.js)
-* **Styling**: Tailwind CSS (Premium Design, Responsive)
-* **State Management**: Zustand or Context API
-* **Charts**: Recharts or Chart.js
-* **Icons**: Lucide React or Heroicons
-
-## 5. 디자인 가이드 (Aesthetics)
-* **Color Palette**: 신뢰감을 주는 Deep Blue & Purple Gradients.
-* **Typography**: Pretendard or Inter (가독성 최우선).
-* **Interaction**: 버튼 호버 효과, 모달 등장 애니메이션, 수치 카운트업 효과.
-
-## 6. 개발 단계
-1. **기획 및 디자인**: 본 계획서 확정 및 와이어프레임 작성.
-2. **프론트엔드 개발**: 컴포넌트 개발 및 시뮬레이션 로직 구현.
-3. **데이터 연동**: (백엔드 필요 시) API 연동 또는 로컬 목업 데이터 사용.
-4. **배포 및 테스트**.
diff --git a/salesmangement/sales_management_api_plan.md b/salesmangement/sales_management_api_plan.md
deleted file mode 100644
index 0a87c2e3..00000000
--- a/salesmangement/sales_management_api_plan.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Sales Management UI & API Integration Plan
-
-## 1. 개요
-**목표**: `salesmangement/index.php`에 영업 관리 UI를 구현하고, 멀티테넌시 환경을 지원하기 위해 회사별 기본 정보를 가져오는 API를 설계 및 연동함.
-**디자인**: `tone.md`에 정의된 CodeBridgeExy 디자인 시스템(Pretendard 폰트, Glassmorphism, Primary Blue 등)을 적용.
-
-## 2. API 설계 (Multi-tenancy Support)
-
-### 2.1 API 개요
-각 클라이언트(회사)의 고유 식별자(Tenant ID or Company ID)를 기반으로 해당 회사의 영업 설정 및 기본 정보를 반환하는 API.
-
-### 2.2 엔드포인트
-* **URL**: `/api/v1/company/sales-config` (예시)
-* **Method**: `GET`
-* **Headers**:
- * `X-Tenant-ID`: `{company_id}` (또는 세션/토큰 기반 인증)
-
-### 2.3 응답 데이터 구조 (JSON)
-```json
-{
- "company_info": {
- "id": "comp_12345",
- "name": "건축자재(주)",
- "logo_url": "https://...",
- "currency": "KRW"
- },
- "sales_config": {
- "programs": [
- {
- "id": "prog_qr",
- "name": "QR코드",
- "unit_price": 10400000,
- "subscription_fee": 100000,
- "commission_rates": {
- "seller": { "join": 0.2, "sub": 0.5 },
- "manager": { "join": 0.05, "sub": 0.3 }
- }
- },
- // ... 기타 프로그램
- ],
- "default_contract_period": 84 // months
- }
-}
-```
-
-## 3. UI 구현 계획 (`salesmangement/index.php`)
-
-### 3.1 기술 스택
-* **Frontend**: React (CDN 방식 또는 번들링), Tailwind CSS (CDN)
- * *Note*: `index.php` 환경이므로 React를 CDN으로 로드하거나, 빌드된 JS를 포함하는 방식을 사용.
- * **User Request**: Next.js 프로젝트 제안이 있었으나, 현재 파일이 `index.php`이므로 PHP 환경 내 리액트 통합 또는 순수 JS/HTML + Tailwind로 구현할지 결정 필요. (본 계획은 **React + Tailwind CDN**을 가정하여 빠른 프로토타이핑 지원)
-
-### 3.2 주요 컴포넌트
-1. **Header**: 회사 로고 및 회사명 표시 (API 데이터 연동).
-2. **Sales Dashboard**: `sales_commission_ui_plan.md`의 대시보드 구현.
- * KPI 카드 (총 매출, 수당 등) - `tone.md`의 Card 스타일 적용.
-3. **Commission Simulator**:
- * 프로그램 선택 Dropdown (API에서 로드된 프로그램 목록).
- * 실시간 수당 계산기.
-
-### 3.3 디자인 적용 (`tone.md` 준수)
-* **Background**: `bg-gray-50` (rgb(250, 250, 250))
-* **Font**: `font-sans` (Pretendard)
-* **Primary Color**: `text-blue-600`, `bg-blue-600` (버튼 등)
-* **Cards**: `bg-white rounded-xl shadow-sm p-6`
-
-## 4. 개발 단계
-1. **API Mocking**: `api_mock.json` 또는 PHP 파일로 간단한 Mock API 구현.
-2. **UI Skeleton**: `index.php`에 HTML 구조 및 Tailwind CSS 로드.
-3. **Data Fetching**: `fetch()`를 사용하여 API에서 회사 정보 로드.
-4. **Component Rendering**: 로드된 데이터를 기반으로 UI 렌더링.
-
-## 5. User Review Required
-* **API 방식**: 별도의 백엔드 프레임워크 없이 PHP로 간단한 JSON 응답을 주는 API를 `api/company_info.php`와 같이 만들어서 테스트하시겠습니까?
-* **Frontend 환경**: `index.php` 파일 하나에서 작업하려면 React CDN 방식이 간편합니다. Node.js 기반의 Next.js 프로젝트로 완전히 분리하시겠습니까? (현재 폴더 구조상 `index.php` 수정을 요청하셨으므로 **PHP + JS** 방식을 추천합니다.)
diff --git a/salesmangement/tone.md b/salesmangement/tone.md
deleted file mode 100644
index 9cfd4238..00000000
--- a/salesmangement/tone.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# CodeBridgeExy Design Tone & Manner
-
-## 1. Overview
-This document defines the visual style and design tokens extracted from the CodeBridgeExy Dashboard (`https://dev.codebridge-x.com/dashboard`). The design aims for a clean, modern, and professional look suitable for an enterprise ERP system.
-
-**Reference Screenshot**:
-
-
-## 2. Color Palette
-
-### Backgrounds
-* **Page Background**: `rgb(250, 250, 250)` (Light Gray / Off-White) - Creates a clean canvas.
-* **Card/Container Background**: `White` (`#ffffff`) or very light transparency - Used for content areas to separate them from the page background.
-
-### Text Colors
-* **Headings**: `rgb(255, 255, 255)` (White) - Primarily used on dark sidebars or headers.
-* **Body Text**: `oklch(0.551 0.027 264.364)` (Dark Slate Blue/Gray) - High contrast for readability on light backgrounds.
-* **Muted Text**: Gray-400/500 (Inferred) - For secondary labels.
-
-### Brand Colors
-* **Primary Blue**: (Inferred from "경영분석" button) - Likely a standard corporate blue (e.g., Tailwind `blue-600` or `indigo-600`). Used for primary actions and active states.
-
-## 3. Typography
-
-### Font Family
-* **Primary**: `Pretendard`
-* **Fallback**: `-apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif`
-
-### Weights
-* **Regular**: 400 (Body text)
-* **Medium/Bold**: 500/700 (Headings, Buttons)
-
-## 4. UI Components & Layout
-
-### Cards
-* **Border Radius**: `12px` (Rounded-lg/xl) - Soft, modern feel.
-* **Shadow**: Subtle shadows (e.g., `shadow-sm` or `shadow-md`) to lift content off the background.
-* **Padding**: Spacious internal padding to avoid clutter.
-
-### Buttons
-* **Shape**: Rounded corners (matching cards, likely `8px` or `12px`).
-* **Style**: Solid background for primary, outlined/ghost for secondary.
-
-### Layout Density
-* **Spacious**: The design uses whitespace effectively to separate sections, typical of modern dashboards.
-
-## 5. Implementation Guide (Tailwind CSS)
-
-To match this tone in the new Sales Commission System:
-
-```js
-// tailwind.config.js
-module.exports = {
- theme: {
- extend: {
- colors: {
- background: 'rgb(250, 250, 250)',
- foreground: 'oklch(0.551 0.027 264.364)', // Adjust to closest Hex
- primary: {
- DEFAULT: '#2563eb', // Placeholder for Brand Blue
- foreground: '#ffffff',
- },
- },
- fontFamily: {
- sans: ['Pretendard', 'sans-serif'],
- },
- borderRadius: {
- 'card': '12px',
- }
- },
- },
-}
-```