Skip to content

Commit

Permalink
#426 add API for retrieving check-in stats
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone committed May 15, 2018
1 parent 7ec453b commit 7c325b7
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 20 deletions.
18 changes: 13 additions & 5 deletions src/main/java/alfio/controller/api/admin/CheckInApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import alfio.manager.CheckInManager;
import alfio.manager.EventManager;
import alfio.manager.support.CheckInStatistics;
import alfio.manager.support.TicketAndCheckInResult;
import alfio.manager.system.ConfigurationManager;
import alfio.model.Event;
Expand Down Expand Up @@ -86,8 +87,9 @@ public TicketAndCheckInResult checkIn(@PathVariable("eventName") String eventNam
@RequestBody TicketCode ticketCode,
@RequestParam(value = "offlineUser", required = false) String offlineUser,
Principal principal) {
String user = StringUtils.defaultIfBlank(offlineUser, principal.getName());
return checkInManager.checkIn(eventName, ticketIdentifier, Optional.ofNullable(ticketCode).map(TicketCode::getCode), user);
String username = principal.getName();
String auditUser = StringUtils.defaultIfBlank(offlineUser, username);
return checkInManager.checkIn(eventName, ticketIdentifier, Optional.ofNullable(ticketCode).map(TicketCode::getCode), username, auditUser);
}

@RequestMapping(value = "/check-in/{eventId}/ticket/{ticketIdentifier}/manual-check-in", method = POST)
Expand All @@ -112,8 +114,14 @@ public TicketAndCheckInResult confirmOnSitePayment(@PathVariable("eventName") St
@RequestBody TicketCode ticketCode,
@RequestParam(value = "offlineUser", required = false) String offlineUser,
Principal principal) {
String user = StringUtils.defaultIfBlank(offlineUser, principal.getName());
return checkInManager.confirmOnSitePayment(eventName, ticketIdentifier, Optional.ofNullable(ticketCode).map(TicketCode::getCode), user);
String username = principal.getName();
String auditUser = StringUtils.defaultIfBlank(offlineUser, username);
return checkInManager.confirmOnSitePayment(eventName, ticketIdentifier, Optional.ofNullable(ticketCode).map(TicketCode::getCode), username, auditUser);
}

@RequestMapping(value = "/check-in/event/{eventName}/statistics", method = GET)
public CheckInStatistics getStatistics(@PathVariable("eventName") String eventName, Principal principal) {
return checkInManager.getStatistics(eventName, principal.getName());
}

@RequestMapping(value = "/check-in/{eventId}/ticket/{ticketIdentifier}/confirm-on-site-payment", method = POST)
Expand All @@ -122,7 +130,7 @@ public OnSitePaymentConfirmation confirmOnSitePayment(@PathVariable("eventId") i
.map(s -> new OnSitePaymentConfirmation(true, "ok"))
.orElseGet(() -> new OnSitePaymentConfirmation(false, "Ticket with uuid " + ticketIdentifier + " not found"));
}

@RequestMapping(value = "/check-in/{eventId}/ticket-identifiers", method = GET)
public List<Integer> findAllIdentifiersForAdminCheckIn(@PathVariable("eventId") int eventId,
@RequestParam(value = "changedSince", required = false) Long changedSince,
Expand Down
30 changes: 19 additions & 11 deletions src/main/java/alfio/manager/CheckInManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
*/
package alfio.manager;

import alfio.manager.support.CheckInStatus;
import alfio.manager.support.DefaultCheckInResult;
import alfio.manager.support.OnSitePaymentResult;
import alfio.manager.support.TicketAndCheckInResult;
import alfio.manager.support.*;
import alfio.manager.system.ConfigurationManager;
import alfio.model.*;
import alfio.model.Ticket.TicketStatus;
Expand Down Expand Up @@ -95,10 +92,11 @@ private void acquire(String uuid) {
ticketReservationManager.registerAlfioTransaction(eventRepository.findById(ticket.getEventId()), ticket.getTicketsReservationId(), PaymentProxy.ON_SITE);
}

public TicketAndCheckInResult confirmOnSitePayment(String eventName, String ticketIdentifier, Optional<String> ticketCode, String user) {
public TicketAndCheckInResult confirmOnSitePayment(String eventName, String ticketIdentifier, Optional<String> ticketCode, String username, String auditUser) {
return eventRepository.findOptionalByShortName(eventName)
.filter(EventManager.checkOwnership(username, organizationRepository))
.flatMap(e -> confirmOnSitePayment(ticketIdentifier).map((String s) -> Pair.of(s, e)))
.map(p -> checkIn(p.getRight().getId(), ticketIdentifier, ticketCode, user))
.map(p -> checkIn(p.getRight().getId(), ticketIdentifier, ticketCode, auditUser))
.orElseGet(() -> new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.TICKET_NOT_FOUND, "")));
}

Expand All @@ -111,8 +109,11 @@ public Optional<String> confirmOnSitePayment(String ticketIdentifier) {
return uuid;
}

public TicketAndCheckInResult checkIn(String shortName, String ticketIdentifier, Optional<String> ticketCode, String user) {
return eventRepository.findOptionalByShortName(shortName).map(e -> checkIn(e.getId(), ticketIdentifier, ticketCode, user)).orElseGet(() -> new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.EVENT_NOT_FOUND, "event not found")));
public TicketAndCheckInResult checkIn(String shortName, String ticketIdentifier, Optional<String> ticketCode, String username, String auditUser) {
return eventRepository.findOptionalByShortName(shortName)
.filter(EventManager.checkOwnership(username, organizationRepository))
.map(e -> checkIn(e.getId(), ticketIdentifier, ticketCode, auditUser))
.orElseGet(() -> new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.EVENT_NOT_FOUND, "event not found")));
}

public TicketAndCheckInResult checkIn(int eventId, String ticketIdentifier, Optional<String> ticketCode, String user) {
Expand Down Expand Up @@ -263,9 +264,9 @@ public static String decrypt(String key, String payload) {
try {
Pair<Cipher, SecretKeySpec> cipherAndSecret = getCypher(key);
Cipher cipher = cipherAndSecret.getKey();
String[] splitted = payload.split(Pattern.quote("|"));
byte[] iv = Base64.decodeBase64(splitted[0]);
byte[] body = Base64.decodeBase64(splitted[1]);
String[] split = payload.split(Pattern.quote("|"));
byte[] iv = Base64.decodeBase64(split[0]);
byte[] body = Base64.decodeBase64(split[1]);
cipher.init(Cipher.DECRYPT_MODE, cipherAndSecret.getRight(), new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(body);
return new String(decrypted, StandardCharsets.UTF_8);
Expand Down Expand Up @@ -345,4 +346,11 @@ public Map<String,String> getEncryptedAttendeesInformation(Event ev, Set<String>

}).orElseGet(Collections::emptyMap);
}

public CheckInStatistics getStatistics(String eventName, String username) {
return eventRepository.findOptionalByShortName(eventName)
.filter(EventManager.checkOwnership(username, organizationRepository))
.map(event -> eventRepository.retrieveCheckInStatisticsForEvent(event.getId()))
.orElse(null);
}
}
39 changes: 39 additions & 0 deletions src/main/java/alfio/manager/support/CheckInStatistics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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.support;

import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column;
import lombok.Getter;

import java.util.Date;

@Getter
public class CheckInStatistics {
private final int totalAttendees;
private final int checkedIn;
private final long lastUpdate;



public CheckInStatistics(@Column("total_attendees") Integer totalAttendees,
@Column("checked_in") Integer checkedIn,
@Column("last_update") Date lastUpdate) {
this.totalAttendees = totalAttendees;
this.checkedIn = checkedIn;
this.lastUpdate = lastUpdate.getTime();
}
}
6 changes: 6 additions & 0 deletions src/main/java/alfio/repository/EventRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package alfio.repository;

import alfio.manager.support.CheckInStatistics;
import alfio.model.Event;
import alfio.model.EventStatisticView;
import alfio.model.PriceContainer;
Expand Down Expand Up @@ -148,4 +149,9 @@ int updatePrices(@Bind("currency") String currency,

@Query("select coalesce(sum(final_price_cts),0) from ticket where event_id = :eventId and status in("+TicketRepository.CONFIRMED+")")
long getGrossIncome(@Bind("eventId") int eventId);

@Query("select count(*) as total_attendees, COALESCE(SUM(CASE WHEN t.status = 'CHECKED_IN' THEN 1 ELSE 0 END), 0) as checked_in, CURRENT_TIMESTAMP as last_update from ticket t where event_id = :eventId and status in("+TicketRepository.CONFIRMED+")")
CheckInStatistics retrieveCheckInStatisticsForEvent(@Bind("eventId") int eventId);


}
76 changes: 72 additions & 4 deletions src/test/java/alfio/repository/EventRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@
import alfio.config.Initializer;
import alfio.config.RepositoryConfiguration;
import alfio.config.WebSecurityConfig;
import alfio.model.Event;
import alfio.model.PriceContainer;
import alfio.manager.EventManager;
import alfio.manager.EventStatisticsManager;
import alfio.manager.support.CheckInStatistics;
import alfio.manager.user.UserManager;
import alfio.model.*;
import alfio.model.modification.DateTimeModification;
import alfio.model.modification.TicketCategoryModification;
import alfio.model.result.Result;
import alfio.repository.user.OrganizationRepository;
import alfio.test.util.IntegrationTestUtil;
import ch.digitalfondue.npjt.AffectedRowCountAndKey;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
Expand All @@ -37,11 +45,17 @@
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static alfio.test.util.IntegrationTestUtil.DESCRIPTION;
import static alfio.test.util.IntegrationTestUtil.initEvent;
import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RepositoryConfiguration.class, DataSourceConfiguration.class, WebSecurityConfig.class, TestConfiguration.class})
Expand All @@ -61,6 +75,16 @@ public static void initEnv() {
private EventRepository eventRepository;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private EventManager eventManager;
@Autowired
private UserManager userManager;
@Autowired
private EventStatisticsManager eventStatisticsManager;
@Autowired
private TicketRepository ticketRepository;
@Autowired
private TicketReservationRepository ticketReservationRepository;

@Before
public void setUp() throws Exception {
Expand Down Expand Up @@ -91,4 +115,48 @@ public void testJavaInsertedDatesRespectTheirTimeZone() throws Exception {
System.out.println(e.getBegin().toString());
System.out.println(e.getEnd().toString());
}

@Test
public void testCheckInStatistics() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 0,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false, null, null, null, null, null));
Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository);
Event event = pair.getKey();
TicketCategoryModification tcm = new TicketCategoryModification(null, "default", 10,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null);
Result<Integer> result = eventManager.insertCategory(event, tcm, pair.getValue());
assertTrue(result.isSuccess());

//initial state
CheckInStatistics checkInStatistics = eventRepository.retrieveCheckInStatisticsForEvent(event.getId());
assertEquals(0, checkInStatistics.getCheckedIn());
assertEquals(0, checkInStatistics.getTotalAttendees());

EventWithAdditionalInfo eventWithAdditionalInfo = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), pair.getRight());
TicketCategoryWithAdditionalInfo firstCategory = eventWithAdditionalInfo.getTicketCategories().get(0);
List<Integer> ids = ticketRepository.selectNotAllocatedTicketsForUpdate(event.getId(), 5, Collections.singletonList(TicketRepository.FREE));
String reservationId = "12345678";
ticketReservationRepository.createNewReservation(reservationId, DateUtils.addDays(new Date(), 1), null, "en", event.getId(), event.getVat(), event.isVatIncluded());
int reserved = ticketRepository.reserveTickets(reservationId, ids, firstCategory.getId(), "it", 100);
assertEquals(5, reserved);

ticketRepository.updateTicketsStatusWithReservationId(reservationId, Ticket.TicketStatus.ACQUIRED.name());
checkInStatistics = eventRepository.retrieveCheckInStatisticsForEvent(event.getId());
//after buying 5 tickets we expect to have them in the total attendees
assertEquals(0, checkInStatistics.getCheckedIn());
assertEquals(5, checkInStatistics.getTotalAttendees());


List<Ticket> ticketsInReservation = ticketRepository.findTicketsInReservation(reservationId);
ticketRepository.updateTicketStatusWithUUID(ticketsInReservation.get(0).getUuid(), Ticket.TicketStatus.CHECKED_IN.name());
checkInStatistics = eventRepository.retrieveCheckInStatisticsForEvent(event.getId());
//checked in ticket must be taken into account
assertEquals(1, checkInStatistics.getCheckedIn());
assertEquals(5, checkInStatistics.getTotalAttendees());
}
}

0 comments on commit 7c325b7

Please sign in to comment.