View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of 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.util.ArrayList;
41  import java.util.List;
42  import java.util.Map;
43  
44  import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
45  import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
46  import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
47  import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
48  import fr.paris.lutece.plugins.appointment.business.slot.Period;
49  import fr.paris.lutece.plugins.appointment.business.slot.Slot;
50  
51  public class CalendarBuilder
52  {
53      /**
54       * Private constructor
55       */
56      private CalendarBuilder( )
57      {
58  
59      }
60  
61      /**
62       * Build all the slot for a period with all the rules (open hours ...) to apply on each day, for each slot
63       * 
64       * @param nIdForm
65       *            the form Id
66       * @param mapReservationRule
67       *            the map of the rule week definition
68       * @param startingDate
69       *            the starting date of the period
70       * @param endingDate
71       *            the ending date of the periode
72       * @returna list of all the slots built
73       */
74      public static List<Slot> buildListSlot( int nIdForm, Map<WeekDefinition, ReservationRule> mapReservationRule, LocalDate startingDate, LocalDate endingDate )
75      {
76          List<Slot> listSlot = new ArrayList<>( );
77          final List<WeekDefinition> listDateReservationRule = new ArrayList<>( mapReservationRule.keySet( ) );
78          WeekDefinition closestweeDef;
79          ReservationRule reservationRuleToApply = null;
80          LocalDate dateTemp = startingDate;
81          int nMaxCapacity;
82          DayOfWeek dayOfWeek;
83          WorkingDay workingDay;
84          LocalTime minTimeForThisDay;
85          LocalTime maxTimeForThisDay;
86          LocalTime timeTemp;
87          LocalDateTime dateTimeTemp;
88          Slot slotToAdd;
89          TimeSlot timeSlot;
90          LocalDate dateToCompare;
91          // Need to check if this date is not before the form date creation
92          WeekDefinition firsWeek = listDateReservationRule.stream( ).sorted( ( week1, week2 ) -> week1.getDateOfApply( ).compareTo( week2.getDateOfApply( ) ) )
93                  .findFirst( ).orElse( null );
94          final LocalDate firstDateOfReservationRule = firsWeek.getDateOfApply( );
95          LocalDate startingDateToUse = startingDate;
96          if ( firstDateOfReservationRule != null && startingDate.isBefore( firstDateOfReservationRule ) )
97          {
98              startingDateToUse = firstDateOfReservationRule;
99          }
100         // Get all the closing day of this period
101         List<LocalDate> listDateOfClosingDay = ClosingDayService.findListDateOfClosingDayByIdFormAndDateRange( nIdForm, startingDateToUse, endingDate );
102         // Get all the slot between these two dates
103         Map<LocalDateTime, Slot> mapSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( nIdForm, startingDateToUse.atStartOfDay( ),
104                 endingDate.atTime( LocalTime.MAX ) );
105 
106         // Get or build all the event for the period
107         while ( !dateTemp.isAfter( endingDate ) )
108         {
109             dateToCompare = dateTemp;
110             // Find the closest date of apply of reservation rule with the given
111             // date
112             reservationRuleToApply = null;
113             closestweeDef = Utilities.getClosestWeekDefinitionInPast( listDateReservationRule, dateToCompare );
114             if ( closestweeDef != null )
115             {
116 
117                 reservationRuleToApply = mapReservationRule.get( closestweeDef );
118 
119             }
120             nMaxCapacity = 0;
121             // Get the day of week of the date
122             dayOfWeek = dateTemp.getDayOfWeek( );
123             // Get the working day of this day of week
124             workingDay = null;
125             if ( reservationRuleToApply != null )
126             {
127                 nMaxCapacity = reservationRuleToApply.getMaxCapacityPerSlot( );
128                 workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( reservationRuleToApply.getListWorkingDay( ), dayOfWeek );
129 
130             }
131             if ( workingDay != null )
132             {
133                 minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAWorkingDay( workingDay );
134                 maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
135                 // Check if this day is a closing day
136                 if ( listDateOfClosingDay.contains( dateTemp ) )
137                 {
138                     listSlot.add( SlotService.buildSlot( nIdForm, new Period( dateTemp.atTime( minTimeForThisDay ), dateTemp.atTime( maxTimeForThisDay ) ),
139                             nMaxCapacity, nMaxCapacity, nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE ) );
140                 }
141                 else
142                 {
143                     timeTemp = minTimeForThisDay;
144                     // For each slot of this day
145                     while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
146                     {
147                         // Get the LocalDateTime
148                         dateTimeTemp = dateTemp.atTime( timeTemp );
149                         // Search if there is a slot for this datetime
150                         if ( mapSlot.containsKey( dateTimeTemp ) )
151                         {
152                             slotToAdd = mapSlot.get( dateTimeTemp );
153                             timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
154                             listSlot.add( slotToAdd );
155                         }
156                         else
157                         {
158                             // Search the timeslot
159                             timeSlot = TimeSlotService.getTimeSlotInListOfTimeSlotWithStartingTime( workingDay.getListTimeSlot( ), timeTemp );
160                             if ( timeSlot != null )
161                             {
162                                 timeTemp = timeSlot.getEndingTime( );
163                                 int nMaxCapacityToPut = timeSlot.getMaxCapacity( );
164                                 slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacityToPut,
165                                         nMaxCapacityToPut, nMaxCapacityToPut, 0, timeSlot.getIsOpen( ), Boolean.FALSE );
166                                 listSlot.add( slotToAdd );
167                             }
168                             else
169                             {
170                                 break;
171                             }
172                         }
173                     }
174                 }
175             }
176             else
177             {
178                 // This is not a working day
179                 // We build all the slots closed for this day
180                 if ( reservationRuleToApply != null )
181                 {
182                     minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAListOfWorkingDay( reservationRuleToApply.getListWorkingDay( ) );
183                     maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( reservationRuleToApply.getListWorkingDay( ) );
184                     int nDuration = reservationRuleToApply.getDurationAppointments( );
185                     if ( minTimeForThisDay != null && maxTimeForThisDay != null )
186                     {
187                         timeTemp = minTimeForThisDay;
188                         // For each slot of this day
189                         while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
190                         {
191                             // Get the LocalDateTime
192                             dateTimeTemp = dateTemp.atTime( timeTemp );
193                             // Search if there is a slot for this datetime
194                             if ( mapSlot.containsKey( dateTimeTemp ) )
195                             {
196                                 slotToAdd = mapSlot.get( dateTimeTemp );
197                                 timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
198                                 listSlot.add( slotToAdd );
199                             }
200                             else
201                             {
202                                 timeTemp = timeTemp.plusMinutes( nDuration );
203                                 if ( timeTemp.isAfter( maxTimeForThisDay ) )
204                                 {
205                                     timeTemp = maxTimeForThisDay;
206                                 }
207                                 slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacity, nMaxCapacity,
208                                         nMaxCapacity, 0, Boolean.FALSE, Boolean.FALSE );
209                                 listSlot.add( slotToAdd );
210                             }
211                         }
212                     }
213                 }
214             }
215             dateTemp = dateTemp.plusDays( 1 );
216         }
217         return listSlot;
218     }
219 
220     /**
221      * Build all the slot for a period with all the rules (open hours ...) to apply on each day, for each slot
222      * 
223      * @param nIdForm
224      *            the form Id
225      * @param mapReservationRule
226      *            the map of the rule week definition
227      * @param startingDate
228      *            the starting date of the period
229      * @param endingDate
230      *            the ending date of the periode
231      * @param nNbPlaces
232      *            the number of place to take
233      * @param isAllOpenSlot
234      *            build slots with the all open slot
235      * @returna list of all the slots built
236      */
237     public static List<Slot> buildListSlot( int nIdForm, Map<WeekDefinition, ReservationRule> mapReservationRule, LocalDate startingDate, LocalDate endingDate,
238             int nNbPlaces, boolean isAllOpenSlot )
239     {
240         List<Slot> listSlotToShow = new ArrayList<>( );
241 
242         final List<WeekDefinition> listDateReservationRule = new ArrayList<>( mapReservationRule.keySet( ) );
243         WeekDefinition closestweeDef;
244         ReservationRule reservationRuleToApply = null;
245         LocalDate dateTemp = startingDate;
246         DayOfWeek dayOfWeek;
247         WorkingDay workingDay;
248         LocalTime minTimeForThisDay;
249         LocalTime maxTimeForThisDay;
250         LocalTime timeTemp;
251         LocalDateTime dateTimeTemp;
252         LocalDateTime endingDateTime;
253         LocalDateTime startingDateTime = null;
254         LocalTime tempEndingDateTime = null;
255         LocalDateTime localDateTimeNow = LocalDateTime.now( );
256 
257         boolean isChanged = true;
258         boolean isfull = false;
259         int sumNbPotentialRemainingPlaces;
260         int sumNbRemainingPlaces;
261         int nbSlot;
262 
263         Slot slotToAdd;
264         TimeSlot timeSlot;
265         LocalDate dateToCompare;
266         // Need to check if this date is not before the form date creation
267         WeekDefinition firsWeek = listDateReservationRule.stream( ).sorted( ( week1, week2 ) -> week1.getDateOfApply( ).compareTo( week2.getDateOfApply( ) ) )
268                 .findFirst( ).orElse( null );
269         final LocalDate firstDateOfReservationRule = firsWeek.getDateOfApply( );
270         LocalDate startingDateToUse = startingDate;
271         if ( firstDateOfReservationRule != null && startingDate.isBefore( firstDateOfReservationRule ) )
272         {
273             startingDateToUse = firstDateOfReservationRule;
274         }
275         // Get all the closing day of this period
276         List<LocalDate> listDateOfClosingDay = ClosingDayService.findListDateOfClosingDayByIdFormAndDateRange( nIdForm, startingDateToUse, endingDate );
277         // Get all the slot between these two dates
278         Map<LocalDateTime, Slot> mapSlot = SlotService.buildMapSlotsByIdFormAndDateRangeWithDateForKey( nIdForm, startingDateToUse.atStartOfDay( ),
279                 endingDate.atTime( LocalTime.MAX ) );
280 
281         // Get or build all the event for the period
282         while ( !dateTemp.isAfter( endingDate ) )
283         {
284             dateToCompare = dateTemp;
285             // Find the closest date of apply of reservation rule with the given
286             // date
287             reservationRuleToApply = null;
288             closestweeDef = Utilities.getClosestWeekDefinitionInPast( listDateReservationRule, dateToCompare );
289             if ( closestweeDef != null )
290             {
291 
292                 reservationRuleToApply = mapReservationRule.get( closestweeDef );
293 
294             }
295             // Get the day of week of the date
296             dayOfWeek = dateTemp.getDayOfWeek( );
297             // Get the working day of this day of week
298             workingDay = null;
299             if ( reservationRuleToApply != null )
300             {
301                 workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( reservationRuleToApply.getListWorkingDay( ), dayOfWeek );
302 
303             }
304 
305             if ( workingDay != null )
306             {
307                 minTimeForThisDay = WorkingDayService.getMinStartingTimeOfAWorkingDay( workingDay );
308                 maxTimeForThisDay = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
309                 // Check if this day is a closing day
310                 if ( !listDateOfClosingDay.contains( dateTemp ) )
311                 {
312                     timeTemp = minTimeForThisDay;
313                     sumNbPotentialRemainingPlaces = 0;
314                     sumNbRemainingPlaces = 0;
315                     nbSlot = 0;
316                     isChanged = true;
317                     tempEndingDateTime = timeTemp;
318                     // For each slot of this day
319                     while ( timeTemp.isBefore( maxTimeForThisDay ) || !timeTemp.equals( maxTimeForThisDay ) )
320                     {
321 
322                         // Get the LocalDateTime
323                         dateTimeTemp = dateTemp.atTime( timeTemp );
324                         // Search if there is a slot for this datetime
325                         if ( isChanged )
326                         {
327 
328                             startingDateTime = dateTimeTemp;
329                             isChanged = false;
330                         }
331                         if ( mapSlot.containsKey( dateTimeTemp ) )
332                         {
333                             slotToAdd = mapSlot.get( dateTimeTemp );
334                             timeTemp = slotToAdd.getEndingDateTime( ).toLocalTime( );
335 
336                         }
337                         else
338                         {
339                             // Search the timeslot
340                             timeSlot = TimeSlotService.getTimeSlotInListOfTimeSlotWithStartingTime( workingDay.getListTimeSlot( ), timeTemp );
341                             if ( timeSlot != null )
342                             {
343                                 timeTemp = timeSlot.getEndingTime( );
344                                 int nMaxCapacityToPut = timeSlot.getMaxCapacity( );
345                                 slotToAdd = SlotService.buildSlot( nIdForm, new Period( dateTimeTemp, dateTemp.atTime( timeTemp ) ), nMaxCapacityToPut,
346                                         nMaxCapacityToPut, nMaxCapacityToPut, 0, timeSlot.getIsOpen( ), Boolean.FALSE );
347 
348                             }
349                             else
350                             {
351                                 break;
352                             }
353                         }
354 
355                         if ( isNewSlot( sumNbPotentialRemainingPlaces, nNbPlaces, slotToAdd, localDateTimeNow, isAllOpenSlot, nbSlot ) )
356                         {
357 
358                             sumNbPotentialRemainingPlaces = 0;
359                             nbSlot = 0;
360                             sumNbRemainingPlaces = 0;
361                             startingDateTime = slotToAdd.getEndingDateTime( );
362                             tempEndingDateTime = slotToAdd.getEndingTime( );
363                         }
364                         else
365                         {
366                             if ( slotToAdd.getNbPotentialRemainingPlaces( ) <= 0 )
367                             {
368                                 isfull = true;
369                             }
370                             sumNbPotentialRemainingPlaces = sumNbPotentialRemainingPlaces + 1;
371                             nbSlot = nbSlot + 1;
372                             sumNbRemainingPlaces = sumNbRemainingPlaces + 1;
373                         }
374 
375                         if ( buildNewSlot( sumNbPotentialRemainingPlaces, nNbPlaces, isAllOpenSlot, nbSlot ) )
376                         {
377 
378                             endingDateTime = slotToAdd.getEndingDateTime( );
379                             Slotlugins/appointment/business/slot/Slot.html#Slot">Slot slt = new Slot( );
380                             slt.setStartingDateTime( startingDateTime );
381                             slt.setEndingDateTime( endingDateTime );
382                             slt.setIsOpen( true );
383                             slt.setNbPotentialRemainingPlaces( sumNbPotentialRemainingPlaces );
384                             slt.setNbRemainingPlaces( sumNbRemainingPlaces );
385                             slt.setDate( slotToAdd.getDate( ) );
386                             slt.setIdForm( slotToAdd.getIdForm( ) );
387                             slt.setIsFull( isfull ? 1 : 0 );
388                             listSlotToShow.add( slt );
389                             isChanged = true;
390                             isfull = false;
391                             timeTemp = tempEndingDateTime;
392                         }
393 
394                     }
395                 }
396             }
397 
398             dateTemp = dateTemp.plusDays( 1 );
399         }
400         return listSlotToShow;
401 
402     }
403 
404     private static boolean isNewSlot( int sumNbPotentialRemainingPlaces, int nNbPlaces, Slot slotToAdd, LocalDateTime localDateTimeNow, boolean isAllOpenSlot,
405             int nbSlot )
406     {
407 
408         if ( isAllOpenSlot )
409         {
410 
411             return nbSlot == nNbPlaces || !slotToAdd.getIsOpen( ) || slotToAdd.getEndingDateTime( ).isBefore( localDateTimeNow );
412         }
413 
414         return sumNbPotentialRemainingPlaces >= nNbPlaces || !slotToAdd.getIsOpen( ) || slotToAdd.getNbPotentialRemainingPlaces( ) <= 0
415                 || slotToAdd.getEndingDateTime( ).isBefore( localDateTimeNow );
416     }
417 
418     private static boolean buildNewSlot( int sumNbPotentialRemainingPlaces, int nNbPlaces, boolean isAllOpenSlot, int nbSlot )
419     {
420         if ( isAllOpenSlot )
421         {
422 
423             return nbSlot == nNbPlaces;
424         }
425 
426         return sumNbPotentialRemainingPlaces >= nNbPlaces;
427     }
428 }