Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support hybrid events #979

Merged
merged 17 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ junitVersion=5.1.0
systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"

# https://jitpack.io/#alfio-event/alf.io-public-frontend -> go to commit tab, set the version
alfioPublicFrontendVersion=dee7983cb9
alfioPublicFrontendVersion=0c79c9907e
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import alfio.model.BillingDetails;
import alfio.model.OrderSummary;
import alfio.model.SummaryRow.SummaryType;
import alfio.model.TicketCategory;
import alfio.model.TicketReservation.TicketReservationStatus;
import alfio.model.transaction.PaymentMethod;
import alfio.model.transaction.PaymentProxy;
Expand Down Expand Up @@ -77,6 +78,7 @@ public class ReservationInfo {
@Getter
public static class TicketsByTicketCategory {
private final String name;
private final TicketCategory.TicketAccessType ticketAccessType;
private final List<BookingInfoTicket> tickets;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package alfio.controller.api.v2.model;

import alfio.controller.decorator.SaleableTicketCategory;
import alfio.model.TicketCategory.TicketAccessType;
import lombok.Getter;
import org.apache.commons.lang3.builder.CompareToBuilder;

import java.util.Map;

Expand All @@ -31,6 +31,7 @@ public class TicketCategory {
private final Map<String, String> description;
private final int id;
private final String name;
private final TicketAccessType ticketAccessType;
private final boolean bounded;
private final int maximumSaleableTickets;
private final boolean free;
Expand Down Expand Up @@ -59,6 +60,7 @@ public TicketCategory(SaleableTicketCategory saleableTicketCategory,
this.description = description;
this.id = saleableTicketCategory.getId();
this.name = saleableTicketCategory.getName();
this.ticketAccessType = saleableTicketCategory.getTicketAccessType();
this.bounded = saleableTicketCategory.isBounded();
this.maximumSaleableTickets = max(0, min(saleableTicketCategory.getMaxTicketsAfterConfiguration(), saleableTicketCategory.getAvailableTickets()));
this.free = saleableTicketCategory.getFree();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public ResponseEntity<ReservationInfo> getReservationInfo(@PathVariable("eventNa
var ts = e.getValue().stream()
.map(t -> bookingInfoTicketLoader.toBookingInfoTicket(t, hasPaidSupplement, event, ticketFieldsFilterer, descriptionsByTicketFieldId, valuesByTicketIds, Map.of(), false))
.collect(Collectors.toList());
return new TicketsByTicketCategory(tc.getName(), ts);
return new TicketsByTicketCategory(tc.getName(), tc.getTicketAccessType(), ts);
})
.collect(Collectors.toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ public ResponseEntity<ReservationInfo.TicketsByTicketCategory> getTicket(@PathVa
var ticket = complete.getRight();
var event = complete.getLeft();

var categoryName = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()).getName();
return new ReservationInfo.TicketsByTicketCategory(categoryName, List.of(bookingInfoTicketLoader.toBookingInfoTicket(ticket, event)));
var category = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId());
return new ReservationInfo.TicketsByTicketCategory(category.getName(), category.getTicketAccessType(), List.of(bookingInfoTicketLoader.toBookingInfoTicket(ticket, event)));
});
return ResponseEntity.of(optionalTicket);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import alfio.manager.support.PartialTicketTextGenerator;
import alfio.model.*;
import alfio.model.user.Organization;
import alfio.util.EventUtil;
import alfio.util.ImageUtil;
import alfio.util.TemplateManager;
import alfio.util.TemplateResource;
Expand Down Expand Up @@ -62,7 +63,7 @@ public static PartialTicketTextGenerator buildPartialEmail(Event event,
Map<String, Object> additionalOptions) {
return ticket -> {
Map<String, Object> model = TemplateResource.buildModelForTicketEmail(organization, event, ticketReservation, baseUrl, ticketURL, calendarURL, ticket, category, additionalOptions);
return templateManager.renderTemplate(event, event.getIsOnline() ? TemplateResource.TICKET_EMAIL_FOR_ONLINE_EVENT : TemplateResource.TICKET_EMAIL, model, language);
return templateManager.renderTemplate(event, EventUtil.isAccessOnline(category, event) ? TemplateResource.TICKET_EMAIL_FOR_ONLINE_EVENT : TemplateResource.TICKET_EMAIL, model, language);
syjer marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand Down
7 changes: 4 additions & 3 deletions src/main/java/alfio/manager/AdminReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ private void updateExtRefAndLocking(int categoryId, Attendee attendee, Integer t
private Result<List<TicketsInfo>> checkCategoryCapacity(TicketsInfo ti, Event event, AdminReservationModification reservation, String username) {
Result<TicketCategory> ticketCategoryResult = ti.getCategory().isExisting() ? checkExistingCategory(ti, event, username) : createCategory(ti, event, reservation, username);
return ticketCategoryResult
.map(tc -> Collections.singletonList(new TicketsInfo(new Category(tc.getId(), tc.getName(), tc.getPrice()), ti.getAttendees(), ti.isAddSeatsIfNotAvailable(), ti.isUpdateAttendees())));
.map(tc -> List.of(new TicketsInfo(new Category(tc.getId(), tc.getName(), tc.getPrice(), tc.getTicketAccessType()), ti.getAttendees(), ti.isAddSeatsIfNotAvailable(), ti.isUpdateAttendees())));
}

private Result<TicketCategory> createCategory(TicketsInfo ti, Event event, AdminReservationModification reservation, String username) {
Expand All @@ -509,7 +509,8 @@ private Result<TicketCategory> createCategory(TicketsInfo ti, Event event, Admin
DateTimeModification inception = fromZonedDateTime(event.now(clockProvider));

int tickets = attendees.size();
TicketCategoryModification tcm = new TicketCategoryModification(category.getExistingCategoryId(), category.getName(), tickets,
var accessType = event.getFormat() != Event.EventFormat.HYBRID ? TicketCategory.TicketAccessType.INHERIT : Objects.requireNonNull(category.getTicketAccessType());
TicketCategoryModification tcm = new TicketCategoryModification(category.getExistingCategoryId(), category.getName(), accessType, tickets,
inception, reservation.getExpiration(), Collections.emptyMap(), category.getPrice(), true, "",
true, null, null, null, null, null, 0, null, null,
AlfioMetadata.empty());
Expand Down Expand Up @@ -547,7 +548,7 @@ private Result<TicketCategory> checkExistingCategory(TicketsInfo ti, Event event
Event modified = increaseSeatsIfNeeded(ti, event, missingTickets, event);
if(freeTicketsInCategory < tickets && existing.isBounded()) {
int maxTickets = existing.getMaxTickets() + (tickets - freeTicketsInCategory);
TicketCategoryModification tcm = new TicketCategoryModification(existingCategoryId, existing.getName(), maxTickets,
TicketCategoryModification tcm = new TicketCategoryModification(existingCategoryId, existing.getName(), existing.getTicketAccessType(), maxTickets,
fromZonedDateTime(existing.getInception(modified.getZoneId())), fromZonedDateTime(existing.getExpiration(event.getZoneId())),
Collections.emptyMap(), existing.getPrice(), existing.isAccessRestricted(), "", true, existing.getCode(),
fromZonedDateTime(existing.getValidCheckInFrom(modified.getZoneId())),
Expand Down
7 changes: 2 additions & 5 deletions src/main/java/alfio/manager/CheckInManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
import alfio.repository.audit.ScanAuditRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.repository.user.UserRepository;
import alfio.util.ClockProvider;
import alfio.util.Json;
import alfio.util.MonetaryUtil;
import alfio.util.PinGenerator;
import alfio.util.*;
import com.google.gson.reflect.TypeToken;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j2;
Expand Down Expand Up @@ -115,7 +112,7 @@ private void acquire(String uuid) {
* @return
*/
public CheckInStatus performCheckinForOnlineEvent(Ticket ticket, EventCheckInInfo event, TicketCategory tc) {
Validate.isTrue(event.getFormat() == Event.EventFormat.ONLINE);
Validate.isTrue(EventUtil.isAccessOnline(tc, event));
if(!tc.hasValidCheckIn(event.now(clockProvider), event.getZoneId())) {
return INVALID_TICKET_CATEGORY_CHECK_IN_DATE;
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/alfio/manager/EventManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ private void createCategoriesForEvent(EventModification em, Event event) {
tc.getExpiration().toZonedDateTime(zoneId), tc.getName(), maxTickets, tc.isTokenGenerationRequested(), eventId, tc.isBounded(), price, StringUtils.trimToNull(tc.getCode()),
atZone(tc.getValidCheckInFrom(), zoneId), atZone(tc.getValidCheckInTo(), zoneId),
atZone(tc.getTicketValidityStart(), zoneId), atZone(tc.getTicketValidityEnd(), zoneId), tc.getOrdinal(), Optional.ofNullable(tc.getTicketCheckInStrategy()).orElse(ONCE_PER_EVENT),
Objects.requireNonNullElseGet(tc.getMetadata(), AlfioMetadata::empty));
Objects.requireNonNullElseGet(tc.getMetadata(), AlfioMetadata::empty), tc.getTicketAccessType());

insertOrUpdateTicketCategoryDescription(category.getKey(), tc, event);

Expand All @@ -622,7 +622,7 @@ private Integer insertCategory(TicketCategoryModification tc, Event event) {
atZone(tc.getTicketValidityStart(), zoneId),
atZone(tc.getTicketValidityEnd(), zoneId), tc.getOrdinal(),
Objects.requireNonNullElse(tc.getTicketCheckInStrategy(), ONCE_PER_EVENT),
Objects.requireNonNullElseGet(tc.getMetadata(), AlfioMetadata::empty));
Objects.requireNonNullElseGet(tc.getMetadata(), AlfioMetadata::empty), tc.getTicketAccessType());
TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(category.getKey(), eventId);
if(tc.isBounded()) {
List<Integer> lockedTickets = ticketRepository.selectNotAllocatedTicketsForUpdate(eventId, ticketCategory.getMaxTickets(), asList(TicketStatus.FREE.name(), TicketStatus.RELEASED.name()));
Expand Down Expand Up @@ -712,7 +712,7 @@ private void updateCategory(TicketCategoryModification tc,
atZone(tc.getValidCheckInTo(), zoneId),
atZone(tc.getTicketValidityStart(), zoneId),
atZone(tc.getTicketValidityEnd(), zoneId),
Optional.ofNullable(tc.getTicketCheckInStrategy()).orElse(ONCE_PER_EVENT));
Objects.requireNonNullElse(tc.getTicketCheckInStrategy(), ONCE_PER_EVENT), tc.getTicketAccessType());
TicketCategory updated = ticketCategoryRepository.getByIdAndActive(tc.getId(), eventId);
int addedTickets = 0;
if(original.isBounded() ^ tc.isBounded()) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/alfio/manager/NotificationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public void sendTicketByEmail(Ticket ticket,
Organization organization = organizationRepository.getById(event.getOrganizationId());

List<Mailer.Attachment> attachments = new ArrayList<>();
if(event.getFormat() == Event.EventFormat.ONLINE) { // generate only calendar invitation
if(EventUtil.isAccessOnline(ticketCategory, event)) { // generate only calendar invitation
var baseUrl = configurationManager.getFor(BASE_URL, ConfigurationLevel.event(event)).getRequiredValue();
var eventMetadata = Optional.ofNullable(eventRepository.getMetadataForEvent(event.getId()).getRequirementsDescriptions()).flatMap(m -> Optional.ofNullable(m.get(locale.getLanguage())));
var categoryMetadata = Optional.ofNullable(ticketCategoryRepository.getMetadata(event.getId(), ticketCategory.getId()).getRequirementsDescriptions()).flatMap(m -> Optional.ofNullable(m.get(locale.getLanguage())));
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/alfio/manager/TicketReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ public PartialTicketTextGenerator getTicketEmailGenerator(Event event, TicketRes
var initialOptions = extensionManager.handleTicketEmailCustomText(event, ticketReservation, ticketReservationRepository.getAdditionalInfo(ticketReservation.getId()), ticketFieldRepository.findAllByTicketId(ticket.getId()))
.map(CustomEmailText::toMap)
.orElse(Map.of());
if(event.getFormat() == Event.EventFormat.ONLINE) {
if(EventUtil.isAccessOnline(ticketCategory, event)) {
initialOptions = new HashMap<>(initialOptions);
var eventMetadata = Optional.ofNullable(eventRepository.getMetadataForEvent(event.getId()).getRequirementsDescriptions()).flatMap(m -> Optional.ofNullable(m.get(ticketLanguage.getLanguage())));
var categoryMetadata = Optional.ofNullable(ticketCategoryRepository.getMetadata(event.getId(), ticketCategory.getId()).getRequirementsDescriptions()).flatMap(m -> Optional.ofNullable(m.get(ticketLanguage.getLanguage())));
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/alfio/model/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public enum Status {
}

public enum EventFormat {
IN_PERSON, ONLINE
IN_PERSON, ONLINE, HYBRID
}

private final EventFormat format;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/alfio/model/FullTicketInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ public FullTicketInfo(@Column("t_id") int id,
@Column("tc_ticket_validity_start") ZonedDateTime ticketValidityStart,
@Column("tc_ticket_validity_end") ZonedDateTime ticketValidityEnd,
@Column("tc_ordinal") int ordinal,
@Column("tc_ticket_checkin_strategy") TicketCategory.TicketCheckInStrategy ticketCheckInStrategy
@Column("tc_ticket_checkin_strategy") TicketCategory.TicketCheckInStrategy ticketCheckInStrategy,
@Column("tc_ticket_access_type") TicketCategory.TicketAccessType ticketAccessType
) {

this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email,
Expand All @@ -130,7 +131,7 @@ public FullTicketInfo(@Column("t_id") int id,
reservationRegistrationTimestamp, reservationSrcPriceCts, reservationFinalPriceCts, reservationVatCts, reservationDiscountCts, reservationCurrencyCode);
this.ticketCategory = new TicketCategory(tcId, tcUtcInception, tcUtcExpiration, tcMaxTickets, tcName,
tcAccessRestricted, tcStatus, tcEventId, bounded, tcSrcPriceCts, code, validCheckInFrom, validCheckInTo,
ticketValidityStart, ticketValidityEnd, currencyCode, ordinal, ticketCheckInStrategy);
ticketValidityStart, ticketValidityEnd, currencyCode, ordinal, ticketCheckInStrategy, ticketAccessType);

this.billingDetails = new BillingDetails(billingAddressCompany, billingAddressLine1, billingAddressLine2, billingAddressZip, billingAddressCity, vatCountry, vatNr, invoicingAdditionalInfo);

Expand Down
26 changes: 25 additions & 1 deletion src/main/java/alfio/model/TicketCategory.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ public enum TicketCheckInStrategy {
ONCE_PER_DAY
}

/**
* Defines the category access type. It will impact how tickets are generated/sent/checked-in.
*/
public enum TicketAccessType {
/**
* Inherit the access type from the Event format (default)
*/
INHERIT,

/**
* Attendees of this category will receive a ticket and Wallet Pass, and will be allowed to access
* the event venue.
*/
IN_PERSON,

/**
* Attendees of this category can only access the event online.
*/
ONLINE
}

private final int id;
private final ZonedDateTime utcInception;
private final ZonedDateTime utcExpiration;
Expand All @@ -72,6 +93,7 @@ public enum TicketCheckInStrategy {
private final String currencyCode;
private final int ordinal;
private final TicketCheckInStrategy ticketCheckInStrategy;
private final TicketAccessType ticketAccessType;


public TicketCategory(@JsonProperty("id") @Column("id") int id,
Expand All @@ -91,7 +113,8 @@ public TicketCategory(@JsonProperty("id") @Column("id") int id,
@JsonProperty("ticketValidityEnd") @Column("ticket_validity_end") ZonedDateTime ticketValidityEnd,
@JsonProperty("currencyCode") @Column("currency_code") String currencyCode,
@JsonProperty("ordinal") @Column("ordinal") Integer ordinal,
@JsonProperty("ticketCheckInStrategy") @Column("ticket_checkin_strategy") TicketCheckInStrategy ticketCheckInStrategy) {
@JsonProperty("ticketCheckInStrategy") @Column("ticket_checkin_strategy") TicketCheckInStrategy ticketCheckInStrategy,
@JsonProperty("ticketAccessType") @Column("ticket_access_type") TicketAccessType ticketAccessType) {
this.id = id;
this.utcInception = utcInception;
this.utcExpiration = utcExpiration;
Expand All @@ -110,6 +133,7 @@ public TicketCategory(@JsonProperty("id") @Column("id") int id,
this.currencyCode = currencyCode;
this.ordinal = ordinal != null ? ordinal : 0;
this.ticketCheckInStrategy = ticketCheckInStrategy;
this.ticketAccessType = ticketAccessType;
}

public BigDecimal getPrice() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class EventCreationRequest{
private String title;
private String slug;
private List<DescriptionRequest> description;
private Event.EventFormat format;
private LocationRequest location;
private String timezone;
private LocalDateTime startDate;
Expand All @@ -73,7 +74,7 @@ public EventModification toEventModification(Organization organization, UnaryOpe

return new EventModification(
null,
Event.EventFormat.IN_PERSON,
Objects.requireNonNullElse(format, Event.EventFormat.IN_PERSON),
websiteUrl,
null,
termsAndConditionsUrl,
Expand Down Expand Up @@ -125,7 +126,7 @@ public EventModification toEventModificationUpdate(EventWithAdditionalInfo origi

return new EventModification(
original.getId(),
Event.EventFormat.IN_PERSON,
original.getFormat(),
first(websiteUrl,original.getWebsiteUrl()),
null,
first(termsAndConditionsUrl,original.getWebsiteUrl()),
Expand Down Expand Up @@ -214,6 +215,7 @@ public static class CategoryRequest {
private String accessCode;
private CustomTicketValidityRequest customValidity;
private GroupLinkRequest groupLink;
private TicketCategory.TicketAccessType ticketAccessType;

TicketCategoryModification toTicketCategoryModification() {
return toTicketCategoryModification(null);
Expand All @@ -227,6 +229,7 @@ TicketCategoryModification toTicketCategoryModification(Integer categoryId) {
return new TicketCategoryModification(
categoryId,
name,
Objects.requireNonNullElse(ticketAccessType, TicketCategory.TicketAccessType.INHERIT),
capacity,
new DateTimeModification(startSellingDate.toLocalDate(),startSellingDate.toLocalTime()),
new DateTimeModification(endSellingDate.toLocalDate(),endSellingDate.toLocalTime()),
Expand Down
Loading