first commit 2
This commit is contained in:
@@ -0,0 +1,470 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* 상담 예약 시스템 - 월별 스케줄 자동 생성 클래스
|
||||
*
|
||||
* 요일별 설정을 기반으로 월별 상세 스케줄을 자동 생성하고 관리합니다.
|
||||
*/
|
||||
|
||||
class ScheduleGenerator
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $connect_db;
|
||||
$this->db = $connect_db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 월의 전체 스케줄 생성
|
||||
*
|
||||
* @param int $year 년도
|
||||
* @param int $month 월
|
||||
* @return bool 성공 여부
|
||||
*/
|
||||
public function generateMonth($year, $month)
|
||||
{
|
||||
try {
|
||||
// 기본 설정 조회
|
||||
$basicSettings = $this->getBasicSettings();
|
||||
|
||||
// 요일별 설정 조회
|
||||
$weeklySettings = $this->getWeeklySettings();
|
||||
|
||||
// 해당 월의 모든 날짜 생성
|
||||
$dates = self::getMonthDates($year, $month);
|
||||
|
||||
// 기존 스케줄 중 예약이 없는 것들 삭제 (재생성을 위해)
|
||||
$this->clearAutoGeneratedSchedules($year, $month);
|
||||
|
||||
// 각 날짜별로 스케줄 생성
|
||||
foreach ($dates as $date) {
|
||||
$dayOfWeek = date('N', strtotime($date)); // 1=월요일, 7=일요일
|
||||
$this->generateDay($date, $dayOfWeek, $weeklySettings, $basicSettings);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
consultant_log("스케줄 생성 실패: " . $e->getMessage(), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 날짜의 스케줄 생성
|
||||
*/
|
||||
private function generateDay($date, $dayOfWeek, $weeklySettings, $basicSettings)
|
||||
{
|
||||
$dayNames = ['', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$dayName = $dayNames[$dayOfWeek];
|
||||
|
||||
// 해당 요일이 운영일인지 확인
|
||||
$isEnabled = $weeklySettings[$dayName . '_enabled'] ?? '0';
|
||||
|
||||
if ($isEnabled == '0') {
|
||||
// 휴무일 처리
|
||||
$this->insertHolidaySchedule($date);
|
||||
return;
|
||||
}
|
||||
|
||||
// 운영시간 정보
|
||||
$startTime = $weeklySettings[$dayName . '_start'] ?? '09:00';
|
||||
$endTime = $weeklySettings[$dayName . '_end'] ?? '18:00';
|
||||
$lunchStart = $weeklySettings[$dayName . '_lunch_start'] ?? '12:00';
|
||||
$lunchEnd = $weeklySettings[$dayName . '_lunch_end'] ?? '13:00';
|
||||
|
||||
// 시간 슬롯 생성
|
||||
$this->createTimeSlots($date, $startTime, $endTime, $basicSettings['consultation_duration'], $basicSettings['max_persons_per_slot']);
|
||||
|
||||
// 점심시간 블록 처리
|
||||
// 💡 [수정] 시작 시간과 종료 시간이 다를 때만 점심시간으로 처리합니다.
|
||||
if ($lunchStart && $lunchEnd && strtotime($lunchStart) < strtotime($lunchEnd)) {
|
||||
$this->blockLunchTime($date, $lunchStart, $lunchEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 시간 슬롯 생성
|
||||
*/
|
||||
private function createTimeSlots($date, $startTime, $endTime, $slotDuration, $maxPersons)
|
||||
{
|
||||
$currentTime = strtotime($startTime);
|
||||
$endTimeStamp = strtotime($endTime);
|
||||
$slotMinutes = (int) $slotDuration;
|
||||
|
||||
while ($currentTime < $endTimeStamp) {
|
||||
$slotStart = date('H:i', $currentTime);
|
||||
$slotEnd = date('H:i', $currentTime + ($slotMinutes * 60));
|
||||
|
||||
// 종료시간을 넘지 않는 경우만 생성
|
||||
if (strtotime($slotEnd) <= $endTimeStamp) {
|
||||
// 재생성 시 중복 생성을 방지하기 위해 기존 슬롯이 있는지 확인
|
||||
$check_sql = "SELECT id FROM consultant_schedule
|
||||
WHERE specific_date = '" . sql_real_escape_string($date) . "'
|
||||
AND start_time = '" . sql_real_escape_string($slotStart) . "'";
|
||||
|
||||
$existing_slot = sql_fetch($check_sql);
|
||||
|
||||
// 기존에 수동으로 추가되었거나 예약으로 보호된 슬롯이 없으면 새로 생성
|
||||
if (!$existing_slot) {
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, created_at)
|
||||
VALUES (
|
||||
'" . sql_real_escape_string($date) . "',
|
||||
'" . sql_real_escape_string($slotStart) . "',
|
||||
'" . sql_real_escape_string($slotEnd) . "',
|
||||
" . (int) $slotMinutes . ",
|
||||
" . (int) $maxPersons . ",
|
||||
1,
|
||||
'auto_generated',
|
||||
NOW()
|
||||
)";
|
||||
|
||||
if (!sql_query($sql)) {
|
||||
// sql_query가 false를 반환하면 오류를 던짐
|
||||
throw new Exception("시간 슬롯 생성 실패: " . sql_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
$currentTime += ($slotMinutes * 60);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 점심시간 블록 처리
|
||||
*/
|
||||
private function blockLunchTime($date, $lunchStart, $lunchEnd)
|
||||
{
|
||||
$sql = "UPDATE consultant_schedule
|
||||
SET is_available = 0, temp_1 = 'lunch_time', temp_2 = '점심시간', updated_at = NOW()
|
||||
WHERE specific_date = '" . sql_real_escape_string($date) . "'
|
||||
-- 💡 [수정] start_time이 휴게시간 범위에 포함되는 모든 슬롯을 대상으로 하도록 변경합니다.
|
||||
-- 이렇게 하면 상담 시간 단위(30분, 60분 등)에 상관없이 정확하게 동작합니다.
|
||||
AND start_time >= '" . sql_real_escape_string($lunchStart) . "' AND start_time < '" . sql_real_escape_string($lunchEnd) . "'";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴무일 스케줄 삽입
|
||||
*/
|
||||
private function insertHolidaySchedule($date)
|
||||
{
|
||||
// 💡 [수정] 휴무일 데이터 중복 생성을 방지하기 위해, 해당 날짜에 이미 스케줄이 있는지 확인합니다.
|
||||
$check_sql = "SELECT id FROM consultant_schedule WHERE specific_date = '" . sql_real_escape_string($date) . "'";
|
||||
$existing_schedule = sql_fetch($check_sql);
|
||||
|
||||
// 해당 날짜에 아무 스케줄도 없을 때만 휴무일 데이터를 삽입합니다.
|
||||
if (!$existing_schedule) {
|
||||
$sql = "INSERT INTO consultant_schedule
|
||||
(specific_date, start_time, end_time, time_slot, max_persons, is_available, temp_1, temp_2, created_at)
|
||||
VALUES (
|
||||
'" . sql_real_escape_string($date) . "',
|
||||
'00:00',
|
||||
'23:59',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'holiday',
|
||||
'휴무일',
|
||||
NOW()
|
||||
)";
|
||||
|
||||
sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 예약 보호 로직 - 예약이 있는 시간대는 삭제하지 않음
|
||||
*/
|
||||
private function protectExistingReservations($year, $month)
|
||||
{
|
||||
$startDate = sprintf('%04d-%02d-01', $year, $month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 해당 월에 예약이 있는 스케줄 ID 조회
|
||||
$sql = "SELECT DISTINCT s.id
|
||||
FROM consultant_schedule s
|
||||
INNER JOIN consultant_reservations r ON (
|
||||
s.specific_date = r.reservation_date
|
||||
AND s.start_time = r.reservation_time
|
||||
)
|
||||
WHERE s.specific_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND r.status NOT IN ('cancelled')
|
||||
AND r.is_deleted = 0";
|
||||
|
||||
$result = sql_query($sql);
|
||||
$protectedIds = [];
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$protectedIds[] = $row['id'];
|
||||
}
|
||||
|
||||
return $protectedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 변경으로 인한 기존 예약과의 충돌 감지 (향상된 로직)
|
||||
* - 휴무일로 변경된 경우
|
||||
* - 운영 시간 밖으로 밀려난 경우
|
||||
* - 점심시간과 겹치는 경우
|
||||
*/
|
||||
public function findConflictsWithNewSettings($year, $month)
|
||||
{
|
||||
$conflicts = [];
|
||||
$startDate = sprintf('%04d-%02d-01', $year, $month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 해당 월의 모든 예약 조회
|
||||
$sql = "SELECT r.reservation_date, r.reservation_time, r.customer_name, r.customer_phone
|
||||
FROM consultant_reservations r
|
||||
WHERE r.reservation_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND r.status NOT IN ('cancelled')
|
||||
AND r.is_deleted = 0";
|
||||
|
||||
$weeklySettings = $this->getWeeklySettings();
|
||||
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$dayOfWeek = date('N', strtotime($row['reservation_date']));
|
||||
$dayNames = ['', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$dayName = $dayNames[$dayOfWeek];
|
||||
|
||||
$isEnabled = $weeklySettings[$dayName . '_enabled'] ?? '0';
|
||||
$startTime = $weeklySettings[$dayName . '_start'] ?? '09:00';
|
||||
$endTime = $weeklySettings[$dayName . '_end'] ?? '18:00';
|
||||
$lunchStart = $weeklySettings[$dayName . '_lunch_start'] ?? '12:00';
|
||||
$lunchEnd = $weeklySettings[$dayName . '_lunch_end'] ?? '13:00';
|
||||
|
||||
$reservationTime = $row['reservation_time'];
|
||||
$conflictReason = '';
|
||||
|
||||
if ($isEnabled == '0') {
|
||||
$conflictReason = '휴무일로 변경됨';
|
||||
} elseif (strtotime($reservationTime) < strtotime($startTime) || strtotime($reservationTime) >= strtotime($endTime)) {
|
||||
$conflictReason = '운영 시간 벗어남';
|
||||
} elseif (strtotime($reservationTime) >= strtotime($lunchStart) && strtotime($reservationTime) < strtotime($lunchEnd)) {
|
||||
$conflictReason = '점심시간과 겹침';
|
||||
}
|
||||
|
||||
if ($conflictReason) {
|
||||
$conflicts[] = [
|
||||
'date' => $row['reservation_date'],
|
||||
'time' => $reservationTime,
|
||||
'customer' => $row['customer_name'],
|
||||
'phone' => $row['customer_phone'],
|
||||
'reason' => $conflictReason
|
||||
];
|
||||
}
|
||||
}
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 생성된 스케줄 삭제 (예약이 없는 것만)
|
||||
*/
|
||||
private function clearAutoGeneratedSchedules($year, $month)
|
||||
{
|
||||
$startDate = sprintf('%04d-%02d-01', (int)$year, (int)$month);
|
||||
$endDate = date('Y-m-t', strtotime($startDate));
|
||||
|
||||
// 예약이 있는 '시간 슬롯'의 ID를 보호
|
||||
$protectedIds = $this->protectExistingReservations($year, $month);
|
||||
|
||||
// 💡 [수정] 예약이 있는 날짜라도 'holiday' 타입의 스케줄은 삭제될 수 있도록 temp_1 조건만 사용합니다.
|
||||
$whereClause = "specific_date BETWEEN '{$startDate}' AND '{$endDate}'
|
||||
AND temp_1 IN ('auto_generated', 'lunch_time', 'holiday', 'manual_block')";
|
||||
|
||||
if (!empty($protectedIds)) {
|
||||
$whereClause .= " AND id NOT IN (" . implode(',', $protectedIds) . ")";
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM consultant_schedule WHERE " . $whereClause;
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 월의 모든 날짜 배열 생성
|
||||
*/
|
||||
private static function getMonthDates($year, $month)
|
||||
{
|
||||
$dates = [];
|
||||
$daysInMonth = date('t', mktime(0, 0, 0, $month, 1, $year));
|
||||
|
||||
for ($day = 1; $day <= $daysInMonth; $day++) {
|
||||
$dates[] = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 설정 조회
|
||||
*/
|
||||
private function getBasicSettings()
|
||||
{
|
||||
return [
|
||||
'consultation_duration' => (int) consultant_get_config('consultation_duration', 60),
|
||||
'max_persons_per_slot' => (int) consultant_get_config('max_persons_per_slot', 2)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 요일별 설정 조회
|
||||
*/
|
||||
private function getWeeklySettings()
|
||||
{
|
||||
$days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
||||
$settings = [];
|
||||
|
||||
foreach ($days as $day) {
|
||||
$settings[$day . '_enabled'] = consultant_get_config($day . '_enabled', $day == 'saturday' || $day == 'sunday' ? '0' : '1');
|
||||
$settings[$day . '_start'] = consultant_get_config($day . '_start', '09:00');
|
||||
$settings[$day . '_end'] = consultant_get_config($day . '_end', '18:00');
|
||||
$settings[$day . '_lunch_start'] = consultant_get_config($day . '_lunch_start', '12:00');
|
||||
$settings[$day . '_lunch_end'] = consultant_get_config($day . '_lunch_end', '13:00');
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// ❗ [핵심] 필요한 클래스와 라이브러리를 포함합니다.
|
||||
if (file_exists(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php')) {
|
||||
require_once(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php');
|
||||
}
|
||||
if (file_exists(G5_PLUGIN_PATH . '/sms5/sms5.lib.php')) {
|
||||
include_once(G5_PLUGIN_PATH . '/sms5/sms5.lib.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 통합 메일/SMS 발송 클래스
|
||||
* 기존 mail_manage, sms_admin 시스템을 활용하여 발송 및 이력 기록
|
||||
*/
|
||||
class NotificationSender
|
||||
{
|
||||
private $g5;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $g5;
|
||||
$this->g5 = $g5;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 템플릿 기반 메인 발송 함수
|
||||
* @param array $params 발송 파라미터
|
||||
* @return array 발송 결과
|
||||
*/
|
||||
public function send($params)
|
||||
{
|
||||
// 파라미터 검증
|
||||
$validated = $this->validateParams($params);
|
||||
if (!$validated['success']) {
|
||||
return $validated;
|
||||
}
|
||||
|
||||
// 대상 회원 조회
|
||||
$members = $this->getTargetMembers($params);
|
||||
if (empty($members)) {
|
||||
return ['success' => false, 'message' => '발송 대상 회원이 없습니다.'];
|
||||
}
|
||||
|
||||
$results = [
|
||||
'success' => true,
|
||||
'total_targets' => count($members),
|
||||
'sms_success' => 0,
|
||||
'sms_fail' => 0,
|
||||
'email_success' => 0,
|
||||
'email_fail' => 0,
|
||||
'message' => ''
|
||||
];
|
||||
|
||||
// SMS 발송
|
||||
if (!empty($params['sms_template_key'])) {
|
||||
$sms_result = $this->sendSMS($members, $params['sms_template_key'], $params['vars'] ?? []);
|
||||
$results['sms_success'] = $sms_result['success'];
|
||||
$results['sms_fail'] = $sms_result['fail'];
|
||||
}
|
||||
|
||||
// 이메일 발송
|
||||
if (!empty($params['email_template_key'])) {
|
||||
$email_result = $this->sendEmail($members, $params['email_template_key'], $params['vars'] ?? []);
|
||||
$results['email_success'] = $email_result['success'];
|
||||
$results['email_fail'] = $email_result['fail'];
|
||||
}
|
||||
|
||||
$results['message'] = $this->generateResultMessage($results);
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 템플릿 기반 파라미터 검증
|
||||
*/
|
||||
private function validateParams($params)
|
||||
{
|
||||
if (empty($params['target_type'])) {
|
||||
return ['success' => false, 'message' => "필수 파라미터 'target_type'이 누락되었습니다."];
|
||||
}
|
||||
if ($params['target_type'] === 'single' && empty($params['member_id'])) {
|
||||
return ['success' => false, 'message' => '단일 발송 시 회원 ID(member_id)가 필요합니다.'];
|
||||
}
|
||||
if ($params['target_type'] === 'bulk' && empty($params['member_levels'])) {
|
||||
return ['success' => false, 'message' => '대량 발송 시 회원 레벨(member_levels)이 필요합니다.'];
|
||||
}
|
||||
if (empty($params['sms_template_key']) && empty($params['email_template_key'])) {
|
||||
return ['success' => false, 'message' => 'SMS 또는 이메일 템플릿 키 중 하나는 반드시 필요합니다.'];
|
||||
}
|
||||
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* 대상 회원 조회
|
||||
*/
|
||||
private function getTargetMembers($params)
|
||||
{
|
||||
$members = [];
|
||||
$member_table = $this->g5['member_table'];
|
||||
|
||||
if ($params['target_type'] === 'single') {
|
||||
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_id = '" . sql_real_escape_string($params['member_id']) . "' AND mb_leave_date = '' AND mb_intercept_date = ''";
|
||||
} else {
|
||||
$levels = array_map('intval', $params['member_levels']);
|
||||
$level_condition = implode(',', $levels);
|
||||
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_level IN ({$level_condition}) AND mb_leave_date = '' AND mb_intercept_date = ''";
|
||||
}
|
||||
$this->write_debug_log("[SMS 발송 시작] sql '{$sql}'");
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$members[] = $row;
|
||||
}
|
||||
return $members;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] SMS 발송 (템플릿 및 변수 처리, 이력 기록 포함)
|
||||
*/
|
||||
private function sendSMS($members, $template_key, $common_vars)
|
||||
{
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
$notification_mode = get_order_config('notification_mode', 'log');
|
||||
$is_test_mode = ($notification_mode !== 'send');
|
||||
|
||||
$sizeof_members = count($members);
|
||||
|
||||
// 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_sms_templates) -> 기본 테이블(sms_templates) 순서로 조회
|
||||
$template = null;
|
||||
|
||||
// 1. 지정 테이블 조회
|
||||
$check_table = sql_query("SHOW TABLES LIKE 'consultant_sms_templates'", false);
|
||||
if (sql_num_rows($check_table) > 0) {
|
||||
$template = sql_fetch("SELECT * FROM `consultant_sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
if ($template) {
|
||||
// 필드명 통일 (기본 테이블과 필드명이 다를 수 있음)
|
||||
$template['content'] = $template['template_content'];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 기본 테이블 조회 (지정 테이블에 없을 경우)
|
||||
if (!$template) {
|
||||
$template = sql_fetch("SELECT * FROM `sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
}
|
||||
|
||||
// 3. 둘 다 없으면 에러 처리
|
||||
if (!$template) {
|
||||
$this->write_debug_log("[SMS 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
|
||||
$count= count($members);
|
||||
if ($is_test_mode) {
|
||||
// --- 개발 모드: 로그 파일에만 기록 ---
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$personal_message = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
|
||||
}
|
||||
$this->write_debug_log("[SMS LOG] To: {$member['mb_hp']}, Content: {$personal_message}");
|
||||
$success++;
|
||||
} else {
|
||||
$empty = !empty($member['mb_hp']);
|
||||
$this->write_debug_log("[SMS 발송오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- 실제 발송 모드: DB 기록 및 실제 발송 ---
|
||||
$sms_config = sql_fetch("SELECT * FROM {$this->g5['sms5_config_table']}");
|
||||
$send_phone = $sms_config['cf_phone'];
|
||||
|
||||
$wr_message = sql_real_escape_string($template['content']);
|
||||
$wr_reply = sql_real_escape_string($send_phone);
|
||||
sql_query("INSERT INTO {$this->g5['sms5_write_table']} (wr_message, wr_reply, wr_total, wr_datetime) VALUES ('{$wr_message}', '{$wr_reply}', '" . count($members) . "', '" . G5_TIME_YMDHIS . "')");
|
||||
$wr_no = sql_insert_id();
|
||||
|
||||
$SMS = null;
|
||||
if (class_exists('SMS5')) {
|
||||
$SMS = new SMS5;
|
||||
$SMS->SMS_con($sms_config['cf_sms_ip'], $sms_config['cf_sms_id'], $sms_config['cf_sms_pw'], $sms_config['cf_sms_port']);
|
||||
} else {
|
||||
$this->write_debug_log("[SMS 발송 오류] SMS5 클래스를 찾을 수 없습니다. 실제 발송을 건너뜁니다.");
|
||||
}
|
||||
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$personal_message = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
|
||||
}
|
||||
|
||||
$result_code = 'Fail';
|
||||
$result_msg = 'SMS5 클래스가 없어 발송할 수 없습니다.';
|
||||
$hs_status = '0';
|
||||
|
||||
if ($SMS) {
|
||||
$SMS->Add($member['mb_hp'], $send_phone, '', $personal_message);
|
||||
$SMS->Send();
|
||||
$result_arr = $SMS->Result;
|
||||
$result_code = 'Fail';
|
||||
$result_msg = '서버로부터 응답이 없습니다.';
|
||||
|
||||
if(!empty($result_arr)){
|
||||
$result_parts = explode(':', $result_arr[0]);
|
||||
if(count($result_parts) > 1 && strpos($result_parts[1], 'Error') === false) {
|
||||
$result_code = 'Success';
|
||||
$result_msg = $result_parts[1];
|
||||
} else {
|
||||
$result_msg = $result_arr[0];
|
||||
}
|
||||
}
|
||||
$hs_status = ($result_code == 'Success') ? '1' : '0';
|
||||
$SMS->Init();
|
||||
}
|
||||
|
||||
sql_query("INSERT INTO {$this->g5['sms5_history_table']} (wr_no, mb_id, hs_name, hs_hp, hs_datetime, hs_status, hs_message) VALUES ('{$wr_no}', '{$member['mb_id']}', '{$member['mb_name']}', '{$member['mb_hp']}', '" . G5_TIME_YMDHIS . "', '{$hs_status}', '{$result_msg}')");
|
||||
|
||||
if ($hs_status == '1') $success++;
|
||||
else $fail++;
|
||||
} else {
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['success' => $success, 'fail' => $fail];
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 이메일 발송 (MailSender 클래스 활용)
|
||||
*/
|
||||
private function sendEmail($members, $template_key, $common_vars)
|
||||
{
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
|
||||
$notification_mode = get_order_config('notification_mode', 'log');
|
||||
$is_test_mode = ($notification_mode !== 'send');
|
||||
$sizeof_members = count($members);
|
||||
|
||||
// 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_mail_templates) -> 기본 테이블(mail_templates) 순서로 조회
|
||||
$template = null;
|
||||
|
||||
// 1. 지정 테이블 조회
|
||||
$check_table = sql_query("SHOW TABLES LIKE 'consultant_mail_templates'", false);
|
||||
if (sql_num_rows($check_table) > 0) {
|
||||
$template = sql_fetch("SELECT * FROM `consultant_mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
if ($template) {
|
||||
// 필드명 통일
|
||||
$template['subject'] = $template['template_subject'];
|
||||
$template['content'] = $template['template_content'];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 기본 테이블 조회 (지정 테이블에 없을 경우)
|
||||
if (!$template) {
|
||||
$template = sql_fetch("SELECT * FROM `mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
|
||||
}
|
||||
|
||||
// 3. 둘 다 없으면 에러 처리
|
||||
if (!$template) {
|
||||
$this->write_debug_log("[EMAIL 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
|
||||
if ($is_test_mode) {
|
||||
// --- 개발 모드: 로그 파일에만 기록 ---
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
$subject = $template['subject'];
|
||||
$content = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$search = '{' . $key . '}';
|
||||
$subject = str_replace($search, $value, $subject);
|
||||
$content = str_replace($search, $value, $content);
|
||||
}
|
||||
$this->write_debug_log("[EMAIL LOG] To: {$member['mb_email']}, Subject: {$subject}, Content: {$content}");
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
$empty = !empty($member['mb_email']);
|
||||
$this->write_debug_log("[EMAIL 발송 오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --- 실제 발송 모드: MailSender 호출 ---
|
||||
if (!class_exists('MailSender')) {
|
||||
$this->write_debug_log("[EMAIL 발송 오류] MailSender 클래스를 찾을 수 없습니다.");
|
||||
return ['success' => 0, 'fail' => count($members)];
|
||||
}
|
||||
$mailSender = new MailSender();
|
||||
|
||||
foreach ($members as $member) {
|
||||
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
|
||||
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
|
||||
|
||||
// 💡 [수정] MailSender가 템플릿 키로 조회하는 방식일 수 있으므로,
|
||||
// 커스텀 템플릿 내용을 직접 전달하거나 MailSender를 수정해야 할 수 있음.
|
||||
// 여기서는 MailSender가 템플릿 키를 받아서 내부적으로 처리한다고 가정하고,
|
||||
// 만약 커스텀 템플릿을 사용해야 한다면 MailSender의 동작 방식에 따라 수정이 필요함.
|
||||
// 현재 구조상 MailSender::send()는 템플릿 키를 받으므로,
|
||||
// MailSender 내부에서도 동일한 우선순위 로직이 필요하거나,
|
||||
// 여기서 내용을 다 만들어서 보내는 방식(sendDirect 등)이 있다면 그걸 써야 함.
|
||||
// 일단 기존 로직 유지하되, MailSender가 커스텀 테이블을 인지하지 못할 수 있음을 주석으로 남김.
|
||||
|
||||
// 만약 MailSender가 내용을 직접 받는 메소드가 없다면,
|
||||
// 여기서 내용을 치환해서 보내는 로직을 직접 구현해야 할 수도 있음.
|
||||
// 하지만 요청사항은 "지정 테이블을 읽고 없으면 기본 테이블을 읽어서 발송"이므로,
|
||||
// 위에서 $template을 구했으니, 내용을 직접 치환해서 메일 발송 함수(mailer)를 직접 호출하는 것이 안전함.
|
||||
|
||||
$subject = $template['subject'];
|
||||
$content = $template['content'];
|
||||
foreach ($personal_vars as $key => $value) {
|
||||
$search = '{' . $key . '}';
|
||||
$subject = str_replace($search, $value, $subject);
|
||||
$content = str_replace($search, $value, $content);
|
||||
}
|
||||
|
||||
// G5 기본 mailer 함수 사용 (MailSender 의존성 제거 또는 우회)
|
||||
include_once(G5_LIB_PATH.'/mailer.lib.php');
|
||||
mailer($this->g5['title'], $this->g5['admin_email'], $member['mb_email'], $subject, $content, 1);
|
||||
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['success' => $success, 'fail' => $fail];
|
||||
}
|
||||
|
||||
/**
|
||||
* 결과 메시지 생성
|
||||
*/
|
||||
private function generateResultMessage($results)
|
||||
{
|
||||
$message = "발송이 완료되었습니다.\n\n";
|
||||
$message .= "전체 대상: " . $results['total_targets'] . "명\n\n";
|
||||
if (isset($results['sms_success'])) {
|
||||
$message .= "SMS 발송 결과: 성공 " . $results['sms_success'] . "건, 실패 " . $results['sms_fail'] . "건\n";
|
||||
}
|
||||
if (isset($results['email_success'])) {
|
||||
$message .= "이메일 발송 결과: 성공 " . $results['email_success'] . "건, 실패 " . $results['email_fail'] . "건\n";
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] 디버그 로그 기록 함수 (권한 문제 해결)
|
||||
*/
|
||||
private function write_debug_log($message)
|
||||
{
|
||||
$log_dir = G5_PATH . '/log';
|
||||
|
||||
// 1. 디렉토리 존재 여부 확인 및 생성
|
||||
if (!is_dir($log_dir)) {
|
||||
if (!@mkdir($log_dir, 0755, true) && !is_dir($log_dir)) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 디렉토리 생성 실패. '{$log_dir}' 경로를 확인하거나 수동으로 생성 후 웹서버 쓰기 권한을 부여해주세요.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 디렉토리 쓰기 권한 확인
|
||||
if (!is_writable($log_dir)) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 쓰기 오류. '{$log_dir}' 디렉토리에 쓰기 권한이 없습니다. 웹서버의 폴더 권한을 확인해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 로그 파일에 내용 기록
|
||||
$log_file = $log_dir . '/notification_debug.log';
|
||||
$log_message = date("[Y-m-d H:i:s]") . " " . $message . "\n";
|
||||
|
||||
if (file_put_contents($log_file, $log_message, FILE_APPEND | LOCK_EX) === false) {
|
||||
error_log("--- NotificationSender ERROR: 디버그 로그 파일 쓰기 실패. '{$log_file}' 파일에 내용을 쓸 수 없습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user