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.sql.Date;
37  import java.time.DayOfWeek;
38  import java.time.LocalDate;
39  import java.time.LocalDateTime;
40  import java.time.LocalTime;
41  import java.time.temporal.ChronoUnit;
42  import java.time.temporal.WeekFields;
43  import java.util.ArrayList;
44  import java.util.Comparator;
45  import java.util.HashMap;
46  import java.util.List;
47  import java.util.Locale;
48  import java.util.Map;
49  import java.util.Objects;
50  import java.util.Set;
51  import java.util.concurrent.Executors;
52  import java.util.concurrent.ScheduledExecutorService;
53  import java.util.concurrent.ScheduledFuture;
54  import java.util.concurrent.TimeUnit;
55  import java.util.concurrent.locks.Lock;
56  import java.util.stream.Collectors;
57  
58  import javax.servlet.http.HttpServletRequest;
59  import javax.validation.ConstraintViolation;
60  
61  import org.apache.commons.collections.CollectionUtils;
62  import org.apache.commons.lang3.StringUtils;
63  
64  import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
65  import fr.paris.lutece.plugins.appointment.business.appointment.AppointmentSlot;
66  import fr.paris.lutece.plugins.appointment.business.category.Category;
67  import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
68  import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
69  import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
70  import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
71  import fr.paris.lutece.plugins.appointment.business.slot.Slot;
72  import fr.paris.lutece.plugins.appointment.business.user.User;
73  import fr.paris.lutece.plugins.appointment.service.lock.SlotEditTask;
74  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
75  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFilterDTO;
76  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
77  import fr.paris.lutece.plugins.appointment.web.dto.ReservationRuleDTO;
78  import fr.paris.lutece.plugins.appointment.web.dto.ResponseRecapDTO;
79  import fr.paris.lutece.plugins.genericattributes.business.Entry;
80  import fr.paris.lutece.plugins.genericattributes.business.EntryFilter;
81  import fr.paris.lutece.plugins.genericattributes.business.EntryHome;
82  import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;
83  import fr.paris.lutece.plugins.genericattributes.business.Response;
84  import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
85  import fr.paris.lutece.plugins.genericattributes.service.entrytype.IEntryTypeService;
86  import fr.paris.lutece.plugins.genericattributes.util.GenericAttributesUtils;
87  import fr.paris.lutece.portal.business.user.AdminUser;
88  import fr.paris.lutece.portal.business.user.AdminUserHome;
89  import fr.paris.lutece.portal.service.i18n.I18nService;
90  import fr.paris.lutece.portal.service.rbac.RBACService;
91  import fr.paris.lutece.portal.service.util.AppLogService;
92  import fr.paris.lutece.portal.service.util.AppPropertiesService;
93  import fr.paris.lutece.util.beanvalidation.BeanValidationUtil;
94  
95  /**
96   * Utility class for Appointment Mutualize methods between MVCApplication and MVCAdminJspBean
97   * 
98   * @author Laurent Payen
99   *
100  */
101 public final class AppointmentUtilities
102 {
103 
104     public static final String ERROR_MESSAGE_EMPTY_CONFIRM_EMAIL = "appointment.validation.appointment.EmailConfirmation.email";
105     public static final String ERROR_MESSAGE_CONFIRM_EMAIL = "appointment.message.error.confirmEmail";
106     public static final String ERROR_MESSAGE_DATE_APPOINTMENT = "appointment.message.error.dateAppointment";
107     public static final String ERROR_MESSAGE_EMPTY_EMAIL = "appointment.validation.appointment.Email.notEmpty";
108     public static final String ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.notEmpty";
109     public static final String ERROR_MESSAGE_FORMAT_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.notNumberFormat";
110     public static final String ERROR_MESSAGE_ERROR_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.error";
111 
112     public static final String MARK_PERMISSION_ADD_COMMENT = "permission_add_comment";
113     public static final String MARK_PERMISSION_MODERATE_COMMENT = "permission_moderate_comment";
114     public static final String MARK_PERMISSION_ACCESS_CODE = "permission_access_code";
115     public static final String OLD_APPOINTMENT_DTO = "oldAppointment";
116     public static final String SESSION_TASK_TIMER_SLOT = "appointment.session.task.timer.slot";
117 
118     public static final String PROPERTY_DEFAULT_EXPIRED_TIME_EDIT_APPOINTMENT = "appointment.edit.expired.time";
119 
120     public static final int THIRTY_MINUTES = 30;
121     // We don't need to instantiate an ScheduledExecutorService with the removeOnCancel=true because we are using non-periodic and short time tasks.
122     // The getTask call removes the task from the queue.
123     private static final ScheduledExecutorService _secheduledExecutor = Executors
124             .newSingleThreadScheduledExecutor( r -> new Thread( r, "Lutece-AppointmentSecheduledExecutor-thread" ) );
125 
126     // CONSTANTS
127     // Name of the phone number's generic attribute bean
128     public static final String CONSTANT_GENERIC_ATTRIBUTE_TYPE_PHONE_NAME = "appointment.entryTypePhone";
129     // Characters used to separate phone numbers, in the case where a form allows multiple entries
130     public static final String CONSTANT_PHONE_NUMBERS_SEPARATOR = ", ";
131 
132     /**
133      * Private constructor - this class does not need to be instantiated
134      */
135     private AppointmentUtilities( )
136     {
137     }
138 
139     /**
140      * Check that the email is correct and matches the confirm email
141      * 
142      * @param strEmail
143      *            the email
144      * @param strConfirmEmail
145      *            the confirm email
146      * @param form
147      *            the form
148      * @param locale
149      *            the local
150      * @param listFormErrors
151      *            the list of errors that can be fill in with the errors found for the email
152      */
153     public static void checkEmail( String strEmail, String strConfirmEmail, AppointmentFormDTO form, Locale locale, List<GenericAttributeError> listFormErrors )
154     {
155         if ( form.getEnableMandatoryEmail( ) )
156         {
157             if ( StringUtils.isEmpty( strEmail ) )
158             {
159                 GenericAttributeError genAttError = new GenericAttributeError( );
160                 genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_EMAIL, locale ) );
161                 listFormErrors.add( genAttError );
162             }
163             if ( StringUtils.isEmpty( strConfirmEmail ) )
164             {
165                 GenericAttributeError genAttError = new GenericAttributeError( );
166                 genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_CONFIRM_EMAIL, locale ) );
167                 listFormErrors.add( genAttError );
168             }
169         }
170         if ( !StringUtils.equalsIgnoreCase( strEmail, strConfirmEmail ) )
171         {
172             GenericAttributeError genAttError = new GenericAttributeError( );
173             genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_CONFIRM_EMAIL, locale ) );
174             listFormErrors.add( genAttError );
175         }
176     }
177 
178     /**
179      * Check that the date of the appointment we try to take is not in the past
180      * 
181      * @param appointmentDTO
182      *            the appointment
183      * @param locale
184      *            the local
185      * @param listFormErrors
186      *            the list of errors that can be fill in with the error found with the date
187      */
188     public static void checkDateOfTheAppointmentIsNotBeforeNow( AppointmentDTO appointmentDTO, Locale locale, List<GenericAttributeError> listFormErrors )
189     {
190         LocalDateTime startingDateTime = getStartingDateTime( appointmentDTO );
191         if ( startingDateTime == null || startingDateTime.toLocalDate( ).isBefore( LocalDate.now( ) ) )
192         {
193             GenericAttributeError genAttError = new GenericAttributeError( );
194             genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_DATE_APPOINTMENT, locale ) );
195             listFormErrors.add( genAttError );
196         }
197     }
198 
199     /**
200      * Check that the delay between two appointments for the same use has been respected
201      * 
202      * @param appointmentDTO
203      *            the appointment
204      * @param strEmail
205      *            the email
206      * @param form
207      *            the form
208      * @return false if the delay is not respected
209      */
210     public static boolean checkNbDaysBetweenTwoAppointments( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
211     {
212         boolean bCheckPassed = true;
213         int nbDaysBetweenTwoAppointments = form.getNbDaysBeforeNewAppointment( );
214         if ( nbDaysBetweenTwoAppointments != 0 )
215         {
216             List<Slot> listSlots = getSlotsByEmail( strEmail, appointmentDTO.getIdAppointment( ) );
217             if ( CollectionUtils.isNotEmpty( listSlots ) )
218             {
219                 // Get the last appointment date for this form
220                 listSlots = listSlots.stream( ).filter( s -> s.getIdForm( ) == form.getIdForm( ) ).collect( Collectors.toList( ) );
221                 if ( CollectionUtils.isNotEmpty( listSlots ) )
222                 {
223                     LocalDateTime dateOfTheLastAppointment = listSlots.stream( ).map( Slot::getStartingDateTime ).max( LocalDateTime::compareTo )
224                             .orElse( null );
225 
226                     // Check the number of days between this appointment and
227                     // the last appointment the user has taken
228                     LocalDateTime dateOfTheAppointment = getStartingDateTime( appointmentDTO );
229                     if ( dateOfTheLastAppointment != null && Math
230                             .abs( dateOfTheLastAppointment.toLocalDate( ).until( dateOfTheAppointment, ChronoUnit.DAYS ) ) <= nbDaysBetweenTwoAppointments )
231                     {
232                         bCheckPassed = false;
233                     }
234                 }
235             }
236         }
237         return bCheckPassed;
238     }
239 
240     /**
241      * Check that the delay between two appointments for the same use has been respected
242      * 
243      * @param appointmentDTO
244      *            the appointment
245      * @param strEmail
246      *            the email
247      * @param form
248      *            the form
249      * @return false if the delay is not respected
250      */
251     public static boolean checkNbDaysBetweenTwoAppointmentsTaken( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
252     {
253         boolean bCheckPassed = true;
254         int nbDaysBetweenTwoAppointments = form.getNbDaysBeforeNewAppointment( );
255         if ( nbDaysBetweenTwoAppointments != 0 && StringUtils.isNotEmpty( strEmail ) )
256         {
257             AppointmentFilterDTOweb/dto/AppointmentFilterDTO.html#AppointmentFilterDTO">AppointmentFilterDTO filter = new AppointmentFilterDTO( );
258             filter.setEmail( strEmail );
259             filter.setStatus( 0 );
260             filter.setIdForm( form.getIdForm( ) );
261             List<Appointment> listAppointment = AppointmentService.findListAppointmentsByFilter( filter );
262             // If we modify an appointment, we remove the
263             // appointment that we currently edit
264             if ( appointmentDTO.getIdAppointment( ) != 0 )
265             {
266                 listAppointment.removeIf( a -> a.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
267             }
268 
269             if ( CollectionUtils.isNotEmpty( listAppointment ) )
270             {
271 
272                 LocalDateTime dateOfTheLastAppointmentTaken = listAppointment.stream( ).map( Appointment::getDateAppointmentTaken )
273                         .max( LocalDateTime::compareTo ).orElse( null );
274 
275                 if ( dateOfTheLastAppointmentTaken != null
276                         && Math.abs( dateOfTheLastAppointmentTaken.until( LocalDateTime.now( ), ChronoUnit.DAYS ) ) < nbDaysBetweenTwoAppointments )
277                 {
278                     bCheckPassed = false;
279                 }
280 
281             }
282         }
283         return bCheckPassed;
284     }
285 
286     /**
287      * Get the appointment of a user appointment
288      * 
289      * @param strEmail
290      *            the user's email
291      * @param idAppointment
292      *            the id of the appointment
293      * @return the list of appointment
294      */
295     private static List<Appointment> getAppointmentByEmail( String strEmail, int idAppointment )
296     {
297         List<Appointment> listAppointment = new ArrayList<>( );
298         if ( StringUtils.isNotEmpty( strEmail ) )
299         {
300             // Looking for existing users with this email
301             List<User> listUsers = UserService.findUsersByEmail( strEmail );
302             if ( listUsers != null )
303             {
304                 // For each User
305                 for ( User user : listUsers )
306                 {
307                     // looking for its appointment
308                     listAppointment.addAll( AppointmentService.findListAppointmentByUserId( user.getIdUser( ) ) );
309                 }
310 
311                 // If we modify an appointment, we remove the
312                 // appointment that we currently edit
313                 if ( idAppointment != 0 )
314                 {
315                     listAppointment = listAppointment.stream( ).filter( a -> a.getIdAppointment( ) != idAppointment ).collect( Collectors.toList( ) );
316                 }
317 
318             }
319         }
320         return listAppointment;
321     }
322 
323     /**
324      * Get the slot of a user appointment
325      * 
326      * @param strEmail
327      *            the user's email
328      * @param idAppointment
329      *            the id of the appointment
330      * @return the list of slots
331      */
332     private static List<Slot> getSlotsByEmail( String strEmail, int idAppointment )
333     {
334         List<Slot> listSlots = new ArrayList<>( );
335         if ( StringUtils.isNotEmpty( strEmail ) )
336         {
337             List<Appointment> listAppointment = getAppointmentByEmail( strEmail, idAppointment );
338             if ( CollectionUtils.isNotEmpty( listAppointment ) )
339             {
340                 // I know we could have a join sql query, but I don't
341                 // want to join the appointment table with the slot
342                 // table, it's too big and not efficient
343 
344                 for ( Appointment appointment : listAppointment )
345                 {
346                     if ( !appointment.getIsCancelled( ) )
347                     {
348                         listSlots = SlotService.findListSlotByIdAppointment( appointment.getIdAppointment( ) );
349                     }
350                 }
351 
352             }
353 
354         }
355         return listSlots;
356     }
357 
358     /**
359      * Check that the number of appointments on a defined period is not above the maximum authorized
360      * 
361      * @param appointmentDTO
362      *            the appointment
363      * @param strEmail
364      *            the email of the user
365      * @param form
366      *            the form
367      * @return false if the number of appointments is above the maximum authorized on the defined period
368      */
369     public static boolean checkNbMaxAppointmentsOnAGivenPeriod( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
370     {
371         if ( form.getNbMaxAppointmentsPerUser( ) > 0 && StringUtils.isNotEmpty( strEmail ) )
372         {
373             // Get the date of the future appointment
374             LocalDateTime startingDateTime = getStartingDateTime( appointmentDTO );
375             if ( startingDateTime == null )
376             {
377                 AppLogService.error( "Error checkNbMaxAppointmentsOnAGivenPeriod, startingDateTime is null" );
378                 return false;
379             }
380             LocalDate dateOfTheAppointment = startingDateTime.toLocalDate( );
381 
382             AppointmentFilterDTOweb/dto/AppointmentFilterDTO.html#AppointmentFilterDTO">AppointmentFilterDTO filter = new AppointmentFilterDTO( );
383             filter.setEmail( strEmail );
384             filter.setIdForm( form.getIdForm( ) );
385             filter.setStatus( 0 );
386             if ( form.getNbDaysForMaxAppointmentsPerUser( ) > 0 )
387             {
388                 filter.setStartingDateOfSearch( Date.valueOf( dateOfTheAppointment.minusDays( (long) form.getNbDaysForMaxAppointmentsPerUser( ) - 1 ) ) );
389                 filter.setEndingDateOfSearch( Date.valueOf( dateOfTheAppointment.plusDays( (long) form.getNbDaysForMaxAppointmentsPerUser( ) - 1 ) ) );
390             }
391             List<AppointmentDTO> listAppointmentsDTO = AppointmentService.findListAppointmentsDTOByFilter( filter );
392             // If we modify an appointment, we remove the
393             // appointment that we currently edit
394             if ( appointmentDTO.getIdAppointment( ) != 0 )
395             {
396 
397                 listAppointmentsDTO.removeIf( appt -> appt.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
398             }
399 
400             if ( CollectionUtils.isNotEmpty( listAppointmentsDTO ) )
401             {
402                 if ( form.getNbDaysForMaxAppointmentsPerUser( ) > 0 )
403                 {
404 
405                     List<AppointmentDTO> listAppointmentsBefore = listAppointmentsDTO.stream( )
406                             .filter( appt -> appt.getStartingDateTime( ).toLocalDate( ).isBefore( dateOfTheAppointment )
407                                     || appt.getStartingDateTime( ).toLocalDate( ).isEqual( dateOfTheAppointment ) )
408                             .collect( Collectors.toList( ) );
409                     List<AppointmentDTO> listAppointmentsAfter = listAppointmentsDTO.stream( )
410                             .filter( appt -> appt.getStartingDateTime( ).toLocalDate( ).isAfter( dateOfTheAppointment )
411                                     || appt.getStartingDateTime( ).toLocalDate( ).isEqual( dateOfTheAppointment ) )
412                             .collect( Collectors.toList( ) );
413 
414                     if ( listAppointmentsBefore.size( ) >= form.getNbMaxAppointmentsPerUser( )
415                             || listAppointmentsAfter.size( ) >= form.getNbMaxAppointmentsPerUser( ) )
416                     {
417                         return false;
418                     }
419                 }
420                 else
421                     if ( listAppointmentsDTO.size( ) >= form.getNbMaxAppointmentsPerUser( ) )
422                     {
423 
424                         return false;
425                     }
426             }
427         }
428         return true;
429 
430     }
431 
432     /**
433      * Check that the number of appointments on a defined category is not above the maximum authorized
434      * 
435      * @param appointmentDTO
436      *            the appointment
437      * @param strEmail
438      *            the email of the user
439      * @param form
440      *            the form
441      * @return false if the number of appointments is above the maximum authorized on the defined category
442      */
443     public static boolean checkNbMaxAppointmentsDefinedOnCategory( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form,
444             List<AppointmentDTO> listAppointments )
445     {
446         if ( form.getIdCategory( ) != 0 && StringUtils.isNotEmpty( strEmail ) )
447         {
448             Category category = CategoryService.findCategoryById( form.getIdCategory( ) );
449             if ( category != null && category.getNbMaxAppointmentsPerUser( ) > 0 )
450             {
451                 LocalDateTime now = LocalDateTime.now( );
452                 List<AppointmentDTO> listAppointmentsDTO = AppointmentService.findAppointmentByMailAndCategory( category.getIdCategory( ), strEmail );
453                 listAppointmentsDTO.removeIf( appt -> appt.getEndingDateTime( ).isBefore( now ) || appt.getIsCancelled( ) );
454                 // If we modify an appointment, we remove the
455                 // appointment that we currently edit
456                 if ( appointmentDTO.getIdAppointment( ) != 0 )
457                 {
458 
459                     listAppointmentsDTO.removeIf( appt -> appt.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
460                 }
461                 listAppointments.addAll( listAppointmentsDTO );
462                 if ( CollectionUtils.isNotEmpty( listAppointmentsDTO ) && listAppointmentsDTO.size( ) >= category.getNbMaxAppointmentsPerUser( ) )
463                 {
464                     return false;
465 
466                 }
467             }
468         }
469         return true;
470 
471     }
472 
473     /**
474      * Check and validate all the rules for the number of booked seats asked
475      * 
476      * @param strNbBookedSeats
477      *            the number of booked seats
478      * @param form
479      *            the form
480      * @param nbRemainingPlaces
481      *            the number of remaining places on the slot asked
482      * @param locale
483      *            the locale
484      * @param listFormErrors
485      *            the list of errors that can be fill in with the errors found for the number of booked seats
486      * @return
487      */
488     public static int checkAndReturnNbBookedSeats( String strNbBookedSeats, AppointmentFormDTO form, AppointmentDTO appointmentDTO, Locale locale,
489             List<GenericAttributeError> listFormErrors )
490     {
491         int nbBookedSeats = 1;
492         if ( StringUtils.isEmpty( strNbBookedSeats ) && form.getMaxPeoplePerAppointment( ) > 1 )
493         {
494             GenericAttributeError genAttError = new GenericAttributeError( );
495             genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT, locale ) );
496             listFormErrors.add( genAttError );
497         }
498         if ( StringUtils.isNotEmpty( strNbBookedSeats ) )
499         {
500             try
501             {
502                 nbBookedSeats = Integer.parseInt( strNbBookedSeats );
503             }
504             catch( NumberFormatException | NullPointerException e )
505             {
506                 GenericAttributeError genAttError = new GenericAttributeError( );
507                 genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_FORMAT_NB_BOOKED_SEAT, locale ) );
508                 listFormErrors.add( genAttError );
509             }
510         }
511         // if it's a new appointment, need to check if the number of booked
512         // seats is under or equal to the number of remaining places
513         // if it's a modification, need to check if the new number of booked
514         // seats is under or equal to the number of the remaining places + the
515         // previous number of booked seats of the appointment
516         if ( nbBookedSeats > appointmentDTO.getNbMaxPotentialBookedSeats( ) && !appointmentDTO.getOverbookingAllowed( ) )
517 
518         {
519             GenericAttributeError genAttError = new GenericAttributeError( );
520             genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_ERROR_NB_BOOKED_SEAT, locale ) );
521             listFormErrors.add( genAttError );
522         }
523 
524         if ( nbBookedSeats == 0 )
525         {
526             GenericAttributeError genAttError = new GenericAttributeError( );
527             genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT, locale ) );
528             listFormErrors.add( genAttError );
529         }
530         return nbBookedSeats;
531     }
532 
533     /**
534      * Fill the appoinmentFront DTO with the given parameters
535      * 
536      * @param appointmentDTO
537      *            the appointmentFront DTO
538      * @param nbBookedSeats
539      *            the number of booked seats
540      * @param strEmail
541      *            the email of the user
542      * @param strFirstName
543      *            the first name of the user
544      * @param strLastName
545      *            the last name of the user
546      */
547     public static void fillAppointmentDTO( AppointmentDTO appointmentDTO, int nbBookedSeats, String strEmail, String strEmailConfirm, String strFirstName, String strLastName )
548     {
549         appointmentDTO.setDateOfTheAppointment( appointmentDTO.getSlot( ).get( 0 ).getDate( ).format( Utilities.getFormatter( ) ) );
550         appointmentDTO.setNbBookedSeats( nbBookedSeats );
551         appointmentDTO.setEmail( strEmail );
552         appointmentDTO.setConfirmEmail(strEmailConfirm);
553         appointmentDTO.setFirstName( strFirstName );
554         appointmentDTO.setLastName( strLastName );
555     }
556 
557     /**
558      * Validate the form and the additional entries of the form
559      * 
560      * @param appointmentDTO
561      *            the appointmentFron DTo to validate
562      * @param request
563      *            the request
564      * @param listFormErrors
565      *            the list of errors that can be fill with the errors found at the validation
566      */
567     public static void validateFormAndEntries( AppointmentDTO appointmentDTO, HttpServletRequest request, List<GenericAttributeError> listFormErrors,
568             boolean allEntries )
569     {
570         Set<ConstraintViolation<AppointmentDTO>> listErrors = BeanValidationUtil.validate( appointmentDTO );
571 
572         if ( CollectionUtils.isNotEmpty( listErrors ) )
573         {
574             for ( ConstraintViolation<AppointmentDTO> constraintViolation : listErrors )
575             {
576                 GenericAttributeError genAttError = new GenericAttributeError( );
577                 genAttError.setErrorMessage( I18nService.getLocalizedString( constraintViolation.getMessageTemplate( ), request.getLocale( ) ) );
578                 listFormErrors.add( genAttError );
579             }
580         }
581         
582         EntryFilter filter = EntryService.buildEntryFilter( appointmentDTO.getIdForm( ) );
583         if ( allEntries )
584         {
585             filter.setIsOnlyDisplayInBack( GenericAttributesUtils.CONSTANT_ID_NULL );
586         }
587         List<Entry> listEntryFirstLevel = EntryHome.getEntryList( filter );
588         for ( Entry entry : listEntryFirstLevel )
589         {
590             listFormErrors.addAll( EntryService.getResponseEntry( request, entry.getIdEntry( ), request.getLocale( ), appointmentDTO ) );
591         }
592     }
593 
594     public static void fillInListResponseWithMapResponse( AppointmentDTO appointmentDTO )
595     {
596         Map<Integer, List<Response>> mapResponses = appointmentDTO.getMapResponsesByIdEntry( );
597         if ( mapResponses != null && !mapResponses.isEmpty( ) )
598         {
599             List<Response> listResponse = new ArrayList<>( );
600             for ( List<Response> listResponseByEntry : mapResponses.values( ) )
601             {
602                 listResponse.addAll( listResponseByEntry );
603             }
604             appointmentDTO.setListResponse( listResponse );
605         }
606     }
607 
608     /**
609      * Build a list of response of the appointment
610      * 
611      * @param appointment
612      *            the appointment
613      * @param request
614      *            the request
615      * @param locale
616      *            the local
617      * @return a list of response
618      */
619     public static List<ResponseRecapDTO> buildListResponse( AppointmentDTO appointment, HttpServletRequest request, Locale locale )
620     {
621         List<ResponseRecapDTO> listResponseRecapDTO = new ArrayList<>( );
622         HashMap<Integer, List<ResponseRecapDTO>> mapResponse = new HashMap<>( );
623         if ( CollectionUtils.isNotEmpty( appointment.getListResponse( ) ) )
624         {
625             listResponseRecapDTO = new ArrayList<>( appointment.getListResponse( ).size( ) );
626             for ( Response response : appointment.getListResponse( ) )
627             {
628                 int nIndex = response.getEntry( ).getPosition( );
629                 IEntryTypeService entryTypeService = EntryTypeServiceManager.getEntryTypeService( response.getEntry( ) );
630                 ResponseRecapDTOo/ResponseRecapDTO.html#ResponseRecapDTO">ResponseRecapDTO responseRecapDTO = new ResponseRecapDTO( response,
631                         entryTypeService.getResponseValueForRecap( response.getEntry( ), request, response, locale ) );
632 
633                 List<ResponseRecapDTO> listResponse = mapResponse.computeIfAbsent( nIndex, ArrayList::new );
634                 listResponse.add( responseRecapDTO );
635             }
636         }
637         for ( List<ResponseRecapDTO> listResponse : mapResponse.values( ) )
638         {
639             listResponseRecapDTO.addAll( listResponse );
640         }
641         return listResponseRecapDTO;
642     }
643 
644     /**
645      * Attempts to cancel execution of the task.
646      * 
647      * @param request
648      *            the request
649      * @param idSlot
650      *            the id Slot
651      */
652 
653     public static void cancelTaskTimer( HttpServletRequest request, int idSlot )
654     {
655         ScheduledFuture<Slot> task = (ScheduledFuture) request.getSession( ).getAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
656         if ( task != null )
657         {
658             if ( !task.isDone( ) )
659                 task.cancel( false );
660             request.getSession( ).removeAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
661         }
662     }
663 
664     public static boolean isEditSlotTaskExpiredTime( HttpServletRequest request, int idSlot )
665     {
666         ScheduledFuture<Slot> task = (ScheduledFuture) request.getSession( ).getAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
667         return ( task != null && task.isDone( ) );
668     }
669 
670     /**
671      * Create a timer on a slot
672      * 
673      * @param slot
674      *            the slot
675      * @param appointmentDTO
676      *            the appointment
677      * @param maxPeoplePerAppointment
678      *            the max people per appointment
679      * @return the ScheduledFuture
680      */
681     public static ScheduledFuture<Slot> putTimerInSession( HttpServletRequest request, int nIdSlot, AppointmentDTO appointmentDTO, int maxPeoplePerAppointment )
682     {
683         Lock lock = SlotSafeService.getLockOnSlot( nIdSlot );
684         lock.lock( );
685         try
686         {
687             Slot slot = SlotService.findSlotById( nIdSlot );
688 
689             int nbPotentialRemainingPlaces = slot.getNbPotentialRemainingPlaces( );
690             int nbPotentialPlacesTaken = Math.min( nbPotentialRemainingPlaces, maxPeoplePerAppointment );
691             int nNewNbMaxPotentialBookedSeats = Math.min( nbPotentialPlacesTaken + appointmentDTO.getNbMaxPotentialBookedSeats( ), maxPeoplePerAppointment );
692 
693             if ( slot.getNbPotentialRemainingPlaces( ) > 0 )
694             {
695 
696                 ScheduledFuture<Slot> scheduledFuture = _secheduledExecutor.schedule( new SlotEditTask( slot.getIdSlot( ), nbPotentialPlacesTaken ),
697                         AppPropertiesService.getPropertyInt( PROPERTY_DEFAULT_EXPIRED_TIME_EDIT_APPOINTMENT, 1 ), TimeUnit.MINUTES );
698                 appointmentDTO.setNbMaxPotentialBookedSeats( nNewNbMaxPotentialBookedSeats );
699                 SlotSafeService.decrementPotentialRemainingPlaces( nbPotentialPlacesTaken, slot.getIdSlot( ) );
700 
701                 request.getSession( ).setAttribute( SESSION_TASK_TIMER_SLOT + slot.getIdSlot( ), scheduledFuture );
702                 return scheduledFuture;
703             }
704             appointmentDTO.setNbMaxPotentialBookedSeats( 0 );
705         }
706 
707         finally
708         {
709 
710             lock.unlock( );
711         }
712         return null;
713     }
714 
715     /**
716      * Get Form Permissions
717      * 
718      * @param listForms
719      * @param request
720      * @return
721      */
722     public static String [ ] [ ] getPermissions( List<AppointmentFormDTO> listForms, AdminUser user )
723     {
724         String [ ] [ ] retour = new String [ listForms.size( )] [ 6];
725         int nI = 0;
726 
727         for ( AppointmentFormDTO tmpForm : listForms )
728         {
729 
730             String [ ] strRetour = new String [ 7];
731             strRetour [0] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
732                     AppointmentResourceIdService.PERMISSION_VIEW_APPOINTMENT, (fr.paris.lutece.api.user.User) user ) );
733             strRetour [1] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
734                     AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM, (fr.paris.lutece.api.user.User) user ) );
735             strRetour [2] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
736                     AppointmentResourceIdService.PERMISSION_MODIFY_FORM, (fr.paris.lutece.api.user.User) user ) );
737             strRetour [3] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
738                     AppointmentResourceIdService.PERMISSION_MODIFY_FORM, (fr.paris.lutece.api.user.User) user ) );
739             strRetour [4] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
740                     AppointmentResourceIdService.PERMISSION_CHANGE_STATE, (fr.paris.lutece.api.user.User) user ) );
741             strRetour [5] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
742                     AppointmentResourceIdService.PERMISSION_DELETE_FORM, (fr.paris.lutece.api.user.User) user ) );
743             retour [nI++] = strRetour;
744         }
745 
746         return retour;
747     }
748 
749     /**
750      * Return the min starting time to display
751      * 
752      * @param minStartingTime
753      *            the min starting time
754      * @return 00 if the minstarting time is under 30, 30 otherwise
755      */
756     public static LocalTime getMinTimeToDisplay( LocalTime minStartingTime )
757     {
758         LocalTime minStartingTimeToDisplay;
759         if ( minStartingTime.getMinute( ) < THIRTY_MINUTES )
760         {
761             minStartingTimeToDisplay = LocalTime.of( minStartingTime.getHour( ), 0 );
762         }
763         else
764         {
765             minStartingTimeToDisplay = LocalTime.of( minStartingTime.getHour( ), THIRTY_MINUTES );
766         }
767         return minStartingTimeToDisplay;
768     }
769 
770     /**
771      * Return the max ending time to display
772      * 
773      * @param maxEndingTime
774      *            the max ending time
775      * @return 30 if the max ending time is under 30, otherwise the next hour
776      */
777     public static LocalTime getMaxTimeToDisplay( LocalTime maxEndingTime )
778     {
779         LocalTime maxEndingTimeToDisplay;
780         if ( maxEndingTime.getMinute( ) < THIRTY_MINUTES )
781         {
782             maxEndingTimeToDisplay = LocalTime.of( maxEndingTime.getHour( ), THIRTY_MINUTES );
783         }
784         else
785         {
786             maxEndingTimeToDisplay = LocalTime.of( maxEndingTime.getHour( ) + 1, 0 );
787         }
788         return maxEndingTimeToDisplay;
789     }
790 
791     /**
792      * Check if there are appointments impacted by the new week definition
793      * 
794      * @param listAppointment
795      *            the list of appointments
796      * @param nIdForm
797      *            the form Id
798      * @param dateOfModification
799      *            the date of modification (date of apply of the new week definition)
800      * @param appointmentForm
801      *            the appointment form
802      * @return true if there are appointments impacted
803      */
804     public static boolean checkNoAppointmentsImpacted( List<Appointment> listAppointment, int nIdForm, LocalDate dateOfModification,
805             AppointmentFormDTO appointmentForm )
806     {
807         ReservationRule previousReservationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( nIdForm, dateOfModification );
808         return checkNoAppointmentsImpacted( listAppointment, nIdForm, previousReservationRule.getIdReservationRule( ), appointmentForm );
809     }
810 
811     /**
812      * Check if there are appointments impacted by the new week definition
813      * 
814      * @param listAppointment
815      *            the list of appointments
816      * @param nIdForm
817      *            the form Id
818      * @param nIdreservationRule
819      *            the reservationRule id
820      * @param appointmentForm
821      *            the appointment form
822      * @return true if there are appointments impacted
823      */
824     public static boolean checkNoAppointmentsImpacted( List<Appointment> listAppointment, int nIdForm, int nIdreservationRule,
825             AppointmentFormDTO appointmentForm )
826     {
827         boolean bNoAppointmentsImpacted = true;
828         // Build the previous appointment form with the previous week
829         // definition and the previous reservation rule
830         AppointmentFormDTO previousAppointmentForm = FormService.buildAppointmentForm( nIdForm, nIdreservationRule );
831         // Need to check if the new definition week has more open days.
832         List<DayOfWeek> previousOpenDays = WorkingDayService.getOpenDays( previousAppointmentForm );
833         List<DayOfWeek> newOpenDays = WorkingDayService.getOpenDays( appointmentForm );
834         // If new open days
835         if ( newOpenDays.containsAll( previousOpenDays ) )
836         {
837             // Nothing to check
838         }
839         else
840         {
841             // Else we remove all the corresponding days
842             previousOpenDays.removeAll( newOpenDays );
843             // For the remaining days
844             // for each appointment, need to check if the appointment is
845             // not in the remaining open days
846             boolean bAppointmentOnOpenDays = false;
847             for ( Appointment appointment : listAppointment )
848             {
849                 for ( AppointmentSlot appSlot : appointment.getListAppointmentSlot( ) )
850                 {
851 
852                     Slot tempSlot = SlotService.findSlotById( appSlot.getIdSlot( ) );
853                     if ( previousOpenDays.contains( tempSlot.getStartingDateTime( ).getDayOfWeek( ) ) )
854                     {
855                         bAppointmentOnOpenDays = true;
856                         break;
857                     }
858                 }
859                 if ( bAppointmentOnOpenDays )
860                 {
861                     break;
862                 }
863             }
864             bNoAppointmentsImpacted = !bAppointmentOnOpenDays;
865         }
866         LocalTime newStartingTime = LocalTime.parse( appointmentForm.getTimeStart( ) );
867         LocalTime newEndingTime = LocalTime.parse( appointmentForm.getTimeEnd( ) );
868         LocalTime oldStartingTime = LocalTime.parse( previousAppointmentForm.getTimeStart( ) );
869         LocalTime oldEndingTime = LocalTime.parse( previousAppointmentForm.getTimeEnd( ) );
870         // If we have changed the duration of an appointment
871         if ( appointmentForm.getDurationAppointments( ) != previousAppointmentForm.getDurationAppointments( ) )
872         {
873             bNoAppointmentsImpacted = false;
874         }
875         // If we have change the open hours
876 
877         if ( !newStartingTime.equals( oldStartingTime ) || !newEndingTime.equals( oldEndingTime ) )
878         {
879             bNoAppointmentsImpacted = false;
880         }
881 
882         return bNoAppointmentsImpacted;
883     }
884 
885     /**
886      * Check if there are appointments impacted by the new week definition
887      * 
888      * @param listSlotsImpacted
889      *            the list of slot impacted
890      * @param appointmentForm
891      *            the appointment form
892      * @return true if there are appointments impacted
893      */
894     public static boolean checkNoAppointmentsImpacted( List<Slot> listSlotWithAppointment, AppointmentFormDTO appointmentForm )
895     {
896         // Build the previous appointment form with the previous week
897         // definition and the previous reservation rule
898         AppointmentFormDTO previousAppointmentForm = FormService.buildAppointmentForm( appointmentForm.getIdForm( ), appointmentForm.getIdReservationRule( ) );
899         // Need to check if the new definition week has more open days.
900         List<DayOfWeek> previousOpenDays = WorkingDayService.getOpenDays( previousAppointmentForm );
901         List<DayOfWeek> newOpenDays = WorkingDayService.getOpenDays( appointmentForm );
902         // If new open days
903         if ( newOpenDays.containsAll( previousOpenDays ) )
904         {
905             // Nothing to check
906         }
907         else
908         {
909             // Else we remove all the corresponding days
910             previousOpenDays.removeAll( newOpenDays );
911             // For the remaining days
912             // for each appointment, need to check if the appointment is
913             // not in the remaining open days
914 
915             for ( Slot tempSlot : listSlotWithAppointment )
916             {
917                 if ( previousOpenDays.contains( tempSlot.getStartingDateTime( ).getDayOfWeek( ) ) )
918                 {
919                     return false;
920                 }
921             }
922 
923         }
924         LocalTime newStartingTime = LocalTime.parse( appointmentForm.getTimeStart( ) );
925         LocalTime newEndingTime = LocalTime.parse( appointmentForm.getTimeEnd( ) );
926         LocalTime oldStartingTime = LocalTime.parse( previousAppointmentForm.getTimeStart( ) );
927         LocalTime oldEndingTime = LocalTime.parse( previousAppointmentForm.getTimeEnd( ) );
928         // If we have changed the duration of an appointment
929         if ( appointmentForm.getDurationAppointments( ) != previousAppointmentForm.getDurationAppointments( ) )
930         {
931             return false;
932         }
933         // If we have change the open hours
934 
935         return !newStartingTime.equals( oldStartingTime ) || !newEndingTime.equals( oldEndingTime );
936     }
937 
938     /**
939      * Check if there are appointments impacted by the new week definition
940      * 
941      * @param listSlotsImpacted
942      *            the list of slot impacted
943      * @param newReservationRule
944      *            the reservation rule
945      * @return true if there are no appointments impacted
946      */
947     public static boolean checkNoAppointmentsImpacted( List<Slot> listSlotsImpacted, int idReservationRule )
948     {
949 
950         List<WorkingDay> listWorkingDay = WorkingDayService.findListWorkingDayByWeekDefinitionRule( idReservationRule );
951         for ( Slot slot : listSlotsImpacted )
952         {
953             WorkingDay workingDay = listWorkingDay.stream( ).filter( day -> day.getDayOfWeek( ) == slot.getDate( ).getDayOfWeek( ).getValue( ) ).findFirst( )
954                     .orElse( null );
955             if ( workingDay != null )
956             {
957 
958                 if ( workingDay.getListTimeSlot( ).stream( ).noneMatch(
959                         time -> slot.getStartingTime( ).equals( time.getStartingTime( ) ) && slot.getEndingTime( ).equals( time.getEndingTime( ) ) ) )
960                 {
961 
962                     return false;
963                 }
964 
965             }
966             else
967             {
968 
969                 return false;
970             }
971         }
972 
973         return true;
974     }
975 
976     /**
977      * Check that there is no validated appointments on a slot
978      * 
979      * @param slot
980      *            the slot
981      * @return true if there are no validated appointments on this slot, false otherwise
982      */
983     public static boolean checkNoValidatedAppointmentsOnThisSlot( Slot slot )
984     {
985         boolean bNoValidatedAppointmentsOnThisSlot = true;
986         List<Appointment> listAppointmentsOnThisSlot = AppointmentService.findListAppointmentBySlot( slot.getIdSlot( ) );
987         if ( CollectionUtils.isNotEmpty( listAppointmentsOnThisSlot ) )
988         {
989             listAppointmentsOnThisSlot = listAppointmentsOnThisSlot.stream( ).filter( a -> !a.getIsCancelled( ) ).collect( Collectors.toList( ) );
990         }
991         if ( CollectionUtils.isNotEmpty( listAppointmentsOnThisSlot ) )
992         {
993             bNoValidatedAppointmentsOnThisSlot = false;
994         }
995         return bNoValidatedAppointmentsOnThisSlot;
996     }
997 
998     /**
999      * Return the slots impacted by the modification of this time slot
1000      * 
1001      * @param timeSlot
1002      *            the time slot
1003      * @param nIdForm
1004      *            the form id
1005      * @param nIdWeekDefinition
1006      *            the week definition id
1007      * @param bShiftSlot
1008      *            the boolean value for the shift
1009      * @return the list of slots impacted
1010      */
1011     public static List<Slot> findSlotsImpactedByThisTimeSlot( TimeSlot timeSlot, int nIdForm, int nIdWeekDefinition, boolean bShiftSlot )
1012     {
1013         List<Slot> listSlotsImpacted = null;
1014         // Get the weekDefinition that is currently modified
1015         WeekDefinition currentModifiedWeekDefinition = WeekDefinitionService.findWeekDefinitionById( nIdWeekDefinition );
1016         // We have an upper bound to search with
1017         List<Slot> listSlots = SlotService.findSlotsByIdFormAndDateRange( nIdForm, currentModifiedWeekDefinition.getDateOfApply( ).atStartOfDay( ),
1018                 currentModifiedWeekDefinition.getEndingDateOfApply( ).atTime( LocalTime.MAX ) );
1019         // Need to check if the modification of the time slot or the typical
1020         // week impacts these slots
1021         WorkingDay workingDay = WorkingDayService.findWorkingDayLightById( timeSlot.getIdWorkingDay( ) );
1022         // Filter all the slots with the working day and the starting time
1023         // ending time of the time slot
1024         // The begin time of the slot can be before or after the begin time
1025         // of the time slot
1026         // and the ending time of the slot can be before or after the ending
1027         // time of the time slot (specific slot)
1028 
1029         // If shiftTimeSlot is checked, need to check all the slots impacted
1030         // until the end of the day
1031         if ( bShiftSlot )
1032         {
1033             listSlotsImpacted = listSlots.stream( )
1034                     .filter( slot -> ( ( slot.getStartingDateTime( ).getDayOfWeek( ) == DayOfWeek.of( workingDay.getDayOfWeek( ) ) )
1035                             && ( !slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
1036                                     || ( slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
1037                                             && ( slot.getEndingTime( ).isAfter( timeSlot.getStartingTime( ) ) ) ) ) ) )
1038                     .collect( Collectors.toList( ) );
1039         }
1040         else
1041         {
1042             listSlotsImpacted = listSlots.stream( )
1043                     .filter( slot -> ( slot.getStartingDateTime( ).getDayOfWeek( ) == DayOfWeek.of( workingDay.getDayOfWeek( ) ) )
1044                             && ( slot.getStartingTime( ).equals( timeSlot.getStartingTime( ) )
1045                                     || ( slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
1046                                             && ( slot.getEndingTime( ).isAfter( timeSlot.getStartingTime( ) ) ) )
1047                                     || ( slot.getStartingTime( ).isAfter( timeSlot.getStartingTime( ) )
1048                                             && ( !slot.getEndingTime( ).isAfter( timeSlot.getEndingTime( ) ) ) ) ) )
1049                     .collect( Collectors.toList( ) );
1050         }
1051 
1052         return listSlotsImpacted;
1053     }
1054 
1055     public static LocalDateTime getStartingDateTime( Appointment appointmentDTO )
1056     {
1057 
1058         List<Slot> listSlot = appointmentDTO.getSlot( );
1059         if ( CollectionUtils.isNotEmpty( listSlot ) )
1060         {
1061             Slot slot = listSlot.stream( ).min( Comparator.comparing( Slot::getStartingDateTime ) ).orElse( listSlot.get( 0 ) );
1062             return slot.getStartingDateTime( );
1063         }
1064 
1065         return null;
1066     }
1067 
1068     public static LocalDateTime getEndingDateTime( Appointment appointmentDTO )
1069     {
1070 
1071         List<Slot> listSlot = appointmentDTO.getSlot( );
1072         if ( listSlot != null && !listSlot.isEmpty( ) )
1073         {
1074 
1075             Slot slot = listSlot.stream( ).max( Comparator.comparing( Slot::getStartingDateTime ) ).orElse( listSlot.get( 0 ) );
1076             return slot.getEndingDateTime( );
1077         }
1078 
1079         return null;
1080     }
1081 
1082     /**
1083      * return true if all slots are consecutive
1084      * 
1085      * @param allSlots
1086      *            the list of slots
1087      * @return true if all slots are consecutive
1088      */
1089     public static boolean isConsecutiveSlots( List<Slot> allSlots )
1090     {
1091         Slot slot = null;
1092         for ( Slot nextSlot : allSlots )
1093         {
1094             if ( nextSlot == null || ( slot != null && !Objects.equals( slot.getEndingDateTime( ), nextSlot.getStartingDateTime( ) ) ) )
1095             {
1096                 return false;
1097             }
1098             slot = nextSlot;
1099         }
1100         return true;
1101     }
1102 
1103     /**
1104      * Fill the reservation rule object with the corresponding values of an appointmentForm DTO
1105      * 
1106      * @param reservationRuleDTO
1107      *            the reservation rule object to fill in
1108      * @param appointmentForm
1109      *            the appointmentForm DTO
1110      * 
1111      */
1112     public static void fillInReservationRuleAdvancedParam( ReservationRuleDTO reservationRuleDTO, AppointmentFormDTO appointmentForm )
1113     {
1114         reservationRuleDTO.setMaxCapacityPerSlot( appointmentForm.getMaxCapacityPerSlot( ) );
1115         reservationRuleDTO.setMaxPeoplePerAppointment( appointmentForm.getMaxPeoplePerAppointment( ) );
1116         reservationRuleDTO.setName( appointmentForm.getName( ) );
1117         reservationRuleDTO.setDescriptionRule( appointmentForm.getDescriptionRule( ) );
1118         reservationRuleDTO.setColor( appointmentForm.getColor( ) );
1119         reservationRuleDTO.setDurationAppointments( appointmentForm.getDurationAppointments( ) );
1120         reservationRuleDTO.setTimeEnd( appointmentForm.getTimeEnd( ) );
1121         reservationRuleDTO.setTimeStart( appointmentForm.getTimeStart( ) );
1122 
1123         reservationRuleDTO.setIdForm( appointmentForm.getIdForm( ) );
1124     }
1125 
1126     /**
1127      * check if one of the weeks in the list is open on the FO calendar
1128      * 
1129      * @param appointmentFormDTO
1130      *            the appointment form
1131      * @param listWeek
1132      *            the list of weekDefinition
1133      * @param locale
1134      *            the locale
1135      * @return true if one of the weeks in the list is open on the FO calendar
1136      */
1137     public static boolean weekIsOpenInFO( AppointmentFormDTO appointmentFormDTO, List<WeekDefinition> listWeek, Locale locale )
1138     {
1139 
1140         if ( appointmentFormDTO.getIsActive( ) )
1141         {
1142             Date startingValidityDate = appointmentFormDTO.getDateStartValidity( );
1143             LocalDate startingDateOfDisplay = LocalDate.now( );
1144 
1145             if ( startingValidityDate != null && startingValidityDate.toLocalDate( ).isAfter( startingDateOfDisplay ) )
1146             {
1147                 startingDateOfDisplay = startingValidityDate.toLocalDate( );
1148             }
1149             // Calculate the ending date of display with the nb weeks to display
1150             // since today
1151             // We calculate the number of weeks including the current week, so it
1152             // will end to the (n) next sunday
1153             LocalDate dateOfSunday = startingDateOfDisplay.with( WeekFields.of( locale ).dayOfWeek( ), DayOfWeek.SUNDAY.getValue( ) );
1154             LocalDate endingDateOfDisplay = dateOfSunday.plusWeeks( (long) appointmentFormDTO.getNbWeeksToDisplay( ) - 1 );
1155             Date endingValidityDate = appointmentFormDTO.getDateEndValidity( );
1156             if ( endingValidityDate != null && endingDateOfDisplay.isAfter( endingValidityDate.toLocalDate( ) ) )
1157             {
1158                 endingDateOfDisplay = endingValidityDate.toLocalDate( );
1159             }
1160             if ( startingDateOfDisplay.isAfter( endingDateOfDisplay ) )
1161             {
1162 
1163                 return false;
1164             }
1165 
1166             for ( WeekDefinition week : listWeek )
1167             {
1168 
1169                 if ( ( week.getDateOfApply( ).isBefore( endingDateOfDisplay ) || week.getDateOfApply( ).isEqual( endingDateOfDisplay ) )
1170                         && ( week.getEndingDateOfApply( ).isAfter( startingDateOfDisplay ) || week.getEndingDateOfApply( ).isEqual( startingDateOfDisplay ) ) )
1171                 {
1172 
1173                     return true;
1174                 }
1175             }
1176         }
1177 
1178         return false;
1179     }
1180 
1181     /**
1182      * Check if list of slot is builded correctly
1183      * 
1184      * @param nIdForm
1185      *            the id form
1186      * @param listSlots
1187      *            the list of slot to check
1188      * @return true if the listSlots is builded correctly
1189      */
1190     public static boolean checkListSlotIsBuildedCorrectly( int nIdForm, List<Slot> listSlots )
1191     {
1192         if ( !CollectionUtils.isEmpty( listSlots ) )
1193         {
1194 
1195             LocalDateTime minDate = listSlots.stream( ).map( Slot::getStartingDateTime ).min( LocalDateTime::compareTo ).orElse( null );
1196             LocalDateTime maxDate = listSlots.stream( ).map( Slot::getStartingDateTime ).max( LocalDateTime::compareTo ).orElse( null );
1197             if ( minDate != null && maxDate != null )
1198             {
1199                 List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
1200                 Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );
1201                 List<Slot> listSlotBuilded = SlotService.buildListSlot( nIdForm, mapReservationRule, minDate.toLocalDate( ), maxDate.toLocalDate( ) );
1202                 for ( Slot slt : listSlots )
1203                 {
1204                     if ( listSlotBuilded.stream( ).noneMatch( slot -> slt.getStartingDateTime( ).isEqual( slot.getStartingDateTime( ) )
1205                             && slt.getEndingDateTime( ).isEqual( slot.getEndingDateTime( ) ) ) )
1206                     {
1207                         return false;
1208                     }
1209                 }
1210             }
1211         }
1212         return true;
1213     }
1214 
1215     /**
1216      * The following method shuts down the _executorService in two phases, first by calling shutdown to reject incoming tasks, and then calling shutdownNow, if
1217      * necessary, to cancel any lingering tasks:
1218      */
1219     public static void shutdownSecheduledExecutor( )
1220     {
1221         _secheduledExecutor.shutdown( );
1222         try
1223         {
1224             if ( !_secheduledExecutor.awaitTermination( 60, TimeUnit.SECONDS ) )
1225             {
1226                 _secheduledExecutor.shutdownNow( );
1227             }
1228         }
1229         catch( InterruptedException e )
1230         {
1231             // (Re-)Cancel if current thread also interrupted
1232             AppLogService.error( e.getMessage( ), e );
1233             _secheduledExecutor.shutdownNow( );
1234             Thread.currentThread().interrupt();
1235         }
1236 
1237     }
1238     
1239     /**
1240      * Build an appointment dto from an appointment business object
1241      * 
1242      * @param appointment
1243      *            the appointment business object
1244      * @return the appointment DTO
1245      */
1246     public static AppointmentDTO buildAppointmentDTO( Appointment appointment )
1247     {
1248         AppointmentDTOb/dto/AppointmentDTO.html#AppointmentDTO">AppointmentDTO appointmentDTO = new AppointmentDTO( );
1249         User user = appointment.getUser();
1250         
1251         appointmentDTO.setIdForm( appointment.getSlot( ).get( 0 ).getIdForm( ) );
1252         appointmentDTO.setIdUser( appointment.getIdUser( ) );
1253         appointmentDTO.setListAppointmentSlot( appointment.getListAppointmentSlot( ) );
1254         appointmentDTO.setIdAppointment( appointment.getIdAppointment( ) );
1255        
1256         if(user != null) {
1257 	        appointmentDTO.setFirstName( user.getFirstName( ) );
1258 	        appointmentDTO.setLastName( user.getLastName( ) );
1259 	        appointmentDTO.setEmail( user.getEmail( ) );
1260 	        appointmentDTO.setPhoneNumber( user.getPhoneNumber( ) );
1261 	        appointmentDTO.setGuid( user.getGuid( ) );
1262 	        appointmentDTO.setReference( appointment.getReference( ) );
1263         }
1264         
1265         LocalDateTime startingDateTime = AppointmentUtilities.getStartingDateTime( appointment );
1266         LocalDateTime endingDateTime = AppointmentUtilities.getEndingDateTime( appointment );
1267         
1268         appointmentDTO.setStartingDateTime( startingDateTime );
1269         appointmentDTO.setEndingDateTime( endingDateTime );
1270         appointmentDTO.setDateOfTheAppointment( startingDateTime.toLocalDate( ).format( Utilities.getFormatter( ) ) );
1271         appointmentDTO.setStartingTime( startingDateTime.toLocalTime( ) );
1272         appointmentDTO.setEndingTime( endingDateTime.toLocalTime( ) );
1273         appointmentDTO.setIsCancelled( appointment.getIsCancelled( ) );
1274         appointmentDTO.setNbBookedSeats( appointment.getNbPlaces( ) );
1275         appointment.getSlot().forEach(SlotService::addDateAndTimeToSlot);
1276         appointmentDTO.setSlot( appointment.getSlot( ) );
1277         appointmentDTO.setUser( appointment.getUser( ) );
1278  
1279         appointmentDTO.setAdminUserCreate( appointment.getAdminUserCreate( ) );
1280         appointmentDTO.setDateAppointmentTaken( appointment.getDateAppointmentTaken( ) );
1281        
1282         return appointmentDTO;
1283     }
1284 
1285     /**
1286      * Set the appointment's phone number values, if a user entered them in a Generic Attributes
1287      * of type 'Phone Number'. These values are retrieved from the appointment's responses.
1288      * 
1289      * @param appointment
1290      *            the appointment to process
1291      */
1292     public static void setAppointmentPhoneNumberValuesFromResponse( AppointmentDTO appointment )
1293     {
1294     	// Retrieve the appointment's responses
1295     	List<Response> listAppointmentResponses = appointment.getListResponse( );
1296 
1297     	if( CollectionUtils.isNotEmpty( listAppointmentResponses ) )
1298     	{
1299     		try
1300     		{
1301     			// Retrieve the user's phone number value(s) from the appointment's responses
1302     			List<String> listPhoneNumbers = listAppointmentResponses.stream( )
1303     					.filter( elem -> elem.getEntry( ) != null && elem.getEntry( ).getEntryType( ).getBeanName( ).contentEquals( AppointmentUtilities.CONSTANT_GENERIC_ATTRIBUTE_TYPE_PHONE_NAME ) )
1304     					.map( Response::getResponseValue )
1305     					.filter( value -> value != null && !value.trim( ).isEmpty( ) )
1306     					.map( String::trim )
1307     					.collect( Collectors.toList( ) );
1308 
1309     			if( !listPhoneNumbers.isEmpty( ) )
1310     			{
1311     				// Set the current appointment's phone number value(s). Use a separator if there are multiple values
1312     				String strPhoneNumbers = StringUtils.join( listPhoneNumbers, CONSTANT_PHONE_NUMBERS_SEPARATOR );
1313     				appointment.setPhoneNumber( strPhoneNumbers );
1314     			}
1315     		}
1316     		catch ( NullPointerException e)
1317     		{
1318                 AppLogService.error( "Error when retrieving appointment's phone number value", e );
1319     		}
1320     	}
1321     }
1322 }