diff --git a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java index c2abf69bd0..72f0214499 100644 --- a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java @@ -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, diff --git a/src/main/java/alfio/manager/ExtensionManager.java b/src/main/java/alfio/manager/ExtensionManager.java index fe601c98b1..d5931bafd8 100644 --- a/src/main/java/alfio/manager/ExtensionManager.java +++ b/src/main/java/alfio/manager/ExtensionManager.java @@ -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; @@ -359,7 +360,7 @@ void handleRefund(PurchaseContext purchaseContext, TicketReservation reservation * @param userProfile existing user profile, may be null * @return the keys to persist, or {@code null} */ - public List filterAdditionalInfoToSave(PurchaseContext purchaseContext, + public Optional> filterAdditionalInfoToSave(PurchaseContext purchaseContext, Map> userAdditionalData, PublicUserProfile userProfile) { var payload = new HashMap(); @@ -367,9 +368,9 @@ public List filterAdditionalInfoToSave(PurchaseContext purch 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) { @@ -503,7 +504,7 @@ public Optional 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 @@ -514,4 +515,13 @@ public Optional handleCustomOnlineJoinUrl(Event event, }); return result.or(() -> existingMetadata); } + + public Optional handleTicketAssignmentMetadata(TicketWithMetadataAttributes ticketWithMetadata, + Event event) { + + var context = new HashMap(); + 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)); + } } diff --git a/src/main/java/alfio/manager/NotificationManager.java b/src/main/java/alfio/manager/NotificationManager.java index 28d3a4fb79..af9c806d62 100644 --- a/src/main/java/alfio/manager/NotificationManager.java +++ b/src/main/java/alfio/manager/NotificationManager.java @@ -147,7 +147,7 @@ private static Function, 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); diff --git a/src/main/java/alfio/manager/TicketReservationManager.java b/src/main/java/alfio/manager/TicketReservationManager.java index 4e46ce34af..557e77e8d8 100644 --- a/src/main/java/alfio/manager/TicketReservationManager.java +++ b/src/main/java/alfio/manager/TicketReservationManager.java @@ -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; @@ -1331,13 +1332,24 @@ private void acquireEventTickets(PaymentProxy paymentProxy, String reservationId Validate.isTrue(updatedTickets == locked, "Expected to lock "+updatedTickets+" tickets, locked "+ locked); Map 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 ticketsInReservation = ticketRepository.findTicketsInReservation(reservationId); - Map 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"); } @@ -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> 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 preUpdateTicketFields, Ticket postUpdateTicket, Map postUpdateTicketFields, int eventId) { List diffTicket = ObjectDiffUtil.diff(preUpdateTicket, postUpdateTicket); List diffTicketFields = ObjectDiffUtil.diff(preUpdateTicketFields, postUpdateTicketFields); - List> changes = Stream.concat(diffTicket.stream(), diffTicketFields.stream()).map(change -> { - var v = new HashMap(); - 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> 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 processChange(ObjectDiffUtil.Change change) { + var v = new HashMap(); + 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) { diff --git a/src/main/java/alfio/manager/support/extension/ExtensionEvent.java b/src/main/java/alfio/manager/support/extension/ExtensionEvent.java index de3e75bcaa..7d5e6d9585 100644 --- a/src/main/java/alfio/manager/support/extension/ExtensionEvent.java +++ b/src/main/java/alfio/manager/support/extension/ExtensionEvent.java @@ -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, diff --git a/src/main/java/alfio/manager/user/PublicUserManager.java b/src/main/java/alfio/manager/user/PublicUserManager.java index 778c5d770b..35b599ef65 100644 --- a/src/main/java/alfio/manager/user/PublicUserManager.java +++ b/src/main/java/alfio/manager/user/PublicUserManager.java @@ -117,11 +117,8 @@ private Map buildAdditionalInfoWithLabels(Publi var userLanguage = form.getUserLanguage(); var filteredItems = extensionManager.filterAdditionalInfoToSave(purchaseContext, form.getAdditional(), existingProfile); final Map 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)) diff --git a/src/main/java/alfio/model/Audit.java b/src/main/java/alfio/model/Audit.java index da785cf78f..39dc074a0d 100644 --- a/src/main/java/alfio/model/Audit.java +++ b/src/main/java/alfio/model/Audit.java @@ -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; diff --git a/src/main/java/alfio/model/TicketWithMetadataAttributes.java b/src/main/java/alfio/model/TicketWithMetadataAttributes.java index 29eb5dea6a..70f4c457f3 100644 --- a/src/main/java/alfio/model/TicketWithMetadataAttributes.java +++ b/src/main/java/alfio/model/TicketWithMetadataAttributes.java @@ -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 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); } @@ -42,4 +72,13 @@ public Map 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); + } } diff --git a/src/main/java/alfio/model/metadata/TicketMetadata.java b/src/main/java/alfio/model/metadata/TicketMetadata.java index f423d643bc..8425c46848 100644 --- a/src/main/java/alfio/model/metadata/TicketMetadata.java +++ b/src/main/java/alfio/model/metadata/TicketMetadata.java @@ -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; + } } diff --git a/src/main/java/alfio/model/metadata/TicketMetadataContainer.java b/src/main/java/alfio/model/metadata/TicketMetadataContainer.java index a6badbc68f..d746e309f3 100644 --- a/src/main/java/alfio/model/metadata/TicketMetadataContainer.java +++ b/src/main/java/alfio/model/metadata/TicketMetadataContainer.java @@ -83,4 +83,13 @@ public static TicketMetadataContainer fromMetadata(TicketMetadata metadata) { } return null; } + + public static TicketMetadataContainer copyOf(TicketMetadataContainer src) { + if (src != null) { + Map newMap = new HashMap<>(); + src.metadataMap.forEach((key, tm) -> newMap.put(key, TicketMetadata.copyOf(tm))); + return new TicketMetadataContainer(newMap); + } + return null; + } } diff --git a/src/main/java/alfio/repository/TicketRepository.java b/src/main/java/alfio/repository/TicketRepository.java index f0737cd9a7..1d540f8f3a 100644 --- a/src/main/java/alfio/repository/TicketRepository.java +++ b/src/main/java/alfio/repository/TicketRepository.java @@ -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; @@ -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 { @@ -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"; @@ -211,7 +209,7 @@ int updateTicketPrice(@Bind("ids") List 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 ids); - @Query("select * from ticket where tickets_reservation_id = :reservationId " + SORT_TICKETS) + @Query(FIND_TICKETS_IN_RESERVATION) List findTicketsInReservation(@Bind("reservationId") String reservationId); @Query("select id from ticket where tickets_reservation_id = :reservationId " + SORT_TICKETS) @@ -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 findTicketsInReservationWithMetadata(@Bind("reservationId") String reservationId); + } diff --git a/src/main/java/alfio/util/ObjectDiffUtil.java b/src/main/java/alfio/util/ObjectDiffUtil.java index bc93a2755d..d31d07a61f 100644 --- a/src/main/java/alfio/util/ObjectDiffUtil.java +++ b/src/main/java/alfio/util/ObjectDiffUtil.java @@ -36,7 +36,7 @@ public static List diff(Map before, Map } private static String formatPropertyName(String k, String propertyNameBefore, String propertyNameAfter) { - return new StringBuilder(propertyNameBefore).append(k).append(propertyNameAfter).toString(); + return propertyNameBefore + k + propertyNameAfter; } private static List diffUntyped(Map before, Map after, String propertyNameBefore, String propertyNameAfter) { @@ -54,7 +54,7 @@ private static List diffUntyped(Map before, Map af removed.stream().map(k -> new Change(formatPropertyName(k, propertyNameBefore, propertyNameAfter), State.REMOVED, before.get(k), null)).forEach(changes::add); added.stream().map(k -> new Change(formatPropertyName(k, propertyNameBefore, propertyNameAfter), State.ADDED, null, after.get(k))).forEach(changes::add); - changedOrUntouched.stream().forEach(k -> { + changedOrUntouched.forEach(k -> { var beforeValue = before.get(k); var afterValue = after.get(k); if(!Objects.equals(beforeValue, afterValue)) { @@ -66,10 +66,13 @@ private static List diffUntyped(Map before, Map af } public static List diff(Ticket before, Ticket after) { + return diff(before, after, Ticket.class); + } + public static List diff(T before, T after, Class objectType) { var beforeAsMap = new HashMap(); var afterAsMap = new HashMap(); - Stream.of(BeanUtils.getPropertyDescriptors(Ticket.class)).forEach(propertyDescriptor -> { + Stream.of(BeanUtils.getPropertyDescriptors(objectType)).forEach(propertyDescriptor -> { var method = propertyDescriptor.getReadMethod(); var name = propertyDescriptor.getName(); if (method != null) { diff --git a/src/main/java/alfio/util/SqlUtils.java b/src/main/java/alfio/util/SqlUtils.java index 6d72dc6b42..c049e38c8c 100644 --- a/src/main/java/alfio/util/SqlUtils.java +++ b/src/main/java/alfio/util/SqlUtils.java @@ -21,11 +21,13 @@ import org.postgresql.util.ServerErrorMessage; import org.springframework.jdbc.UncategorizedSQLException; +import java.sql.Timestamp; +import java.time.ZonedDateTime; import java.util.Optional; @UtilityClass public class SqlUtils { - public Optional findServerError(UncategorizedSQLException exception) { + public static Optional findServerError(UncategorizedSQLException exception) { for (var throwable : exception.getSQLException()) { if(throwable instanceof PSQLException && ((PSQLException)throwable).getServerErrorMessage() != null) { return Optional.ofNullable(((PSQLException)throwable).getServerErrorMessage()); @@ -33,4 +35,11 @@ public Optional findServerError(UncategorizedSQLException ex } return Optional.empty(); } + + public static ZonedDateTime timestampToZoneDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return ZonedDateTime.ofInstant(timestamp.toInstant(), ClockProvider.clock().getZone()); + } } diff --git a/src/main/java/alfio/util/TemplateResource.java b/src/main/java/alfio/util/TemplateResource.java index 9e3cbf2d1e..4630b7c95b 100644 --- a/src/main/java/alfio/util/TemplateResource.java +++ b/src/main/java/alfio/util/TemplateResource.java @@ -181,7 +181,7 @@ public Map prepareSampleModel(Organization organization, Event e public Map prepareSampleModel(Organization organization, Event event, Optional imageData) { var now = event.now(ClockProvider.clock()); TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, event.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); - var ticketWithMetadata = new TicketWithMetadataAttributes(sampleTicket(event.getZoneId()), null); + var ticketWithMetadata = TicketWithMetadataAttributes.build(sampleTicket(event.getZoneId()), null); return buildModelForTicketPDF(organization, event, sampleTicketReservation(event.getZoneId()), ticketCategory, ticketWithMetadata, imageData, "ABCD", Collections.emptyMap()); } }, diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java index 3b5e62048a..8cfdb7cf3c 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/BaseReservationFlowTest.java @@ -430,7 +430,7 @@ protected void testBasicFlow(Supplier contextSupplier) t var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); form.setPromoCode("DYNAMIC_CODE"); - ticketReservation.setAmount(1); + ticketReservation.setQuantity(1); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), null); @@ -464,7 +464,7 @@ protected void testBasicFlow(Supplier contextSupplier) t var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); form.setPromoCode(HIDDEN_CODE); - ticketReservation.setAmount(1); + ticketReservation.setQuantity(1); ticketReservation.setTicketCategoryId(hiddenCat.getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser()); @@ -600,7 +600,7 @@ protected void testBasicFlow(Supplier contextSupplier) t { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); - ticketReservation.setAmount(1); + ticketReservation.setQuantity(1); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser()); @@ -625,12 +625,12 @@ protected void testBasicFlow(Supplier contextSupplier) t var categories = eventApiV2Controller.getTicketCategories(context.event.getShortName(), HIDDEN_CODE).getBody().getTicketCategories(); var c1 = new TicketReservationModification(); - c1.setAmount(1); + c1.setQuantity(1); int firstCategoryId = categories.get(0).getId(); c1.setTicketCategoryId(firstCategoryId); var c2 = new TicketReservationModification(); - c2.setAmount(1); + c2.setQuantity(1); c2.setTicketCategoryId(categories.get(1).getId()); form.setReservation(List.of(c1, c2)); @@ -656,7 +656,7 @@ protected void testBasicFlow(Supplier contextSupplier) t { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); - ticketReservation.setAmount(2); + ticketReservation.setQuantity(2); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); @@ -742,7 +742,7 @@ protected void testBasicFlow(Supplier contextSupplier) t { var form = new ReservationForm(); var ticketReservation = new TicketReservationModification(); - ticketReservation.setAmount(1); + ticketReservation.setQuantity(1); ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId()); form.setReservation(Collections.singletonList(ticketReservation)); var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser()); @@ -870,13 +870,14 @@ protected void testBasicFlow(Supplier contextSupplier) t extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); boolean online = containsOnlineTickets(context, reservationId); - assertEventLogged(extLogs, RESERVATION_CONFIRMED, online ? 10 : 8); - assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT, online ? 10 : 8); - assertEventLogged(extLogs, TICKET_ASSIGNED, online ? 10 : 8); + assertEventLogged(extLogs, RESERVATION_CONFIRMED, online ? 12 : 10); + assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT, online ? 12 : 10); + assertEventLogged(extLogs, TICKET_ASSIGNED, online ? 12 : 10); if(online) { - assertEventLogged(extLogs, CUSTOM_ONLINE_JOIN_URL, 10); + assertEventLogged(extLogs, CUSTOM_ONLINE_JOIN_URL, 12); } - assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT, online ? 10 : 8); + assertEventLogged(extLogs, TICKET_ASSIGNED_GENERATE_METADATA, online ? 12 : 10); + assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT, online ? 12 : 10); diff --git a/src/test/java/alfio/util/TemplateResourceTest.java b/src/test/java/alfio/util/TemplateResourceTest.java index cb3ea7a619..b32b61e6ed 100644 --- a/src/test/java/alfio/util/TemplateResourceTest.java +++ b/src/test/java/alfio/util/TemplateResourceTest.java @@ -51,7 +51,7 @@ void setUp() { ticketReservation = mock(TicketReservation.class); ticketCategory = mock(TicketCategory.class); ticket = mock(Ticket.class); - ticketWithMetadata = new TicketWithMetadataAttributes(ticket, null); + ticketWithMetadata = TicketWithMetadataAttributes.build(ticket, null); } @Test diff --git a/website/content/en/docs/Reference/Extensions/reference/ticket.md b/website/content/en/docs/Reference/Extensions/reference/ticket.md index ac77f9e5e8..900e1d99c3 100644 --- a/website/content/en/docs/Reference/Extensions/reference/ticket.md +++ b/website/content/en/docs/Reference/Extensions/reference/ticket.md @@ -188,4 +188,34 @@ A result of type [`TicketMetadata`](https://github.com/alfio-event/alf.io/blob/m + + +### Customize ticket metadata +`TICKET_ASSIGNED_GENERATE_METADATA` + +Fired **synchronously** before marking a ticket as "acquired". The purpose of this extension is to allow metadata customization. + +A result of type [`TicketMetadata`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/metadata/TicketMetadata.java) is expected. Return `null` if you don't need to modify the current metadata. +
+ + + + + + + + + + + + + + + + + + + + +
VariableTypeAbout
`ticket`[`Ticket`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/Ticket.java)Details about the ticket
`ticketMetadata`[`TicketMetadata`](https://github.com/alfio-event/alf.io/blob/master/src/main/java/alfio/model/metadata/TicketMetadata.java)Existing metadata for ticket.
\ No newline at end of file