table_name} WHERE {$where_clause}"; $count_result = sql_fetch($count_sql); $total = $count_result['total']; // 페이징 계산 $offset = ($page - 1) * $per_page; $total_pages = ceil($total / $per_page); // 목록 조회 $sql = "SELECT * FROM {$this->table_name} WHERE {$where_clause} ORDER BY created_at DESC LIMIT {$offset}, {$per_page}"; $reservations = []; $result = sql_query($sql); while ($row = sql_fetch_array($result)) { $reservations[] = $row; } return [ 'success' => true, 'data' => [ 'reservations' => $reservations, 'pagination' => [ 'current_page' => $page, 'per_page' => $per_page, 'total' => $total, 'total_pages' => $total_pages ] ] ]; } catch (Exception $e) { consultant_log("예약 목록 조회 실패: " . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => '예약 목록 조회 중 오류가 발생했습니다.' ]; } } /** * 예약 생성 */ public function createReservation($data) { try { // 필수 필드 검증 $required_fields = ['customer_name', 'customer_phone', 'reservation_date', 'reservation_time']; foreach ($required_fields as $field) { if (empty($data[$field])) { throw new Exception("필수 필드가 누락되었습니다: {$field}"); } } // 중복 예약 확인 $check_sql = "SELECT COUNT(*) as count FROM {$this->table_name} WHERE reservation_date = '" . sql_real_escape_string($data['reservation_date']) . "' AND reservation_time = '" . sql_real_escape_string($data['reservation_time']) . "' AND status IN ('payment_pending', 'reserved') AND is_deleted = 0"; $check_result = sql_fetch($check_sql); $current_count = $check_result['count']; // 최대 인원 확인 (기본값 2명) $max_persons = consultant_get_config('default_max_persons', 2); if ($current_count >= $max_persons) { throw new Exception('해당 시간대는 예약이 마감되었습니다.'); } // 데이터 준비 $insert_data = [ 'customer_name' => sql_real_escape_string($data['customer_name']), 'customer_phone' => sql_real_escape_string($data['customer_phone']), 'customer_email' => sql_real_escape_string($data['customer_email'] ?? ''), 'reservation_date' => sql_real_escape_string($data['reservation_date']), 'reservation_time' => sql_real_escape_string($data['reservation_time']), 'consultation_type' => sql_real_escape_string($data['consultation_type'] ?? 'onsite'), 'status' => 'payment_pending', 'payment_amount' => (int) ($data['payment_amount'] ?? consultant_get_config('consultation_fee', 50000)), 'payment_status' => 'pending', 'request_memo' => sql_real_escape_string($data['request_memo'] ?? ''), 'wr_id' => (int) ($data['wr_id'] ?? 0), 'temp_1' => sql_real_escape_string($data['temp_1'] ?? ''), 'created_at' => 'NOW()', 'updated_at' => 'NOW()' ]; // SQL 생성 $fields = implode(', ', array_keys($insert_data)); $values = "'" . implode("', '", array_values($insert_data)) . "'"; $values = str_replace("'NOW()'", "NOW()", $values); // NOW() 함수 처리 $sql = "INSERT INTO {$this->table_name} ({$fields}) VALUES ({$values})"; if (!sql_query($sql)) { throw new Exception('데이터베이스 저장 실패: ' . sql_error()); } $reservation_id = sql_insert_id(); // 알림 발송 $this->sendReservationNotification($reservation_id, 'created'); consultant_log("새 예약 생성: ID {$reservation_id}, 고객: {$data['customer_name']}"); return [ 'success' => true, 'message' => '예약이 성공적으로 접수되었습니다.', 'data' => [ 'reservation_id' => $reservation_id ] ]; } catch (Exception $e) { consultant_log("예약 생성 실패: " . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * 예약 상태 변경 */ public function updateReservationStatus($reservation_id, $new_status, $admin_memo = '') { try { // 예약 정보 조회 $reservation = $this->getReservationById($reservation_id); if (!$reservation) { throw new Exception('예약 정보를 찾을 수 없습니다.'); } // 상태 변경 가능 여부 확인 $valid_transitions = [ 'payment_pending' => ['reserved', 'cancelled'], 'reserved' => ['completed', 'cancelled'], 'completed' => [], 'cancelled' => [] ]; $current_status = $reservation['status']; if (!in_array($new_status, $valid_transitions[$current_status])) { throw new Exception('현재 상태에서 해당 상태로 변경할 수 없습니다.'); } // 상태 업데이트 $sql = "UPDATE {$this->table_name} SET status = '" . sql_real_escape_string($new_status) . "', admin_memo = '" . sql_real_escape_string($admin_memo) . "', updated_at = NOW() WHERE id = {$reservation_id}"; if (!sql_query($sql)) { throw new Exception('상태 변경 실패: ' . sql_error()); } // 알림 발송 $this->sendReservationNotification($reservation_id, 'status_changed', $new_status); consultant_log("예약 상태 변경: ID {$reservation_id}, {$current_status} -> {$new_status}"); return [ 'success' => true, 'message' => '예약 상태가 성공적으로 변경되었습니다.' ]; } catch (Exception $e) { consultant_log("예약 상태 변경 실패: " . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * 입금 확인 */ public function confirmPayment($reservation_id, $admin_id) { try { // 예약 정보 조회 $reservation = $this->getReservationById($reservation_id); if (!$reservation) { throw new Exception('예약 정보를 찾을 수 없습니다.'); } if ($reservation['status'] !== 'payment_pending') { throw new Exception('입금 대기 상태의 예약만 확인할 수 있습니다.'); } // 입금 확인 처리 $sql = "UPDATE {$this->table_name} SET status = 'reserved', payment_status = 'paid', payment_confirmed_at = NOW(), payment_confirmed_by = '" . sql_real_escape_string($admin_id) . "', updated_at = NOW() WHERE id = {$reservation_id}"; if (!sql_query($sql)) { throw new Exception('입금 확인 처리 실패: ' . sql_error()); } // 알림 발송 $this->sendReservationNotification($reservation_id, 'payment_confirmed'); consultant_log("입금 확인: ID {$reservation_id}, 확인자: {$admin_id}"); return [ 'success' => true, 'message' => '입금이 확인되어 예약이 확정되었습니다.' ]; } catch (Exception $e) { consultant_log("입금 확인 실패: " . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * 예약 취소 */ public function cancelReservation($reservation_id, $reason) { try { // 예약 정보 조회 $reservation = $this->getReservationById($reservation_id); if (!$reservation) { throw new Exception('예약 정보를 찾을 수 없습니다.'); } if ($reservation['status'] === 'cancelled') { throw new Exception('이미 취소된 예약입니다.'); } if ($reservation['status'] === 'completed') { throw new Exception('완료된 예약은 취소할 수 없습니다.'); } // 예약 취소 처리 $sql = "UPDATE {$this->table_name} SET status = 'cancelled', admin_memo = '" . sql_real_escape_string($reason) . "', updated_at = NOW() WHERE id = {$reservation_id}"; if (!sql_query($sql)) { throw new Exception('예약 취소 처리 실패: ' . sql_error()); } // 알림 발송 $this->sendReservationNotification($reservation_id, 'cancelled', $reason); consultant_log("예약 취소: ID {$reservation_id}, 사유: {$reason}"); return [ 'success' => true, 'message' => '예약이 성공적으로 취소되었습니다.' ]; } catch (Exception $e) { consultant_log("예약 취소 실패: " . $e->getMessage(), 'error'); return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * 예약 정보 조회 */ public function getReservationById($reservation_id) { $sql = "SELECT * FROM {$this->table_name} WHERE id = {$reservation_id} AND is_deleted = 0"; return sql_fetch($sql); } /** * 예약 알림 발송 */ private function sendReservationNotification($reservation_id, $type, $extra_data = null) { try { $reservation = $this->getReservationById($reservation_id); if (!$reservation) { return false; } // 알림 템플릿 키 결정 $template_keys = [ 'created' => 'consultant_reservation_customer', 'payment_confirmed' => 'consultant_confirmed_customer', 'cancelled' => 'consultant_cancelled_customer', 'status_changed' => 'consultant_status_changed_customer' ]; $template_key = $template_keys[$type] ?? null; if (!$template_key) { return false; } // 템플릿 변수 준비 $variables = [ 'customer_name' => $reservation['customer_name'], 'customer_phone' => $reservation['customer_phone'], 'customer_email' => $reservation['customer_email'], 'reservation_date' => $reservation['reservation_date'], 'reservation_time' => $reservation['reservation_time'], 'payment_amount' => number_format($reservation['payment_amount']), 'account_info' => consultant_get_config('account_info', ''), 'cancel_reason' => $extra_data ?? '' ]; // 이메일 발송 if (!empty($reservation['customer_email'])) { $this->sendEmailNotification($reservation['customer_email'], $template_key, $variables); } // SMS 발송 if (!empty($reservation['customer_phone'])) { $this->sendSmsNotification($reservation['customer_phone'], $template_key, $variables); } // 관리자 알림 (새 예약 시) if ($type === 'created') { $this->sendAdminNotification($reservation, $variables); } return true; } catch (Exception $e) { consultant_log("알림 발송 실패: " . $e->getMessage(), 'error'); return false; } } /** * 이메일 알림 발송 */ private function sendEmailNotification($email, $template_key, $variables) { // 💡 [수정] consultant_send_notification 함수 사용으로 변경 return consultant_send_notification('email', $template_key, array_merge($variables, ['customer_email' => $email])); } /** * SMS 알림 발송 */ private function sendSmsNotification($phone, $template_key, $variables) { // 💡 [수정] consultant_send_notification 함수 사용으로 변경 return consultant_send_notification('sms', $template_key, array_merge($variables, ['customer_phone' => $phone])); } /** * 관리자 알림 발송 */ private function sendAdminNotification($reservation, $variables) { // 관리자 이메일 알림 $admin_template_key = 'consultant_reservation_admin'; // 관리자 이메일 주소 (설정에서 가져오거나 기본값 사용) $admin_email = consultant_get_config('admin_email', get_admin_email()); if ($admin_email) { // 💡 [수정] consultant_send_notification 함수 사용으로 변경 consultant_send_notification('email', $admin_template_key, array_merge($variables, ['customer_email' => $admin_email])); } } /** * 템플릿 변수 치환 */ private function replaceTemplateVariables($template, $variables) { foreach ($variables as $key => $value) { $template = str_replace('{' . $key . '}', $value, $template); } return $template; } /** * 예약 통계 조회 */ public function getReservationStats($start_date = null, $end_date = null) { try { if (!$start_date) $start_date = date('Y-m-01'); // 이번 달 첫날 if (!$end_date) $end_date = date('Y-m-d'); // 오늘 $sql = "SELECT COUNT(*) as total, COUNT(CASE WHEN status = 'payment_pending' THEN 1 END) as pending, COUNT(CASE WHEN status = 'reserved' THEN 1 END) as confirmed, COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed, COUNT(CASE WHEN status = 'cancelled' THEN 1 END) as cancelled, SUM(CASE WHEN status = 'completed' THEN payment_amount ELSE 0 END) as total_revenue FROM {$this->table_name} WHERE reservation_date BETWEEN '{$start_date}' AND '{$end_date}' AND is_deleted = 0"; return sql_fetch($sql); } catch (Exception $e) { consultant_log("통계 조회 실패: " . $e->getMessage(), 'error'); return null; } } }