View Javadoc
1   /*
2    * Copyright (c) 2002-2018, Mairie de 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.stock.modules.billetterie.web;
35  
36  import fr.paris.lutece.plugins.stock.business.attribute.AbstractAttributeNum;
37  import fr.paris.lutece.plugins.stock.business.offer.OfferFilter;
38  import fr.paris.lutece.plugins.stock.business.product.Product;
39  import fr.paris.lutece.plugins.stock.business.product.ProductFilter;
40  import fr.paris.lutece.plugins.stock.business.purchase.PurchaseFilter;
41  import fr.paris.lutece.plugins.stock.commons.ResultList;
42  import fr.paris.lutece.plugins.stock.commons.dao.PaginationProperties;
43  import fr.paris.lutece.plugins.stock.commons.exception.BusinessException;
44  import fr.paris.lutece.plugins.stock.commons.exception.FunctionnalException;
45  import fr.paris.lutece.plugins.stock.modules.billetterie.utils.constants.BilletterieConstants;
46  import fr.paris.lutece.plugins.stock.modules.tickets.business.Contact;
47  import fr.paris.lutece.plugins.stock.modules.tickets.business.NotificationDTO;
48  import fr.paris.lutece.plugins.stock.modules.tickets.business.PartnerDTO;
49  import fr.paris.lutece.plugins.stock.modules.tickets.business.ReservationDTO;
50  import fr.paris.lutece.plugins.stock.modules.tickets.business.SeanceDTO;
51  import fr.paris.lutece.plugins.stock.modules.tickets.business.SeanceFilter;
52  import fr.paris.lutece.plugins.stock.modules.tickets.business.ShowDTO;
53  import fr.paris.lutece.plugins.stock.modules.tickets.service.INotificationService;
54  import fr.paris.lutece.plugins.stock.modules.tickets.service.IProviderService;
55  import fr.paris.lutece.plugins.stock.modules.tickets.service.IPurchaseService;
56  import fr.paris.lutece.plugins.stock.modules.tickets.service.ISeanceService;
57  import fr.paris.lutece.plugins.stock.modules.tickets.service.IShowService;
58  import fr.paris.lutece.plugins.stock.modules.tickets.utils.constants.TicketsConstants;
59  import fr.paris.lutece.plugins.stock.modules.tickets.utils.export.TicketsExportUtils;
60  import fr.paris.lutece.plugins.stock.service.IPurchaseSessionManager;
61  import fr.paris.lutece.plugins.stock.service.ISubscriptionProductService;
62  import fr.paris.lutece.plugins.stock.utils.CsvUtils;
63  import fr.paris.lutece.plugins.stock.utils.DateUtils;
64  import fr.paris.lutece.plugins.stock.utils.ListUtils;
65  import fr.paris.lutece.plugins.stock.utils.constants.StockConstants;
66  import fr.paris.lutece.portal.service.i18n.I18nService;
67  import fr.paris.lutece.portal.service.message.AdminMessage;
68  import fr.paris.lutece.portal.service.message.AdminMessageService;
69  import fr.paris.lutece.portal.service.spring.SpringContextService;
70  import fr.paris.lutece.portal.service.template.AppTemplateService;
71  import fr.paris.lutece.portal.service.util.AppLogService;
72  import fr.paris.lutece.portal.service.util.AppPathService;
73  import fr.paris.lutece.portal.service.util.AppPropertiesService;
74  import fr.paris.lutece.portal.web.constants.Parameters;
75  import fr.paris.lutece.util.ReferenceList;
76  import fr.paris.lutece.util.datatable.DataTableManager;
77  import fr.paris.lutece.util.html.HtmlTemplate;
78  
79  import org.apache.commons.lang.StringUtils;
80  import org.apache.log4j.Logger;
81  
82  import java.io.IOException;
83  import java.io.OutputStream;
84  import java.lang.reflect.Method;
85  import java.util.ArrayList;
86  import java.util.HashMap;
87  import java.util.List;
88  import java.util.Locale;
89  import java.util.Map;
90  import java.util.Optional;
91  
92  import javax.servlet.http.HttpServletRequest;
93  import javax.servlet.http.HttpServletResponse;
94  
95  import static java.util.Optional.ofNullable;
96  
97  /**
98   * This class provides the user interface to manage form features ( manage, create, modify, remove)
99   */
100 public class OfferJspBean extends AbstractJspBean
101 {
102     public static final Logger LOGGER = Logger.getLogger( OfferJspBean.class );
103 
104     // PARAMETERS
105     public static final String PARAMETER_OFFER_ID = "offer_id";
106     public static final String PARAMETER_OFFERS_ID = "offers_id";
107     public static final String PARAMETER_OFFER_DUPLICATE = "duplicate";
108     public static final String PARAMETER_OFFER_PRODUCT_ID = "productId";
109     public static final String PARAMETER_OFFER_ID_PRODUCT = "product.id";
110     public static final String PARAMETER_SUP_TICKETS = "offer_sup_tickets";
111     public static final String PARAMETER_OFFER_GENRE_LIST = "offer_genre_list";
112     public static final String PARAMETER_OFFER_GENRE_LIST_DEFAULT = "offer_genre_list_default";
113     public static final String PARAMETER_BUTTON_DELETE = "delete";
114     public static final String PARAMETER_FILTER_NAME = "filter_name";
115     public static final String PARAMETER_FILTER_ID = "filter_id";
116     public static final String PARAMETER_FILTER_PARTNER_NAME = "filter_partner_name";
117     public static final String PARAMETER_FILTER_PARTNER_NICKNAME = "filter_partner_nickname";
118     public static final String PARAMETER_FILTER_OFFER_TYPE = "filter_offer_type";
119     public static final String PARAMETER_FILTER_DATE_BEGIN = "filter_date_begin";
120     public static final String PARAMETER_FILTER_DATE_END = "filter_date_end";
121     public static final String PARAMETER_ORDER_BY_ID = "order_by_id";
122     public static final String PARAMETER_ORDER_BY_LABEL = "order_by_label";
123     public static final String PARAMETER_ORDER_BY_PLACE = "order_by_place";
124     public static final String PARAMETER_ORDER_BY_TYPE = "order_by_type";
125     public static final String PARAMETER_ORDER_ASC = "order_asc";
126     public static final String PARAMETER_ORDER_DESC = "order_desc";
127     public static final String PARAMETER_FILTER = "filter";
128     public static final String PARAMETER_PRODUCT_ID = "product_id";
129     public static final String PARAMETER_REFRESH_CONTACT = "refresh_contact";
130     public static final String PARAMETER_OLD_QUANTITY = "old_quantity";
131     public static final String RIGHT_MANAGE_OFFERS = "OFFERS_MANAGEMENT";
132     public static final String RESOURCE_TYPE = "STOCK";
133 
134     public static final String QUERY_SPECIFIC_PRODUCT = "page=billetterie&action=fiche-spectacle&product_id=";
135 
136     // MARKS
137     public static final String MARK_OFFER = "offer";
138     public static final String MARK_PRODUCT = "product";
139     public static final String MARK_TITLE = "title";
140     public static final String MARK_LOCALE = "locale";
141     public static final String MARK_OFFER_STATUT_CANCEL = "strStatutCancel";
142     public static final String MARK_OFFER_STATUT_LOCK = "strStatutLock";
143     public static final String MARK_CURRENT_DATE = "currentDate";
144     public static final String MARK_ERRORS = "errors";
145     public static final String MARK_CONTACT_LIST = "contact_list";
146     public static final String MARK_PURCHASE = "purchase";
147     public static final String MARK_BASE_URL = "base_url";
148     public static final String MARK_USER_NAME = "userName";
149     public static final String MARK_OFFER_ID = "offer_id";
150     public static final String MARK_PRODUCT_ID = "pruductId";
151 
152     /** The constants for DataTableManager */
153     public static final String MARK_DATA_TABLE_OFFER = "dataTableOffer";
154     public static final String MARK_FILTER_OFFER = "filterOffer";
155     public static final String MACRO_COLUMN_CHECKBOX_DELETE_OFFER = "columnCheckboxDeleteOffer";
156     public static final String MACRO_COLUMN_PRODUCT_OFFER = "columnProductOffer";
157     public static final String MACRO_COLUMN_STATUT_OFFER = "columnStatutOffer";
158     public static final String MACRO_COLUMN_ACTIONS_OFFER = "columnActionsOffer";
159     public static final String MACRO_COLUMN_NAME_OFFER = "columnNameOffer";
160     public static final String MACRO_COLUMN_DATES_OFFER = "columnDatesOffer";
161     private static final String MARK_LIST_PRODUCT = "product_list";
162     private static final String MARK_LIST_OFFER_GENRE = "offerGenre_list";
163     private static final String MARK_RESERVE = "RESERVATIONS";
164 
165     // JSP
166     private static final String JSP_MANAGE_OFFERS = "jsp/admin/plugins/stock/modules/billetterie/ManageOffers.jsp";
167     private static final String JSP_DO_DELETE_OFFER = "jsp/admin/plugins/stock/modules/billetterie/DoDeleteOffer.jsp";
168     private static final String JSP_DO_MASSE_DELETE_OFFER = "jsp/admin/plugins/stock/modules/billetterie/DoMasseDeleteOffer.jsp";
169     private static final String JSP_SAVE_OFFER = "SaveOffer.jsp";
170 
171     // TEMPLATES
172     private static final String TEMPLATE_MANAGE_OFFERS = "admin/plugins/stock/modules/billetterie/manage_offers.html";
173     private static final String TEMPLATE_SAVE_OFFER = "admin/plugins/stock/modules/billetterie/save_offer.html";
174     private static final String TEMPLATE_NOTIFICATION_CREATE_OFFER = "admin/plugins/stock/modules/billetterie/notification_create_offer.html";
175     private static final String TEMPLATE_NOTIFICATION_NEW_TICKETS = "admin/plugins/stock/modules/billetterie/notification_new_tickets.html";
176 
177     // PAGE TITLES
178     private static final String PROPERTY_PAGE_TITLE_MANAGE_OFFER = "module.stock.billetterie.list_offres.title";
179     private static final String PROPERTY_PAGE_TITLE_CREATE_OFFER = "module.stock.billetterie.create_offer.title";
180     private static final String PROPERTY_PAGE_TITLE_MODIFY_OFFER = "module.stock.billetterie.modify_offer.title";
181 
182     // MESSAGES
183     private static final String MESSAGE_CONFIRMATION_DELETE_OFFER = "module.stock.billetterie.message.deleteOffer.confirmation";
184     private static final String MESSAGE_CONFIRMATION_MASSE_DELETE_OFFER = "module.stock.billetterie.message.deleteOffer.masseConfirmation";
185     private static final String MESSAGE_TITLE_CONFIRMATION_DELETE_OFFER = "module.stock.billetterie.message.title.deleteOffer.confirmation";
186     private static final String MESSAGE_TITLE_CONFIRMATION_MASSE_DELETE_OFFER = "module.stock.billetterie.message.title.masseDeleteOffer.confirmation";
187     private static final String MESSAGE_OFFER_STATUT_ISNT_CANCEL = "module.stock.billetterie.message.offer.statut.isnt.cancel";
188     private static final String MESSAGE_OFFER_STATUT_ISNT_MASSE_CANCEL = "module.stock.billetterie.message.offer.statut.isnt.masseCancel";
189     private static final String MESSAGE_DELETE_MASSE_OFFER_NO_OFFER_CHECK = "module.stock.billetterie.message.deleteMasseOffer.noCaseCheck";
190     private static final String MESSAGE_SEARCH_PURCHASE_DATE = "module.stock.billetterie.message.search.purchase.date";
191     private static final String MESSAGE_NOTIFICATION_OFFER_PRODUCT = "module.stock.billetterie.notification.offer.product";
192     private static final String MESSAGE_ERROR_TICKETS_RANGE = "module.stock.billetterie.message.error.tickets.range";
193     private static final String MESSAGE_ERROR_SUPP_TICKETS = "module.stock.billetterie.message.error.supp.tickets";
194     // BEANS
195     private static final String BEAN_STOCK_TICKETS_SHOW_SERVICE = "stock-tickets.showService";
196     private static final String BEAN_STOCK_TICKETS_SEANCE_SERVICE = "stock-tickets.seanceService";
197     private static final String BEAN_STOCK_TICKETS_PARTNER_SERVICE = "stock-tickets.providerService";
198 
199     // ORDER FILTERS
200     private static final String ORDER_FILTER_DATE = "date";
201     private static final String ORDER_FILTER_PRODUCT_NAME = "product.name";
202     private static final String ORDER_FILTER_TYPE_NAME = "typeName";
203 
204     // MEMBERS VARIABLES
205     // @Inject
206     // @Named( "stock-tickets.seanceService" )
207     private ISeanceService _serviceOffer;
208 
209     // @Inject
210     // @Named( "stock-tickets.showService" )
211     private IShowService _serviceProduct;
212     private IProviderService _servicePartner;
213     private IPurchaseService _servicePurchase;
214     private INotificationService _serviceNotification;
215     private ISubscriptionProductService _subscriptionProductService;
216     private IPurchaseSessionManager _purchaseSessionManager;
217     private SeanceFilter _offerFilter;
218 
219     /**
220      * Instantiates a new offer jsp bean.
221      */
222     public OfferJspBean( )
223     {
224         super( );
225 
226         _offerFilter = new SeanceFilter( );
227         _serviceOffer = (ISeanceService) SpringContextService.getBean( BEAN_STOCK_TICKETS_SEANCE_SERVICE );
228         _serviceProduct = (IShowService) SpringContextService.getBean( BEAN_STOCK_TICKETS_SHOW_SERVICE );
229         _servicePartner = (IProviderService) SpringContextService.getBean( BEAN_STOCK_TICKETS_PARTNER_SERVICE );
230         _purchaseSessionManager = SpringContextService.getContext( ).getBean( IPurchaseSessionManager.class );
231         _servicePurchase = SpringContextService.getContext( ).getBean( IPurchaseService.class );
232         _serviceNotification = SpringContextService.getContext( ).getBean( INotificationService.class );
233         _subscriptionProductService = SpringContextService.getContext( ).getBean( ISubscriptionProductService.class );
234     }
235 
236     /**
237      * Builds the filter.
238      *
239      * @param filter
240      *            the filter
241      * @param request
242      *            the request
243      */
244     protected void buildFilter( OfferFilter filter, HttpServletRequest request )
245     {
246         populate( filter, request );
247     }
248 
249     /**
250      * Gets the offer filter.
251      *
252      * @param request
253      *            the request
254      * @return the offer filter
255      */
256     private OfferFilter getOfferFilter( HttpServletRequest request )
257     {
258         // SORT
259         String strSortedAttributeName = request.getParameter( Parameters.SORTED_ATTRIBUTE_NAME );
260 
261         // "filter" in request ==> new filter. use old one otherwise
262         // if ( request.getParameter( TicketsConstants.PARAMETER_FILTER ) !=
263         // null )
264         // {
265         SeanceFilter filter = new SeanceFilter( );
266         buildFilter( filter, request );
267         _offerFilter = filter;
268 
269         // }
270         if ( strSortedAttributeName != null )
271         {
272             _offerFilter.getOrders( ).add( strSortedAttributeName );
273 
274             String strAscSort = request.getParameter( Parameters.SORTED_ASC );
275             boolean bIsAscSort = Boolean.parseBoolean( strAscSort );
276             _offerFilter.setOrderAsc( bIsAscSort );
277         }
278 
279         return _offerFilter;
280     }
281 
282     /**
283      * Get the manage offers page
284      *
285      * @param request
286      *            the request
287      * @return the page with offers list
288      */
289     public String getManageOffers( HttpServletRequest request )
290     {
291         setPageTitleProperty( PROPERTY_PAGE_TITLE_MANAGE_OFFER );
292 
293         SeanceFilter filter = (SeanceFilter) getOfferFilter( request );
294 
295         boolean forceNewFilter = false;
296         if ( ( filter.getProductId( ) != null ) && ( filter.getProductId( ) > 0 ) )
297         {
298             ShowDTO show = this._serviceProduct.findById( filter.getProductId( ) );
299             filter.setProductName( show.getName( ) );
300             forceNewFilter = true;
301         }
302 
303         /*if ( StringUtils.isNotEmpty( filter.getProductName( ) ) )
304         {
305             ProductFilter productFilter = new ProductFilter( );
306             productFilter.setName( filter.getProductName( ) );
307 
308             List<ShowDTO> listShow = this._serviceProduct.findByFilter( productFilter );
309 
310             if ( !listShow.isEmpty( ) )
311             {
312                 filter.setProductId( listShow.get( 0 ).getId( ) );
313             }
314         }*/
315 
316         // Fill the model
317         Map<String, Object> model = new HashMap<String, Object>( );
318         model.put( MARK_LOCALE, getLocale( ) );
319 
320         // if date begin is after date end, add error
321         List<String> errors = new ArrayList<String>( );
322 
323         if ( ( filter.getDateBegin( ) != null ) && ( filter.getDateEnd( ) != null ) && filter.getDateBegin( ).after( filter.getDateEnd( ) ) )
324         {
325             errors.add( I18nService.getLocalizedString( MESSAGE_SEARCH_PURCHASE_DATE, request.getLocale( ) ) );
326         }
327 
328         model.put( MARK_ERRORS, errors );
329 
330         List<String> orderList = new ArrayList<String>( );
331         orderList.add( ORDER_FILTER_DATE );
332         orderList.add( ORDER_FILTER_PRODUCT_NAME );
333         orderList.add( ORDER_FILTER_TYPE_NAME );
334         filter.setOrders( orderList );
335         filter.setOrderAsc( true );
336 
337         DataTableManager<SeanceDTO> dataTableToUse = getDataTable( request, filter, forceNewFilter );
338 
339         for ( SeanceDTO seance : dataTableToUse.getItems( ) )
340         {
341             // Update quantity with quantity in session for this offer
342             seance.setQuantity( _purchaseSessionManager.updateQuantityWithSession( seance.getQuantity( ), seance.getId( ) ) );
343         }
344 
345         model.put( MARK_DATA_TABLE_OFFER, dataTableToUse );
346 
347         // the paginator
348         model.put( TicketsConstants.MARK_NB_ITEMS_PER_PAGE, String.valueOf( _nItemsPerPage ) );
349         // the filter
350         model.put( TicketsConstants.MARK_FILTER, filter );
351 
352         // Combo
353         ReferenceList offerGenreComboList = ListUtils.toReferenceList( _serviceOffer.findAllGenre( ), BilletterieConstants.ID, BilletterieConstants.NAME,
354                 StockConstants.EMPTY_STRING );
355         model.put( MARK_LIST_OFFER_GENRE, offerGenreComboList );
356         // offer statut
357         model.put( MARK_OFFER_STATUT_CANCEL, TicketsConstants.OFFER_STATUT_CANCEL );
358         model.put( MARK_OFFER_STATUT_LOCK, TicketsConstants.OFFER_STATUT_LOCK );
359         // currentDate
360         model.put( MARK_CURRENT_DATE, DateUtils.getCurrentDateString( ) );
361 
362         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MANAGE_OFFERS, getLocale( ), model );
363 
364         // opération nécessaire pour eviter les fuites de mémoires
365         dataTableToUse.clearItems( );
366 
367         return getAdminPage( template.getHtml( ) );
368     }
369 
370     /**
371      * Get the DataTableManager object for the ShowDTO bean
372      *
373      * @param request
374      *            the http request
375      * @param filter
376      *            the filter
377      * @return the data table to use
378      */
379     private <T> DataTableManager<T> getDataTable( HttpServletRequest request, SeanceFilter filter, boolean forceNewFilter )
380     {
381         // si un objet est déjà présent en session, on l'utilise
382         Method findMethod = null;
383 
384         try
385         {
386             findMethod = _serviceOffer.getClass( ).getMethod( PARAMETER_FIND_BY_FILTER_NAME_METHOD, OfferFilter.class, PaginationProperties.class );
387         }
388         catch( Exception e )
389         {
390             LOGGER.error( "Erreur lors de l'obtention du data table : ", e );
391         }
392 
393         DataTableManager<T> dataTableToUse = getAbstractDataTableManager( request, filter, MARK_DATA_TABLE_OFFER, JSP_MANAGE_OFFERS, _serviceOffer, findMethod,
394                 forceNewFilter );
395 
396         // si pas d'objet en session, il faut ajouter les colonnes à afficher
397         if ( dataTableToUse.getListColumn( ).isEmpty( ) )
398         {
399             dataTableToUse.addFreeColumn( StringUtils.EMPTY, MACRO_COLUMN_STATUT_OFFER );
400             dataTableToUse.addFreeColumn( StringUtils.EMPTY, MACRO_COLUMN_CHECKBOX_DELETE_OFFER );
401             dataTableToUse.addColumn( "module.stock.billetterie.list_offres.filter.name", "name", true );
402             dataTableToUse.addColumn( "module.stock.billetterie.list_offres.filter.product", "product.name", true );
403             dataTableToUse.addColumn( "module.stock.billetterie.list_offres.type", "typeName", true );
404             dataTableToUse.addFreeColumn( "module.stock.billetterie.save_offer.date", MACRO_COLUMN_DATES_OFFER );
405             /* dataTableToUse.addFreeColumn( "module.stock.billetterie.save_offer.initialQuantity", "columnInitialQuantityOffer" ); */
406             dataTableToUse.addFreeColumn( "module.stock.billetterie.save_offer.totalQuantity", "columnTotalQuantityOffer" );
407             dataTableToUse.addColumn( "module.stock.billetterie.save_offer.quantity", "quantity", false );
408             dataTableToUse.addFreeColumn( "module.stock.billetterie.save_offer.sessions.actions", MACRO_COLUMN_ACTIONS_OFFER );
409         }
410 
411         saveDataTableInSession( request, dataTableToUse, MARK_DATA_TABLE_OFFER );
412 
413         return dataTableToUse;
414     }
415 
416     /**
417      * Returns the form for offer creation and modification
418      *
419      * @param request
420      *            The HTTP request
421      * @return HTML Form
422      */
423     public String getSaveOffer( HttpServletRequest request )
424     {
425         SeanceDTO offer = null;
426         Map<String, Object> model = new HashMap<String, Object>( );
427         model.put( MARK_LOCALE, getLocale( ) );
428 
429         // Manage validation errors
430         FunctionnalException ve = getErrorOnce( request );
431 
432         if ( ve != null )
433         {
434             offer = (SeanceDTO) ve.getBean( );
435             model.put( BilletterieConstants.ERROR, getHtmlError( ve ) );
436         }
437         else
438         {
439             // No error, get offer if modify
440             String strOfferId = request.getParameter( PARAMETER_OFFER_ID );
441             String strDuplicate = request.getParameter( PARAMETER_OFFER_DUPLICATE );
442             String strProductId = request.getParameter( PARAMETER_OFFER_PRODUCT_ID );
443             ShowDTO selectedProduct;
444 
445             // Modification of an existing offer
446             if ( StringUtils.isNotEmpty( strOfferId ) )
447             {
448                 setPageTitleProperty( PROPERTY_PAGE_TITLE_MODIFY_OFFER );
449 
450                 Integer nIdOffer = Integer.parseInt( strOfferId );
451                 offer = _serviceOffer.findSeanceById( nIdOffer );
452 
453                 int idProvider = 0;
454 
455                 if ( request.getParameter( PARAMETER_REFRESH_CONTACT ) != null )
456                 {
457                     // case of wanting to get the contact which can be link to the offer
458                     populate( offer, request );
459 
460                     String strIdProduct = request.getParameter( PARAMETER_OFFER_ID_PRODUCT );
461                     int nIdSelectedProvider = Integer.valueOf( strIdProduct );
462 
463                     if ( nIdSelectedProvider >= 0 )
464                     {
465                         selectedProduct = _serviceProduct.findById( nIdSelectedProvider );
466                     }
467                     else
468                     {
469                         selectedProduct = _serviceProduct.findById( offer.getProduct( ).getId( ) );
470                     }
471                 }
472                 else
473                 {
474                     // case of taking the contact linking to the offer
475                     selectedProduct = _serviceProduct.findById( offer.getProduct( ).getId( ) );
476                 }
477                 idProvider = selectedProduct.getIdProvider( );
478 
479                 Integer idOffer = Integer.parseInt( strOfferId );
480 
481                 model.put( MARK_CONTACT_LIST, getContactList( idOffer ) );
482 
483                 // Duplicate offer, set id to 0 to create a new offer
484                 if ( StringUtils.isNotEmpty( strDuplicate ) )
485                 {
486                     offer.setId( null );
487                     offer.setStatut( StringUtils.EMPTY );
488                     offer.setQuantity(offer.getTotalQuantity());
489                 }
490             } // Create a new offer
491             else
492             {
493                 setPageTitleProperty( PROPERTY_PAGE_TITLE_CREATE_OFFER );
494                 // Create new Offer
495                 offer = new SeanceDTO( );
496 
497                 // If creation and filter "Spectacle" exist, pre-select the spectacle
498                 if ( StringUtils.isNotBlank( strProductId ) && !strProductId.equals( "0" ) )
499                 {
500                     Integer nIdProduct = Integer.parseInt( strProductId );
501                     offer.setProduct( _serviceProduct.findById( nIdProduct ) );
502                 }
503                 else
504                 {
505                     populate( offer, request );
506                 }
507 
508                 if ( ( request.getParameter( PARAMETER_OFFER_ID_PRODUCT ) != null ) && !request.getParameter( PARAMETER_OFFER_ID_PRODUCT ).equals( "-1" ) )
509                 {
510                     Integer idProduct = Integer.valueOf( request.getParameter( PARAMETER_OFFER_ID_PRODUCT ) );
511 
512                     ShowDTO productChoose = _serviceProduct.findById( idProduct );
513                     int idProvider = productChoose.getIdProvider( );
514 
515                     Product productById = _serviceProduct.getProductById( idProduct );
516 
517                     List<String> listContact = new ArrayList<>( );
518                     List<Contact> lstContact = _servicePartner.findById( idProvider ).getContactList( );
519 
520                     if ( lstContact != null && productById != null )
521                     {
522                         for ( Contact c : lstContact )
523                         {
524                             for ( AbstractAttributeNum sh : productById.getAttributeNumList( ) )
525                             {
526                                 if ( sh.getValue( ).intValueExact( ) == c.getId( ) && sh.getKey( ).contains( "idContact" ) )
527                                 {
528                                     listContact.add( c.getName( ) + ", email : " + c.getMail( ) );
529                                 }
530                             }
531                         }
532                     }
533 
534                     model.put( MARK_CONTACT_LIST, listContact );
535                 }
536             }
537         }
538 
539         // Combo
540         List<String> orderList = new ArrayList<String>( );
541         orderList.add( BilletterieConstants.NAME );
542 
543         ReferenceList productComboList = ListUtils.toReferenceList( _serviceProduct.getCurrentAndComeProduct( orderList ), BilletterieConstants.ID,
544                 BilletterieConstants.NAME, StockConstants.EMPTY_STRING );
545         ReferenceList offerGenreComboList = ListUtils.toReferenceList( _serviceOffer.findAllGenre( ), BilletterieConstants.ID, BilletterieConstants.NAME,
546                 StockConstants.EMPTY_STRING );
547         model.put( MARK_LIST_PRODUCT, productComboList );
548         model.put( MARK_LIST_OFFER_GENRE, offerGenreComboList );
549 
550         // Add the JSP wich called this action
551         model.put( StockConstants.MARK_JSP_BACK, request.getParameter( StockConstants.MARK_JSP_BACK ) );
552         model.put( MARK_OFFER, offer );
553 
554         if ( offer.getId( ) != null )
555         {
556             model.put( MARK_TITLE, I18nService.getLocalizedString( PROPERTY_PAGE_TITLE_MODIFY_OFFER, Locale.getDefault( ) ) );
557         }
558         else
559         {
560             model.put( MARK_TITLE, I18nService.getLocalizedString( PROPERTY_PAGE_TITLE_CREATE_OFFER, Locale.getDefault( ) ) );
561         }
562 
563         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_SAVE_OFFER, getLocale( ), model );
564 
565         return getAdminPage( template.getHtml( ) );
566     }
567 
568     private List<String> getContactList( Integer idOfferProduct )
569     {
570         SeanceDTO seanceDto = new SeanceDTO( );
571         seanceDto = ofNullable( idOfferProduct ).map( e -> _serviceOffer.findSeanceById( e ) ).orElse( null );
572         ShowDTO showDto = ofNullable( seanceDto ).map( e -> e.getProduct( ) ).orElse( null );
573         Product productById = _serviceProduct.getProductById( ofNullable( showDto ).map( e -> e.getId( ) ).orElse( null ) );
574 
575         List<String> listContact = new ArrayList<>( );
576         List<Contact> lstContact = ofNullable( showDto ).map( e -> _servicePartner.findById( e.getIdProvider( ) ).getContactList( ) ).orElse( null );
577 
578         if ( lstContact != null && productById != null )
579         {
580             for ( Contact c : lstContact )
581             {
582                 for ( AbstractAttributeNum sh : productById.getAttributeNumList( ) )
583                 {
584                     if ( sh.getValue( ).intValueExact( ) == c.getId( ) && sh.getKey( ).contains( "idContact" ) )
585                     {
586                         listContact.add( c.getName( ) + ", email : " + c.getMail( ) );
587                     }
588                 }
589             }
590         }
591         return listContact;
592     }
593 
594     /**
595      * Get only the selected contacts in the product, not all the contacts from the provider of the product
596      * 
597      * @param idProvider
598      * @return The ReferenceList
599      */
600     private ReferenceList getContactComboList( int idProvider )
601     {
602         PartnerDTO findById = _servicePartner.findById( idProvider );
603         ReferenceList contactComboList = ListUtils.toReferenceList( findById.getContactList( ), BilletterieConstants.ID, BilletterieConstants.NAME,
604                 StockConstants.EMPTY_STRING );
605         return contactComboList;
606     }
607 
608     /**
609      * Save a offer
610      * 
611      * @param request
612      *            The HTTP request
613      * @return redirection url
614      */
615     public String doSaveOffer( HttpServletRequest request )
616     {
617         if ( request.getParameter( StockConstants.PARAMETER_BUTTON_CANCEL ) != null )
618         {
619             return doGoBack( request );
620         }
621         Integer nSuppTickets = 0;
622         String strSuppTickets = request.getParameter( PARAMETER_SUP_TICKETS );
623         if ( StringUtils.isNotBlank( strSuppTickets ) )
624         {
625             nSuppTickets = Integer.parseInt( strSuppTickets );
626         }
627 
628         SeanceDTO offer = new SeanceDTO( );
629         SeanceDTO offerInBdd = new SeanceDTO( );
630         populate( offer, request );
631         boolean isNewOffer = offer.getId( ) == null;
632 
633         if ( !isNewOffer )
634         {
635             offerInBdd = _serviceOffer.findSeanceById( offer.getId( ) );
636         }
637         Integer nSuppSign = nSuppTickets;
638         if (nSuppTickets<0){
639             nSuppSign = nSuppTickets*(-1);
640         }
641         Integer restQuantity = null;
642         if ( offer.getTotalQuantity( ) != null )
643         {
644             restQuantity = offer.getQuantity( );
645             if (nSuppTickets>0) {
646                     if (offerInBdd.getTotalQuantity() != null) {
647 
648                         if (offerInBdd.getTotalQuantity().equals(offer.getTotalQuantity())) {
649                             offer.setTotalQuantity(offer.getTotalQuantity() + nSuppTickets);
650                         } else {
651                             offer.setTotalQuantity(offerInBdd.getTotalQuantity() + nSuppTickets);
652                         }
653                         if (offerInBdd.getQuantity() == offer.getQuantity()) {
654                              offer.setQuantity(offer.getQuantity() + nSuppTickets);
655                          } else {
656                                 offer.setQuantity(offer.getQuantity()+nSuppTickets);
657                         }
658                     } else {
659                         offer.setTotalQuantity(offer.getTotalQuantity() + nSuppTickets);
660                         if (!isNewOffer) {
661                             offer.setQuantity(offer.getQuantity()+nSuppTickets);
662                         } else {
663                             offer.setQuantity(offer.getTotalQuantity());
664                         }
665                     }
666             }
667             else
668             {
669                 if (nSuppTickets<0) {
670                     if (offer.getQuantity() - nSuppSign >=0) {
671                         if (offerInBdd.getTotalQuantity() != null) {
672 
673                             if (offerInBdd.getTotalQuantity().equals(offer.getTotalQuantity())) {
674                                 offer.setTotalQuantity(offer.getTotalQuantity() + nSuppTickets);
675                             } else {
676                                 offer.setTotalQuantity(offerInBdd.getTotalQuantity() + nSuppTickets);
677                             }
678                             if (offerInBdd.getQuantity() == offer.getQuantity()) {
679                                     offer.setQuantity(offer.getQuantity()+nSuppTickets);
680                             } else {
681                                 offer.setQuantity(offer.getQuantity()+nSuppTickets);
682                             }
683                         } else {
684                             offer.setTotalQuantity(offer.getTotalQuantity() + nSuppTickets);
685                             if (!isNewOffer) {
686                                 offer.setQuantity(offer.getQuantity()+nSuppTickets);
687                             } else {
688                                 offer.setQuantity(offer.getTotalQuantity());
689                             }
690                         }
691                     }
692                 }
693                 else {
694                     if ( isNewOffer )
695                     {
696                         offer.setQuantity( offer.getTotalQuantity( ) );
697                     }
698                 }
699             }
700         }
701         else
702         {
703             offer.setTotalQuantity( offer.getQuantity( ) );
704         }
705 
706         int oldQuantity = 0;
707         if ( StringUtils.isNotEmpty( request.getParameter( PARAMETER_OLD_QUANTITY ) ) )
708         {
709             oldQuantity = Integer.valueOf( request.getParameter( PARAMETER_OLD_QUANTITY ) );
710         }
711 
712         // make sur you set the initial quantity only in the first save offer, not in modify offer
713         try
714         {
715             // Controls mandatory fields
716             validateBilletterie( offer );
717             if (restQuantity != null){
718                 if ( restQuantity < nSuppSign && nSuppTickets < 0)
719                 {
720                     throw new BusinessException( offer, MESSAGE_ERROR_SUPP_TICKETS );
721                 }
722             }
723 
724             if ( offer.getMinTickets( ) > offer.getMaxTickets( ) )
725             {
726                 throw new BusinessException( offer, MESSAGE_ERROR_TICKETS_RANGE );
727             }
728 
729             _serviceOffer.doSaveOffer( offer );
730         }
731         catch( FunctionnalException e )
732         {
733             return manageFunctionnalException( request, e, JSP_SAVE_OFFER );
734         }
735 
736         if ( isNewOffer )
737         {
738             doNotifyCreateOffer( request, offer );
739         }
740         else
741             if ( offer.getQuantity( ) > oldQuantity )
742             {
743                 doNotifyNewTickets( request, offer );
744             }
745 
746         return doGoBack( request );
747     }
748 
749     /**
750      * Send notification for user who subscribed to the product link with an offer.
751      * 
752      * @param offer
753      *            the offer create
754      * @param request
755      *            The Http request
756      */
757     public void doNotifyCreateOffer( HttpServletRequest request, SeanceDTO offer )
758     {
759         // get all subscription for product
760         Product product = _serviceProduct.findById( offer.getProduct( ).getId( ) ).convert( );
761 
762         List<String> listUserEmail = _subscriptionProductService.getListEmailSubscriber( Integer.toString( offer.getProduct( ).getId( ) ) );
763 
764         // Generate mail content
765         Map<String, Object> model = new HashMap<String, Object>( );
766         model.put( MARK_OFFER, offer );
767         model.put( MARK_PRODUCT, product );
768         model.put( MARK_BASE_URL, AppPathService.getBaseUrl( request ) + AppPathService.getPortalUrl( ) + "?" + QUERY_SPECIFIC_PRODUCT + product.getId( ) );
769 
770         // Create mail object
771         HtmlTemplate template;
772         NotificationDTO notificationDTO;
773 
774         for ( String strUserEmail : listUserEmail )
775         {
776             model.put( MARK_USER_NAME, strUserEmail );
777             template = AppTemplateService.getTemplate( TEMPLATE_NOTIFICATION_CREATE_OFFER, request.getLocale( ), model );
778 
779             notificationDTO = new NotificationDTO( );
780             notificationDTO.setRecipientsTo( strUserEmail );
781 
782             String [ ] args = new String [ ] {
783                 product.getName( ),
784             };
785             notificationDTO.setSubject( I18nService.getLocalizedString( MESSAGE_NOTIFICATION_OFFER_PRODUCT, args, request.getLocale( ) ) );
786             notificationDTO.setMessage( template.getHtml( ) );
787 
788             // Send it
789             _serviceNotification.send( notificationDTO );
790         }
791     }
792 
793     /**
794      * Send notification for user who subscribed to the product link with an offer.
795      * 
796      * @param offer
797      *            the offer create
798      * @param request
799      *            The Http request
800      */
801     public void doNotifyNewTickets( HttpServletRequest request, SeanceDTO offer )
802     {
803         // get all subscription for product
804         Product product = _serviceProduct.findById( offer.getProduct( ).getId( ) ).convert( );
805 
806         List<String> listUserEmail = _subscriptionProductService.getListEmailSubscriber( Integer.toString( offer.getProduct( ).getId( ) ) );
807 
808         // Generate mail content
809         Map<String, Object> model = new HashMap<String, Object>( );
810         model.put( MARK_OFFER, offer );
811         model.put( MARK_PRODUCT, product );
812         model.put( MARK_BASE_URL, AppPathService.getBaseUrl( request ) + AppPathService.getPortalUrl( ) + "?" + QUERY_SPECIFIC_PRODUCT + product.getId( ) );
813 
814         // Create mail object
815         HtmlTemplate template;
816         NotificationDTO notificationDTO;
817 
818         for ( String strUserEmail : listUserEmail )
819         {
820             model.put( MARK_USER_NAME, strUserEmail );
821             template = AppTemplateService.getTemplate( TEMPLATE_NOTIFICATION_NEW_TICKETS, request.getLocale( ), model );
822 
823             notificationDTO = new NotificationDTO( );
824             notificationDTO.setRecipientsTo( strUserEmail );
825 
826             String [ ] args = new String [ ] {
827                 product.getName( ),
828             };
829             notificationDTO.setSubject( I18nService.getLocalizedString( MESSAGE_NOTIFICATION_OFFER_PRODUCT, args, request.getLocale( ) ) );
830             notificationDTO.setMessage( template.getHtml( ) );
831 
832             // Send it
833             _serviceNotification.send( notificationDTO );
834         }
835     }
836 
837     /**
838      * Return the url of the JSP which called the last action
839      * 
840      * @param request
841      *            The Http request
842      * @return The url of the last JSP
843      */
844     private String doGoBack( HttpServletRequest request )
845     {
846         String strJspBack = request.getParameter( StockConstants.MARK_JSP_BACK );
847 
848         return StringUtils.isNotBlank( strJspBack ) ? ( AppPathService.getBaseUrl( request ) + strJspBack )
849                 : ( AppPathService.getBaseUrl( request ) + JSP_MANAGE_OFFERS );
850     }
851 
852     /**
853      * Returns the confirmation message to delete an offer
854      *
855      * @param request
856      *            The Http request
857      * @return the html code message
858      */
859     public String getDeleteOffer( HttpServletRequest request )
860     {
861         String strOfferId = request.getParameter( PARAMETER_OFFER_ID );
862 
863         Integer nIdOffer;
864 
865         try
866         {
867             nIdOffer = Integer.parseInt( strOfferId );
868         }
869         catch( NumberFormatException e )
870         {
871             LOGGER.debug( e );
872 
873             return AdminMessageService.getMessageUrl( request, StockConstants.MESSAGE_ERROR_OCCUR, AdminMessage.TYPE_STOP );
874         }
875 
876         Map<String, Object> urlParam = new HashMap<String, Object>( );
877 
878         String strJspBack = JSP_MANAGE_OFFERS;
879 
880         ArrayList<Integer> offersIdToDelete = new ArrayList<Integer>( );
881         offersIdToDelete.add( nIdOffer );
882 
883         String error = errorWithDeleteOffer( request, urlParam, offersIdToDelete );
884 
885         if ( error != null )
886         {
887             return error;
888         }
889 
890         return AdminMessageService.getMessageUrl( request, MESSAGE_CONFIRMATION_DELETE_OFFER, null, MESSAGE_TITLE_CONFIRMATION_DELETE_OFFER,
891                 JSP_DO_DELETE_OFFER, BilletterieConstants.TARGET_SELF, AdminMessage.TYPE_CONFIRMATION, urlParam, strJspBack );
892     }
893 
894     /**
895      * Delete an offer
896      *
897      * @param request
898      *            The Http request
899      * @return the html code message
900      */
901     public String doDeleteOffer( HttpServletRequest request )
902     {
903         String strOfferId = request.getParameter( PARAMETER_OFFER_ID );
904 
905         int nIdOffer;
906 
907         try
908         {
909             nIdOffer = Integer.parseInt( strOfferId );
910         }
911         catch( NumberFormatException e )
912         {
913             LOGGER.debug( e );
914 
915             return AdminMessageService.getMessageUrl( request, StockConstants.MESSAGE_ERROR_OCCUR, AdminMessage.TYPE_STOP );
916         }
917 
918         _serviceOffer.doDeleteOffer( nIdOffer );
919 
920         return doGoBack( request );
921     }
922 
923     /**
924      * Returns the confirmation message to delete an offer
925      *
926      * @param request
927      *            The Http request
928      * @return the html code message
929      */
930     public String getMasseDeleteOffer( HttpServletRequest request )
931     {
932         ArrayList<Integer> offersIdToDelete = new ArrayList<Integer>( );
933         String [ ] parameterValues = request.getParameterValues( PARAMETER_OFFERS_ID );
934 
935         // if no case checked
936         if ( parameterValues == null )
937         {
938             return AdminMessageService.getMessageUrl( request, MESSAGE_DELETE_MASSE_OFFER_NO_OFFER_CHECK, AdminMessage.TYPE_STOP );
939         }
940 
941         try
942         {
943             for ( String id : parameterValues )
944             {
945                 offersIdToDelete.add( Integer.parseInt( id ) );
946             }
947         }
948         catch( NumberFormatException e )
949         {
950             LOGGER.debug( e );
951 
952             return AdminMessageService.getMessageUrl( request, StockConstants.MESSAGE_ERROR_OCCUR, AdminMessage.TYPE_STOP );
953         }
954 
955         boolean masse = offersIdToDelete.size( ) > 1;
956 
957         String strJspBack = JSP_MANAGE_OFFERS;
958 
959         Map<String, Object> urlParam = new HashMap<String, Object>( );
960         String error = errorWithDeleteOffer( request, urlParam, offersIdToDelete );
961 
962         if ( error != null )
963         {
964             return error;
965         }
966 
967         return AdminMessageService.getMessageUrl( request, masse ? MESSAGE_CONFIRMATION_MASSE_DELETE_OFFER : MESSAGE_CONFIRMATION_DELETE_OFFER, null,
968                 MESSAGE_TITLE_CONFIRMATION_MASSE_DELETE_OFFER, masse ? JSP_DO_MASSE_DELETE_OFFER : JSP_DO_DELETE_OFFER, BilletterieConstants.TARGET_SELF,
969                 AdminMessage.TYPE_CONFIRMATION, urlParam, strJspBack );
970     }
971 
972     /**
973      * Delete offers on masse
974      *
975      * @param request
976      *            The Http request which contains the offer checked
977      * @return the html code message
978      */
979     public String doMasseDeleteOffer( HttpServletRequest request )
980     {
981         ArrayList<Integer> listOffer = new ArrayList<Integer>( );
982 
983         try
984         {
985             for ( Object key : request.getParameterMap( ).keySet( ) )
986             {
987                 // ensure the key parameter it rightly an offer, can be offer_id1, offer_id2, ... or "offer_id" with only one offer
988                 if ( ( (String) key ).startsWith( PARAMETER_OFFER_ID ) )
989                 {
990                     listOffer.add( Integer.parseInt( request.getParameter( (String) key ) ) );
991                 }
992             }
993         }
994         catch( NumberFormatException e )
995         {
996             LOGGER.debug( e );
997 
998             return AdminMessageService.getMessageUrl( request, StockConstants.MESSAGE_ERROR_OCCUR, AdminMessage.TYPE_STOP );
999         }
1000 
1001         _serviceOffer.doMasseDeleteOffer( listOffer );
1002 
1003         return doGoBack( request );
1004     }
1005 
1006     /**
1007      * Return a error message if it's not possible to delete the offer given, with deleteOffer or masseDeleteOffer
1008      * 
1009      * @param request
1010      *            The Http request
1011      * @param urlParam
1012      *            the url where the id offer to delete must be set
1013      * @param offersIdToDelete
1014      *            the list which contains the offer to delete
1015      * @return null if there aren't error, the message otherwise
1016      */
1017     private String errorWithDeleteOffer( HttpServletRequest request, Map<String, Object> urlParam, ArrayList<Integer> offersIdToDelete )
1018     {
1019         boolean masse = offersIdToDelete.size( ) > 1;
1020         PurchaseFilter filter = new PurchaseFilter( );
1021         int i = 0;
1022 
1023         for ( Integer nIdOffer : offersIdToDelete )
1024         {
1025             urlParam.put( masse ? ( PARAMETER_OFFER_ID + i++ ) : PARAMETER_OFFER_ID, nIdOffer );
1026             filter.setIdOffer( nIdOffer );
1027 
1028             ResultList<ReservationDTO> bookingList = _servicePurchase.findByFilter( filter, null );
1029 
1030             // // BO-E07-RGE02 : Only offer with date before current date or offer
1031             // with statut "Cancel" can be delete
1032             SeanceDTO seance = _serviceOffer.findSeanceById( nIdOffer );
1033 
1034             if ( !bookingList.isEmpty( ) && !seance.getStatut( ).equals( TicketsConstants.OFFER_STATUT_CANCEL ) )
1035             {
1036                 if ( !DateUtils.getDate( seance.getDate( ), false ).before( DateUtils.getCurrentDate( ) ) )
1037                 {
1038                     return AdminMessageService.getMessageUrl( request, masse ? MESSAGE_OFFER_STATUT_ISNT_MASSE_CANCEL : MESSAGE_OFFER_STATUT_ISNT_CANCEL,
1039                             AdminMessage.TYPE_STOP );
1040                 }
1041             }
1042         }
1043 
1044         return null;
1045     }
1046 
1047     public void doExportPurchase( HttpServletRequest request, HttpServletResponse response )
1048     {
1049         ResultList<ReservationDTO> reservationModel = new ResultList<ReservationDTO>( );
1050         PurchaseFilter filter = new PurchaseFilter( );
1051         String parameter = request.getParameter( PARAMETER_OFFER_ID );
1052         int idOffer = Integer.valueOf( parameter );
1053         filter.setIdOffer( idOffer );
1054         reservationModel.addAll( _servicePurchase.findByFilter( filter ) );
1055 
1056         try
1057         {
1058             // Génère le CSV
1059             String strFormatExtension = AppPropertiesService.getProperty( TicketsConstants.PROPERTY_CSV_EXTENSION );
1060             String strFileName = AppPropertiesService.getProperty( TicketsConstants.PROPERTY_CSV_PURCHASE_NAME ) + "." + strFormatExtension;
1061             TicketsExportUtils.addHeaderResponse( request, response, strFileName, strFormatExtension );
1062 
1063             OutputStream os = response.getOutputStream( );
1064 
1065             // say how to decode the csv file, with utf8
1066             byte [ ] bom = new byte [ ] {
1067                     (byte) 0xEF, (byte) 0xBB, (byte) 0xBF
1068             }; // BOM values
1069             os.write( bom ); // adds BOM
1070             CsvUtils.ecrireCsv( MARK_RESERVE, reservationModel, os );
1071 
1072             os.flush( );
1073             os.close( );
1074         }
1075         catch( IOException e )
1076         {
1077             AppLogService.error( e );
1078         }
1079     }
1080 }