/**
* 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.manager;
import alfio.TestConfiguration;
import alfio.config.DataSourceConfiguration;
import alfio.config.Initializer;
import alfio.config.RepositoryConfiguration;
import alfio.manager.support.PaymentResult;
import alfio.manager.user.UserManager;
import alfio.model.*;
import alfio.model.modification.*;
import alfio.model.transaction.PaymentProxy;
import alfio.repository.TicketRepository;
import alfio.repository.system.ConfigurationRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.test.util.IntegrationTestUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import static alfio.test.util.IntegrationTestUtil.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RepositoryConfiguration.class, DataSourceConfiguration.class, TestConfiguration.class})
@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS})
@Transactional
public class TicketReservationManagerIntegrationTest {
private static final Map<String, String> DESCRIPTION = Collections.singletonMap("en", "desc");
@BeforeClass
public static void initEnv() {
initSystemProperties();
}
@Autowired
private EventManager eventManager;
@Autowired
private EventStatisticsManager eventStatisticsManager;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private UserManager userManager;
@Autowired
private TicketRepository ticketRepository;
@Autowired
private TicketReservationManager ticketReservationManager;
@Autowired
private ConfigurationRepository configurationRepository;
@Autowired
private WaitingQueueManager waitingQueueManager;
@Before
public void ensureConfiguration() {
IntegrationTestUtil.ensureMinimalConfiguration(configurationRepository);
}
@Test
public void testPriceIsOverridden() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
EventWithStatistics event = eventStatisticsManager.fillWithStatistics(initEvent(categories, organizationRepository, userManager, eventManager).getKey());
TicketReservationModification tr = new TicketReservationModification();
tr.setAmount(2);
TicketCategoryWithStatistic category = event.getTicketCategories().get(0);
tr.setTicketCategoryId(category.getId());
TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
List<Ticket> pendingTickets = ticketRepository.findPendingTicketsInCategories(Collections.singletonList(category.getId()));
assertEquals(2, pendingTickets.size());
pendingTickets.forEach(t -> assertEquals(1000, t.getFinalPriceCts()));
List<Ticket> tickets = ticketRepository.findFreeByEventId(event.getId());
assertEquals(18, tickets.size());
assertTrue(tickets.stream().allMatch(t -> t.getCategoryId() == null));
}
@Test
public void testTicketSelection() {
List<TicketCategoryModification> categories = Arrays.asList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false),
new TicketCategoryModification(null, "default", 10,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true));
EventWithStatistics event = eventStatisticsManager.fillWithStatistics(initEvent(categories, organizationRepository, userManager, eventManager).getKey());
TicketCategoryWithStatistic bounded = event.getTicketCategories().stream().filter(TicketCategoryWithStatistic::isBounded).findFirst().orElseThrow(IllegalStateException::new);
TicketCategoryWithStatistic unbounded = event.getTicketCategories().stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new);
TicketReservationModification tr = new TicketReservationModification();
tr.setAmount(10);
tr.setTicketCategoryId(bounded.getId());
TicketReservationModification tr2 = new TicketReservationModification();
tr2.setAmount(9);
tr2.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
TicketReservationWithOptionalCodeModification mod2 = new TicketReservationWithOptionalCodeModification(tr2, Optional.empty());
String reservationId = ticketReservationManager.createTicketReservation(event.getEvent(), Arrays.asList(mod, mod2), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
List<TicketReservation> reservations = ticketReservationManager.findAllReservationsInEvent(event.getId());
assertTrue(reservations.size() == 1);
assertEquals(reservationId, reservations.get(0).getId());
List<Ticket> pendingTickets = ticketRepository.findPendingTicketsInCategories(Arrays.asList(bounded.getId(), unbounded.getId()));
assertEquals(19, pendingTickets.size());
pendingTickets.forEach(t -> assertEquals(1000, t.getFinalPriceCts()));
List<Ticket> tickets = ticketRepository.findFreeByEventId(event.getId());
assertEquals(1, tickets.size());
assertTrue(tickets.stream().allMatch(t -> t.getCategoryId() == null));
TotalPrice totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId);
assertEquals(0, ticketReservationManager.getPendingPayments(event).size());
PaymentResult confirm = ticketReservationManager.confirm(null, null, event.getEvent(), reservationId, "email@example.com", new CustomerName("full name", "full", "name", event.getEvent()), Locale.ENGLISH, "billing address",
totalPrice, Optional.empty(), Optional.of(PaymentProxy.OFFLINE));
assertTrue(confirm.isSuccessful());
assertEquals(TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, ticketReservationManager.findById(reservationId).get().getStatus());
assertEquals(1, ticketReservationManager.getPendingPayments(event).size());
ticketReservationManager.validateAndConfirmOfflinePayment(reservationId, event.getEvent(), new BigDecimal("190.00"));
assertEquals(TicketReservation.TicketReservationStatus.COMPLETE, ticketReservationManager.findById(reservationId).get().getStatus());
//-------------------
TicketReservationModification trForDelete = new TicketReservationModification();
trForDelete.setAmount(1);
trForDelete.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification modForDelete = new TicketReservationWithOptionalCodeModification(trForDelete, Optional.empty());
String reservationId2 = ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(modForDelete), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
ticketReservationManager.confirm(null, null, event.getEvent(), reservationId2, "email@example.com", new CustomerName("full name", "full", "name", event.getEvent()), Locale.ENGLISH, "billing address",
totalPrice, Optional.empty(), Optional.of(PaymentProxy.OFFLINE));
assertTrue(ticketReservationManager.findById(reservationId2).isPresent());
ticketReservationManager.deleteOfflinePayment(event.getEvent(), reservationId2, false);
Assert.assertFalse(ticketReservationManager.findById(reservationId2).isPresent());
}
@Test
public void testTicketWithDiscount() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
EventWithStatistics event = eventStatisticsManager.fillWithStatistics(initEvent(categories, organizationRepository, userManager, eventManager).getKey());
TicketCategoryWithStatistic unbounded = event.getTicketCategories().stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new);
//promo code at event level
eventManager.addPromoCode("MYPROMOCODE", event.getId(), null, event.getBegin(), event.getEnd(), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null);
//promo code at organization level
eventManager.addPromoCode("MYFIXEDPROMO", null, event.getOrganizationId(), event.getBegin(), event.getEnd(), 5, PromoCodeDiscount.DiscountType.FIXED_AMOUNT, null);
TicketReservationModification tr = new TicketReservationModification();
tr.setAmount(3);
tr.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
String reservationId = ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.of("MYPROMOCODE"), Locale.ENGLISH, false);
TotalPrice totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId);
// 3 * 10 chf is the normal price, 10% discount -> 300 discount
Assert.assertEquals(2700, totalPrice.getPriceWithVAT());
Assert.assertEquals(27, totalPrice.getVAT());
Assert.assertEquals(-300, totalPrice.getDiscount());
Assert.assertEquals(1, totalPrice.getDiscountAppliedCount());
OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event.getEvent(), Locale.ENGLISH);
Assert.assertEquals("27.00", orderSummary.getTotalPrice());
Assert.assertEquals("0.27", orderSummary.getTotalVAT());
Assert.assertEquals(3, orderSummary.getTicketAmount());
TicketReservationModification trFixed = new TicketReservationModification();
trFixed.setAmount(3);
trFixed.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification modFixed = new TicketReservationWithOptionalCodeModification(trFixed, Optional.empty());
String reservationIdFixed = ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(modFixed), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.of("MYFIXEDPROMO"), Locale.ENGLISH, false);
TotalPrice totalPriceFixed = ticketReservationManager.totalReservationCostWithVAT(reservationIdFixed);
// 3 * 10 chf is the normal price, 3 * 5 is the discount
Assert.assertEquals(2985, totalPriceFixed.getPriceWithVAT());
Assert.assertEquals(30, totalPriceFixed.getVAT());
Assert.assertEquals(-15, totalPriceFixed.getDiscount());
Assert.assertEquals(3, totalPriceFixed.getDiscountAppliedCount());
OrderSummary orderSummaryFixed = ticketReservationManager.orderSummaryForReservationId(reservationIdFixed, event.getEvent(), Locale.ENGLISH);
Assert.assertEquals("29.85", orderSummaryFixed.getTotalPrice());
Assert.assertEquals("0.30", orderSummaryFixed.getTotalVAT());
Assert.assertEquals(3, orderSummaryFixed.getTicketAmount());
}
@Test(expected = TicketReservationManager.NotEnoughTicketsException.class)
public void testTicketSelectionNotEnoughTicketsAvailable() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
EventWithStatistics event = eventStatisticsManager.fillWithStatistics(initEvent(categories, organizationRepository, userManager, eventManager).getKey());
TicketCategoryWithStatistic unbounded = event.getTicketCategories().stream().filter(t -> !t.isBounded()).findFirst().orElseThrow(IllegalStateException::new);
TicketReservationModification tr = new TicketReservationModification();
tr.setAmount(AVAILABLE_SEATS + 1);
tr.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
}
@Test
public void testDeletePendingPaymentUnboundedCategory() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
EventWithStatistics event = eventStatisticsManager.fillWithStatistics(initEvent(categories, organizationRepository, userManager, eventManager).getKey());
TicketCategoryWithStatistic unbounded = event.getTicketCategories().get(0);
TicketReservationModification tr = new TicketReservationModification();
tr.setAmount(AVAILABLE_SEATS / 2 + 1);
tr.setTicketCategoryId(unbounded.getId());
TicketReservationWithOptionalCodeModification mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
String reservationId = ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId);
PaymentResult result = ticketReservationManager.confirm("", null, event.getEvent(), reservationId, "test@test.ch", new CustomerName("full name", "full", "name", event.getEvent()), Locale.ENGLISH, "", reservationCost, Optional.empty(), Optional.of(PaymentProxy.OFFLINE));
assertTrue(result.isSuccessful());
ticketReservationManager.deleteOfflinePayment(event.getEvent(), reservationId, false);
waitingQueueManager.distributeSeats(event.getEvent());
mod = new TicketReservationWithOptionalCodeModification(tr, Optional.empty());
reservationId = ticketReservationManager.createTicketReservation(event.getEvent(), Collections.singletonList(mod), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId);
result = ticketReservationManager.confirm("", null, event.getEvent(), reservationId, "test@test.ch", new CustomerName("full name", "full", "name", event.getEvent()), Locale.ENGLISH, "", reservationCost, Optional.empty(), Optional.of(PaymentProxy.OFFLINE));
assertTrue(result.isSuccessful());
}
}