SpecificWeekJspBean.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.sql.Date;
import java.time.LocalDate;
import java.time.LocalDateTime;
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.appointment.Appointment;
import fr.paris.lutece.plugins.appointment.business.display.Display;
import fr.paris.lutece.plugins.appointment.business.form.Form;
import fr.paris.lutece.plugins.appointment.business.planning.ClosingDay;
import fr.paris.lutece.plugins.appointment.business.planning.ClosingDayHome;
import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
import fr.paris.lutece.plugins.appointment.business.slot.Period;
import fr.paris.lutece.plugins.appointment.business.slot.Slot;
import fr.paris.lutece.plugins.appointment.log.LogUtilities;
import fr.paris.lutece.plugins.appointment.service.AppointmentResourceIdService;
import fr.paris.lutece.plugins.appointment.service.AppointmentService;
import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
import fr.paris.lutece.plugins.appointment.service.ClosingDayService;
import fr.paris.lutece.plugins.appointment.service.CommentService;
import fr.paris.lutece.plugins.appointment.service.DisplayService;
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.WeekDefinitionService;
import fr.paris.lutece.plugins.appointment.service.WorkingDayService;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
import fr.paris.lutece.portal.service.admin.AccessDeniedException;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.rbac.RBACService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.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;

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

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

    // Messages
    private static final String MESSAGE_SPECIFIC_WEEK_PAGE_TITLE = "appointment.specificWeek.pageTitle";
    private static final String MESSAGE_MODIFY_SLOT_PAGE_TITLE = "appointment.modifyCalendarSlots.pageTitle";
    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_INFO_SURBOOKING = "appointment.modifyCalendarSlots.messageSurbooking";
    private static final String MESSAGE_INFO_MULTI_SURBOOKING = "appointment.modifyCalendarMultiSlots.messageSurbooking";

    private static final String MESSAGE_INFO_OVERLOAD = "appointment.modifyCalendarSlots.messageOverload";
    private static final String MESSAGE_ERROR_PARSING_JSON = "appointment.message.error.parsing.json";

    // Parameters
    private static final String PARAMETER_ENDING_DATE_TO_APPLY = "ending_date_apply";
    private static final String PARAMETER_STARTING_DATE_TO_APPLY = "starting_date_apply";
    private static final String PARAMETER_ENDING_DATE_OF_DISPLAY = "ending_date_of_display";
    private static final String PARAMETER_DATE_OF_DISPLAY = "date_of_display";
    private static final String PARAMETER_ID_FORM = "id_form";
    private static final String PARAMETER_ID_SLOT = "id_slot";
    private static final String PARAMETER_STARTING_DATE_TIME = "starting_date_time";
    private static final String PARAMETER_ENDING_DATE_TIME = "ending_date_time";
    private static final String PARAMETER_EVENTS_COMMENTS = "comment_events";
    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_IS_SPECIFIC = "is_specific";
    private static final String PARAMETER_ENDING_TIME = "ending_time";
    private static final String PARAMETER_MAX_CAPACITY = "max_capacity";

    private static final String PARAMETER_SHIFT_SLOT = "shift_slot";
    private static final String PARAMETER_DATA = "slotsData";
    private static final String PARAMETER_IDENTICAL = "identical";

    // Marks
    private static final String MARK_SLOT = "slot";
    private static final String MARK_LOCALE_TINY = "locale";
    // Views
    private static final String VIEW_MANAGE_SPECIFIC_WEEK = "manageSpecificWeek";
    private static final String VIEW_MODIFY_SLOT = "viewModifySlot";

    // Actions
    private static final String ACTION_DO_MODIFY_SLOT = "doModifySlot";
    private static final String ACTION_DO_MODIFY_LIST_SLOT = "doModifyListSlot";

    // Templates
    private static final String TEMPLATE_MANAGE_SPECIFIC_WEEK = "admin/plugins/appointment/slots/manage_specific_week.html";
    private static final String TEMPLATE_MODIFY_SLOT = "admin/plugins/appointment/slots/modify_slot.html";

    // Porperties
    private static final String PROPERTY_NB_WEEKS_TO_DISPLAY_IN_BO = "appointment.nbWeeksToDisplayInBO";

    // Infos
    private AppointmentFormDTO _appointmentForm;
    private Slot _slot;

    /**
     * Get the view of the specific week
     * 
     * @param request
     *            the request
     * @return the page
     * @throws AccessDeniedException
     */
    @View( defaultView = true, value = VIEW_MANAGE_SPECIFIC_WEEK )
    public String getViewManageSpecificWeek( HttpServletRequest request ) throws AccessDeniedException
    {
        _slot = null;
        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 );
        }
        Form form = FormService.findFormLightByPrimaryKey( nIdForm );
        // Get the nb weeks to display
        Display display = DisplayService.findDisplayWithFormId( nIdForm );
        int nNbWeeksToDisplay = AppPropertiesService.getPropertyInt( PROPERTY_NB_WEEKS_TO_DISPLAY_IN_BO, display.getNbWeeksToDisplay( ) );
        if ( ( _appointmentForm == null ) || ( nIdForm != _appointmentForm.getIdForm( ) ) )
        {
            _appointmentForm = FormService.buildAppointmentForm( nIdForm, 0 );
        }
        LocalDate dateOfDisplay = LocalDate.now( );
        if ( _appointmentForm.getDateStartValidity( ) != null && _appointmentForm.getDateStartValidity( ).toLocalDate( ).isAfter( dateOfDisplay ) )
        {
            dateOfDisplay = _appointmentForm.getDateStartValidity( ).toLocalDate( );
        }
        LocalDate endingDateOfDisplay = LocalDate.now( ).plusWeeks( nNbWeeksToDisplay );
        LocalDate endingValidityDate = form.getEndingValidityDate( );
        if ( endingValidityDate != null && endingDateOfDisplay.isAfter( endingValidityDate ) )
        {
            endingDateOfDisplay = endingValidityDate;
        }
        // Get all the week definitions
        List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
        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> listDayOfWeek = new ArrayList<>( WeekDefinitionService.getSetDaysOfWeekOfAListOfWeekDefinitionForFullCalendar( listReservationRules ) );
        // Build the slots
        List<Slot> listSlot = SlotService.buildListSlot( nIdForm, mapReservationRule, dateOfDisplay, endingDateOfDisplay );
        listSlot = listSlot.stream( ).filter( s -> s.getEndingDateTime( ).isAfter( LocalDateTime.now( ) ) ).collect( Collectors.toList( ) );
        String strDateOfDisplay = request.getParameter( PARAMETER_DATE_OF_DISPLAY );
        if ( StringUtils.isNotEmpty( strDateOfDisplay ) )
        {
            dateOfDisplay = LocalDate.parse( strDateOfDisplay );
        }
        addInfo( MESSAGE_INFO_OVERLOAD, getLocale( ) );
        Map<String, Object> model = getModel( );
        model.put( PARAMETER_DATE_OF_DISPLAY, dateOfDisplay );
        model.put( PARAMETER_ENDING_DATE_OF_DISPLAY, endingDateOfDisplay );
        model.put( PARAMETER_DAY_OF_WEEK, listDayOfWeek );
        model.put( PARAMETER_EVENTS, listSlot );
        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( PARAMETER_ID_FORM, nIdForm );
        model.put( PARAMETER_EVENTS_COMMENTS, CommentService
                .buildCommentDTO( CommentService.finListComments( Date.valueOf( dateOfDisplay ), Date.valueOf( endingDateOfDisplay ), nIdForm ) ) );
        addElementsToModel( _appointmentForm, getUser( ), getLocale( ), model );
        model.put( MARK_LOCALE_TINY, getLocale( ) );
        return getPage( MESSAGE_SPECIFIC_WEEK_PAGE_TITLE, TEMPLATE_MANAGE_SPECIFIC_WEEK, model );
    }

    /**
     * Get the view to modify a slot
     * 
     * @param request
     *            the request
     * @return the page
     * @throws AccessDeniedException
     */
    @View( VIEW_MODIFY_SLOT )
    public String getViewModifySlot( 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 );
        }
        if ( _slot == null )
        {
            int nIdSlot = Integer.parseInt( request.getParameter( PARAMETER_ID_SLOT ) );
            // If nIdSlot == 0, the slot has not been created yet
            if ( nIdSlot == 0 )
            {
                // Need to get all the informations to create the slot
                LocalDateTime startingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_STARTING_DATE_TIME ) );
                LocalDateTime endingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_ENDING_DATE_TIME ) );
                boolean bIsOpen = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_OPEN ) );
                boolean bIsSpecific = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_SPECIFIC ) );
                int nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
                _slot = SlotService.buildSlot( nIdForm, new Period( startingDateTime, endingDateTime ), nMaxCapacity, nMaxCapacity, nMaxCapacity, 0, bIsOpen,
                        bIsSpecific );
            }
            else
            {
                _slot = SlotService.findSlotById( nIdSlot );
            }
        }
        Map<String, Object> model = getModel( );
        model.put( PARAMETER_DATE_OF_DISPLAY, _slot.getDate( ) );
        model.put( MARK_SLOT, _slot );
        model.put( PARAMETER_ID_FORM, strIdForm );
        return getPage( MESSAGE_MODIFY_SLOT_PAGE_TITLE, TEMPLATE_MODIFY_SLOT, model );
    }

    /**
     * Do modify a slot
     * 
     * @param request
     *            the request
     * @return to the page of the specific week
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_MODIFY_SLOT )
    public String doModifySlot( HttpServletRequest request ) throws AccessDeniedException
    {
        boolean bOpeningHasChanged = false;
        String strIdSlot = request.getParameter( PARAMETER_ID_SLOT );
        LocalTime endingTime = LocalTime.parse( request.getParameter( PARAMETER_ENDING_TIME ) );
        boolean bIsOpen = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_OPEN ) );
        int nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
        boolean bEndingTimeHasChanged = false;
        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        boolean bShiftSlot = Boolean.parseBoolean( request.getParameter( PARAMETER_SHIFT_SLOT ) );
        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 nIdSlot = Integer.parseInt( strIdSlot );
        Lock lock = SlotSafeService.getLockOnSlot( nIdSlot );
        lock.lock( );
        try
        {
            if ( nIdSlot != 0 )
            {
                _slot = SlotService.findSlotById( nIdSlot );
            }

            if ( bIsOpen != _slot.getIsOpen( ) )
            {
                _slot.setIsOpen( bIsOpen );
                bOpeningHasChanged = true;
            }

            // If we edit the slot, we need to check if this slot is not a closing
            // day
            ClosingDay closingDay = ClosingDayService.findClosingDayByIdFormAndDateOfClosingDay( _slot.getIdForm( ), _slot.getDate( ) );
            if ( closingDay != null )
            {
                // If the slot is a closing day, we need to remove it from the table
                // closing day so that the slot is not in conflict with the
                // definition of the closing days
                ClosingDayService.removeClosingDay( closingDay );
            }
            if ( nMaxCapacity != _slot.getMaxCapacity( ) )
            {
                _slot.setMaxCapacity( nMaxCapacity );
                // Need to set also the nb remaining places and the nb potential
                // remaining places
                // If the slot already exist, the good values will be set at the
                // update of the slot with taking the old values
                // If it is a new slot, the value set here will be good
                _slot.setNbRemainingPlaces( nMaxCapacity );
                _slot.setNbPotentialRemainingPlaces( nMaxCapacity );
            }
            LocalTime previousEndingTime = _slot.getEndingTime( );
            if ( !endingTime.equals( previousEndingTime ) )
            {
                _slot.setEndingTime( endingTime );
                _slot.setEndingDateTime( _slot.getDate( ).atTime( endingTime ) );
                bEndingTimeHasChanged = true;
            }
            if ( ( bEndingTimeHasChanged && !checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( _slot, bShiftSlot ) )
                    || ( bEndingTimeHasChanged && !checkEndingTimeOfSlot( endingTime, _slot ) ) )
            {
                return redirect( request, VIEW_MODIFY_SLOT, PARAMETER_ID_FORM, _slot.getIdForm( ) );
            }
            SlotSafeService.updateSlot( _slot, bEndingTimeHasChanged, previousEndingTime, bShiftSlot );

        }
        finally
        {

            lock.unlock( );
        }
        AppLogService.info( LogUtilities.buildLog( ACTION_DO_MODIFY_SLOT, strIdSlot, getUser( ) ) );
        addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
        boolean appointmentsImpacted = !AppointmentUtilities.checkNoValidatedAppointmentsOnThisSlot( _slot );
        if ( appointmentsImpacted && bOpeningHasChanged )
        {
            addInfo( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
        }
        if ( appointmentsImpacted && nMaxCapacity < _slot.getNbPlacesTaken( ) )
        {
            addInfo( MESSAGE_INFO_SURBOOKING, getLocale( ) );
        }

        Map<String, String> additionalParameters = new HashMap<>( );
        additionalParameters.put( PARAMETER_ID_FORM, Integer.toString( _slot.getIdForm( ) ) );
        additionalParameters.put( PARAMETER_DATE_OF_DISPLAY, _slot.getDate( ).toString( ) );
        return redirect( request, VIEW_MANAGE_SPECIFIC_WEEK, additionalParameters );
    }

    /**
     * Do modify a list of slot selected
     * 
     * @param request
     *            the request
     * @return to the page of the specific week
     * @throws AccessDeniedException
     */
    @Action( ACTION_DO_MODIFY_LIST_SLOT )
    public String doModifyListSlots( HttpServletRequest request ) throws AccessDeniedException
    {
        int nVarMaxCapacity = 0;
        int nMaxCapacity = -1;
        boolean bShiftSlot = false;
        LocalTime endingTime = null;

        String strIdForm = request.getParameter( PARAMETER_ID_FORM );
        String strShiftSlot = request.getParameter( PARAMETER_SHIFT_SLOT );
        String strEndingTime = request.getParameter( PARAMETER_ENDING_TIME );
        String strDateOfDisplay = request.getParameter( PARAMETER_DATE_OF_DISPLAY );
        String strApplyOnIdentical = request.getParameter( PARAMETER_IDENTICAL );

        boolean bStateHasChanged = false;
        boolean bIsOpen = false;
        String strIsOpen = request.getParameter( PARAMETER_IS_OPEN );
        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 ( strIsOpen == null || strIsOpen.equalsIgnoreCase( "true" ) || strIsOpen.equalsIgnoreCase( "false" ) )
        {

            bStateHasChanged = true;
            bIsOpen = Boolean.parseBoolean( strIsOpen );
        }

        String strCap = request.getParameter( PARAMETER_CAPACITY_MOD );

        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 ) );

            }

        if ( !StringUtils.isEmpty( strShiftSlot ) && !StringUtils.isEmpty( strEndingTime ) )
        {

            bShiftSlot = Boolean.parseBoolean( request.getParameter( PARAMETER_SHIFT_SLOT ) );
            endingTime = LocalTime.parse( strEndingTime );
        }

        String strJson = request.getParameter( PARAMETER_DATA );
        AppLogService.debug( "slot - Received strJson : " + strJson );
        ObjectMapper mapper = new ObjectMapper( );
        mapper.registerModule( new JavaTimeModule( ) );
        mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );

        List<Slot> listSlot = new ArrayList<>( );
        try
        {

            listSlot = mapper.readValue( strJson, new TypeReference<List<Slot>>( )
            {
            } );
            for ( Slot slt : listSlot )
            {

                if ( slt.getIdSlot( ) != 0 )
                {

                    Slot slot = SlotService.findSlotById( slt.getIdSlot( ) );
                    slt.setNbPlacestaken( slot.getNbPlacesTaken( ) );
                    slt.setNbRemainingPlaces( slot.getNbRemainingPlaces( ) );
                    slt.setNbPotentialRemainingPlaces( slot.getNbPotentialRemainingPlaces( ) );
                }
                else
                {

                    slt.setNbRemainingPlaces( slt.getMaxCapacity( ) );
                    slt.setNbPotentialRemainingPlaces( slt.getMaxCapacity( ) );
                }

            }
            if ( !StringUtils.isEmpty( strApplyOnIdentical ) && Boolean.parseBoolean( strApplyOnIdentical ) )
            {

                LocalDate startingDate = LocalDate.parse( request.getParameter( PARAMETER_STARTING_DATE_TO_APPLY ) );
                LocalDate endingDate = LocalDate.parse( request.getParameter( PARAMETER_ENDING_DATE_TO_APPLY ) );

                listSlot = buildListSlotsToUpdate( listSlot, Integer.parseInt( strIdForm ), startingDate, endingDate );
            }

        }
        catch( IOException e )
        {

            AppLogService.error( MESSAGE_ERROR_PARSING_JSON + e.getMessage( ), e );
            addError( MESSAGE_ERROR_PARSING_JSON, getLocale( ) );

        }

        if ( !AppointmentUtilities.checkListSlotIsBuildedCorrectly( Integer.parseInt( strIdForm ), listSlot ) )
        {

            addError( MESSAGE_ERROR_PARSING_JSON, getLocale( ) );

        }
        else
        {

            updateListSlots( listSlot, nVarMaxCapacity, nMaxCapacity, bIsOpen, bStateHasChanged, bShiftSlot, endingTime );
        }
        Map<String, String> additionalParameters = new HashMap<>( );
        additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
        additionalParameters.put( PARAMETER_DATE_OF_DISPLAY, strDateOfDisplay );
        return redirect( request, VIEW_MANAGE_SPECIFIC_WEEK, additionalParameters );
    }

    /**
     * Check the ending time of a slot
     * 
     * @param endingTime
     *            the new ending time
     * @param slot
     *            the slot
     * @return false if there is an error
     */
    private boolean checkEndingTimeOfSlot( LocalTime endingTime, Slot slot )
    {
        boolean bReturn = true;
        LocalDate dateOfSlot = slot.getDate( );
        ReservationRule resrvationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( slot.getIdForm( ), dateOfSlot );
        WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( resrvationRule.getListWorkingDay( ), dateOfSlot.getDayOfWeek( ) );
        LocalTime maxEndingTime = null;
        if ( workingDay == null )
        {
            maxEndingTime = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( resrvationRule.getListWorkingDay( ) );
        }
        else
        {
            maxEndingTime = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
        }
        if ( endingTime.isAfter( maxEndingTime ) )
        {
            bReturn = false;
            addError( MESSAGE_SLOT_CAN_NOT_END_AFTER_DAY_OR_FORM, getLocale( ) );
        }
        if ( endingTime.isBefore( slot.getStartingTime( ) ) || endingTime.equals( slot.getStartingTime( ) ) )
        {
            bReturn = false;
            addError( MESSAGE_ERROR_TIME_END_BEFORE_TIME_START, getLocale( ) );
        }
        return bReturn;
    }

    /**
     * Check that there is no appointment on a slot or on the impacted slots that will be modified
     * 
     * @param slot
     *            the slot
     * @param bShiftSLot
     *            true if the next slots will be modified
     * @return false if there is an error
     */
    private boolean checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( Slot slot, boolean bShiftSLot )
    {
        boolean bReturn = true;
        LocalDateTime endingDateTime = slot.getEndingDateTime( );
        // If all the slot will be shifted,
        // Need to check if there is no appointment until the end of the day
        if ( bShiftSLot )
        {
            endingDateTime = slot.getDate( ).atTime( LocalTime.MAX );
        }
        List<Slot> listSlotImpacted = SlotService.findSlotsByIdFormAndDateRange( slot.getIdForm( ), slot.getStartingDateTime( ), endingDateTime );
        List<Appointment> listAppointment = AppointmentService.findListAppointmentByListSlot( listSlotImpacted );
        if ( CollectionUtils.isNotEmpty( listAppointment ) )
        {
            bReturn = false;
        }
        return bReturn;
    }

    /**
     * Update a list of slot
     * 
     * @param listSlot
     *            the list of slot to update
     * @param nVarMaxCapacity
     *            the var capacity
     * @param nMaxCapacity
     *            the Max capacity
     * @param bIsOpen
     *            he new boolean opening value
     * @param bShiftSlot
     *            The shift
     * @param endingTime
     *            rhe Ending time
     */
    private void updateListSlots( List<Slot> listSlot, int nVarMaxCapacity, int nMaxCapacity, boolean bIsOpen, boolean bStateHasChanged, boolean bShiftSlot,
            LocalTime endingTime )
    {
        int nNewMaxCapacity = 0;
        boolean bOpeningHasChanged = false;
        boolean appointmentsImpacted = false;
        boolean bEndingTimeHasChanged = false;
        boolean bNoApptImpacted = true;
        LocalDate dateSlot = null;
        StringBuilder sbAlert = new StringBuilder( );

        for ( Slot slot : listSlot )
        {
            Lock lock = SlotSafeService.getLockOnSlot( slot.getIdSlot( ) );
            lock.lock( );
            try
            {
                if ( bStateHasChanged && bIsOpen != slot.getIsOpen( ) )
                {
                    slot.setIsOpen( bIsOpen );
                    bOpeningHasChanged = true;
                }
                if ( dateSlot == null || !dateSlot.isEqual( slot.getDate( ) ) )
                {
                    dateSlot = slot.getDate( );
                    // If we edit the slot, we need to check if this slot is not a closing day
                    // If the slot is a closing day, we need to remove it from the table
                    // closing day so that the slot is not in conflict with the
                    // definition of the closing days
                    ClosingDayHome.deleteByIdFormAndDateOfClosingDay( slot.getIdForm( ), dateSlot );
                }
                if ( nVarMaxCapacity != 0 || ( nMaxCapacity >= 0 && nMaxCapacity != slot.getMaxCapacity( ) ) )
                {
                    nNewMaxCapacity = ( nVarMaxCapacity != 0 ) ? ( slot.getMaxCapacity( ) + nVarMaxCapacity ) : nMaxCapacity;
                    if ( nNewMaxCapacity < 0 )
                    {
                        nNewMaxCapacity = 0;
                    }
                    slot.setMaxCapacity( nNewMaxCapacity );
                    // Need to set also the nb remaining places and the nb potential
                    // remaining places
                    // If the slot already exist, the good values will be set at the
                    // update of the slot with taking the old values
                    // If it is a new slot, the value set here will be good
                    slot.setNbRemainingPlaces( nNewMaxCapacity );
                    slot.setNbPotentialRemainingPlaces( nNewMaxCapacity );
                }
                LocalTime previousEndingTime = slot.getEndingTime( );
                if ( endingTime != null && !endingTime.equals( previousEndingTime ) )
                {
                    bNoApptImpacted = checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( slot, bShiftSlot );
                    slot.setEndingTime( endingTime );
                    slot.setEndingDateTime( slot.getDate( ).atTime( endingTime ) );
                    bEndingTimeHasChanged = true;
                }
                if ( ( bEndingTimeHasChanged && !bNoApptImpacted ) || ( bEndingTimeHasChanged && !checkEndingTimeOfSlot( endingTime, slot ) ) )
                {
                    addWarning( MESSAGE_ERROR_APPOINTMENT_ON_SLOT, getLocale( ) );

                }
                else
                {
                    SlotSafeService.updateSlot( slot, bEndingTimeHasChanged, previousEndingTime, bShiftSlot );
                    if ( !appointmentsImpacted && slot.getNbPlacesTaken( ) > 0 )
                    {
                        appointmentsImpacted = true;
                    }
                    AppLogService.info( LogUtilities.buildLog( ACTION_DO_MODIFY_SLOT, String.valueOf( slot.getIdSlot( ) ), getUser( ) ) );

                    if ( slot.getMaxCapacity( ) < slot.getNbPlacesTaken( ) )
                    {
                        sbAlert.append( slot.getStartingDateTime( ) );
                        sbAlert.append( "-" );
                        sbAlert.append( slot.getEndingDateTime( ) );
                        sbAlert.append( ", " );
                    }
                }
            }
            finally
            {
                lock.unlock( );
            }
        }

        if ( appointmentsImpacted && bOpeningHasChanged )
        {
            addError( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
        }
        else
            if ( CollectionUtils.isNotEmpty( listSlot ) )
            {
                boolean bNoSlotImpacted = true;
                for ( Slot slt : listSlot )
                {
                    if ( !checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( slt, bShiftSlot ) )
                        bNoSlotImpacted = false;
                }
                if ( bNoSlotImpacted )
                    addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
            }

        if ( !StringUtils.isEmpty( sbAlert.toString( ) ) )
        {
            Object [ ] args = {
                    sbAlert.toString( )
            };
            addWarning( I18nService.getLocalizedString( MESSAGE_INFO_MULTI_SURBOOKING, args, getLocale( ) ) );
        }
    }

    /**
     * Build list of slot
     * 
     * @param listSlotSelected
     *            the list of slot builded
     * @param nIdForm
     *            the id form
     * @param startingDate
     *            the starting date
     * @param endingDate
     *            the ending date
     * @return the list builded
     */
    private List<Slot> buildListSlotsToUpdate( List<Slot> listSlotSelected, int nIdForm, LocalDate startingDate, LocalDate endingDate )
    {
        List<Slot> listBuilded = new ArrayList<>( );
        listBuilded.addAll( listSlotSelected );
        HashMap<LocalDate, WeekDefinition> mapWeekDefinition = WeekDefinitionService.findAllWeekDefinition( nIdForm );
        List<Slot> listSlots = SlotService.buildListSlot( nIdForm, mapWeekDefinition, startingDate, endingDate );
        for ( Slot slot : listSlotSelected )
        {
            listBuilded
                    .addAll( listSlots.stream( )
                            .filter( slt -> slt.getStartingTime( ).equals( slot.getStartingTime( ) ) && slt.getEndingTime( ).equals( slot.getEndingTime( ) )
                                    && slt.getDate( ).getDayOfWeek( ).getValue( ) == slot.getDate( ).getDayOfWeek( ).getValue( ) )
                            .collect( Collectors.toList( ) ) );
        }
        return listBuilded;
    }
}