/**
* 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.model.modification;
import alfio.model.Event;
import alfio.model.EventDescription;
import alfio.model.EventHiddenFieldContainer;
import alfio.model.PriceContainer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.experimental.Delegate;
import org.apache.commons.lang3.builder.CompareToBuilder;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Getter
public class EventWithStatistics implements StatisticsContainer, Comparable<EventWithStatistics>, PriceContainer {
public static final DateTimeFormatter JSON_DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm");
public static final Predicate<TicketCategoryWithStatistic> IS_BOUNDED = TicketCategoryWithStatistic::isBounded;
@Delegate(excludes = {EventHiddenFieldContainer.class, PriceContainer.class})
@JsonIgnore
private final Event event;
private final List<TicketCategoryWithStatistic> ticketCategories;
private final Map<String, String> description;
private final int soldTickets;
private final int checkedInTickets;
private final int allocatedTickets;
private final int releasedTickets;
private final boolean containsUnboundedCategories;
public EventWithStatistics(Event event,
List<EventDescription> eventDescriptions,
List<TicketCategoryWithStatistic> ticketCategories,
int releasedTickets) {
this.event = event;
this.ticketCategories = ticketCategories;
this.soldTickets = countSoldTickets(ticketCategories);
this.checkedInTickets = countCheckedInTickets(ticketCategories);
this.allocatedTickets = ticketCategories.stream().filter(IS_BOUNDED).mapToInt(TicketCategoryWithStatistic::getMaxTickets).sum();
this.releasedTickets = releasedTickets;
this.containsUnboundedCategories = ticketCategories.stream().anyMatch(IS_BOUNDED.negate());
this.description = eventDescriptions.stream().collect(Collectors.toMap(EventDescription::getLocale, EventDescription::getDescription));
}
public boolean isWarningNeeded() {
return !isExpired() && (containsOrphanTickets() || containsStuckReservations());
}
public String getFormattedBegin() {
return getBegin().format(JSON_DATE_FORMATTER);
}
public String getFormattedEnd() {
return getEnd().format(JSON_DATE_FORMATTER);
}
boolean containsOrphanTickets() {
return ticketCategories.stream().anyMatch(TicketCategoryWithStatistic::isContainingOrphans);
}
boolean containsStuckReservations() {
return ticketCategories.stream().anyMatch(TicketCategoryWithStatistic::isContainingStuckTickets);
}
public boolean isAddCategoryEnabled() {
return ticketCategories.stream()
.mapToInt(TicketCategoryWithStatistic::getMaxTickets)
.sum() < getAvailableSeats();
}
@Override
public int getNotAllocatedTickets() {
return containsUnboundedCategories ? 0 : countNotAllocatedTickets();
}
@Override
public int getPendingTickets() {
return getTicketCategories().stream().mapToInt(TicketCategoryWithStatistic::getPendingTickets).sum();
}
@Override
public int getDynamicAllocation() {
if(containsUnboundedCategories) {
List<TicketCategoryWithStatistic> unboundedCategories = ticketCategories.stream().filter(IS_BOUNDED.negate()).collect(Collectors.toList());
return countNotAllocatedTickets() - countSoldTickets(unboundedCategories) - countCheckedInTickets(unboundedCategories) - countPendingTickets(unboundedCategories) - releasedTickets;
}
return 0;
}
private int countNotAllocatedTickets() {
return event.getAvailableSeats() - allocatedTickets;
}
private int countPendingTickets(List<TicketCategoryWithStatistic> unboundedCategories) {
return unboundedCategories.stream().mapToInt(TicketCategoryWithStatistic::getPendingTickets).sum();
}
@Override
public int getNotSoldTickets() {
int pendingTickets = getPendingTickets();
if(containsUnboundedCategories) {
List<TicketCategoryWithStatistic> boundedCategories = ticketCategories.stream().filter(IS_BOUNDED).collect(Collectors.toList());
return allocatedTickets - countSoldTickets(boundedCategories) - countCheckedInTickets(boundedCategories) - pendingTickets;
}
return allocatedTickets - soldTickets - checkedInTickets - pendingTickets;
}
public boolean isExpired() {
return event.expired();
}
@Override
public int compareTo(EventWithStatistics o) {
CompareToBuilder builder = new CompareToBuilder();
return builder.append(isExpired(), o.isExpired()).append(getBegin().withZoneSameInstant(ZoneId.systemDefault()), o.getBegin().withZoneSameInstant(ZoneId.systemDefault())).build();
}
private int countCheckedInTickets(List<TicketCategoryWithStatistic> ticketCategories) {
return ticketCategories.stream().mapToInt(TicketCategoryWithStatistic::getCheckedInTickets).sum();
}
private static int countSoldTickets(List<TicketCategoryWithStatistic> ticketCategories) {
return ticketCategories.stream().mapToInt(TicketCategoryWithStatistic::getSoldTickets).sum();
}
@Override
@JsonIgnore
public int getSrcPriceCts() {
return event.getSrcPriceCts();
}
public BigDecimal getGrossIncome() {
return getTicketCategories().stream()
.flatMap(tc -> tc.getTickets().stream())
.map(TicketWithStatistic::getFinalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public String getCurrencyCode() {
return getCurrency();
}
@Override
@JsonIgnore
public Optional<BigDecimal> getOptionalVatPercentage() {
return getVatStatus() != VatStatus.NONE ? Optional.ofNullable(event.getVat()) : Optional.empty();
}
public BigDecimal getVatPercentage() {
return getVatPercentageOrZero();
}
public BigDecimal getVat() {
return getVAT();
}
@Override
public VatStatus getVatStatus() {
return event.getVatStatus();
}
}