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