View Javadoc
1   /*
2    * Copyright (c) 2002-2018, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.appointment.service;
35  
36  import java.time.DayOfWeek;
37  import java.time.LocalDate;
38  import java.time.LocalDateTime;
39  import java.time.LocalTime;
40  import java.time.temporal.ChronoUnit;
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.stream.Collectors;
46  
47  import org.apache.commons.collections.CollectionUtils;
48  
49  import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
50  import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
51  import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
52  import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
53  import fr.paris.lutece.plugins.appointment.business.slot.Period;
54  import fr.paris.lutece.plugins.appointment.business.slot.Slot;
55  import fr.paris.lutece.plugins.appointment.business.slot.SlotHome;
56  import fr.paris.lutece.plugins.appointment.service.listeners.SlotListenerManager;
57  
58  /**
59   * Service class of a slot
60   * 
61   * @author Laurent Payen
62   *
63   */
64  public final class SlotService
65  {
66  
67      /**
68       * Private constructor - this class does not need to be instantiated
69       */
70      private SlotService( )
71      {
72      }
73  
74      /**
75       * Find slots of a form on a given period of time
76       * 
77       * @param nIdForm
78       *            the form Id
79       * @param startingDateTime
80       *            the starting date time to search
81       * @param endingDateTime
82       *            the ending date time to search
83       * @return a list of the slots found
84       */
85      public static List<Slot> findSlotsByIdFormAndDateRange( int nIdForm, LocalDateTime startingDateTime, LocalDateTime endingDateTime )
86      {
87          List<Slot> listSlots = SlotHome.findByIdFormAndDateRange( nIdForm, startingDateTime, endingDateTime );
88          for ( Slot slot : listSlots )
89          {
90              addDateAndTimeToSlot( slot );
91          }
92          return listSlots;
93      }
94  
95      /**
96       * Find specific slots of a form
97       * 
98       * @param nIdForm
99       *            the form Id
100      * @return a list of the slots found
101      */
102     public static List<Slot> findSpecificSlotsByIdForm( int nIdForm )
103     {
104         List<Slot> listSpecificSlots = SlotHome.findIsSpecificByIdForm( nIdForm );
105         for ( Slot slot : listSpecificSlots )
106         {
107             addDateAndTimeToSlot( slot );
108         }
109         return listSpecificSlots;
110     }
111 
112     /**
113      * Build a map (Date, Slot) of all the slots found between the two dates
114      * 
115      * @param nIdForm
116      *            the form id
117      * @param startingDateTime
118      *            the starting date time
119      * @param endingDateTime
120      *            the ending date time
121      * @return the map
122      */
123     public static HashMap<LocalDateTime, Slot> buildMapSlotsByIdFormAndDateRangeWithDateForKey( int nIdForm, LocalDateTime startingDateTime,
124             LocalDateTime endingDateTime )
125     {
126         HashMap<LocalDateTime, Slot> mapSlots = new HashMap<>( );
127         for ( Slot slot : findSlotsByIdFormAndDateRange( nIdForm, startingDateTime, endingDateTime ) )
128         {
129             mapSlots.put( slot.getStartingDateTime( ), slot );
130         }
131         return mapSlots;
132     }
133 
134     /**
135      * Fins all the slots of a form
136      * 
137      * @param nIdForm
138      *            the form id
139      * @return a list of all the slots of a form
140      */
141     public static List<Slot> findListSlot( int nIdForm )
142     {
143         return SlotHome.findByIdForm( nIdForm );
144     }
145 
146     /**
147      * Find the open slots of a form on a given period of time
148      * 
149      * @param nIdForm
150      *            the form Id
151      * @param startingDateTime
152      *            the starting Date time to search
153      * @param endingDateTime
154      *            the ending Date time to search
155      * @return a list of open slots whose matches the criteria
156      */
157     public static List<Slot> findListOpenSlotByIdFormAndDateRange( int nIdForm, LocalDateTime startingDateTime, LocalDateTime endingDateTime )
158     {
159         return SlotHome.findOpenSlotsByIdFormAndDateRange( nIdForm, startingDateTime, endingDateTime );
160     }
161 
162     /**
163      * Find a slot with its primary key
164      * 
165      * @param nIdSlot
166      *            the slot Id
167      * @return the Slot object
168      */
169     public static Slot findSlotById( int nIdSlot )
170     {
171         Slot slot = SlotHome.findByPrimaryKey( nIdSlot );
172         if ( slot != null )
173         {
174             SlotService.addDateAndTimeToSlot( slot );
175         }
176         return slot;
177     }
178 
179     /**
180      * Build all the slot for a period with all the rules (open hours ...) to apply on each day, for each slot
181      * 
182      * @param nIdForm
183      *            the form Id
184      * @param mapWeekDefinition
185      *            the map of the week definition
186      * @param startingDate
187      *            the starting date of the period
188      * @param nNbWeeksToDisplay
189      *            the number of weeks to build
190      * @return a list of all the slots built
191      */
192     public static List<Slot> buildListSlot( int nIdForm, HashMap<LocalDate, WeekDefinition> mapWeekDefinition, LocalDate startingDate, LocalDate endingDate )
193     {
194         List<Slot> listSlot = new ArrayList<>( );
195         // Get all the reservation rules
196         final HashMap<LocalDate, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm );
197         final List<LocalDate> listDateWeekDefinition = new ArrayList<>( mapWeekDefinition.keySet( ) );
198         final List<LocalDate> listDateReservationTule = new ArrayList<>( mapReservationRule.keySet( ) );
199         LocalDate closestDateWeekDefinition;
200         LocalDate closestDateReservationRule;
201         WeekDefinition weekDefinitionToApply;
202         ReservationRule reservationRuleToApply;
203         LocalDate dateTemp = startingDate;
204         int nMaxCapacity;
205         DayOfWeek dayOfWeek;
206         WorkingDay workingDay;
207         LocalTime minTimeForThisDay;
208         LocalTime maxTimeForThisDay;
209         LocalTime timeTemp;
210         LocalDateTime dateTimeTemp;
211         Slot slotToAdd;
212         TimeSlot timeSlot;
213         LocalDate dateToCompare;
214         // Need to check if this date is not before the form date creation
215         final LocalDate firstDateOfReservationRule = new ArrayList<>( mapReservationRule.keySet( ) ).stream( ).sorted( ).findFirst( ).orElse( null );
216         LocalDate startingDateToUse = startingDate;
217         if ( firstDateOfReservationRule != null && startingDate.isBefore( firstDateOfReservationRule ) )
218         {
219             startingDateToUse = firstDateOfReservationRule;
220         }
221         // Get all the closing day of this period
222         List<LocalDate> listDateOfClosingDay = ClosingDayService.findListDateOfClosingDayByIdFormAndDateRange( nIdForm, startingDateToUse, endingDate );
223         // Get all the slot between these two dates
224         HashMap<LocalDateTime, Slot> mapSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( nIdForm, startingDateToUse.atStartOfDay( ),
225                 endingDate.atTime( LocalTime.MAX ) );
226 
227         // Get or build all the event for the period
228         while ( !dateTemp.isAfter( endingDate ) )
229         {
230             dateToCompare = dateTemp;
231             // Find the closest date of apply of week definition with the given
232             // date
233             closestDateWeekDefinition = Utilities.getClosestDateInPast( listDateWeekDefinition, dateToCompare );
234             weekDefinitionToApply = mapWeekDefinition.get( closestDateWeekDefinition );
235             // Find the closest date of apply of reservation rule with the given
236             // date
237             closestDateReservationRule = Utilities.getClosestDateInPast( listDateReservationTule, dateToCompare );
238             reservationRuleToApply = mapReservationRule.get( closestDateReservationRule );
239             nMaxCapacity = 0;
240             if ( reservationRuleToApply != null )
241             {
242                 nMaxCapacity = reservationRuleToApply.getMaxCapacityPerSlot( );
243             }
244             // Get the day of week of the date
245             dayOfWeek = dateTemp.getDayOfWeek( );
246             // Get the working day of this day of week
247             workingDay = null;
248             if ( weekDefinitionToApply != null )
249             {
250                 workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( weekDefinitionToApply.getListWorkingDay( ), dayOfWeek );
251             }
252             if ( workingDay != null )
253             {
254                 minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAWorkingDay( workingDay );
255                 maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
256                 // Check if this day is a closing day
257                 if ( listDateOfClosingDay.contains( dateTemp ) )
258                 {
259                     listSlot.add( buildSlot( nIdForm, new Period( dateTemp.atTime( minTimeForThisDay ), dateTemp.atTime( maxTimeForThisDay ) ), nMaxCapacity,
260                             nMaxCapacity, nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE ) );
261                 }
262                 else
263                 {
264                     timeTemp = minTimeForThisDay;
265                     // For each slot of this day
266                     while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
267                     {
268                         // Get the LocalDateTime
269                         dateTimeTemp = dateTemp.atTime( timeTemp );
270                         // Search if there is a slot for this datetime
271                         if ( mapSlot.containsKey( dateTimeTemp ) )
272                         {
273                             slotToAdd = mapSlot.get( dateTimeTemp );
274                             timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
275                             listSlot.add( slotToAdd );
276                         }
277                         else
278                         {
279                             // Search the timeslot
280                             timeSlot = TimeSlotService.getTimeSlotInListOfTimeSlotWithStartingTime( workingDay.getListTimeSlot( ), timeTemp );
281                             if ( timeSlot != null )
282                             {
283                                 timeTemp = timeSlot.getEndingTime( );
284                                 int nMaxCapacityToPut = nMaxCapacity;
285                                 if ( timeSlot.getMaxCapacity( ) != 0 )
286                                 {
287                                     nMaxCapacityToPut = timeSlot.getMaxCapacity( );
288                                 }
289                                 slotToAdd = buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacityToPut, nMaxCapacityToPut,
290                                         nMaxCapacityToPut, 0, timeSlot.getIsOpen( ), Boolean.FALSE );
291                                 listSlot.add( slotToAdd );
292                             }
293                             else
294                             {
295                                 break;
296                             }
297                         }
298                     }
299                 }
300             }
301             else
302             {
303                 // This is not a working day
304                 // We build all the slots closed for this day
305                 if ( reservationRuleToApply != null && weekDefinitionToApply != null )
306                 {
307                     minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAListOfWorkingDay( weekDefinitionToApply.getListWorkingDay( ) );
308                     maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( weekDefinitionToApply.getListWorkingDay( ) );
309                     int nDuration = WorkingDayService.getMinDurationTimeSlotOfAListOfWorkingDay( weekDefinitionToApply.getListWorkingDay( ) );
310                     if ( minTimeForThisDay != null && maxTimeForThisDay != null )
311                     {
312                         timeTemp = minTimeForThisDay;
313                         // For each slot of this day
314                         while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
315                         {
316                             // Get the LocalDateTime
317                             dateTimeTemp = dateTemp.atTime( timeTemp );
318                             // Search if there is a slot for this datetime
319                             if ( mapSlot.containsKey( dateTimeTemp ) )
320                             {
321                                 slotToAdd = mapSlot.get( dateTimeTemp );
322                                 timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
323                                 listSlot.add( slotToAdd );
324                             }
325                             else
326                             {
327                                 timeTemp = timeTemp.plusMinutes( Long.valueOf( nDuration ) );
328                                 if ( timeTemp.isAfter( maxTimeForThisDay ) )
329                                 {
330                                     timeTemp = maxTimeForThisDay;
331                                 }
332                                 slotToAdd = buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacity, nMaxCapacity,
333                                         nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE );
334                                 listSlot.add( slotToAdd );
335                             }
336                         }
337                     }
338                 }
339             }
340             dateTemp = dateTemp.plusDays( 1 );
341         }
342         return listSlot;
343 
344     }
345 
346     /**
347      * Build a slot with all its values
348      * 
349      * @param nIdForm
350      *            the form Id
351      * @param startingDateTime
352      *            the starting date time
353      * @param endingDateTime
354      *            the ending date time
355      * @param nMaxCapacity
356      *            the maximum capacity for the slot
357      * @param nNbRemainingPlaces
358      *            the number of remaining places of the slot
359      * @param bIsOpen
360      *            true if the slot is open
361      * @return the slot built
362      */
363     public static Slot buildSlot( int nIdForm, Period period, int nMaxCapacity, int nNbRemainingPlaces, int nNbPotentialRemainingPlaces, int nNbPlacesTaken,
364             boolean bIsOpen, boolean bIsSpecific )
365     {
366         Slot slot = new Slot( );
367         slot.setIdSlot( 0 );
368         slot.setIdForm( nIdForm );
369         slot.setStartingDateTime( period.getStartingDateTime( ) );
370         slot.setEndingDateTime( period.getEndingDateTime( ) );
371         slot.setMaxCapacity( nMaxCapacity );
372         slot.setNbRemainingPlaces( nNbRemainingPlaces );
373         slot.setNbPotentialRemainingPlaces( nNbPotentialRemainingPlaces );
374         slot.setNbPlacestaken( nNbPlacesTaken );
375         slot.setIsOpen( bIsOpen );
376         slot.setIsSpecific( bIsSpecific );
377         addDateAndTimeToSlot( slot );
378         return slot;
379     }
380 
381     /**
382      * To know if it's a specific slot, need to search for a similar time slot
383      * 
384      * @param slot
385      *            the slot
386      * @return true if specific
387      */
388     private static boolean isSpecificSlot( Slot slot )
389     {
390         LocalDate dateOfSlot = slot.getDate( );
391         WeekDefinition weekDefinition = WeekDefinitionService.findWeekDefinitionByIdFormAndClosestToDateOfApply( slot.getIdForm( ), dateOfSlot );
392         ReservationRule reservationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( slot.getIdForm( ), slot.getDate( ) );
393         WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( weekDefinition.getListWorkingDay( ), dateOfSlot.getDayOfWeek( ) );
394         List<TimeSlot> listTimeSlot = null;
395         if ( workingDay != null )
396         {
397             listTimeSlot = TimeSlotService.findListTimeSlotByWorkingDay( workingDay.getIdWorkingDay( ) );
398         }
399         return isSpecificSlot( slot, workingDay, listTimeSlot, reservationRule.getMaxCapacityPerSlot( ) );
400     }
401 
402     /**
403      * To know if it's a specific slot, need to search for a similar time slot
404      * 
405      * @param slot
406      *            the slot
407      * @param workingDay
408      *            the working day
409      * @param listTimeSlot
410      *            the list of time slots
411      * @return true if it's a specific slot
412      */
413     private static boolean isSpecificSlot( Slot slot, WorkingDay workingDay, List<TimeSlot> listTimeSlot, int nMaxCapacity )
414     {
415         boolean bIsSpecific = Boolean.TRUE;
416         List<TimeSlot> listMatchTimeSlot = null;
417         if ( workingDay == null )
418         {
419             if ( !slot.getIsOpen( ) && slot.getMaxCapacity( ) == nMaxCapacity )
420             {
421                 bIsSpecific = Boolean.FALSE;
422             }
423         }
424         else
425         {
426             listMatchTimeSlot = listTimeSlot
427                     .stream( )
428                     .filter(
429                             t -> ( t.getStartingTime( ).equals( slot.getStartingDateTime( ).toLocalTime( ) ) )
430                                     && ( t.getEndingTime( ).equals( slot.getEndingDateTime( ).toLocalTime( ) ) ) && ( t.getIsOpen( ) == slot.getIsOpen( ) )
431                                     && ( t.getMaxCapacity( ) == slot.getMaxCapacity( ) ) ).collect( Collectors.toList( ) );
432             if ( CollectionUtils.isNotEmpty( listMatchTimeSlot ) )
433             {
434                 bIsSpecific = Boolean.FALSE;
435             }
436         }
437         return bIsSpecific;
438     }
439 
440     /**
441      * Update a slot in database and possibly all the slots after (if the ending hour has changed, all the next slots are impacted in case of the user decide to
442      * shift the next slots)
443      * 
444      * @param slot
445      *            the slot to update
446      * @param bEndingTimeHasChanged
447      *            true if the ending time has changed
448      * @param previousEndingTime
449      *            the previous ending time
450      * @param bShifSlot
451      *            true if the user has decided to shift the next slots
452      */
453     public static void updateSlot( Slot slot, boolean bEndingTimeHasChanged, LocalTime previousEndingTime, boolean bShifSlot )
454     {
455         slot.setIsSpecific( isSpecificSlot( slot ) );
456         // If the ending time of the slot has changed
457         if ( bEndingTimeHasChanged )
458         {
459             // If we don't want to shift the next slots
460             if ( !bShifSlot )
461             {
462                 updateSlotWithoutShift( slot );
463             }
464             else
465             {
466                 // We want to shift the next slots at the end of the current
467                 // slot
468                 updateSlotWithShift( slot, previousEndingTime );
469             }
470         }
471         else
472         {
473             // The ending time of the slot has not changed
474             // If it's an update of an existing slot
475             if ( slot.getIdSlot( ) != 0 )
476             {
477                 updateRemainingPlaces( slot );
478             }
479             saveSlot( slot );
480         }
481 
482     }
483 
484     /**
485      * Update the current slot and don't shift the next slots
486      * 
487      * @param slot
488      *            the current slot
489      */
490     private static void updateSlotWithoutShift( Slot slot )
491     {
492         List<Slot> listSlotToCreate = new ArrayList<>( );
493         // Need to get all the slots until the new end of this slot
494         List<Slot> listSlotToDelete = SlotService.findSlotsByIdFormAndDateRange( slot.getIdForm( ), slot.getStartingDateTime( ).plusMinutes( 1 ),
495                 slot.getEndingDateTime( ) );
496         deleteListSlots( listSlotToDelete );
497         // Get the list of slot after the modified slot
498         HashMap<LocalDateTime, Slot> mapNextSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( slot.getIdForm( ), slot.getEndingDateTime( ),
499                 slot.getDate( ).atTime( LocalTime.MAX ) );
500         List<LocalDateTime> listStartingDateTimeNextSlot = new ArrayList<>( mapNextSlot.keySet( ) );
501         // Get the next date time slot
502         LocalDateTime nextStartingDateTime = null;
503         if ( CollectionUtils.isNotEmpty( listStartingDateTimeNextSlot ) )
504         {
505             nextStartingDateTime = Utilities.getClosestDateTimeInFuture( listStartingDateTimeNextSlot, slot.getEndingDateTime( ) );
506         }
507         else
508         {
509             LocalDate dateOfSlot = slot.getDate( );
510             WeekDefinition weekDefinition = WeekDefinitionService.findWeekDefinitionByIdFormAndClosestToDateOfApply( slot.getIdForm( ), dateOfSlot );
511             WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( weekDefinition.getListWorkingDay( ), dateOfSlot.getDayOfWeek( ) );
512             // No slot after this one.
513             // Need to compute between the end of this slot and the next
514             // time slot
515             if ( workingDay != null )
516             {
517                 List<TimeSlot> nextTimeSlots = TimeSlotService.getNextTimeSlotsInAListOfTimeSlotAfterALocalTime( workingDay.getListTimeSlot( ),
518                         slot.getEndingTime( ) );
519                 TimeSlot nextTimeSlot = null;
520                 if ( CollectionUtils.isNotEmpty( nextTimeSlots ) )
521                 {
522                     nextTimeSlot = nextTimeSlots.stream( ).min( ( t1, t2 ) -> t1.getStartingTime( ).compareTo( t2.getStartingTime( ) ) ).get( );
523                 }
524                 if ( nextTimeSlot != null )
525                 {
526                     nextStartingDateTime = nextTimeSlot.getStartingTime( ).atDate( dateOfSlot );
527                 }
528             }
529             else
530             {
531                 // This is not a working day
532                 // Generated the new slots at the end of the modified
533                 // slot
534                 listSlotToCreate.addAll( generateListSlotToCreateAfterATime( slot.getEndingDateTime( ), slot.getIdForm( ) ) );
535             }
536         }
537         // Need to create a slot between these two dateTime
538         if ( nextStartingDateTime != null && !slot.getEndingDateTime( ).isEqual( nextStartingDateTime ) )
539         {
540             Slot slotToCreate = buildSlot( slot.getIdForm( ), new Period( slot.getEndingDateTime( ), nextStartingDateTime ), slot.getMaxCapacity( ),
541                     slot.getMaxCapacity( ), slot.getMaxCapacity( ), 0, Boolean.FALSE, Boolean.TRUE );
542             listSlotToCreate.add( slotToCreate );
543         }
544         // If it's an update of an existing slot
545         if ( slot.getIdSlot( ) != 0 )
546         {
547             updateRemainingPlaces( slot );
548         }
549         saveSlot( slot );
550         createListSlot( listSlotToCreate );
551     }
552 
553     /**
554      * update the current slot and shift the next slots at the end of the current slot
555      * 
556      * @param slot
557      *            the current slot
558      * @param previousEndingTime
559      *            the previous ending time of the current slot
560      */
561     private static void updateSlotWithShift( Slot slot, LocalTime previousEndingTime )
562     {
563         // We want to shift all the next slots
564         LocalDate dateOfSlot = slot.getDate( );
565         HashMap<LocalDate, WeekDefinition> mapWeekDefinition = WeekDefinitionService.findAllWeekDefinition( slot.getIdForm( ) );
566         // Build or get all the slots of the day
567         List<Slot> listAllSlotsOfThisDayToBuildOrInDb = buildListSlot( slot.getIdForm( ), mapWeekDefinition, dateOfSlot, dateOfSlot );
568         // Remove the current slot and all the slot before it
569         listAllSlotsOfThisDayToBuildOrInDb = listAllSlotsOfThisDayToBuildOrInDb.stream( )
570                 .filter( slotToKeep -> slotToKeep.getStartingDateTime( ).isAfter( slot.getStartingDateTime( ) ) ).collect( Collectors.toList( ) );
571         // Need to delete all the slots until the new end of this slot
572         List<Slot> listSlotToDelete = listAllSlotsOfThisDayToBuildOrInDb
573                 .stream( )
574                 .filter(
575                         slotToDelete -> slotToDelete.getStartingDateTime( ).isAfter( slot.getStartingDateTime( ) )
576                                 && !slotToDelete.getEndingDateTime( ).isAfter( slot.getEndingDateTime( ) ) && slotToDelete.getIdSlot( ) != 0 )
577                 .collect( Collectors.toList( ) );
578         deleteListSlots( listSlotToDelete );
579         listAllSlotsOfThisDayToBuildOrInDb.removeAll( listSlotToDelete );
580         // Need to find all the existing slots
581         List<Slot> listExistingSlots = listAllSlotsOfThisDayToBuildOrInDb.stream( ).filter( existingSlot -> existingSlot.getIdSlot( ) != 0 )
582                 .collect( Collectors.toList( ) );
583         // Remove them from the list of slot to build
584         listAllSlotsOfThisDayToBuildOrInDb.removeAll( listExistingSlots );
585         // Save this list
586         createListSlot( listAllSlotsOfThisDayToBuildOrInDb );
587         List<Slot> listSlotToShift = new ArrayList<>( );
588         listSlotToShift.addAll( listExistingSlots );
589         listSlotToShift.addAll( listAllSlotsOfThisDayToBuildOrInDb );
590         // Need to order the list of slot to shift according to the shift
591         // if the new ending time is before the previous ending time,
592         // the list has to be ordered in chronological order ascending
593         // and the first slot to shift is the closest to the current
594         // slot
595         // (because we have an integrity constraint for the slot, it
596         // can't have the same starting or ending time as another slot
597         listSlotToShift = listSlotToShift.stream( ).sorted( ( slot1, slot2 ) -> slot1.getStartingDateTime( ).compareTo( slot2.getStartingDateTime( ) ) )
598                 .collect( Collectors.toList( ) );
599         boolean bNewEndingTimeIsAfterThePreviousTime = false;
600         // Need to know the ending time of the day
601         LocalDateTime endingDateTimeOfTheDay = null;
602         WeekDefinition weekDefinition = WeekDefinitionService.findWeekDefinitionByIdFormAndClosestToDateOfApply( slot.getIdForm( ), dateOfSlot );
603         WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( weekDefinition.getListWorkingDay( ), dateOfSlot.getDayOfWeek( ) );
604         LocalTime endingTimeOfTheDay;
605         if ( workingDay != null )
606         {
607             endingTimeOfTheDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
608         }
609         else
610         {
611             endingTimeOfTheDay = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( weekDefinition.getListWorkingDay( ) );
612         }
613         endingDateTimeOfTheDay = endingTimeOfTheDay.atDate( dateOfSlot );
614         long timeToAdd = 0;
615         long timeToSubstract = 0;
616         if ( previousEndingTime.isBefore( slot.getEndingTime( ) ) )
617         {
618             bNewEndingTimeIsAfterThePreviousTime = true;
619             // Need to find the next available slot, to know how to
620             // add to the starting time of the next slot to match
621             // with the new end of the current slot
622             if ( CollectionUtils.isNotEmpty( listSlotToShift ) )
623             {
624                 Slot nextSlot = listSlotToShift.stream( ).min( ( s1, s2 ) -> s1.getStartingDateTime( ).compareTo( s2.getStartingDateTime( ) ) ).get( );
625                 if ( slot.getEndingDateTime( ).isAfter( nextSlot.getStartingDateTime( ) ) )
626                 {
627                     timeToAdd = nextSlot.getStartingDateTime( ).until( slot.getEndingDateTime( ), ChronoUnit.MINUTES );
628                 }
629                 else
630                 {
631                     timeToAdd = slot.getEndingDateTime( ).until( nextSlot.getStartingDateTime( ), ChronoUnit.MINUTES );
632                 }
633                 Collections.reverse( listSlotToShift );
634             }
635             else
636             {
637                 timeToAdd = previousEndingTime.until( slot.getEndingTime( ), ChronoUnit.MINUTES );
638             }
639         }
640         else
641         {
642             timeToSubstract = slot.getEndingTime( ).until( previousEndingTime, ChronoUnit.MINUTES );
643         }
644         // If it's an update of an existing slot
645         if ( slot.getIdSlot( ) != 0 )
646         {
647             updateRemainingPlaces( slot );
648         }
649         saveSlot( slot );
650         // Need to set the new starting and ending time of all the slots
651         // to shift and update them
652         for ( Slot slotToShift : listSlotToShift )
653         {
654             // If the new ending time is after the previous time
655             if ( bNewEndingTimeIsAfterThePreviousTime )
656             {
657                 // If the starting time + the time to add is before the
658                 // ending time of the day
659                 if ( slotToShift.getStartingDateTime( ).plus( timeToAdd, ChronoUnit.MINUTES ).isBefore( endingDateTimeOfTheDay ) )
660                 {
661                     slotToShift.setStartingDateTime( slotToShift.getStartingDateTime( ).plus( timeToAdd, ChronoUnit.MINUTES ) );
662                     // if the ending time is after the ending time of
663                     // the day, we set the new ending time to the ending
664                     // time of the day
665                     if ( slotToShift.getEndingDateTime( ).plus( timeToAdd, ChronoUnit.MINUTES ).isAfter( endingDateTimeOfTheDay ) )
666                     {
667                         slotToShift.setEndingDateTime( endingDateTimeOfTheDay );
668                     }
669                     else
670                     {
671                         slotToShift.setEndingDateTime( slotToShift.getEndingDateTime( ).plus( timeToAdd, ChronoUnit.MINUTES ) );
672                     }
673                     slotToShift.setIsSpecific( isSpecificSlot( slotToShift ) );
674                     saveSlot( slotToShift );
675                 }
676                 else
677                 {
678                     // Delete this slot (the slot can not be after the
679                     // ending time of the day)
680                     deleteSlot( slotToShift );
681                 }
682             }
683             else
684             {
685                 // The new ending time is before the previous ending
686                 // time
687                 slotToShift.setStartingDateTime( slotToShift.getStartingDateTime( ).minus( timeToSubstract, ChronoUnit.MINUTES ) );
688                 slotToShift.setEndingDateTime( slotToShift.getEndingDateTime( ).minus( timeToSubstract, ChronoUnit.MINUTES ) );
689                 slotToShift.setIsSpecific( isSpecificSlot( slotToShift ) );
690                 saveSlot( slotToShift );
691             }
692         }
693         if ( !bNewEndingTimeIsAfterThePreviousTime )
694         {
695             // If the slots have been shift earlier,
696             // there is no slot(s) between the last slot created
697             // and the ending time of the day, need to create it(them)
698             List<Slot> listSlotsToAdd = generateListSlotToCreateAfterATime( endingDateTimeOfTheDay.minusMinutes( timeToSubstract ), slot.getIdForm( ) );
699             createListSlot( listSlotsToAdd );
700         }
701 
702     }
703 
704     /**
705      * Update the capacity of the slot
706      * 
707      * @param slot
708      *            the slot to update
709      */
710     public static void updateRemainingPlaces( Slot slot )
711     {
712         Slot oldSlot = SlotService.findSlotById( slot.getIdSlot( ) );
713         int nNewNbMaxCapacity = slot.getMaxCapacity( );
714         int nOldBnMaxCapacity = oldSlot.getMaxCapacity( );
715         // If the max capacity has been modified
716         if ( nNewNbMaxCapacity != nOldBnMaxCapacity )
717         {
718             // Need to update the remaining places
719 
720             // Need to add the diff between the old value and the new value
721             // to the remaining places (if the new is higher)
722             if ( nNewNbMaxCapacity > nOldBnMaxCapacity )
723             {
724                 int nValueToAdd = nNewNbMaxCapacity - nOldBnMaxCapacity;
725                 slot.setNbPotentialRemainingPlaces( oldSlot.getNbPotentialRemainingPlaces( ) + nValueToAdd );
726                 slot.setNbRemainingPlaces( oldSlot.getNbRemainingPlaces( ) + nValueToAdd );
727             }
728             else
729             {
730                 // the new value is lower than the previous capacity
731                 // !!!! If there are appointments on this slot and if the
732                 // slot is already full, the slot will be surbooked !!!!
733                 int nValueToSubstract = nOldBnMaxCapacity - nNewNbMaxCapacity;
734                 slot.setNbPotentialRemainingPlaces( Math.max( 0, oldSlot.getNbPotentialRemainingPlaces( ) - nValueToSubstract ) );
735                 slot.setNbRemainingPlaces( Math.max( 0, oldSlot.getNbRemainingPlaces( ) - nValueToSubstract ) );
736             }
737         }
738     }
739 
740     /**
741      * Save a slot in database
742      * 
743      * @param slot
744      *            the slot to save
745      * @return the slot saved
746      */
747     public static Slot saveSlot( Slot slot )
748     {
749         Slot slotSaved = null;
750         if ( slot.getIdSlot( ) == 0 )
751         {
752             slotSaved = SlotService.createSlot( slot );
753         }
754         else
755         {
756             slotSaved = SlotService.updateSlot( slot );
757         }
758         return slotSaved;
759     }
760 
761     /**
762      * Update a slot
763      * 
764      * @param slot
765      *            the slot updated
766      */
767     public static Slot updateSlot( Slot slot )
768     {
769         Slot slotToReturn = SlotHome.update( slot );
770         SlotListenerManager.notifyListenersSlotChange( slot.getIdSlot( ) );
771         return slotToReturn;
772     }
773 
774     /**
775      * Generate the list of slot to create after a slot (taking into account the week definition and the rules to apply)
776      * 
777      * @param slot
778      *            the slot
779      * @return the list of next slots
780      */
781     private static List<Slot> generateListSlotToCreateAfterATime( LocalDateTime dateTimeToStartCreation, int nIdForm )
782     {
783         List<Slot> listSlotToCreate = new ArrayList<>( );
784         LocalDate dateOfCreation = dateTimeToStartCreation.toLocalDate( );
785         ReservationRule reservationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( nIdForm, dateOfCreation );
786         int nMaxCapacity = reservationRule.getMaxCapacityPerSlot( );
787         WeekDefinition weekDefinition = WeekDefinitionService.findWeekDefinitionByIdFormAndClosestToDateOfApply( nIdForm, dateOfCreation );
788         WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( weekDefinition.getListWorkingDay( ), dateOfCreation.getDayOfWeek( ) );
789         LocalTime endingTimeOfTheDay = null;
790         List<TimeSlot> listTimeSlot = new ArrayList<>( );
791         int nDurationSlot = 0;
792         if ( workingDay != null )
793         {
794             endingTimeOfTheDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
795             nDurationSlot = WorkingDayService.getMinDurationTimeSlotOfAWorkingDay( workingDay );
796             listTimeSlot = TimeSlotService.findListTimeSlotByWorkingDay( workingDay.getIdWorkingDay( ) );
797         }
798         else
799         {
800             endingTimeOfTheDay = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( weekDefinition.getListWorkingDay( ) );
801             nDurationSlot = WorkingDayService.getMinDurationTimeSlotOfAListOfWorkingDay( weekDefinition.getListWorkingDay( ) );
802         }
803         LocalDateTime endingDateTimeOfTheDay = endingTimeOfTheDay.atDate( dateOfCreation );
804         LocalDateTime startingDateTime = dateTimeToStartCreation;
805         LocalDateTime endingDateTime = startingDateTime.plusMinutes( nDurationSlot );
806         while ( !endingDateTime.isAfter( endingDateTimeOfTheDay ) )
807         {
808             Slot slotToCreate = buildSlot( nIdForm, new Period( startingDateTime, endingDateTime ), nMaxCapacity, nMaxCapacity, nMaxCapacity, 0, Boolean.FALSE,
809                     Boolean.TRUE );
810             slotToCreate.setIsSpecific( isSpecificSlot( slotToCreate, workingDay, listTimeSlot, nMaxCapacity ) );
811             startingDateTime = endingDateTime;
812             endingDateTime = startingDateTime.plusMinutes( nDurationSlot );
813             listSlotToCreate.add( slotToCreate );
814         }
815         if ( startingDateTime.isBefore( endingDateTimeOfTheDay ) && endingDateTime.isAfter( endingDateTimeOfTheDay ) )
816         {
817             Slot slotToCreate = buildSlot( nIdForm, new Period( startingDateTime, endingDateTimeOfTheDay ), nMaxCapacity, nMaxCapacity, nMaxCapacity, 0,
818                     Boolean.FALSE, Boolean.TRUE );
819             slotToCreate.setIsSpecific( isSpecificSlot( slotToCreate, workingDay, listTimeSlot, nMaxCapacity ) );
820             listSlotToCreate.add( slotToCreate );
821         }
822         return listSlotToCreate;
823     }
824 
825     /**
826      * Form the DTO, adding the date and the time to the slot
827      * 
828      * @param slot
829      *            the slot on which to add values
830      */
831     public static void addDateAndTimeToSlot( Slot slot )
832     {
833         if ( slot.getStartingDateTime( ) != null )
834         {
835             slot.setDate( slot.getStartingDateTime( ).toLocalDate( ) );
836             slot.setStartingTime( slot.getStartingDateTime( ).toLocalTime( ) );
837         }
838         if ( slot.getEndingDateTime( ) != null )
839         {
840             slot.setEndingTime( slot.getEndingDateTime( ).toLocalTime( ) );
841         }
842     }
843 
844     /**
845      * Create in database the slots given
846      * 
847      * @param listSlotToCreate
848      *            the list of slots to create in database
849      */
850     private static void createListSlot( List<Slot> listSlotToCreate )
851     {
852         if ( CollectionUtils.isNotEmpty( listSlotToCreate ) )
853         {
854             for ( Slot slotTemp : listSlotToCreate )
855             {
856                 SlotService.createSlot( slotTemp );
857             }
858         }
859     }
860 
861     /**
862      * Create a slot in db
863      * 
864      * @param slot
865      *            the slot to create
866      * @return the slot created
867      */
868     public static Slot createSlot( Slot slot )
869     {
870         Slot slotCreated = SlotHome.create( slot );
871         SlotListenerManager.notifyListenersSlotCreation( slot.getIdSlot( ) );
872         return slotCreated;
873     }
874 
875     /**
876      * Delete a list of slots
877      * 
878      * @param listSlotToDelete
879      *            the lost of slots to delete
880      */
881     public static void deleteListSlots( List<Slot> listSlotToDelete )
882     {
883         for ( Slot slotToDelete : listSlotToDelete )
884         {
885             SlotService.deleteSlot( slotToDelete );
886         }
887     }
888 
889     /**
890      * Delete a slot
891      * 
892      * @param slot
893      *            the slot to delete
894      */
895     public static void deleteSlot( Slot slot )
896     {
897         int nIdSlot = slot.getIdSlot( );
898         SlotListenerManager.notifyListenersSlotRemoval( nIdSlot );
899         SlotHome.delete( nIdSlot );
900     }
901 
902     /**
903      * Return the slot with the max Date
904      * 
905      * @param nIdForm
906      *            the form id
907      * @return the slot with the max date
908      */
909     public static Slot findSlotWithMaxDate( int nIdForm )
910     {
911         return SlotHome.findSlotWithTheMaxDate( nIdForm );
912     }
913 
914 }