AppointmentUtilities.java

/*
 * Copyright (c) 2002-2022, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.appointment.service;

import java.sql.Date;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
import fr.paris.lutece.plugins.appointment.business.appointment.AppointmentSlot;
import fr.paris.lutece.plugins.appointment.business.category.Category;
import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
import fr.paris.lutece.plugins.appointment.business.slot.Slot;
import fr.paris.lutece.plugins.appointment.business.user.User;
import fr.paris.lutece.plugins.appointment.service.lock.SlotEditTask;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFilterDTO;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
import fr.paris.lutece.plugins.appointment.web.dto.ReservationRuleDTO;
import fr.paris.lutece.plugins.appointment.web.dto.ResponseRecapDTO;
import fr.paris.lutece.plugins.genericattributes.business.Entry;
import fr.paris.lutece.plugins.genericattributes.business.EntryFilter;
import fr.paris.lutece.plugins.genericattributes.business.EntryHome;
import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;
import fr.paris.lutece.plugins.genericattributes.business.Response;
import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
import fr.paris.lutece.plugins.genericattributes.service.entrytype.IEntryTypeService;
import fr.paris.lutece.plugins.genericattributes.util.GenericAttributesUtils;
import fr.paris.lutece.portal.business.user.AdminUser;
import fr.paris.lutece.portal.business.user.AdminUserHome;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.rbac.RBACService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.beanvalidation.BeanValidationUtil;

/**
 * Utility class for Appointment Mutualize methods between MVCApplication and MVCAdminJspBean
 * 
 * @author Laurent Payen
 *
 */
public final class AppointmentUtilities
{

    public static final String ERROR_MESSAGE_EMPTY_CONFIRM_EMAIL = "appointment.validation.appointment.EmailConfirmation.email";
    public static final String ERROR_MESSAGE_CONFIRM_EMAIL = "appointment.message.error.confirmEmail";
    public static final String ERROR_MESSAGE_DATE_APPOINTMENT = "appointment.message.error.dateAppointment";
    public static final String ERROR_MESSAGE_EMPTY_EMAIL = "appointment.validation.appointment.Email.notEmpty";
    public static final String ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.notEmpty";
    public static final String ERROR_MESSAGE_FORMAT_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.notNumberFormat";
    public static final String ERROR_MESSAGE_ERROR_NB_BOOKED_SEAT = "appointment.validation.appointment.NbBookedSeat.error";

    public static final String MARK_PERMISSION_ADD_COMMENT = "permission_add_comment";
    public static final String MARK_PERMISSION_MODERATE_COMMENT = "permission_moderate_comment";
    public static final String MARK_PERMISSION_ACCESS_CODE = "permission_access_code";
    public static final String OLD_APPOINTMENT_DTO = "oldAppointment";
    public static final String SESSION_TASK_TIMER_SLOT = "appointment.session.task.timer.slot";

    public static final String PROPERTY_DEFAULT_EXPIRED_TIME_EDIT_APPOINTMENT = "appointment.edit.expired.time";

    public static final int THIRTY_MINUTES = 30;
    // We don't need to instantiate an ScheduledExecutorService with the removeOnCancel=true because we are using non-periodic and short time tasks.
    // The getTask call removes the task from the queue.
    private static final ScheduledExecutorService _secheduledExecutor = Executors
            .newSingleThreadScheduledExecutor( r -> new Thread( r, "Lutece-AppointmentSecheduledExecutor-thread" ) );

    // CONSTANTS
    // Name of the phone number's generic attribute bean
    public static final String CONSTANT_GENERIC_ATTRIBUTE_TYPE_PHONE_NAME = "appointment.entryTypePhone";
    // Characters used to separate phone numbers, in the case where a form allows multiple entries
    public static final String CONSTANT_PHONE_NUMBERS_SEPARATOR = ", ";

    /**
     * Private constructor - this class does not need to be instantiated
     */
    private AppointmentUtilities( )
    {
    }

    /**
     * Check that the email is correct and matches the confirm email
     * 
     * @param strEmail
     *            the email
     * @param strConfirmEmail
     *            the confirm email
     * @param form
     *            the form
     * @param locale
     *            the local
     * @param listFormErrors
     *            the list of errors that can be fill in with the errors found for the email
     */
    public static void checkEmail( String strEmail, String strConfirmEmail, AppointmentFormDTO form, Locale locale, List<GenericAttributeError> listFormErrors )
    {
        if ( form.getEnableMandatoryEmail( ) )
        {
            if ( StringUtils.isEmpty( strEmail ) )
            {
                GenericAttributeError genAttError = new GenericAttributeError( );
                genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_EMAIL, locale ) );
                listFormErrors.add( genAttError );
            }
            if ( StringUtils.isEmpty( strConfirmEmail ) )
            {
                GenericAttributeError genAttError = new GenericAttributeError( );
                genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_CONFIRM_EMAIL, locale ) );
                listFormErrors.add( genAttError );
            }
        }
        if ( !StringUtils.equalsIgnoreCase( strEmail, strConfirmEmail ) )
        {
            GenericAttributeError genAttError = new GenericAttributeError( );
            genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_CONFIRM_EMAIL, locale ) );
            listFormErrors.add( genAttError );
        }
    }

    /**
     * Check that the date of the appointment we try to take is not in the past
     * 
     * @param appointmentDTO
     *            the appointment
     * @param locale
     *            the local
     * @param listFormErrors
     *            the list of errors that can be fill in with the error found with the date
     */
    public static void checkDateOfTheAppointmentIsNotBeforeNow( AppointmentDTO appointmentDTO, Locale locale, List<GenericAttributeError> listFormErrors )
    {
        LocalDateTime startingDateTime = getStartingDateTime( appointmentDTO );
        if ( startingDateTime == null || startingDateTime.toLocalDate( ).isBefore( LocalDate.now( ) ) )
        {
            GenericAttributeError genAttError = new GenericAttributeError( );
            genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_DATE_APPOINTMENT, locale ) );
            listFormErrors.add( genAttError );
        }
    }

    /**
     * Check that the delay between two appointments for the same use has been respected
     * 
     * @param appointmentDTO
     *            the appointment
     * @param strEmail
     *            the email
     * @param form
     *            the form
     * @return false if the delay is not respected
     */
    public static boolean checkNbDaysBetweenTwoAppointments( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
    {
        boolean bCheckPassed = true;
        int nbDaysBetweenTwoAppointments = form.getNbDaysBeforeNewAppointment( );
        if ( nbDaysBetweenTwoAppointments != 0 )
        {
            List<Slot> listSlots = getSlotsByEmail( strEmail, appointmentDTO.getIdAppointment( ) );
            if ( CollectionUtils.isNotEmpty( listSlots ) )
            {
                // Get the last appointment date for this form
                listSlots = listSlots.stream( ).filter( s -> s.getIdForm( ) == form.getIdForm( ) ).collect( Collectors.toList( ) );
                if ( CollectionUtils.isNotEmpty( listSlots ) )
                {
                    LocalDateTime dateOfTheLastAppointment = listSlots.stream( ).map( Slot::getStartingDateTime ).max( LocalDateTime::compareTo )
                            .orElse( null );

                    // Check the number of days between this appointment and
                    // the last appointment the user has taken
                    LocalDateTime dateOfTheAppointment = getStartingDateTime( appointmentDTO );
                    if ( dateOfTheLastAppointment != null && Math
                            .abs( dateOfTheLastAppointment.toLocalDate( ).until( dateOfTheAppointment, ChronoUnit.DAYS ) ) <= nbDaysBetweenTwoAppointments )
                    {
                        bCheckPassed = false;
                    }
                }
            }
        }
        return bCheckPassed;
    }

    /**
     * Check that the delay between two appointments for the same use has been respected
     * 
     * @param appointmentDTO
     *            the appointment
     * @param strEmail
     *            the email
     * @param form
     *            the form
     * @return false if the delay is not respected
     */
    public static boolean checkNbDaysBetweenTwoAppointmentsTaken( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
    {
        boolean bCheckPassed = true;
        int nbDaysBetweenTwoAppointments = form.getNbDaysBeforeNewAppointment( );
        if ( nbDaysBetweenTwoAppointments != 0 && StringUtils.isNotEmpty( strEmail ) )
        {
            AppointmentFilterDTO filter = new AppointmentFilterDTO( );
            filter.setEmail( strEmail );
            filter.setStatus( 0 );
            filter.setIdForm( form.getIdForm( ) );
            List<Appointment> listAppointment = AppointmentService.findListAppointmentsByFilter( filter );
            // If we modify an appointment, we remove the
            // appointment that we currently edit
            if ( appointmentDTO.getIdAppointment( ) != 0 )
            {
                listAppointment.removeIf( a -> a.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
            }

            if ( CollectionUtils.isNotEmpty( listAppointment ) )
            {

                LocalDateTime dateOfTheLastAppointmentTaken = listAppointment.stream( ).map( Appointment::getDateAppointmentTaken )
                        .max( LocalDateTime::compareTo ).orElse( null );

                if ( dateOfTheLastAppointmentTaken != null
                        && Math.abs( dateOfTheLastAppointmentTaken.until( LocalDateTime.now( ), ChronoUnit.DAYS ) ) < nbDaysBetweenTwoAppointments )
                {
                    bCheckPassed = false;
                }

            }
        }
        return bCheckPassed;
    }

    /**
     * Get the appointment of a user appointment
     * 
     * @param strEmail
     *            the user's email
     * @param idAppointment
     *            the id of the appointment
     * @return the list of appointment
     */
    private static List<Appointment> getAppointmentByEmail( String strEmail, int idAppointment )
    {
        List<Appointment> listAppointment = new ArrayList<>( );
        if ( StringUtils.isNotEmpty( strEmail ) )
        {
            // Looking for existing users with this email
            List<User> listUsers = UserService.findUsersByEmail( strEmail );
            if ( listUsers != null )
            {
                // For each User
                for ( User user : listUsers )
                {
                    // looking for its appointment
                    listAppointment.addAll( AppointmentService.findListAppointmentByUserId( user.getIdUser( ) ) );
                }

                // If we modify an appointment, we remove the
                // appointment that we currently edit
                if ( idAppointment != 0 )
                {
                    listAppointment = listAppointment.stream( ).filter( a -> a.getIdAppointment( ) != idAppointment ).collect( Collectors.toList( ) );
                }

            }
        }
        return listAppointment;
    }

    /**
     * Get the slot of a user appointment
     * 
     * @param strEmail
     *            the user's email
     * @param idAppointment
     *            the id of the appointment
     * @return the list of slots
     */
    private static List<Slot> getSlotsByEmail( String strEmail, int idAppointment )
    {
        List<Slot> listSlots = new ArrayList<>( );
        if ( StringUtils.isNotEmpty( strEmail ) )
        {
            List<Appointment> listAppointment = getAppointmentByEmail( strEmail, idAppointment );
            if ( CollectionUtils.isNotEmpty( listAppointment ) )
            {
                // I know we could have a join sql query, but I don't
                // want to join the appointment table with the slot
                // table, it's too big and not efficient

                for ( Appointment appointment : listAppointment )
                {
                    if ( !appointment.getIsCancelled( ) )
                    {
                        listSlots = SlotService.findListSlotByIdAppointment( appointment.getIdAppointment( ) );
                    }
                }

            }

        }
        return listSlots;
    }

    /**
     * Check that the number of appointments on a defined period is not above the maximum authorized
     * 
     * @param appointmentDTO
     *            the appointment
     * @param strEmail
     *            the email of the user
     * @param form
     *            the form
     * @return false if the number of appointments is above the maximum authorized on the defined period
     */
    public static boolean checkNbMaxAppointmentsOnAGivenPeriod( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form )
    {
        if ( form.getNbMaxAppointmentsPerUser( ) > 0 && StringUtils.isNotEmpty( strEmail ) )
        {
            // Get the date of the future appointment
            LocalDateTime startingDateTime = getStartingDateTime( appointmentDTO );
            if ( startingDateTime == null )
            {
                AppLogService.error( "Error checkNbMaxAppointmentsOnAGivenPeriod, startingDateTime is null" );
                return false;
            }
            LocalDate dateOfTheAppointment = startingDateTime.toLocalDate( );

            AppointmentFilterDTO filter = new AppointmentFilterDTO( );
            filter.setEmail( strEmail );
            filter.setIdForm( form.getIdForm( ) );
            filter.setStatus( 0 );
            if ( form.getNbDaysForMaxAppointmentsPerUser( ) > 0 )
            {
                filter.setStartingDateOfSearch( Date.valueOf( dateOfTheAppointment.minusDays( (long) form.getNbDaysForMaxAppointmentsPerUser( ) - 1 ) ) );
                filter.setEndingDateOfSearch( Date.valueOf( dateOfTheAppointment.plusDays( (long) form.getNbDaysForMaxAppointmentsPerUser( ) - 1 ) ) );
            }
            List<AppointmentDTO> listAppointmentsDTO = AppointmentService.findListAppointmentsDTOByFilter( filter );
            // If we modify an appointment, we remove the
            // appointment that we currently edit
            if ( appointmentDTO.getIdAppointment( ) != 0 )
            {

                listAppointmentsDTO.removeIf( appt -> appt.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
            }

            if ( CollectionUtils.isNotEmpty( listAppointmentsDTO ) )
            {
                if ( form.getNbDaysForMaxAppointmentsPerUser( ) > 0 )
                {

                    List<AppointmentDTO> listAppointmentsBefore = listAppointmentsDTO.stream( )
                            .filter( appt -> appt.getStartingDateTime( ).toLocalDate( ).isBefore( dateOfTheAppointment )
                                    || appt.getStartingDateTime( ).toLocalDate( ).isEqual( dateOfTheAppointment ) )
                            .collect( Collectors.toList( ) );
                    List<AppointmentDTO> listAppointmentsAfter = listAppointmentsDTO.stream( )
                            .filter( appt -> appt.getStartingDateTime( ).toLocalDate( ).isAfter( dateOfTheAppointment )
                                    || appt.getStartingDateTime( ).toLocalDate( ).isEqual( dateOfTheAppointment ) )
                            .collect( Collectors.toList( ) );

                    if ( listAppointmentsBefore.size( ) >= form.getNbMaxAppointmentsPerUser( )
                            || listAppointmentsAfter.size( ) >= form.getNbMaxAppointmentsPerUser( ) )
                    {
                        return false;
                    }
                }
                else
                    if ( listAppointmentsDTO.size( ) >= form.getNbMaxAppointmentsPerUser( ) )
                    {

                        return false;
                    }
            }
        }
        return true;

    }

    /**
     * Check that the number of appointments on a defined category is not above the maximum authorized
     * 
     * @param appointmentDTO
     *            the appointment
     * @param strEmail
     *            the email of the user
     * @param form
     *            the form
     * @return false if the number of appointments is above the maximum authorized on the defined category
     */
    public static boolean checkNbMaxAppointmentsDefinedOnCategory( AppointmentDTO appointmentDTO, String strEmail, AppointmentFormDTO form,
            List<AppointmentDTO> listAppointments )
    {
        if ( form.getIdCategory( ) != 0 && StringUtils.isNotEmpty( strEmail ) )
        {
            Category category = CategoryService.findCategoryById( form.getIdCategory( ) );
            if ( category != null && category.getNbMaxAppointmentsPerUser( ) > 0 )
            {
                LocalDateTime now = LocalDateTime.now( );
                List<AppointmentDTO> listAppointmentsDTO = AppointmentService.findAppointmentByMailAndCategory( category.getIdCategory( ), strEmail );
                listAppointmentsDTO.removeIf( appt -> appt.getEndingDateTime( ).isBefore( now ) || appt.getIsCancelled( ) );
                // If we modify an appointment, we remove the
                // appointment that we currently edit
                if ( appointmentDTO.getIdAppointment( ) != 0 )
                {

                    listAppointmentsDTO.removeIf( appt -> appt.getIdAppointment( ) != appointmentDTO.getIdAppointment( ) );
                }
                listAppointments.addAll( listAppointmentsDTO );
                if ( CollectionUtils.isNotEmpty( listAppointmentsDTO ) && listAppointmentsDTO.size( ) >= category.getNbMaxAppointmentsPerUser( ) )
                {
                    return false;

                }
            }
        }
        return true;

    }

    /**
     * Check and validate all the rules for the number of booked seats asked
     * 
     * @param strNbBookedSeats
     *            the number of booked seats
     * @param form
     *            the form
     * @param nbRemainingPlaces
     *            the number of remaining places on the slot asked
     * @param locale
     *            the locale
     * @param listFormErrors
     *            the list of errors that can be fill in with the errors found for the number of booked seats
     * @return
     */
    public static int checkAndReturnNbBookedSeats( String strNbBookedSeats, AppointmentFormDTO form, AppointmentDTO appointmentDTO, Locale locale,
            List<GenericAttributeError> listFormErrors )
    {
        int nbBookedSeats = 1;
        if ( StringUtils.isEmpty( strNbBookedSeats ) && form.getMaxPeoplePerAppointment( ) > 1 )
        {
            GenericAttributeError genAttError = new GenericAttributeError( );
            genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT, locale ) );
            listFormErrors.add( genAttError );
        }
        if ( StringUtils.isNotEmpty( strNbBookedSeats ) )
        {
            try
            {
                nbBookedSeats = Integer.parseInt( strNbBookedSeats );
            }
            catch( NumberFormatException | NullPointerException e )
            {
                GenericAttributeError genAttError = new GenericAttributeError( );
                genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_FORMAT_NB_BOOKED_SEAT, locale ) );
                listFormErrors.add( genAttError );
            }
        }
        // if it's a new appointment, need to check if the number of booked
        // seats is under or equal to the number of remaining places
        // if it's a modification, need to check if the new number of booked
        // seats is under or equal to the number of the remaining places + the
        // previous number of booked seats of the appointment
        if ( nbBookedSeats > appointmentDTO.getNbMaxPotentialBookedSeats( ) && !appointmentDTO.getOverbookingAllowed( ) )

        {
            GenericAttributeError genAttError = new GenericAttributeError( );
            genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_ERROR_NB_BOOKED_SEAT, locale ) );
            listFormErrors.add( genAttError );
        }

        if ( nbBookedSeats == 0 )
        {
            GenericAttributeError genAttError = new GenericAttributeError( );
            genAttError.setErrorMessage( I18nService.getLocalizedString( ERROR_MESSAGE_EMPTY_NB_BOOKED_SEAT, locale ) );
            listFormErrors.add( genAttError );
        }
        return nbBookedSeats;
    }

    /**
     * Fill the appoinmentFront DTO with the given parameters
     * 
     * @param appointmentDTO
     *            the appointmentFront DTO
     * @param nbBookedSeats
     *            the number of booked seats
     * @param strEmail
     *            the email of the user
     * @param strFirstName
     *            the first name of the user
     * @param strLastName
     *            the last name of the user
     */
    public static void fillAppointmentDTO( AppointmentDTO appointmentDTO, int nbBookedSeats, String strEmail, String strEmailConfirm, String strFirstName, String strLastName )
    {
        appointmentDTO.setDateOfTheAppointment( appointmentDTO.getSlot( ).get( 0 ).getDate( ).format( Utilities.getFormatter( ) ) );
        appointmentDTO.setNbBookedSeats( nbBookedSeats );
        appointmentDTO.setEmail( strEmail );
        appointmentDTO.setConfirmEmail(strEmailConfirm);
        appointmentDTO.setFirstName( strFirstName );
        appointmentDTO.setLastName( strLastName );
    }

    /**
     * Validate the form and the additional entries of the form
     * 
     * @param appointmentDTO
     *            the appointmentFron DTo to validate
     * @param request
     *            the request
     * @param listFormErrors
     *            the list of errors that can be fill with the errors found at the validation
     */
    public static void validateFormAndEntries( AppointmentDTO appointmentDTO, HttpServletRequest request, List<GenericAttributeError> listFormErrors,
            boolean allEntries )
    {
        Set<ConstraintViolation<AppointmentDTO>> listErrors = BeanValidationUtil.validate( appointmentDTO );

        if ( CollectionUtils.isNotEmpty( listErrors ) )
        {
            for ( ConstraintViolation<AppointmentDTO> constraintViolation : listErrors )
            {
                GenericAttributeError genAttError = new GenericAttributeError( );
                genAttError.setErrorMessage( I18nService.getLocalizedString( constraintViolation.getMessageTemplate( ), request.getLocale( ) ) );
                listFormErrors.add( genAttError );
            }
        }
        
        EntryFilter filter = EntryService.buildEntryFilter( appointmentDTO.getIdForm( ) );
        if ( allEntries )
        {
            filter.setIsOnlyDisplayInBack( GenericAttributesUtils.CONSTANT_ID_NULL );
        }
        List<Entry> listEntryFirstLevel = EntryHome.getEntryList( filter );
        for ( Entry entry : listEntryFirstLevel )
        {
            listFormErrors.addAll( EntryService.getResponseEntry( request, entry.getIdEntry( ), request.getLocale( ), appointmentDTO ) );
        }
    }

    public static void fillInListResponseWithMapResponse( AppointmentDTO appointmentDTO )
    {
        Map<Integer, List<Response>> mapResponses = appointmentDTO.getMapResponsesByIdEntry( );
        if ( mapResponses != null && !mapResponses.isEmpty( ) )
        {
            List<Response> listResponse = new ArrayList<>( );
            for ( List<Response> listResponseByEntry : mapResponses.values( ) )
            {
                listResponse.addAll( listResponseByEntry );
            }
            appointmentDTO.setListResponse( listResponse );
        }
    }

    /**
     * Build a list of response of the appointment
     * 
     * @param appointment
     *            the appointment
     * @param request
     *            the request
     * @param locale
     *            the local
     * @return a list of response
     */
    public static List<ResponseRecapDTO> buildListResponse( AppointmentDTO appointment, HttpServletRequest request, Locale locale )
    {
        List<ResponseRecapDTO> listResponseRecapDTO = new ArrayList<>( );
        HashMap<Integer, List<ResponseRecapDTO>> mapResponse = new HashMap<>( );
        if ( CollectionUtils.isNotEmpty( appointment.getListResponse( ) ) )
        {
            listResponseRecapDTO = new ArrayList<>( appointment.getListResponse( ).size( ) );
            for ( Response response : appointment.getListResponse( ) )
            {
                int nIndex = response.getEntry( ).getPosition( );
                IEntryTypeService entryTypeService = EntryTypeServiceManager.getEntryTypeService( response.getEntry( ) );
                ResponseRecapDTO responseRecapDTO = new ResponseRecapDTO( response,
                        entryTypeService.getResponseValueForRecap( response.getEntry( ), request, response, locale ) );

                List<ResponseRecapDTO> listResponse = mapResponse.computeIfAbsent( nIndex, ArrayList::new );
                listResponse.add( responseRecapDTO );
            }
        }
        for ( List<ResponseRecapDTO> listResponse : mapResponse.values( ) )
        {
            listResponseRecapDTO.addAll( listResponse );
        }
        return listResponseRecapDTO;
    }

    /**
     * Attempts to cancel execution of the task.
     * 
     * @param request
     *            the request
     * @param idSlot
     *            the id Slot
     */

    public static void cancelTaskTimer( HttpServletRequest request, int idSlot )
    {
        ScheduledFuture<Slot> task = (ScheduledFuture) request.getSession( ).getAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
        if ( task != null )
        {
            if ( !task.isDone( ) )
                task.cancel( false );
            request.getSession( ).removeAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
        }
    }

    public static boolean isEditSlotTaskExpiredTime( HttpServletRequest request, int idSlot )
    {
        ScheduledFuture<Slot> task = (ScheduledFuture) request.getSession( ).getAttribute( SESSION_TASK_TIMER_SLOT + idSlot );
        return ( task != null && task.isDone( ) );
    }

    /**
     * Create a timer on a slot
     * 
     * @param slot
     *            the slot
     * @param appointmentDTO
     *            the appointment
     * @param maxPeoplePerAppointment
     *            the max people per appointment
     * @return the ScheduledFuture
     */
    public static ScheduledFuture<Slot> putTimerInSession( HttpServletRequest request, int nIdSlot, AppointmentDTO appointmentDTO, int maxPeoplePerAppointment )
    {
        Lock lock = SlotSafeService.getLockOnSlot( nIdSlot );
        lock.lock( );
        try
        {
            Slot slot = SlotService.findSlotById( nIdSlot );

            int nbPotentialRemainingPlaces = slot.getNbPotentialRemainingPlaces( );
            int nbPotentialPlacesTaken = Math.min( nbPotentialRemainingPlaces, maxPeoplePerAppointment );
            int nNewNbMaxPotentialBookedSeats = Math.min( nbPotentialPlacesTaken + appointmentDTO.getNbMaxPotentialBookedSeats( ), maxPeoplePerAppointment );

            if ( slot.getNbPotentialRemainingPlaces( ) > 0 )
            {

                ScheduledFuture<Slot> scheduledFuture = _secheduledExecutor.schedule( new SlotEditTask( slot.getIdSlot( ), nbPotentialPlacesTaken ),
                        AppPropertiesService.getPropertyInt( PROPERTY_DEFAULT_EXPIRED_TIME_EDIT_APPOINTMENT, 1 ), TimeUnit.MINUTES );
                appointmentDTO.setNbMaxPotentialBookedSeats( nNewNbMaxPotentialBookedSeats );
                SlotSafeService.decrementPotentialRemainingPlaces( nbPotentialPlacesTaken, slot.getIdSlot( ) );

                request.getSession( ).setAttribute( SESSION_TASK_TIMER_SLOT + slot.getIdSlot( ), scheduledFuture );
                return scheduledFuture;
            }
            appointmentDTO.setNbMaxPotentialBookedSeats( 0 );
        }

        finally
        {

            lock.unlock( );
        }
        return null;
    }

    /**
     * Get Form Permissions
     * 
     * @param listForms
     * @param request
     * @return
     */
    public static String [ ] [ ] getPermissions( List<AppointmentFormDTO> listForms, AdminUser user )
    {
        String [ ] [ ] retour = new String [ listForms.size( )] [ 6];
        int nI = 0;

        for ( AppointmentFormDTO tmpForm : listForms )
        {

            String [ ] strRetour = new String [ 7];
            strRetour [0] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_VIEW_APPOINTMENT, (fr.paris.lutece.api.user.User) user ) );
            strRetour [1] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM, (fr.paris.lutece.api.user.User) user ) );
            strRetour [2] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_MODIFY_FORM, (fr.paris.lutece.api.user.User) user ) );
            strRetour [3] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_MODIFY_FORM, (fr.paris.lutece.api.user.User) user ) );
            strRetour [4] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_CHANGE_STATE, (fr.paris.lutece.api.user.User) user ) );
            strRetour [5] = String.valueOf( RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( tmpForm.getIdForm( ) ),
                    AppointmentResourceIdService.PERMISSION_DELETE_FORM, (fr.paris.lutece.api.user.User) user ) );
            retour [nI++] = strRetour;
        }

        return retour;
    }

    /**
     * Return the min starting time to display
     * 
     * @param minStartingTime
     *            the min starting time
     * @return 00 if the minstarting time is under 30, 30 otherwise
     */
    public static LocalTime getMinTimeToDisplay( LocalTime minStartingTime )
    {
        LocalTime minStartingTimeToDisplay;
        if ( minStartingTime.getMinute( ) < THIRTY_MINUTES )
        {
            minStartingTimeToDisplay = LocalTime.of( minStartingTime.getHour( ), 0 );
        }
        else
        {
            minStartingTimeToDisplay = LocalTime.of( minStartingTime.getHour( ), THIRTY_MINUTES );
        }
        return minStartingTimeToDisplay;
    }

    /**
     * Return the max ending time to display
     * 
     * @param maxEndingTime
     *            the max ending time
     * @return 30 if the max ending time is under 30, otherwise the next hour
     */
    public static LocalTime getMaxTimeToDisplay( LocalTime maxEndingTime )
    {
        LocalTime maxEndingTimeToDisplay;
        if ( maxEndingTime.getMinute( ) < THIRTY_MINUTES )
        {
            maxEndingTimeToDisplay = LocalTime.of( maxEndingTime.getHour( ), THIRTY_MINUTES );
        }
        else
        {
            maxEndingTimeToDisplay = LocalTime.of( maxEndingTime.getHour( ) + 1, 0 );
        }
        return maxEndingTimeToDisplay;
    }

    /**
     * Check if there are appointments impacted by the new week definition
     * 
     * @param listAppointment
     *            the list of appointments
     * @param nIdForm
     *            the form Id
     * @param dateOfModification
     *            the date of modification (date of apply of the new week definition)
     * @param appointmentForm
     *            the appointment form
     * @return true if there are appointments impacted
     */
    public static boolean checkNoAppointmentsImpacted( List<Appointment> listAppointment, int nIdForm, LocalDate dateOfModification,
            AppointmentFormDTO appointmentForm )
    {
        ReservationRule previousReservationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( nIdForm, dateOfModification );
        return checkNoAppointmentsImpacted( listAppointment, nIdForm, previousReservationRule.getIdReservationRule( ), appointmentForm );
    }

    /**
     * Check if there are appointments impacted by the new week definition
     * 
     * @param listAppointment
     *            the list of appointments
     * @param nIdForm
     *            the form Id
     * @param nIdreservationRule
     *            the reservationRule id
     * @param appointmentForm
     *            the appointment form
     * @return true if there are appointments impacted
     */
    public static boolean checkNoAppointmentsImpacted( List<Appointment> listAppointment, int nIdForm, int nIdreservationRule,
            AppointmentFormDTO appointmentForm )
    {
        boolean bNoAppointmentsImpacted = true;
        // Build the previous appointment form with the previous week
        // definition and the previous reservation rule
        AppointmentFormDTO previousAppointmentForm = FormService.buildAppointmentForm( nIdForm, nIdreservationRule );
        // Need to check if the new definition week has more open days.
        List<DayOfWeek> previousOpenDays = WorkingDayService.getOpenDays( previousAppointmentForm );
        List<DayOfWeek> newOpenDays = WorkingDayService.getOpenDays( appointmentForm );
        // If new open days
        if ( newOpenDays.containsAll( previousOpenDays ) )
        {
            // Nothing to check
        }
        else
        {
            // Else we remove all the corresponding days
            previousOpenDays.removeAll( newOpenDays );
            // For the remaining days
            // for each appointment, need to check if the appointment is
            // not in the remaining open days
            boolean bAppointmentOnOpenDays = false;
            for ( Appointment appointment : listAppointment )
            {
                for ( AppointmentSlot appSlot : appointment.getListAppointmentSlot( ) )
                {

                    Slot tempSlot = SlotService.findSlotById( appSlot.getIdSlot( ) );
                    if ( previousOpenDays.contains( tempSlot.getStartingDateTime( ).getDayOfWeek( ) ) )
                    {
                        bAppointmentOnOpenDays = true;
                        break;
                    }
                }
                if ( bAppointmentOnOpenDays )
                {
                    break;
                }
            }
            bNoAppointmentsImpacted = !bAppointmentOnOpenDays;
        }
        LocalTime newStartingTime = LocalTime.parse( appointmentForm.getTimeStart( ) );
        LocalTime newEndingTime = LocalTime.parse( appointmentForm.getTimeEnd( ) );
        LocalTime oldStartingTime = LocalTime.parse( previousAppointmentForm.getTimeStart( ) );
        LocalTime oldEndingTime = LocalTime.parse( previousAppointmentForm.getTimeEnd( ) );
        // If we have changed the duration of an appointment
        if ( appointmentForm.getDurationAppointments( ) != previousAppointmentForm.getDurationAppointments( ) )
        {
            bNoAppointmentsImpacted = false;
        }
        // If we have change the open hours

        if ( !newStartingTime.equals( oldStartingTime ) || !newEndingTime.equals( oldEndingTime ) )
        {
            bNoAppointmentsImpacted = false;
        }

        return bNoAppointmentsImpacted;
    }

    /**
     * Check if there are appointments impacted by the new week definition
     * 
     * @param listSlotsImpacted
     *            the list of slot impacted
     * @param appointmentForm
     *            the appointment form
     * @return true if there are appointments impacted
     */
    public static boolean checkNoAppointmentsImpacted( List<Slot> listSlotWithAppointment, AppointmentFormDTO appointmentForm )
    {
        // Build the previous appointment form with the previous week
        // definition and the previous reservation rule
        AppointmentFormDTO previousAppointmentForm = FormService.buildAppointmentForm( appointmentForm.getIdForm( ), appointmentForm.getIdReservationRule( ) );
        // Need to check if the new definition week has more open days.
        List<DayOfWeek> previousOpenDays = WorkingDayService.getOpenDays( previousAppointmentForm );
        List<DayOfWeek> newOpenDays = WorkingDayService.getOpenDays( appointmentForm );
        // If new open days
        if ( newOpenDays.containsAll( previousOpenDays ) )
        {
            // Nothing to check
        }
        else
        {
            // Else we remove all the corresponding days
            previousOpenDays.removeAll( newOpenDays );
            // For the remaining days
            // for each appointment, need to check if the appointment is
            // not in the remaining open days

            for ( Slot tempSlot : listSlotWithAppointment )
            {
                if ( previousOpenDays.contains( tempSlot.getStartingDateTime( ).getDayOfWeek( ) ) )
                {
                    return false;
                }
            }

        }
        LocalTime newStartingTime = LocalTime.parse( appointmentForm.getTimeStart( ) );
        LocalTime newEndingTime = LocalTime.parse( appointmentForm.getTimeEnd( ) );
        LocalTime oldStartingTime = LocalTime.parse( previousAppointmentForm.getTimeStart( ) );
        LocalTime oldEndingTime = LocalTime.parse( previousAppointmentForm.getTimeEnd( ) );
        // If we have changed the duration of an appointment
        if ( appointmentForm.getDurationAppointments( ) != previousAppointmentForm.getDurationAppointments( ) )
        {
            return false;
        }
        // If we have change the open hours

        return !newStartingTime.equals( oldStartingTime ) || !newEndingTime.equals( oldEndingTime );
    }

    /**
     * Check if there are appointments impacted by the new week definition
     * 
     * @param listSlotsImpacted
     *            the list of slot impacted
     * @param newReservationRule
     *            the reservation rule
     * @return true if there are no appointments impacted
     */
    public static boolean checkNoAppointmentsImpacted( List<Slot> listSlotsImpacted, int idReservationRule )
    {

        List<WorkingDay> listWorkingDay = WorkingDayService.findListWorkingDayByWeekDefinitionRule( idReservationRule );
        for ( Slot slot : listSlotsImpacted )
        {
            WorkingDay workingDay = listWorkingDay.stream( ).filter( day -> day.getDayOfWeek( ) == slot.getDate( ).getDayOfWeek( ).getValue( ) ).findFirst( )
                    .orElse( null );
            if ( workingDay != null )
            {

                if ( workingDay.getListTimeSlot( ).stream( ).noneMatch(
                        time -> slot.getStartingTime( ).equals( time.getStartingTime( ) ) && slot.getEndingTime( ).equals( time.getEndingTime( ) ) ) )
                {

                    return false;
                }

            }
            else
            {

                return false;
            }
        }

        return true;
    }

    /**
     * Check that there is no validated appointments on a slot
     * 
     * @param slot
     *            the slot
     * @return true if there are no validated appointments on this slot, false otherwise
     */
    public static boolean checkNoValidatedAppointmentsOnThisSlot( Slot slot )
    {
        boolean bNoValidatedAppointmentsOnThisSlot = true;
        List<Appointment> listAppointmentsOnThisSlot = AppointmentService.findListAppointmentBySlot( slot.getIdSlot( ) );
        if ( CollectionUtils.isNotEmpty( listAppointmentsOnThisSlot ) )
        {
            listAppointmentsOnThisSlot = listAppointmentsOnThisSlot.stream( ).filter( a -> !a.getIsCancelled( ) ).collect( Collectors.toList( ) );
        }
        if ( CollectionUtils.isNotEmpty( listAppointmentsOnThisSlot ) )
        {
            bNoValidatedAppointmentsOnThisSlot = false;
        }
        return bNoValidatedAppointmentsOnThisSlot;
    }

    /**
     * Return the slots impacted by the modification of this time slot
     * 
     * @param timeSlot
     *            the time slot
     * @param nIdForm
     *            the form id
     * @param nIdWeekDefinition
     *            the week definition id
     * @param bShiftSlot
     *            the boolean value for the shift
     * @return the list of slots impacted
     */
    public static List<Slot> findSlotsImpactedByThisTimeSlot( TimeSlot timeSlot, int nIdForm, int nIdWeekDefinition, boolean bShiftSlot )
    {
        List<Slot> listSlotsImpacted = null;
        // Get the weekDefinition that is currently modified
        WeekDefinition currentModifiedWeekDefinition = WeekDefinitionService.findWeekDefinitionById( nIdWeekDefinition );
        // We have an upper bound to search with
        List<Slot> listSlots = SlotService.findSlotsByIdFormAndDateRange( nIdForm, currentModifiedWeekDefinition.getDateOfApply( ).atStartOfDay( ),
                currentModifiedWeekDefinition.getEndingDateOfApply( ).atTime( LocalTime.MAX ) );
        // Need to check if the modification of the time slot or the typical
        // week impacts these slots
        WorkingDay workingDay = WorkingDayService.findWorkingDayLightById( timeSlot.getIdWorkingDay( ) );
        // Filter all the slots with the working day and the starting time
        // ending time of the time slot
        // The begin time of the slot can be before or after the begin time
        // of the time slot
        // and the ending time of the slot can be before or after the ending
        // time of the time slot (specific slot)

        // If shiftTimeSlot is checked, need to check all the slots impacted
        // until the end of the day
        if ( bShiftSlot )
        {
            listSlotsImpacted = listSlots.stream( )
                    .filter( slot -> ( ( slot.getStartingDateTime( ).getDayOfWeek( ) == DayOfWeek.of( workingDay.getDayOfWeek( ) ) )
                            && ( !slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
                                    || ( slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
                                            && ( slot.getEndingTime( ).isAfter( timeSlot.getStartingTime( ) ) ) ) ) ) )
                    .collect( Collectors.toList( ) );
        }
        else
        {
            listSlotsImpacted = listSlots.stream( )
                    .filter( slot -> ( slot.getStartingDateTime( ).getDayOfWeek( ) == DayOfWeek.of( workingDay.getDayOfWeek( ) ) )
                            && ( slot.getStartingTime( ).equals( timeSlot.getStartingTime( ) )
                                    || ( slot.getStartingTime( ).isBefore( timeSlot.getStartingTime( ) )
                                            && ( slot.getEndingTime( ).isAfter( timeSlot.getStartingTime( ) ) ) )
                                    || ( slot.getStartingTime( ).isAfter( timeSlot.getStartingTime( ) )
                                            && ( !slot.getEndingTime( ).isAfter( timeSlot.getEndingTime( ) ) ) ) ) )
                    .collect( Collectors.toList( ) );
        }

        return listSlotsImpacted;
    }

    public static LocalDateTime getStartingDateTime( Appointment appointmentDTO )
    {

        List<Slot> listSlot = appointmentDTO.getSlot( );
        if ( CollectionUtils.isNotEmpty( listSlot ) )
        {
            Slot slot = listSlot.stream( ).min( Comparator.comparing( Slot::getStartingDateTime ) ).orElse( listSlot.get( 0 ) );
            return slot.getStartingDateTime( );
        }

        return null;
    }

    public static LocalDateTime getEndingDateTime( Appointment appointmentDTO )
    {

        List<Slot> listSlot = appointmentDTO.getSlot( );
        if ( listSlot != null && !listSlot.isEmpty( ) )
        {

            Slot slot = listSlot.stream( ).max( Comparator.comparing( Slot::getStartingDateTime ) ).orElse( listSlot.get( 0 ) );
            return slot.getEndingDateTime( );
        }

        return null;
    }

    /**
     * return true if all slots are consecutive
     * 
     * @param allSlots
     *            the list of slots
     * @return true if all slots are consecutive
     */
    public static boolean isConsecutiveSlots( List<Slot> allSlots )
    {
        Slot slot = null;
        for ( Slot nextSlot : allSlots )
        {
            if ( nextSlot == null || ( slot != null && !Objects.equals( slot.getEndingDateTime( ), nextSlot.getStartingDateTime( ) ) ) )
            {
                return false;
            }
            slot = nextSlot;
        }
        return true;
    }

    /**
     * Fill the reservation rule object with the corresponding values of an appointmentForm DTO
     * 
     * @param reservationRuleDTO
     *            the reservation rule object to fill in
     * @param appointmentForm
     *            the appointmentForm DTO
     * 
     */
    public static void fillInReservationRuleAdvancedParam( ReservationRuleDTO reservationRuleDTO, AppointmentFormDTO appointmentForm )
    {
        reservationRuleDTO.setMaxCapacityPerSlot( appointmentForm.getMaxCapacityPerSlot( ) );
        reservationRuleDTO.setMaxPeoplePerAppointment( appointmentForm.getMaxPeoplePerAppointment( ) );
        reservationRuleDTO.setName( appointmentForm.getName( ) );
        reservationRuleDTO.setDescriptionRule( appointmentForm.getDescriptionRule( ) );
        reservationRuleDTO.setColor( appointmentForm.getColor( ) );
        reservationRuleDTO.setDurationAppointments( appointmentForm.getDurationAppointments( ) );
        reservationRuleDTO.setTimeEnd( appointmentForm.getTimeEnd( ) );
        reservationRuleDTO.setTimeStart( appointmentForm.getTimeStart( ) );

        reservationRuleDTO.setIdForm( appointmentForm.getIdForm( ) );
    }

    /**
     * check if one of the weeks in the list is open on the FO calendar
     * 
     * @param appointmentFormDTO
     *            the appointment form
     * @param listWeek
     *            the list of weekDefinition
     * @param locale
     *            the locale
     * @return true if one of the weeks in the list is open on the FO calendar
     */
    public static boolean weekIsOpenInFO( AppointmentFormDTO appointmentFormDTO, List<WeekDefinition> listWeek, Locale locale )
    {

        if ( appointmentFormDTO.getIsActive( ) )
        {
            Date startingValidityDate = appointmentFormDTO.getDateStartValidity( );
            LocalDate startingDateOfDisplay = LocalDate.now( );

            if ( startingValidityDate != null && startingValidityDate.toLocalDate( ).isAfter( startingDateOfDisplay ) )
            {
                startingDateOfDisplay = startingValidityDate.toLocalDate( );
            }
            // Calculate the ending date of display with the nb weeks to display
            // since today
            // We calculate the number of weeks including the current week, so it
            // will end to the (n) next sunday
            LocalDate dateOfSunday = startingDateOfDisplay.with( WeekFields.of( locale ).dayOfWeek( ), DayOfWeek.SUNDAY.getValue( ) );
            LocalDate endingDateOfDisplay = dateOfSunday.plusWeeks( (long) appointmentFormDTO.getNbWeeksToDisplay( ) - 1 );
            Date endingValidityDate = appointmentFormDTO.getDateEndValidity( );
            if ( endingValidityDate != null && endingDateOfDisplay.isAfter( endingValidityDate.toLocalDate( ) ) )
            {
                endingDateOfDisplay = endingValidityDate.toLocalDate( );
            }
            if ( startingDateOfDisplay.isAfter( endingDateOfDisplay ) )
            {

                return false;
            }

            for ( WeekDefinition week : listWeek )
            {

                if ( ( week.getDateOfApply( ).isBefore( endingDateOfDisplay ) || week.getDateOfApply( ).isEqual( endingDateOfDisplay ) )
                        && ( week.getEndingDateOfApply( ).isAfter( startingDateOfDisplay ) || week.getEndingDateOfApply( ).isEqual( startingDateOfDisplay ) ) )
                {

                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check if list of slot is builded correctly
     * 
     * @param nIdForm
     *            the id form
     * @param listSlots
     *            the list of slot to check
     * @return true if the listSlots is builded correctly
     */
    public static boolean checkListSlotIsBuildedCorrectly( int nIdForm, List<Slot> listSlots )
    {
        if ( !CollectionUtils.isEmpty( listSlots ) )
        {

            LocalDateTime minDate = listSlots.stream( ).map( Slot::getStartingDateTime ).min( LocalDateTime::compareTo ).orElse( null );
            LocalDateTime maxDate = listSlots.stream( ).map( Slot::getStartingDateTime ).max( LocalDateTime::compareTo ).orElse( null );
            if ( minDate != null && maxDate != null )
            {
                List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
                Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );
                List<Slot> listSlotBuilded = SlotService.buildListSlot( nIdForm, mapReservationRule, minDate.toLocalDate( ), maxDate.toLocalDate( ) );
                for ( Slot slt : listSlots )
                {
                    if ( listSlotBuilded.stream( ).noneMatch( slot -> slt.getStartingDateTime( ).isEqual( slot.getStartingDateTime( ) )
                            && slt.getEndingDateTime( ).isEqual( slot.getEndingDateTime( ) ) ) )
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * The following method shuts down the _executorService in two phases, first by calling shutdown to reject incoming tasks, and then calling shutdownNow, if
     * necessary, to cancel any lingering tasks:
     */
    public static void shutdownSecheduledExecutor( )
    {
        _secheduledExecutor.shutdown( );
        try
        {
            if ( !_secheduledExecutor.awaitTermination( 60, TimeUnit.SECONDS ) )
            {
                _secheduledExecutor.shutdownNow( );
            }
        }
        catch( InterruptedException e )
        {
            // (Re-)Cancel if current thread also interrupted
            AppLogService.error( e.getMessage( ), e );
            _secheduledExecutor.shutdownNow( );
            Thread.currentThread().interrupt();
        }

    }
    
    /**
     * Build an appointment dto from an appointment business object
     * 
     * @param appointment
     *            the appointment business object
     * @return the appointment DTO
     */
    public static AppointmentDTO buildAppointmentDTO( Appointment appointment )
    {
        AppointmentDTO appointmentDTO = new AppointmentDTO( );
        User user = appointment.getUser();
        
        appointmentDTO.setIdForm( appointment.getSlot( ).get( 0 ).getIdForm( ) );
        appointmentDTO.setIdUser( appointment.getIdUser( ) );
        appointmentDTO.setListAppointmentSlot( appointment.getListAppointmentSlot( ) );
        appointmentDTO.setIdAppointment( appointment.getIdAppointment( ) );
       
        if(user != null) {
	        appointmentDTO.setFirstName( user.getFirstName( ) );
	        appointmentDTO.setLastName( user.getLastName( ) );
	        appointmentDTO.setEmail( user.getEmail( ) );
	        appointmentDTO.setPhoneNumber( user.getPhoneNumber( ) );
	        appointmentDTO.setGuid( user.getGuid( ) );
	        appointmentDTO.setReference( appointment.getReference( ) );
        }
        
        LocalDateTime startingDateTime = AppointmentUtilities.getStartingDateTime( appointment );
        LocalDateTime endingDateTime = AppointmentUtilities.getEndingDateTime( appointment );
        
        appointmentDTO.setStartingDateTime( startingDateTime );
        appointmentDTO.setEndingDateTime( endingDateTime );
        appointmentDTO.setDateOfTheAppointment( startingDateTime.toLocalDate( ).format( Utilities.getFormatter( ) ) );
        appointmentDTO.setStartingTime( startingDateTime.toLocalTime( ) );
        appointmentDTO.setEndingTime( endingDateTime.toLocalTime( ) );
        appointmentDTO.setIsCancelled( appointment.getIsCancelled( ) );
        appointmentDTO.setNbBookedSeats( appointment.getNbPlaces( ) );
        appointment.getSlot().forEach(SlotService::addDateAndTimeToSlot);
        appointmentDTO.setSlot( appointment.getSlot( ) );
        appointmentDTO.setUser( appointment.getUser( ) );
 
        appointmentDTO.setAdminUserCreate( appointment.getAdminUserCreate( ) );
        appointmentDTO.setDateAppointmentTaken( appointment.getDateAppointmentTaken( ) );
       
        return appointmentDTO;
    }

    /**
     * Set the appointment's phone number values, if a user entered them in a Generic Attributes
     * of type 'Phone Number'. These values are retrieved from the appointment's responses.
     * 
     * @param appointment
     *            the appointment to process
     */
    public static void setAppointmentPhoneNumberValuesFromResponse( AppointmentDTO appointment )
    {
    	// Retrieve the appointment's responses
    	List<Response> listAppointmentResponses = appointment.getListResponse( );

    	if( CollectionUtils.isNotEmpty( listAppointmentResponses ) )
    	{
    		try
    		{
    			// Retrieve the user's phone number value(s) from the appointment's responses
    			List<String> listPhoneNumbers = listAppointmentResponses.stream( )
    					.filter( elem -> elem.getEntry( ) != null && elem.getEntry( ).getEntryType( ).getBeanName( ).contentEquals( AppointmentUtilities.CONSTANT_GENERIC_ATTRIBUTE_TYPE_PHONE_NAME ) )
    					.map( Response::getResponseValue )
    					.filter( value -> value != null && !value.trim( ).isEmpty( ) )
    					.map( String::trim )
    					.collect( Collectors.toList( ) );

    			if( !listPhoneNumbers.isEmpty( ) )
    			{
    				// Set the current appointment's phone number value(s). Use a separator if there are multiple values
    				String strPhoneNumbers = StringUtils.join( listPhoneNumbers, CONSTANT_PHONE_NUMBERS_SEPARATOR );
    				appointment.setPhoneNumber( strPhoneNumbers );
                }
                else
                {
                    // Set the appointment's phone number value to 'null' if no response was entered
                    appointment.setPhoneNumber( null );
                }
            }
    		catch ( NullPointerException e)
    		{
                AppLogService.error( "Error when retrieving appointment's phone number value", e );
    		}
    	}
    }
}