alfio.controller.EventController.java Source code

Java tutorial

Introduction

Here is the source code for alfio.controller.EventController.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;

import alfio.controller.decorator.EventDescriptor;
import alfio.controller.decorator.SaleableAdditionalService;
import alfio.controller.decorator.SaleableTicketCategory;
import alfio.controller.form.ReservationForm;
import alfio.controller.support.SessionUtil;
import alfio.manager.EventManager;
import alfio.manager.EventStatisticsManager;
import alfio.manager.TicketReservationManager;
import alfio.manager.i18n.I18nManager;
import alfio.manager.system.ConfigurationManager;
import alfio.model.*;
import alfio.model.modification.TicketReservationModification;
import alfio.model.modification.support.LocationDescriptor;
import alfio.model.result.ValidationResult;
import alfio.model.system.Configuration;
import alfio.model.system.ConfigurationKeys;
import alfio.model.transaction.PaymentProxy;
import alfio.repository.*;
import alfio.repository.user.OrganizationRepository;
import alfio.util.ErrorsCode;
import alfio.util.EventUtil;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static alfio.controller.support.SessionUtil.addToFlash;
import static alfio.util.OptionalWrapper.optionally;

@Controller
@AllArgsConstructor
public class EventController {

    private static final String REDIRECT = "redirect:";
    private final EventRepository eventRepository;
    private final EventDescriptionRepository eventDescriptionRepository;
    private final I18nManager i18nManager;
    private final TicketCategoryRepository ticketCategoryRepository;
    private final TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository;
    private final ConfigurationManager configurationManager;
    private final OrganizationRepository organizationRepository;
    private final SpecialPriceRepository specialPriceRepository;
    private final PromoCodeDiscountRepository promoCodeRepository;
    private final EventManager eventManager;
    private final TicketReservationManager ticketReservationManager;
    private final EventStatisticsManager eventStatisticsManager;
    private final AdditionalServiceRepository additionalServiceRepository;
    private final AdditionalServiceTextRepository additionalServiceTextRepository;
    private final TicketRepository ticketRepository;

    @RequestMapping(value = "/", method = RequestMethod.HEAD)
    public ResponseEntity<String> replyToProxy() {
        return ResponseEntity.ok("Up and running!");
    }

    @RequestMapping(value = "/healthz", method = RequestMethod.GET)
    public ResponseEntity<String> replyToK8s() {
        return ResponseEntity.ok("Up and running!");
    }

    @RequestMapping(value = { "/" }, method = RequestMethod.GET)
    public String listEvents(Model model, Locale locale) {
        List<Event> events = eventManager.getPublishedEvents();
        if (events.size() == 1) {
            return REDIRECT + "/event/" + events.get(0).getShortName() + "/";
        } else {
            model.addAttribute("events", events.stream().map(e -> {
                String eventDescription = eventDescriptionRepository
                        .findDescriptionByEventIdTypeAndLocale(e.getId(),
                                EventDescription.EventDescriptionType.DESCRIPTION, locale.getLanguage())
                        .orElse("");
                return new EventDescriptor(e, eventDescription);
            }).collect(Collectors.toList()));
            model.addAttribute("pageTitle", "event-list.header.title");
            model.addAttribute("event", null);

            model.addAttribute("showAvailableLanguagesInPageTop", true);
            model.addAttribute("availableLanguages", i18nManager.getSupportedLanguages());
            return "/event/event-list";
        }
    }

    @RequestMapping("/session-expired")
    public String sessionExpired(Model model) {
        model.addAttribute("pageTitle", "session-expired.header.title");
        model.addAttribute("event", null);
        return "/event/session-expired";
    }

    @RequestMapping(value = "/event/{eventName}/promoCode/{promoCode}", method = RequestMethod.POST)
    @ResponseBody
    public ValidationResult savePromoCode(@PathVariable("eventName") String eventName,
            @PathVariable("promoCode") String promoCode, Model model, HttpServletRequest request) {

        SessionUtil.removeSpecialPriceData(request);

        Optional<Event> optional = eventRepository.findOptionalByShortName(eventName);
        if (!optional.isPresent()) {
            return ValidationResult.failed(new ValidationResult.ErrorDescriptor("event", ""));
        }
        Event event = optional.get();
        ZonedDateTime now = ZonedDateTime.now(event.getZoneId());
        Optional<String> maybeSpecialCode = Optional.ofNullable(StringUtils.trimToNull(promoCode));
        Optional<SpecialPrice> specialCode = maybeSpecialCode
                .flatMap((trimmedCode) -> specialPriceRepository.getByCode(trimmedCode));
        Optional<PromoCodeDiscount> promotionCodeDiscount = maybeSpecialCode
                .flatMap((trimmedCode) -> promoCodeRepository.findPromoCodeInEventOrOrganization(event.getId(),
                        trimmedCode));

        if (specialCode.isPresent()) {
            if (!optionally(() -> eventManager.getTicketCategoryById(specialCode.get().getTicketCategoryId(),
                    event.getId())).isPresent()) {
                return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", ""));
            }

            if (specialCode.get().getStatus() != SpecialPrice.Status.FREE) {
                return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", ""));
            }

        } else if (promotionCodeDiscount.isPresent()
                && !promotionCodeDiscount.get().isCurrentlyValid(event.getZoneId(), now)) {
            return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", ""));
        } else if (!specialCode.isPresent() && !promotionCodeDiscount.isPresent()) {
            return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", ""));
        }

        if (maybeSpecialCode.isPresent() && !model.asMap().containsKey("hasErrors")) {
            if (specialCode.isPresent()) {
                SessionUtil.saveSpecialPriceCode(maybeSpecialCode.get(), request);
            } else if (promotionCodeDiscount.isPresent()) {
                SessionUtil.savePromotionCodeDiscount(maybeSpecialCode.get(), request);
            }
            return ValidationResult.success();
        }
        return ValidationResult.failed(new ValidationResult.ErrorDescriptor("promoCode", ""));
    }

    @RequestMapping(value = "/event/{eventName}", method = { RequestMethod.GET, RequestMethod.HEAD })
    public String showEvent(@PathVariable("eventName") String eventName, Model model, HttpServletRequest request,
            Locale locale) {

        return eventRepository.findOptionalByShortName(eventName)
                .filter(e -> e.getStatus() != Event.Status.DISABLED).map(event -> {
                    Optional<String> maybeSpecialCode = SessionUtil.retrieveSpecialPriceCode(request);
                    Optional<SpecialPrice> specialCode = maybeSpecialCode
                            .flatMap((trimmedCode) -> specialPriceRepository.getByCode(trimmedCode));

                    Optional<PromoCodeDiscount> promoCodeDiscount = SessionUtil
                            .retrievePromotionCodeDiscount(request).flatMap((code) -> promoCodeRepository
                                    .findPromoCodeInEventOrOrganization(event.getId(), code));

                    final ZonedDateTime now = ZonedDateTime.now(event.getZoneId());
                    //hide access restricted ticket categories
                    List<TicketCategory> ticketCategories = ticketCategoryRepository
                            .findAllTicketCategories(event.getId());
                    Map<Integer, String> categoriesDescription = ticketCategoryDescriptionRepository
                            .descriptionsByTicketCategory(ticketCategories.stream().map(TicketCategory::getId)
                                    .collect(Collectors.toList()), locale.getLanguage());

                    List<SaleableTicketCategory> saleableTicketCategories = ticketCategories.stream()
                            .filter((c) -> !c.isAccessRestricted() || (specialCode
                                    .filter(sc -> sc.getTicketCategoryId() == c.getId()).isPresent()))
                            .map((m) -> new SaleableTicketCategory(m,
                                    categoriesDescription.getOrDefault(m.getId(), ""), now, event,
                                    ticketReservationManager.countAvailableTickets(event, m),
                                    configurationManager.getIntConfigValue(
                                            Configuration.from(event.getOrganizationId(), event.getId(), m.getId(),
                                                    ConfigurationKeys.MAX_AMOUNT_OF_TICKETS_BY_RESERVATION),
                                            5),
                                    promoCodeDiscount.filter(promoCode -> shouldApplyDiscount(promoCode, m))
                                            .orElse(null)))
                            .collect(Collectors.toList());
                    //

                    final int orgId = event.getOrganizationId();
                    final int eventId = event.getId();
                    Map<ConfigurationKeys, Optional<String>> geoInfoConfiguration = configurationManager
                            .getStringConfigValueFrom(
                                    Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_PROVIDER),
                                    Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_CLIENT_API_KEY),
                                    Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_HERE_APP_ID),
                                    Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_HERE_APP_CODE));

                    LocationDescriptor ld = LocationDescriptor.fromGeoData(event.getLatLong(),
                            TimeZone.getTimeZone(event.getTimeZone()), geoInfoConfiguration);

                    final boolean hasAccessPromotions = ticketCategoryRepository
                            .countAccessRestrictedRepositoryByEventId(event.getId()) > 0
                            || promoCodeRepository.countByEventAndOrganizationId(event.getId(),
                                    event.getOrganizationId()) > 0;

                    String eventDescription = eventDescriptionRepository
                            .findDescriptionByEventIdTypeAndLocale(event.getId(),
                                    EventDescription.EventDescriptionType.DESCRIPTION, locale.getLanguage())
                            .orElse("");

                    final EventDescriptor eventDescriptor = new EventDescriptor(event, eventDescription);
                    List<SaleableTicketCategory> expiredCategories = saleableTicketCategories.stream()
                            .filter(SaleableTicketCategory::getExpired).collect(Collectors.toList());
                    List<SaleableTicketCategory> validCategories = saleableTicketCategories.stream()
                            .filter(tc -> !tc.getExpired()).collect(Collectors.toList());
                    List<SaleableAdditionalService> additionalServices = additionalServiceRepository
                            .loadAllForEvent(event.getId()).stream().map((as) -> getSaleableAdditionalService(event,
                                    locale, as, promoCodeDiscount.orElse(null)))
                            .collect(Collectors.toList());
                    Predicate<SaleableTicketCategory> waitingQueueTargetCategory = tc -> !tc.getExpired()
                            && !tc.isBounded();
                    boolean validPaymentConfigured = isEventHasValidPaymentConfigurations(event,
                            configurationManager);

                    List<SaleableAdditionalService> notExpiredServices = additionalServices.stream()
                            .filter(SaleableAdditionalService::isNotExpired).collect(Collectors.toList());

                    List<SaleableAdditionalService> supplements = adjustIndex(0,
                            notExpiredServices.stream()
                                    .filter(a -> a.getType() == AdditionalService.AdditionalServiceType.SUPPLEMENT)
                                    .collect(Collectors.toList()));
                    List<SaleableAdditionalService> donations = adjustIndex(supplements.size(),
                            notExpiredServices.stream()
                                    .filter(a -> a.getType() == AdditionalService.AdditionalServiceType.DONATION)
                                    .collect(Collectors.toList()));

                    model.addAttribute("event", eventDescriptor)//
                            .addAttribute("organization", organizationRepository.getById(event.getOrganizationId()))
                            .addAttribute("ticketCategories", validCategories)//
                            .addAttribute("expiredCategories", expiredCategories)//
                            .addAttribute("containsExpiredCategories", !expiredCategories.isEmpty())//
                            .addAttribute("showNoCategoriesWarning", validCategories.isEmpty())
                            .addAttribute("hasAccessPromotions", hasAccessPromotions)
                            .addAttribute("promoCode", specialCode.map(SpecialPrice::getCode).orElse(null))
                            .addAttribute("locationDescriptor", ld)
                            .addAttribute("pageTitle", "show-event.header.title")
                            .addAttribute("hasPromoCodeDiscount", promoCodeDiscount.isPresent())
                            .addAttribute("promoCodeDiscount", promoCodeDiscount.orElse(null))
                            .addAttribute("displayWaitingQueueForm",
                                    EventUtil.displayWaitingQueueForm(event, saleableTicketCategories,
                                            configurationManager, eventStatisticsManager.noSeatsAvailable()))
                            .addAttribute("displayCategorySelectionForWaitingQueue",
                                    saleableTicketCategories.stream().filter(waitingQueueTargetCategory)
                                            .count() > 1)
                            .addAttribute("unboundedCategories",
                                    saleableTicketCategories.stream().filter(waitingQueueTargetCategory)
                                            .collect(Collectors.toList()))
                            .addAttribute("preSales", EventUtil.isPreSales(event, saleableTicketCategories))
                            .addAttribute("userLanguage", locale.getLanguage())
                            .addAttribute("showAdditionalServices", !notExpiredServices.isEmpty())
                            .addAttribute("showAdditionalServicesDonations", !donations.isEmpty())
                            .addAttribute("showAdditionalServicesSupplements", !supplements.isEmpty())
                            .addAttribute("enabledAdditionalServicesDonations", donations)
                            .addAttribute("enabledAdditionalServicesSupplements", supplements)
                            .addAttribute("forwardButtonDisabled",
                                    (saleableTicketCategories.stream()
                                            .noneMatch(SaleableTicketCategory::getSaleable))
                                            || !validPaymentConfigured)
                            .addAttribute("useFirstAndLastName", event.mustUseFirstAndLastName())
                            .addAttribute("validPaymentMethodAvailable", validPaymentConfigured)
                            .addAttribute("validityStart", event.getBegin())
                            .addAttribute("validityEnd", event.getEnd());

                    model.asMap().putIfAbsent("hasErrors", false);//
                    return "/event/show-event";
                }).orElse(REDIRECT + "/");
    }

    enum CodeType {
        SPECIAL_PRICE, PROMO_CODE_DISCOUNT, TICKET_CATEGORY_CODE, NOT_FOUND
    }

    //not happy with that code...
    private CodeType getCodeType(int eventId, String code) {
        String trimmedCode = StringUtils.trimToNull(code);
        if (trimmedCode == null) {
            return CodeType.NOT_FOUND;
        } else if (specialPriceRepository.getByCode(trimmedCode).isPresent()) {
            return CodeType.SPECIAL_PRICE;
        } else if (promoCodeRepository.findPromoCodeInEventOrOrganization(eventId, trimmedCode).isPresent()) {
            return CodeType.PROMO_CODE_DISCOUNT;
        } else if (ticketCategoryRepository.findCodeInEvent(eventId, trimmedCode).isPresent()) {
            return CodeType.TICKET_CATEGORY_CODE;
        } else {
            return CodeType.NOT_FOUND;
        }
    }

    @RequestMapping(value = "/event/{eventName}/code/{code}", method = { RequestMethod.GET, RequestMethod.HEAD })
    public String handleCode(@PathVariable("eventName") String eventName, @PathVariable("code") String code,
            Model model, ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale) {
        String trimmedCode = StringUtils.trimToNull(code);
        return eventRepository.findOptionalByShortName(eventName).map(event -> {
            String redirectToEvent = "redirect:/event/" + eventName + "/";
            ValidationResult res = savePromoCode(eventName, trimmedCode, model, request.getRequest());
            CodeType codeType = getCodeType(event.getId(), trimmedCode);
            if (res.isSuccess() && codeType == CodeType.PROMO_CODE_DISCOUNT) {
                return redirectToEvent;
            } else if (codeType == CodeType.TICKET_CATEGORY_CODE) {
                TicketCategory category = ticketCategoryRepository.findCodeInEvent(event.getId(), trimmedCode)
                        .get();
                if (!category.isAccessRestricted()) {
                    return makeSimpleReservation(eventName, request, redirectAttributes, locale, null, event,
                            category.getId());
                } else {
                    Optional<SpecialPrice> specialPrice = specialPriceRepository
                            .findActiveNotAssignedByCategoryId(category.getId()).stream().findFirst();
                    if (!specialPrice.isPresent()) {
                        return redirectToEvent;
                    }
                    savePromoCode(eventName, specialPrice.get().getCode(), model, request.getRequest());
                    return makeSimpleReservation(eventName, request, redirectAttributes, locale,
                            specialPrice.get().getCode(), event, category.getId());
                }
            } else if (res.isSuccess() && codeType == CodeType.SPECIAL_PRICE) {
                int ticketCategoryId = specialPriceRepository.getByCode(trimmedCode).get().getTicketCategoryId();
                return makeSimpleReservation(eventName, request, redirectAttributes, locale, trimmedCode, event,
                        ticketCategoryId);
            } else {
                return redirectToEvent;
            }
        }).orElse("redirect:/");
    }

    private String makeSimpleReservation(String eventName, ServletWebRequest request,
            RedirectAttributes redirectAttributes, Locale locale, String trimmedCode, Event event,
            int ticketCategoryId) {
        ReservationForm form = new ReservationForm();
        form.setPromoCode(trimmedCode);
        TicketReservationModification reservation = new TicketReservationModification();
        reservation.setAmount(1);
        reservation.setTicketCategoryId(ticketCategoryId);
        form.setReservation(Collections.singletonList(reservation));
        return validateAndReserve(eventName, form, new BeanPropertyBindingResult(form, "reservationForm"), request,
                redirectAttributes, locale, event);
    }

    @RequestMapping(value = "/event/{eventName}/calendar/locale/{locale}", method = { RequestMethod.GET,
            RequestMethod.HEAD })
    public void calendar(@PathVariable("eventName") String eventName, @PathVariable("locale") String locale,
            @RequestParam(value = "type", required = false) String calendarType,
            @RequestParam(value = "ticketId", required = false) String ticketId, HttpServletResponse response)
            throws IOException {
        Optional<Event> event = eventRepository.findOptionalByShortName(eventName);
        if (!event.isPresent()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        //meh
        Event ev = event.get();

        String description = eventDescriptionRepository.findDescriptionByEventIdTypeAndLocale(ev.getId(),
                EventDescription.EventDescriptionType.DESCRIPTION, locale).orElse("");
        TicketCategory category = ticketRepository.findOptionalByUUID(ticketId)
                .map(t -> ticketCategoryRepository.getById(t.getCategoryId())).orElse(null);

        if ("google".equals(calendarType)) {
            response.sendRedirect(EventUtil.getGoogleCalendarURL(ev, category, description));
        } else {
            Optional<byte[]> ical = EventUtil.getIcalForEvent(ev, category, description);
            //meh, checked exceptions don't work well with Function & co :(
            if (ical.isPresent()) {
                response.setContentType("text/calendar");
                response.setHeader("Content-Disposition", "inline; filename=\"calendar.ics\"");
                response.getOutputStream().write(ical.get());
            } else {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            }
        }
    }

    @RequestMapping(value = "/event/{eventName}/reserve-tickets", method = { RequestMethod.POST, RequestMethod.GET,
            RequestMethod.HEAD })
    public String reserveTicket(@PathVariable("eventName") String eventName,
            @ModelAttribute ReservationForm reservation, BindingResult bindingResult, Model model,
            ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale) {

        return eventRepository.findOptionalByShortName(eventName).map(event -> {
            if (request.getHttpMethod() == HttpMethod.GET) {
                return "redirect:/event/" + eventName + "/";
            } else {
                return validateAndReserve(eventName, reservation, bindingResult, request, redirectAttributes,
                        locale, event);
            }
        }).orElse("redirect:/");

    }

    private String validateAndReserve(String eventName, ReservationForm reservation, BindingResult bindingResult,
            ServletWebRequest request, RedirectAttributes redirectAttributes, Locale locale, Event event) {
        final String redirectToEvent = "redirect:/event/" + eventName + "/";
        return reservation
                .validate(bindingResult, ticketReservationManager, additionalServiceRepository, eventManager, event)
                .map(selected -> {

                    Date expiration = DateUtils.addMinutes(new Date(),
                            ticketReservationManager.getReservationTimeout(event));

                    try {
                        String reservationId = ticketReservationManager.createTicketReservation(event,
                                selected.getLeft(), selected.getRight(), expiration,
                                SessionUtil.retrieveSpecialPriceSessionId(request.getRequest()),
                                SessionUtil.retrievePromotionCodeDiscount(request.getRequest()), locale, false);
                        return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/book";
                    } catch (TicketReservationManager.NotEnoughTicketsException nete) {
                        bindingResult.reject(ErrorsCode.STEP_1_NOT_ENOUGH_TICKETS);
                        addToFlash(bindingResult, redirectAttributes);
                        return redirectToEvent;
                    } catch (TicketReservationManager.MissingSpecialPriceTokenException missing) {
                        bindingResult.reject(ErrorsCode.STEP_1_ACCESS_RESTRICTED);
                        addToFlash(bindingResult, redirectAttributes);
                        return redirectToEvent;
                    } catch (TicketReservationManager.InvalidSpecialPriceTokenException invalid) {
                        bindingResult.reject(ErrorsCode.STEP_1_CODE_NOT_FOUND);
                        addToFlash(bindingResult, redirectAttributes);
                        SessionUtil.removeSpecialPriceData(request.getRequest());
                        return redirectToEvent;
                    }
                }).orElseGet(() -> {
                    addToFlash(bindingResult, redirectAttributes);
                    return redirectToEvent;
                });
    }

    private SaleableAdditionalService getSaleableAdditionalService(Event event, Locale locale, AdditionalService as,
            PromoCodeDiscount promoCodeDiscount) {
        return new SaleableAdditionalService(event, as,
                additionalServiceTextRepository.findBestMatchByLocaleAndType(as.getId(), locale.getLanguage(),
                        AdditionalServiceText.TextType.TITLE).getValue(),
                additionalServiceTextRepository.findBestMatchByLocaleAndType(as.getId(), locale.getLanguage(),
                        AdditionalServiceText.TextType.DESCRIPTION).getValue(),
                promoCodeDiscount, 0);
    }

    private static List<SaleableAdditionalService> adjustIndex(int offset, List<SaleableAdditionalService> l) {
        List<SaleableAdditionalService> n = new ArrayList<>(l.size());

        for (int i = 0; i < l.size(); i++) {
            n.add(l.get(i).withIndex(i + offset));
        }
        return n;
    }

    private static boolean shouldApplyDiscount(PromoCodeDiscount promoCodeDiscount, TicketCategory ticketCategory) {
        return promoCodeDiscount.getCategories().isEmpty()
                || promoCodeDiscount.getCategories().contains(ticketCategory.getId());
    }

    private boolean isEventHasValidPaymentConfigurations(Event event, ConfigurationManager configurationManager) {
        if (event.isFreeOfCharge()) {
            return true;
        } else if (event.getAllowedPaymentProxies().size() == 0) {
            return false;
        } else {
            //Check whether event already started and it has only PaymentProxy.OFFLINE as payment method
            return !(event.getAllowedPaymentProxies().size() == 1
                    && event.getAllowedPaymentProxies().contains(PaymentProxy.OFFLINE)
                    && !TicketReservationManager.hasValidOfflinePaymentWaitingPeriod(event, configurationManager));
        }
    }

}