alfio.controller.api.support.TicketHelper.java Source code

Java tutorial

Introduction

Here is the source code for alfio.controller.api.support.TicketHelper.java

Source

/**
 * This file is part of alf.io.
 *
 * alf.io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alf.io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with alf.io.  If not, see <http://www.gnu.org/licenses/>.
 */
package alfio.controller.api.support;

import alfio.controller.form.UpdateTicketOwnerForm;
import alfio.controller.support.TemplateProcessor;
import alfio.manager.FileUploadManager;
import alfio.manager.TicketReservationManager;
import alfio.manager.support.PartialTicketTextGenerator;
import alfio.model.*;
import alfio.model.result.ValidationResult;
import alfio.model.user.Organization;
import alfio.repository.AdditionalServiceItemRepository;
import alfio.repository.TicketCategoryRepository;
import alfio.repository.TicketFieldRepository;
import alfio.repository.TicketRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.util.LocaleUtil;
import alfio.util.TemplateManager;
import alfio.util.Validator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static alfio.model.TicketFieldConfiguration.Context.ATTENDEE;

@Component
public class TicketHelper {

    private static Set<TicketReservation.TicketReservationStatus> PENDING_RESERVATION_STATUSES = EnumSet.of(
            TicketReservation.TicketReservationStatus.PENDING,
            TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT);

    private final TicketReservationManager ticketReservationManager;
    private final OrganizationRepository organizationRepository;
    private final TicketCategoryRepository ticketCategoryRepository;
    private final TicketRepository ticketRepository;
    private final TemplateManager templateManager;
    private final FileUploadManager fileUploadManager;
    private final TicketFieldRepository ticketFieldRepository;
    private final AdditionalServiceItemRepository additionalServiceItemRepository;

    @Autowired
    public TicketHelper(TicketReservationManager ticketReservationManager,
            OrganizationRepository organizationRepository, TicketCategoryRepository ticketCategoryRepository,
            TicketRepository ticketRepository, TemplateManager templateManager, FileUploadManager fileUploadManager,
            TicketFieldRepository ticketFieldRepository,
            AdditionalServiceItemRepository additionalServiceItemRepository) {
        this.ticketReservationManager = ticketReservationManager;
        this.organizationRepository = organizationRepository;
        this.ticketCategoryRepository = ticketCategoryRepository;
        this.ticketRepository = ticketRepository;
        this.templateManager = templateManager;
        this.fileUploadManager = fileUploadManager;
        this.ticketFieldRepository = ticketFieldRepository;
        this.additionalServiceItemRepository = additionalServiceItemRepository;
    }

    public List<TicketFieldConfigurationDescriptionAndValue> findTicketFieldConfigurationAndValue(int eventId,
            Ticket ticket, Locale locale) {

        List<Ticket> ticketsInReservation = ticketRepository
                .findTicketsInReservation(ticket.getTicketsReservationId());
        //WORKAROUND: we only add the additionalServiceItems related fields only if it's the _first_ ticket of the reservation
        boolean isFirstTicket = ticketsInReservation.get(0).getId() == ticket.getId();

        Map<Integer, TicketFieldDescription> descriptions = ticketFieldRepository.findTranslationsFor(locale,
                eventId);
        Map<String, TicketFieldValue> values = ticketFieldRepository.findAllByTicketIdGroupedByName(ticket.getId());
        Function<TicketFieldConfiguration, String> extractor = (f) -> Optional.ofNullable(values.get(f.getName()))
                .map(TicketFieldValue::getValue).orElse("");
        List<AdditionalServiceItem> additionalServiceItems = isFirstTicket
                ? additionalServiceItemRepository.findByReservationUuid(ticket.getTicketsReservationId())
                : Collections.emptyList();
        Set<Integer> additionalServiceIds = additionalServiceItems.stream()
                .map(AdditionalServiceItem::getAdditionalServiceId).collect(Collectors.toSet());
        return ticketFieldRepository
                .findAdditionalFieldsForEvent(eventId).stream().filter(f -> f.getContext() == ATTENDEE || Optional
                        .ofNullable(f.getAdditionalServiceId()).filter(additionalServiceIds::contains).isPresent())
                .map(f -> {
                    int count = Math
                            .max(1, Optional
                                    .ofNullable(f.getAdditionalServiceId()).map(id -> (int) additionalServiceItems
                                            .stream().filter(i -> i.getAdditionalServiceId() == id).count())
                                    .orElse(1));
                    return new TicketFieldConfigurationDescriptionAndValue(f,
                            descriptions.getOrDefault(f.getId(), TicketFieldDescription.MISSING_FIELD), count,
                            extractor.apply(f));
                }).collect(Collectors.toList());
    }

    public Optional<Triple<ValidationResult, Event, Ticket>> assignTicket(String eventName, String ticketIdentifier,
            UpdateTicketOwnerForm updateTicketOwner, Optional<Errors> bindingResult, HttpServletRequest request,
            Consumer<Triple<ValidationResult, Event, Ticket>> reservationConsumer,
            Optional<UserDetails> userDetails) {

        Optional<Triple<ValidationResult, Event, Ticket>> triple = ticketReservationManager
                .fetchComplete(eventName, ticketIdentifier)
                .map(result -> assignTicket(updateTicketOwner, bindingResult, request, userDetails, result));
        triple.ifPresent(reservationConsumer);
        return triple;
    }

    private Triple<ValidationResult, Event, Ticket> assignTicket(UpdateTicketOwnerForm updateTicketOwner,
            Optional<Errors> bindingResult, HttpServletRequest request, Optional<UserDetails> userDetails,
            Triple<Event, TicketReservation, Ticket> result) {
        Ticket t = result.getRight();
        final Event event = result.getLeft();
        if (t.getLockedAssignment()) {
            //in case of locked assignment, fullName and Email will be overwritten
            updateTicketOwner.setFirstName(t.getFirstName());
            updateTicketOwner.setLastName(t.getLastName());
            updateTicketOwner.setFullName(t.getFullName());
            updateTicketOwner.setEmail(t.getEmail());
        }

        final TicketReservation ticketReservation = result.getMiddle();
        List<TicketFieldConfiguration> fieldConf = ticketFieldRepository
                .findAdditionalFieldsForEvent(event.getId());
        ValidationResult validationResult = Validator
                .validateTicketAssignment(updateTicketOwner, fieldConf, bindingResult, event)
                .ifSuccess(() -> updateTicketOwner(updateTicketOwner, request, t, event, ticketReservation,
                        userDetails));
        return Triple.of(validationResult, event, ticketRepository.findByUUID(t.getUuid()));
    }

    /**
     * This method has been implemented explicitly for PayPal, since we need to pre-assign tickets before payment, in order to keep the data inserted by the customer
     */
    public Optional<Triple<ValidationResult, Event, Ticket>> preAssignTicket(String eventName, String reservationId,
            String ticketIdentifier, UpdateTicketOwnerForm updateTicketOwner, Optional<Errors> bindingResult,
            HttpServletRequest request, Consumer<Triple<ValidationResult, Event, Ticket>> reservationConsumer,
            Optional<UserDetails> userDetails) {

        Optional<Triple<ValidationResult, Event, Ticket>> triple = ticketReservationManager
                .from(eventName, reservationId, ticketIdentifier)
                .filter(temp -> PENDING_RESERVATION_STATUSES.contains(temp.getMiddle().getStatus())
                        && temp.getRight().getStatus() == Ticket.TicketStatus.PENDING)
                .map(result -> assignTicket(updateTicketOwner, bindingResult, request, userDetails, result));
        triple.ifPresent(reservationConsumer);
        return triple;
    }

    public Optional<Triple<ValidationResult, Event, Ticket>> assignTicket(String eventName, String reservationId,
            String ticketIdentifier, UpdateTicketOwnerForm updateTicketOwner, Optional<Errors> bindingResult,
            HttpServletRequest request, Model model) {
        return assignTicket(eventName, ticketIdentifier, updateTicketOwner, bindingResult, request, t -> {
            model.addAttribute("value", t.getRight());
            model.addAttribute("validationResult", t.getLeft());
            model.addAttribute("countries", getLocalizedCountries(RequestContextUtils.getLocale(request)));
            model.addAttribute("event", t.getMiddle());
        }, Optional.empty());
    }

    public Optional<Triple<ValidationResult, Event, Ticket>> directTicketAssignment(String eventName,
            String reservationId, String email, String fullName, String firstName, String lastName,
            String userLanguage, Optional<Errors> bindingResult, HttpServletRequest request, Model model) {
        List<Ticket> tickets = ticketReservationManager.findTicketsInReservation(reservationId);
        if (tickets.size() > 1) {
            return Optional.empty();
        }
        String ticketUuid = tickets.get(0).getUuid();
        UpdateTicketOwnerForm form = new UpdateTicketOwnerForm();
        form.setAdditional(Collections.emptyMap());
        form.setEmail(email);
        form.setFullName(fullName);
        form.setFirstName(firstName);
        form.setLastName(lastName);
        form.setUserLanguage(userLanguage);
        return assignTicket(eventName, reservationId, ticketUuid, form, bindingResult, request, model);
    }

    public static List<Pair<String, String>> getLocalizedCountries(Locale locale) {
        return mapISOCountries(Stream.of(Locale.getISOCountries()), locale);
    }

    public static List<Pair<String, String>> getLocalizedEUCountries(Locale locale, String euCountries) {
        return mapISOCountries(
                Stream.of(Locale.getISOCountries()).filter(isoCode -> StringUtils.contains(euCountries, isoCode)),
                locale);
    }

    private static List<Pair<String, String>> mapISOCountries(Stream<String> isoCountries, Locale locale) {
        return isoCountries.map(isoCode -> Pair.of(isoCode, new Locale("", isoCode).getDisplayCountry(locale)))
                .sorted(Comparator.comparing(Pair::getRight)).collect(Collectors.toList());
    }

    private void updateTicketOwner(UpdateTicketOwnerForm updateTicketOwner, HttpServletRequest request, Ticket t,
            Event event, TicketReservation ticketReservation, Optional<UserDetails> userDetails) {
        Locale language = Optional.ofNullable(updateTicketOwner.getUserLanguage()).filter(StringUtils::isNotBlank)
                .map(Locale::forLanguageTag).orElseGet(() -> RequestContextUtils.getLocale(request));
        TicketCategory category = ticketCategoryRepository.getById(t.getCategoryId());
        ticketReservationManager.updateTicketOwner(t, language, event, updateTicketOwner,
                getConfirmationTextBuilder(request, event, ticketReservation, t, category),
                getOwnerChangeTextBuilder(request, t, event), userDetails);
    }

    private PartialTicketTextGenerator getOwnerChangeTextBuilder(HttpServletRequest request, Ticket t,
            Event event) {
        Organization organization = organizationRepository.getById(event.getOrganizationId());
        String ticketUrl = ticketReservationManager.ticketUpdateUrl(event, t.getUuid());
        return TemplateProcessor.buildEmailForOwnerChange(event, t, organization, ticketUrl, templateManager,
                LocaleUtil.getTicketLanguage(t, request));
    }

    private PartialTicketTextGenerator getConfirmationTextBuilder(HttpServletRequest request, Event event,
            TicketReservation ticketReservation, Ticket ticket, TicketCategory ticketCategory) {
        Organization organization = organizationRepository.getById(event.getOrganizationId());
        String ticketUrl = ticketReservationManager.ticketUpdateUrl(event, ticket.getUuid());
        return TemplateProcessor.buildPartialEmail(event, organization, ticketReservation, ticketCategory,
                templateManager, ticketUrl, request);
    }

}