Skip to content

Commit

Permalink
custom metadata on ticket confirmation
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone committed Jan 31, 2022
1 parent b80320a commit 14a5489
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public void generateTicketPdf(@PathVariable("eventName") String eventName,
TicketCategory ticketCategory = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId());
Organization organization = organizationRepository.getById(event.getOrganizationId());
String reservationID = ticketReservationManager.getShortReservationID(event, ticketReservation);
var ticketWithMetadata = new TicketWithMetadataAttributes(ticket, ticketRepository.getTicketMetadata(ticket.getId()));
var ticketWithMetadata = TicketWithMetadataAttributes.build(ticket, ticketRepository.getTicketMetadata(ticket.getId()));
TemplateProcessor.renderPDFTicket(LocaleUtil.getTicketLanguage(ticket, LocaleUtil.forLanguageTag(ticketReservation.getUserLanguage(), event)), event, ticketReservation,
ticketWithMetadata, ticketCategory, organization,
templateManager, fileUploadManager,
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/alfio/manager/ExtensionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public class ExtensionManager {
private static final String ORGANIZATION_ID = "organizationId";
private static final String RESERVATION_ID = "reservationId";
private static final String EVENT = "event";
public static final String TICKET_METADATA = "ticketMetadata";
private final ExtensionService extensionService;
private final EventRepository eventRepository;
private final TicketReservationRepository ticketReservationRepository;
Expand Down Expand Up @@ -359,17 +360,17 @@ void handleRefund(PurchaseContext purchaseContext, TicketReservation reservation
* @param userProfile existing user profile, may be null
* @return the keys to persist, or {@code null}
*/
public List<AdditionalInfoItem> filterAdditionalInfoToSave(PurchaseContext purchaseContext,
public Optional<List<AdditionalInfoItem>> filterAdditionalInfoToSave(PurchaseContext purchaseContext,
Map<String, List<String>> userAdditionalData,
PublicUserProfile userProfile) {
var payload = new HashMap<String, Object>();
payload.put("userAdditionalData", userAdditionalData);
payload.put("userProfile", userProfile);
var result = syncCall(ExtensionEvent.USER_ADDITIONAL_INFO_FILTER, purchaseContext, payload, AdditionalInfoFilterResult.class);
if(result != null) {
return result.getItems();
return Optional.of(result.getItems());
}
return null;
return Optional.empty();
}

public boolean handlePdfTransformation(String html, PurchaseContext purchaseContext, OutputStream outputStream) {
Expand Down Expand Up @@ -503,7 +504,7 @@ public Optional<TicketMetadata> handleCustomOnlineJoinUrl(Event event,
context.put(TICKET, ticket);
context.put(ADDITIONAL_INFO, ticketAdditionalInfo);
var existingMetadata = ticketMetadataContainer.getMetadataForKey(key);
existingMetadata.ifPresent(m -> context.put("ticketMetadata", m));
existingMetadata.ifPresent(m -> context.put(TICKET_METADATA, m));
var result = Optional.ofNullable(syncCall(ExtensionEvent.CUSTOM_ONLINE_JOIN_URL, event, context, TicketMetadata.class, false));
result.ifPresent(m -> {
// we update the value only if it's changed
Expand All @@ -514,4 +515,13 @@ public Optional<TicketMetadata> handleCustomOnlineJoinUrl(Event event,
});
return result.or(() -> existingMetadata);
}

public Optional<TicketMetadata> handleTicketAssignmentMetadata(TicketWithMetadataAttributes ticketWithMetadata,
Event event) {

var context = new HashMap<String, Object>();
context.put(TICKET, ticketWithMetadata.getTicket());
context.put(TICKET_METADATA, ticketWithMetadata.getMetadata().getMetadataForKey(TicketMetadataContainer.GENERAL).orElseGet(TicketMetadata::empty));
return Optional.ofNullable(syncCall(ExtensionEvent.TICKET_ASSIGNED_GENERATE_METADATA, event, context, TicketMetadata.class, false));
}
}
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 @@ -147,7 +147,7 @@ private static Function<Map<String, String>, byte[]> generateTicketPDF(EventRepo
TicketCategory ticketCategory = Json.fromJson(model.get("ticketCategory"), TicketCategory.class);
Event event = eventRepository.findById(ticket.getEventId());
Organization organization = organizationRepository.getById(Integer.valueOf(model.get("organizationId"), 10));
var ticketWithMetadata = new TicketWithMetadataAttributes(ticket, ticketRepository.getTicketMetadata(ticket.getId()));
var ticketWithMetadata = TicketWithMetadataAttributes.build(ticket, ticketRepository.getTicketMetadata(ticket.getId()));
TemplateProcessor.renderPDFTicket(LocaleUtil.forLanguageTag(ticket.getUserLanguage()), event, reservation,
ticketWithMetadata, ticketCategory, organization, templateManager, fileUploadManager,
configurationManager.getShortReservationID(event, reservation), baos, retrieveFieldValues, extensionManager);
Expand Down
59 changes: 44 additions & 15 deletions src/main/java/alfio/manager/TicketReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
import java.util.stream.Stream;

import static alfio.model.Audit.EntityType.RESERVATION;
import static alfio.model.Audit.EntityType.TICKET;
import static alfio.model.Audit.EventType.*;
import static alfio.model.BillingDocument.Type.*;
import static alfio.model.PromoCodeDiscount.categoriesOrNull;
Expand Down Expand Up @@ -1331,13 +1332,24 @@ private void acquireEventTickets(PaymentProxy paymentProxy, String reservationId
Validate.isTrue(updatedTickets == locked, "Expected to lock "+updatedTickets+" tickets, locked "+ locked);
Map<Integer, Ticket> postUpdateTicket = ticketRepository.findTicketsInReservation(reservationId).stream().collect(toMap(Ticket::getId, Function.identity()));

postUpdateTicket.forEach((id, ticket) -> {
auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticket, Collections.emptyMap(), event.getId());
postUpdateTicket.forEach(
(id, ticket) -> auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticket, Collections.emptyMap(), event.getId()));
}
var ticketsWithMetadataById = ticketRepository.findTicketsInReservationWithMetadata(reservationId)
.stream().collect(toMap(twm -> twm.getTicket().getId(), Function.identity()));
ticketsWithMetadataById.forEach((id, ticketWithMetadata) -> {
var newMetadataOptional = extensionManager.handleTicketAssignmentMetadata(ticketWithMetadata, event);
newMetadataOptional.ifPresent(metadata -> {
var existingContainer = TicketMetadataContainer.copyOf(ticketWithMetadata.getMetadata());
var general = new HashMap<>(existingContainer.getMetadataForKey(TicketMetadataContainer.GENERAL)
.orElseGet(TicketMetadata::empty).getAttributes());
general.putAll(metadata.getAttributes());
existingContainer.putMetadata(TicketMetadataContainer.GENERAL, new TicketMetadata(null, null, general));
ticketRepository.updateTicketMetadata(id, existingContainer);
auditUpdateMetadata(reservationId, id, event.getId(), existingContainer, ticketWithMetadata.getMetadata());
});
}
List<Ticket> ticketsInReservation = ticketRepository.findTicketsInReservation(reservationId);
Map<Integer, Ticket> postUpdateTicket = ticketsInReservation.stream().collect(toMap(Ticket::getId, Function.identity()));
postUpdateTicket.forEach((id, ticket) -> auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticket, Collections.emptyMap(), event.getId()));
auditUpdateTicket(preUpdateTicket.get(id), Collections.emptyMap(), ticketWithMetadata.getTicket(), Collections.emptyMap(), event.getId());
});
int updatedAS = additionalServiceItemRepository.updateItemsStatusWithReservationUUID(reservationId, asStatus);
Validate.isTrue(updatedTickets + updatedAS > 0, "no items have been updated");
}
Expand Down Expand Up @@ -2045,21 +2057,38 @@ boolean isTicketBeingReassigned(Ticket original, UpdateTicketOwnerForm updated,
&& (!equalsIgnoreCase(original.getEmail(), updated.getEmail()) || !equalsIgnoreCase(original.getFullName(), customerName.getFullName()));
}

private void auditUpdateMetadata(String reservationId,
int ticketId,
int eventId,
TicketMetadataContainer newMetadata,
TicketMetadataContainer oldMetadata) {
List<Map<String, Object>> changes = ObjectDiffUtil.diff(oldMetadata, newMetadata, TicketMetadataContainer.class).stream()
.map(this::processChange)
.collect(Collectors.toList());

auditingRepository.insert(reservationId, null, eventId, Audit.EventType.UPDATE_TICKET_METADATA, new Date(),
TICKET, Integer.toString(ticketId), changes);
}

private void auditUpdateTicket(Ticket preUpdateTicket, Map<String, String> preUpdateTicketFields, Ticket postUpdateTicket, Map<String, String> postUpdateTicketFields, int eventId) {
List<ObjectDiffUtil.Change> diffTicket = ObjectDiffUtil.diff(preUpdateTicket, postUpdateTicket);
List<ObjectDiffUtil.Change> diffTicketFields = ObjectDiffUtil.diff(preUpdateTicketFields, postUpdateTicketFields);

List<Map<String, Object>> changes = Stream.concat(diffTicket.stream(), diffTicketFields.stream()).map(change -> {
var v = new HashMap<String, Object>();
v.put("propertyName", change.getPropertyName());
v.put("state", change.getState());
v.put("oldValue", change.getOldValue());
v.put("newValue", change.getNewValue());
return v;
}).collect(Collectors.toList());
List<Map<String, Object>> changes = Stream.concat(diffTicket.stream(), diffTicketFields.stream())
.map(this::processChange)
.collect(Collectors.toList());

auditingRepository.insert(preUpdateTicket.getTicketsReservationId(), null, eventId,
Audit.EventType.UPDATE_TICKET, new Date(), Audit.EntityType.TICKET, Integer.toString(preUpdateTicket.getId()), changes);
Audit.EventType.UPDATE_TICKET, new Date(), TICKET, Integer.toString(preUpdateTicket.getId()), changes);
}

private HashMap<String, Object> processChange(ObjectDiffUtil.Change change) {
var v = new HashMap<String, Object>();
v.put("propertyName", change.getPropertyName());
v.put("state", change.getState());
v.put("oldValue", change.getOldValue());
v.put("newValue", change.getNewValue());
return v;
}

private boolean isAdmin(Optional<UserDetails> userDetails) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public enum ExtensionEvent {
TICKET_CANCELLED,
RESERVATION_EXPIRED,
TICKET_ASSIGNED,
TICKET_ASSIGNED_GENERATE_METADATA,
WAITING_QUEUE_SUBSCRIBED,
INVOICE_GENERATION,
CREDIT_NOTE_GENERATION,
Expand Down
7 changes: 2 additions & 5 deletions src/main/java/alfio/manager/user/PublicUserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,8 @@ private Map<String, AdditionalInfoWithLabel> buildAdditionalInfoWithLabels(Publi
var userLanguage = form.getUserLanguage();
var filteredItems = extensionManager.filterAdditionalInfoToSave(purchaseContext, form.getAdditional(), existingProfile);
final Map<String, AdditionalInfoItem> filteredItemsByKey;
if(filteredItems != null) {
filteredItemsByKey = filteredItems.stream().collect(toMap(AdditionalInfoItem::getKey, Function.identity()));
} else {
filteredItemsByKey = null;
}
filteredItemsByKey = filteredItems.map(additionalInfoItems -> additionalInfoItems.stream().collect(toMap(AdditionalInfoItem::getKey, Function.identity())))
.orElse(null);
var labels = ticketFieldRepository.findDescriptions(event.getId(), userLanguage).stream()
.filter(f -> fieldsById.containsKey(f.getTicketFieldConfigurationId()))
.map(f -> Map.entry(fieldsById.get(f.getTicketFieldConfigurationId()).getName(), f))
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/alfio/model/Audit.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public enum EventType {
AUTOMATIC_PAYMENT_CONFIRMATION_FAILED,
DYNAMIC_DISCOUNT_CODE_CREATED,
SUBSCRIPTION_ACQUIRED,
WARNING_IGNORED
UPDATE_TICKET_METADATA, WARNING_IGNORED
}

private final String reservationId;
Expand Down
47 changes: 43 additions & 4 deletions src/main/java/alfio/model/TicketWithMetadataAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,47 @@
package alfio.model;

import alfio.model.metadata.TicketMetadataContainer;
import alfio.model.support.Array;
import alfio.model.support.JSONData;
import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column;
import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.time.ZonedDateTime;
import java.util.*;

public class TicketWithMetadataAttributes {

private final Ticket ticket;
private final TicketMetadataContainer ticketMetadataContainer;

public TicketWithMetadataAttributes(Ticket ticket, TicketMetadataContainer ticketMetadataContainer) {
public TicketWithMetadataAttributes(@Column("id") int id,
@Column("uuid") String uuid,
@Column("creation") ZonedDateTime creation,
@Column("category_id") Integer categoryId,
@Column("status") String status,
@Column("event_id") int eventId,
@Column("tickets_reservation_id") String ticketsReservationId,
@Column("full_name") String fullName,
@Column("first_name") String firstName,
@Column("last_name") String lastName,
@Column("email_address") String email,
@Column("locked_assignment") boolean lockedAssignment,
@Column("user_language") String userLanguage,
@Column("src_price_cts") int srcPriceCts,
@Column("final_price_cts") int finalPriceCts,
@Column("vat_cts") int vatCts,
@Column("discount_cts") int discountCts,
@Column("ext_reference") String extReference,
@Column("currency_code") String currencyCode,
@Column("tags") @Array List<String> tags,
@Column("subscription_id_fk") UUID subscriptionId,
@Column("vat_status") PriceContainer.VatStatus vatStatus,
@Column("metadata") @JSONData TicketMetadataContainer ticketMetadataContainer) {
this(new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, srcPriceCts, finalPriceCts, vatCts, discountCts, extReference, currencyCode, tags, subscriptionId, vatStatus),
ticketMetadataContainer);
}

private TicketWithMetadataAttributes(Ticket ticket, TicketMetadataContainer ticketMetadataContainer) {
this.ticket = ticket;
this.ticketMetadataContainer = Objects.requireNonNullElseGet(ticketMetadataContainer, TicketMetadataContainer::empty);
}
Expand All @@ -42,4 +72,13 @@ public Map<String, String> getAttributes() {
public Ticket getTicket() {
return ticket;
}

@JsonIgnore
public TicketMetadataContainer getMetadata() {
return ticketMetadataContainer;
}

public static TicketWithMetadataAttributes build(Ticket ticket, TicketMetadataContainer ticketMetadataContainer) {
return new TicketWithMetadataAttributes(ticket, ticketMetadataContainer);
}
}
11 changes: 11 additions & 0 deletions src/main/java/alfio/model/metadata/TicketMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,15 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(joinLink, linkDescription, attributes);
}

public static TicketMetadata empty() {
return new TicketMetadata(null, null, Map.of());
}

public static TicketMetadata copyOf(TicketMetadata src) {
if (src != null) {
return new TicketMetadata(src.joinLink, Map.copyOf(src.linkDescription), Map.copyOf(src.attributes));
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,13 @@ public static TicketMetadataContainer fromMetadata(TicketMetadata metadata) {
}
return null;
}

public static TicketMetadataContainer copyOf(TicketMetadataContainer src) {
if (src != null) {
Map<String, TicketMetadata> newMap = new HashMap<>();
src.metadataMap.forEach((key, tm) -> newMap.put(key, TicketMetadata.copyOf(tm)));
return new TicketMetadataContainer(newMap);
}
return null;
}
}
9 changes: 5 additions & 4 deletions src/main/java/alfio/repository/TicketRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import alfio.model.*;
import alfio.model.checkin.OnlineCheckInFullInfo;
import alfio.model.metadata.TicketMetadata;
import alfio.model.metadata.TicketMetadataContainer;
import alfio.model.poll.PollParticipant;
import alfio.model.support.Array;
Expand All @@ -34,9 +33,7 @@
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;

@QueryRepository
public interface TicketRepository {
Expand All @@ -46,6 +43,7 @@ public interface TicketRepository {
String RELEASED = "RELEASED";
String REVERT_TO_FREE = "update ticket set status = 'FREE' where status = 'RELEASED' and event_id = :eventId";
String SORT_TICKETS = "order by category_id asc, uuid asc";
String FIND_TICKETS_IN_RESERVATION = "select * from ticket where tickets_reservation_id = :reservationId " + SORT_TICKETS;

String RESET_TICKET = " TICKETS_RESERVATION_ID = null, FULL_NAME = null, EMAIL_ADDRESS = null, SPECIAL_PRICE_ID_FK = null, LOCKED_ASSIGNMENT = false, USER_LANGUAGE = null, REMINDER_SENT = false, SRC_PRICE_CTS = 0, FINAL_PRICE_CTS = 0, VAT_CTS = 0, DISCOUNT_CTS = 0, FIRST_NAME = null, LAST_NAME = null, EXT_REFERENCE = null, TAGS = array[]::text[], VAT_STATUS = null, METADATA = '{}'::jsonb ";
String RELEASE_TICKET_QUERY = "update ticket set status = 'RELEASED', uuid = :newUuid, " + RESET_TICKET + " where id = :ticketId and status in('ACQUIRED', 'PENDING', 'TO_BE_PAID') and tickets_reservation_id = :reservationId and event_id = :eventId";
Expand Down Expand Up @@ -211,7 +209,7 @@ int updateTicketPrice(@Bind("ids") List<Integer> ids,
@Query("update ticket set category_id = null where event_id = :eventId and category_id = :categoryId and id in (:ticketIds)")
int unbindTicketsFromCategory(@Bind("eventId") int eventId, @Bind("categoryId") int categoryId, @Bind("ticketIds") List<Integer> ids);

@Query("select * from ticket where tickets_reservation_id = :reservationId " + SORT_TICKETS)
@Query(FIND_TICKETS_IN_RESERVATION)
List<Ticket> findTicketsInReservation(@Bind("reservationId") String reservationId);

@Query("select id from ticket where tickets_reservation_id = :reservationId " + SORT_TICKETS)
Expand Down Expand Up @@ -446,4 +444,7 @@ int applySubscriptionToTicketsInReservation(@Bind("reservationId") String reserv
@Query("update ticket set vat_status = :vatStatus::VAT_STATUS where tickets_reservation_id = :reservationId")
int updateVatStatusForReservation(@Bind("reservationId") String reservationId, @Bind("vatStatus") @EnumTypeAsString PriceContainer.VatStatus vatStatus);

@Query(FIND_TICKETS_IN_RESERVATION)
List<TicketWithMetadataAttributes> findTicketsInReservationWithMetadata(@Bind("reservationId") String reservationId);

}
Loading

0 comments on commit 14a5489

Please sign in to comment.