AppointmentExportService.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.service.export;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import fr.paris.lutece.plugins.appointment.business.appointment.Appointment;
import fr.paris.lutece.plugins.appointment.business.category.Category;
import fr.paris.lutece.plugins.appointment.business.category.CategoryHome;
import fr.paris.lutece.plugins.appointment.business.form.Form;
import fr.paris.lutece.plugins.appointment.business.form.FormHome;
import fr.paris.lutece.plugins.appointment.service.AppointmentResponseService;
import fr.paris.lutece.plugins.appointment.service.Utilities;
import fr.paris.lutece.plugins.appointment.service.entrytype.EntryTypeGroup;
import fr.paris.lutece.plugins.appointment.web.dto.AppointmentDTO;
import fr.paris.lutece.plugins.genericattributes.business.Entry;
import fr.paris.lutece.plugins.genericattributes.business.EntryFilter;
import fr.paris.lutece.plugins.genericattributes.business.EntryHome;
import fr.paris.lutece.plugins.genericattributes.business.Field;
import fr.paris.lutece.plugins.genericattributes.business.FieldHome;
import fr.paris.lutece.plugins.genericattributes.business.Response;
import fr.paris.lutece.plugins.genericattributes.business.ResponseHome;
import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
import fr.paris.lutece.plugins.workflowcore.business.state.State;
import fr.paris.lutece.plugins.workflowcore.service.state.StateService;
import fr.paris.lutece.portal.service.i18n.I18nService;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.workflow.WorkflowService;
import fr.paris.lutece.util.ReferenceList;

public final class AppointmentExportService
{

    private static final String KEY_RESOURCE_TYPE = "appointment.appointment.name";
    private static final String KEY_COLUMN_FORM_CATEGORY = "appointment.manageAppointments.columnFormCategory";
    private static final String KEY_COLUMN_FORM_TITLE = "appointment.manageAppointments.columnFormTitle";
    private static final String KEY_COLUMN_LAST_NAME = "appointment.manageAppointments.columnLastName";
    private static final String KEY_COLUMN_FIRST_NAME = "appointment.manageAppointments.columnFirstName";
    private static final String KEY_COLUMN_EMAIL = "appointment.manageAppointments.columnEmail";
    private static final String KEY_COLUMN_DATE_APPOINTMENT = "appointment.dateAppointment.title";
    private static final String KEY_TIME_START = "appointment.model.entity.appointmentform.attribute.timeStart";
    private static final String KEY_TIME_END = "appointment.model.entity.appointmentform.attribute.timeEnd";
    private static final String KEY_COLUMN_ADMIN = "appointment.manageAppointments.columnAdmin";
    private static final String KEY_COLUMN_STATUS = "appointment.labelStatus";
    private static final String KEY_COLUMN_STATE = "appointment.manageAppointments.columnState";
    private static final String KEY_COLUMN_NB_BOOKED_SEATS = "appointment.manageAppointments.columnNumberOfBookedseatsPerAppointment";
    private static final String KEY_DATE_APPOINT_TAKEN = "appointment.model.entity.appointmentform.attribute.dateTaken";
    private static final String KEY_HOUR_APPOINT_TAKEN = "appointment.model.entity.appointmentform.attribute.hourTaken";

    private static final String CONSTANT_COMMA = ",";

    private static final List<String> DEFAULT_COLUMN_LIST = Arrays.asList( KEY_COLUMN_FORM_CATEGORY, KEY_COLUMN_FORM_TITLE, KEY_COLUMN_LAST_NAME,
            KEY_COLUMN_FIRST_NAME, KEY_COLUMN_EMAIL, KEY_COLUMN_DATE_APPOINTMENT, KEY_TIME_START, KEY_TIME_END, KEY_COLUMN_ADMIN, KEY_COLUMN_STATUS,
            KEY_COLUMN_STATE, KEY_COLUMN_NB_BOOKED_SEATS, KEY_DATE_APPOINT_TAKEN, KEY_HOUR_APPOINT_TAKEN );

    private AppointmentExportService( )
    {
    }

    /**
     * Build the excel fil of the list of the appointments found in the manage appointment viw by filter
     * 
     * @param excelFile
     *            the excel file to write
     * @param defaultColumnList
     *            the default columns to export
     * @param entryList
     *            the entries to export
     * @param locale
     *            the local
     * @param listAppointmentsDTO
     *            the list of the appointments to input in the excel file
     */
    public static void buildExcelFileWithAppointments( List<String> defaultColumnList, List<Integer> entryList, Path excelFile, Locale locale,
            List<AppointmentDTO> listAppointmentsDTO )
    {
        List<List<Object>> linesValues = new ArrayList<>( );
        EntryFilter entryFilter = new EntryFilter( );
        List<Entry> listEntry = EntryHome.getEntryList( entryFilter ).stream( ).filter( e -> entryList.contains( e.getIdEntry( ) ) ).map( Entry::getIdEntry )
                .map( EntryHome::findByPrimaryKey ).collect( Collectors.toList( ) );

        linesValues.add( createHeaderContent( defaultColumnList, listEntry, locale ) );

        if ( listAppointmentsDTO != null )
        {
            StateService stateService = null;
            if ( WorkflowService.getInstance( ).isAvailable( ) )
            {
                stateService = SpringContextService.getBean( StateService.BEAN_SERVICE );
            }
            Map<Integer, Form> formMap = new HashMap<>( );
            for ( AppointmentDTO appointmentDTO : listAppointmentsDTO )
            {
                Form form = formMap.computeIfAbsent( appointmentDTO.getIdForm( ), FormHome::findByPrimaryKey );
                linesValues.add( createLineContent( appointmentDTO, form, defaultColumnList, listEntry, stateService, locale ) );
            }
        }
        writeWorkbook( linesValues, excelFile, locale );
    }

    private static final void writeWorkbook( List<List<Object>> linesValues, Path excelFile, Locale locale )
    {
        int nRownum = 0;
        try ( XSSFWorkbook workbook = new XSSFWorkbook( ) ; OutputStream os = Files.newOutputStream( excelFile ) )
        {
            XSSFSheet sheet = workbook.createSheet( I18nService.getLocalizedString( KEY_RESOURCE_TYPE, locale ) );
            for ( List<Object> line : linesValues )
            {
                Row row = sheet.createRow( nRownum++ );
                int nCellnum = 0;
                for ( Object cellValue : line )
                {
                    Cell cell = row.createCell( nCellnum++ );
                    if ( cellValue instanceof String )
                    {
                        cell.setCellValue( (String) cellValue );
                    }
                    else
                        if ( cellValue instanceof Boolean )
                        {
                            cell.setCellValue( (Boolean) cellValue );
                        }
                        else
                            if ( cellValue instanceof Date )
                            {
                                cell.setCellValue( (Date) cellValue );
                            }
                            else
                                if ( cellValue instanceof Double )
                                {
                                    cell.setCellValue( (Double) cellValue );
                                }
                }
            }
            workbook.write( os );
        }
        catch( IOException e )
        {
            AppLogService.error( e );
        }
    }

    private static final List<Object> createHeaderContent( List<String> defaultColumnList, List<Entry> listEntry, Locale locale )
    {
        List<Object> strInfos = new ArrayList<>( );
        for ( String key : defaultColumnList )
        {
            strInfos.add( I18nService.getLocalizedString( key, locale ) );
        }

        if ( CollectionUtils.isNotEmpty( listEntry ) )
        {
            for ( Entry e : listEntry )
            {
                strInfos.add( e.getTitle( ) );
            }
        }
        return strInfos;
    }

    private static final List<Object> createLineContent( AppointmentDTO appointmentDTO, Form form, List<String> defaultColumnList, List<Entry> listEntry,
            StateService stateService, Locale locale )
    {
        List<Object> strWriter = new ArrayList<>( );
        addDefaultColumnValues( appointmentDTO, form, defaultColumnList, strWriter, stateService, locale );

        List<Integer> listIdResponse = AppointmentResponseService.findListIdResponse( appointmentDTO.getIdAppointment( ) );
        List<Response> listResponses = new ArrayList<>( );
        for ( int nIdResponse : listIdResponse )
        {
            Response resp = ResponseHome.findByPrimaryKey( nIdResponse );
            if ( resp != null )
            {
                listResponses.add( resp );
            }
        }
        for ( Entry e : listEntry )
        {
            String value = getEntryValue( e, listResponses, locale );
            strWriter.add( value );
        }
        return strWriter;
    }

    private static final void addDefaultColumnValues( AppointmentDTO appointmentDTO, Form form, List<String> defaultColumnList, List<Object> strWriter,
            StateService stateService, Locale locale )
    {
        if ( defaultColumnList.contains( KEY_COLUMN_FORM_CATEGORY ) )
        {
            Category category = CategoryHome.findByPrimaryKey( form.getIdCategory( ) );
            String catStr = "";
            if ( category != null )
            {
                catStr = category.getLabel( );
            }
            strWriter.add( catStr );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_FORM_TITLE ) )
        {
            strWriter.add( form.getTitle( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_LAST_NAME ) )
        {
            strWriter.add( appointmentDTO.getLastName( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_FIRST_NAME ) )
        {
            strWriter.add( appointmentDTO.getFirstName( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_EMAIL ) )
        {
            strWriter.add( appointmentDTO.getEmail( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_DATE_APPOINTMENT ) )
        {
            strWriter.add( appointmentDTO.getDateOfTheAppointment( ) );
        }
        if ( defaultColumnList.contains( KEY_TIME_START ) )
        {
            strWriter.add( appointmentDTO.getStartingTime( ).toString( ) );
        }
        if ( defaultColumnList.contains( KEY_TIME_END ) )
        {
            strWriter.add( appointmentDTO.getEndingTime( ).toString( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_ADMIN ) )
        {
            strWriter.add( appointmentDTO.getAdminUser( ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_STATUS ) )
        {
            strWriter.add( getStatusValue( appointmentDTO, locale ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_STATE ) )
        {
            strWriter.add( getStateValue( appointmentDTO, form.getIdWorkflow( ), stateService ) );
        }
        if ( defaultColumnList.contains( KEY_COLUMN_NB_BOOKED_SEATS ) )
        {
            strWriter.add( Integer.toString( appointmentDTO.getNbBookedSeats( ) ) );
        }
        if ( defaultColumnList.contains( KEY_DATE_APPOINT_TAKEN ) )
        {
            strWriter.add( appointmentDTO.getDateAppointmentTaken( ).toLocalDate( ).format( Utilities.getFormatter( ) ) );
        }
        if ( defaultColumnList.contains( KEY_HOUR_APPOINT_TAKEN ) )
        {
            strWriter.add( appointmentDTO.getDateAppointmentTaken( ).toLocalTime( ).withSecond( 0 ).toString( ) );
        }
    }

    private static String getStatusValue( AppointmentDTO appointmentDTO, Locale locale )
    {
        String status = I18nService.getLocalizedString( AppointmentDTO.PROPERTY_APPOINTMENT_STATUS_RESERVED, locale );
        if ( appointmentDTO.getIsCancelled( ) )
        {
            status = I18nService.getLocalizedString( AppointmentDTO.PROPERTY_APPOINTMENT_STATUS_UNRESERVED, locale );
        }
        return status;
    }

    private static String getStateValue( AppointmentDTO appointmentDTO, int idWorkflow, StateService stateService )
    {
        String strState = StringUtils.EMPTY;
        if ( stateService != null )
        {
            State stateAppointment = stateService.findByResource( appointmentDTO.getIdAppointment( ), Appointment.APPOINTMENT_RESOURCE_TYPE, idWorkflow );
            if ( stateAppointment != null )
            {
                appointmentDTO.setState( stateAppointment );
                strState = stateAppointment.getName( );
            }
        }
        return strState;
    }

    private static final String getEntryValue( Entry e, List<Response> listResponses, Locale locale )
    {
        Integer key = e.getIdEntry( );
        StringBuilder strValue = new StringBuilder( );
        String strPrefix = StringUtils.EMPTY;

        List<Response> listResponsesForEntry = listResponses.stream( ).filter( resp -> key.equals( resp.getEntry( ).getIdEntry( ) ) )
                .filter( resp -> StringUtils.isNotEmpty( resp.getResponseValue( ) ) ).collect( Collectors.toList( ) );

        for ( Response resp : listResponsesForEntry )
        {
            Field f = resp.getField( );
            if ( f != null )
            {
                resp.setField( FieldHome.findByPrimaryKey( f.getIdField( ) ) );
            }

            String valueExport = EntryTypeServiceManager.getEntryTypeService( e ).getResponseValueForExport( e, null, resp, locale );
            if ( StringUtils.isNotEmpty( valueExport ) )
            {
                strValue.append( strPrefix + valueExport );
                strPrefix = CONSTANT_COMMA;
            }
        }
        return strValue.toString( );
    }

    public static ReferenceList getDefaultColumnList( Locale locale )
    {
        ReferenceList refList = new ReferenceList( );
        for ( String key : DEFAULT_COLUMN_LIST )
        {
            refList.addItem( key, I18nService.getLocalizedString( key, locale ) );
        }
        return refList;
    }

    public static ReferenceList getCustomColumnList( String strIdForm )
    {
        EntryFilter entryFilter = new EntryFilter( );
        entryFilter.setIdResource( Integer.valueOf( strIdForm ) );
        List<Entry> listEntry = EntryHome.getEntryList( entryFilter );

        ReferenceList refList = new ReferenceList( );
        for ( Entry entry : listEntry )
        {
            if ( !( EntryTypeServiceManager.getEntryTypeService( entry ) instanceof EntryTypeGroup ) )
            {
                refList.addItem( String.valueOf( entry.getIdEntry( ) ), entry.getTitle( ) );
            }
        }
        return refList;
    }
}