View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.appointment.web;
35  
36  import java.io.IOException;
37  import java.sql.Date;
38  import java.time.LocalDate;
39  import java.time.LocalDateTime;
40  import java.time.LocalTime;
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.concurrent.locks.Lock;
46  import java.util.stream.Collectors;
47  
48  import javax.servlet.http.HttpServletRequest;
49  
50  import org.apache.commons.collections.CollectionUtils;
51  import org.apache.commons.lang3.StringUtils;
52  
53  import com.fasterxml.jackson.core.type.TypeReference;
54  import com.fasterxml.jackson.databind.DeserializationFeature;
55  import com.fasterxml.jackson.databind.ObjectMapper;
56  import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
57  
58  import fr.paris.lutece.api.user.User;
59  import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
60  import fr.paris.lutece.plugins.appointment.business.display.Display;
61  import fr.paris.lutece.plugins.appointment.business.form.Form;
62  import fr.paris.lutece.plugins.appointment.business.planning.ClosingDay;
63  import fr.paris.lutece.plugins.appointment.business.planning.ClosingDayHome;
64  import fr.paris.lutece.plugins.appointment.business.planning.WeekDefinition;
65  import fr.paris.lutece.plugins.appointment.business.planning.WorkingDay;
66  import fr.paris.lutece.plugins.appointment.business.rule.ReservationRule;
67  import fr.paris.lutece.plugins.appointment.business.slot.Period;
68  import fr.paris.lutece.plugins.appointment.business.slot.Slot;
69  import fr.paris.lutece.plugins.appointment.log.LogUtilities;
70  import fr.paris.lutece.plugins.appointment.service.AppointmentResourceIdService;
71  import fr.paris.lutece.plugins.appointment.service.AppointmentService;
72  import fr.paris.lutece.plugins.appointment.service.AppointmentUtilities;
73  import fr.paris.lutece.plugins.appointment.service.ClosingDayService;
74  import fr.paris.lutece.plugins.appointment.service.CommentService;
75  import fr.paris.lutece.plugins.appointment.service.DisplayService;
76  import fr.paris.lutece.plugins.appointment.service.FormService;
77  import fr.paris.lutece.plugins.appointment.service.ReservationRuleService;
78  import fr.paris.lutece.plugins.appointment.service.SlotSafeService;
79  import fr.paris.lutece.plugins.appointment.service.SlotService;
80  import fr.paris.lutece.plugins.appointment.service.WeekDefinitionService;
81  import fr.paris.lutece.plugins.appointment.service.WorkingDayService;
82  import fr.paris.lutece.plugins.appointment.web.dto.AppointmentFormDTO;
83  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
84  import fr.paris.lutece.portal.service.i18n.I18nService;
85  import fr.paris.lutece.portal.service.rbac.RBACService;
86  import fr.paris.lutece.portal.service.util.AppLogService;
87  import fr.paris.lutece.portal.service.util.AppPropertiesService;
88  import fr.paris.lutece.portal.util.mvc.admin.annotations.Controller;
89  import fr.paris.lutece.portal.util.mvc.commons.annotations.Action;
90  import fr.paris.lutece.portal.util.mvc.commons.annotations.View;
91  
92  /**
93   * JspBean to manage calendar slots
94   * 
95   * @author Laurent Payen
96   *
97   */
98  @Controller( controllerJsp = SpecificWeekJspBean.JSP_MANAGE_APPOINTMENT_SLOTS, controllerPath = "jsp/admin/plugins/appointment/", right = AppointmentFormJspBean.RIGHT_MANAGEAPPOINTMENTFORM )
99  public class SpecificWeekJspBean extends AbstractAppointmentFormAndSlotJspBean
100 {
101     /**
102      * JSP of this JSP Bean
103      */
104     public static final String JSP_MANAGE_APPOINTMENT_SLOTS = "ManageSpecificWeek.jsp";
105 
106     /**
107      * Serial version UID
108      */
109     private static final long serialVersionUID = 2376721852596997810L;
110 
111     // Messages
112     private static final String MESSAGE_SPECIFIC_WEEK_PAGE_TITLE = "appointment.specificWeek.pageTitle";
113     private static final String MESSAGE_MODIFY_SLOT_PAGE_TITLE = "appointment.modifyCalendarSlots.pageTitle";
114     private static final String MESSAGE_ERROR_TIME_END_BEFORE_TIME_START = "appointment.modifyCalendarSlots.errorTimeEndBeforeTimeStart";
115     private static final String MESSAGE_SLOT_CAN_NOT_END_AFTER_DAY_OR_FORM = "appointment.message.error.slotCanNotEndAfterDayOrForm";
116     private static final String MESSAGE_ERROR_APPOINTMENT_ON_SLOT = "appointment.message.error.appointmentOnSlot";
117     private static final String MESSAGE_INFO_SLOT_UPDATED = "appointment.modifyCalendarSlots.messageSlotUpdated";
118     private static final String MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED = "appointment.modifyCalendarSlots.messageValidatedAppointmentsImpacted";
119     private static final String MESSAGE_INFO_SURBOOKING = "appointment.modifyCalendarSlots.messageSurbooking";
120     private static final String MESSAGE_INFO_MULTI_SURBOOKING = "appointment.modifyCalendarMultiSlots.messageSurbooking";
121 
122     private static final String MESSAGE_INFO_OVERLOAD = "appointment.modifyCalendarSlots.messageOverload";
123     private static final String MESSAGE_ERROR_PARSING_JSON = "appointment.message.error.parsing.json";
124 
125     // Parameters
126     private static final String PARAMETER_ENDING_DATE_TO_APPLY = "ending_date_apply";
127     private static final String PARAMETER_STARTING_DATE_TO_APPLY = "starting_date_apply";
128     private static final String PARAMETER_ENDING_DATE_OF_DISPLAY = "ending_date_of_display";
129     private static final String PARAMETER_DATE_OF_DISPLAY = "date_of_display";
130     private static final String PARAMETER_ID_FORM = "id_form";
131     private static final String PARAMETER_ID_SLOT = "id_slot";
132     private static final String PARAMETER_STARTING_DATE_TIME = "starting_date_time";
133     private static final String PARAMETER_ENDING_DATE_TIME = "ending_date_time";
134     private static final String PARAMETER_EVENTS_COMMENTS = "comment_events";
135     private static final String PARAMETER_DAY_OF_WEEK = "dow";
136     private static final String PARAMETER_EVENTS = "events";
137     private static final String PARAMETER_MIN_DURATION = "min_duration";
138     private static final String PARAMETER_MIN_TIME = "min_time";
139     private static final String PARAMETER_MAX_TIME = "max_time";
140     private static final String PARAMETER_IS_OPEN = "is_open";
141     private static final String PARAMETER_IS_SPECIFIC = "is_specific";
142     private static final String PARAMETER_ENDING_TIME = "ending_time";
143     private static final String PARAMETER_MAX_CAPACITY = "max_capacity";
144 
145     private static final String PARAMETER_SHIFT_SLOT = "shift_slot";
146     private static final String PARAMETER_DATA = "slotsData";
147     private static final String PARAMETER_IDENTICAL = "identical";
148 
149     // Marks
150     private static final String MARK_SLOT = "slot";
151     private static final String MARK_LOCALE_TINY = "locale";
152     // Views
153     private static final String VIEW_MANAGE_SPECIFIC_WEEK = "manageSpecificWeek";
154     private static final String VIEW_MODIFY_SLOT = "viewModifySlot";
155 
156     // Actions
157     private static final String ACTION_DO_MODIFY_SLOT = "doModifySlot";
158     private static final String ACTION_DO_MODIFY_LIST_SLOT = "doModifyListSlot";
159 
160     // Templates
161     private static final String TEMPLATE_MANAGE_SPECIFIC_WEEK = "admin/plugins/appointment/slots/manage_specific_week.html";
162     private static final String TEMPLATE_MODIFY_SLOT = "admin/plugins/appointment/slots/modify_slot.html";
163 
164     // Porperties
165     private static final String PROPERTY_NB_WEEKS_TO_DISPLAY_IN_BO = "appointment.nbWeeksToDisplayInBO";
166 
167     // Infos
168     private AppointmentFormDTO _appointmentForm;
169     private Slot _slot;
170 
171     /**
172      * Get the view of the specific week
173      * 
174      * @param request
175      *            the request
176      * @return the page
177      * @throws AccessDeniedException
178      */
179     @View( defaultView = true, value = VIEW_MANAGE_SPECIFIC_WEEK )
180     public String getViewManageSpecificWeek( HttpServletRequest request ) throws AccessDeniedException
181     {
182         _slot = null;
183         String strIdForm = request.getParameter( PARAMETER_ID_FORM );
184         int nIdForm = Integer.parseInt( strIdForm );
185         if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
186                 (User) getUser( ) ) )
187         {
188             throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
189         }
190         Form form = FormService.findFormLightByPrimaryKey( nIdForm );
191         // Get the nb weeks to display
192         Display display = DisplayService.findDisplayWithFormId( nIdForm );
193         int nNbWeeksToDisplay = AppPropertiesService.getPropertyInt( PROPERTY_NB_WEEKS_TO_DISPLAY_IN_BO, display.getNbWeeksToDisplay( ) );
194         if ( ( _appointmentForm == null ) || ( nIdForm != _appointmentForm.getIdForm( ) ) )
195         {
196             _appointmentForm = FormService.buildAppointmentForm( nIdForm, 0 );
197         }
198         LocalDate dateOfDisplay = LocalDate.now( );
199         if ( _appointmentForm.getDateStartValidity( ) != null && _appointmentForm.getDateStartValidity( ).toLocalDate( ).isAfter( dateOfDisplay ) )
200         {
201             dateOfDisplay = _appointmentForm.getDateStartValidity( ).toLocalDate( );
202         }
203         LocalDate endingDateOfDisplay = LocalDate.now( ).plusWeeks( nNbWeeksToDisplay );
204         LocalDate endingValidityDate = form.getEndingValidityDate( );
205         if ( endingValidityDate != null && endingDateOfDisplay.isAfter( endingValidityDate ) )
206         {
207             endingDateOfDisplay = endingValidityDate;
208         }
209         // Get all the week definitions
210         List<WeekDefinition> listWeekDefinition = WeekDefinitionService.findListWeekDefinition( nIdForm );
211         Map<WeekDefinition, ReservationRule> mapReservationRule = ReservationRuleService.findAllReservationRule( nIdForm, listWeekDefinition );
212         List<ReservationRule> listReservationRules = new ArrayList<>( mapReservationRule.values( ) );
213 
214         // Get the min time of all the week definitions
215         LocalTime minStartingTime = WeekDefinitionService.getMinStartingTimeOfAListOfWeekDefinition( listReservationRules );
216         // Get the max time of all the week definitions
217         LocalTime maxEndingTime = WeekDefinitionService.getMaxEndingTimeOfAListOfWeekDefinition( listReservationRules );
218         // Get all the working days of all the week definitions
219         List<String> listDayOfWeek = new ArrayList<>( WeekDefinitionService.getSetDaysOfWeekOfAListOfWeekDefinitionForFullCalendar( listReservationRules ) );
220         // Build the slots
221         List<Slot> listSlot = SlotService.buildListSlot( nIdForm, mapReservationRule, dateOfDisplay, endingDateOfDisplay );
222         listSlot = listSlot.stream( ).filter( s -> s.getEndingDateTime( ).isAfter( LocalDateTime.now( ) ) ).collect( Collectors.toList( ) );
223         String strDateOfDisplay = request.getParameter( PARAMETER_DATE_OF_DISPLAY );
224         if ( StringUtils.isNotEmpty( strDateOfDisplay ) )
225         {
226             dateOfDisplay = LocalDate.parse( strDateOfDisplay );
227         }
228         addInfo( MESSAGE_INFO_OVERLOAD, getLocale( ) );
229         Map<String, Object> model = getModel( );
230         model.put( PARAMETER_DATE_OF_DISPLAY, dateOfDisplay );
231         model.put( PARAMETER_ENDING_DATE_OF_DISPLAY, endingDateOfDisplay );
232         model.put( PARAMETER_DAY_OF_WEEK, listDayOfWeek );
233         model.put( PARAMETER_EVENTS, listSlot );
234         model.put( PARAMETER_MIN_TIME, minStartingTime );
235         model.put( PARAMETER_MAX_TIME, maxEndingTime );
236         model.put( PARAMETER_MIN_DURATION, LocalTime.MIN.plusMinutes( AppointmentUtilities.THIRTY_MINUTES ) );
237         model.put( PARAMETER_ID_FORM, nIdForm );
238         model.put( PARAMETER_EVENTS_COMMENTS, CommentService
239                 .buildCommentDTO( CommentService.finListComments( Date.valueOf( dateOfDisplay ), Date.valueOf( endingDateOfDisplay ), nIdForm ) ) );
240         addElementsToModel( _appointmentForm, getUser( ), getLocale( ), model );
241         model.put( MARK_LOCALE_TINY, getLocale( ) );
242         return getPage( MESSAGE_SPECIFIC_WEEK_PAGE_TITLE, TEMPLATE_MANAGE_SPECIFIC_WEEK, model );
243     }
244 
245     /**
246      * Get the view to modify a slot
247      * 
248      * @param request
249      *            the request
250      * @return the page
251      * @throws AccessDeniedException
252      */
253     @View( VIEW_MODIFY_SLOT )
254     public String getViewModifySlot( HttpServletRequest request ) throws AccessDeniedException
255     {
256         String strIdForm = request.getParameter( PARAMETER_ID_FORM );
257         int nIdForm = Integer.parseInt( strIdForm );
258         if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
259                 (User) getUser( ) ) )
260         {
261             throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
262         }
263         if ( _slot == null )
264         {
265             int nIdSlot = Integer.parseInt( request.getParameter( PARAMETER_ID_SLOT ) );
266             // If nIdSlot == 0, the slot has not been created yet
267             if ( nIdSlot == 0 )
268             {
269                 // Need to get all the informations to create the slot
270                 LocalDateTime startingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_STARTING_DATE_TIME ) );
271                 LocalDateTime endingDateTime = LocalDateTime.parse( request.getParameter( PARAMETER_ENDING_DATE_TIME ) );
272                 boolean bIsOpen = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_OPEN ) );
273                 boolean bIsSpecific = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_SPECIFIC ) );
274                 int nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
275                 _slot = SlotService.buildSlot( nIdForm, new Period( startingDateTime, endingDateTime ), nMaxCapacity, nMaxCapacity, nMaxCapacity, 0, bIsOpen,
276                         bIsSpecific );
277             }
278             else
279             {
280                 _slot = SlotService.findSlotById( nIdSlot );
281             }
282         }
283         Map<String, Object> model = getModel( );
284         model.put( PARAMETER_DATE_OF_DISPLAY, _slot.getDate( ) );
285         model.put( MARK_SLOT, _slot );
286         model.put( PARAMETER_ID_FORM, strIdForm );
287         return getPage( MESSAGE_MODIFY_SLOT_PAGE_TITLE, TEMPLATE_MODIFY_SLOT, model );
288     }
289 
290     /**
291      * Do modify a slot
292      * 
293      * @param request
294      *            the request
295      * @return to the page of the specific week
296      * @throws AccessDeniedException
297      */
298     @Action( ACTION_DO_MODIFY_SLOT )
299     public String doModifySlot( HttpServletRequest request ) throws AccessDeniedException
300     {
301         boolean bOpeningHasChanged = false;
302         String strIdSlot = request.getParameter( PARAMETER_ID_SLOT );
303         LocalTime endingTime = LocalTime.parse( request.getParameter( PARAMETER_ENDING_TIME ) );
304         boolean bIsOpen = Boolean.parseBoolean( request.getParameter( PARAMETER_IS_OPEN ) );
305         int nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
306         boolean bEndingTimeHasChanged = false;
307         String strIdForm = request.getParameter( PARAMETER_ID_FORM );
308         boolean bShiftSlot = Boolean.parseBoolean( request.getParameter( PARAMETER_SHIFT_SLOT ) );
309         if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
310                 (User) getUser( ) ) )
311         {
312             throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
313         }
314         int nIdSlot = Integer.parseInt( strIdSlot );
315         Lock lock = SlotSafeService.getLockOnSlot( nIdSlot );
316         lock.lock( );
317         try
318         {
319             if ( nIdSlot != 0 )
320             {
321                 _slot = SlotService.findSlotById( nIdSlot );
322             }
323 
324             if ( bIsOpen != _slot.getIsOpen( ) )
325             {
326                 _slot.setIsOpen( bIsOpen );
327                 bOpeningHasChanged = true;
328             }
329 
330             // If we edit the slot, we need to check if this slot is not a closing
331             // day
332             ClosingDay closingDay = ClosingDayService.findClosingDayByIdFormAndDateOfClosingDay( _slot.getIdForm( ), _slot.getDate( ) );
333             if ( closingDay != null )
334             {
335                 // If the slot is a closing day, we need to remove it from the table
336                 // closing day so that the slot is not in conflict with the
337                 // definition of the closing days
338                 ClosingDayService.removeClosingDay( closingDay );
339             }
340             if ( nMaxCapacity != _slot.getMaxCapacity( ) )
341             {
342                 _slot.setMaxCapacity( nMaxCapacity );
343                 // Need to set also the nb remaining places and the nb potential
344                 // remaining places
345                 // If the slot already exist, the good values will be set at the
346                 // update of the slot with taking the old values
347                 // If it is a new slot, the value set here will be good
348                 _slot.setNbRemainingPlaces( nMaxCapacity );
349                 _slot.setNbPotentialRemainingPlaces( nMaxCapacity );
350             }
351             LocalTime previousEndingTime = _slot.getEndingTime( );
352             if ( !endingTime.equals( previousEndingTime ) )
353             {
354                 _slot.setEndingTime( endingTime );
355                 _slot.setEndingDateTime( _slot.getDate( ).atTime( endingTime ) );
356                 bEndingTimeHasChanged = true;
357             }
358             if ( ( bEndingTimeHasChanged && !checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( _slot, bShiftSlot ) )
359                     || ( bEndingTimeHasChanged && !checkEndingTimeOfSlot( endingTime, _slot ) ) )
360             {
361                 return redirect( request, VIEW_MODIFY_SLOT, PARAMETER_ID_FORM, _slot.getIdForm( ) );
362             }
363             SlotSafeService.updateSlot( _slot, bEndingTimeHasChanged, previousEndingTime, bShiftSlot );
364 
365         }
366         finally
367         {
368 
369             lock.unlock( );
370         }
371         AppLogService.info( LogUtilities.buildLog( ACTION_DO_MODIFY_SLOT, strIdSlot, getUser( ) ) );
372         addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
373         boolean appointmentsImpacted = !AppointmentUtilities.checkNoValidatedAppointmentsOnThisSlot( _slot );
374         if ( appointmentsImpacted && bOpeningHasChanged )
375         {
376             addInfo( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
377         }
378         if ( appointmentsImpacted && nMaxCapacity < _slot.getNbPlacesTaken( ) )
379         {
380             addInfo( MESSAGE_INFO_SURBOOKING, getLocale( ) );
381         }
382 
383         Map<String, String> additionalParameters = new HashMap<>( );
384         additionalParameters.put( PARAMETER_ID_FORM, Integer.toString( _slot.getIdForm( ) ) );
385         additionalParameters.put( PARAMETER_DATE_OF_DISPLAY, _slot.getDate( ).toString( ) );
386         return redirect( request, VIEW_MANAGE_SPECIFIC_WEEK, additionalParameters );
387     }
388 
389     /**
390      * Do modify a list of slot selected
391      * 
392      * @param request
393      *            the request
394      * @return to the page of the specific week
395      * @throws AccessDeniedException
396      */
397     @Action( ACTION_DO_MODIFY_LIST_SLOT )
398     public String doModifyListSlots( HttpServletRequest request ) throws AccessDeniedException
399     {
400         int nVarMaxCapacity = 0;
401         int nMaxCapacity = -1;
402         boolean bShiftSlot = false;
403         LocalTime endingTime = null;
404 
405         String strIdForm = request.getParameter( PARAMETER_ID_FORM );
406         String strShiftSlot = request.getParameter( PARAMETER_SHIFT_SLOT );
407         String strEndingTime = request.getParameter( PARAMETER_ENDING_TIME );
408         String strDateOfDisplay = request.getParameter( PARAMETER_DATE_OF_DISPLAY );
409         String strApplyOnIdentical = request.getParameter( PARAMETER_IDENTICAL );
410 
411         boolean bStateHasChanged = false;
412         boolean bIsOpen = false;
413         String strIsOpen = request.getParameter( PARAMETER_IS_OPEN );
414         if ( !RBACService.isAuthorized( AppointmentFormDTO.RESOURCE_TYPE, strIdForm, AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM,
415                 (User) getUser( ) ) )
416         {
417             throw new AccessDeniedException( AppointmentResourceIdService.PERMISSION_MODIFY_ADVANCED_SETTING_FORM );
418         }
419         if ( strIsOpen == null || strIsOpen.equalsIgnoreCase( "true" ) || strIsOpen.equalsIgnoreCase( "false" ) )
420         {
421 
422             bStateHasChanged = true;
423             bIsOpen = Boolean.parseBoolean( strIsOpen );
424         }
425 
426         String strCap = request.getParameter( PARAMETER_CAPACITY_MOD );
427 
428         if ( strCap.equals( VAR_CAP ) )
429         {
430 
431             nVarMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
432 
433         }
434         else
435             if ( strCap.equals( NEW_CAP ) )
436             {
437 
438                 nMaxCapacity = Integer.parseInt( request.getParameter( PARAMETER_MAX_CAPACITY ) );
439 
440             }
441 
442         if ( !StringUtils.isEmpty( strShiftSlot ) && !StringUtils.isEmpty( strEndingTime ) )
443         {
444 
445             bShiftSlot = Boolean.parseBoolean( request.getParameter( PARAMETER_SHIFT_SLOT ) );
446             endingTime = LocalTime.parse( strEndingTime );
447         }
448 
449         String strJson = request.getParameter( PARAMETER_DATA );
450         AppLogService.debug( "slot - Received strJson : " + strJson );
451         ObjectMapper mapper = new ObjectMapper( );
452         mapper.registerModule( new JavaTimeModule( ) );
453         mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
454 
455         List<Slot> listSlot = new ArrayList<>( );
456         try
457         {
458 
459             listSlot = mapper.readValue( strJson, new TypeReference<List<Slot>>( )
460             {
461             } );
462             for ( Slot slt : listSlot )
463             {
464 
465                 if ( slt.getIdSlot( ) != 0 )
466                 {
467 
468                     Slot slot = SlotService.findSlotById( slt.getIdSlot( ) );
469                     slt.setNbPlacestaken( slot.getNbPlacesTaken( ) );
470                     slt.setNbRemainingPlaces( slot.getNbRemainingPlaces( ) );
471                     slt.setNbPotentialRemainingPlaces( slot.getNbPotentialRemainingPlaces( ) );
472                 }
473                 else
474                 {
475 
476                     slt.setNbRemainingPlaces( slt.getMaxCapacity( ) );
477                     slt.setNbPotentialRemainingPlaces( slt.getMaxCapacity( ) );
478                 }
479 
480             }
481             if ( !StringUtils.isEmpty( strApplyOnIdentical ) && Boolean.parseBoolean( strApplyOnIdentical ) )
482             {
483 
484                 LocalDate startingDate = LocalDate.parse( request.getParameter( PARAMETER_STARTING_DATE_TO_APPLY ) );
485                 LocalDate endingDate = LocalDate.parse( request.getParameter( PARAMETER_ENDING_DATE_TO_APPLY ) );
486 
487                 listSlot = buildListSlotsToUpdate( listSlot, Integer.parseInt( strIdForm ), startingDate, endingDate );
488             }
489 
490         }
491         catch( IOException e )
492         {
493 
494             AppLogService.error( MESSAGE_ERROR_PARSING_JSON + e.getMessage( ), e );
495             addError( MESSAGE_ERROR_PARSING_JSON, getLocale( ) );
496 
497         }
498 
499         if ( !AppointmentUtilities.checkListSlotIsBuildedCorrectly( Integer.parseInt( strIdForm ), listSlot ) )
500         {
501 
502             addError( MESSAGE_ERROR_PARSING_JSON, getLocale( ) );
503 
504         }
505         else
506         {
507 
508             updateListSlots( listSlot, nVarMaxCapacity, nMaxCapacity, bIsOpen, bStateHasChanged, bShiftSlot, endingTime );
509         }
510         Map<String, String> additionalParameters = new HashMap<>( );
511         additionalParameters.put( PARAMETER_ID_FORM, strIdForm );
512         additionalParameters.put( PARAMETER_DATE_OF_DISPLAY, strDateOfDisplay );
513         return redirect( request, VIEW_MANAGE_SPECIFIC_WEEK, additionalParameters );
514     }
515 
516     /**
517      * Check the ending time of a slot
518      * 
519      * @param endingTime
520      *            the new ending time
521      * @param slot
522      *            the slot
523      * @return false if there is an error
524      */
525     private boolean checkEndingTimeOfSlot( LocalTime endingTime, Slot slot )
526     {
527         boolean bReturn = true;
528         LocalDate dateOfSlot = slot.getDate( );
529         ReservationRule resrvationRule = ReservationRuleService.findReservationRuleByIdFormAndClosestToDateOfApply( slot.getIdForm( ), dateOfSlot );
530         WorkingDay workingDay = WorkingDayService.getWorkingDayOfDayOfWeek( resrvationRule.getListWorkingDay( ), dateOfSlot.getDayOfWeek( ) );
531         LocalTime maxEndingTime = null;
532         if ( workingDay == null )
533         {
534             maxEndingTime = WorkingDayService.getMaxEndingTimeOfAListOfWorkingDay( resrvationRule.getListWorkingDay( ) );
535         }
536         else
537         {
538             maxEndingTime = WorkingDayService.getMaxEndingTimeOfAWorkingDay( workingDay );
539         }
540         if ( endingTime.isAfter( maxEndingTime ) )
541         {
542             bReturn = false;
543             addError( MESSAGE_SLOT_CAN_NOT_END_AFTER_DAY_OR_FORM, getLocale( ) );
544         }
545         if ( endingTime.isBefore( slot.getStartingTime( ) ) || endingTime.equals( slot.getStartingTime( ) ) )
546         {
547             bReturn = false;
548             addError( MESSAGE_ERROR_TIME_END_BEFORE_TIME_START, getLocale( ) );
549         }
550         return bReturn;
551     }
552 
553     /**
554      * Check that there is no appointment on a slot or on the impacted slots that will be modified
555      * 
556      * @param slot
557      *            the slot
558      * @param bShiftSLot
559      *            true if the next slots will be modified
560      * @return false if there is an error
561      */
562     private boolean checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( Slot slot, boolean bShiftSLot )
563     {
564         boolean bReturn = true;
565         LocalDateTime endingDateTime = slot.getEndingDateTime( );
566         // If all the slot will be shifted,
567         // Need to check if there is no appointment until the end of the day
568         if ( bShiftSLot )
569         {
570             endingDateTime = slot.getDate( ).atTime( LocalTime.MAX );
571         }
572         List<Slot> listSlotImpacted = SlotService.findSlotsByIdFormAndDateRange( slot.getIdForm( ), slot.getStartingDateTime( ), endingDateTime );
573         List<Appointment> listAppointment = AppointmentService.findListAppointmentByListSlot( listSlotImpacted );
574         if ( CollectionUtils.isNotEmpty( listAppointment ) )
575         {
576             bReturn = false;
577         }
578         return bReturn;
579     }
580 
581     /**
582      * Update a list of slot
583      * 
584      * @param listSlot
585      *            the list of slot to update
586      * @param nVarMaxCapacity
587      *            the var capacity
588      * @param nMaxCapacity
589      *            the Max capacity
590      * @param bIsOpen
591      *            he new boolean opening value
592      * @param bShiftSlot
593      *            The shift
594      * @param endingTime
595      *            rhe Ending time
596      */
597     private void updateListSlots( List<Slot> listSlot, int nVarMaxCapacity, int nMaxCapacity, boolean bIsOpen, boolean bStateHasChanged, boolean bShiftSlot,
598             LocalTime endingTime )
599     {
600         int nNewMaxCapacity = 0;
601         boolean bOpeningHasChanged = false;
602         boolean appointmentsImpacted = false;
603         boolean bEndingTimeHasChanged = false;
604         boolean bNoApptImpacted = true;
605         LocalDate dateSlot = null;
606         StringBuilder sbAlert = new StringBuilder( );
607 
608         for ( Slot slot : listSlot )
609         {
610             Lock lock = SlotSafeService.getLockOnSlot( slot.getIdSlot( ) );
611             lock.lock( );
612             try
613             {
614                 if ( bStateHasChanged && bIsOpen != slot.getIsOpen( ) )
615                 {
616                     slot.setIsOpen( bIsOpen );
617                     bOpeningHasChanged = true;
618                 }
619                 if ( dateSlot == null || !dateSlot.isEqual( slot.getDate( ) ) )
620                 {
621                     dateSlot = slot.getDate( );
622                     // If we edit the slot, we need to check if this slot is not a closing day
623                     // If the slot is a closing day, we need to remove it from the table
624                     // closing day so that the slot is not in conflict with the
625                     // definition of the closing days
626                     ClosingDayHome.deleteByIdFormAndDateOfClosingDay( slot.getIdForm( ), dateSlot );
627                 }
628                 if ( nVarMaxCapacity != 0 || ( nMaxCapacity >= 0 && nMaxCapacity != slot.getMaxCapacity( ) ) )
629                 {
630                     nNewMaxCapacity = ( nVarMaxCapacity != 0 ) ? ( slot.getMaxCapacity( ) + nVarMaxCapacity ) : nMaxCapacity;
631                     if ( nNewMaxCapacity < 0 )
632                     {
633                         nNewMaxCapacity = 0;
634                     }
635                     slot.setMaxCapacity( nNewMaxCapacity );
636                     // Need to set also the nb remaining places and the nb potential
637                     // remaining places
638                     // If the slot already exist, the good values will be set at the
639                     // update of the slot with taking the old values
640                     // If it is a new slot, the value set here will be good
641                     slot.setNbRemainingPlaces( nNewMaxCapacity );
642                     slot.setNbPotentialRemainingPlaces( nNewMaxCapacity );
643                 }
644                 LocalTime previousEndingTime = slot.getEndingTime( );
645                 if ( endingTime != null && !endingTime.equals( previousEndingTime ) )
646                 {
647                     bNoApptImpacted = checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( slot, bShiftSlot );
648                     slot.setEndingTime( endingTime );
649                     slot.setEndingDateTime( slot.getDate( ).atTime( endingTime ) );
650                     bEndingTimeHasChanged = true;
651                 }
652                 if ( ( bEndingTimeHasChanged && !bNoApptImpacted ) || ( bEndingTimeHasChanged && !checkEndingTimeOfSlot( endingTime, slot ) ) )
653                 {
654                     addWarning( MESSAGE_ERROR_APPOINTMENT_ON_SLOT, getLocale( ) );
655 
656                 }
657                 else
658                 {
659                     SlotSafeService.updateSlot( slot, bEndingTimeHasChanged, previousEndingTime, bShiftSlot );
660                     if ( !appointmentsImpacted && slot.getNbPlacesTaken( ) > 0 )
661                     {
662                         appointmentsImpacted = true;
663                     }
664                     AppLogService.info( LogUtilities.buildLog( ACTION_DO_MODIFY_SLOT, String.valueOf( slot.getIdSlot( ) ), getUser( ) ) );
665 
666                     if ( slot.getMaxCapacity( ) < slot.getNbPlacesTaken( ) )
667                     {
668                         sbAlert.append( slot.getStartingDateTime( ) );
669                         sbAlert.append( "-" );
670                         sbAlert.append( slot.getEndingDateTime( ) );
671                         sbAlert.append( ", " );
672                     }
673                 }
674             }
675             finally
676             {
677                 lock.unlock( );
678             }
679         }
680 
681         if ( appointmentsImpacted && bOpeningHasChanged )
682         {
683             addError( MESSAGE_INFO_VALIDATED_APPOINTMENTS_IMPACTED, getLocale( ) );
684         }
685         else
686             if ( CollectionUtils.isNotEmpty( listSlot ) )
687             {
688                 boolean bNoSlotImpacted = true;
689                 for ( Slot slt : listSlot )
690                 {
691                     if ( !checkNoAppointmentsOnThisSlotOrOnTheSlotsImpacted( slt, bShiftSlot ) )
692                         bNoSlotImpacted = false;
693                 }
694                 if ( bNoSlotImpacted )
695                     addInfo( MESSAGE_INFO_SLOT_UPDATED, getLocale( ) );
696             }
697 
698         if ( !StringUtils.isEmpty( sbAlert.toString( ) ) )
699         {
700             Object [ ] args = {
701                     sbAlert.toString( )
702             };
703             addWarning( I18nService.getLocalizedString( MESSAGE_INFO_MULTI_SURBOOKING, args, getLocale( ) ) );
704         }
705     }
706 
707     /**
708      * Build list of slot
709      * 
710      * @param listSlotSelected
711      *            the list of slot builded
712      * @param nIdForm
713      *            the id form
714      * @param startingDate
715      *            the starting date
716      * @param endingDate
717      *            the ending date
718      * @return the list builded
719      */
720     private List<Slot> buildListSlotsToUpdate( List<Slot> listSlotSelected, int nIdForm, LocalDate startingDate, LocalDate endingDate )
721     {
722         List<Slot> listBuilded = new ArrayList<>( );
723         listBuilded.addAll( listSlotSelected );
724         HashMap<LocalDate, WeekDefinition> mapWeekDefinition = WeekDefinitionService.findAllWeekDefinition( nIdForm );
725         List<Slot> listSlots = SlotService.buildListSlot( nIdForm, mapWeekDefinition, startingDate, endingDate );
726         for ( Slot slot : listSlotSelected )
727         {
728             listBuilded
729                     .addAll( listSlots.stream( )
730                             .filter( slt -> slt.getStartingTime( ).equals( slot.getStartingTime( ) ) && slt.getEndingTime( ).equals( slot.getEndingTime( ) )
731                                     && slt.getDate( ).getDayOfWeek( ).getValue( ) == slot.getDate( ).getDayOfWeek( ).getValue( ) )
732                             .collect( Collectors.toList( ) ) );
733         }
734         return listBuilded;
735     }
736 }