471 lines
17 KiB
PHP
471 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* 상담 예약 관리 클래스
|
|
*/
|
|
|
|
if (!defined('_GNUBOARD_'))
|
|
exit;
|
|
|
|
class ReservationManager
|
|
{
|
|
|
|
private $table_name = 'consultant_reservations';
|
|
|
|
/**
|
|
* 예약 목록 조회
|
|
*/
|
|
public function getReservationList($status = '', $date = '', $page = 1, $per_page = 20, $search_type = '', $search_keyword = '')
|
|
{
|
|
try {
|
|
$where_conditions = ["is_deleted = 0"];
|
|
|
|
// 상태 필터
|
|
if (!empty($status)) {
|
|
$where_conditions[] = "status = '" . sql_real_escape_string($status) . "'";
|
|
}
|
|
|
|
// 날짜 필터
|
|
if (!empty($date)) {
|
|
$where_conditions[] = "reservation_date = '" . sql_real_escape_string($date) . "'";
|
|
}
|
|
|
|
// 검색 조건
|
|
if (!empty($search_keyword) && !empty($search_type)) {
|
|
$search_keyword = sql_real_escape_string($search_keyword);
|
|
switch ($search_type) {
|
|
case 'customer_name':
|
|
$where_conditions[] = "customer_name LIKE '%{$search_keyword}%'";
|
|
break;
|
|
case 'customer_phone':
|
|
$where_conditions[] = "customer_phone LIKE '%{$search_keyword}%'";
|
|
break;
|
|
}
|
|
}
|
|
|
|
$where_clause = implode(' AND ', $where_conditions);
|
|
|
|
// 전체 개수 조회
|
|
$count_sql = "SELECT COUNT(*) as total FROM {$this->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;
|
|
}
|
|
}
|
|
}
|