Я использую FullCalendar для бронирования инструментов, но не знаю, как предотвратить перекрытие периодов бронирования.

Вопрос или проблема

В красном – предыдущие бронирования, в зеленом – новое, перекрывающее одно из предыдущих бронирований

Предыдущие бронирования определяются элементом кода php. Параметры eventOverlap и selectOverlap равны false, но у меня есть перекрытие событий.

document.addEventListener('DOMContentLoaded', function() {
    var calendarEl = document.getElementById('calendar');
    var reservations = [
        <?php
        foreach ($reservations as $index => $reservation) {
            if ($index > 0) echo ',';
            echo "{
                start: '{$reservation['date_debut']}',
                end: '{$reservation['date_fin']}',
                backgroundColor: 'red',
                rendering: 'background'
            }";
        }
        ?>
    ];
    var calendar = new FullCalendar.Calendar(calendarEl, {
        initialView: 'dayGridMonth',
        selectable: true,
        selectOverlap: function(event) {
            return !event.rendering;
        },
        events: reservations,
        eventOverlap: false,
        selectOverlap: false,
        select: function(info) {
            var isConflict = reservations.some(function(reservation) {
                var reservationStart = new Date(reservation.start).getTime();
                //var reservationEnd = new Date(reservation.end).getTime() - 86400000; // Вычитать 1 день
                var reservationEnd = new Date(reservation.end).getTime();
                var selectedStart = new Date(info.startStr).getTime();
                //var selectedEnd = new Date(info.endStr).getTime() - 86400000; // Вычитать 1 день
                var selectedEnd = new Date(info.endStr).getTime();
                return selectedStart <= reservationEnd && selectedEnd >= reservationStart;
            });
            if (isConflict) {
                alert('Ошибка: выбранный период пересекается с существующим бронированием.');
                calendar.unselect();
            } else {
                document.getElementById('date_debut').value = info.startStr;
                // Корректное обновление даты окончания без дополнительного вычитания
                document.getElementById('date_fin').value = info.endStr;
            }
        },
        validRange: {
            start: new Date().toISOString().split('T')[0] // Отключить выбор в прошлом
        },
        unselectAuto: false
    });
    calendar.render();

Не используйте eventOverlap: false или selectOverlap: false. Вместо этого обрабатывайте конфликты в функции saveevent, вызываемой функцией select.

    //Эта функция срабатывает, когда в календаре нажимается свободный временной интервал
    select: function(info) {
          console.log('выбрано ' + info.startStr + ' до ' + info.endStr);    
          var options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
          var date = new Date(info.start);
          // пример: Суббота, 17 сентября 2016
          var formatteddate=date.toLocaleTimeString("en-US", options)             
          document.getElementById("formatteddate").value = formatteddate;
          document.getElementById("hour").value = info.startStr;  
          saveevent();
         }

saveevent

function saveevent(event) {
    console.log("сохранить событие");
    
    const user_id = JSON.parse(document.getElementById('user_id').textContent);
    const servicelist = document.getElementById('servicelist').value;
    const eventstart = document.getElementById("hour").value;
    const totalminutes = document.getElementById("totalminutes").textContent;
    
    console.log("час:" + eventstart );
    
    fetch('/saveevent', {
      method: 'PUT',
      credentials:'include',
      body: JSON.stringify({
          userid: user_id,
          eventstart:eventstart,
          servicelist:servicelist,
          totalminutes:totalminutes,
      })
    })
    .then(response => response.json())
    .then(result => {
        // Печать результата
        console.log(result);

        if(result.message=="событие успешно сохранено.")
        {
          document.getElementById("errormessage").innerHTML="";
          calendar.refetchEvents();
        }
        else
        {       
          document.getElementById("errormessage").innerHTML=result.message;
        }
        
        
    });
};

функция python

def saveevent(request):
    if request.method == "PUT":
    
        # Получить содержимое события
        data = json.loads(request.body)
        startdate_str = data.get("eventstart", "")    
        servicelist=data.get("servicelist", "")    
        user = User.objects.get(id=request.user.id)    
        totalminutes=data.get("totalminutes", "")    

        start_date_obj =  parse(startdate_str) 
        end_date_obj = start_date_obj + timedelta(minutes=int(totalminutes) )
        
        print('вызов представления saveevent для пользователя с id: ' + str(request.user.id) + ' дата начала:' + str(startdate_str)    + ' услуги ' + str(servicelist) )

        now_obj=datetime.now(tz=timezone.utc)
        earlieststart=now_obj + timedelta(hours=int(22) )
        earlieststart_str = earlieststart.strftime('%A, %B %d в %I:%M %p')
        
            if start_date_obj < earlieststart:
                 #Недостаточно предварительного уведомления. Самое раннее время начала - вторник, 17 ноября в 10:00.
                 return JsonResponse({"message": "Недостаточно предварительного уведомления. Самое раннее время начала - " + earlieststart_str }, status=201)               
            else:
                #Конфликт возникает в 2 возможных сценариях:
                
                #1. Предложенная дата начала попадает в промежуток времени существующего события
                StartDateConflicts = Event.objects.filter(start_date__gt=startdate_str,start_date__lt=end_date_obj) 

                #2. Предложенная дата окончания попадает в промежуток времени существующего события
                EndDateConflicts = Event.objects.filter(end_date__gt=startdate_str,end_date__lt=end_date_obj)
                
                conflictingevents = StartDateConflicts | EndDateConflicts
                
                if conflictingevents:
                    return JsonResponse({"message": "событие конфликтует с " + str(conflictingevents.count()) + " другими событиями в расписании"}, status=201)        
                else:          
                    #datetime.strptime(startdate_str, '%Y-%m-%d %H:%M:%S.%f')

                    #удалить все неподтвержденные события для пользователя
                    Event.objects.filter(owner_id=user.id, confirmed=False).delete()
                    
                    event = Event(
                            event_name=user.username,
                            start_date=start_date_obj,
                            end_date=end_date_obj,
                            owner_id=user.id,
                            create_date=now_obj,
                        )
                    event.save()
                    print('событие сохранено:' + user.username + ' ' + startdate_str)
                    return JsonResponse({"message": "событие успешно сохранено."}, status=201)                               
    else:
        return JsonResponse({"message": "Неверный метод запроса. Ожидался метод POST."}, status=400)    

Ответ или решение

Предотвращение наложения периодов бронирования с использованием FullCalendar

Ваша задача — предотвратить наложение периодов бронирования при использовании FullCalendar для управления событиями бронирования. Давайте рассмотрим, как это можно реализовать шаг за шагом, устраняя существующие проблемы и улучшая поведение вашего приложения.

1. Проблема наложения событий

Ваша текущая реализация использует следующие параметры:

eventOverlap: false,
selectOverlap: false,

Однако они не предотвращают наложение событий, поскольку ваша реализация обработки выборки событий не учитывает существующие события. Вместо того чтобы использовать некоторые пропущенные параметры, вы можете воспользоваться подходом обработки конфликтов в select функции.

2. Обработка конфликтов в функции select

Измените функцию select, чтобы она проверяла наличие конфликтов с существующими бронированиями, а затем принимала решение о выборе:

select: function(info) {
    var isConflict = reservations.some(function(reservation) {
        var reservationStart = new Date(reservation.start).getTime();
        var reservationEnd = new Date(reservation.end).getTime();
        var selectedStart = new Date(info.startStr).getTime();
        var selectedEnd = new Date(info.endStr).getTime();
        return selectedStart < reservationEnd && selectedEnd > reservationStart;
    });
    if (isConflict) {
        alert('Ошибка: выбранный период наложивается на существующее бронирование.');
        calendar.unselect();
    } else {
        document.getElementById('date_debut').value = info.startStr;
        document.getElementById('date_fin').value = info.endStr;
        saveevent(); // сериализуйте событие, если выбор успешен
    }
}

Этот код проверяет, пересекается ли выбранный период с любым из существующих бронирований. Если конфликт обнаруживается, пользователю показывается сообщение об ошибке, и выделение отменяется.

3. Рендеринг и управление событиями

Обратите особое внимание на массив reservations, в который должны были попасть все существующие бронирования. Убедитесь, что данные формируются корректно через PHP.

Пример PHP скрипта для создания событий

Убедитесь, что ваши события правильно формируются в массиве JavaScript:

var reservations = [
    <?php foreach ($reservations as $index => $reservation): ?>
        {
            start: '<?php echo $reservation['date_debut']; ?>',
            end: '<?php echo $reservation['date_fin']; ?>',
            backgroundColor: 'red',
            rendering: 'background'
        }<?php if ($index < count($reservations) - 1) echo ','; ?>
    <?php endforeach; ?>
];

4. Проверка на серверной стороне

Несмотря на проверки на клиенте, рекомендуется дублировать проверку на серверной стороне. Ваш метод saveevent в Python выполняет проверку пересечений, и это важно для обеспечения целостности данных:

StartDateConflicts = Event.objects.filter(start_date__lt=end_date_obj, end_date__gt=start_date_obj)

Этот запрос полезен, так как проверяет пересечения на самом языке базы данных, что делает вашу систему более надежной.

5. Вывод

Теперь, когда вы внедрили проверки как на клиенте, так и на сервере, ваше приложение должно корректно обрабатывать ситуации наложения событий. Это улучшит пользовательский опыт, а также избежать проблем с пересечениями, которые могут привести к недоразумениям при бронировании.

Надеюсь, этот процесс поможет вам быстро устранить проблему и сделать вашу систему бронирования более эффективной и надежной. Если у вас возникнут дополнительные вопросы или потребуется больше примеров, пожалуйста, не стесняйтесь обращаться за помощью.

Оцените материал
Добавить комментарий

Капча загружается...