TypicalWeekJspBean.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.io.IOException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

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

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import fr.paris.lutece.api.user.User;
import fr.paris.lutece.plugins.appointment.business.planning.TimeSlot;
import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRuleHome;
import fr.paris.lutece.plugins.appointment.business.slot.Slot;
import fr.paris.lutece.plugins.appointment.business.slot.SlotHome;
import fr.paris.lutece.plugins.appointment.log.LogUtilities;
import fr.paris.lutece.plugins.appointment.service.AppointmentResourceIdService;
import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
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.TimeSlotService;
import fr.paris.lutece.plugins.appointment.service.WeekDefinitionService;
import fr.paris.lutece.plugins.appointment.service.WorkingDayService;
import fr.paris.lutece.plugins.appointment.service.listeners.WeekDefinitionManagerListener;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
import fr.paris.lutece.portal.service.message.AdminMessage;
import fr.paris.lutece.portal.service.message.AdminMessageService;
import fr.paris.lutece.portal.service.rbac.RBACService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.util.mvc.admin.annotations.Controller;
import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
import fr.paris.lutece.util.url.UrlItem;

/**
 * JspBean to manage calendar slots
 * 
 * @author Laurent Payen
 *
 */
@Controller( controllerJsp = TypicalWeekJspBean.JSP_MANAGE_APPOINTMENT_SLOTS, controllerPath = "jsp/admin/plugins/appointment/", right = AppointmentFormJspBean.RIGHT_MANAGEAPPOINTMENTFORM )
public class TypicalWeekJspBean extends AbstractAppointmentFormAndSlotJspBean
{
    /**
     * JSP of this JSP Bean
     */
    public static final String JSP_MANAGE_APPOINTMENT_SLOTS = "ManageAppointmentSlots.jsp";

    /**
     * Serial version UID
     */
    private static final long serialVersionUID = 2376721852596997810L;

    // Messages
    private static final String MESSAGE_TYPICAL_WEEK_PAGE_TITLE = "appointment.typicalWeek.pageTitle";
    private static final String MESSAGE_MODIFY_TIME_SLOT_PAGE_TITLE = "appointment.modifyCalendarSlots.pageTitle";
    private static final String MESSAGE_WARNING_CHANGES_APPLY_TO_ALL = "appointment.modifyCalendarSlots.warningModifiyingEndingTime";
    private static final String MESSAGE_ERROR_TIME_END_BEFORE_TIME_START = "appointment.modifyCalendarSlots.errorTimeEndBeforeTimeStart";
    private static final String MESSAGE_SLOT_CAN_NOT_END_AFTER_DAY_OR_FORM = "appointment.message.error.slotCanNotEndAfterDayOrForm";
    private static final String MESSAGE_ERROR_APPOINTMENT_ON_SLOT = "appointment.message.error.appointmentOnSlot";
    private static final String MESSAGE_INFO_SLOT_UPDATED = "appointment.modifyCalendarSlots.messageSlotUpdated";
    private static final String MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED = "appointment.modifyCalendarSlots.messageValidatedAppointmentsImpacted";

    private static final String MESSAGE_ERROR_MODIFY_FORM_HAS_APPOINTMENTS_AFTER_DATE_OF_MODIFICATION = "appointment.message.error.refreshDays.modifyFormHasAppointments";
    private static final String VALIDATION_ATTRIBUTES_PREFIX = "appointment.model.entity.appointmentform.attribute.";
    private static final String MESSAGE_CONFIRM_REMOVE_WEEK_DEFINITION = "appointment.message.confirmRemoveWeekDefinition";
    private static final String MESSAGE_ERROR_RULE_ASSIGNED = "appointment.message.error.rule.assigned";
    private static final String MESSAGE_ERROR_MODIFICATION_WEEK_ASSIGNED_IN_PAST = "appointment.message.error.week.assigned.past";
    private static final String MESSAGE_ERROR_PARSING_JSON = "appointment.message.error.parsing.json";

    // Parameters
    private static final String PARAMETER_ID_FORM = "id_form";
    private static final String PARAMETER_ID_TIME_SLOT = "id_time_slot";
    private static final String PARAMETER_DAY_OF_WEEK = "dow";
    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_IS_OPEN = "is_open";
    private static final String PARAMETER_ENDING_TIME = "ending_time";
    private static final String PARAMETER_MAX_CAPACITY = "max_capacity";
    private static final String PARAMETER_ID_RULE = "id_reservation_rule";
    private static final String PARAMETER_TIME_SLOT_DATA = "timeSlotData";
    private static final String PARAMETER_SHIFT_SLOT = "shift_slot";

    // Marks
    private static final String MARK_TIME_SLOT = "timeSlot";
    private static final String MARK_LIST_RESERVATION_RULE = "listReservationRule";
    private static final String MARK_ID_RULE = "id_reservation_rule";
    private static final String CAN_UPDATE_ADVANCED_PARAM = "canUpdateAdvancedParam";

    // Views
    private static final String VIEW_MANAGE_TYPICAL_WEEK = "manageTypicalWeek";
    private static final String VIEW_MODIFY_TIME_SLOT = "viewModifyTimeSlot";

    // Actions
    private static final String ACTION_DO_MODIFY_TIME_SLOT = "doModifyTimeSlot";
    private static final String ACTION_DO_MODIFY_LIST_TIME_SLOT = "doModifyListTimeSlot";
    private static final String ACTION_MODIFY_ADVANCED_PARAMETERS = "modifyAdvancedParameters";
    private static final String ACTION_MODIFY_GLOBAL_PARAM = "modifyGlobalParameters";
    private static final String ACTION_CONFIRM_REMOVE_PARAMETER = "confirmRemoveParameter";
    private static final String ACTION_REMOVE_PARAMETER = "doRemoveParameter";
    private static final String ACTION_CREATE_TYPICAL_WEEK = "createTypicalWeek";
    private static final String ACTION_DO_COPY_WEEK = "copyTypicalWeek";

    // Templates
    private static final String TEMPLATE_MANAGE_TYPICAL_WEEK = "admin/plugins/appointment/slots/manage_typical_week.html";
    private static final String TEMPLATE_MODIFY_TIME_SLOT = "admin/plugins/appointment/slots/modify_time_slot.html";

    // Porperties

    // Infos
    private static final String INFO_ADVANCED_PARAMETERS_UPDATED = "appointment.info.advancedparameters.updated";
    private static final String INFO_GLOBAL_PARAMETERS_UPDATED = "appointment.info.globalparameters.updated";

    private static final String INFO_PARAMETER_REMOVED = "appointment.info.advancedparameters.removed";

    private AppointmentFormDTO _appointmentForm;
    private TimeSlot _timeSlot;

    /**
     * Get the view of the typical week
     * 
     * @param request
     *            the request
     * @return the page
     * @throws AccessDeniedException
     */
    @View( value = VIEW_MANAGE_TYPICAL_WEEK )
    public String getViewManageTypicalWeek( HttpServletRequest request ) throws AccessDeniedException
    {
        _timeSlot = null;
        boolean bCanUpdateAdvancedParam = true;
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        int nIdForm = Integer.parseInt( strIdForm );
        String strIdReservationRule = request.getParameter( PARAMETER_ID_RULE );
        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        int nIdReservationRule = 0;
        if ( StringUtils.isNotEmpty( strIdReservationRule ) )
        {
            nIdReservationRule = Integer.parseInt( strIdReservationRule );
        }
        LocalDate dateOfApply = LocalDate.now( );
        ReservationRule reservationRule;
        if ( nIdReservationRule != 0 )
        {
            reservationRule = ReservationRuleService.findReservationRuleById( nIdReservationRule );
            LocalDate dateNow = LocalDate.now( );
            List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findByReservationRule( nIdReservationRule );
            if ( listWeekDefinition.stream( ).anyMatch( week -> week.getDateOfApply( ).isBefore( dateNow ) ) )
            {

                bCanUpdateAdvancedParam = false;
            }
        }
        else
        {
            reservationRule = new ReservationRule( );
        }

        Map<String, Object> model = getModel( );
        List<String> listDayOfWeek = new ArrayList<>( );
        List<TimeSlot> listTimeSlot = new ArrayList<>( );
        LocalTime minStartingTime = LocalTime.MIN;
        LocalTime maxEndingTime = LocalTime.MAX;

        if ( nIdReservationRule == 0
                && ( _appointmentForm == null || _appointmentForm.getIdReservationRule( ) != 0 || _appointmentForm.getIdForm( ) != nIdForm ) )
        {
            _appointmentForm = FormService.buildAppointmentFormWithoutReservationRule( nIdForm );

        }
        else
            if ( nIdReservationRule != 0 )
            {
                _appointmentForm = FormService.buildAppointmentForm( nIdForm, nIdReservationRule );
                List<WorkingDay> listWorkingDay = reservationRule.getListWorkingDay( );
                listDayOfWeek = new ArrayList<>( WorkingDayService.getSetDaysOfWeekOfAListOfWorkingDayForFullCalendar( listWorkingDay ) );
                listTimeSlot = TimeSlotService.getListTimeSlotOfAListOfWorkingDay( listWorkingDay, dateOfApply );
                minStartingTime = WorkingDayService.getMinStartingTimeOfAListOfWorkingDay( listWorkingDay );
                maxEndingTime = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( listWorkingDay );
            }

        model.put( CAN_UPDATE_ADVANCED_PARAM, bCanUpdateAdvancedParam );
        model.put( PARAMETER_DAY_OF_WEEK, listDayOfWeek );
        model.put( PARAMETER_EVENTS, listTimeSlot );
        model.put( PARAMETER_MIN_TIME, minStartingTime );
        model.put( PARAMETER_MAX_TIME, maxEndingTime );
        model.put( PARAMETER_MIN_DURATION, LocalTime.MIN.plusMinutes( AppointmentUtilities.THIRTY_MINUTES ) );
        model.put( MARK_ID_RULE, nIdReservationRule );
        model.put( MARK_LIST_RESERVATION_RULE, ReservationRuleService.findListReservationRule( nIdForm ) );
        addElementsToModel( _appointmentForm, getUser( ), getLocale( ), model );
        return getPage( MESSAGE_TYPICAL_WEEK_PAGE_TITLE, TEMPLATE_MANAGE_TYPICAL_WEEK, model );
    }

    /**
     * Create typical week
     * 
     * @param request
     *            the request
     * @return Html Page
     * @throws AccessDeniedException
     */
    @Action( ACTION_CREATE_TYPICAL_WEEK )
    public String doCreateTypicalWeek( HttpServletRequest request ) throws AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        int nIdForm = Integer.parseInt( strIdForm );
        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        populate( _appointmentForm, request );
        _appointmentForm.setCalendarTemplateId( 1 );
        if ( !validateBean( _appointmentForm, VALIDATION_ATTRIBUTES_PREFIX ) || !validateReservationRuleBean( request, VALIDATION_ATTRIBUTES_PREFIX )
                || !checkConstraints( _appointmentForm ) )
        {
            addError( PARAMETER_ERROR_MODIFICATION );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, _appointmentForm.getIdReservationRule( ) );
        }

        int nIdreservationRule = ReservationRuleService.createTypicalWeek( _appointmentForm );
        AppLogService.info( LogUtilities.buildLog( ACTION_MODIFY_ADVANCED_PARAMETERS, strIdForm, getUser( ) ) );
        addInfo( INFO_ADVANCED_PARAMETERS_UPDATED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdreservationRule );
    }

    /**
     * Modify typical week
     * 
     * @param request
     *            the request
     * @return the page
     * @throws AccessDeniedException
     */
    @Action( ACTION_MODIFY_ADVANCED_PARAMETERS )
    public String doModifyAdvancedParameters( HttpServletRequest request ) throws AccessDeniedException
    {

        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( _appointmentForm.getIdForm( ) ),
                AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM, (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        populate( _appointmentForm, request );
        List<Slot> listSlotsImpacted = new ArrayList<>( );
        List<Slot> listSlotsImpactedWithAppointment = new ArrayList<>( );

        LocalDate dateNow = LocalDate.now( );
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findByReservationRule( _appointmentForm.getIdReservationRule( ) );
        if ( listWeekDefinition.stream( ).anyMatch( week -> week.getDateOfApply( ).isBefore( dateNow ) ) )
        {

            return redirect( request, AdminMessageService.getMessageUrl( request, MESSAGE_ERROR_MODIFICATION_WEEK_ASSIGNED_IN_PAST, AdminMessage.TYPE_STOP ) );
        }
        if ( AppointmentUtilities.weekIsOpenInFO( _appointmentForm, listWeekDefinition, getLocale( ) ) )
        {

            addError( ERROR_MESSAGE_WEEK_IS_OPEN_FO, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                    _appointmentForm.getIdReservationRule( ) );

        }
        if ( !validateReservationRuleBean( _appointmentForm, VALIDATION_ATTRIBUTES_PREFIX ) || !validateBean( _appointmentForm, VALIDATION_ATTRIBUTES_PREFIX )
                || !checkConstraints( _appointmentForm ) )
        {
            addError( PARAMETER_ERROR_MODIFICATION );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                    _appointmentForm.getIdReservationRule( ) );
        }

        for ( WeekDefinition week : listWeekDefinition )
        {

            listSlotsImpacted.addAll( SlotService.findSlotsByIdFormAndDateRange( _appointmentForm.getIdForm( ), week.getDateOfApply( ).atStartOfDay( ),
                    week.getEndingDateOfApply( ).atTime( LocalTime.MAX ) ) );
            listSlotsImpactedWithAppointment.addAll( SlotService.findSlotWithAppointmentByDateRange( _appointmentForm.getIdForm( ),
                    week.getDateOfApply( ).atStartOfDay( ), week.getEndingDateOfApply( ).atTime( LocalTime.MAX ) ) );

        }

        // if there are slots impacted
        if ( CollectionUtils.isNotEmpty( listSlotsImpacted ) )
        {
            // if there are appointments impacted
            if ( CollectionUtils.isNotEmpty( listSlotsImpactedWithAppointment ) )
            {

                if ( !AppointmentUtilities.checkNoAppointmentsImpacted( listSlotsImpactedWithAppointment, _appointmentForm ) )
                {
                    addError( MESSAGE_ERROR_MODIFY_FORM_HAS_APPOINTMENTS_AFTER_DATE_OF_MODIFICATION, getLocale( ) );
                    return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                            _appointmentForm.getIdReservationRule( ) );
                }
                manageTheSlotsAndAppointmentsImpacted( listSlotsImpactedWithAppointment, listSlotsImpacted, Boolean.TRUE,
                        _appointmentForm.getMaxCapacityPerSlot( ), Boolean.FALSE, Boolean.FALSE );
            }
            else
            {
                // No check, delete all the slots
                SlotService.deleteListSlots( listSlotsImpacted );
            }
        }
        ReservationRuleService.updateAdvancedParameters( _appointmentForm );

        AppLogService.info( LogUtilities.buildLog( ACTION_MODIFY_ADVANCED_PARAMETERS, String.valueOf( _appointmentForm.getIdForm( ) ), getUser( ) ) );
        addInfo( INFO_ADVANCED_PARAMETERS_UPDATED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                _appointmentForm.getIdReservationRule( ) );
    }

    /**
     * Modify global param of thr typical week
     * 
     * @param request
     *            the request
     * @return the page
     * @throws AccessDeniedException
     */
    @Action( ACTION_MODIFY_GLOBAL_PARAM )
    public String doModifyGlobalParameters( HttpServletRequest request ) throws AccessDeniedException
    {
        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, String.valueOf( _appointmentForm.getIdForm( ) ),
                AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM, (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        ReservationRule reservationRule = new ReservationRule( );
        reservationRule.setIdReservationRule( _appointmentForm.getIdReservationRule( ) );
        populate( _appointmentForm, request );
        ReservationRuleService.fillInReservationRule( reservationRule, _appointmentForm, _appointmentForm.getIdForm( ) );

        if ( !validateReservationRuleBean( _appointmentForm, VALIDATION_ATTRIBUTES_PREFIX ) || !checkMultiSlotFormTypeBookablePlaces( _appointmentForm )
                || !checkSlotCapacityAndPeoplePerAppointment( _appointmentForm ) )
        {
            addError( PARAMETER_ERROR_MODIFICATION );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                    _appointmentForm.getIdReservationRule( ) );
        }

        ReservationRuleHome.update( reservationRule );
        addInfo( INFO_GLOBAL_PARAMETERS_UPDATED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, _appointmentForm.getIdForm( ), PARAMETER_ID_RULE,
                _appointmentForm.getIdReservationRule( ) );
    }

    /**
     * Manages the removal form of a appointment whose identifier is in the HTTP request
     * 
     * @param request
     *            The HTTP request
     * @return the HTML code to confirm
     */
    @Action( ACTION_CONFIRM_REMOVE_PARAMETER )
    public String getConfirmRemoveParameter( HttpServletRequest request )
    {
        UrlItem url = new UrlItem( getActionUrl( ACTION_REMOVE_PARAMETER ) );
        url.addParameter( PARAMETER_ID_RULE, request.getParameter( PARAMETER_ID_RULE ) );
        url.addParameter( PARAMETER_ID_FORM, request.getParameter( PARAMETER_ID_FORM ) );

        String strMessageUrl = AdminMessageService.getMessageUrl( request, MESSAGE_CONFIRM_REMOVE_WEEK_DEFINITION, url.getUrl( ),
                AdminMessage.TYPE_CONFIRMATION );
        return redirect( request, strMessageUrl );
    }

    /**
     * Handles the removal form of a week rul
     * 
     * @param request
     *            The HTTP request
     * @throws AccessDeniedException
     *             If the user is not authorized to access this feature
     */
    @Action( ACTION_REMOVE_PARAMETER )
    public String doRemoveParameter( HttpServletRequest request ) throws AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        int nIdReservationRule = Integer.parseInt( request.getParameter( PARAMETER_ID_RULE ) );
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findByReservationRule( nIdReservationRule );
        if ( CollectionUtils.isNotEmpty( listWeekDefinition ) )
        {

            return redirect( request, AdminMessageService.getMessageUrl( request, MESSAGE_ERROR_RULE_ASSIGNED, AdminMessage.TYPE_STOP ) );
        }

        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        if ( AppointmentUtilities.weekIsOpenInFO( _appointmentForm, listWeekDefinition, getLocale( ) ) )
        {

            return redirect( request, AdminMessageService.getMessageUrl( request, ERROR_MESSAGE_WEEK_IS_OPEN_FO, AdminMessage.TYPE_STOP ) );

        }
        ReservationRuleService.removeReservationRule( nIdReservationRule );
        addInfo( INFO_PARAMETER_REMOVED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, Integer.parseInt( strIdForm ) );
    }

    /**
     * Copy typical week
     * 
     * @param request
     *            the request
     * @return Html Page
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_COPY_WEEK )
    public String doCopyWeek( HttpServletRequest request ) throws AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        int nIdForm = Integer.parseInt( strIdForm );
        int nIdReservationRule = Integer.parseInt( request.getParameter( PARAMETER_ID_RULE ) );

        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        ReservationRuleService.copyReservationRule( nIdReservationRule );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );

    }

    /**
     * Get the view to modify a time slot
     * 
     * @param request
     *            the request
     * @return the page
     */
    @View( VIEW_MODIFY_TIME_SLOT )
    public String getViewModifyTimeSlot( HttpServletRequest request )
    {
        int nIdTimeSlot = Integer.parseInt( request.getParameter( PARAMETER_ID_TIME_SLOT ) );
        if ( ( _timeSlot == null ) || ( nIdTimeSlot != _timeSlot.getIdTimeSlot( ) ) )
        {
            _timeSlot = TimeSlotService.findTimeSlotById( nIdTimeSlot );
        }
        addInfo( MESSAGE_WARNING_CHANGES_APPLY_TO_ALL, getLocale( ) );
        Map<String, Object> model = getModel( );
        model.put( PARAMETER_ID_FORM, request.getParameter( PARAMETER_ID_FORM ) );
        model.put( MARK_ID_RULE, request.getParameter( PARAMETER_ID_RULE ) );
        model.put( MARK_TIME_SLOT, _timeSlot );
        return getPage( MESSAGE_MODIFY_TIME_SLOT_PAGE_TITLE, TEMPLATE_MODIFY_TIME_SLOT, model );
    }

    /**
     * Do modify a time slot
     * 
     * @param request
     *            the request
     * @return to the page of the typical week
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_MODIFY_TIME_SLOT )
    public String doModifyTimeSlot( HttpServletRequest request ) throws AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        int nIdForm = Integer.parseInt( strIdForm );
        String strIdReservationRule = request.getParameter( PARAMETER_ID_RULE );
        int nIdReservationRule = Integer.parseInt( strIdReservationRule );
        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        String strIdTimeSlot = request.getParameter( PARAMETER_ID_TIME_SLOT );
        int nIdTimeSlot = Integer.parseInt( strIdTimeSlot );
        TimeSlot oldTimeSlot = TimeSlotService.findTimeSlotById( nIdTimeSlot );

        if ( _timeSlot == null || nIdTimeSlot != _timeSlot.getIdTimeSlot( ) )
        {
            _timeSlot = oldTimeSlot;
        }
        boolean bIsOpen = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_OPEN ) );
        boolean bOpeningHasChanged = false;
        int nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
        LocalTime endingTime = LocalTime.parse( request.getParameter( PARAMETER_ENDING_TIME ) );
        boolean bShiftSlot = Boolean.parseBoolean( request.getParameter( PARAMETER_SHIFT_SLOT ) );
        boolean bEndingTimeHasChanged = false;
        boolean bMaxCapacityHasChanged = false;
        LocalDate dateNow = LocalDate.now( );
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findByReservationRule( nIdReservationRule );

        if ( listWeekDefinition.stream( ).anyMatch( week -> week.getDateOfApply( ).isBefore( dateNow ) ) )
        {

            addError( MESSAGE_ERROR_MODIFICATION_WEEK_ASSIGNED_IN_PAST, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );
        }
        if ( AppointmentUtilities.weekIsOpenInFO( _appointmentForm, listWeekDefinition, getLocale( ) ) )
        {

            addError( ERROR_MESSAGE_WEEK_IS_OPEN_FO, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );

        }
        if ( bIsOpen != _timeSlot.getIsOpen( ) )
        {
            _timeSlot.setIsOpen( bIsOpen );
            bOpeningHasChanged = true;
        }
        if ( nMaxCapacity != oldTimeSlot.getMaxCapacity( ) )
        {
            _timeSlot.setMaxCapacity( nMaxCapacity );
            bMaxCapacityHasChanged = true;
        }
        LocalTime previousEndingTime = oldTimeSlot.getEndingTime( );
        if ( !endingTime.equals( previousEndingTime ) )
        {
            _timeSlot.setEndingTime( endingTime );
            if ( !checkEndingTimeOfTimeSlot( endingTime, _timeSlot ) )
            {
                Map<String, String> additionalParameters = new HashMap<>( );
                additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
                additionalParameters.put( PARAMETER_ID_RULE, strIdReservationRule );
                return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, additionalParameters );
            }
            bEndingTimeHasChanged = true;
        }
        List<Slot> listSlotsImpacted = new ArrayList<>( );
        List<Slot> listSlotsImpactedByDate = new ArrayList<>( );

        for ( WeekDefinition week : listWeekDefinition )
        {

            listSlotsImpacted.addAll( AppointmentUtilities.findSlotsImpactedByThisTimeSlot( _timeSlot, nIdForm, week.getIdWeekDefinition( ), bShiftSlot ) );
            listSlotsImpactedByDate.addAll( SlotService.findSlotWithAppointmentByDateRange( nIdForm, week.getDateOfApply( ).atStartOfDay( ),
                    week.getEndingDateOfApply( ).atTime( LocalTime.MAX ) ) );

        }

        // If there are slots impacted
        if ( CollectionUtils.isNotEmpty( listSlotsImpacted ) )
        {
            List<Integer> listIdSlotsImpacted = listSlotsImpacted.stream( ).map( Slot::getIdSlot ).collect( Collectors.toList( ) );
            List<Slot> listSlotsImpactedWithAppointment = listSlotsImpactedByDate.stream( ).filter( slot -> listIdSlotsImpacted.contains( slot.getIdSlot( ) ) )
                    .collect( Collectors.toList( ) );
            // if there are appointments impacted
            if ( CollectionUtils.isNotEmpty( listSlotsImpactedWithAppointment ) )
            {
                // If the ending time of the time slot has changed or if the max
                // capacity has decreased
                if ( bEndingTimeHasChanged || nMaxCapacity < oldTimeSlot.getMaxCapacity( ) )
                {
                    // Error, the time slot can't be changed
                    addError( MESSAGE_ERROR_APPOINTMENT_ON_SLOT, getLocale( ) );
                    addError( listSlotsImpactedWithAppointment.size( ) + " slot impacté(s)" );
                    Map<String, String> additionalParameters = new HashMap<>( );
                    additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
                    additionalParameters.put( PARAMETER_ID_RULE, strIdReservationRule );
                    return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, additionalParameters );
                }
                // Get the slot whith appointment (the appointments that are not
                // cancelled)
                List<Slot> listSlotsWithAppointmentNotCancelled = listSlotsImpactedWithAppointment.stream( ).filter( slot -> slot.getNbPlacesTaken( ) > 0 )
                        .collect( Collectors.toList( ) );
                if ( bOpeningHasChanged && CollectionUtils.isNotEmpty( listSlotsWithAppointmentNotCancelled ) )
                {
                    addInfo( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
                }
                manageTheSlotsAndAppointmentsImpacted( listSlotsImpactedWithAppointment, listSlotsImpacted, bMaxCapacityHasChanged, nMaxCapacity,
                        bOpeningHasChanged, bIsOpen );
            }
            else
            {
                // no need to check appointments, delete all the slots
                SlotService.deleteListSlots( listSlotsImpacted );
            }
        }
        TimeSlotService.updateTimeSlot( _timeSlot, bEndingTimeHasChanged, previousEndingTime, bShiftSlot );

        AppLogService.info( LogUtilities.buildLog( ACTION_DO_MODIFY_TIME_SLOT, strIdTimeSlot, getUser( ) ) );
        addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );
    }

    /**
     * Do modify a time slot
     * 
     * @param request
     *            the request
     * @return to the page of the typical week
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_MODIFY_LIST_TIME_SLOT )
    public String doModifyListTimeSlot( HttpServletRequest request ) throws AccessDeniedException
    {
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        String strCap = request.getParameter( PARAMETER_CAPACITY_MOD );
        int nIdForm = Integer.parseInt( strIdForm );
        String strIdReservationRule = request.getParameter( PARAMETER_ID_RULE );
        int nIdReservationRule = Integer.parseInt( strIdReservationRule );
        int nVarMaxCapacity = 0;
        int nMaxCapacity = -1;
        if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
                (User) getUser( ) ) )
        {
            throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
        }
        String strJson = request.getParameter( PARAMETER_TIME_SLOT_DATA );
        AppLogService.debug( "slot - Received strJson : " + strJson );
        ObjectMapper mapper = new ObjectMapper( );
        mapper.registerModule( new JavaTimeModule( ) );
        mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );

        List<TimeSlot> listTimeSlot = new ArrayList<>( );
        List<TimeSlot> listTimeSlotJson = new ArrayList<>( );

        try
        {

            listTimeSlotJson = mapper.readValue( strJson, new TypeReference<List<TimeSlot>>( )
            {
            } );

        }
        catch( IOException e )
        {

            AppLogService.error( MESSAGE_ERROR_PARSING_JSON + e.getMessage( ), e );
            addError( MESSAGE_ERROR_PARSING_JSON, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );

        }
        boolean bStateHasChanged = false;
        boolean bIsOpen = false;
        String strIsOpen = request.getParameter( PARAMETER_IS_OPEN );

        if ( strIsOpen.equalsIgnoreCase( "true" ) || strIsOpen.equalsIgnoreCase( "false" ) )
        {

            bStateHasChanged = true;
            bIsOpen = Boolean.parseBoolean( strIsOpen );
        }
        boolean bMaxCapacityIsLower = false;
        LocalDate dateNow = LocalDate.now( );

        if ( strCap.equals( VAR_CAP ) )
        {

            nVarMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );

        }
        else
            if ( strCap.equals( NEW_CAP ) )
            {

                nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );

            }

        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findByReservationRule( nIdReservationRule );

        if ( listWeekDefinition.stream( ).anyMatch( week -> week.getDateOfApply( ).isBefore( dateNow ) ) )
        {

            addError( MESSAGE_ERROR_MODIFICATION_WEEK_ASSIGNED_IN_PAST, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );
        }
        if ( AppointmentUtilities.weekIsOpenInFO( _appointmentForm, listWeekDefinition, getLocale( ) ) )
        {

            addError( ERROR_MESSAGE_WEEK_IS_OPEN_FO, getLocale( ) );
            return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );

        }
        for ( TimeSlot time : listTimeSlotJson )
        {

            TimeSlot timeSlot = TimeSlotService.findTimeSlotById( time.getIdTimeSlot( ) );
            if ( bStateHasChanged && bIsOpen != timeSlot.getIsOpen( ) )
            {
                timeSlot.setIsOpen( bIsOpen );
            }
            if ( nMaxCapacity >= 0 && nMaxCapacity != timeSlot.getMaxCapacity( ) )
            {
                timeSlot.setMaxCapacity( nMaxCapacity );
                if ( nMaxCapacity < timeSlot.getMaxCapacity( ) )
                {

                    bMaxCapacityIsLower = true;
                }
            }
            else
            {
                if ( timeSlot.getMaxCapacity( ) + nVarMaxCapacity > 0 )
                {

                    timeSlot.setMaxCapacity( timeSlot.getMaxCapacity( ) + nVarMaxCapacity );

                }
                else
                {

                    timeSlot.setMaxCapacity( 0 );
                }
                if ( nVarMaxCapacity < 0 )
                {

                    bMaxCapacityIsLower = true;
                }
            }
            listTimeSlot.add( timeSlot );
        }

        List<Slot> listSlotsImpacted = new ArrayList<>( );
        List<Slot> listSlotsImpactedByDate = new ArrayList<>( );

        for ( WeekDefinition week : listWeekDefinition )
        {
            for ( TimeSlot timeSlot : listTimeSlot )
            {
                listSlotsImpacted.addAll( AppointmentUtilities.findSlotsImpactedByThisTimeSlot( timeSlot, nIdForm, week.getIdWeekDefinition( ), false ) );
            }
            listSlotsImpactedByDate.addAll( SlotService.findSlotWithAppointmentByDateRange( nIdForm, week.getDateOfApply( ).atStartOfDay( ),
                    week.getEndingDateOfApply( ).atTime( LocalTime.MAX ) ) );
        }

        // If there are slots impacted
        if ( CollectionUtils.isNotEmpty( listSlotsImpacted ) )
        {
            List<Integer> listIdSlotsImpacted = listSlotsImpacted.stream( ).map( Slot::getIdSlot ).collect( Collectors.toList( ) );
            List<Slot> listSlotsImpactedWithAppointment = listSlotsImpactedByDate.stream( ).filter( slot -> listIdSlotsImpacted.contains( slot.getIdSlot( ) ) )
                    .collect( Collectors.toList( ) );

            // if there are appointments impacted
            if ( CollectionUtils.isNotEmpty( listSlotsImpactedWithAppointment ) )
            {
                // if the max capacity has decreased
                if ( bMaxCapacityIsLower )
                {
                    // Error, the time slot can't be changed
                    addError( MESSAGE_ERROR_APPOINTMENT_ON_SLOT, getLocale( ) );
                    addError( listSlotsImpactedWithAppointment.size( ) + " slot impacté(s)" );
                    Map<String, String> additionalParameters = new HashMap<>( );
                    additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
                    additionalParameters.put( PARAMETER_ID_RULE, strIdReservationRule );
                    return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, additionalParameters );
                }

                manageTheSlotsAndAppointmentsImpacted( listSlotsImpactedWithAppointment, listSlotsImpacted, nMaxCapacity, nVarMaxCapacity, bIsOpen,
                        bStateHasChanged );
            }
            else
            {
                // no need to check appointments, delete all the slots
                SlotService.deleteListSlots( listSlotsImpacted );
            }
        }

        TimeSlotService.updateListTimeSlot( listTimeSlot );
        if ( CollectionUtils.isNotEmpty( listTimeSlot ) && CollectionUtils.isNotEmpty( listWeekDefinition ) )
        {

            WeekDefinitionManagerListener.notifyListenersListWeekDefinitionChanged( nIdForm, listWeekDefinition );
        }

        addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
        return redirect( request, VIEW_MANAGE_TYPICAL_WEEK, PARAMETER_ID_FORM, nIdForm, PARAMETER_ID_RULE, nIdReservationRule );
    }

    /**
     * Check the ending time of a time slot
     * 
     * @param endingTime
     *            the new ending time
     * @param timeSlot
     *            the time slot
     * @return false if there is an error
     */
    private boolean checkEndingTimeOfTimeSlot( LocalTime endingTime, TimeSlot timeSlot )
    {
        boolean bReturn = true;
        WorkingDay workingDay = WorkingDayService.findWorkingDayById( timeSlot.getIdWorkingDay( ) );
        if ( endingTime.isAfter( WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay ) ) )
        {
            bReturn = false;
            addError( MESSAGE_SLOT_CAN_NOT_END_AFTER_DAY_OR_FORM, getLocale( ) );
        }
        if ( endingTime.isBefore( timeSlot.getStartingTime( ) ) || endingTime.equals( timeSlot.getStartingTime( ) ) )
        {
            bReturn = false;
            addError( MESSAGE_ERROR_TIME_END_BEFORE_TIME_START, getLocale( ) );
        }
        return bReturn;
    }

    /**
     * Update the slots with appointments impacted by a modification of a typical week or a modification of a timeSlot Delete the slots with no appointments
     * 
     * @param listAppointmentsImpacted
     *            the appointments impacted
     * @param listSlotsImpacted
     *            the slots impacted
     * @param bMaxCapacityHasChanged
     *            True if the capacity has changed
     * @param nMaxCapacity
     *            the max capacity
     * @param bOpeningHasChanged
     *            true if the opening has changed
     * @param bIsOpen
     *            the new boolean opening value
     */
    private void manageTheSlotsAndAppointmentsImpacted( List<Slot> listSlotsImpactedWithAppointments, List<Slot> listSlotsImpacted,
            boolean bMaxCapacityHasChanged, int nMaxCapacity, boolean bOpeningHasChanged, boolean bIsOpen )
    {
        // Need to delete the slots that are impacted but with no
        // appointments
        List<Integer> listIdSlotsImpactedWithAppointments = listSlotsImpactedWithAppointments.stream( ).map( Slot::getIdSlot ).collect( Collectors.toList( ) );
        List<Slot> listslotImpactedWithoutAppointments = listSlotsImpacted.stream( )
                .filter( p -> !listIdSlotsImpactedWithAppointments.contains( p.getIdSlot( ) ) ).collect( Collectors.toList( ) );

        SlotService.deleteListSlots( listslotImpactedWithoutAppointments );
        for ( Slot slotImpacted : listSlotsImpactedWithAppointments )
        {
            Lock lock = SlotSafeService.getLockOnSlot( slotImpacted.getIdSlot( ) );
            lock.lock( );
            try
            {
                slotImpacted = updateRemainingPlaces( slotImpacted, bMaxCapacityHasChanged, nMaxCapacity, bOpeningHasChanged, bIsOpen );
                SlotSafeService.updateSlot( slotImpacted );
            }
            finally
            {
                lock.unlock( );
            }
        }
    }

    /**
     * Update the slots with appointments impacted by a modification of a typical week or a modification of a timeSlot Delete the slots with no appointments
     * 
     * @param listAppointmentsImpacted
     *            the appointments impacted
     * @param listSlotsImpacted
     *            the slots impacted
     * @param nMaxCapacity
     *            the max capacity
     * @param bIsOpen
     *            the new boolean opening value
     */
    private void manageTheSlotsAndAppointmentsImpacted( List<Slot> listSlotsImpactedWithAppointments, List<Slot> listSlotsImpacted, int nMaxCapacity,
            int nVarMaxCapacity, boolean bIsOpen, boolean bStateHasChanged )
    {
        boolean bOpeningHasChanged = false;
        boolean binfoOpeningHasChanged = false;
        boolean bMaxCapacityHasChanged = false;
        // Need to delete the slots that are impacted but with no
        // appointments
        List<Integer> listIdSlotsImpactedWithAppointments = listSlotsImpactedWithAppointments.stream( ).map( Slot::getIdSlot ).collect( Collectors.toList( ) );
        List<Slot> listslotImpactedWithoutAppointments = listSlotsImpacted.stream( )
                .filter( p -> !listIdSlotsImpactedWithAppointments.contains( p.getIdSlot( ) ) ).collect( Collectors.toList( ) );

        SlotService.deleteListSlots( listslotImpactedWithoutAppointments );
        for ( Slot slotImpacted : listSlotsImpactedWithAppointments )
        {
            bOpeningHasChanged = false;
            bMaxCapacityHasChanged = false;

            Lock lock = SlotSafeService.getLockOnSlot( slotImpacted.getIdSlot( ) );
            lock.lock( );
            try
            {
                if ( ( nMaxCapacity != -1 && slotImpacted.getMaxCapacity( ) != nMaxCapacity ) )
                {

                    bMaxCapacityHasChanged = true;
                }
                else
                    if ( nVarMaxCapacity != 0 )
                    {

                        nMaxCapacity = ( slotImpacted.getMaxCapacity( ) + nVarMaxCapacity ) >= 0 ? ( slotImpacted.getMaxCapacity( ) + nVarMaxCapacity ) : 0;
                        bMaxCapacityHasChanged = true;

                    }
                if ( bStateHasChanged && slotImpacted.getIsOpen( ) != bIsOpen )
                {

                    bOpeningHasChanged = true;
                    binfoOpeningHasChanged = true;
                }
                slotImpacted = updateRemainingPlaces( slotImpacted, bMaxCapacityHasChanged, nMaxCapacity, bOpeningHasChanged, bIsOpen );
                SlotSafeService.updateSlot( slotImpacted );
            }
            finally
            {
                lock.unlock( );
            }
        }
        // Get the slot whith appointment (the appointments that are not
        // cancelled)
        List<Slot> listSlotsWithAppointmentNotCancelled = listSlotsImpactedWithAppointments.stream( ).filter( slot -> slot.getNbPlacesTaken( ) > 0 )
                .collect( Collectors.toList( ) );
        if ( binfoOpeningHasChanged && CollectionUtils.isNotEmpty( listSlotsWithAppointmentNotCancelled ) )
        {
            addInfo( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
        }
    }

    /**
     * Update the capacity of the slot
     * 
     * @param slot
     *            the slot to update
     * @param bMaxCapacityHasChanged
     *            True if the capacity has changed
     * @param nMaxCapacity
     *            the max capacity
     * @param bOpeningHasChanged
     *            true if the opening has changed
     * @param bIsOpen
     *            the new boolean opening value Return the slot updated
     */
    private static Slot updateRemainingPlaces( Slot slot, boolean bMaxCapacityHasChanged, int nNewNbMaxCapacity, boolean bOpeningHasChanged, boolean bIsOpen )
    {
        slot = SlotHome.findByPrimaryKey( slot.getIdSlot( ) );
        // If the max capacity has been modified
        if ( bMaxCapacityHasChanged )
        {
            int nOldBnMaxCapacity = slot.getMaxCapacity( );
            nNewNbMaxCapacity = ( nNewNbMaxCapacity >= 0 ) ? nNewNbMaxCapacity : 0;
            // Need to add the diff between the old value and the new value
            // to the remaining places (if the new is higher)
            if ( nNewNbMaxCapacity > nOldBnMaxCapacity )
            {
                int nValueToAdd = nNewNbMaxCapacity - nOldBnMaxCapacity;
                slot.setNbPotentialRemainingPlaces( slot.getNbPotentialRemainingPlaces( ) + nValueToAdd );
                slot.setNbRemainingPlaces( slot.getNbRemainingPlaces( ) + nValueToAdd );
            }
            else
            {
                // the new value is lower than the previous capacity
                // !!!! If there are appointments on this slot and if the
                // slot is already full, the slot will be surbooked !!!!
                int nValueToSubstract = nOldBnMaxCapacity - nNewNbMaxCapacity;
                slot.setNbPotentialRemainingPlaces( slot.getNbPotentialRemainingPlaces( ) - nValueToSubstract );
                slot.setNbRemainingPlaces( slot.getNbRemainingPlaces( ) - nValueToSubstract );
            }
            slot.setMaxCapacity( nNewNbMaxCapacity );
        }
        if ( bOpeningHasChanged )
        {
            slot.setIsOpen( bIsOpen );
        }
        slot.setIsSpecific( false );

        return slot;
    }
}