Files
dnssash/adm/consultant_manage/classes/ScheduleGenerator.class.php
T
2026-06-11 18:47:38 +09:00

332 lines
13 KiB
PHP

<?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;
}
}
?>