CalendarBuilder.java
/*
* Copyright (c) 2002-2022, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.plugins.appointment.service;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
import fr.paris.lutece.plugins.appointment.business.slot.Period;
import fr.paris.lutece.plugins.appointment.business.slot.Slot;
public class CalendarBuilder
{
/**
* Private constructor
*/
private CalendarBuilder( )
{
}
/**
* Build all the slot for a period with all the rules (open hours ...) to apply on each day, for each slot
*
* @param nIdForm
* the form Id
* @param mapReservationRule
* the map of the rule week definition
* @param startingDate
* the starting date of the period
* @param endingDate
* the ending date of the periode
* @returna list of all the slots built
*/
public static List<Slot> buildListSlot( int nIdForm, Map<WeekDefinition, ReservationRule> mapReservationRule, LocalDate startingDate, LocalDate endingDate )
{
List<Slot> listSlot = new ArrayList<>( );
final List<WeekDefinition> listDateReservationRule = new ArrayList<>( mapReservationRule.keySet( ) );
WeekDefinition closestweeDef;
ReservationRule reservationRuleToApply = null;
LocalDate dateTemp = startingDate;
int nMaxCapacity;
DayOfWeek dayOfWeek;
WorkingDay workingDay;
LocalTime minTimeForThisDay;
LocalTime maxTimeForThisDay;
LocalTime timeTemp;
LocalDateTime dateTimeTemp;
Slot slotToAdd;
TimeSlot timeSlot;
LocalDate dateToCompare;
// Need to check if this date is not before the form date creation
WeekDefinition firsWeek = listDateReservationRule.stream( ).sorted( ( week1, week2 ) -> week1.getDateOfApply( ).compareTo( week2.getDateOfApply( ) ) )
.findFirst( ).orElse( null );
final LocalDate firstDateOfReservationRule = firsWeek.getDateOfApply( );
LocalDate startingDateToUse = startingDate;
if ( firstDateOfReservationRule != null && startingDate.isBefore( firstDateOfReservationRule ) )
{
startingDateToUse = firstDateOfReservationRule;
}
// Get all the closing day of this period
List<LocalDate> listDateOfClosingDay = ClosingDayService.findListDateOfClosingDayByIdFormAndDateRange( nIdForm, startingDateToUse, endingDate );
// Get all the slot between these two dates
Map<LocalDateTime, Slot> mapSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( nIdForm, startingDateToUse.atStartOfDay( ),
endingDate.atTime( LocalTime.MAX ) );
// Get or build all the event for the period
while ( !dateTemp.isAfter( endingDate ) )
{
dateToCompare = dateTemp;
// Find the closest date of apply of reservation rule with the given
// date
reservationRuleToApply = null;
closestweeDef = Utilities.getClosestWeekDefinitionInPast( listDateReservationRule, dateToCompare );
if ( closestweeDef != null )
{
reservationRuleToApply = mapReservationRule.get( closestweeDef );
}
nMaxCapacity = 0;
// Get the day of week of the date
dayOfWeek = dateTemp.getDayOfWeek( );
// Get the working day of this day of week
workingDay = null;
if ( reservationRuleToApply != null )
{
nMaxCapacity = reservationRuleToApply.getMaxCapacityPerSlot( );
workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( reservationRuleToApply.getListWorkingDay( ), dayOfWeek );
}
if ( workingDay != null )
{
minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAWorkingDay( workingDay );
maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
// Check if this day is a closing day
if ( listDateOfClosingDay.contains( dateTemp ) )
{
listSlot.add( SlotService.buildSlot( nIdForm, new Period( dateTemp.atTime( minTimeForThisDay ), dateTemp.atTime( maxTimeForThisDay ) ),
nMaxCapacity, nMaxCapacity, nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE ) );
}
else
{
timeTemp = minTimeForThisDay;
// For each slot of this day
while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
{
// Get the LocalDateTime
dateTimeTemp = dateTemp.atTime( timeTemp );
// Search if there is a slot for this datetime
if ( mapSlot.containsKey( dateTimeTemp ) )
{
slotToAdd = mapSlot.get( dateTimeTemp );
timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
listSlot.add( slotToAdd );
}
else
{
// Search the timeslot
timeSlot = TimeSlotService.getTimeSlotInListOfTimeSlotWithStartingTime( workingDay.getListTimeSlot( ), timeTemp );
if ( timeSlot != null )
{
timeTemp = timeSlot.getEndingTime( );
int nMaxCapacityToPut = timeSlot.getMaxCapacity( );
slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacityToPut,
nMaxCapacityToPut, nMaxCapacityToPut, 0, timeSlot.getIsOpen( ), Boolean.FALSE );
listSlot.add( slotToAdd );
}
else
{
break;
}
}
}
}
}
else
{
// This is not a working day
// We build all the slots closed for this day
if ( reservationRuleToApply != null )
{
minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAListOfWorkingDay( reservationRuleToApply.getListWorkingDay( ) );
maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( reservationRuleToApply.getListWorkingDay( ) );
int nDuration = reservationRuleToApply.getDurationAppointments( );
if ( minTimeForThisDay != null && maxTimeForThisDay != null )
{
timeTemp = minTimeForThisDay;
// For each slot of this day
while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
{
// Get the LocalDateTime
dateTimeTemp = dateTemp.atTime( timeTemp );
// Search if there is a slot for this datetime
if ( mapSlot.containsKey( dateTimeTemp ) )
{
slotToAdd = mapSlot.get( dateTimeTemp );
timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
listSlot.add( slotToAdd );
}
else
{
timeTemp = timeTemp.plusMinutes( nDuration );
if ( timeTemp.isAfter( maxTimeForThisDay ) )
{
timeTemp = maxTimeForThisDay;
}
slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacity, nMaxCapacity,
nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE );
listSlot.add( slotToAdd );
}
}
}
}
}
dateTemp = dateTemp.plusDays( 1 );
}
return listSlot;
}
/**
* Build all the slot for a period with all the rules (open hours ...) to apply on each day, for each slot
*
* @param nIdForm
* the form Id
* @param mapReservationRule
* the map of the rule week definition
* @param startingDate
* the starting date of the period
* @param endingDate
* the ending date of the periode
* @param nNbPlaces
* the number of place to take
* @param isAllOpenSlot
* build slots with the all open slot
* @returna list of all the slots built
*/
public static List<Slot> buildListSlot( int nIdForm, Map<WeekDefinition, ReservationRule> mapReservationRule, LocalDate startingDate, LocalDate endingDate,
int nNbPlaces, boolean isAllOpenSlot )
{
List<Slot> listSlotToShow = new ArrayList<>( );
final List<WeekDefinition> listDateReservationRule = new ArrayList<>( mapReservationRule.keySet( ) );
WeekDefinition closestweeDef;
ReservationRule reservationRuleToApply = null;
LocalDate dateTemp = startingDate;
DayOfWeek dayOfWeek;
WorkingDay workingDay;
LocalTime minTimeForThisDay;
LocalTime maxTimeForThisDay;
LocalTime timeTemp;
LocalDateTime dateTimeTemp;
LocalDateTime endingDateTime;
LocalDateTime startingDateTime = null;
LocalTime tempEndingDateTime = null;
LocalDateTime localDateTimeNow = LocalDateTime.now( );
boolean isChanged = true;
boolean isfull = false;
int sumNbPotentialRemainingPlaces;
int sumNbRemainingPlaces;
int nbSlot;
Slot slotToAdd;
TimeSlot timeSlot;
LocalDate dateToCompare;
// Need to check if this date is not before the form date creation
WeekDefinition firsWeek = listDateReservationRule.stream( ).sorted( ( week1, week2 ) -> week1.getDateOfApply( ).compareTo( week2.getDateOfApply( ) ) )
.findFirst( ).orElse( null );
final LocalDate firstDateOfReservationRule = firsWeek.getDateOfApply( );
LocalDate startingDateToUse = startingDate;
if ( firstDateOfReservationRule != null && startingDate.isBefore( firstDateOfReservationRule ) )
{
startingDateToUse = firstDateOfReservationRule;
}
// Get all the closing day of this period
List<LocalDate> listDateOfClosingDay = ClosingDayService.findListDateOfClosingDayByIdFormAndDateRange( nIdForm, startingDateToUse, endingDate );
// Get all the slot between these two dates
Map<LocalDateTime, Slot> mapSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( nIdForm, startingDateToUse.atStartOfDay( ),
endingDate.atTime( LocalTime.MAX ) );
// Get or build all the event for the period
while ( !dateTemp.isAfter( endingDate ) )
{
dateToCompare = dateTemp;
// Find the closest date of apply of reservation rule with the given
// date
reservationRuleToApply = null;
closestweeDef = Utilities.getClosestWeekDefinitionInPast( listDateReservationRule, dateToCompare );
if ( closestweeDef != null )
{
reservationRuleToApply = mapReservationRule.get( closestweeDef );
}
// Get the day of week of the date
dayOfWeek = dateTemp.getDayOfWeek( );
// Get the working day of this day of week
workingDay = null;
if ( reservationRuleToApply != null )
{
workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( reservationRuleToApply.getListWorkingDay( ), dayOfWeek );
}
if ( workingDay != null )
{
minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAWorkingDay( workingDay );
maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
// Check if this day is a closing day
if ( !listDateOfClosingDay.contains( dateTemp ) )
{
timeTemp = minTimeForThisDay;
sumNbPotentialRemainingPlaces = 0;
sumNbRemainingPlaces = 0;
nbSlot = 0;
isChanged = true;
tempEndingDateTime = timeTemp;
// For each slot of this day
while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
{
// Get the LocalDateTime
dateTimeTemp = dateTemp.atTime( timeTemp );
// Search if there is a slot for this datetime
if ( isChanged )
{
startingDateTime = dateTimeTemp;
isChanged = false;
}
if ( mapSlot.containsKey( dateTimeTemp ) )
{
slotToAdd = mapSlot.get( dateTimeTemp );
timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
}
else
{
// Search the timeslot
timeSlot = TimeSlotService.getTimeSlotInListOfTimeSlotWithStartingTime( workingDay.getListTimeSlot( ), timeTemp );
if ( timeSlot != null )
{
timeTemp = timeSlot.getEndingTime( );
int nMaxCapacityToPut = timeSlot.getMaxCapacity( );
slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacityToPut,
nMaxCapacityToPut, nMaxCapacityToPut, 0, timeSlot.getIsOpen( ), Boolean.FALSE );
}
else
{
break;
}
}
if ( isNewSlot( sumNbPotentialRemainingPlaces, nNbPlaces, slotToAdd, localDateTimeNow, isAllOpenSlot, nbSlot ) )
{
sumNbPotentialRemainingPlaces = 0;
nbSlot = 0;
sumNbRemainingPlaces = 0;
startingDateTime = slotToAdd.getEndingDateTime( );
tempEndingDateTime = slotToAdd.getEndingTime( );
}
else
{
if ( slotToAdd.getNbPotentialRemainingPlaces( ) <= 0 )
{
isfull = true;
}
sumNbPotentialRemainingPlaces = sumNbPotentialRemainingPlaces + 1;
nbSlot = nbSlot + 1;
sumNbRemainingPlaces = sumNbRemainingPlaces + 1;
}
if ( buildNewSlot( sumNbPotentialRemainingPlaces, nNbPlaces, isAllOpenSlot, nbSlot ) )
{
endingDateTime = slotToAdd.getEndingDateTime( );
Slot slt = new Slot( );
slt.setStartingDateTime( startingDateTime );
slt.setEndingDateTime( endingDateTime );
slt.setIsOpen( true );
slt.setNbPotentialRemainingPlaces( sumNbPotentialRemainingPlaces );
slt.setNbRemainingPlaces( sumNbRemainingPlaces );
slt.setDate( slotToAdd.getDate( ) );
slt.setIdForm( slotToAdd.getIdForm( ) );
slt.setIsFull( isfull ? 1 : 0 );
listSlotToShow.add( slt );
isChanged = true;
isfull = false;
timeTemp = tempEndingDateTime;
}
}
}
}
dateTemp = dateTemp.plusDays( 1 );
}
return listSlotToShow;
}
private static boolean isNewSlot( int sumNbPotentialRemainingPlaces, int nNbPlaces, Slot slotToAdd, LocalDateTime localDateTimeNow, boolean isAllOpenSlot,
int nbSlot )
{
if ( isAllOpenSlot )
{
return nbSlot == nNbPlaces || !slotToAdd.getIsOpen( ) || slotToAdd.getEndingDateTime( ).isBefore( localDateTimeNow );
}
return sumNbPotentialRemainingPlaces >= nNbPlaces || !slotToAdd.getIsOpen( ) || slotToAdd.getNbPotentialRemainingPlaces( ) <= 0
|| slotToAdd.getEndingDateTime( ).isBefore( localDateTimeNow );
}
private static boolean buildNewSlot( int sumNbPotentialRemainingPlaces, int nNbPlaces, boolean isAllOpenSlot, int nbSlot )
{
if ( isAllOpenSlot )
{
return nbSlot == nNbPlaces;
}
return sumNbPotentialRemainingPlaces >= nNbPlaces;
}
}