AppointmentApp.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.web;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;

import fr.paris.lutece.portal.business.file.File;
import fr.paris.lutece.portal.service.file.FileService;
import fr.paris.lutece.portal.service.file.FileServiceException;
import fr.paris.lutece.portal.service.file.IFileStoreServiceProvider;
import fr.paris.lutece.portal.service.security.SecurityTokenService;
import org.apache.commons.codec.binary.Base64;
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.calendar.CalendarTemplate;
import fr.paris.lutece.plugins.appointment.business.calendar.CalendarTemplateHome;
import fr.paris.lutece.plugins.appointment.business.form.Form;
import fr.paris.lutece.plugins.appointment.business.message.FormMessage;
import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
import fr.paris.lutece.plugins.appointment.business.slot.Slot;
import fr.paris.lutece.plugins.appointment.exception.AppointmentSavedException;
import fr.paris.lutece.plugins.appointment.exception.SlotEditTaskExpiredTimeException;
import fr.paris.lutece.plugins.appointment.exception.SlotFullException;
import fr.paris.lutece.plugins.appointment.log.LogUtilities;
import fr.paris.lutece.plugins.appointment.service.AppointmentResponseService;
import fr.paris.lutece.plugins.appointment.service.AppointmentService;
import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
import fr.paris.lutece.plugins.appointment.service.EntryService;
import fr.paris.lutece.plugins.appointment.service.FormMessageService;
import fr.paris.lutece.plugins.appointment.service.FormService;
import fr.paris.lutece.plugins.appointment.service.ReservationRuleService;
import fr.paris.lutece.plugins.appointment.service.SlotSafeService;
import fr.paris.lutece.plugins.appointment.service.SlotService;
import fr.paris.lutece.plugins.appointment.service.UserService;
import fr.paris.lutece.plugins.appointment.service.Utilities;
import fr.paris.lutece.plugins.appointment.service.WeekDefinitionService;
import fr.paris.lutece.plugins.appointment.service.listeners.AppointmentListenerManager;
import fr.paris.lutece.plugins.appointment.service.upload.AppointmentAsynchronousUploadHandler;
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.genericattributes.business.Entry;
import fr.paris.lutece.plugins.genericattributes.business.GenericAttributeError;
import fr.paris.lutece.plugins.genericattributes.business.Response;
import fr.paris.lutece.plugins.workflowcore.service.task.ITask;
import fr.paris.lutece.plugins.workflowcore.service.task.ITaskService;
import fr.paris.lutece.plugins.workflowcore.service.task.TaskService;
import fr.paris.lutece.portal.service.accesscontrol.AccessControlService;
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
import fr.paris.lutece.portal.service.captcha.CaptchaSecurityService;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.image.ImageResource;
import fr.paris.lutece.portal.service.message.SiteMessageException;
import fr.paris.lutece.portal.service.security.LuteceUser;
import fr.paris.lutece.portal.service.security.SecurityService;
import fr.paris.lutece.portal.service.security.UserNotSignedException;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.template.AppTemplateService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPathService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.portal.service.workflow.WorkflowService;
import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
import fr.paris.lutece.portal.util.mvc.utils.MVCMessage;
import fr.paris.lutece.portal.util.mvc.utils.MVCUtils;
import fr.paris.lutece.portal.util.mvc.xpage.MVCApplication;
import fr.paris.lutece.portal.util.mvc.xpage.annotations.Controller;
import fr.paris.lutece.portal.web.xpages.XPage;
import fr.paris.lutece.util.ErrorMessage;
import fr.paris.lutece.util.html.HtmlTemplate;
import fr.paris.lutece.util.url.UrlItem;

/**
 * This class provides a simple implementation of an Appointment XPage (On Front Office)
 *
 * @author Laurent Payen
 *
 */
@Controller( xpageName = AppointmentApp.XPAGE_NAME, pageTitleI18nKey = AppointmentApp.MESSAGE_DEFAULT_PAGE_TITLE, pagePathI18nKey = AppointmentApp.MESSAGE_DEFAULT_PATH )
public class AppointmentApp extends MVCApplication
{

    /**
     * Default page of XPages of this app
     */
    public static final String MESSAGE_DEFAULT_PATH = "appointment.appointment.name";

    /**
     * Default page title of XPages of this app
     */
    public static final String MESSAGE_DEFAULT_PAGE_TITLE = "appointment.appointmentApp.defaultTitle";

    /**
     * The name of the XPage
     */
    protected static final String XPAGE_NAME = "appointment";

    /**
     * Generated serial version UID
     */
    private static final long serialVersionUID = 5741361182728887387L;

    // Templates
    private static final String TEMPLATE_APPOINTMENT_FORM_LIST = "/skin/plugins/appointment/appointment_form_list.html";
    private static final String TEMPLATE_APPOINTMENT_FORM = "/skin/plugins/appointment/appointment_form.html";
    private static final String TEMPLATE_APPOINTMENT_FORM_RECAP = "/skin/plugins/appointment/appointment_form_recap.html";
    private static final String TEMPLATE_APPOINTMENT_CREATED = "skin/plugins/appointment/appointment_created.html";
    private static final String TEMPLATE_CANCEL_APPOINTMENT = "skin/plugins/appointment/cancel_appointment.html";
    private static final String TEMPLATE_APPOINTMENT_CANCELED = "skin/plugins/appointment/appointment_canceled.html";
    private static final String TEMPLATE_MY_APPOINTMENTS = "skin/plugins/appointment/my_appointments.html";
    private static final String TEMPLATE_HTML_CODE_FORM = "skin/plugins/appointment/html_code_form.html";
    private static final String TEMPLATE_HTML_CODE_NB_PLACES_TO_TAKE_FORM = "skin/plugins/appointment/appointment_nb_places_to_take_form.html";
    private static final String TEMPLATE_TASKS_FORM_WORKFLOW = "skin/plugins/appointment/tasks_form_workflow.html";
    private static final String TEMPLATE_ERROR_APPOINTMENT_REFERENCE = "skin/plugins/appointment/error_appointment_reference.html";

    // Views
    public static final String VIEW_APPOINTMENT_FORM = "getViewAppointmentForm";
    public static final String VIEW_APPOINTMENT_CALENDAR = "getViewAppointmentCalendar";
    private static final String VIEW_APPOINTMENT_FORM_LIST = "getViewFormList";
    private static final String VIEW_DISPLAY_RECAP_APPOINTMENT = "displayRecapAppointment";
    private static final String VIEW_GET_APPOINTMENT_CREATED = "getAppointmentCreated";
    private static final String VIEW_APPOINTMENT_CANCELED = "getAppointmentCanceled";
    private static final String VIEW_GET_MY_APPOINTMENTS = "getMyAppointments";
    private static final String VIEW_GET_VIEW_CANCEL_APPOINTMENT = "getViewCancelAppointment";
    private static final String VIEW_WORKFLOW_ACTION_FORM = "viewWorkflowActionForm";
    private static final String VIEW_CHANGE_DATE_APPOINTMENT = "viewChangeDateAppointment";

    // Actions
    private static final String ACTION_DO_VALIDATE_FORM = "doValidateForm";
    private static final String ACTION_DO_MAKE_APPOINTMENT = "doMakeAppointment";
    private static final String ACTION_DO_CANCEL_APPOINTMENT = "doCancelAppointment";
    private static final String ACTION_DO_PROCESS_WORKFLOW_ACTION = "doProcessWorkflowAction";

    // Parameters
    private static final String PARAMETER_STARTING_DATE_TIME = "starting_date_time";
    private static final String PARAMETER_ENDING_DATE_OF_DISPLAY = "ending_date_of_display";
    private static final String PARAMETER_STR_ENDING_DATE_OF_DISPLAY = "str_ending_date_of_display";
    private static final String PARAMETER_DATE_OF_DISPLAY = "date_of_display";
    private static final String PARAMETER_DAY_OF_WEEK = "dow";
    private static final String PARAMETER_HIDDEN_DAYS = "hidden_days";
    private static final String PARAMETER_ID_FORM = "id_form";
    private static final String PARAMETER_EVENTS = "events";
    private static final String PARAMETER_MIN_DURATION = "min_duration";
    private static final String PARAMETER_MIN_TIME = "min_time";
    private static final String PARAMETER_MAX_TIME = "max_time";
    private static final String PARAMETER_EMAIL = "email";
    private static final String PARAMETER_EMAIL_CONFIRMATION = "emailConfirm";
    private static final String PARAMETER_FIRST_NAME = "firstname";
    private static final String PARAMETER_LAST_NAME = "lastname";
    private static final String PARAMETER_NUMBER_OF_BOOKED_SEATS = "nbBookedSeats";
    private static final String PARAMETER_BACK = "back";
    private static final String PARAMETER_REF_APPOINTMENT = "refAppointment";
    private static final String PARAMETER_FROM_MY_APPOINTMENTS = "fromMyappointments";
    private static final String PARAMETER_REFERER = "referer";
    private static final String PARAMETER_WEEK_VIEW = "week_view";
    private static final String PARAMETER_DAY_VIEW = "day_view";
    private static final String PARAMETER_ANCHOR = "anchor";
    private static final String PARAMETER_MODIFICATION_FORM = "mod";
    private static final String PARAMETER_MIN_DATE_OF_OPEN_DAY = "min_date_of_open_day";
    private static final String PARAMETER_MAX_DATE_OF_OPEN_DAY = "max_date_of_open_day";
    private static final String PARAMETER_IS_MODIFICATION = "is_modification";
    private static final String PARAMETER_NB_PLACE_TO_TAKE = "nbPlacesToTake";
    private static final String PARAMETER_ID_ACTION = "id_action";
    private static final String PARAMETER_MODIF_DATE = "modif_date";

    // Mark
    private static final String MARK_MODIFICATION_DATE_APPOINTMENT = "modifDateAppointment";
    private static final String MARK_NBPLACESTOTAKE = "nbPlacesToTake";
    private static final String MARK_MAX_NBPLACESTOTAKE = "maxNbPlacesToTake";
    private static final String MARK_INFOS = "infos";
    private static final String MARK_LOCALE = "locale";
    private static final String MARK_FORM = "form";
    private static final String MARK_USER = "user";
    private static final String MARK_FORM_MESSAGES = "formMessages";
    private static final String MARK_STR_ENTRY = "str_entry";
    private static final String MARK_APPOINTMENT = "appointment";
    private static final String MARK_LIST_ERRORS = "listAllErrors";
    private static final String MARK_PLACES = "nbplaces";
    private static final String MARK_DATE_APPOINTMENT = "dateAppointment";
    private static final String MARK_STARTING_TIME_APPOINTMENT = "startingTimeAppointment";
    private static final String MARK_ENDING_TIME_APPOINTMENT = "endingTimeAppointment";
    private static final String MARK_FORM_LIST = "form_list";
    private static final String MARK_FORM_HTML = "form_html";
    private static final String MARK_FORM_NB_PLACES_TO_TAKE_HTML = "mark_nb_places_to_take_form";
    private static final String MARK_FORM_ERRORS = "form_errors";
    private static final String MARK_FORM_CALENDAR_ERRORS = "formCalendarErrors";
    private static final String MARK_CAPTCHA = "captcha";
    private static final String MARK_REF = "%%REF%%";
    private static final String MARK_DATE_APP = "%%DATE%%";
    private static final String MARK_TIME_BEGIN = "%%HEURE_DEBUT%%";
    private static final String MARK_TIME_END = "%%HEURE_FIN%%";
    private static final String MARK_LIST_APPOINTMENTS = "list_appointments";
    private static final String MARK_BACK_URL = "backUrl";
    private static final String MARK_FROM_URL = "fromUrl";
    private static final String MARK_LIST_RESPONSE_RECAP_DTO = "listResponseRecapDTO";
    private static final String MARK_DATA = "data";
    private static final String MARK_BASE_64 = "base64";
    private static final String MARK_SEMI_COLON = ";";
    private static final String MARK_COMMA = ",";
    private static final String MARK_COLON = ":";
    private static final String MARK_ICONS = "icons";
    private static final String MARK_ICON_NULL = "NULL";
    private static final String MARK_ANCHOR = "#";
    private static final String MARK_APPOINTMENT_ALREADY_CANCELLED = "alreadyCancelled";
    private static final String MARK_NO_APPOINTMENT_WITH_THIS_REFERENCE = "noAppointmentWithThisReference";
    private static final String MARK_APPOINTMENT_PASSED = "appointmentPassed";
    private static final String MARK_TASKS_FORM = "tasks_form";
    private static final String MARK_LOCALE_DATE_TIME = "localeDateTime";
    private static final String MARK_USER_PREFERRED_NAME = "preferred_user_name";

    // Errors
    private static final String ERROR_MESSAGE_SLOT_FULL = "appointment.message.error.slotFull";
    private static final String ERROR_MESSAGE_SLOT_EDIT_TASK_EXPIRED_TIME = "appointment.message.error.appointment.edit.expired.time";
    private static final String ERROR_MESSAGE_CAPTCHA = "portal.admin.message.wrongCaptcha";
    private static final String ERROR_MESSAGE_NB_MIN_DAYS_BETWEEN_TWO_APPOINTMENTS = "appointment.validation.appointment.NbMinDaysBetweenTwoAppointments.error";
    private static final String ERROR_MESSAGE_NB_MAX_APPOINTMENTS_ON_A_PERIOD = "appointment.validation.appointment.NbMaxAppointmentsOnAPeriod.error";
    private static final String ERROR_MESSAGE_NB_MAX_APPOINTMENTS_ON_A_CATEGORY = "appointment.validation.appointment.NbMaxAppointmentsOnCategory.error";
    private static final String ERROR_MESSAGE_FORM_NOT_ACTIVE = "appointment.validation.appointment.formNotActive";
    private static final String ERROR_MESSAGE_NO_STARTING_VALIDITY_DATE = "appointment.validation.appointment.noStartingValidityDate";
    private static final String ERROR_MESSAGE_FORM_NO_MORE_VALID = "appointment.validation.appointment.formNoMoreValid";
    private static final String ERROR_MESSAGE_REPORT_APPOINTMENT = "appointment.message.error.report.appointment";
    private static final String ERROR_MESSAGE_NO_AVAILABLE_SLOT = "appointment.validation.appointment.noAvailableSlot";

    private static final String ERROR_MESSAGE_NB_PLACE_TO_TAKE_TO_BIG = "appointment.message.error.nbplacestotake.toobig";

    private static final String MESSAGE_ERROR_TOKEN = "Invalid security token";

    // Messages
    private static final String MESSAGE_CANCEL_APPOINTMENT_PAGE_TITLE = "appointment.cancelAppointment.pageTitle";
    private static final String MESSAGE_MY_APPOINTMENTS_PAGE_TITLE = "appointment.myAppointments.name";
    private static final String MESSAGE_WF_ACTION_SUCESS = "appointment.wf.action.success";

    // Properties
    private static final String PROPERTY_USER_ATTRIBUTE_FIRST_NAME = "appointment.userAttribute.firstName";
    private static final String PROPERTY_USER_ATTRIBUTE_LAST_NAME = "appointment.userAttribute.lastName";
    private static final String PROPERTY_USER_ATTRIBUTE_PREFERED_NAME = "appointment.userAttribute.preferred_username";
    private static final String PROPERTY_USER_ATTRIBUTE_EMAIL = "appointment.userAttribute.email";
    private static final String AGENDA_WEEK = "agendaWeek";
    private static final String BASIC_WEEK = "basicWeek";
    private static final String AGENDA_DAY = "agendaDay";
    private static final String BASIC_DAY = "basicDay";
    private static final String STEP_3 = "step3";

    // Local variables
    private transient CaptchaSecurityService _captchaSecurityService;
    private int _nNbPlacesToTake;
    private String _strNbPlacesToTakeLength;
    private AppointmentFormDTO _appointmentForm;
    private AppointmentDTO _notValidatedAppointment;
    private AppointmentDTO _validatedAppointment;

    /**
     * Get the calendar view
     *
     * @param request
     * @return the Xpage
     * @throws AccessDeniedException
     */
    @SuppressWarnings( "unchecked" )
    @View( VIEW_APPOINTMENT_CALENDAR )
    public synchronized XPage getViewAppointmentCalendar( HttpServletRequest request ) throws AccessDeniedException
    {
        Map<String, Object> model = getModel( );
        Locale locale = getLocale( request );
        _nNbPlacesToTake = 0;

        int nIdForm = Integer.parseInt( request.getParameter( PARAMETER_ID_FORM ) );
        String nbPlacesToTake = request.getParameter( PARAMETER_NB_PLACE_TO_TAKE );
        String refAppointment = request.getParameter( PARAMETER_REF_APPOINTMENT );

        _appointmentForm = FormService.buildAppointmentFormWithoutReservationRule( nIdForm );
        _strNbPlacesToTakeLength = String.valueOf(_appointmentForm.getNbConsecutiveSlots());
        boolean bError = false;
        if ( !_appointmentForm.getIsActive( ) )
        {
            addError( ERROR_MESSAGE_FORM_NOT_ACTIVE, locale );
            bError = true;
        }

        FormMessage formMessages = FormMessageService.findFormMessageByIdForm( nIdForm );

        if ( StringUtils.isNotEmpty( refAppointment ) )
        {
            // If we want to change the date of an appointment
            AppointmentDTO appointmentDTO = AppointmentService.buildAppointmentDTOFromRefAppointment( refAppointment );
            // Check if an appointment Object was found and built with the given reference
            if( appointmentDTO == null )
            {
            	// When the appointment Object doesn't exist, we display an error message to the user
            	return getXPage( TEMPLATE_ERROR_APPOINTMENT_REFERENCE, locale, model );
            }
            if ( appointmentDTO.getIsCancelled( ) || appointmentDTO.getStartingDateTime( ).isBefore( LocalDateTime.now( ) ) )
            {
                addError( ERROR_MESSAGE_REPORT_APPOINTMENT, locale );
                bError = true;
            }
            else
            {
                _validatedAppointment = appointmentDTO;
                AppointmentService.addAppointmentResponses( _validatedAppointment );
                nbPlacesToTake = Integer.toString( _validatedAppointment.getNbBookedSeats( ) );
            }
        }
        LocalDate startingDateOfDisplay = LocalDate.now( );
        // Check if the date of display and the endDateOfDisplay are in the
        // validity date range of the form
        LocalDate startingValidityDate = null;
        if ( _appointmentForm.getDateStartValidity( ) == null )
        {
            addError( ERROR_MESSAGE_NO_STARTING_VALIDITY_DATE, locale );
            bError = true;
        }
        else
        {
            startingValidityDate = _appointmentForm.getDateStartValidity( ).toLocalDate( );
        }
        if ( startingValidityDate != null && startingValidityDate.isAfter( startingDateOfDisplay ) )
        {
            startingDateOfDisplay = startingValidityDate;
        }
        // Get the nb weeks to display
        int nNbWeeksToDisplay = _appointmentForm.getNbWeeksToDisplay( );
        // 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
        TemporalField fieldISO = WeekFields.of( locale ).dayOfWeek( );
        LocalDate dateOfSunday = startingDateOfDisplay.with( fieldISO, DayOfWeek.SUNDAY.getValue( ) );
        LocalDate endingDateOfDisplay = dateOfSunday.plusWeeks( (long) nNbWeeksToDisplay - 1 );
        // if the ending date of display is after the ending validity date of
        // the form
        // assign the ending date of display with the ending validity date of
        // the form
        LocalDate endingValidityDate = null;
        if ( _appointmentForm.getDateEndValidity( ) != null )
        {
            endingValidityDate = _appointmentForm.getDateEndValidity( ).toLocalDate( );
            if ( endingDateOfDisplay.isAfter( endingValidityDate ) )
            {
                endingDateOfDisplay = endingValidityDate;
            }
            if ( startingDateOfDisplay.isAfter( endingDateOfDisplay ) )
            {
                addError( ERROR_MESSAGE_FORM_NO_MORE_VALID, locale );
                bError = true;
            }
        }
        // Get the current date of display of the calendar, if it exists
        String strDateOfDisplay = request.getParameter( PARAMETER_DATE_OF_DISPLAY );
        LocalDate dateOfDisplay = startingDateOfDisplay;
        if ( StringUtils.isNotEmpty( strDateOfDisplay ) )
        {
            dateOfDisplay = LocalDate.parse( strDateOfDisplay );
        }
        // Get all the week definitions
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findWeekDefinitionByDateOfApply( nIdForm, startingDateOfDisplay, endingDateOfDisplay );

        Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );
        List<ReservationRule> listReservationRules = new ArrayList<>( mapReservationRule.values( ) );
        // Get the min time of all the week definitions
        LocalTime minStartingTime = WeekDefinitionService.getMinStartingTimeOfAListOfWeekDefinition( listReservationRules );
        // Get the max time of all the week definitions
        LocalTime maxEndingTime = WeekDefinitionService.getMaxEndingTimeOfAListOfWeekDefinition( listReservationRules );
        // Get all the working days of all the week definitions

        List<String> listStrBase0OpenDaysOfWeek = new ArrayList<>(
                WeekDefinitionService.getSetDaysOfWeekOfAListOfWeekDefinitionForFullCalendar( listReservationRules ) );
        // Build the slots if no errors
        List<Slot> listSlots = new ArrayList<>( );
        if ( !bError )
        {
            boolean isNewNbPlacesToTake = ( nbPlacesToTake != null && StringUtils.isNumeric( nbPlacesToTake ) );
            if ( _appointmentForm.getIsMultislotAppointment( ) && ( _nNbPlacesToTake != 0 || isNewNbPlacesToTake ) )
            {
                _nNbPlacesToTake = isNewNbPlacesToTake ? Integer.parseInt( nbPlacesToTake ) : _nNbPlacesToTake;
                listSlots = SlotService.buildListSlot( nIdForm, mapReservationRule, startingDateOfDisplay, endingDateOfDisplay, _nNbPlacesToTake );

            }
            else
            {
                _nNbPlacesToTake = 0;
                listSlots = SlotService.buildListSlot( nIdForm, mapReservationRule, startingDateOfDisplay, endingDateOfDisplay );
            }

            if ( _nNbPlacesToTake > Integer.parseInt( _strNbPlacesToTakeLength ) )
            {
            	addError( ERROR_MESSAGE_NB_PLACE_TO_TAKE_TO_BIG, locale );
            }

            // Check the Access Controls of the form, when displaying the calendar
            XPage accessControlPage = AccessControlService.getInstance( ).doExecuteAccessControl(
            		request,
            		nIdForm,
            		Form.RESOURCE_TYPE,
            		null
            		);
            if ( accessControlPage != null )
            {
            	return accessControlPage;
            }

            // Get the min time from now before a user can take an appointment (in hours)
            int minTimeBeforeAppointment = _appointmentForm.getMinTimeBeforeAppointment( );
            LocalDateTime dateTimeBeforeAppointment = LocalDateTime.now( ).plusHours( minTimeBeforeAppointment );
            // Filter the list of slots
            if ( CollectionUtils.isNotEmpty( listSlots ) )
            {
                listSlots = listSlots.stream( ).filter( s -> s.getStartingDateTime( ).isAfter( dateTimeBeforeAppointment ) ).collect( Collectors.toList( ) );
            }

            // If we change the date of an appointment
            // filter the list of slot with only the ones that have enough places at
            // the moment of the edition
            if ( _validatedAppointment != null )
            {
                int nbBookedSeats = _validatedAppointment.getNbBookedSeats( );
                listSlots = listSlots.stream( ).filter( s -> s.getNbPotentialRemainingPlaces( ) >= nbBookedSeats && s.getIsOpen( ) )
                        .collect( Collectors.toList( ) );
                model.put( MARK_MODIFICATION_DATE_APPOINTMENT, true );
                model.put( PARAMETER_REF_APPOINTMENT, refAppointment );
            }
            else
            {
                model.put( MARK_MODIFICATION_DATE_APPOINTMENT, false );
            }

            LocalDate firstDateOfFreeOpenSlot = null;
            if ( CollectionUtils.isNotEmpty( listSlots ) )
            {
                // Need to find the first available slot from now (with time)
                List<Slot> listAvailableSlots = listSlots.stream( ).filter( s -> ( s.getNbPotentialRemainingPlaces( ) > 0 && s.getIsOpen( ) == Boolean.TRUE ) )
                        .collect( Collectors.toList( ) );
                if ( CollectionUtils.isNotEmpty( listAvailableSlots ) )
                {
                    firstDateOfFreeOpenSlot = listAvailableSlots.stream( ).min( ( s1, s2 ) -> s1.getStartingDateTime( ).compareTo( s2.getStartingDateTime( ) ) )
                            .get( ).getDate( );
                }
            }
            if (firstDateOfFreeOpenSlot == null) {
                if (formMessages != null && StringUtils.isNotEmpty(formMessages.getNoAvailableSlot())) {
                    addError(formMessages.getNoAvailableSlot());
                } else {
                    addError(ERROR_MESSAGE_NO_AVAILABLE_SLOT, locale);
                }
                bError = true;
            }
            // Display the week with the first available slot
            if ( firstDateOfFreeOpenSlot != null && firstDateOfFreeOpenSlot.isAfter( dateOfDisplay ) )
            {
                dateOfDisplay = firstDateOfFreeOpenSlot;
            }
        }
        if ( bError )
        {
            model.put( MARK_FORM_CALENDAR_ERRORS, bError );
        }
        if ( formMessages != null && StringUtils.isNotEmpty( formMessages.getCalendarDescription( ) ) )
        {
            List<ErrorMessage> listInfos = (List<ErrorMessage>) model.get( MARK_INFOS );
            if ( listInfos == null )
            {
                listInfos = new ArrayList<>( );
                model.put( MARK_INFOS, listInfos );
            }
            MVCMessage message = new MVCMessage( formMessages.getCalendarDescription( ) );
            listInfos.add( message );
        }

        CalendarTemplate calendarTemplate = CalendarTemplateHome.findByPrimaryKey( _appointmentForm.getCalendarTemplateId( ) );
        List<String> listHiddenDays = Stream.of( "0", "1", "2", "3", "4", "5", "6" ).collect( Collectors.toList( ) );

        /**
         * Calculate the hidden days and set the view (Day and week) with the type of calendar
         */
        String dayView = null;
        String weekView = null;
        switch( calendarTemplate.getTitle( ) )
        {
            case CalendarTemplate.FREE_SLOTS:
                // Keep only the available slots
                listSlots = listSlots.stream( ).filter( s -> ( ( s.getNbRemainingPlaces( ) > 0 ) && ( s.getIsOpen( ) ) ) ).collect( Collectors.toList( ) );
                listHiddenDays.clear( );
                dayView = BASIC_DAY;
                weekView = BASIC_WEEK;
                break;
            case CalendarTemplate.FREE_SLOTS_GROUPED:
                // Keep only the available slots
                listHiddenDays.clear( );
                dayView = BASIC_DAY;
                weekView = BASIC_WEEK;
                break;
            case CalendarTemplate.CALENDAR_OPEN_DAYS:
                // update the list of the days to hide
                listHiddenDays.removeAll( listStrBase0OpenDaysOfWeek );
                dayView = AGENDA_DAY;
                weekView = AGENDA_WEEK;
                break;
            case CalendarTemplate.FREE_SLOTS_ON_OPEN_DAYS:
                // Keep only the available slots
                listSlots = listSlots.stream( ).filter( s -> ( ( s.getNbRemainingPlaces( ) > 0 ) && ( s.getIsOpen( ) ) ) ).collect( Collectors.toList( ) );
                // update the list of the days to hide
                listHiddenDays.removeAll( listStrBase0OpenDaysOfWeek );
                dayView = BASIC_DAY;
                weekView = BASIC_WEEK;
                break;
            case CalendarTemplate.CALENDAR:
            default:
                listHiddenDays.clear( );
                dayView = AGENDA_DAY;
                weekView = AGENDA_WEEK;
                break;
        }
        // Get the min and max date of the open days (for the week navigation on
        // open days calendar templates)
        Set<Integer> setOpenDays = WeekDefinitionService.getOpenDaysOfWeek( listReservationRules );
        model.put( PARAMETER_MIN_DATE_OF_OPEN_DAY,
                LocalDate.now( ).with( DayOfWeek.of( setOpenDays.stream( ).min( Comparator.naturalOrder( ) ).orElse( 1 ) ) ) );
        model.put( PARAMETER_MAX_DATE_OF_OPEN_DAY,
                endingDateOfDisplay.with( DayOfWeek.of( setOpenDays.stream( ).max( Comparator.naturalOrder( ) ).orElse( 1 ) ) ) );

        model.put( MARK_FORM, _appointmentForm );
        model.put( PARAMETER_ID_FORM, nIdForm );
        model.put( MARK_FORM_MESSAGES, formMessages );
        model.put( PARAMETER_ENDING_DATE_OF_DISPLAY, endingDateOfDisplay );
        model.put( PARAMETER_STR_ENDING_DATE_OF_DISPLAY, endingDateOfDisplay.format( Utilities.getFormatter( ) ) );
        model.put( PARAMETER_DATE_OF_DISPLAY, dateOfDisplay );
        model.put( PARAMETER_DAY_OF_WEEK, listStrBase0OpenDaysOfWeek );
        model.put( PARAMETER_MIN_TIME, AppointmentUtilities.getMinTimeToDisplay( minStartingTime ) );
        model.put( PARAMETER_MAX_TIME, AppointmentUtilities.getMaxTimeToDisplay( maxEndingTime ) );
        model.put( PARAMETER_MIN_DURATION, LocalTime.MIN.plusMinutes( AppointmentUtilities.THIRTY_MINUTES ) );
        model.put( MARK_NBPLACESTOTAKE, _nNbPlacesToTake );
        model.put( PARAMETER_EVENTS, listSlots );
        model.put( PARAMETER_HIDDEN_DAYS, listHiddenDays );
        model.put( PARAMETER_DAY_VIEW, dayView );
        model.put( PARAMETER_WEEK_VIEW, weekView );
        model.put( MARK_MAX_NBPLACESTOTAKE, Integer.valueOf( _strNbPlacesToTakeLength ) );
        HtmlTemplate templateNbPlacesToTakeForm = AppTemplateService.getTemplate( TEMPLATE_HTML_CODE_NB_PLACES_TO_TAKE_FORM, locale, model );
        model.put( MARK_FORM_NB_PLACES_TO_TAKE_HTML, templateNbPlacesToTakeForm.getHtml( ) );

        return getXPage( calendarTemplate.getTemplatePath( ), locale, model );
    }

    /**
     * Get the form appointment view (front office)
     *
     * @param request
     *            the request
     * @return the xpage
     * @throws UserNotSignedException
     */
    @View( VIEW_APPOINTMENT_FORM )
    public synchronized XPage getViewAppointmentForm( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        String strNbPlacesToTake = request.getParameter( PARAMETER_NB_PLACE_TO_TAKE );
        String strModifDateAppointment = request.getParameter( PARAMETER_MODIF_DATE );

        if ( strModifDateAppointment != null && Boolean.parseBoolean( strModifDateAppointment ) && _validatedAppointment != null
                && _validatedAppointment.getIdAppointment( ) != 0 )
        {
            return getViewChangeDateAppointment( request );
        }
        if ( strNbPlacesToTake != null )
        {
            _nNbPlacesToTake = Integer.parseInt( strNbPlacesToTake );
        }

        int nIdForm = Integer.parseInt( strIdForm );
        if ( _appointmentForm == null || _appointmentForm.getIdForm( ) != nIdForm )
        {
            _appointmentForm = FormService.buildAppointmentFormWithoutReservationRule( nIdForm );
        }
        if ( !_appointmentForm.getIsActive( ) )
        {
            addError( ERROR_MESSAGE_FORM_NOT_ACTIVE, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
        }
        if(!_appointmentForm.getIsMultislotAppointment())
        {
            _nNbPlacesToTake = 0;
        }
        checkMyLuteceAuthentication( _appointmentForm, request );
        // Patch needed for authentication after being on the form
        String secondAttempt = request.getParameter( "secondAttempt" );
        boolean bTestSecondAttempt = Boolean.FALSE;
        if ( StringUtils.isNotEmpty( secondAttempt ) )
        {
            bTestSecondAttempt = Boolean.TRUE;
        }
        // Need to manage the anchor
        String anchor = request.getParameter( PARAMETER_ANCHOR );
        if ( StringUtils.isNotEmpty( anchor ) )
        {
            LinkedHashMap<String, String> additionalParameters = new LinkedHashMap<>( );
            additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
            additionalParameters.put( PARAMETER_STARTING_DATE_TIME, request.getParameter( PARAMETER_STARTING_DATE_TIME ) );
            additionalParameters.put( PARAMETER_NB_PLACE_TO_TAKE, Integer.toString( _nNbPlacesToTake ) );
            additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + anchor );
            return redirect( request, VIEW_APPOINTMENT_FORM, additionalParameters );

        }

        String isModification = request.getParameter( PARAMETER_IS_MODIFICATION );
        boolean bModificationForm = false;
        List<Slot> listSlot = null;
        if ( isModification != null )

        {
            bModificationForm = true;

        }
        else
        {

            int nNbConsecutiveSlot = ( _nNbPlacesToTake == 0 ) ? 1 : _nNbPlacesToTake;
            LocalDateTime startingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_STARTING_DATE_TIME ) );
            if ( !isAuthorizedDate( startingDateTime, getLocale( request ) ) )
            {

                addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
                return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
            }
            // Get all the week definitions
            List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
            Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );

            listSlot = SlotService.buildListSlot( nIdForm, mapReservationRule, startingDateTime.toLocalDate( ), startingDateTime.toLocalDate( ) );
            listSlot = listSlot.stream( ).filter(
                    s -> ( ( startingDateTime.compareTo( s.getStartingDateTime( ) ) <= 0 ) && ( s.getNbRemainingPlaces( ) > 0 ) && ( s.getIsOpen( ) ) ) )
                    .limit( nNbConsecutiveSlot ).collect( Collectors.toList( ) );

            if ( listSlot == null || listSlot.stream( ).noneMatch( slot -> slot.getStartingDateTime( ).isEqual( startingDateTime ) )
                    || ( _nNbPlacesToTake > 0 && listSlot.size( ) != _nNbPlacesToTake ) || !AppointmentUtilities.isConsecutiveSlots( listSlot ) )
            {
                addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
                return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
            }

        }

        if ( _notValidatedAppointment == null || _notValidatedAppointment.getIdForm( ) != _appointmentForm.getIdForm( ) )
        {
            if ( _validatedAppointment != null && _validatedAppointment.getIdForm( ) == _appointmentForm.getIdForm( ) )
            {

                // Try to get the validated appointment in session
                // (in case the user click on back button in the recap view (or
                // modification)
                _notValidatedAppointment = _validatedAppointment;
                _validatedAppointment = null;
            }
            else
            {
                // Need to get back the informations the user has entered
                _notValidatedAppointment = new AppointmentDTO( );
            }
        }
        if ( !bModificationForm )
        {

            boolean bool = true;
            _notValidatedAppointment.setIdForm( nIdForm );
            _notValidatedAppointment.setSlot( null );
            _notValidatedAppointment.setNbMaxPotentialBookedSeats( 0 );
            for ( Slot slot : listSlot )
            {

                // If nIdSlot == 0, the slot has not been created yet
                if ( slot.getIdSlot( ) == 0 )
                {
                    slot = SlotSafeService.createSlot( slot );
                }
                else
                {
                    slot = SlotService.findSlotById( slot.getIdSlot( ) );
                }

                // Need to check competitive access
                // May be the slot is already taken at the same time
                if ( !bTestSecondAttempt && slot.getNbPotentialRemainingPlaces( ) == 0 )
                {
                    _notValidatedAppointment = null;
                    addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
                    return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
                }

                _notValidatedAppointment.addSlot( slot );

                if ( bool )
                {
                    _notValidatedAppointment.setDateOfTheAppointment( slot.getDate( ).format( Utilities.getFormatter( ) ) );
                    if ( SecurityService.getInstance( ).getRegisteredUser( request ) != null )
                    {
                        setUserInfo( request, _notValidatedAppointment );
                    }
                    FormService.fillAppointmentFormWithReservationRulePart( _appointmentForm,
                            ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( nIdForm, slot.getDate( ) ) );
                    bool = false;
                }
                AppointmentUtilities.putTimerInSession( request, slot.getIdSlot( ), _notValidatedAppointment, _appointmentForm.getMaxPeoplePerAppointment( ) );
            }
            if ( _notValidatedAppointment.getNbMaxPotentialBookedSeats( ) == 0 )
            {
                addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
                return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
            }
        }
        else
        {
            // Modification of the Form only, Need to redirect for the anchor
            if ( StringUtils.isEmpty( request.getParameter( PARAMETER_MODIFICATION_FORM ) ) )
            {
                LinkedHashMap<String, String> additionalParameters = new LinkedHashMap<>( );
                additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
                additionalParameters.put( PARAMETER_MODIFICATION_FORM, String.valueOf( Boolean.TRUE ) );
                additionalParameters.put( PARAMETER_IS_MODIFICATION, String.valueOf( Boolean.TRUE ) );
                additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + STEP_3 );
                return redirect( request, VIEW_APPOINTMENT_FORM, additionalParameters );
            }
        }
        Map<String, Object> model = getModel( );
        Locale locale = getLocale( request );
        StringBuilder strBuffer = new StringBuilder( );
        List<Entry> listEntryFirstLevel = EntryService.getFilter( _appointmentForm.getIdForm( ), true );
        for ( Entry entry : listEntryFirstLevel )
        {
            EntryService.getHtmlEntry( model, entry.getIdEntry( ), strBuffer, locale, true, _notValidatedAppointment );
        }
        FormMessage formMessages = FormMessageService.findFormMessageByIdForm( nIdForm );

        if ( _nNbPlacesToTake != 0 )
        {

            _notValidatedAppointment.setNbBookedSeats( _nNbPlacesToTake );
        }
        model.put( MARK_APPOINTMENT, _notValidatedAppointment );
        model.put( MARK_NBPLACESTOTAKE, _nNbPlacesToTake );
        model.put( PARAMETER_DATE_OF_DISPLAY, _notValidatedAppointment.getSlot( ).get( 0 ).getDate( ) );
        model.put( MARK_FORM, _appointmentForm );
        model.put( MARK_FORM_MESSAGES, formMessages );
        model.put( MARK_STR_ENTRY, strBuffer.toString( ) );
        model.put( MARK_LOCALE, locale );
        model.put( MARK_PLACES, _notValidatedAppointment.getNbMaxPotentialBookedSeats( ) );
        model.put( MARK_LIST_ERRORS, AppointmentDTO.getAllErrors( locale ) );
        LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );
        if( user != null )
        {
        	model.put( MARK_USER,  user );
        	model.put( MARK_USER_PREFERRED_NAME, user.getUserInfo( AppPropertiesService.getProperty( PROPERTY_USER_ATTRIBUTE_PREFERED_NAME, StringUtils.EMPTY ) ) );
        }
        else
        {
        	model.put( MARK_USER,  null );
        }

        // Check the Access Controls of the form
        XPage accessControlPage = AccessControlService.getInstance( ).doExecuteAccessControl(
        		request,
        		nIdForm,
        		Form.RESOURCE_TYPE,
        		null
        		);
        if ( accessControlPage != null )
        {
        	return accessControlPage;
        }

        model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, ACTION_DO_VALIDATE_FORM ) );
        HtmlTemplate templateForm = AppTemplateService.getTemplate( TEMPLATE_HTML_CODE_FORM, locale, model );
        model.put( MARK_FORM_HTML, templateForm.getHtml( ) );
        XPage xPage = getXPage( TEMPLATE_APPOINTMENT_FORM, locale, model );

        if ( _appointmentForm.getDisplayTitleFo( ) )
        {
            xPage.setTitle( _appointmentForm.getTitle( ) );
        }
        return xPage;
    }

    /**
     * Do validate data entered by a user to fill a form
     *
     * @param request
     *            The request
     * @return The next URL to redirect to
     * @throws SiteMessageException
     * @throws UserNotSignedException
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_VALIDATE_FORM )
    public synchronized XPage doValidateForm( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        // CSRF Token control
        if ( !SecurityTokenService.getInstance( ).validate( request, ACTION_DO_VALIDATE_FORM ) )
        {
            throw new AccessDeniedException( MESSAGE_ERROR_TOKEN );
        }
        checkMyLuteceAuthentication( _appointmentForm, request );
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        if ( _notValidatedAppointment == null || _appointmentForm == null || _notValidatedAppointment.getIdForm( ) != _appointmentForm.getIdForm( ) )
        {

            addError( ERROR_MESSAGE_FORM_NO_MORE_VALID, getLocale( request ) );
            _notValidatedAppointment = null;
            _validatedAppointment = null;
            return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
        }
        List<GenericAttributeError> listFormErrors = new ArrayList<>( );
        Locale locale = request.getLocale( );
        String strEmail = request.getParameter( PARAMETER_EMAIL );
        String strEmailConfirm = request.getParameter( PARAMETER_EMAIL_CONFIRMATION );
        String strFirstName = request.getParameter( PARAMETER_FIRST_NAME );
        String strLastName = request.getParameter( PARAMETER_LAST_NAME );
        AppointmentUtilities.checkDateOfTheAppointmentIsNotBeforeNow( _notValidatedAppointment, locale, listFormErrors );
        AppointmentUtilities.checkEmail( strEmail, strEmailConfirm, _appointmentForm, locale, listFormErrors );
        int nbBookedSeats = _nNbPlacesToTake;
        if ( _nNbPlacesToTake == 0 )
        {

            nbBookedSeats = AppointmentUtilities.checkAndReturnNbBookedSeats( request.getParameter( PARAMETER_NUMBER_OF_BOOKED_SEATS ), _appointmentForm,
                    _notValidatedAppointment, locale, listFormErrors );

        }
        AppointmentUtilities.fillAppointmentDTO( _notValidatedAppointment, nbBookedSeats, strEmail, strEmailConfirm, strFirstName, strLastName );
        AppointmentUtilities.validateFormAndEntries( _notValidatedAppointment, request, listFormErrors, false );
        AppointmentUtilities.fillInListResponseWithMapResponse( _notValidatedAppointment );
        AppointmentUtilities.setAppointmentPhoneNumberValuesFromResponse( _notValidatedAppointment );

        boolean bErrors = false;
        if ( _appointmentForm.getEnableMandatoryEmail( )
                && !AppointmentUtilities.checkNbDaysBetweenTwoAppointmentsTaken( _notValidatedAppointment, strEmail, _appointmentForm ) )
        {
            addError( ERROR_MESSAGE_NB_MIN_DAYS_BETWEEN_TWO_APPOINTMENTS, locale );
            bErrors = true;
        }
        if ( _appointmentForm.getEnableMandatoryEmail( )
                && !AppointmentUtilities.checkNbMaxAppointmentsOnAGivenPeriod( _notValidatedAppointment, strEmail, _appointmentForm ) )
        {
            addError( ERROR_MESSAGE_NB_MAX_APPOINTMENTS_ON_A_PERIOD, locale );
            bErrors = true;
        }

        List<AppointmentDTO> listAppointments = new ArrayList<>( );
        if ( _appointmentForm.getEnableMandatoryEmail( )
                && !AppointmentUtilities.checkNbMaxAppointmentsDefinedOnCategory( _notValidatedAppointment, strEmail, _appointmentForm, listAppointments ) )
        {
            StringJoiner builder = new StringJoiner( StringUtils.SPACE );
            String lf = System.getProperty( "line.separator" );
            for ( AppointmentDTO appt : listAppointments )
            {
                builder.add( appt.getLastName( ) );
                builder.add( appt.getFirstName( ) );
                builder.add( appt.getDateOfTheAppointment( ) );
                builder.add( appt.getStartingTime( ).toString( ) );
                builder.add( lf );
            }
            Object [ ] tabAppointment = {
                    builder.toString( )
            };

            String strErrorMessageDateWithAppointments = I18nService.getLocalizedString( ERROR_MESSAGE_NB_MAX_APPOINTMENTS_ON_A_CATEGORY, tabAppointment,
                    locale );
            addError( strErrorMessageDateWithAppointments );
            bErrors = true;
        }
        if ( CollectionUtils.isNotEmpty( listFormErrors ) )
        {
            getModel( ).put( MARK_FORM_ERRORS, listFormErrors );
            for ( GenericAttributeError error : listFormErrors )
            {
            	addError( error.getErrorMessage( ) );
            }
            bErrors = true;
        }
        if ( bErrors )
        {
            LinkedHashMap<String, String> additionalParameters = new LinkedHashMap<>( );
            additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
            additionalParameters.put( PARAMETER_MODIFICATION_FORM, String.valueOf( Boolean.TRUE ) );
            additionalParameters.put( PARAMETER_IS_MODIFICATION, String.valueOf( Boolean.TRUE ) );
            additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + STEP_3 );
            return redirect( request, VIEW_APPOINTMENT_FORM, additionalParameters );
        }
        _validatedAppointment = _notValidatedAppointment;
        _notValidatedAppointment = null;
        String anchor = request.getParameter( PARAMETER_ANCHOR );
        if ( StringUtils.isNotEmpty( anchor ) )
        {
            Map<String, String> additionalParameters = new HashMap<>( );
            additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + anchor );

            return redirect( request, VIEW_DISPLAY_RECAP_APPOINTMENT, additionalParameters );
        }
        else
        {
            return redirectView( request, VIEW_DISPLAY_RECAP_APPOINTMENT );
        }
    }

    /**
     * Display the recap before validating an appointment
     *
     * @param request
     *            The request
     * @return The HTML content to display or the next URL to redirect to
     * @throws UserNotSignedException
     * @throws AccessDeniedException
     */
    @View( VIEW_DISPLAY_RECAP_APPOINTMENT )
    public synchronized XPage displayRecapAppointment( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        checkMyLuteceAuthentication( _appointmentForm, request );
        String anchor = request.getParameter( PARAMETER_ANCHOR );
        String strModifDateAppointment = request.getParameter( PARAMETER_MODIF_DATE );

        if ( StringUtils.isNotEmpty( anchor ) )
        {
            Map<String, String> additionalParameters = new HashMap<>( );
            additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + anchor );
            return redirect( request, VIEW_DISPLAY_RECAP_APPOINTMENT, additionalParameters );
        }

        if ( _validatedAppointment == null || _appointmentForm == null || _validatedAppointment.getIdForm( ) != _appointmentForm.getIdForm( ) )
        {
            addError( ERROR_MESSAGE_FORM_NO_MORE_VALID, getLocale( request ) );
            _notValidatedAppointment = null;
            _validatedAppointment = null;
            return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
        }
        Map<String, Object> model = new HashMap<>( );
        if ( _appointmentForm.getEnableCaptcha( ) && getCaptchaService( ).isAvailable( ) )
        {
            model.put( MARK_CAPTCHA, getCaptchaService( ).getHtmlCode( ) );
        }
        if ( strModifDateAppointment != null && Boolean.parseBoolean( strModifDateAppointment ) )
        {
            model.put( MARK_MODIFICATION_DATE_APPOINTMENT, Boolean.TRUE );

        }
        else
        {

            model.put( MARK_MODIFICATION_DATE_APPOINTMENT, Boolean.FALSE );

        }
        model.put( MARK_FORM_MESSAGES, FormMessageService.findFormMessageByIdForm( _validatedAppointment.getIdForm( ) ) );
        fillCommons( model );
        model.put( MARK_APPOINTMENT, _validatedAppointment );
        Locale locale = getLocale( request );
        model.put( MARK_LIST_RESPONSE_RECAP_DTO, AppointmentUtilities.buildListResponse( _validatedAppointment, request, locale ) );
        model.put( MARK_FORM, _appointmentForm );
        model.put( MARK_NBPLACESTOTAKE, _nNbPlacesToTake );
        model.put( PARAMETER_DATE_OF_DISPLAY, _validatedAppointment.getSlot( ).get( 0 ).getDate( ) );
        model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, ACTION_DO_MAKE_APPOINTMENT ) );

        return getXPage( TEMPLATE_APPOINTMENT_FORM_RECAP, locale, model );
    }

    /**
     * Do save an appointment into the database if it is valid
     *
     * @param request
     *            The request
     * @return The XPage to display
     * @throws UserNotSignedException
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_MAKE_APPOINTMENT )
    public synchronized XPage doMakeAppointment( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        checkMyLuteceAuthentication( _appointmentForm, request );
        // CSRF Token control
        if ( !SecurityTokenService.getInstance( ).validate( request, ACTION_DO_MAKE_APPOINTMENT ) )
        {
            throw new AccessDeniedException( MESSAGE_ERROR_TOKEN );
        }
        if ( _validatedAppointment == null || _appointmentForm == null || _validatedAppointment.getIdForm( ) != _appointmentForm.getIdForm( ) )
        {
            addError( ERROR_MESSAGE_FORM_NO_MORE_VALID, getLocale( request ) );
            _notValidatedAppointment = null;
            _validatedAppointment = null;
            return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
        }
        if ( !_appointmentForm.getIsActive( ) )
        {
            addError( ERROR_MESSAGE_FORM_NOT_ACTIVE, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_NB_PLACE_TO_TAKE,
                    _nNbPlacesToTake );
        }

        if ( StringUtils.isNotEmpty( request.getParameter( PARAMETER_BACK ) ) )
        {
            LinkedHashMap<String, String> parameters = new LinkedHashMap<>( );
            parameters.put( PARAMETER_ID_FORM, String.valueOf( _validatedAppointment.getIdForm( ) ) );
            parameters.put( PARAMETER_IS_MODIFICATION, String.valueOf( Boolean.TRUE ) );

            return redirect( request, VIEW_APPOINTMENT_FORM, parameters );
        }
        if ( _appointmentForm.getEnableCaptcha( ) && getCaptchaService( ).isAvailable( ) && !getCaptchaService( ).validate( request ) )
        {
            addError( ERROR_MESSAGE_CAPTCHA, getLocale( request ) );
            return redirect( request, VIEW_DISPLAY_RECAP_APPOINTMENT, PARAMETER_ID_FORM, _validatedAppointment.getIdForm( ) );
        }

        int nIdAppointment;
        try
        {

            nIdAppointment = SlotSafeService.saveAppointment( _validatedAppointment, request );

        }
        catch( SlotFullException e )
        {

            addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, _validatedAppointment.getIdForm( ), PARAMETER_NB_PLACE_TO_TAKE,
                    _nNbPlacesToTake );
        }
        catch( SlotEditTaskExpiredTimeException e )
        {
            addError( ERROR_MESSAGE_SLOT_EDIT_TASK_EXPIRED_TIME, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, _validatedAppointment.getIdForm( ), PARAMETER_NB_PLACE_TO_TAKE,
                    _nNbPlacesToTake );
        }
        catch( AppointmentSavedException e )
        {
            nIdAppointment = _validatedAppointment.getIdAppointment( );
            AppLogService.error( "Error Save appointment: " + e.getMessage( ), e );
        }
        AppLogService.info( LogUtilities.buildLog( ACTION_DO_MAKE_APPOINTMENT, Integer.toString( nIdAppointment ), null ) );
        AppointmentAsynchronousUploadHandler.getHandler( ).removeSessionFiles( request.getSession( ) );
        _nNbPlacesToTake = 0;
        int nIdForm = _validatedAppointment.getIdForm( );
       // _validatedAppointment = null;

        // Remove the session data of this form
        AccessControlService.getInstance( ).cleanSessionData( request, nIdForm, Form.RESOURCE_TYPE );

        String anchor = request.getParameter( PARAMETER_ANCHOR );
        if ( StringUtils.isNotEmpty( anchor ) )
        {
            LinkedHashMap<String, String> additionalParameters = new LinkedHashMap<>( );
            additionalParameters.put( PARAMETER_ID_FORM, String.valueOf( nIdForm ) );
            additionalParameters.put( PARAMETER_ANCHOR, MARK_ANCHOR + anchor );
            return redirect( request, VIEW_GET_APPOINTMENT_CREATED, additionalParameters );
        }
        else
        {
            return redirect( request, VIEW_GET_APPOINTMENT_CREATED, PARAMETER_ID_FORM, nIdForm );
        }
    }

    /**
     * Get the page to notify the user that the appointment has been created
     *
     * @param request
     *            The request
     * @return The XPage to display
     */
    @View( VIEW_GET_APPOINTMENT_CREATED )
    public synchronized XPage getAppointmentCreated( HttpServletRequest request )
    {
    	if( _validatedAppointment == null )
    	{
    		return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
    	}
        int nIdForm = Integer.parseInt( request.getParameter( PARAMETER_ID_FORM ) );
        FormMessage formMessages = FormMessageService.findFormMessageByIdForm( nIdForm );
        AppointmentFormDTO form = FormService.buildAppointmentForm( nIdForm, 0 );
        Slot firstSlot = _validatedAppointment.getSlot().get(0);
        Slot lastSlot = firstSlot;
        if (  _validatedAppointment.getSlot().size( ) > 1 )
        {
                    lastSlot = _validatedAppointment.getSlot().get(_validatedAppointment.getSlot().size( ) -1 );
        }
        String strTimeBegin = firstSlot.getStartingDateTime( ).toLocalTime( ).toString( );
        String strTimeEnd = lastSlot.getEndingDateTime( ).toLocalTime( ).toString( );
        formMessages.setTextAppointmentCreated( formMessages.getTextAppointmentCreated( ).replace( MARK_REF, _validatedAppointment.getReference( ) )
                .replace( MARK_DATE_APP, firstSlot.getStartingDateTime().format( Utilities.getFormatter( ) ) )
                .replace( MARK_TIME_BEGIN, strTimeBegin ).replace( MARK_TIME_END, strTimeEnd) );
        Map<String, Object> model = new HashMap<>( );
        model.put( MARK_LIST_RESPONSE_RECAP_DTO, AppointmentUtilities.buildListResponse( _validatedAppointment, request, getLocale( request ) ) );
        model.put( MARK_DATE_APPOINTMENT, firstSlot.getDate().format( Utilities.getFormatter( ) ) );
        model.put( MARK_STARTING_TIME_APPOINTMENT, firstSlot.getStartingTime( ) );
        model.put( MARK_ENDING_TIME_APPOINTMENT, lastSlot.getEndingTime( ) );
        model.put( MARK_USER,  _validatedAppointment.getUser( ) );
        model.put( MARK_PLACES, _validatedAppointment.getNbPlaces( ) );
        model.put( MARK_FORM, form );
        model.put( MARK_FORM_MESSAGES, formMessages );
        _appointmentForm = null;
        _validatedAppointment = null;
        return getXPage( TEMPLATE_APPOINTMENT_CREATED, getLocale( request ), model );
    }

    /**
     * Return to the display recap view with the new date selected on the calendar
     *
     * @param request
     *            the request
     * @return to the display recap view
     * @throws AccessDeniedException
     * @throws SiteMessageException
     */

    @View( VIEW_CHANGE_DATE_APPOINTMENT )
    public synchronized XPage getViewChangeDateAppointment( HttpServletRequest request )
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        LocalDateTime startingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_STARTING_DATE_TIME ) );
        Locale locale = getLocale( request );
        int nIdForm = Integer.parseInt( strIdForm );
        Form form = FormService.findFormLightByPrimaryKey( nIdForm );

        if ( !form.getIsActive( ) || _validatedAppointment.getStartingDateTime( ).isBefore( LocalDateTime.now( ) )
                || !isAuthorizedDate( startingDateTime, locale ) )
        {
            _validatedAppointment = null;
            addError( ERROR_MESSAGE_FORM_NOT_ACTIVE, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, form.getIdForm( ), PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
        }
        int nNbConsecutiveSlot = ( _nNbPlacesToTake == 0 ) ? 1 : _nNbPlacesToTake;
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
        Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );
        List<Slot> listSlot = SlotService.buildListSlot( nIdForm, mapReservationRule, startingDateTime.toLocalDate( ), startingDateTime.toLocalDate( ) );
        listSlot = listSlot.stream( )
                .filter( s -> ( ( startingDateTime.compareTo( s.getStartingDateTime( ) ) <= 0 ) && ( s.getNbRemainingPlaces( ) > 0 ) && ( s.getIsOpen( ) ) ) )
                .limit( nNbConsecutiveSlot ).collect( Collectors.toList( ) );

        if ( listSlot == null || ( _nNbPlacesToTake > 0 && listSlot.size( ) != _nNbPlacesToTake ) || !AppointmentUtilities.isConsecutiveSlots( listSlot ) )
        {
            addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
        }

        boolean bool = true;
        _validatedAppointment.setSlot( null );
        _validatedAppointment.setNbMaxPotentialBookedSeats( 0 );
        for ( Slot slot : listSlot )
        {

            if ( slot.getIdSlot( ) == 0 )
            {

                slot = SlotSafeService.createSlot( slot );

            }
            else
            {

                slot = SlotService.findSlotById( slot.getIdSlot( ) );
            }

            // Need to check competitive access
            // May be the slot is already taken at the same time
            if ( slot.getNbPotentialRemainingPlaces( ) <= 0 )
            {
                addError( ERROR_MESSAGE_SLOT_FULL, locale );
                return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
            }

            _validatedAppointment.addSlot( slot );

            if ( bool )
            {
                _validatedAppointment.setDateOfTheAppointment( slot.getDate( ).format( Utilities.getFormatter( ) ) );
                ReservationRule reservationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( nIdForm, slot.getDate( ) );
                _appointmentForm = FormService.buildAppointmentForm( nIdForm, reservationRule );
                bool = false;
            }
            AppointmentUtilities.putTimerInSession( request, slot.getIdSlot( ), _validatedAppointment, _appointmentForm.getMaxPeoplePerAppointment( ) );
        }

        if ( _validatedAppointment.getNbMaxPotentialBookedSeats( ) == 0 )
        {
            addError( ERROR_MESSAGE_SLOT_FULL, locale );
            return redirect( request, VIEW_APPOINTMENT_CALENDAR, PARAMETER_ID_FORM, nIdForm, PARAMETER_NB_PLACE_TO_TAKE, _nNbPlacesToTake );
        }

        for ( Response response : _validatedAppointment.getListResponse( ) )
        {
            if ( response.getFile( ) != null )
            {
                IFileStoreServiceProvider fileStoreService = FileService.getInstance( ).getFileStoreServiceProvider( );
                File file = null;
                try
                {
                    file = fileStoreService.getFile( response.getFile( ).getFileKey( ) );
                }
                catch( FileServiceException e )
                {
                    AppLogService.error( "Error get file: " + response.getFile( ).getFileKey( ), e );
                }
                response.setFile( file );
            }
        }

        Map<String, String> additionalParameters = new HashMap<>( );
        additionalParameters.put( PARAMETER_MODIF_DATE, "true" );
        additionalParameters.put( PARAMETER_ID_FORM, Integer.toString( nIdForm ) );
        return redirect( request, VIEW_DISPLAY_RECAP_APPOINTMENT, additionalParameters );
    }

    /**
     * Get the view of all the forms on front office side
     *
     * @param request
     *            the request
     * @return the xpage
     */
    @View( value = VIEW_APPOINTMENT_FORM_LIST, defaultView = true )
    public synchronized XPage getFormList( HttpServletRequest request )
    {
        Locale locale = getLocale( request );
        _appointmentForm = null;
        _validatedAppointment = null;
        String strHtmlContent = getFormListHtml( locale, getModel( ) );

        XPage xPage = getXPage( );
        xPage.setContent( strHtmlContent );
        return xPage;
    }

    /**
     * Get the view for the user who wants to cancel its appointment
     *
     * @param request
     * @return the view
     */
    @View( VIEW_GET_VIEW_CANCEL_APPOINTMENT )
    public synchronized XPage getViewCancelAppointment( HttpServletRequest request )
    {
        String refAppointment = request.getParameter( PARAMETER_REF_APPOINTMENT );
        Appointment appointment = null;
        if ( StringUtils.isNotEmpty( refAppointment ) )
        {
            appointment = AppointmentService.findAppointmentByReference( refAppointment );
        }
        Map<String, Object> model = new HashMap<>( );
        model.put( PARAMETER_REF_APPOINTMENT, refAppointment );
        model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, ACTION_DO_CANCEL_APPOINTMENT ) );
        if ( appointment != null )
        {

            if ( appointment.getIsCancelled( ) )
            {
                model.put( MARK_APPOINTMENT_ALREADY_CANCELLED, Boolean.TRUE );
            }

            int nIdAppointment = appointment.getIdAppointment( );
            AppointmentDTO appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( nIdAppointment );
            // Check if the appointment is passed
            if ( appointmentDto.getStartingDateTime( ).isBefore( LocalDateTime.now( ) ) )
            {
                model.put( MARK_APPOINTMENT_PASSED, Boolean.TRUE );
            }
            model.put( MARK_DATE_APPOINTMENT, appointmentDto.getDateOfTheAppointment( ) );
            model.put( MARK_STARTING_TIME_APPOINTMENT, appointmentDto.getStartingTime( ) );
            model.put( MARK_ENDING_TIME_APPOINTMENT, appointmentDto.getEndingTime( ) );
            model.put( MARK_PLACES, appointment.getNbPlaces( ) );
            model.put( MARK_FORM, FormService.buildAppointmentForm( appointmentDto.getIdForm( ), 0 ) );
            model.put( MARK_FORM_MESSAGES, FormMessageService.findFormMessageByIdForm( appointmentDto.getIdForm( ) ) );
            AppointmentDTO appointmentDTO = AppointmentService.buildAppointmentDTOFromIdAppointment( nIdAppointment );
            appointmentDTO.setListResponse( AppointmentResponseService.findAndBuildListResponse( nIdAppointment, request ) );
            appointmentDTO.setMapResponsesByIdEntry( AppointmentResponseService.buildMapFromListResponse( appointmentDTO.getListResponse( ) ) );
            model.put( MARK_LIST_RESPONSE_RECAP_DTO, AppointmentUtilities.buildListResponse( appointmentDTO, request, getLocale( request ) ) );
            model.put( MARK_USER, UserService.findUserById( appointment.getIdUser( ) ) );

        }
        else
        {
            model.put( MARK_NO_APPOINTMENT_WITH_THIS_REFERENCE, Boolean.TRUE );
        }
        Locale locale = getLocale( request );
        XPage xpage = getXPage( TEMPLATE_CANCEL_APPOINTMENT, locale, model );
        xpage.setTitle( I18nService.getLocalizedString( MESSAGE_CANCEL_APPOINTMENT_PAGE_TITLE, locale ) );
        return xpage;

    }

    /**
     * Cancel an appointment
     *
     * @param request
     * @return the confirmation view of the appointment cancelled
     */
    @Action( ACTION_DO_CANCEL_APPOINTMENT )
    public synchronized XPage doCancelAppointment( HttpServletRequest request ) throws AccessDeniedException
    {
        // CSRF Token control
        if ( !SecurityTokenService.getInstance( ).validate( request, ACTION_DO_CANCEL_APPOINTMENT ) )
        {
            throw new AccessDeniedException( MESSAGE_ERROR_TOKEN );
        }

        String strRef = request.getParameter( PARAMETER_REF_APPOINTMENT );
        if ( StringUtils.isNotEmpty( strRef ) )
        {
            Appointment appointment = AppointmentService.findAppointmentByReference( strRef );
            AppointmentDTO appointmentDto = AppointmentService.buildAppointmentDTOFromIdAppointment( appointment.getIdAppointment( ) );

            // Accept only one cancel !!!
            if ( !appointment.getIsCancelled( ) && !appointmentDto.getStartingDateTime( ).isBefore( LocalDateTime.now( ) ) )
            {
                // Check if the appointment is passed
                if ( appointment.getIdActionCancelled( ) > 0 )
                {
                    try
                    {
                        WorkflowService.getInstance( ).doProcessAction( appointment.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE,
                                appointment.getIdActionCancelled( ), appointmentDto.getIdForm( ), request, request.getLocale( ), true, null );
                        AppointmentListenerManager.notifyAppointmentWFActionTriggered( appointment.getIdAppointment( ), appointment.getIdActionCancelled( ) );
                    }
                    catch( Exception e )
                    {
                        AppLogService.error( "Error Workflow", e );
                    }
                }
                else
                {
                    appointment.setIsCancelled( Boolean.TRUE );
                    AppointmentService.updateAppointment( appointment );
                    AppLogService.info( LogUtilities.buildLog( ACTION_DO_CANCEL_APPOINTMENT, Integer.toString( appointment.getIdAppointment( ) ), null ) );
                }
                Map<String, String> mapParameters = new HashMap<>( );
                if ( StringUtils.isNotEmpty( request.getParameter( PARAMETER_FROM_MY_APPOINTMENTS ) ) )
                {
                    String strReferer = request.getHeader( PARAMETER_REFERER );
                    if ( StringUtils.isNotEmpty( strReferer ) )
                    {
                        mapParameters.put( MARK_FROM_URL, strReferer );
                    }
                    mapParameters.put( PARAMETER_FROM_MY_APPOINTMENTS, request.getParameter( PARAMETER_FROM_MY_APPOINTMENTS ) );
                }
                mapParameters.put( PARAMETER_ID_FORM, Integer.toString( appointmentDto.getIdForm( ) ) );
                return redirect( request, VIEW_APPOINTMENT_CANCELED, mapParameters );
            }
        }
        return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
    }

    /**
     * Get the page to confirm that the appointment has been canceled
     *
     * @param request
     *            The request
     * @return The XPage to display
     */
    @View( VIEW_APPOINTMENT_CANCELED )
    public synchronized XPage getAppointmentCanceled( HttpServletRequest request )
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        if ( StringUtils.isNotEmpty( strIdForm ) && StringUtils.isNumeric( strIdForm ) )
        {
            int nIdForm = Integer.parseInt( strIdForm );
            Map<String, Object> model = new HashMap<>( );
            model.put( MARK_FORM_MESSAGES, FormMessageService.findFormMessageByIdForm( nIdForm ) );
            if ( Boolean.parseBoolean( request.getParameter( PARAMETER_FROM_MY_APPOINTMENTS ) ) )
            {
                String strFromUrl = request.getParameter( MARK_FROM_URL );
                model.put( MARK_BACK_URL, StringUtils.isNotEmpty( strFromUrl ) ? strFromUrl : getViewUrl( VIEW_GET_MY_APPOINTMENTS ) );
            }
            return getXPage( TEMPLATE_APPOINTMENT_CANCELED, getLocale( request ), model );
        }
        return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
    }

    /**
     * Get the page to view the appointments of a user
     *
     * @param request
     *            The request
     * @return The XPage to display
     * @throws UserNotSignedException
     *             If the authentication is enabled and the user has not signed in
     */
    @View( VIEW_GET_MY_APPOINTMENTS )
    public synchronized XPage getMyAppointments( HttpServletRequest request ) throws UserNotSignedException
    {
        if ( !SecurityService.isAuthenticationEnable( ) )
        {
            return redirectView( request, VIEW_APPOINTMENT_FORM_LIST );
        }
        Locale locale = getLocale( request );
        XPage xPage = getXPage( );
        xPage.setContent( getMyAppointmentsHtml( request, locale, getModel( ) ) );
        xPage.setTitle( I18nService.getLocalizedString( MESSAGE_MY_APPOINTMENTS_PAGE_TITLE, locale ) );
        return xPage;
    }

    /**
     * Get the HTML content of the my appointment page of a user
     *
     * @param request
     *            The request
     * @param locale
     *            The locale
     * @return The HTML content, or null if the
     * @throws UserNotSignedException
     *             If the user has not signed in
     * @deprecated use {@link #getMyAppointmentsHtml(HttpServletRequest, Locale, Map)}
     */
    @Deprecated
    public static String getMyAppointmentsXPage( HttpServletRequest request, Locale locale, Map<String, Object> model ) throws UserNotSignedException
    {
        if ( !SecurityService.isAuthenticationEnable( ) )
        {
            return null;
        }
        LuteceUser luteceUser = SecurityService.getInstance( ).getRegisteredUser( request );
        if ( luteceUser == null )
        {
            throw new UserNotSignedException( );
        }
        AppointmentFilterDTO appointmentFilter = new AppointmentFilterDTO( );
        appointmentFilter.setGuid( luteceUser.getName( ) );
        List<AppointmentDTO> listAppointmentDTO = AppointmentService.findListAppointmentsDTOByFilter( appointmentFilter );
        for ( AppointmentDTO apptDto : listAppointmentDTO )
        {
            Form form = FormService.findFormLightByPrimaryKey( apptDto.getIdForm( ) );
            if ( form.getIdWorkflow( ) > 0 && WorkflowService.getInstance( ).isAvailable( ) )
            {

                apptDto.setListWorkflowActions( WorkflowService.getInstance( ).getActions( apptDto.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE,
                        form.getIdWorkflow( ), luteceUser ) );

            }
        }

        model = ( model == null ) ? new HashMap<>( ) : model;
        model.put( MARK_LIST_APPOINTMENTS, listAppointmentDTO );
        model.put( MARK_FORM_LIST, FormService.findAllInReferenceList( ) );
        model.put( MARK_LOCALE_DATE_TIME, LocalDateTime.now( ) );
        HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MY_APPOINTMENTS, locale, model );
        return template.getHtml( );
    }

    /**
     * Get the HTML content of a user's "My appointments" page
     *
     * @param request
     *            The request
     * @param locale
     *            The locale
     * @return The HTML content, or null if the user is not logged in
     * @throws UserNotSignedException
     *             If the user has not signed in
     */
    public static String getMyAppointmentsHtml( HttpServletRequest request, Locale locale, Map<String, Object> model ) throws UserNotSignedException
    {
        if ( !SecurityService.isAuthenticationEnable( ) )
        {
            return null;
        }
        LuteceUser luteceUser = SecurityService.getInstance( ).getRegisteredUser( request );
        if ( luteceUser != null )
        {
            throw new UserNotSignedException( );
        }
        AppointmentFilterDTO appointmentFilter = new AppointmentFilterDTO( );
        appointmentFilter.setGuid( luteceUser.getName( ) );
        List<AppointmentDTO> listAppointmentDTO = AppointmentService.findListAppointmentsDTOByFilter( appointmentFilter );
        for ( AppointmentDTO apptDto : listAppointmentDTO )
        {
            Form form = FormService.findFormLightByPrimaryKey( apptDto.getIdForm( ) );
            if ( form.getIdWorkflow( ) > 0 && WorkflowService.getInstance( ).isAvailable( ) )
            {
                apptDto.setListWorkflowActions( WorkflowService.getInstance( ).getActions( apptDto.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE,
                        form.getIdWorkflow( ), luteceUser ) );
            }
        }

        model = ( model == null ) ? new HashMap<>( ) : model;
        model.put( MARK_LIST_APPOINTMENTS, listAppointmentDTO );
        model.put( MARK_FORM_LIST, FormService.findAllInReferenceList( ) );
        model.put( MARK_LOCALE_DATE_TIME, LocalDateTime.now( ) );
        HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MY_APPOINTMENTS, locale, model );
        return template.getHtml( );
    }

    /**
     * Get the html content of the list of forms
     *
     * @param locale
     *            The locale
     * @param model
     *            The model
     * @return The HTML content to display
     */
    public static String getFormListHtml( Locale locale, Map<String, Object> model )
    {
        model = ( model == null ) ? new HashMap<>( ) : model;
        List<AppointmentFormDTO> listAppointmentForm = FormService.buildAllActiveAndDisplayedOnPortletAppointmentForm( );
        // We keep only the active
        if ( CollectionUtils.isNotEmpty( listAppointmentForm ) )
        {
            listAppointmentForm = listAppointmentForm.stream( )
                    .filter( a -> ( a.getDateStartValidity( ) != null ) && ( a.getDateStartValidity( ).toLocalDate( ).isBefore( LocalDate.now( ) )
                            || a.getDateStartValidity( ).toLocalDate( ).equals( LocalDate.now( ) ) ) )
                    .sorted( ( a1, a2 ) -> a1.getTitle( ).compareTo( a2.getTitle( ) ) ).collect( Collectors.toList( ) );
        }
        List<String> icons = new ArrayList<>( );
        for ( AppointmentFormDTO form : listAppointmentForm )
        {
            ImageResource img = form.getIcon( );
            if ( img == null || img.getImage( ) == null || StringUtils.isEmpty( img.getMimeType( ) )
                    || StringUtils.equals( img.getMimeType( ), MARK_ICON_NULL ) )
            {
                icons.add( MARK_ICON_NULL );
            }
            else
            {
                byte [ ] imgBytesAsBase64 = Base64.encodeBase64( img.getImage( ) );
                String imgDataAsBase64 = new String( imgBytesAsBase64 );
                String strMimeType = img.getMimeType( );
                String imgAsBase64 = MARK_DATA + MARK_COLON + strMimeType + MARK_SEMI_COLON + MARK_BASE_64 + MARK_COMMA + imgDataAsBase64;
                icons.add( imgAsBase64 );
            }
        }
        model.put( MARK_ICONS, icons );
        model.put( MARK_FORM_LIST, listAppointmentForm );
        HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_APPOINTMENT_FORM_LIST, locale, model );
        return template.getHtml( );
    }

    /**
     * Get the workflow action form before processing the action. If the action does not need to display any form, then redirect the user to the workflow action
     * processing page.
     *
     * @param request
     *            The request
     * @return The HTML content to display, or the next URL to redirect the user to
     */
    @View( VIEW_WORKFLOW_ACTION_FORM )
    public synchronized XPage getWorkflowActionForm( HttpServletRequest request ) throws UserNotSignedException
    {
        String strIdAction = request.getParameter( PARAMETER_ID_ACTION );
        String refAppointment = request.getParameter( PARAMETER_REF_APPOINTMENT );
        if ( StringUtils.isNotEmpty( strIdAction ) && StringUtils.isNumeric( strIdAction ) && StringUtils.isNotEmpty( refAppointment ) )
        {
            int nIdAction = Integer.parseInt( strIdAction );
            if ( WorkflowService.getInstance( ).isDisplayTasksForm( nIdAction, getLocale( request ) ) )
            {
                AppointmentDTO appointment = AppointmentService.buildAppointmentDTOFromRefAppointment( refAppointment );
                // Check if an appointment Object was found and built with the given reference
                if( appointment == null )
                {
                	// When the appointment Object doesn't exist, we display an error message to the user
                	return getXPage( TEMPLATE_ERROR_APPOINTMENT_REFERENCE, getLocale( request ), null );
                }
                ITaskService taskService = SpringContextService.getBean( TaskService.BEAN_SERVICE );
                List<ITask> listActionTasks = taskService.getListTaskByIdAction( nIdAction, getLocale( request ) );
                if ( listActionTasks.stream( ).anyMatch( task -> task.getTaskType( ).getKey( ).equals( "taskReportAppointment" ) ) )
                {
                    Map<String, String> additionalParameters = new HashMap<>( );
                    additionalParameters.put( PARAMETER_REF_APPOINTMENT, appointment.getReference( ) );
                    additionalParameters.put( PARAMETER_NB_PLACE_TO_TAKE, String.valueOf( appointment.getNbBookedSeats( ) ) );
                    additionalParameters.put( PARAMETER_ID_FORM, String.valueOf( appointment.getIdForm( ) ) );
                    return redirect( request, VIEW_APPOINTMENT_CALENDAR, additionalParameters );
                }
                String strHtmlTasksForm = WorkflowService.getInstance( ).getDisplayTasksForm( appointment.getIdAppointment( ),
                        Appointment.APPOINTMENT_RESOURCE_TYPE, nIdAction, request, getLocale( request ), null );
                Map<String, Object> model = getModel( );
                model.put( MARK_TASKS_FORM, strHtmlTasksForm );
                model.put( PARAMETER_ID_ACTION, nIdAction );
                model.put( PARAMETER_REF_APPOINTMENT, refAppointment );

                return getXPage( TEMPLATE_TASKS_FORM_WORKFLOW, getLocale( request ), model );
            }

            return doProcessWorkflowAction( request );
        }
        return getMyAppointments( request );
    }

    /**
     * Do process a workflow action over an appointment
     *
     * @param request
     *            The request
     * @return The next URL to redirect to
     */
    @Action( ACTION_DO_PROCESS_WORKFLOW_ACTION )
    public synchronized XPage doProcessWorkflowAction( HttpServletRequest request ) throws UserNotSignedException
    {
        LuteceUser luteceUser = SecurityService.getInstance( ).getRegisteredUser( request );
        String strIdAction = request.getParameter( PARAMETER_ID_ACTION );
        String refAppointment = request.getParameter( PARAMETER_REF_APPOINTMENT );
        if ( StringUtils.isNotEmpty( strIdAction ) && StringUtils.isNumeric( strIdAction ) && StringUtils.isNotEmpty( refAppointment ) )
        {
            int nIdAction = Integer.parseInt( strIdAction );
            Appointment appointment = AppointmentService.findAppointmentByReference( refAppointment );
            // Check if an appointment Object was found and built with the given reference
            if( appointment == null )
            {
            	// When the appointment Object doesn't exist, we display an error message to the user
            	return getXPage( TEMPLATE_ERROR_APPOINTMENT_REFERENCE, getLocale( request ), null );
            }
            int nIdAppointment = appointment.getIdAppointment( );

            List<AppointmentSlot> listApptSlot = appointment.getListAppointmentSlot( );
            Slot slot = SlotService.findSlotById( listApptSlot.get( 0 ).getIdSlot( ) );

            if ( request.getParameter( PARAMETER_BACK ) == null )
            {
                try
                {
                    if ( WorkflowService.getInstance( ).isDisplayTasksForm( nIdAction, getLocale( request ) ) )
                    {
                        if ( WorkflowService.getInstance( ).canProcessAction( nIdAppointment, Appointment.APPOINTMENT_RESOURCE_TYPE, nIdAction,
                                slot.getIdForm( ), request, false, luteceUser ) )
                        {

                            String strError = WorkflowService.getInstance( ).doSaveTasksForm( nIdAppointment, Appointment.APPOINTMENT_RESOURCE_TYPE, nIdAction,
                                    slot.getIdForm( ), request, getLocale( request ), luteceUser );
                            if ( strError != null )
                            {
                                AppLogService.error( "Error Workflow:" + strError );
                                addError( strError );
                                return getWorkflowActionForm( request );
                            }
                        }
                        else
                        {

                            AppLogService.error( "Error Workflow can not process Action" );
                            addError( "Error Workflow can not process Action" );
                            return getMyAppointments( request );
                        }
                    }
                    else
                    {
                        ITaskService taskService = SpringContextService.getBean( TaskService.BEAN_SERVICE );
                        List<ITask> listActionTasks = taskService.getListTaskByIdAction( nIdAction, getLocale( request ) );
                        for ( ITask task : listActionTasks )
                        {
                            if ( task.getTaskType( ).getKey( ).equals( "taskChangeAppointmentStatus" ) && ( appointment.getIsCancelled( ) ) )
                            {
                                for ( AppointmentSlot apptSlt : listApptSlot )
                                {

                                    Slot slt = SlotService.findSlotById( apptSlt.getIdSlot( ) );

                                    if ( apptSlt.getNbPlaces( ) > slt.getNbRemainingPlaces( ) )
                                    {
                                        AppLogService.error( "Error Workflow:" + ERROR_MESSAGE_SLOT_FULL );
                                        addError( ERROR_MESSAGE_SLOT_FULL, getLocale( request ) );
                                        return getMyAppointments( request );

                                    }
                                }
                            }
                        }

                        WorkflowService.getInstance( ).doProcessAction( nIdAppointment, Appointment.APPOINTMENT_RESOURCE_TYPE, nIdAction, slot.getIdForm( ),
                                request, getLocale( request ), false, luteceUser );
                        AppointmentListenerManager.notifyAppointmentWFActionTriggered( nIdAppointment, nIdAction );

                    }
                    addInfo( MESSAGE_WF_ACTION_SUCESS, getLocale( request ) );
                }
                catch( Exception e )
                {
                    AppLogService.error( "Error Workflow", e );
                }

            }
        }
        return getMyAppointments( request );
    }

    /**
     * Get the captcha security service
     *
     * @return The captcha security service
     */
    private CaptchaSecurityService getCaptchaService( )
    {
        if ( _captchaSecurityService == null )
        {
            _captchaSecurityService = new CaptchaSecurityService( );
        }

        return _captchaSecurityService;
    }

    /**
     * Get the URL
     *
     * @param request
     *            Get the URL to cancel an appointment in FO
     * @param appointment
     *            The appointment
     * @return The URL to cancel the appointment
     */
    public static String getCancelAppointmentUrl( HttpServletRequest request, Appointment appointment )
    {
        UrlItem urlItem = new UrlItem( AppPathService.getProdUrl( request ) + AppPathService.getPortalUrl( ) );
        urlItem.addParameter( MVCUtils.PARAMETER_PAGE, XPAGE_NAME );
        urlItem.addParameter( MVCUtils.PARAMETER_VIEW, VIEW_GET_VIEW_CANCEL_APPOINTMENT );
        urlItem.addParameter( PARAMETER_REF_APPOINTMENT, appointment.getReference( ) );
        return urlItem.getUrl( );
    }

    /**
     * Get the URL
     *
     * @param request
     *            Get the URL to cancel an appointment in FO
     * @param appointment
     *            The appointment
     * @return The URL to cancel the appointment
     */
    public static String getCancelAppointmentUrl( Appointment appointment )
    {
        UrlItem urlItem = new UrlItem( AppPathService.getProdUrl( StringUtils.EMPTY ) + AppPathService.getPortalUrl( ) );
        urlItem.addParameter( MVCUtils.PARAMETER_PAGE, XPAGE_NAME );
        urlItem.addParameter( MVCUtils.PARAMETER_VIEW, VIEW_GET_VIEW_CANCEL_APPOINTMENT );
        urlItem.addParameter( PARAMETER_REF_APPOINTMENT, appointment.getReference( ) );
        return urlItem.getUrl( );
    }

    /**
     * Get the URL
     *
     * @param request
     *            Get the URL to report an appointment in FO
     * @param appointment
     *            The appointment
     * @return The URL to report the appointment
     */
    public static String getReportAppointmentUrl( AppointmentDTO appointment )
    {
        UrlItem urlItem = new UrlItem( AppPathService.getProdUrl( StringUtils.EMPTY ) + AppPathService.getPortalUrl( ) );
        urlItem.addParameter( MVCUtils.PARAMETER_PAGE, XPAGE_NAME );
        urlItem.addParameter( MVCUtils.PARAMETER_VIEW, VIEW_APPOINTMENT_CALENDAR );
        urlItem.addParameter( PARAMETER_ID_FORM, appointment.getIdForm( ) );
        urlItem.addParameter( PARAMETER_NB_PLACE_TO_TAKE, appointment.getNbBookedSeats( ) );
        urlItem.addParameter( PARAMETER_REF_APPOINTMENT, appointment.getReference( ) );
        return urlItem.getUrl( );
    }

    /**
     * Set the user infos to the appointment DTO
     *
     * @param request
     *            the request
     * @param appointment
     *            the appointment DTO
     */
    public void setUserInfo( HttpServletRequest request, AppointmentDTO appointment )
    {
        if ( SecurityService.isAuthenticationEnable( ) && ( appointment != null ) )
        {
            LuteceUser user = SecurityService.getInstance( ).getRegisteredUser( request );

            if ( user != null )
            {
                appointment.setGuid( user.getName( ) );
                appointment.setFirstName( user.getUserInfo( AppPropertiesService.getProperty( PROPERTY_USER_ATTRIBUTE_FIRST_NAME, StringUtils.EMPTY ) ) );
                appointment.setEmail( user.getUserInfo( AppPropertiesService.getProperty( PROPERTY_USER_ATTRIBUTE_EMAIL, StringUtils.EMPTY ) ) );
                String lastName = user.getUserInfo( AppPropertiesService.getProperty( PROPERTY_USER_ATTRIBUTE_PREFERED_NAME, StringUtils.EMPTY ) );
                if ( ( lastName == null ) || lastName.isEmpty( ) )
                {
                    lastName = user.getUserInfo( AppPropertiesService.getProperty( PROPERTY_USER_ATTRIBUTE_LAST_NAME, StringUtils.EMPTY ) );
                }
                appointment.setLastName( lastName );
            }
        }
    }

    /**
     * check if authentication
     *
     * @param form
     *            Form
     * @param request
     *            HttpServletRequest
     * @throws UserNotSignedException
     *             exception if the form requires an authentication and the user is not logged
     */
    private void checkMyLuteceAuthentication( AppointmentFormDTO form, HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        if ( !SecurityService.isAuthenticationEnable( ) )
        {
            return;
        }

        // Try to register the user in case of external authentication
        SecurityService securityService = SecurityService.getInstance( );
        if ( securityService.isExternalAuthentication( ) )
        {
            if ( form.getActiveAuthentication( ) )
            {
                // The authentication is external
                // Should register the user if it's not already done
                if ( securityService.getRegisteredUser( request ) == null && securityService.getRemoteUser( request ) == null )
                {
                    // Authentication is required to access to the portal
                    throw new UserNotSignedException( );
                }

                if ( !Form.ROLE_NONE.equals( form.getRole( ) ) && !securityService.isUserInRole( request, form.getRole( ) ) )
                {
                    // User must have the right role
                    throw new AccessDeniedException( "Unauthorized" );
                }
            }
        }
        else
        {
            // If portal authentication is enabled and user is null and the
            // requested URL
            // is not the login URL, user cannot access to Portal
            if ( form.getActiveAuthentication( ) && securityService.getRegisteredUser( request ) == null && !securityService.isLoginUrl( request ) )
            {
                // Authentication is required to access to the portal
                throw new UserNotSignedException( );
            }
        }
    }

    /**
     * Check if the date are in the validity date display range of the form
     *
     * @param date
     *            the starting date of slot
     * @param locale
     *            the locale
     * @return true if the starting date of slot is displayed on FO
     */
    private boolean isAuthorizedDate( LocalDateTime date, Locale locale )
    {
        // Get the min time from now before a user can take an appointment (in hours)
        LocalDateTime startingDateOfDisplay = LocalDateTime.now( ).plusHours( _appointmentForm.getMinTimeBeforeAppointment( ) );
        // validity date range of the form
        LocalDateTime startingValidityDate = _appointmentForm.getDateStartValidity( ).toLocalDate( ).atStartOfDay( );
        if ( startingValidityDate.isAfter( startingDateOfDisplay ) )
        {
            startingDateOfDisplay = startingValidityDate;
        }
        // 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 and will end to the (n) next sunday
        LocalDate endingDateOfDisplay = startingDateOfDisplay.toLocalDate( ).with( WeekFields.of( locale ).dayOfWeek( ), DayOfWeek.SUNDAY.getValue( ) )
                .plusWeeks( (long) _appointmentForm.getNbWeeksToDisplay( ) - 1 );
        return !( date.toLocalDate( ).isAfter( endingDateOfDisplay ) || date.isBefore( startingDateOfDisplay ) );

    }
}