From f2d89c9f8b6481e87ec152f7bf57dc18482870d0 Mon Sep 17 00:00:00 2001 From: Celestino Bellone <3385346+cbellone@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:13:44 +0200 Subject: [PATCH] use different UUIDs for reservation/UI and check-in (#1375) * use different UUIDs for reservation/UI and check-in * fix ticket update URL * handle online check-in * fix imports; add test for checking that both unique constraints are applied * fix imports after merge * specify charset when parsing string --- .../controller/OnlineCheckInController.java | 22 +- .../api/admin/ResourceController.java | 5 +- .../api/pass/PassKitApiController.java | 5 +- .../api/support/BookingInfoTicketLoader.java | 4 +- .../controller/api/support/TicketHelper.java | 32 +- .../v2/user/ReservationApiV2Controller.java | 14 +- .../api/v2/user/TicketApiV2Controller.java | 22 +- .../form/ContactAndTicketsForm.java | 4 +- .../manager/AdditionalServiceManager.java | 9 +- .../java/alfio/manager/CheckInManager.java | 2 +- .../java/alfio/manager/ExtensionManager.java | 2 +- .../alfio/manager/ReverseChargeManager.java | 2 +- .../manager/TicketReservationManager.java | 48 +- .../manager/support/CustomMessageManager.java | 4 +- .../ReservationEmailContentHelper.java | 2 +- .../java/alfio/model/DetailedScanData.java | 3 +- src/main/java/alfio/model/FullTicketInfo.java | 3 +- src/main/java/alfio/model/Ticket.java | 5 + src/main/java/alfio/model/TicketInfo.java | 5 + .../java/alfio/model/TicketWithCategory.java | 68 +- .../model/TicketWithMetadataAttributes.java | 3 +- .../TicketWithReservationAndTransaction.java | 3 +- .../alfio/model/checkin/CheckInFullInfo.java | 3 +- .../support/TicketWithAdditionalFields.java | 38 +- .../PurchaseContextFieldRepository.java | 3 +- .../repository/SponsorScanRepository.java | 2 +- .../alfio/repository/TicketRepository.java | 41 +- .../repository/TicketSearchRepository.java | 2 +- src/main/java/alfio/util/EventUtil.java | 1 + src/main/java/alfio/util/ReservationUtil.java | 2 +- src/main/java/alfio/util/TemplateManager.java | 3 +- .../java/alfio/util/TemplateResource.java | 158 +-- src/main/java/alfio/util/Validator.java | 6 +- .../alfio/util/checkin/TicketCheckInUtil.java | 3 +- .../V205_2.0.0.57__TICKET_PUBLIC_UUID.sql | 41 + ...006_VIEW_reservation_and_ticket_and_tx.sql | 1 + ...checkin_ticket_event_and_category_info.sql | 1 + .../ticket-has-been-cancelled-admin-txt.ms | 2 +- src/main/resources/alfio/templates/ticket.ms | 2 +- .../js/admin/feature/copy-event/copy-event.js | 4 +- .../reservation/BaseReservationFlowTest.java | 77 +- .../CustomTaxPolicyIntegrationTest.java | 24 +- .../manager/EventManagerIntegrationTest.java | 39 + .../ReverseChargeManagerIntegrationTest.java | 6 +- .../manager/TicketReservationManagerTest.java | 24 +- .../WaitingQueueProcessorIntegrationTest.java | 2 +- src/test/java/alfio/util/ObjectDiffTest.java | 6 +- src/test/resources/api/descriptor.json | 998 ++++++++---------- 48 files changed, 934 insertions(+), 822 deletions(-) create mode 100644 src/main/resources/alfio/db/PGSQL/V205_2.0.0.57__TICKET_PUBLIC_UUID.sql diff --git a/src/main/java/alfio/controller/OnlineCheckInController.java b/src/main/java/alfio/controller/OnlineCheckInController.java index 9ad1beecf9..b73aa3af9a 100644 --- a/src/main/java/alfio/controller/OnlineCheckInController.java +++ b/src/main/java/alfio/controller/OnlineCheckInController.java @@ -20,7 +20,6 @@ import alfio.manager.ExtensionManager; import alfio.manager.TicketReservationManager; import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +30,7 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Optional; +import java.util.UUID; import static alfio.manager.support.CheckInStatus.ALREADY_CHECK_IN; import static alfio.manager.support.CheckInStatus.SUCCESS; @@ -45,25 +45,25 @@ public class OnlineCheckInController { private final CheckInManager checkInManager; private final ExtensionManager extensionManager; - @GetMapping("/event/{shortName}/ticket/{ticketUUID}/check-in/{ticketCodeHash}") + @GetMapping("/event/{shortName}/ticket/{publicUUID}/check-in/{ticketCodeHash}") public String performCheckIn(@PathVariable("shortName") String eventShortName, - @PathVariable String ticketUUID, + @PathVariable UUID publicUUID, @PathVariable String ticketCodeHash) { - return ticketReservationManager.fetchCompleteAndAssignedForOnlineCheckIn(eventShortName, ticketUUID) + return ticketReservationManager.fetchCompleteAndAssignedForOnlineCheckIn(eventShortName, publicUUID) .flatMap(data -> { var ticket = data.getTicket(); var event = data.getEventWithCheckInInfo(); String ticketCode = ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive()); if(MessageDigest.isEqual(DigestUtils.sha256Hex(ticketCode).getBytes(StandardCharsets.UTF_8), ticketCodeHash.getBytes(StandardCharsets.UTF_8))) { - log.debug("code successfully validated for ticket {}", ticketUUID); + log.debug("code successfully validated for ticket {}", publicUUID); // check-in can be done. Let's check if there is a redirection URL var categoryConfiguration = data.getCategoryMetadata().getOnlineConfiguration(); var eventConfiguration = event.getMetadata().getOnlineConfiguration(); var match = findMatchingLink(event.getZoneId(), categoryConfiguration, eventConfiguration); if(match.isPresent()) { var checkInStatus = checkInManager.performCheckinForOnlineEvent(ticket, event, data.getTicketCategory()); - log.info("check-in status {} for ticket {}", checkInStatus, ticketUUID); + log.info("check-in status {} for ticket {}", checkInStatus, publicUUID); if(checkInStatus == SUCCESS || (checkInStatus == ALREADY_CHECK_IN && ticket.isCheckedIn())) { // invoke the extension for customizing the URL, if any // we call the extension from here because it will have a smaller impact on the throughput compared to @@ -71,13 +71,13 @@ public String performCheckIn(@PathVariable("shortName") String eventShortName, var customUrlOptional = extensionManager.handleOnlineCheckInLink(match.get(), ticket, event, data.getTicketAdditionalInfo()); return customUrlOptional.or(() -> match); } - log.info("denying check-in for ticket {} because check-in status was {}", ticketUUID, checkInStatus); - return Optional.of("/event/"+event.getShortName()+"/ticket/"+ticketUUID+"/update"); + log.info("denying check-in for ticket {} because check-in status was {}", publicUUID, checkInStatus); + return Optional.of("/event/"+event.getShortName()+"/ticket/"+ publicUUID +"/update"); } - log.info("validation was successful, but cannot find a valid link for {}", ticketUUID); - return Optional.of("/event/"+event.getShortName()+"/ticket/"+ticketUUID+"/check-in/"+ticketCodeHash+"/waiting-room"); + log.info("validation was successful, but cannot find a valid link for {}", publicUUID); + return Optional.of("/event/"+event.getShortName()+"/ticket/"+ publicUUID +"/check-in/"+ticketCodeHash+"/waiting-room"); } - log.warn("code validation failed for ticket {}", ticketUUID); + log.warn("code validation failed for ticket {}", publicUUID); return Optional.empty(); }) .map(link -> "redirect:"+link) diff --git a/src/main/java/alfio/controller/api/admin/ResourceController.java b/src/main/java/alfio/controller/api/admin/ResourceController.java index ab35cbe338..20857da692 100644 --- a/src/main/java/alfio/controller/api/admin/ResourceController.java +++ b/src/main/java/alfio/controller/api/admin/ResourceController.java @@ -31,8 +31,8 @@ import alfio.util.TemplateManager; import alfio.util.TemplateResource; import com.samskivert.mustache.MustacheException; +import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; @@ -42,7 +42,6 @@ import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.*; -import jakarta.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -126,7 +125,7 @@ public void previewTemplate(@PathVariable TemplateResource name, @PathVariable S Organization organization = organizationRepository.getById(organizationId); Optional image = TemplateProcessor.extractImageModel(purchaseContext, fileUploadManager); Map model = name.prepareSampleModel(organization, purchaseContext, image); - String renderedTemplate = templateManager.renderString(purchaseContext, template.getFileAsString(), model, loc, name.getTemplateOutput()); + String renderedTemplate = templateManager.renderString(purchaseContext, new String(name.replaceTokens(template.getFile())), model, loc, name.getTemplateOutput()); if(MediaType.TEXT_PLAIN_VALUE.equals(name.getRenderedContentType()) || TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE.equals(name.getRenderedContentType())) { response.addHeader("Content-Disposition", "attachment; filename="+name.name()+".txt"); response.setContentType(MediaType.TEXT_PLAIN_VALUE); diff --git a/src/main/java/alfio/controller/api/pass/PassKitApiController.java b/src/main/java/alfio/controller/api/pass/PassKitApiController.java index 132114b75f..7e8057a91c 100644 --- a/src/main/java/alfio/controller/api/pass/PassKitApiController.java +++ b/src/main/java/alfio/controller/api/pass/PassKitApiController.java @@ -19,15 +19,14 @@ import alfio.manager.PassKitManager; import alfio.model.EventAndOrganizationId; import alfio.model.Ticket; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Optional; @@ -80,7 +79,7 @@ private void writePassResponse(HttpServletResponse response, try (var os = response.getOutputStream()) { response.setContentType("application/vnd.apple.pkpass"); if (addFilename) { - response.setHeader("Content-Disposition", "attachment; filename=Passbook-"+ticket.getUuid().substring(0, 8)+".pkpass"); + response.setHeader("Content-Disposition", "attachment; filename=Passbook-"+ticket.getPublicUuid().toString().substring(0, 8)+".pkpass"); markAsNoIndex(response); } passKitManager.writePass(ticket, eventAndOrganizationId, os); diff --git a/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java b/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java index 052f377598..4489d84c18 100644 --- a/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java +++ b/src/main/java/alfio/controller/api/support/BookingInfoTicketLoader.java @@ -101,7 +101,7 @@ public BookingInfoTicket toBookingInfoTicket(Ticket t, cancellationEnabled, configuration.get(SEND_TICKETS_AUTOMATICALLY).getValueAsBooleanOrDefault(), configuration.get(ALLOW_TICKET_DOWNLOAD).getValueAsBooleanOrDefault(), - additionalFieldsFilterer.getFieldsForTicket(t.getUuid(), contexts), + additionalFieldsFilterer.getFieldsForTicket(t.getPublicUuid(), contexts), descriptionsByTicketFieldId, valuesByTicketIds.getOrDefault(t.getId(), Collections.emptyList()), formattedOnlineCheckInDate, @@ -145,7 +145,7 @@ private static BookingInfoTicket toBookingInfoTicket(Ticket ticket, .flatMap(tfc -> toAdditionalFieldsStream(descriptionsByTicketFieldId, tfc, valuesById)) .collect(Collectors.toList()); - return new BookingInfoTicket(ticket.getUuid(), + return new BookingInfoTicket(ticket.getPublicUuid().toString(), ticket.getFirstName(), ticket.getLastName(), ticket.getEmail(), diff --git a/src/main/java/alfio/controller/api/support/TicketHelper.java b/src/main/java/alfio/controller/api/support/TicketHelper.java index 240395f1a8..d0a80d9c07 100644 --- a/src/main/java/alfio/controller/api/support/TicketHelper.java +++ b/src/main/java/alfio/controller/api/support/TicketHelper.java @@ -42,8 +42,6 @@ import java.text.Collator; import java.util.*; import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -74,17 +72,13 @@ public BiFunction> bu return EventUtil.retrieveFieldValues(ticketRepository, purchaseContextFieldManager, additionalServiceItemRepository, formatValues); } - public Function getTicketUUIDToCategoryId() { - return ticketRepository::getTicketCategoryByUIID; - } - public Optional> assignTicket(String eventName, - String ticketIdentifier, - UpdateTicketOwnerForm updateTicketOwner, - Optional bindingResult, - Locale fallbackLocale, - Optional userDetails, - boolean addPrefix) { + UUID ticketIdentifier, + UpdateTicketOwnerForm updateTicketOwner, + Optional bindingResult, + Locale fallbackLocale, + Optional userDetails, + boolean addPrefix) { return ticketReservationManager.fetchComplete(eventName, ticketIdentifier) .map(result -> assignTicket(updateTicketOwner, bindingResult, fallbackLocale, userDetails, result, addPrefix ? "tickets["+ticketIdentifier+"]" : "")); @@ -123,8 +117,8 @@ private Triple assignTicket(UpdateTicketOwnerFo Validator.AdvancedValidationContext context = new Validator.AdvancedValidationContext(updateTicketOwner, fieldConf, t.getCategoryId(), t.getUuid(), formPrefix); - ValidationResult validationResult = Validator.validateTicketAssignment(updateTicketOwner, ticketFieldFilterer.getFieldsForTicket(t.getUuid(), EnumSet.of(ATTENDEE)), bindingResult, event, formPrefix, sameCountryValidator, extensionManager) - .or(validateAdditionalItemsFields(event, updateTicketOwner, t.getUuid(), ticketFieldFilterer.getFieldsForTicket(t.getUuid(), EnumSet.of(ADDITIONAL_SERVICE)), additionalServiceItems, bindingResult.orElse(null), sameCountryValidator)) + ValidationResult validationResult = Validator.validateTicketAssignment(updateTicketOwner, ticketFieldFilterer.getFieldsForTicket(t.getPublicUuid(), EnumSet.of(ATTENDEE)), bindingResult, event, formPrefix, sameCountryValidator, extensionManager) + .or(validateAdditionalItemsFields(event, updateTicketOwner, t.getUuid(), ticketFieldFilterer.getFieldsForTicket(t.getPublicUuid(), EnumSet.of(ADDITIONAL_SERVICE)), additionalServiceItems, bindingResult.orElse(null), sameCountryValidator)) .or(Validator.performAdvancedValidation(advancedValidator, context, bindingResult.orElse(null))) .ifSuccess(() -> updateTicketOwner(updateTicketOwner, fallbackLocale, t, event, ticketReservation, userDetails)); return Triple.of(validationResult, event, ticketsInReservation.stream().filter(t2 -> t2.getUuid().equals(t.getUuid())).findFirst().orElseThrow()); @@ -177,18 +171,18 @@ private ValidationResult validateAdditionalItemsFields(Event event, */ public Optional> preAssignTicket(String eventName, String reservationId, - String ticketIdentifier, + UUID publicTicketUUID, UpdateTicketOwnerForm updateTicketOwner, Optional bindingResult, Locale fallbackLocale) { - return ticketReservationManager.from(eventName, reservationId, ticketIdentifier) + return ticketReservationManager.from(eventName, reservationId, publicTicketUUID) .filter(temp -> PENDING_RESERVATION_STATUSES.contains(temp.getMiddle().getStatus()) && temp.getRight().getStatus() == Ticket.TicketStatus.PENDING) - .map(result -> assignTicket(updateTicketOwner, bindingResult, fallbackLocale,Optional.empty(), result,"tickets["+ticketIdentifier+"]")); + .map(result -> assignTicket(updateTicketOwner, bindingResult, fallbackLocale,Optional.empty(), result,"tickets["+publicTicketUUID.toString()+"]")); } public Optional> assignTicket(String eventName, - String ticketIdentifier, + UUID ticketIdentifier, UpdateTicketOwnerForm updateTicketOwner, Optional bindingResult, Locale locale) { @@ -208,7 +202,7 @@ public Optional> directTicketAssignment( if(tickets.size() > 1) { return Optional.empty(); } - String ticketUuid = tickets.get(0).getUuid(); + var ticketUuid = tickets.get(0).getPublicUuid(); UpdateTicketOwnerForm form = new UpdateTicketOwnerForm(); form.setAdditional(Collections.emptyMap()); form.setEmail(email); diff --git a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java index e75e797622..de147e134c 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -437,14 +437,13 @@ public ResponseEntity> validateToOverview(@PathVariab ); } - if (purchaseContext.ofType(PurchaseContextType.event)) { - var event = (Event) purchaseContext; + if (purchaseContext instanceof Event event) { if (event.supportsLinkedAdditionalServices() && contactAndTicketsForm.hasAdditionalServices()) { var tickets = ticketReservationManager.findTicketsInReservation(reservationId); additionalServiceManager.linkItemsToTickets(reservationId, contactAndTicketsForm.getAdditionalServices(), tickets); additionalServiceManager.persistFieldsForAdditionalItems(event.getId(), event.getOrganizationId(), contactAndTicketsForm.getAdditionalServices(), tickets); } - assignTickets(event.getShortName(), reservationId, contactAndTicketsForm, bindingResult, locale, true, true); + assignTickets(event, reservationId, contactAndTicketsForm, bindingResult, locale, true, true); } if(purchaseContext.ofType(PurchaseContextType.subscription)) { @@ -518,14 +517,15 @@ private TicketReservationInvoicingAdditionalInfo.ItalianEInvoicing getItalianInv contactAndTicketsForm.isItalyEInvoicingSplitPayment()); } - private void assignTickets(String eventName, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, Locale locale, boolean preAssign, boolean skipValidation) { + private void assignTickets(Event event, String reservationId, ContactAndTicketsForm contactAndTicketsForm, BindingResult bindingResult, Locale locale, boolean preAssign, boolean skipValidation) { if(!contactAndTicketsForm.isPostponeAssignment()) { - contactAndTicketsForm.getTickets().forEach((ticketId, owner) -> { + contactAndTicketsForm.getTickets().forEach((ticketPublicId, owner) -> { + var publicUUID = UUID.fromString(ticketPublicId); if (preAssign) { Optional bindingResultOptional = skipValidation ? Optional.empty() : Optional.of(bindingResult); - ticketHelper.preAssignTicket(eventName, reservationId, ticketId, owner, bindingResultOptional, locale); + Validate.isTrue(ticketHelper.preAssignTicket(event.getShortName(), reservationId, publicUUID, owner, bindingResultOptional, locale).isPresent()); } else { - ticketHelper.assignTicket(eventName, ticketId, owner, Optional.of(bindingResult), locale, Optional.empty(), true); + Validate.isTrue(ticketHelper.assignTicket(event.getShortName(), publicUUID, owner, Optional.of(bindingResult), locale, Optional.empty(), true).isPresent()); } }); } 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 dcfe86a4c4..ae4e245492 100644 --- a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java @@ -39,6 +39,7 @@ import alfio.util.ImageUtil; import alfio.util.LocaleUtil; import alfio.util.TemplateManager; +import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; @@ -50,7 +51,6 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -60,6 +60,7 @@ import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.UUID; import static alfio.model.PurchaseContextFieldConfiguration.EVENT_RELATED_CONTEXTS; import static alfio.util.EventUtil.firstMatchingCallLink; @@ -90,7 +91,8 @@ public class TicketApiV2Controller { "/event/{eventName}/ticket/{ticketIdentifier}/code.png" }) public void showQrCode(@PathVariable String eventName, - @PathVariable String ticketIdentifier, HttpServletResponse response) throws IOException { + @PathVariable UUID ticketIdentifier, + HttpServletResponse response) throws IOException { var oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); if (oData.isEmpty()) { response.sendError(HttpServletResponse.SC_FORBIDDEN); @@ -112,7 +114,7 @@ public void showQrCode(@PathVariable String eventName, @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/download-ticket") public void generateTicketPdf(@PathVariable String eventName, - @PathVariable String ticketIdentifier, + @PathVariable UUID ticketIdentifier, HttpServletResponse response) { ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier).ifPresentOrElse(data -> { @@ -152,7 +154,7 @@ public void generateTicketPdf(@PathVariable String eventName, @PostMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/send-ticket-by-email") public ResponseEntity sendTicketByEmail(@PathVariable String eventName, - @PathVariable String ticketIdentifier) { + @PathVariable UUID ticketIdentifier) { return ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier).map(data -> { Event event = data.getLeft(); @@ -176,7 +178,7 @@ public ResponseEntity sendTicketByEmail(@PathVariable String eventName, @DeleteMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}") public ResponseEntity releaseTicket(@PathVariable String eventName, - @PathVariable String ticketIdentifier) { + @PathVariable UUID ticketIdentifier) { var oData = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier); try { oData.ifPresent(triple -> ticketReservationManager.releaseTicket(triple.getLeft(), triple.getMiddle(), triple.getRight())); @@ -188,7 +190,7 @@ public ResponseEntity releaseTicket(@PathVariable String eventName, @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/full") public ResponseEntity getTicket(@PathVariable String eventName, - @PathVariable String ticketIdentifier) { + @PathVariable UUID ticketIdentifier) { var optionalTicket = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier) .map(complete -> { @@ -203,7 +205,7 @@ public ResponseEntity getTicket(@PathVa @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}") public ResponseEntity getTicketInfo(@PathVariable String eventName, - @PathVariable String ticketIdentifier) { + @PathVariable UUID ticketIdentifier) { //TODO: cleanup, we load useless data here! @@ -237,7 +239,7 @@ public ResponseEntity getTicketInfo(@PathVariable String eventName, return ResponseEntity.ok(new TicketInfo( ticket.getFullName(), ticket.getEmail(), - ticket.getUuid(), + ticket.getPublicUuid().toString(), ticketCategory.getName(), ticketReservation.getFullName(), configurationManager.getShortReservationID(event, ticketReservation), @@ -255,7 +257,7 @@ public ResponseEntity getTicketInfo(@PathVariable String eventName, @PutMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}") public ResponseEntity> updateTicketInfo(@PathVariable String eventName, - @PathVariable String ticketIdentifier, + @PathVariable UUID ticketIdentifier, @RequestBody UpdateTicketOwnerForm updateTicketOwner, BindingResult bindingResult, Authentication authentication) { @@ -287,7 +289,7 @@ public ResponseEntity> updateTicketInfo(@PathVariable @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/code/{checkInCode}/check-in-info") public ResponseEntity getCheckInInfo(@PathVariable String eventName, - @PathVariable String ticketIdentifier, + @PathVariable UUID ticketIdentifier, @PathVariable String checkInCode, @RequestParam(value = "tz", required = false) String userTz) { return ResponseEntity.of(ticketReservationManager.fetchCompleteAndAssignedForOnlineCheckIn(eventName, ticketIdentifier) diff --git a/src/main/java/alfio/controller/form/ContactAndTicketsForm.java b/src/main/java/alfio/controller/form/ContactAndTicketsForm.java index 7b81c6184f..7411acbe25 100644 --- a/src/main/java/alfio/controller/form/ContactAndTicketsForm.java +++ b/src/main/java/alfio/controller/form/ContactAndTicketsForm.java @@ -119,7 +119,7 @@ public void validate(CustomBindingResult bindingResult, Optional> validationResults = Optional.ofNullable(tickets) .filter(m -> !m.isEmpty()) .map(m -> m.entrySet().stream().map(e -> { - var filteredForTicket = fieldsFilterer.getFieldsForTicket(e.getKey(), EnumSet.of(ATTENDEE)); + var filteredForTicket = fieldsFilterer.getFieldsForTicket(UUID.fromString(e.getKey()), EnumSet.of(ATTENDEE)); return Validator.validateTicketAssignment(e.getValue(), filteredForTicket, Optional.of(bindingResult), event, "tickets[" + e.getKey() + "]", vatValidator, extensionManager); })) .map(s -> s.collect(Collectors.toList())); @@ -164,7 +164,7 @@ private void checkAdditionalServiceItemsLink(Event event, } var result = ValidationResult.success(); for (var ticketAndFields : form.entrySet()) { - var filteredForTicket = additionalFieldsFilterer.getFieldsForTicket(ticketAndFields.getKey(), EnumSet.of(ADDITIONAL_SERVICE)); + var filteredForTicket = additionalFieldsFilterer.getFieldsForTicket(UUID.fromString(ticketAndFields.getKey()), EnumSet.of(ADDITIONAL_SERVICE)); var fieldForms = ticketAndFields.getValue(); for (int i = 0; i < fieldForms.size(); i++) { result = result.or(Validator.validateAdditionalItemFieldsForTicket(fieldForms.get(i), filteredForTicket, bindingResult, "additionalServices["+ticketAndFields.getKey()+"]["+i+"]", vatValidator, fieldForms, additionalServiceItems)); diff --git a/src/main/java/alfio/manager/AdditionalServiceManager.java b/src/main/java/alfio/manager/AdditionalServiceManager.java index 4919a66dd0..d73acbeddd 100644 --- a/src/main/java/alfio/manager/AdditionalServiceManager.java +++ b/src/main/java/alfio/manager/AdditionalServiceManager.java @@ -378,7 +378,7 @@ public void linkItemsToTickets(String reservationId, .flatMap(entry -> { var asl = entry.getValue(); Integer ticketId = tickets.stream() - .filter(t -> StringUtils.isNotEmpty(entry.getKey()) && t.getUuid().equals(entry.getKey())) + .filter(t -> StringUtils.isNotEmpty(entry.getKey()) && t.getPublicUuid().toString().equals(entry.getKey())) .findFirst() .map(Ticket::getId) .orElse(null); @@ -425,8 +425,8 @@ public void persistFieldsForAdditionalItems(int eventId, int organizationId, Map> additionalServices, List tickets) { - var ticketIdsByUuid = tickets.stream().collect(Collectors.toMap(Ticket::getUuid, Ticket::getId)); - int res = purchaseContextFieldRepository.deleteAllValuesForAdditionalItems(ticketIdsByUuid.values(), eventId); + var ticketIds = tickets.stream().map(Ticket::getId).collect(Collectors.toSet()); + int res = purchaseContextFieldRepository.deleteAllValuesForAdditionalItems(ticketIds, eventId); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Deleted {} field values", res); } @@ -448,7 +448,8 @@ public void persistFieldsForAdditionalItems(int eventId, .addValue("value", purchaseContextFieldRepository.getFieldValueJson(e2.getValue())) .addValue("organizationId", organizationId); }))).toArray(MapSqlParameterSource[]::new); - jdbcTemplate.batchUpdate(purchaseContextFieldRepository.batchInsertAdditionalItemsFields(), sources); + var results = jdbcTemplate.batchUpdate(purchaseContextFieldRepository.batchInsertAdditionalItemsFields(), sources); + Validate.isTrue(Arrays.stream(results).allMatch(r -> r == 1), "error while persisting additional fields"); } private void reserveAdditionalServicesForReservation(Event event, diff --git a/src/main/java/alfio/manager/CheckInManager.java b/src/main/java/alfio/manager/CheckInManager.java index cd64547796..54579e2587 100644 --- a/src/main/java/alfio/manager/CheckInManager.java +++ b/src/main/java/alfio/manager/CheckInManager.java @@ -129,7 +129,7 @@ public AttendeeSearchResults searchAttendees(Event event, String query, int page var priceContainer = TicketPriceContainer.from(ticket, reservation.getVatStatus(), reservation.getVAT(), event.getVatStatus(), reservation.getDiscount().orElse(null)); amountToPay = event.getCurrency() + " " + MonetaryUtil.formatUnit(priceContainer.getFinalPrice(), event.getCurrency()); } - return new AttendeeSearchResults.Attendee(ticket.getUuid(), ticket.getFirstName(), + return new AttendeeSearchResults.Attendee(ticket.getPublicUuid().toString(), ticket.getFirstName(), ticket.getLastName(), fi.getTicketCategory().getName(), fi.getTicketAdditionalInfo(), ticket.getStatus(), amountToPay); }).collect(Collectors.toList()); diff --git a/src/main/java/alfio/manager/ExtensionManager.java b/src/main/java/alfio/manager/ExtensionManager.java index 1a783f8e13..2e8ebb89a4 100644 --- a/src/main/java/alfio/manager/ExtensionManager.java +++ b/src/main/java/alfio/manager/ExtensionManager.java @@ -627,7 +627,7 @@ public Optional handleCustomTaxPolicy(PurchaseContext purchaseC var categoriesById = ticketCategoryRepository.findCategoriesInReservation(reservationId).stream() .collect(Collectors.toMap(TicketCategory::getId, Function.identity())); var ticketInfoById = ticketRepository.findBasicTicketInfoForReservation(event.getId(), reservationId).stream() - .collect(Collectors.toMap(TicketInfo::getTicketUuid, Function.identity())); + .collect(Collectors.toMap(ti -> ti.getTicketPublicUUID().toString(), Function.identity())); var context = new HashMap(); context.put(EVENT, event); context.put(RESERVATION_ID, reservationId); diff --git a/src/main/java/alfio/manager/ReverseChargeManager.java b/src/main/java/alfio/manager/ReverseChargeManager.java index 4c3db03974..1f88e76ce6 100644 --- a/src/main/java/alfio/manager/ReverseChargeManager.java +++ b/src/main/java/alfio/manager/ReverseChargeManager.java @@ -225,7 +225,7 @@ public void applyCustomTaxPolicy(PurchaseContext purchaseContext, var reservation = ticketReservationManager.findById(reservationId).orElseThrow(); var currencyCode = reservation.getCurrencyCode(); var ticketsInReservation = ticketRepository.findTicketsInReservation(reservationId).stream() - .collect(toMap(Ticket::getUuid, Function.identity())); + .collect(toMap(t -> t.getPublicUuid().toString(), Function.identity())); var ticketIds = ticketsInReservation.keySet(); if (customTaxPolicy.getTicketPolicies().stream().anyMatch(tp -> !ticketIds.contains(tp.getUuid()))) { log.warn("Error in custom tax policy: some tickets are not included in reservation {}", reservationId); diff --git a/src/main/java/alfio/manager/TicketReservationManager.java b/src/main/java/alfio/manager/TicketReservationManager.java index 7e872332e9..160b6b9543 100644 --- a/src/main/java/alfio/manager/TicketReservationManager.java +++ b/src/main/java/alfio/manager/TicketReservationManager.java @@ -939,17 +939,24 @@ private void reTransitionToPending(String reservationId) { } //check internal consistency between the 3 values + public Optional> from(String eventName, String reservationId, Ticket ticket) { + return eventRepository.findOptionalByShortName(eventName).flatMap(event -> + ticketReservationRepository.findOptionalReservationById(reservationId) + .map(reservation -> Triple.of(event, reservation, ticket))) + .filter(x -> { + Ticket t = x.getRight(); + Event e = x.getLeft(); + TicketReservation tr = x.getMiddle(); + return tr.getId().equals(t.getTicketsReservationId()) && e.getId() == t.getEventId(); + }); + } + public Optional> from(String eventName, String reservationId, String ticketIdentifier) { + return ticketRepository.findOptionalByUUID(ticketIdentifier).flatMap(ticket -> from(eventName, reservationId, ticket)); + } - return eventRepository.findOptionalByShortName(eventName).flatMap(event -> - ticketReservationRepository.findOptionalReservationById(reservationId).flatMap(reservation -> - ticketRepository.findOptionalByUUID(ticketIdentifier).flatMap(ticket -> Optional.of(Triple.of(event, reservation, ticket))))) - .filter(x -> { - Ticket t = x.getRight(); - Event e = x.getLeft(); - TicketReservation tr = x.getMiddle(); - return tr.getId().equals(t.getTicketsReservationId()) && e.getId() == t.getEventId(); - }); + public Optional> from(String eventName, String reservationId, UUID publicTicketUUID) { + return ticketRepository.findOptionalByPublicUUID(publicTicketUUID).flatMap(ticket -> from(eventName, reservationId, ticket)); } /** @@ -1173,11 +1180,6 @@ String reservationUrl(TicketReservation reservation, PurchaseContext purchaseCon return ReservationUtil.reservationUrl(reservation, purchaseContext, configurationManager); } - String ticketUrl(Event event, String ticketId) { - Ticket ticket = ticketRepository.findByUUID(ticketId); - return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticketId + "?lang=" + ticket.getUserLanguage(); - } - public String ticketOnlineCheckIn(Event event, String ticketId) { Ticket ticket = ticketRepository.findByUUID(ticketId); @@ -1257,7 +1259,7 @@ private void cleanupReferencesToReservation(boolean expired, String username, St log.debug("Deleted {} and updated {} additionalServiceItems for reservation {}", deletedItems, updatedItems, reservationId); int updatedAS = additionalServiceManager.updateStatusForReservationId(event.getId(), reservationId, expired ? AdditionalServiceItemStatus.EXPIRED : AdditionalServiceItemStatus.CANCELLED); int updatedTickets = ticketRepository.findTicketIdsInReservation(reservationId).stream().mapToInt( - tickedId -> ticketRepository.releaseExpiredTicket(reservationId, event.getId(), tickedId, UUID.randomUUID().toString()) + tickedId -> ticketRepository.releaseExpiredTicket(reservationId, event.getId(), tickedId, UUID.randomUUID().toString(), UUID.randomUUID()) ).sum(); Validate.isTrue(updatedTickets + updatedAS > 0, "no items have been updated"); }); @@ -1384,9 +1386,9 @@ private boolean isAdmin(Optional userDetails) { return userDetails.flatMap(u -> u.getAuthorities().stream().map(a -> Role.fromRoleName(a.getAuthority())).filter(Role.ADMIN::equals).findFirst()).isPresent(); } - public Optional> fetchComplete(String eventName, String ticketIdentifier) { - return ticketRepository.findOptionalByUUID(ticketIdentifier) - .flatMap(ticket -> from(eventName, ticket.getTicketsReservationId(), ticketIdentifier) + public Optional> fetchComplete(String eventName, UUID ticketPublicUUID) { + return ticketRepository.findOptionalByPublicUUID(ticketPublicUUID) + .flatMap(ticket -> from(eventName, ticket.getTicketsReservationId(), ticket) .flatMap(triple -> { if(triple.getMiddle().getStatus() == TicketReservationStatus.COMPLETE) { return Optional.of(triple); @@ -1397,13 +1399,13 @@ public Optional> fetchComplete(String e } /** - * Return a fully present triple only if the values are present (obviously) and the the reservation has a COMPLETE status and the ticket is considered assigned. + * Return a fully present triple only if the values are present (obviously) and the reservation has a COMPLETE status and the ticket is considered assigned. * * @param eventName * @param ticketIdentifier * @return */ - public Optional> fetchCompleteAndAssigned(String eventName, String ticketIdentifier) { + public Optional> fetchCompleteAndAssigned(String eventName, UUID ticketIdentifier) { return fetchComplete(eventName, ticketIdentifier).flatMap(t -> { if (t.getRight().getAssigned()) { return Optional.of(t); @@ -1413,8 +1415,8 @@ public Optional> fetchCompleteAndAssign }); } - public Optional fetchCompleteAndAssignedForOnlineCheckIn(String eventName, String ticketIdentifier) { - return ticketRepository.getFullInfoForOnlineCheckin(eventName, ticketIdentifier); + public Optional fetchCompleteAndAssignedForOnlineCheckIn(String eventName, UUID publicUUID) { + return ticketRepository.getFullInfoForOnlineCheckin(eventName, publicUUID); } public void sendReminderForOfflinePayments() { @@ -1574,7 +1576,7 @@ public void releaseTicket(Event event, TicketReservation ticketReservation, fina String reservationId = ticketReservation.getId(); //#365 - reset UUID when releasing a ticket - int result = ticketRepository.releaseTicket(reservationId, UUID.randomUUID().toString(), event.getId(), ticket.getId()); + int result = ticketRepository.releaseTicket(reservationId, UUID.randomUUID().toString(), UUID.randomUUID(), event.getId(), ticket.getId()); Validate.isTrue(result == 1, "Expected 1 row to be updated, got %d".formatted(result)); if(category.isAccessRestricted() || !category.isBounded()) { ticketRepository.unbindTicketsFromCategory(event.getId(), category.getId(), singletonList(ticket.getId())); diff --git a/src/main/java/alfio/manager/support/CustomMessageManager.java b/src/main/java/alfio/manager/support/CustomMessageManager.java index f5061b0744..7b914d6d64 100644 --- a/src/main/java/alfio/manager/support/CustomMessageManager.java +++ b/src/main/java/alfio/manager/support/CustomMessageManager.java @@ -99,7 +99,7 @@ public void sendMessages(String eventName, Optional categoryId, List getModelForTicket(Ticket ticket, TicketReservation reservation, TicketCategory ticketCategory, Organization organization) { diff --git a/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java b/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java index 47db1de701..1d21a1722e 100644 --- a/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java +++ b/src/main/java/alfio/manager/support/reservation/ReservationEmailContentHelper.java @@ -263,7 +263,7 @@ public Map prepareModelForReservationEmail(PurchaseContext purch if(purchaseContext.getType() == PurchaseContext.PurchaseContextType.event) { var event = purchaseContext.event().orElseThrow(); model.put("displayLocation", ticketsWithCategory.stream() - .noneMatch(tc -> EventUtil.isAccessOnline(tc.getCategory(), event))); + .noneMatch(tc -> EventUtil.isAccessOnline(tc.category(), event))); } else { model.put("displayLocation", false); } diff --git a/src/main/java/alfio/model/DetailedScanData.java b/src/main/java/alfio/model/DetailedScanData.java index 0653d741fa..25fe0d0dcb 100644 --- a/src/main/java/alfio/model/DetailedScanData.java +++ b/src/main/java/alfio/model/DetailedScanData.java @@ -32,6 +32,7 @@ public class DetailedScanData { public DetailedScanData(@Column("t_id") int ticketId, @Column("t_uuid") String ticketUuid, + @Column("t_public_uuid") UUID publicUuid, @Column("t_creation") ZonedDateTime ticketCreation, @Column("t_category_id") int ticketCategoryId, @Column("t_status") String ticketStatus, @@ -62,7 +63,7 @@ public DetailedScanData(@Column("t_id") int ticketId, @Column("s_notes") String notes, @Column("s_lead_status") SponsorScan.LeadStatus leadStatus, @Column("s_operator") String operator) { - this.ticket = new Ticket(ticketId, ticketUuid, ticketCreation, ticketCategoryId, + this.ticket = new Ticket(ticketId, ticketUuid, publicUuid, ticketCreation, ticketCategoryId, ticketStatus, ticketEventId, ticketsReservationId, ticketFullName, ticketFirstName, ticketLastName, ticketEmail, ticketLockedAssignment, ticketUserLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, ticketTags, diff --git a/src/main/java/alfio/model/FullTicketInfo.java b/src/main/java/alfio/model/FullTicketInfo.java index cba542ebde..2339f3e64d 100644 --- a/src/main/java/alfio/model/FullTicketInfo.java +++ b/src/main/java/alfio/model/FullTicketInfo.java @@ -44,6 +44,7 @@ public class FullTicketInfo implements TicketInfoContainer { public FullTicketInfo(@Column("t_id") int id, @Column("t_uuid") String uuid, + @Column("t_public_uuid") UUID publicUuid, @Column("t_creation") ZonedDateTime creation, @Column("t_category_id") int categoryId, @Column("t_status") String status, @@ -127,7 +128,7 @@ public FullTicketInfo(@Column("t_id") int id, @Column("tc_ticket_access_type") TicketCategory.TicketAccessType ticketAccessType ) { - this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, + this.ticket = new Ticket(id, uuid, publicUuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, ticketTags, ticketSubscriptionId, ticketVatStatus); this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, diff --git a/src/main/java/alfio/model/Ticket.java b/src/main/java/alfio/model/Ticket.java index caf9d7377a..cce38a96a1 100644 --- a/src/main/java/alfio/model/Ticket.java +++ b/src/main/java/alfio/model/Ticket.java @@ -20,6 +20,7 @@ import alfio.util.MonetaryUtil; import alfio.util.checkin.NameNormalizer; import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.apache.commons.codec.digest.HmacAlgorithms; @@ -40,6 +41,7 @@ public enum TicketStatus { private final int id; private final String uuid; + private final UUID publicUuid; private final ZonedDateTime creation; private final Integer categoryId; private final int eventId; @@ -65,6 +67,7 @@ public enum TicketStatus { public Ticket(@JsonProperty("id") @Column("id") int id, @JsonProperty("uuid") @Column("uuid") String uuid, + @JsonProperty("publicUuid") @Column("public_uuid") UUID publicUuid, @JsonProperty("creation") @Column("creation") ZonedDateTime creation, @JsonProperty("categoryId") @Column("category_id") Integer categoryId, @JsonProperty("status") @Column("status") String status, @@ -87,6 +90,7 @@ public Ticket(@JsonProperty("id") @Column("id") int id, @JsonProperty("vatStatus") @Column("vat_status") PriceContainer.VatStatus vatStatus) { this.id = id; this.uuid = uuid; + this.publicUuid = publicUuid; this.creation = creation; this.categoryId = categoryId; this.eventId = eventId; @@ -174,6 +178,7 @@ public Ticket withVatStatus(PriceContainer.VatStatus newVatStatus) { return new Ticket( id, uuid, + publicUuid, creation, categoryId, status.name(), diff --git a/src/main/java/alfio/model/TicketInfo.java b/src/main/java/alfio/model/TicketInfo.java index 0209b5c51f..98136822fc 100644 --- a/src/main/java/alfio/model/TicketInfo.java +++ b/src/main/java/alfio/model/TicketInfo.java @@ -19,11 +19,14 @@ import ch.digitalfondue.npjt.ConstructorAnnotationRowMapper.Column; import lombok.Getter; +import java.util.UUID; + @Getter public class TicketInfo { private final int ticketId; private final String ticketUuid; + private final UUID ticketPublicUUID; private final int ticketCategoryId; private final boolean ticketCategoryBounded; private final PriceContainer.VatStatus taxPolicy; @@ -31,11 +34,13 @@ public class TicketInfo { public TicketInfo(@Column("t_id") int id, @Column("t_uuid") String ticketUuid, + @Column("t_public_uuid") UUID ticketPublicUUID, @Column("tc_id") int tcId, @Column("tc_bounded") boolean bounded, @Column("t_vat_status") PriceContainer.VatStatus taxPolicy) { this.ticketId = id; this.ticketUuid = ticketUuid; + this.ticketPublicUUID = ticketPublicUUID; this.ticketCategoryId = tcId; this.ticketCategoryBounded = bounded; this.taxPolicy = taxPolicy; diff --git a/src/main/java/alfio/model/TicketWithCategory.java b/src/main/java/alfio/model/TicketWithCategory.java index 2fb97e48da..db071bec53 100644 --- a/src/main/java/alfio/model/TicketWithCategory.java +++ b/src/main/java/alfio/model/TicketWithCategory.java @@ -16,26 +16,70 @@ */ package alfio.model; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Delegate; +import com.fasterxml.jackson.annotation.JsonIgnore; -@RequiredArgsConstructor -public class TicketWithCategory implements TicketInfoContainer { - - @Delegate - private final Ticket ticket; - private final TicketCategory category; +public record TicketWithCategory(@JsonIgnore Ticket ticket, @JsonIgnore TicketCategory category) implements TicketInfoContainer { public String getCategoryName() { return category != null ? category.getName() : null; } - public TicketCategory getCategory() { - return category; + @Override + public boolean getAssigned() { + return ticket.getAssigned(); + } + + @Override + public boolean isCheckedIn() { + return ticket.isCheckedIn(); + } + + @Override + public int getId() { + return ticket.getId(); + } + + @Override + public String getUuid() { + return ticket.getUuid(); + } + + @Override + public int getEventId() { + return ticket.getEventId(); + } + + @Override + public String getTicketsReservationId() { + return ticket.getTicketsReservationId(); + } + + @Override + public String getFirstName() { + return ticket.getFirstName(); + } + + @Override + public String getLastName() { + return ticket.getLastName(); } - protected Ticket getTicket() { - return ticket; + @Override + public String getEmail() { + return ticket.getEmail(); } + @Override + public String getUserLanguage() { + return ticket.getUserLanguage(); + } + + @Override + public Integer getCategoryId() { + return ticket.getCategoryId(); + } + + public String getFullName() { + return ticket.getFullName(); + } } diff --git a/src/main/java/alfio/model/TicketWithMetadataAttributes.java b/src/main/java/alfio/model/TicketWithMetadataAttributes.java index 70f4c457f3..add2a0787b 100644 --- a/src/main/java/alfio/model/TicketWithMetadataAttributes.java +++ b/src/main/java/alfio/model/TicketWithMetadataAttributes.java @@ -32,6 +32,7 @@ public class TicketWithMetadataAttributes { public TicketWithMetadataAttributes(@Column("id") int id, @Column("uuid") String uuid, + @Column("public_uuid") UUID publicUuid, @Column("creation") ZonedDateTime creation, @Column("category_id") Integer categoryId, @Column("status") String status, @@ -53,7 +54,7 @@ public TicketWithMetadataAttributes(@Column("id") int id, @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), + this(new Ticket(id, uuid, publicUuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, srcPriceCts, finalPriceCts, vatCts, discountCts, extReference, currencyCode, tags, subscriptionId, vatStatus), ticketMetadataContainer); } diff --git a/src/main/java/alfio/model/TicketWithReservationAndTransaction.java b/src/main/java/alfio/model/TicketWithReservationAndTransaction.java index 08e1d59c5f..605f1cee53 100644 --- a/src/main/java/alfio/model/TicketWithReservationAndTransaction.java +++ b/src/main/java/alfio/model/TicketWithReservationAndTransaction.java @@ -42,6 +42,7 @@ public class TicketWithReservationAndTransaction { public TicketWithReservationAndTransaction(@Column("t_id") Integer id, @Column("t_uuid") String uuid, + @Column("t_public_uuid") UUID publicUuid, @Column("t_creation") ZonedDateTime creation, @Column("t_category_id") Integer categoryId, @Column("t_status") String status, @@ -124,7 +125,7 @@ public TicketWithReservationAndTransaction(@Column("t_id") Integer id, @Column("special_price_token") String specialPriceToken ) { - this.ticket = id != null ? new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, + this.ticket = id != null ? new Ticket(id, uuid, publicUuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, srcPriceCts, finalPriceCts, vatCts, discountCts, extReference, currencyCode, ticketTags, ticketSubscriptionId, vatStatus) : null; diff --git a/src/main/java/alfio/model/checkin/CheckInFullInfo.java b/src/main/java/alfio/model/checkin/CheckInFullInfo.java index 569d59fcaf..95f9f58c7b 100644 --- a/src/main/java/alfio/model/checkin/CheckInFullInfo.java +++ b/src/main/java/alfio/model/checkin/CheckInFullInfo.java @@ -45,6 +45,7 @@ public class CheckInFullInfo { public CheckInFullInfo(@Column("t_id") int id, @Column("t_uuid") String uuid, + @Column("t_public_uuid") UUID publicUuid, @Column("t_creation") ZonedDateTime creation, @Column("t_category_id") int categoryId, @Column("t_status") String status, @@ -143,7 +144,7 @@ public CheckInFullInfo(@Column("t_id") int id, @Column("tai_additional_info") @JSONData Map> ticketAdditionalInfo ) { - this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, + this.ticket = new Ticket(id, uuid, publicUuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference, currencyCode, ticketTags, ticketSubscriptionId, ticketVatStatus); this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, diff --git a/src/main/java/alfio/model/support/TicketWithAdditionalFields.java b/src/main/java/alfio/model/support/TicketWithAdditionalFields.java index 6773d968d8..11d8b1324c 100644 --- a/src/main/java/alfio/model/support/TicketWithAdditionalFields.java +++ b/src/main/java/alfio/model/support/TicketWithAdditionalFields.java @@ -16,29 +16,51 @@ */ package alfio.model.support; -import alfio.model.EventAndOrganizationId; import alfio.model.FieldConfigurationDescriptionAndValue; import alfio.model.Ticket; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.AllArgsConstructor; -import lombok.experimental.Delegate; import org.apache.commons.lang3.tuple.Pair; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@AllArgsConstructor public class TicketWithAdditionalFields { - @Delegate(excludes = EventAndOrganizationId.class) - @JsonIgnore private final Ticket ticket; - @JsonIgnore private final List additionalFieldsDescriptionsAndValues; + public TicketWithAdditionalFields(Ticket ticket, + List additionalFieldsDescriptionsAndValues) { + this.ticket = ticket; + this.additionalFieldsDescriptionsAndValues = additionalFieldsDescriptionsAndValues; + } + public Map getAdditionalFields() { return additionalFieldsDescriptionsAndValues.stream() .map(af -> Pair.of(af.getLabelDescription(), af.getValueDescription())) .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); } + + public String getUuid() { + return ticket.getUuid(); + } + + public String getFullName() { + return ticket.getFullName(); + } + + public String getFirstName() { + return ticket.getFirstName(); + } + + public String getLastName() { + return ticket.getLastName(); + } + + public String getEmail() { + return ticket.getEmail(); + } + + public String getUserLanguage() { + return ticket.getUserLanguage(); + } } diff --git a/src/main/java/alfio/repository/PurchaseContextFieldRepository.java b/src/main/java/alfio/repository/PurchaseContextFieldRepository.java index 2ea56a76e0..54a8e32cae 100644 --- a/src/main/java/alfio/repository/PurchaseContextFieldRepository.java +++ b/src/main/java/alfio/repository/PurchaseContextFieldRepository.java @@ -154,8 +154,7 @@ default void updateOrInsert(Map> values, PurchaseContext pu Map toUpdate; values = Optional.ofNullable(values).orElseGet(Collections::emptyMap); List additionalFields; - if (purchaseContext.ofType(PurchaseContextType.event)) { - Event event = (Event) purchaseContext; + if (purchaseContext instanceof Event event) { additionalFields = findAdditionalFieldsForEvent(event.getId()); toUpdate = findAllByTicketIdGroupedByName(ticketId, event.supportsLinkedAdditionalServices()); } else { diff --git a/src/main/java/alfio/repository/SponsorScanRepository.java b/src/main/java/alfio/repository/SponsorScanRepository.java index cf6e9a7e56..41794cc03e 100644 --- a/src/main/java/alfio/repository/SponsorScanRepository.java +++ b/src/main/java/alfio/repository/SponsorScanRepository.java @@ -54,7 +54,7 @@ int updateNotesAndLeadStatus(@Bind("userId") int userId, @Bind("operator") String operator); @Query(""" - select t.id t_id, t.uuid t_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, t.event_id t_event_id,\ + select t.id t_id, t.uuid t_uuid, t.public_uuid t_public_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, t.event_id t_event_id,\ t.src_price_cts t_src_price_cts, t.final_price_cts t_final_price_cts, t.vat_cts t_vat_cts, t.discount_cts t_discount_cts, t.tickets_reservation_id t_tickets_reservation_id,\ t.full_name t_full_name, t.first_name t_first_name, t.last_name t_last_name, t.email_address t_email_address, t.locked_assignment t_locked_assignment,\ t.user_language t_user_language, t.ext_reference t_ext_reference, t.currency_code t_currency_code, t.tags t_tags, t.subscription_id_fk t_subscription_id, t.vat_status t_vat_status,\ diff --git a/src/main/java/alfio/repository/TicketRepository.java b/src/main/java/alfio/repository/TicketRepository.java index a9bc1624fd..b1d7300cf0 100644 --- a/src/main/java/alfio/repository/TicketRepository.java +++ b/src/main/java/alfio/repository/TicketRepository.java @@ -50,16 +50,16 @@ public interface TicketRepository { 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"; - String FIND_BASIC_TICKET_INFO_BY_EVENT_ID = "select t.id t_id, t.uuid t_uuid, tc.id tc_id, tc.bounded tc_bounded, t.vat_status t_vat_status from ticket t inner join ticket_category tc on t.category_id = tc.id where t.event_id = :eventId"; + String RELEASE_TICKET_QUERY = "update ticket set status = 'RELEASED', uuid = :newUuid, public_uuid = :newPublicUuid, " + RESET_TICKET + " where id = :ticketId and status in('ACQUIRED', 'PENDING', 'TO_BE_PAID') and tickets_reservation_id = :reservationId and event_id = :eventId"; + String FIND_BASIC_TICKET_INFO_BY_EVENT_ID = "select t.id t_id, t.uuid t_uuid, t.public_uuid t_public_uuid, tc.id tc_id, tc.bounded tc_bounded, t.vat_status t_vat_status from ticket t inner join ticket_category tc on t.category_id = tc.id where t.event_id = :eventId"; String UPDATE_TICKET_PRICE = "update ticket set src_price_cts = :srcPriceCts, final_price_cts = :finalPriceCts, vat_cts = :vatCts, discount_cts = :discountCts, currency_code = :currencyCode, vat_status = :vatStatus::VAT_STATUS where event_id = :eventId and category_id = :categoryId"; //TODO: refactor, try to move the MapSqlParameterSource inside the default method! default void bulkTicketInitialization(MapSqlParameterSource[] args) { getNamedParameterJdbcTemplate().batchUpdate(""" - insert into ticket (uuid, creation, category_id, event_id, status, original_price_cts, paid_price_cts, src_price_cts)\ - values(:uuid, :creation, :categoryId, :eventId, :status, 0, 0, :srcPriceCts)\ + insert into ticket (uuid, public_uuid, creation, category_id, event_id, status, original_price_cts, paid_price_cts, src_price_cts) + values(:uuid, :publicUuid, :creation, :categoryId, :eventId, :status, 0, 0, :srcPriceCts) """, args); } @@ -259,12 +259,21 @@ int updateTicketPrice(@Bind("ids") List ids, @Query("select * from ticket where uuid = :uuid") Ticket findByUUID(@Bind("uuid") String uuid); + @Query("select * from ticket where public_uuid = :publicUuid") + Ticket findByPublicUUID(@Bind("publicUuid") UUID publicUuid); + + @Query("select uuid from ticket where public_uuid = :publicUuid and event_id = :eventId") + Optional getInternalUuid(@Bind("publicUuid") UUID publicUuid, @Bind("eventId") int eventId); + @Query("select category_id from ticket where uuid = :uuid") - Integer getTicketCategoryByUIID(@Bind("uuid") String uuid); + Integer getTicketCategoryByUUID(@Bind("uuid") String uuid); @Query("select * from ticket where uuid = :uuid") Optional findOptionalByUUID(@Bind("uuid") String uuid); + @Query("select * from ticket where public_uuid = :publicUuid") + Optional findOptionalByPublicUUID(@Bind("publicUuid") UUID uuid); + @Query("select * from ticket where uuid = :uuid for update") Optional findByUUIDForUpdate(@Bind("uuid") String uuid); @@ -318,7 +327,7 @@ int updateTicketPrice(@Bind("ids") List ids, String FIND_FULL_TICKET_INFO = """ select \ - t.id t_id, t.uuid t_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, t.event_id t_event_id,\ + t.id t_id, t.uuid t_uuid, t.public_uuid t_public_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, t.event_id t_event_id,\ t.src_price_cts t_src_price_cts, t.final_price_cts t_final_price_cts, t.vat_cts t_vat_cts, t.discount_cts t_discount_cts, t.tickets_reservation_id t_tickets_reservation_id,\ t.full_name t_full_name, t.first_name t_first_name, t.last_name t_last_name, t.email_address t_email_address, t.locked_assignment t_locked_assignment,\ t.user_language t_user_language, t.ext_reference t_ext_reference, t.currency_code t_currency_code, t.tags t_tags, t.subscription_id_fk t_subscription_id, t.vat_status t_vat_status, \ @@ -378,25 +387,31 @@ default Set findAllReservationsConfirmedButNotAssignedForUpdate(int even int flagTicketAsReminderSent(@Bind("id") int ticketId); @Query(RELEASE_TICKET_QUERY) - int releaseTicket(@Bind("reservationId") String reservationId, @Bind("newUuid") String newUuid, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId); + int releaseTicket(@Bind("reservationId") String reservationId, @Bind("newUuid") String newUuid, @Bind("newPublicUuid") UUID newPublicUuid, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId); default int[] batchReleaseTickets(String reservationId, List ticketIds, Event event) { MapSqlParameterSource[] args = ticketIds.stream().map(id -> new MapSqlParameterSource("ticketId", id) .addValue("reservationId", reservationId) .addValue("eventId", event.getId()) .addValue("newUuid", UUID.randomUUID().toString()) + .addValue("newPublicUuid", UUID.randomUUID()) ).toArray(MapSqlParameterSource[]::new); return getNamedParameterJdbcTemplate().batchUpdate(RELEASE_TICKET_QUERY, args); } - @Query("update ticket set status = 'RELEASED', uuid = :newUuid, " + RESET_TICKET + " where id = :ticketId and status = 'PENDING' and tickets_reservation_id = :reservationId and event_id = :eventId") - int releaseExpiredTicket(@Bind("reservationId") String reservationId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("newUuid") String newUuid); + @Query("update ticket set status = 'RELEASED', uuid = :newUuid, public_uuid = :newPublicUuid, " + RESET_TICKET + " where id = :ticketId and status = 'PENDING' and tickets_reservation_id = :reservationId and event_id = :eventId") + int releaseExpiredTicket(@Bind("reservationId") String reservationId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId, @Bind("newUuid") String newUuid, @Bind("newPublicUuid") UUID newPublicUuid); NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(); default void resetTickets(List ticketIds) { - MapSqlParameterSource[] params = ticketIds.stream().map(ticketId -> new MapSqlParameterSource("ticketId", ticketId).addValue("newUuid", UUID.randomUUID().toString())).toArray(MapSqlParameterSource[]::new); - getNamedParameterJdbcTemplate().batchUpdate("update ticket set status = 'RELEASED', uuid = :newUuid, " + RESET_TICKET + " where id = :ticketId", params); + MapSqlParameterSource[] params = ticketIds.stream() + .map(ticketId -> new MapSqlParameterSource("ticketId", ticketId) + .addValue("newUuid", UUID.randomUUID().toString()) + .addValue("newPublicUuid", UUID.randomUUID()) + ) + .toArray(MapSqlParameterSource[]::new); + getNamedParameterJdbcTemplate().batchUpdate("update ticket set status = 'RELEASED', uuid = :newUuid, public_uuid = :newPublicUuid, " + RESET_TICKET + " where id = :ticketId", params); } @Query("select count(*) from ticket where status = 'RELEASED' and event_id = :eventId") @@ -445,8 +460,8 @@ default void preReserveTicket(List ids) { @Query("select distinct category_id from ticket where tickets_reservation_id = :reservationId and src_price_cts > 0") List getCategoriesIdToPayInReservation(@Bind("reservationId") String reservationId); - @Query("select * from checkin_ticket_event_and_category_info where t_uuid = :ticketUUID and e_short_name = :eventShortName and (e_format = 'ONLINE' or tc_ticket_access_type = 'ONLINE') ") - Optional getFullInfoForOnlineCheckin(@Bind("eventShortName") String eventShortName, @Bind("ticketUUID") String ticketUUID); + @Query("select * from checkin_ticket_event_and_category_info where t_public_uuid = :publicUUID and e_short_name = :eventShortName and (e_format = 'ONLINE' or tc_ticket_access_type = 'ONLINE') ") + Optional getFullInfoForOnlineCheckin(@Bind("eventShortName") String eventShortName, @Bind("publicUUID") UUID publicUUID); @Query("select * from checkin_ticket_event_and_category_info where e_id = :eventId " + "and (" + TicketSearchRepository.BASE_FILTER + " or lower(tc_name) like lower(:search)) "+ diff --git a/src/main/java/alfio/repository/TicketSearchRepository.java b/src/main/java/alfio/repository/TicketSearchRepository.java index 8d09c85eec..45bdcd39d2 100644 --- a/src/main/java/alfio/repository/TicketSearchRepository.java +++ b/src/main/java/alfio/repository/TicketSearchRepository.java @@ -35,7 +35,7 @@ public interface TicketSearchRepository { String BASE_FILTER = """ - :search is null or (lower(tr_id) like lower(:search) or lower(t_uuid) like lower(:search) or lower(t_full_name) like lower(:search) or lower(t_first_name) like lower(:search) or lower(t_last_name) like lower(:search) or lower(t_email_address) like lower(:search) or \ + :search is null or (lower(tr_id) like lower(:search) or lower(t_uuid) like lower(:search) or lower(t_public_uuid::text) like lower(:search) or lower(t_full_name) like lower(:search) or lower(t_first_name) like lower(:search) or lower(t_last_name) like lower(:search) or lower(t_email_address) like lower(:search) or \ lower(tr_full_name) like lower(:search) or lower(tr_first_name) like lower(:search) or lower(tr_last_name) like lower(:search) or lower(tr_email_address) like lower(:search) or lower(tr_customer_reference) like lower(:search) \ or (tr_invoice_number is not null and lower(tr_invoice_number) like lower(:search)) )\ """; diff --git a/src/main/java/alfio/util/EventUtil.java b/src/main/java/alfio/util/EventUtil.java index 512f8a2004..ccbe831853 100644 --- a/src/main/java/alfio/util/EventUtil.java +++ b/src/main/java/alfio/util/EventUtil.java @@ -147,6 +147,7 @@ private static MapSqlParameterSource buildTicketParams(int eventId, MapSqlParameterSource ps, Ticket.TicketStatus ticketStatus) { return ps.addValue("uuid", UUID.randomUUID().toString()) + .addValue("publicUuid", UUID.randomUUID()) .addValue("creation", creation) .addValue("categoryId", tc.map(TicketCategory::getId).orElse(null)) .addValue("eventId", eventId) diff --git a/src/main/java/alfio/util/ReservationUtil.java b/src/main/java/alfio/util/ReservationUtil.java index f7e7ebaf15..2159317a80 100644 --- a/src/main/java/alfio/util/ReservationUtil.java +++ b/src/main/java/alfio/util/ReservationUtil.java @@ -171,7 +171,7 @@ public static boolean hasPrivacyPolicy(PurchaseContext event) { } public static String ticketUpdateUrl(Event event, Ticket ticket, ConfigurationManager configurationManager) { - return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticket.getUuid() + "/update?lang=" + ticket.getUserLanguage(); + return configurationManager.baseUrl(event) + "/event/" + event.getShortName() + "/ticket/" + ticket.getPublicUuid().toString() + "/update?lang=" + ticket.getUserLanguage(); } public static String reservationUrl(String baseUrl, String reservationId, diff --git a/src/main/java/alfio/util/TemplateManager.java b/src/main/java/alfio/util/TemplateManager.java index 02b192153c..12b23ad1cc 100644 --- a/src/main/java/alfio/util/TemplateManager.java +++ b/src/main/java/alfio/util/TemplateManager.java @@ -28,7 +28,6 @@ import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache.Compiler; import com.samskivert.mustache.Template; -import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -131,7 +130,7 @@ private RenderedTemplate renderMultipartTemplate(PurchaseContext purchaseContext public RenderedTemplate renderTemplate(PurchaseContext purchaseContext, TemplateResource templateResource, Map model, Locale locale) { Map updatedModel = modelEnricher(model, purchaseContext, locale); return uploadedResourceManager.findCascading(purchaseContext.getOrganizationId(), purchaseContext.event().map(Event::getId).orElse(null), templateResource.getSavedName(locale)) - .map(resource -> RenderedTemplate.plaintext(render(new ByteArrayResource(resource), updatedModel, locale, purchaseContext, templateResource.getTemplateOutput()), model)) + .map(resource -> RenderedTemplate.plaintext(render(new ByteArrayResource(templateResource.replaceTokens(resource)), updatedModel, locale, purchaseContext, templateResource.getTemplateOutput()), model)) .orElseGet(() -> renderMultipartTemplate(purchaseContext, templateResource, updatedModel, locale)); } diff --git a/src/main/java/alfio/util/TemplateResource.java b/src/main/java/alfio/util/TemplateResource.java index 64dfe4ead8..b8970bfe0c 100644 --- a/src/main/java/alfio/util/TemplateResource.java +++ b/src/main/java/alfio/util/TemplateResource.java @@ -35,10 +35,12 @@ import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.*; import java.util.function.Function; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static alfio.util.ImageUtil.createQRCode; @@ -80,7 +82,7 @@ public Map prepareSampleModel(Organization organization, Purchas model.put("pin", "ABCDE"); model.put("subscriptionId", UUID.randomUUID().toString()); model.put("includePin", true); - model.put("fullName", "Firstname Lastname"); + model.put(FULL_NAME, "Firstname Lastname"); return model; } }, @@ -119,7 +121,7 @@ public Map prepareSampleModel(Organization organization, Purchas public Map prepareSampleModel(Organization organization, PurchaseContext event, Optional imageData) { return Map.of( "matchingCount", 3, - "eventName", event.getDisplayName(), + EVENT_NAME, event.getDisplayName(), "pendingReviewMatches", true, "pendingReview", List.of(UUID.randomUUID().toString()), "automaticApprovedMatches", true, @@ -152,20 +154,16 @@ public Map prepareSampleModel(Organization organization, Purchas TICKET_EMAIL("/alfio/templates/ticket-email", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override public Map prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional imageData) { - Event event = (Event) purchaseContext; - 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); - return buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of()); + var result = buildTicket((Event) purchaseContext); + return buildModelForTicketEmail(organization, result.event(), sampleTicketReservation(result.event().getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(result.event().getZoneId()), result.ticketCategory(), Map.of()); } }, TICKET_EMAIL_FOR_ONLINE_EVENT("/alfio/templates/ticket-email-online", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override public Map prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional imageData) { - Event event = (Event) purchaseContext; - 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); - return buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of("onlineCheckInUrl", "https://your-domain.tld/check-in", "prerequisites", "An internet connection is required to join the event")); + var result = buildTicket((Event) purchaseContext); + return buildModelForTicketEmail(organization, result.event, sampleTicketReservation(result.event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(result.event.getZoneId()), result.ticketCategory, Map.of("onlineCheckInUrl", "https://your-domain.tld/check-in", "prerequisites", "An internet connection is required to join the event")); } }, @@ -188,6 +186,11 @@ public Map prepareSampleModel(Organization organization, Purchas public Map prepareSampleModel(Organization organization, PurchaseContext event, Optional imageData) { return buildModelForTicketHasBeenCancelledAdmin(organization, (Event) event, sampleTicket(event.getZoneId()), "Category", Collections.emptyList(), asi -> Optional.empty()); } + + @Override + public byte[] replaceTokens(byte[] raw) { + return replaceOldReferences(raw); + } }, @@ -200,6 +203,11 @@ public Map prepareSampleModel(Organization organization, Purchas var ticketWithMetadata = TicketWithMetadataAttributes.build(sampleTicket(event.getZoneId()), null); return buildModelForTicketPDF(organization, event, sampleTicketReservation(event.getZoneId()), ticketCategory, ticketWithMetadata, imageData, "ABCD", Collections.emptyMap(), List.of()); } + + @Override + public byte[] replaceTokens(byte[] raw) { + return replaceOldReferences(raw); + } }, RECEIPT_PDF("/alfio/templates/receipt.ms", APPLICATION_PDF_VALUE, TemplateManager.TemplateOutput.HTML) { @Override @@ -269,24 +277,48 @@ public Map prepareSampleModel(Organization organization, Purchas CUSTOM_MESSAGE("/alfio/templates/custom-message", TemplateResource.MULTIPART_ALTERNATIVE_MIMETYPE, TemplateManager.TemplateOutput.TEXT) { @Override public Map prepareSampleModel(Organization organization, PurchaseContext purchaseContext, Optional imageData) { - var now = purchaseContext.now(ClockProvider.clock()); - var event = (Event) purchaseContext; - 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 model = buildModelForTicketEmail(organization, event, sampleTicketReservation(event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(event.getZoneId()), ticketCategory, Map.of()); + var result = buildTicket((Event) purchaseContext); + var model = buildModelForTicketEmail(organization, result.event, sampleTicketReservation(result.event.getZoneId()), "http://your-domain.tld", "http://your-domain.tld/ticket-url", "http://your-domain.tld/calendar-url", sampleTicket(result.event.getZoneId()), result.ticketCategory, Map.of()); model.put("message", "This is your message"); return model; } - },; + }; + public static final String FULL_NAME = "fullName"; public static final String MULTIPART_ALTERNATIVE_MIMETYPE = "multipart/alternative"; + private static final String EVENT_NAME = "eventName"; private static final String PLAIN_TEMPLATE_SUFFIX = "-txt.ms"; private static final String HTML_TEMPLATE_SUFFIX = "-html.ms"; private static final String TICKET_KEY = "ticket"; private static final String RESERVATION_ID = "reservationId"; private static final String RESERVATION_ID_VALUE = "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e"; + private static final String IMAGE_WIDTH = "imageWidth"; + private static final String IMAGE_HEIGHT = "imageHeight"; + private static final String EVENT = "event"; + private static final String BASE_URL = "baseUrl"; + private static final String RESERVATION = "reservation"; + private static final String RESERVATION_URL = "reservationUrl"; + private static final String ORGANIZATION = "organization"; + private static final Pattern TICKET_UUID_FINDER = Pattern.compile("\\{\\{\\s*ticket\\.uuid\\s*}}", Pattern.MULTILINE); + private static final UUID INTERNAL_UUID = UUID.fromString("aaaaaaaa-0000-0000-0000-000000000000"); + private static final UUID PUBLIC_UUID = UUID.fromString("bbbbbbbb-0000-0000-0000-000000000000"); + + private record SampleData(Event event, TicketCategory ticketCategory) { + } + + private static SampleData buildTicket(Event purchaseContext) { + var now = purchaseContext.now(ClockProvider.clock()); + TicketCategory ticketCategory = new TicketCategory(0, now, now, 42, "Ticket", false, TicketCategory.Status.ACTIVE, purchaseContext.getId(), false, 1000, null, null, null, null, null, "CHF", 0, null, TicketCategory.TicketAccessType.INHERIT); + return new SampleData(purchaseContext, ticketCategory); + } + private static byte[] replaceOldReferences(byte[] raw) { + var contentAsString = new String(raw, StandardCharsets.UTF_8); + return TICKET_UUID_FINDER.matcher(contentAsString).replaceAll("{{ticket.publicUuid}}") + .getBytes(StandardCharsets.UTF_8); + } private final String classPathUrl; // currently not used, might be removed in the future @@ -342,6 +374,14 @@ public PurchaseContextType getPurchaseContextType() { return purchaseContextType; } + /* + * used by resources to replace old (renamed) tokens which might still be present in overridden templates. + * Default implementation is a no-op + */ + public byte[] replaceTokens(byte[] raw) { + return raw; + } + public Map prepareSampleModel(Organization organization, PurchaseContext event, Optional imageData) { return Collections.emptyMap(); } @@ -354,8 +394,8 @@ private static Map sampleBillingDocument(Optional ima Map model = prepareSampleDataForConfirmationEmail(organization, event); imageData.ifPresent(iData -> { model.put("eventImage", iData.getEventImage()); - model.put("imageWidth", iData.getImageWidth()); - model.put("imageHeight", iData.getImageHeight()); + model.put(IMAGE_WIDTH, iData.getImageWidth()); + model.put(IMAGE_HEIGHT, iData.getImageHeight()); }); return model; } @@ -367,7 +407,7 @@ private static TicketCategory sampleCategory(ZoneId zoneId) { } private static Ticket sampleTicket(String firstName, String lastName, String email, ZoneId zoneId) { - return new Ticket(0, RESERVATION_ID_VALUE, ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), 0, "ACQUIRED", 0, + return new Ticket(0, INTERNAL_UUID.toString(), PUBLIC_UUID, ZonedDateTime.now(ClockProvider.clock().withZone(zoneId)), 0, "ACQUIRED", 0, RESERVATION_ID_VALUE, firstName + " " + lastName, firstName, lastName, email, false, "en", 1000, 1000, 80, 0, null, "CHF", List.of(), null, PriceContainer.VatStatus.INCLUDED); } @@ -398,12 +438,12 @@ private static Map prepareSampleDataForChargeFailed(Organization return Map.of( RESERVATION_ID, reservation.getId().substring(0, 8), "reservationCancelled", true, - "reservation", reservation, - "eventName", event.getDisplayName(), + RESERVATION, reservation, + EVENT_NAME, event.getDisplayName(), "provider", PaymentMethod.CREDIT_CARD.name(), "reason", "this is the reason from the provider", - "reservationUrl", "http://your-domain.tld/reservation-url/", - "organization", organization + RESERVATION_URL, "http://your-domain.tld/reservation-url/", + ORGANIZATION, organization ); } @@ -429,8 +469,8 @@ public static Map prepareModelForConfirmationEmail(Organization Optional bankAccountOwner, Map additionalModelObjects) { Map model = new HashMap<>(additionalModelObjects); - model.put("organization", organization); - model.put("event", purchaseContext.event().orElse(null)); + model.put(ORGANIZATION, organization); + model.put(EVENT, purchaseContext.event().orElse(null)); model.put("purchaseContext", purchaseContext); model.put("purchaseContextTitle", purchaseContext.getTitle().get(reservation.getUserLanguage())); model.put("ticketReservation", reservation); @@ -438,8 +478,8 @@ public static Map prepareModelForConfirmationEmail(Organization model.put("vatNr", vat.orElse("")); model.put("tickets", tickets); model.put("orderSummary", orderSummary); - model.put("baseUrl", baseUrl); - model.put("reservationUrl", reservationUrl); + model.put(BASE_URL, baseUrl); + model.put(RESERVATION_URL, reservationUrl); model.put("locale", reservation.getUserLanguage()); model.put("hasRefund", StringUtils.isNotEmpty(orderSummary.getRefundedAmount())); @@ -480,18 +520,18 @@ public static Map prepareModelForConfirmationEmail(Organization // used by OFFLINE_RESERVATION_EXPIRING_EMAIL_FOR_ORGANIZER public static Map prepareModelForOfflineReservationExpiringEmailForOrganizer(Event event, List reservations, String baseUrl) { Map model = new HashMap<>(); - model.put("eventName", event.getDisplayName()); + model.put(EVENT_NAME, event.getDisplayName()); model.put("ticketReservations", reservations.stream().map(r -> new TicketReservationWithZonedExpiringDate(r, event)).collect(Collectors.toList())); - model.put("baseUrl", baseUrl); + model.put(BASE_URL, baseUrl); model.put("eventShortName", event.getShortName()); return model; } private static Map prepareSampleModelForOfflineReservationExpiringEmailForOrganizer(Event event) { Map model = new HashMap<>(); - model.put("eventName", event.getDisplayName()); + model.put(EVENT_NAME, event.getDisplayName()); model.put("ticketReservations", Collections.singletonList(new TicketReservationInfo("id", null, "Firstname", "Lastname", "email@email.email", 1000, "EUR", 42, new Date()))); - model.put("baseUrl", "http://base-url/"); + model.put(BASE_URL, "http://base-url/"); model.put("eventShortName", event.getShortName()); return model; } @@ -504,8 +544,8 @@ public static Map prepareModelForSendReservedCode(Organization o String promoCodeLabel) { Map model = new HashMap<>(); model.put("code", m.getCode()); - model.put("event", event); - model.put("organization", organization); + model.put(EVENT, event); + model.put(ORGANIZATION, organization); model.put("eventPage", eventPageUrl); model.put("assignee", m.getAssignee()); model.put("promoCodeDescription", promoCodeLabel); @@ -519,9 +559,9 @@ public static Map prepareModelForReminderTicketAdditionalInfo(Or Ticket ticket, String ticketUrl) { Map model = new HashMap<>(); - model.put("event", event); - model.put("fullName", ticket.getFullName()); - model.put("organization", organization); + model.put(EVENT, event); + model.put(FULL_NAME, ticket.getFullName()); + model.put(ORGANIZATION, organization); model.put("ticketURL", ticketUrl); return model; } @@ -537,10 +577,10 @@ public static Map buildModelForTicketEmail(Organization organiza TicketCategory ticketCategory, Map additionalOptions) { Map model = new HashMap<>(additionalOptions); - model.put("organization", organization); - model.put("event", event); + model.put(ORGANIZATION, organization); + model.put(EVENT, event); model.put("ticketReservation", ticketReservation); - model.put("baseUrl", baseUrl); + model.put(BASE_URL, baseUrl); model.put("ticketUrl", ticketURL); model.put(TICKET_KEY, ticket); model.put("googleCalendarUrl", calendarURL); @@ -557,8 +597,8 @@ public static void fillTicketValidity(Event event, TicketCategory ticketCategory public static Map buildModelForTicketHasChangedOwner(Organization organization, Event e, Ticket oldTicket, Ticket newTicket, String ticketUrl) { Map emailModel = new HashMap<>(); emailModel.put(TICKET_KEY, oldTicket); - emailModel.put("organization", organization); - emailModel.put("eventName", e.getDisplayName()); + emailModel.put(ORGANIZATION, organization); + emailModel.put(EVENT_NAME, e.getDisplayName()); emailModel.put("previousEmail", oldTicket.getEmail()); emailModel.put("newEmail", newTicket.getEmail()); emailModel.put("ticketUrl", ticketUrl); @@ -568,9 +608,9 @@ public static Map buildModelForTicketHasChangedOwner(Organizatio // used by TICKET_HAS_BEEN_CANCELLED public static Map buildModelForTicketHasBeenCancelled(Organization organization, Event event, Ticket ticket) { Map model = new HashMap<>(); - model.put("eventName", event.getDisplayName()); + model.put(EVENT_NAME, event.getDisplayName()); model.put(TICKET_KEY, ticket); - model.put("organization", organization); + model.put(ORGANIZATION, organization); return model; } @@ -607,10 +647,10 @@ public static Map buildModelForTicketPDF(Organization organizati // Map model = new HashMap<>(); model.put(TICKET_KEY, ticketWithMetadata.getTicket()); - model.put("reservation", ticketReservation); + model.put(RESERVATION, ticketReservation); model.put("ticketCategory", ticketCategory); - model.put("event", event); - model.put("organization", organization); + model.put(EVENT, event); + model.put(ORGANIZATION, organization); model.put(RESERVATION_ID, reservationId); model.put(ADDITIONAL_FIELDS_KEY, additionalFields); model.put(METADATA_ATTRIBUTES_KEY, ticketWithMetadata.getAttributes()); @@ -620,8 +660,8 @@ public static Map buildModelForTicketPDF(Organization organizati imageData.ifPresent(iData -> { model.put("eventImage", iData.getEventImage()); - model.put("imageWidth", iData.getImageWidth()); - model.put("imageHeight", iData.getImageHeight()); + model.put(IMAGE_WIDTH, iData.getImageWidth()); + model.put(IMAGE_HEIGHT, iData.getImageHeight()); }); model.put("deskPaymentRequired", Optional.ofNullable(ticketReservation.getPaymentMethod()).orElse(PaymentProxy.STRIPE).isDeskPaymentRequired()); @@ -648,9 +688,9 @@ private static void fillAdditionalServices(TicketWithMetadataAttributes ticketWi .findFirst() .orElse(tfc.getValue()); return new FieldNameAndValue(tfc.getDescription().get(userLanguage).getLabel(), value); - })).collect(Collectors.toList()) + })).toList() ); - }).collect(Collectors.toList()); + }).toList(); model.put("additionalServices", additionalServices); } @@ -670,15 +710,15 @@ public static Map buildModelForSubscriptionPDF(Subscription subs model.put("validityTypeCustom", subscriptionDescriptor.getValidityType() == SubscriptionValidityType.CUSTOM); model.put("subscription", subscription); model.put(SUBSCRIPTION_DESCRIPTOR_ATTRIBUTE, subscriptionDescriptor); - model.put("organization", organization); - model.put("reservation", reservation); + model.put(ORGANIZATION, organization); + model.put(RESERVATION, reservation); model.put(RESERVATION_ID, reservationId); model.put(METADATA_ATTRIBUTES_KEY, metadata.getProperties()); model.put("displayPin", metadata.getConfiguration().isDisplayPin()); imageData.ifPresent(iData -> { model.put("logo", iData.getEventImage()); - model.put("imageWidth", iData.getImageWidth()); - model.put("imageHeight", iData.getImageHeight()); + model.put(IMAGE_WIDTH, iData.getImageWidth()); + model.put(IMAGE_HEIGHT, iData.getImageHeight()); }); model.put(ADDITIONAL_FIELDS_KEY, fields.stream().collect(Collectors.toMap(FieldConfigurationDescriptionAndValue::getName, FieldConfigurationDescriptionAndValue::getValueDescription))); return model; @@ -687,20 +727,20 @@ public static Map buildModelForSubscriptionPDF(Subscription subs // used by WAITING_QUEUE_JOINED public static Map buildModelForWaitingQueueJoined(Organization organization, Event event, CustomerName name) { Map model = new HashMap<>(); - model.put("eventName", event.getDisplayName()); - model.put("fullName", name.getFullName()); - model.put("organization", organization); + model.put(EVENT_NAME, event.getDisplayName()); + model.put(FULL_NAME, name.getFullName()); + model.put(ORGANIZATION, organization); return model; } // used by WAITING_QUEUE_RESERVATION_EMAIL public static Map buildModelForWaitingQueueReservationEmail(Organization organization, Event event, WaitingQueueSubscription subscription, String reservationUrl, ZonedDateTime expiration) { Map model = new HashMap<>(); - model.put("event", event); + model.put(EVENT, event); model.put("subscription", subscription); - model.put("reservationUrl", reservationUrl); + model.put(RESERVATION_URL, reservationUrl); model.put("reservationTimeout", expiration); - model.put("organization", organization); + model.put(ORGANIZATION, organization); return model; } diff --git a/src/main/java/alfio/util/Validator.java b/src/main/java/alfio/util/Validator.java index 0fb10b0a40..8c51747a57 100644 --- a/src/main/java/alfio/util/Validator.java +++ b/src/main/java/alfio/util/Validator.java @@ -251,9 +251,9 @@ public AdditionalFieldsFilterer(List purchase } - public List getFieldsForTicket(String ticketUuid, Set requestedContexts) { - var ticket = ticketsInReservation.stream().filter(t -> t.getUuid().equals(ticketUuid)).findFirst().orElseThrow(); - var isFirstTicket = firstTicketInReservation.map(first -> ticket.getUuid().equals(first.getUuid())).orElse(false); + public List getFieldsForTicket(UUID publicUuid, Set requestedContexts) { + var ticket = ticketsInReservation.stream().filter(t -> t.getPublicUuid().equals(publicUuid)).findFirst().orElseThrow(); + var isFirstTicket = firstTicketInReservation.map(first -> ticket.getId() == first.getId()).orElse(false); return filterFieldsForTicket(purchaseContextFields, ticket, additionalFieldsForReservation, isFirstTicket, eventSupportsAdditionalFieldsLink, additionalServiceItems, requestedContexts); } diff --git a/src/main/java/alfio/util/checkin/TicketCheckInUtil.java b/src/main/java/alfio/util/checkin/TicketCheckInUtil.java index d3366897cd..d6f6ac108b 100644 --- a/src/main/java/alfio/util/checkin/TicketCheckInUtil.java +++ b/src/main/java/alfio/util/checkin/TicketCheckInUtil.java @@ -23,7 +23,6 @@ import alfio.model.TicketCategory; import alfio.repository.EventRepository; import alfio.repository.TicketCategoryRepository; -import lombok.experimental.UtilityClass; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; @@ -43,7 +42,7 @@ private TicketCheckInUtil() {} public static String ticketOnlineCheckInUrl(Event event, Ticket ticket, String baseUrl) { var ticketCode = DigestUtils.sha256Hex(ticket.ticketCode(event.getPrivateKey(), event.supportsQRCodeCaseInsensitive())); return StringUtils.removeEnd(baseUrl, "/") - + "/event/" + event.getShortName() + "/ticket/" + ticket.getUuid() + "/check-in/"+ticketCode; + + "/event/" + event.getShortName() + "/ticket/" + ticket.getPublicUuid() + "/check-in/"+ticketCode; } public static Map getOnlineCheckInInfo(ExtensionManager extensionManager, diff --git a/src/main/resources/alfio/db/PGSQL/V205_2.0.0.57__TICKET_PUBLIC_UUID.sql b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.57__TICKET_PUBLIC_UUID.sql new file mode 100644 index 0000000000..b7c0d553fe --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V205_2.0.0.57__TICKET_PUBLIC_UUID.sql @@ -0,0 +1,41 @@ +-- +-- 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 . +-- + +alter table ticket add column public_uuid uuid; +update ticket set public_uuid = uuid::uuid; +alter table ticket alter column public_uuid set not null; + +CREATE OR REPLACE FUNCTION fill_public_uuid() + RETURNS TRIGGER AS +$body$ +BEGIN + IF (NEW.public_uuid is null) THEN + NEW.public_uuid := NEW.uuid::uuid; + END IF; + RETURN NEW; +END +$body$ + LANGUAGE plpgsql; + +CREATE TRIGGER t_check_public_uuid + BEFORE INSERT ON ticket + FOR EACH ROW + when ( new.public_uuid is null ) + EXECUTE PROCEDURE fill_public_uuid(); + +alter table ticket add constraint "t_unique_public_uuid" unique(public_uuid); + diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__006_VIEW_reservation_and_ticket_and_tx.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__006_VIEW_reservation_and_ticket_and_tx.sql index 605c0e19a3..1a05042610 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__006_VIEW_reservation_and_ticket_and_tx.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__006_VIEW_reservation_and_ticket_and_tx.sql @@ -62,6 +62,7 @@ create view reservation_and_ticket_and_tx as (select ticket.id t_id, ticket.uuid t_uuid, + ticket.public_uuid t_public_uuid, ticket.creation t_creation, ticket.category_id t_category_id, ticket.status t_status, diff --git a/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql b/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql index f7b8b607d2..f9d898268f 100644 --- a/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql +++ b/src/main/resources/alfio/db/PGSQL/afterMigrate__009_VIEW_checkin_ticket_event_and_category_info.sql @@ -41,6 +41,7 @@ create view checkin_ticket_event_and_category_info as t.id t_id, t.uuid t_uuid, + t.public_uuid t_public_uuid, t.creation t_creation, t.category_id t_category_id, t.status t_status, diff --git a/src/main/resources/alfio/templates/ticket-has-been-cancelled-admin-txt.ms b/src/main/resources/alfio/templates/ticket-has-been-cancelled-admin-txt.ms index 52afd9e1d2..734ca788ec 100644 --- a/src/main/resources/alfio/templates/ticket-has-been-cancelled-admin-txt.ms +++ b/src/main/resources/alfio/templates/ticket-has-been-cancelled-admin-txt.ms @@ -1,4 +1,4 @@ -{{#i18n}}email-ticket-released.admin.text [{{ticket.id}}] [{{ticket.uuid}}] [{{ticket.fullName}}] [{{ticket.email}}] [{{ticketCategoryDescription}}]{{/i18n}} +{{#i18n}}email-ticket-released.admin.text [{{ticket.id}}] [{{ticket.publicUuid}}] [{{ticket.fullName}}] [{{ticket.email}}] [{{ticketCategoryDescription}}]{{/i18n}} {{#hasAdditionalServices}} ***** WARNING ***** diff --git a/src/main/resources/alfio/templates/ticket.ms b/src/main/resources/alfio/templates/ticket.ms index 0f43ffbcad..f17564cd66 100644 --- a/src/main/resources/alfio/templates/ticket.ms +++ b/src/main/resources/alfio/templates/ticket.ms @@ -86,7 +86,7 @@ {{#i18n}}ticket.reference-number{{/i18n}} - {{ticket.uuid}} + {{ticket.publicUuid}} {{#hasSubscription}} diff --git a/src/main/webapp/alfio-admin-v1/js/admin/feature/copy-event/copy-event.js b/src/main/webapp/alfio-admin-v1/js/admin/feature/copy-event/copy-event.js index 2f7d5b34b0..1870e81218 100644 --- a/src/main/webapp/alfio-admin-v1/js/admin/feature/copy-event/copy-event.js +++ b/src/main/webapp/alfio-admin-v1/js/admin/feature/copy-event/copy-event.js @@ -48,8 +48,8 @@ function copyEventCtrl(EventService, AdditionalFieldsService, $q, $templateCache }; ctrl.submit = function() { - var selectedAdditionalFields = ctrl.additionalFields.filter(function(af) {return ctrl.selectedAdditionalFields[af.name]}); - var selectedAdditionalServices = ctrl.additionalServices.filter(function(as) {return ctrl.selectedAdditionalServices[as.id]}); + var selectedAdditionalFields = (ctrl.additionalFields||[]).filter(function(af) {return ctrl.selectedAdditionalFields[af.name]}); + var selectedAdditionalServices = (ctrl.additionalServices||[]).filter(function(as) {return ctrl.selectedAdditionalServices[as.id]}); ctrl.onCopy([ctrl.newEvent, ctrl.selectedEvent, selectedAdditionalFields, selectedAdditionalServices]); }; 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 88f09a0e1b..6465b1de43 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 @@ -764,7 +764,10 @@ protected void testBasicFlow(Supplier contextSupplier) t ticketForm2.setEmail("tickettest@test.com"); ticketForm2.setAdditional(new HashMap<>(Map.of("field1", Collections.singletonList("value")))); - contactForm.setTickets(Map.of(ticket1.getUuid(), ticketForm1, ticket2.getUuid(), ticketForm2)); + contactForm.setTickets(Map.ofEntries( + Map.entry(ticket1.getUuid(), ticketForm1), + Map.entry(ticket2.getUuid(), ticketForm2) + )); var additionalServiceLinkForm = new AdditionalServiceLinkForm(); additionalServiceLinkForm.setAdditionalServiceItemId(additionalServiceWithData.get(0).getItemId()); @@ -794,9 +797,9 @@ protected void testBasicFlow(Supplier contextSupplier) t contactForm.getAdditionalServices().put(ticket2.getUuid(), List.of(linkForm)); var success = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication()); assertEquals(HttpStatus.OK, success.getStatusCode()); - var values = purchaseContextFieldRepository.findAllValuesByTicketIds(List.of(ticketRepository.findByUUID(ticket1.getUuid()).getId())); + var values = purchaseContextFieldRepository.findAllValuesByTicketIds(List.of(ticketRepository.findByPublicUUID(UUID.fromString(ticket1.getUuid())).getId())); assertEquals(1, values.size()); - int ticket2Id = ticketRepository.findByUUID(ticket2.getUuid()).getId(); + int ticket2Id = ticketRepository.findByPublicUUID(UUID.fromString(ticket2.getUuid())).getId(); var ticket2Ids = List.of(ticket2Id); values = purchaseContextFieldRepository.findAllValuesByTicketIds(ticket2Ids); assertEquals(2, values.size()); @@ -1025,10 +1028,10 @@ protected void testBasicFlow(Supplier contextSupplier) t assertEquals("ticketfull", ticket.getFirstName()); assertEquals("ticketname", ticket.getLastName()); - var ticketNotFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), "DONT_EXISTS"); + var ticketNotFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), UUID.fromString("00000000-0000-0000-0000-000000000000")); assertEquals(HttpStatus.NOT_FOUND, ticketNotFoundRes.getStatusCode()); - var ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid()); + var ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), UUID.fromString(ticket.getUuid())); assertEquals(HttpStatus.OK, ticketFoundRes.getStatusCode()); var ticketFoundBody = ticketFoundRes.getBody(); assertNotNull(ticketFoundBody); @@ -1037,7 +1040,7 @@ protected void testBasicFlow(Supplier contextSupplier) t assertEquals("full name", ticketFoundBody.getReservationFullName()); assertTrue(reservationId.startsWith(ticketFoundBody.getReservationId().toLowerCase(Locale.ENGLISH))); - var sendTicketByEmailRes = ticketApiV2Controller.sendTicketByEmail(context.event.getShortName(), ticket.getUuid()); + var sendTicketByEmailRes = ticketApiV2Controller.sendTicketByEmail(context.event.getShortName(), UUID.fromString(ticket.getUuid())); assertEquals(HttpStatus.OK, sendTicketByEmailRes.getStatusCode()); assertNotNull(sendTicketByEmailRes.getBody()); assertTrue(sendTicketByEmailRes.getBody()); @@ -1051,15 +1054,15 @@ protected void testBasicFlow(Supplier contextSupplier) t updateTicketOwnerForm.setLastName("Testson"); updateTicketOwnerForm.setEmail("testmctest@test.com"); updateTicketOwnerForm.setAdditional(new HashMap<>(Collections.singletonMap("field1", Collections.singletonList("value")))); - var updateTicketRes = ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), ticket.getUuid(), updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication()); + var updateTicketRes = ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), UUID.fromString(ticket.getUuid()), updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication()); assertNotNull(updateTicketRes.getBody()); assertTrue(updateTicketRes.getBody().isSuccess()); //not found - assertEquals(HttpStatus.NOT_FOUND, ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), ticket.getUuid()+"42", updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication()).getStatusCode()); + assertEquals(HttpStatus.NOT_FOUND, ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), null, updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication()).getStatusCode()); - ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid()); + ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), UUID.fromString(ticket.getUuid())); ticketFoundBody = ticketFoundRes.getBody(); assertNotNull(ticketFoundBody); assertEquals("testmctest@test.com", ticketFoundBody.getEmail()); @@ -1074,24 +1077,26 @@ protected void testBasicFlow(Supplier contextSupplier) t var ticketPdfMockResp = new MockHttpServletResponse(); - ticketApiV2Controller.generateTicketPdf(context.event.getShortName(), ticket.getUuid(), ticketPdfMockResp); + ticketApiV2Controller.generateTicketPdf(context.event.getShortName(), UUID.fromString(ticket.getUuid()), ticketPdfMockResp); assertEquals("application/pdf", ticketPdfMockResp.getContentType()); assertTrue(ticketPdfMockResp.containsHeader(ExportUtils.X_ROBOTS_TAG)); var ticketQRCodeResp = new MockHttpServletResponse(); - ticketApiV2Controller.showQrCode(context.event.getShortName(), ticket.getUuid(), ticketQRCodeResp); + ticketApiV2Controller.showQrCode(context.event.getShortName(), UUID.fromString(ticket.getUuid()), ticketQRCodeResp); assertEquals("image/png", ticketQRCodeResp.getContentType()); assertTrue(ticketQRCodeResp.containsHeader(ExportUtils.X_ROBOTS_TAG)); - var fullTicketInfo = ticketRepository.findByUUID(ticket.getUuid()); + var fullTicketInfo = ticketRepository.findByPublicUUID(UUID.fromString(ticket.getUuid())); var qrCodeReader = new QRCodeReader(); var qrCodeRead = qrCodeReader.decode(new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(ImageIO.read(new ByteArrayInputStream(ticketQRCodeResp.getContentAsByteArray()))))), Map.of(DecodeHintType.PURE_BARCODE, Boolean.TRUE)); - assertEquals(fullTicketInfo.ticketCode(context.event.getPrivateKey(), context.event.supportsQRCodeCaseInsensitive()), qrCodeRead.getText()); + var text = qrCodeRead.getText(); + assertTrue(text.startsWith(fullTicketInfo.getUuid() + "/")); // ensure that we put + assertEquals(fullTicketInfo.ticketCode(context.event.getPrivateKey(), context.event.supportsQRCodeCaseInsensitive()), text); //can only be done for free tickets - var releaseTicketFailure = ticketApiV2Controller.releaseTicket(context.event.getShortName(), ticket.getUuid()); + var releaseTicketFailure = ticketApiV2Controller.releaseTicket(context.event.getShortName(), UUID.fromString(ticket.getUuid())); assertEquals(HttpStatus.BAD_REQUEST, releaseTicketFailure.getStatusCode()); - assertEquals(HttpStatus.OK, ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid()).getStatusCode()); + assertEquals(HttpStatus.OK, ticketApiV2Controller.getTicketInfo(context.event.getShortName(), UUID.fromString(ticket.getUuid())).getStatusCode()); //no invoice, but receipt @@ -1108,7 +1113,7 @@ protected void testBasicFlow(Supplier contextSupplier) t Principal principal = mock(Principal.class); Mockito.when(principal.getName()).thenReturn(context.userId); - String ticketIdentifier = fullTicketInfo.getUuid(); + String internalTicketIdentifier = fullTicketInfo.getUuid(); String eventName = context.event.getShortName(); // try to search ticket @@ -1137,29 +1142,29 @@ protected void testBasicFlow(Supplier contextSupplier) t } String ticketCode = fullTicketInfo.ticketCode(context.event.getPrivateKey(), context.event.supportsQRCodeCaseInsensitive()); - TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode, principal); + TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(context.event.getId(), internalTicketIdentifier, ticketCode, principal); assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus()); - var basicCheckInStatus = checkInApiController.getTicketStatus(context.event.getShortName(), ticketIdentifier, principal); + var basicCheckInStatus = checkInApiController.getTicketStatus(context.event.getShortName(), internalTicketIdentifier, principal); assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, basicCheckInStatus.getCheckInStatus()); assertNotNull(basicCheckInStatus.getFirstName()); assertNotNull(basicCheckInStatus.getLastName()); CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode(); tc.setCode(ticketCode); - assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, principal).getResult().getStatus()); - basicCheckInStatus = checkInApiController.getTicketStatus(context.event.getShortName(), ticketIdentifier, principal); + assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, tc, principal).getResult().getStatus()); + basicCheckInStatus = checkInApiController.getTicketStatus(context.event.getShortName(), internalTicketIdentifier, principal); assertEquals(CheckInStatus.ALREADY_CHECK_IN, basicCheckInStatus.getCheckInStatus()); assertNotNull(basicCheckInStatus.getFirstName()); assertNotNull(basicCheckInStatus.getLastName()); List audits = scanAuditRepository.findAllForEvent(context.event.getId()); assertFalse(audits.isEmpty()); - assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier))); + assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(internalTicketIdentifier))); extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); assertEventLogged(extLogs, TICKET_CHECKED_IN, 2); validateCheckInData(context); - TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode, principal); + TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(context.event.getId(), internalTicketIdentifier, ticketCode, principal); assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus()); // check stats after check in one ticket @@ -1196,10 +1201,10 @@ protected void testBasicFlow(Supplier contextSupplier) t checkReservationExport(context); //test revert check in - assertTrue(checkInApiController.revertCheckIn(context.event.getId(), ticketIdentifier, principal)); + assertTrue(checkInApiController.revertCheckIn(context.event.getId(), internalTicketIdentifier, principal)); - assertFalse(checkInApiController.revertCheckIn(context.event.getId(), ticketIdentifier, principal)); - TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode, principal); + assertFalse(checkInApiController.revertCheckIn(context.event.getId(), internalTicketIdentifier, principal)); + TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(context.event.getId(), internalTicketIdentifier, ticketCode, principal); assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult2.getResult().getStatus()); UsersApiController.UserWithPasswordAndQRCode sponsorUser = usersApiController.insertUser(new UserModification(null, context.event.getOrganizationId(), "SPONSOR", "sponsor", "first", "last", "email@email.com", User.Type.INTERNAL, null, null), "http://localhost:8080", principal); @@ -1209,7 +1214,7 @@ protected void testBasicFlow(Supplier contextSupplier) t // check failures assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket", null, null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket", null, null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); - assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier, null, null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, internalTicketIdentifier, null, null, null), sponsorPrincipal, null).getBody().getResult().getStatus()); // @@ -1226,7 +1231,7 @@ protected void testBasicFlow(Supplier contextSupplier) t CheckInApiController.TicketCode tc2 = new CheckInApiController.TicketCode(); tc2.setCode(ticketCode); - TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc2, principal); + TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, tc2, principal); assertEquals(CheckInStatus.SUCCESS, ticketAndcheckInResult.getResult().getStatus()); extLogs = extensionLogRepository.getPage(null, null, null, 100, 0); @@ -1306,10 +1311,12 @@ record SponsorScanRecord( // // check update notes - assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT", null), sponsorPrincipal, null).getBody().getResult().getStatus()); + // we need to load the internal UUID before scanning + var internalUUID = ticketRepository.getInternalUuid(UUID.fromString(ticket.getUuid()), context.event.getId()).orElseThrow(); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, internalUUID, "this is a very good lead!", "HOT", null), sponsorPrincipal, null).getBody().getResult().getStatus()); var scannedBadges = attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody(); assertEquals(1, requireNonNull(scannedBadges).size()); - assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT", null), sponsorPrincipal, null).getBody().getResult().getStatus()); + assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, internalUUID, "this is a very good lead!", "HOT", null), sponsorPrincipal, null).getBody().getResult().getStatus()); scannedBadges = attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody(); assertEquals(1, requireNonNull(scannedBadges).size()); response = new MockHttpServletResponse(); @@ -1353,7 +1360,7 @@ record SponsorScanRecord( // since on the badge we don't have the full ticket info, we will pass in "null" as scanned code CheckInApiController.TicketCode badgeScan = new CheckInApiController.TicketCode(); badgeScan.setCode(null); - ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, principal); + ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, badgeScan, principal); // ONCE_PER_DAY is disabled by default, therefore we get an error assertEquals(CheckInStatus.EMPTY_TICKET_CODE, ticketAndcheckInResult.getResult().getStatus()); // enable ONCE_PER_DAYFalse @@ -1363,7 +1370,7 @@ record SponsorScanRecord( TicketCategory.TicketCheckInStrategy.ONCE_PER_DAY, category.getTicketAccessType() ); - ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, principal); + ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, badgeScan, principal); // the event start date is in one week, so we expect an error here assertEquals(CheckInStatus.INVALID_TICKET_CATEGORY_CHECK_IN_DATE, ticketAndcheckInResult.getResult().getStatus()); @@ -1371,7 +1378,7 @@ record SponsorScanRecord( context.event.getFileBlobId(), context.event.getLocation(), context.event.getLatitude(), context.event.getLongitude(), context.event.now(clockProvider).minusSeconds(1), context.event.getEnd(), context.event.getTimeZone(), context.event.getOrganizationId(), context.event.getLocales(), context.event.getFormat()); - ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, principal); + ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, badgeScan, principal); // we have already scanned the ticket today, so we expect to receive a warning assertEquals(CheckInStatus.BADGE_SCAN_ALREADY_DONE, ticketAndcheckInResult.getResult().getStatus()); assertEquals(1, (int) auditingRepository.countAuditsOfTypeForReservation(reservationId, Audit.EventType.BADGE_SCAN)); @@ -1383,7 +1390,7 @@ record SponsorScanRecord( // 1 badge scan assertEquals(3, jdbcTemplate.update("update auditing set event_time = event_time - interval '1 day' where reservation_id = :reservationId and event_type in ('BADGE_SCAN', 'CHECK_IN')", Map.of("reservationId", reservationId))); - ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, principal); + ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), internalTicketIdentifier, badgeScan, principal); // we now expect to receive a successful message assertEquals(CheckInStatus.BADGE_SCAN_SUCCESS, ticketAndcheckInResult.getResult().getStatus()); assertEquals(2, (int) auditingRepository.countAuditsOfTypeForReservation(reservationId, Audit.EventType.BADGE_SCAN)); @@ -1537,8 +1544,8 @@ private TicketWithCategory testEncryptedCheckInPayload(Principal principal, Map payload = checkInApiController.getOfflineEncryptedInfo(context.event.getShortName(), Collections.emptyList(), offlineIdentifiers, principal); assertEquals(1, payload.size()); TicketWithCategory ticketwc = ticketAndcheckInResult.getTicket(); - String ticketKey = ticketwc.hmacTicketInfo(context.event.getPrivateKey(), true); - assertNotEquals(ticketKey, ticketwc.hmacTicketInfo(context.event.getPrivateKey(), false)); + String ticketKey = ticketwc.ticket().hmacTicketInfo(context.event.getPrivateKey(), true); + assertNotEquals(ticketKey, ticketwc.ticket().hmacTicketInfo(context.event.getPrivateKey(), false)); String hashedTicketKey = DigestUtils.sha256Hex(ticketKey); String encJson = payload.get(hashedTicketKey); assertNotNull(encJson); diff --git a/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java b/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java index 2c98790f0c..296fb44f80 100644 --- a/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java +++ b/src/test/java/alfio/controller/api/v2/user/reservation/CustomTaxPolicyIntegrationTest.java @@ -141,15 +141,15 @@ void triggerCustomTaxPolicyTaxIncluded() { var tickets = ticketRepository.findTicketsInReservation(reservationId); assertEquals(2, tickets.size()); - var firstUuid = tickets.get(0).getUuid(); - var secondUuid = tickets.get(1).getUuid(); + var firstUuid = tickets.get(0).getPublicUuid(); + var secondUuid = tickets.get(1).getPublicUuid(); var contactAndTicketsForm = new ContactAndTicketsForm(); contactAndTicketsForm.setFirstName("The"); contactAndTicketsForm.setLastName("Customer"); contactAndTicketsForm.setEmail("email@customer.com"); contactAndTicketsForm.setTickets(Map.of( - firstUuid, updateTicketOwnerForm("example@example.org"), - secondUuid, updateTicketOwnerForm("example@example1.org") + firstUuid.toString(), updateTicketOwnerForm("example@example.org"), + secondUuid.toString(), updateTicketOwnerForm("example@example1.org") )); var bindingResult = new BeanPropertyBindingResult(contactAndTicketsForm, "form"); var response = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactAndTicketsForm, bindingResult, null); @@ -157,8 +157,8 @@ secondUuid, updateTicketOwnerForm("example@example1.org") assertNotNull(response.getBody()); assertTrue(response.getBody().isSuccess()); // verify that first ticket has the expected tax settings - assertEquals(PriceContainer.VatStatus.CUSTOM_INCLUDED_EXEMPT, ticketRepository.findByUUID(firstUuid).getVatStatus()); - assertEquals(PriceContainer.VatStatus.INCLUDED, ticketRepository.findByUUID(secondUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.CUSTOM_INCLUDED_EXEMPT, ticketRepository.findByPublicUUID(firstUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.INCLUDED, ticketRepository.findByPublicUUID(secondUuid).getVatStatus()); totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); assertEquals(19901, totalPrice.getPriceWithVAT()); assertEquals(19901, ticketReservationManager.findById(reservationId).orElseThrow().getFinalPriceCts()); @@ -181,15 +181,15 @@ void triggerCustomTaxPolicyTaxNotIncluded() { var tickets = ticketRepository.findTicketsInReservation(reservationId); assertEquals(2, tickets.size()); - var firstUuid = tickets.get(0).getUuid(); - var secondUuid = tickets.get(1).getUuid(); + var firstUuid = tickets.get(0).getPublicUuid(); + var secondUuid = tickets.get(1).getPublicUuid(); var contactAndTicketsForm = new ContactAndTicketsForm(); contactAndTicketsForm.setFirstName("The"); contactAndTicketsForm.setLastName("Customer"); contactAndTicketsForm.setEmail("email@customer.com"); contactAndTicketsForm.setTickets(Map.of( - firstUuid, updateTicketOwnerForm("example@example.org"), - secondUuid, updateTicketOwnerForm("example@example1.org") + firstUuid.toString(), updateTicketOwnerForm("example@example.org"), + secondUuid.toString(), updateTicketOwnerForm("example@example1.org") )); var bindingResult = new BeanPropertyBindingResult(contactAndTicketsForm, "form"); var response = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactAndTicketsForm, bindingResult, null); @@ -197,8 +197,8 @@ secondUuid, updateTicketOwnerForm("example@example1.org") assertNotNull(response.getBody()); assertTrue(response.getBody().isSuccess()); // verify that first ticket has the expected tax settings - assertEquals(PriceContainer.VatStatus.CUSTOM_NOT_INCLUDED_EXEMPT, ticketRepository.findByUUID(firstUuid).getVatStatus()); - assertEquals(PriceContainer.VatStatus.NOT_INCLUDED, ticketRepository.findByUUID(secondUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.CUSTOM_NOT_INCLUDED_EXEMPT, ticketRepository.findByPublicUUID(firstUuid).getVatStatus()); + assertEquals(PriceContainer.VatStatus.NOT_INCLUDED, ticketRepository.findByPublicUUID(secondUuid).getVatStatus()); totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId).getLeft(); assertEquals(20100, totalPrice.getPriceWithVAT()); assertEquals(20100, ticketReservationManager.findById(reservationId).orElseThrow().getFinalPriceCts()); diff --git a/src/test/java/alfio/manager/EventManagerIntegrationTest.java b/src/test/java/alfio/manager/EventManagerIntegrationTest.java index c69b441537..5fb4de5dfe 100644 --- a/src/test/java/alfio/manager/EventManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/EventManagerIntegrationTest.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; @@ -1045,6 +1046,44 @@ private Pair generateAndEditEvent(int newEventSize) { return Pair.of(eventRepository.findById(event.getId()), pair.getRight()); } + @Test + void ensureUniqueConstraintApplied() { + List categories = Collections.singletonList( + new TicketCategoryModification(null, "default", TicketCategory.TicketAccessType.INHERIT, 10, + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + new DateTimeModification(LocalDate.now(clockProvider.getClock()), LocalTime.now(clockProvider.getClock())), + DESCRIPTION, BigDecimal.TEN, false, "", true, null, null, null, null, null, 0, null, null, AlfioMetadata.empty())); + Event event = initEvent(categories, organizationRepository, userManager, eventManager, eventRepository).getKey(); + var uuid = UUID.randomUUID().toString(); + var publicUuid = UUID.randomUUID(); + ticketRepository.bulkTicketInitialization(new MapSqlParameterSource[] {new MapSqlParameterSource() + .addValue("uuid", uuid) + .addValue("publicUuid", publicUuid) + .addValue("creation", new Date()) + .addValue("categoryId", null) + .addValue("eventId", event.getId()) + .addValue("status", Ticket.TicketStatus.FREE.name()) + .addValue("srcPriceCts", 100)}); + + assertThrows(DataIntegrityViolationException.class, () -> ticketRepository.bulkTicketInitialization(new MapSqlParameterSource[] {new MapSqlParameterSource() + .addValue("uuid", uuid) + .addValue("publicUuid", UUID.randomUUID()) + .addValue("creation", new Date()) + .addValue("categoryId", null) + .addValue("eventId", event.getId()) + .addValue("status", Ticket.TicketStatus.FREE.name()) + .addValue("srcPriceCts", 100)})); + + assertThrows(DataIntegrityViolationException.class, () -> ticketRepository.bulkTicketInitialization(new MapSqlParameterSource[] {new MapSqlParameterSource() + .addValue("uuid", UUID.randomUUID().toString()) + .addValue("publicUuid", publicUuid) + .addValue("creation", new Date()) + .addValue("categoryId", null) + .addValue("eventId", event.getId()) + .addValue("status", Ticket.TicketStatus.FREE.name()) + .addValue("srcPriceCts", 100)})); + } + private EventModification createEventModification(int availableSeats, Event event) { return createEventModification(availableSeats, event, Event.EventFormat.IN_PERSON, event.getAllowedPaymentProxies(), event.getLocation()); } diff --git a/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java b/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java index c34745abb2..cc9ad61a9e 100644 --- a/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/ReverseChargeManagerIntegrationTest.java @@ -242,11 +242,11 @@ private ReservationInfo createReservation(String id, boolean requestInvoice) { var form = new ReservationForm(); var first = new TicketReservationModification(); - first.setAmount(2); + first.setQuantity(2); first.setTicketCategoryId(categories.get(0).getId()); var second = new TicketReservationModification(); - second.setAmount(2); + second.setQuantity(2); second.setTicketCategoryId(categories.get(1).getId()); form.setReservation(List.of(first, second)); @@ -289,7 +289,7 @@ private ReservationInfo createReservation(String id, boolean requestInvoice) { ticketForm.setFirstName("ticketfull"); ticketForm.setLastName("ticketname"); ticketForm.setEmail("tickettest@test.com"); - return Map.entry(t.getUuid(), ticketForm); + return Map.entry(t.getPublicUuid().toString(), ticketForm); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); contactForm.setTickets(tickets); diff --git a/src/test/java/alfio/manager/TicketReservationManagerTest.java b/src/test/java/alfio/manager/TicketReservationManagerTest.java index 8dadd2df6e..3591744ce0 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerTest.java @@ -736,13 +736,13 @@ private void initReleaseTicket() { void sendEmailToAssigneeOnSuccess() { initReleaseTicket(); when(ticketCategory.isAccessRestricted()).thenReturn(false); - when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(1); + when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(1); when(ticketCategory.isAccessRestricted()).thenReturn(false); List expectedReservations = singletonList(RESERVATION_ID); when(ticketReservationRepository.remove(eq(expectedReservations))).thenReturn(1); when(transactionRepository.loadOptionalByReservationId(anyString())).thenReturn(Optional.empty()); trm.releaseTicket(event, ticketReservation, ticket); - verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID)); + verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID)); verify(notificationManager).sendSimpleEmail(eq(event), eq(RESERVATION_ID), eq(RESERVATION_EMAIL), any(), any(TemplateGenerator.class)); verify(notificationManager).sendSimpleEmail(eq(event), isNull(), eq(ORG_EMAIL), any(), any(TemplateGenerator.class)); verify(organizationRepository).getById(eq(ORGANIZATION_ID)); @@ -763,7 +763,7 @@ void cannotReleaseRestrictedTicketIfNoUnboundedCategory() { void releaseRestrictedTicketIfUnboundedCategoryPresent() { initReleaseTicket(); when(ticketCategory.getId()).thenReturn(TICKET_CATEGORY_ID); - when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(1); + when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(1); when(ticketCategoryRepository.getByIdAndActive(eq(TICKET_CATEGORY_ID), eq(EVENT_ID))).thenReturn(ticketCategory); when(ticketCategory.isAccessRestricted()).thenReturn(true); when(ticketCategoryRepository.countUnboundedCategoriesByEventId(eq(EVENT_ID))).thenReturn(1); @@ -771,7 +771,7 @@ void releaseRestrictedTicketIfUnboundedCategoryPresent() { when(ticketReservationRepository.remove(eq(expectedReservations))).thenReturn(1); when(transactionRepository.loadOptionalByReservationId(anyString())).thenReturn(Optional.empty()); trm.releaseTicket(event, ticketReservation, ticket); - verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID)); + verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID)); verify(ticketRepository).unbindTicketsFromCategory(eq(EVENT_ID), eq(TICKET_CATEGORY_ID), eq(singletonList(TICKET_ID))); verify(notificationManager).sendSimpleEmail(eq(event), eq(RESERVATION_ID), eq(RESERVATION_EMAIL), any(), any(TemplateGenerator.class)); verify(organizationRepository).getById(eq(ORGANIZATION_ID)); @@ -781,13 +781,13 @@ void releaseRestrictedTicketIfUnboundedCategoryPresent() { @Test void throwExceptionIfMultipleTickets() { initReleaseTicket(); - when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(2); + when(ticketRepository.releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID))).thenReturn(2); try { trm.releaseTicket(event, ticketReservation, ticket); Assertions.fail(); } catch (IllegalArgumentException e) { Assertions.assertEquals("Expected 1 row to be updated, got 2", e.getMessage()); - verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), eq(EVENT_ID), eq(TICKET_ID)); + verify(ticketRepository).releaseTicket(eq(RESERVATION_ID), anyString(), any(UUID.class), eq(EVENT_ID), eq(TICKET_ID)); verify(notificationManager, never()).sendSimpleEmail(any(), any(), any(), any(), any(TemplateGenerator.class)); } } @@ -1142,14 +1142,14 @@ private static TicketReservation copy(TicketReservation reservation) { @Test void reservationURLGeneration() { String shortName = "shortName"; - String ticketId = "ticketId"; + UUID ticketId = UUID.randomUUID(); when(event.getType()).thenReturn(PurchaseContext.PurchaseContextType.event); when(event.getPublicIdentifier()).thenReturn(shortName); when(ticketReservation.getUserLanguage()).thenReturn("en"); when(ticketReservation.getId()).thenReturn(RESERVATION_ID); when(ticketReservationRepository.findReservationById(RESERVATION_ID)).thenReturn(ticketReservation); - when(ticketRepository.findByUUID(ticketId)).thenReturn(ticket); - when(ticket.getUuid()).thenReturn(ticketId); + when(ticketRepository.findByPublicUUID(ticketId)).thenReturn(ticket); + when(ticket.getPublicUuid()).thenReturn(ticketId); when(ticket.getUserLanguage()).thenReturn(USER_LANGUAGE); //generate the reservationUrl from RESERVATION_ID Assertions.assertEquals(BASE_URL + "event/" + shortName + "/reservation/" + RESERVATION_ID + "?lang=en", trm.reservationUrl(RESERVATION_ID)); @@ -1160,10 +1160,8 @@ void reservationURLGeneration() { when(event.getShortName()).thenReturn(shortName); - //generate the ticket URL - Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/ticketId?lang=it", trm.ticketUrl(event, ticketId)); //generate the ticket update URL - Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/ticketId/update?lang=it", ReservationUtil.ticketUpdateUrl(event, ticket, configurationManager)); + Assertions.assertEquals(BASE_URL + "event/" + shortName + "/ticket/"+ticketId+"/update?lang=it", ReservationUtil.ticketUpdateUrl(event, ticket, configurationManager)); } @Test @@ -1211,7 +1209,7 @@ void sendReminderOnlyIfNoPreviousNotifications() { when(ticket.getTicketsReservationId()).thenReturn(RESERVATION_ID); int ticketId = 2; when(ticket.getId()).thenReturn(ticketId); - when(ticket.getUuid()).thenReturn("uuid"); + when(ticket.getPublicUuid()).thenReturn(UUID.randomUUID()); when(ticket.getEmail()).thenReturn("ciccio"); when(ticketRepository.findAllAssignedButNotYetNotifiedForUpdate(EVENT_ID)).thenReturn(singletonList(ticket)); when(ticketReservationRepository.findOptionalReservationById(eq(RESERVATION_ID))).thenReturn(Optional.of(ticketReservation)); diff --git a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java index b073272785..9e6033e702 100644 --- a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java +++ b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java @@ -139,7 +139,7 @@ public void testSoldOut() throws InterruptedException { Event event = pair.getRight(); String reservationId = pair.getLeft(); Ticket firstTicket = ticketRepository.findTicketsInReservation(reservationId).get(0); - ticketRepository.releaseTicket(reservationId, UUID.randomUUID().toString(), event.getId(), firstTicket.getId()); + ticketRepository.releaseTicket(reservationId, UUID.randomUUID().toString(), UUID.randomUUID(), event.getId(), firstTicket.getId()); waitingQueueSubscriptionProcessor.distributeAvailableSeats(event); List subscriptions = waitingQueueRepository.loadAll(event.getId()); assertEquals(1, subscriptions.stream().filter(w -> StringUtils.isNotBlank(w.getReservationId())).count()); diff --git a/src/test/java/alfio/util/ObjectDiffTest.java b/src/test/java/alfio/util/ObjectDiffTest.java index d713a25c29..79eca9f704 100644 --- a/src/test/java/alfio/util/ObjectDiffTest.java +++ b/src/test/java/alfio/util/ObjectDiffTest.java @@ -25,19 +25,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; public class ObjectDiffTest { ZonedDateTime now = ZonedDateTime.now(TestUtil.clockProvider().getClock()); + UUID publicUUID = UUID.randomUUID(); - Ticket preUpdateTicket = new Ticket(42, "42", now, 1, Ticket.TicketStatus.ACQUIRED.name(), 42, + Ticket preUpdateTicket = new Ticket(42, "42", publicUUID, now, 1, Ticket.TicketStatus.ACQUIRED.name(), 42, "42", "full name", "full", "name", "email@email.com", false, "en", 0, 0, 0, 0, null, null, List.of(), null, PriceContainer.VatStatus.INCLUDED); - Ticket postUpdateTicket = new Ticket(42, "42", now, 1, Ticket.TicketStatus.CANCELLED.name(), 42, + Ticket postUpdateTicket = new Ticket(42, "42", publicUUID, now, 1, Ticket.TicketStatus.CANCELLED.name(), 42, "42", "full name", "full", "name", "email@email.com", false, "en", 0, 0, 0, 0, null, null, List.of(), null, PriceContainer.VatStatus.INCLUDED); diff --git a/src/test/resources/api/descriptor.json b/src/test/resources/api/descriptor.json index 4ef0d35ab2..fd97ff7d10 100644 --- a/src/test/resources/api/descriptor.json +++ b/src/test/resources/api/descriptor.json @@ -25,7 +25,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -96,7 +97,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "requestBody" : { @@ -177,7 +179,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -4113,7 +4116,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -13638,7 +13642,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -13711,7 +13716,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -13777,7 +13783,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } }, { "name" : "checkInCode", @@ -13864,7 +13871,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -13930,7 +13938,8 @@ "in" : "path", "required" : true, "schema" : { - "type" : "string" + "type" : "string", + "format" : "uuid" } } ], "responses" : { @@ -26103,17 +26112,17 @@ "location" : { "type" : "string" }, - "code" : { - "type" : "string" - }, - "description" : { - "type" : "string" - }, "arguments" : { "type" : "array", "items" : { "type" : "object" } + }, + "description" : { + "type" : "string" + }, + "code" : { + "type" : "string" } } }, @@ -26786,14 +26795,14 @@ "type" : "string", "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] }, + "discount" : { + "type" : "boolean" + }, "taxDetail" : { "type" : "boolean" }, "descriptionForPayment" : { "type" : "string" - }, - "discount" : { - "type" : "boolean" } } }, @@ -26807,6 +26816,10 @@ "uuid" : { "type" : "string" }, + "publicUuid" : { + "type" : "string", + "format" : "uuid" + }, "creation" : { "type" : "string", "format" : "date-time" @@ -26880,12 +26893,12 @@ "type" : "string", "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] }, - "formattedNetPrice" : { - "type" : "string" - }, "assigned" : { "type" : "boolean" }, + "formattedNetPrice" : { + "type" : "string" + }, "checkedIn" : { "type" : "boolean" }, @@ -27088,13 +27101,7 @@ "cancelled" : { "type" : "boolean" }, - "lineSplittedBillingAddress" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "hasInvoiceOrReceiptDocument" : { + "stuck" : { "type" : "boolean" }, "hasBillingAddress" : { @@ -27103,25 +27110,31 @@ "hasVatNumber" : { "type" : "boolean" }, - "paidAmount" : { - "type" : "string" - }, "hasInvoiceNumber" : { "type" : "boolean" }, "pendingOfflinePayment" : { "type" : "boolean" }, - "hasBeenPaid" : { + "paidAmount" : { + "type" : "string" + }, + "lineSplittedBillingAddress" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "hasInvoiceOrReceiptDocument" : { "type" : "boolean" }, - "stuck" : { + "hasBeenPaid" : { "type" : "boolean" }, - "netPrice" : { + "finalPrice" : { "type" : "number" }, - "finalPrice" : { + "netPrice" : { "type" : "number" }, "taxablePrice" : { @@ -27217,16 +27230,11 @@ "currencyCode" : { "type" : "string" }, - "lineSplittedBillingAddress" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "directAssignmentRequested" : { - "type" : "boolean" + "status" : { + "type" : "string", + "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] }, - "hasInvoiceOrReceiptDocument" : { + "stuck" : { "type" : "boolean" }, "reminderSent" : { @@ -27252,54 +27260,73 @@ "type" : "string", "format" : "date-time" }, - "status" : { + "customerReference" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "vatStatus" : { "type" : "string", - "enum" : [ "PENDING", "IN_PAYMENT", "EXTERNAL_PROCESSING_PAYMENT", "WAITING_EXTERNAL_CONFIRMATION", "OFFLINE_PAYMENT", "DEFERRED_OFFLINE_PAYMENT", "FINALIZING", "OFFLINE_FINALIZING", "COMPLETE", "STUCK", "CANCELLED", "CREDIT_NOTE_ISSUED" ] + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] }, "vat" : { "type" : "number" }, - "vatCts" : { - "type" : "integer", - "format" : "int32" + "discount" : { + "$ref" : "#/components/schemas/PromoCodeDiscount" }, - "vatNr" : { + "userLanguage" : { "type" : "string" }, - "email" : { - "type" : "string" + "hasInvoiceNumber" : { + "type" : "boolean" }, - "userLanguage" : { + "invoiceNumber" : { "type" : "string" }, - "confirmationTimestamp" : { + "paymentMethod" : { "type" : "string", - "format" : "date-time" + "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] }, - "paidAmount" : { - "type" : "string" + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" }, - "registrationTimestamp" : { - "type" : "string", - "format" : "date-time" + "vatCts" : { + "type" : "integer", + "format" : "int32" }, - "firstName" : { + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "vatNr" : { "type" : "string" }, - "lastName" : { + "vatCountryCode" : { "type" : "string" }, - "hasInvoiceNumber" : { + "invoiceRequested" : { "type" : "boolean" }, - "invoiceNumber" : { + "email" : { "type" : "string" }, - "paymentMethod" : { - "type" : "string", - "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" }, - "invoiceRequested" : { + "finalPrice" : { + "type" : "number" + }, + "appliedDiscount" : { + "type" : "number" + }, + "pendingOfflinePayment" : { "type" : "boolean" }, "promoCodeDiscountId" : { @@ -27319,54 +27346,40 @@ "billingAddress" : { "type" : "string" }, - "customerReference" : { - "type" : "string" - }, - "srcPriceCts" : { - "type" : "integer", - "format" : "int32" - }, - "pendingOfflinePayment" : { - "type" : "boolean" + "registrationTimestamp" : { + "type" : "string", + "format" : "date-time" }, - "vatStatus" : { + "confirmationTimestamp" : { "type" : "string", - "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + "format" : "date-time" }, - "discount" : { - "$ref" : "#/components/schemas/PromoCodeDiscount" + "paidAmount" : { + "type" : "string" }, - "finalPrice" : { - "type" : "number" + "lineSplittedBillingAddress" : { + "type" : "array", + "items" : { + "type" : "string" + } }, - "appliedDiscount" : { - "type" : "number" + "directAssignmentRequested" : { + "type" : "boolean" }, - "finalPriceCts" : { - "type" : "integer", - "format" : "int32" + "vatIncluded" : { + "type" : "boolean" }, - "discountCts" : { - "type" : "integer", - "format" : "int32" + "hasInvoiceOrReceiptDocument" : { + "type" : "boolean" }, - "vatCountryCode" : { - "type" : "string" + "hasBeenPaid" : { + "type" : "boolean" }, "optionalVatPercentage" : { "type" : "number" }, "taxablePrice" : { "type" : "number" - }, - "vatIncluded" : { - "type" : "boolean" - }, - "hasBeenPaid" : { - "type" : "boolean" - }, - "stuck" : { - "type" : "boolean" } } }, @@ -27570,17 +27583,17 @@ "format" : "int32" } }, - "restrictedValuesAsString" : { + "linkedCategoriesIds" : { "type" : "array", "items" : { - "type" : "string" + "type" : "integer", + "format" : "int32" } }, - "linkedCategoriesIds" : { + "restrictedValuesAsString" : { "type" : "array", "items" : { - "type" : "integer", - "format" : "int32" + "type" : "string" } }, "disabledValuesAsString" : { @@ -28176,14 +28189,11 @@ "errorMessage" : { "type" : "string" }, - "reservationStatusChanged" : { - "type" : "boolean" - }, "clientSecret" : { "type" : "string" }, - "token" : { - "type" : "string" + "reservationStatusChanged" : { + "type" : "boolean" }, "paymentMethod" : { "type" : "string", @@ -28192,6 +28202,9 @@ "paymentProvider" : { "type" : "string", "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] + }, + "token" : { + "type" : "string" } } }, @@ -28373,16 +28386,16 @@ "captcha" : { "type" : "string" }, - "tickets" : { + "additionalServices" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/TicketReservationModification" + "$ref" : "#/components/schemas/AdditionalServiceReservationModification" } }, - "additionalServices" : { + "tickets" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/AdditionalServiceReservationModification" + "$ref" : "#/components/schemas/TicketReservationModification" } } } @@ -28697,10 +28710,6 @@ "type" : "integer", "format" : "int32" }, - "numEntries" : { - "type" : "integer", - "format" : "int32" - }, "validityFrom" : { "type" : "string", "format" : "date-time" @@ -28712,6 +28721,10 @@ "timeUnit" : { "type" : "string", "enum" : [ "DAYS", "MONTHS", "YEARS" ] + }, + "numEntries" : { + "type" : "integer", + "format" : "int32" } } }, @@ -29253,12 +29266,6 @@ "TicketWithCategory" : { "type" : "object", "properties" : { - "ticket" : { - "$ref" : "#/components/schemas/Ticket" - }, - "category" : { - "$ref" : "#/components/schemas/TicketCategory" - }, "id" : { "type" : "integer", "format" : "int32" @@ -29266,92 +29273,39 @@ "fullName" : { "type" : "string" }, - "currencyCode" : { - "type" : "string" - }, - "formattedNetPrice" : { - "type" : "string" - }, - "extReference" : { - "type" : "string" - }, - "status" : { - "type" : "string", - "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] - }, - "tags" : { - "type" : "array", - "items" : { - "type" : "string" - } + "assigned" : { + "type" : "boolean" }, "eventId" : { "type" : "integer", "format" : "int32" }, - "vatCts" : { - "type" : "integer", - "format" : "int32" - }, - "email" : { - "type" : "string" - }, - "uuid" : { - "type" : "string" - }, - "assigned" : { - "type" : "boolean" - }, - "userLanguage" : { - "type" : "string" - }, - "checkedIn" : { - "type" : "boolean" - }, "firstName" : { "type" : "string" }, "lastName" : { "type" : "string" }, - "subscriptionId" : { - "type" : "string", - "format" : "uuid" - }, - "lockedAssignment" : { - "type" : "boolean" - }, - "srcPriceCts" : { - "type" : "integer", - "format" : "int32" + "userLanguage" : { + "type" : "string" }, "categoryId" : { "type" : "integer", "format" : "int32" }, - "ticketsReservationId" : { + "email" : { "type" : "string" }, - "vatStatus" : { - "type" : "string", - "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] - }, - "finalPriceCts" : { - "type" : "integer", - "format" : "int32" - }, - "discountCts" : { - "type" : "integer", - "format" : "int32" + "ticketsReservationId" : { + "type" : "string" }, - "categoryName" : { + "uuid" : { "type" : "string" }, - "creation" : { - "type" : "string", - "format" : "date-time" + "checkedIn" : { + "type" : "boolean" }, - "formattedFinalPrice" : { + "categoryName" : { "type" : "string" } } @@ -29834,9 +29788,6 @@ } } }, - "overlap" : { - "type" : "boolean" - }, "duration" : { "type" : "object", "properties" : { @@ -29887,6 +29838,9 @@ "instant" : { "type" : "string", "format" : "date-time" + }, + "overlap" : { + "type" : "boolean" } } } @@ -29964,11 +29918,12 @@ "supportsTicketsGeneration" : { "type" : "boolean" }, - "onSaleToModel" : { - "$ref" : "#/components/schemas/DateTimeModification" + "publicIdentifier" : { + "type" : "string" }, - "onSaleFromModel" : { - "$ref" : "#/components/schemas/DateTimeModification" + "priceCts" : { + "type" : "integer", + "format" : "int32" }, "validityFromModel" : { "$ref" : "#/components/schemas/DateTimeModification" @@ -29976,12 +29931,11 @@ "validityToModel" : { "$ref" : "#/components/schemas/DateTimeModification" }, - "publicIdentifier" : { - "type" : "string" + "onSaleFromModel" : { + "$ref" : "#/components/schemas/DateTimeModification" }, - "priceCts" : { - "type" : "integer", - "format" : "int32" + "onSaleToModel" : { + "$ref" : "#/components/schemas/DateTimeModification" } } }, @@ -30574,12 +30528,6 @@ "currencyCode" : { "type" : "string" }, - "formattedNetPrice" : { - "type" : "string" - }, - "extReference" : { - "type" : "string" - }, "status" : { "type" : "string", "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] @@ -30590,64 +30538,74 @@ "type" : "string" } }, - "eventId" : { - "type" : "integer", - "format" : "int32" + "assigned" : { + "type" : "boolean" }, - "vatCts" : { + "extReference" : { + "type" : "string" + }, + "formattedNetPrice" : { + "type" : "string" + }, + "eventId" : { "type" : "integer", "format" : "int32" }, - "email" : { + "firstName" : { "type" : "string" }, - "uuid" : { + "lastName" : { "type" : "string" }, - "assigned" : { - "type" : "boolean" + "vatStatus" : { + "type" : "string", + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] }, "userLanguage" : { "type" : "string" }, - "checkedIn" : { - "type" : "boolean" - }, - "firstName" : { - "type" : "string" - }, - "lastName" : { - "type" : "string" + "categoryId" : { + "type" : "integer", + "format" : "int32" }, "subscriptionId" : { "type" : "string", "format" : "uuid" }, - "lockedAssignment" : { - "type" : "boolean" + "finalPriceCts" : { + "type" : "integer", + "format" : "int32" }, - "srcPriceCts" : { + "vatCts" : { "type" : "integer", "format" : "int32" }, - "categoryId" : { + "discountCts" : { "type" : "integer", "format" : "int32" }, - "ticketsReservationId" : { + "email" : { "type" : "string" }, - "vatStatus" : { - "type" : "string", - "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + "ticketsReservationId" : { + "type" : "string" }, - "finalPriceCts" : { + "srcPriceCts" : { "type" : "integer", "format" : "int32" }, - "discountCts" : { - "type" : "integer", - "format" : "int32" + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "publicUuid" : { + "type" : "string", + "format" : "uuid" + }, + "checkedIn" : { + "type" : "boolean" }, "creation" : { "type" : "string", @@ -30923,9 +30881,6 @@ } } }, - "overlap" : { - "type" : "boolean" - }, "duration" : { "type" : "object", "properties" : { @@ -30976,6 +30931,9 @@ "instant" : { "type" : "string", "format" : "date-time" + }, + "overlap" : { + "type" : "boolean" } } } @@ -31450,17 +31408,7 @@ "formattedPrice" : { "type" : "string" }, - "begin" : { - "type" : "string", - "format" : "date-time" - }, - "contentLanguages" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/ContentLanguage" - } - }, - "privacyPolicyLinkOrNull" : { + "publicIdentifier" : { "type" : "string" }, "allowedPaymentProxies" : { @@ -31470,12 +31418,22 @@ "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] } }, - "publicIdentifier" : { - "type" : "string" + "contentLanguages" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ContentLanguage" + } + }, + "begin" : { + "type" : "string", + "format" : "date-time" }, "freeOfCharge" : { "type" : "boolean" }, + "privacyPolicyLinkOrNull" : { + "type" : "string" + }, "fileBlobIdIsPresent" : { "type" : "boolean" }, @@ -31554,49 +31512,37 @@ "currency" : { "type" : "string" }, - "offlinePaymentConfiguration" : { - "$ref" : "#/components/schemas/OfflinePaymentConfiguration" - }, - "formattedPrice" : { - "type" : "string" - }, - "title" : { + "description" : { "type" : "object", "additionalProperties" : { "type" : "string" } }, - "vat" : { + "formattedPrice" : { "type" : "string" }, - "free" : { - "type" : "boolean" - }, - "description" : { + "title" : { "type" : "object", "additionalProperties" : { "type" : "string" } }, - "assignmentConfiguration" : { - "$ref" : "#/components/schemas/AssignmentConfiguration" - }, - "canApplySubscriptions" : { - "type" : "boolean" + "maxEntries" : { + "type" : "integer", + "format" : "int32" }, - "fileBlobId" : { + "vat" : { "type" : "string" }, - "usageType" : { - "type" : "string", - "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] - }, "termsAndConditionsUrl" : { "type" : "string" }, "privacyPolicyUrl" : { "type" : "string" }, + "vatIncluded" : { + "type" : "boolean" + }, "validityUnits" : { "type" : "integer", "format" : "int32" @@ -31605,25 +31551,37 @@ "type" : "string", "enum" : [ "DAYS", "MONTHS", "YEARS" ] }, - "maxEntries" : { - "type" : "integer", - "format" : "int32" + "usageType" : { + "type" : "string", + "enum" : [ "ONCE_PER_EVENT", "UNLIMITED" ] }, - "vatIncluded" : { + "free" : { "type" : "boolean" }, + "fileBlobId" : { + "type" : "string" + }, "validityType" : { "type" : "string", "enum" : [ "STANDARD", "CUSTOM", "NOT_SET" ] }, - "currencyDescriptor" : { - "$ref" : "#/components/schemas/CurrencyDescriptor" + "assignmentConfiguration" : { + "$ref" : "#/components/schemas/AssignmentConfiguration" + }, + "offlinePaymentConfiguration" : { + "$ref" : "#/components/schemas/OfflinePaymentConfiguration" + }, + "canApplySubscriptions" : { + "type" : "boolean" }, "contentLanguages" : { "type" : "array", "items" : { "$ref" : "#/components/schemas/Language" } + }, + "currencyDescriptor" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" } } }, @@ -32059,20 +32017,20 @@ "redirectUrl" : { "type" : "string" }, - "failed" : { - "type" : "boolean" - }, "initialized" : { "type" : "boolean" }, "successful" : { "type" : "boolean" }, - "gatewayIdOrNull" : { - "type" : "string" + "failed" : { + "type" : "boolean" }, "redirect" : { "type" : "boolean" + }, + "gatewayIdOrNull" : { + "type" : "string" } } }, @@ -32263,11 +32221,8 @@ "type" : "string" } }, - "online" : { - "type" : "boolean" - }, - "free" : { - "type" : "boolean" + "publicIdentifier" : { + "type" : "string" }, "contentLanguages" : { "type" : "array", @@ -32275,12 +32230,30 @@ "$ref" : "#/components/schemas/ContentLanguage" } }, - "imageIsPresent" : { + "free" : { + "type" : "boolean" + }, + "regularPrice" : { + "type" : "number" + }, + "freeOfCharge" : { "type" : "boolean" }, + "sameDay" : { + "type" : "boolean" + }, + "online" : { + "type" : "boolean" + }, + "privacyPolicyLinkOrNull" : { + "type" : "string" + }, "fileBlobIdIsPresent" : { "type" : "boolean" }, + "imageIsPresent" : { + "type" : "boolean" + }, "multiplePaymentMethods" : { "type" : "boolean" }, @@ -32291,9 +32264,6 @@ "useFirstAndLastName" : { "type" : "boolean" }, - "privacyPolicyLinkOrNull" : { - "type" : "string" - }, "beginTimeZoneOffset" : { "type" : "integer", "format" : "int32" @@ -32305,18 +32275,6 @@ "isOnline" : { "type" : "boolean" }, - "publicIdentifier" : { - "type" : "string" - }, - "freeOfCharge" : { - "type" : "boolean" - }, - "regularPrice" : { - "type" : "number" - }, - "sameDay" : { - "type" : "boolean" - }, "firstContentLanguage" : { "$ref" : "#/components/schemas/ContentLanguage" } @@ -32429,6 +32387,9 @@ "currency" : { "type" : "string" }, + "shortName" : { + "type" : "string" + }, "title" : { "type" : "object", "additionalProperties" : { @@ -32438,47 +32399,44 @@ "vat" : { "type" : "string" }, - "free" : { - "type" : "boolean" - }, - "shortName" : { + "termsAndConditionsUrl" : { "type" : "string" }, - "websiteUrl" : { + "privacyPolicyUrl" : { "type" : "string" }, - "datesWithOffset" : { - "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" - }, - "organizationName" : { - "type" : "string" + "vatIncluded" : { + "type" : "boolean" }, - "organizationEmail" : { - "type" : "string" + "free" : { + "type" : "boolean" }, "fileBlobId" : { "type" : "string" }, - "termsAndConditionsUrl" : { + "websiteUrl" : { "type" : "string" }, - "privacyPolicyUrl" : { + "organizationName" : { "type" : "string" }, - "vatIncluded" : { - "type" : "boolean" + "organizationEmail" : { + "type" : "string" }, "sameDay" : { "type" : "boolean" }, - "currencyDescriptor" : { - "$ref" : "#/components/schemas/CurrencyDescriptor" + "datesWithOffset" : { + "$ref" : "#/components/schemas/DatesWithTimeZoneOffset" }, "contentLanguages" : { "type" : "array", "items" : { "$ref" : "#/components/schemas/Language" } + }, + "currencyDescriptor" : { + "$ref" : "#/components/schemas/CurrencyDescriptor" } } }, @@ -32940,12 +32898,12 @@ "descriptor" : { "$ref" : "#/components/schemas/SubscriptionDescriptor" }, - "unitPrice" : { - "type" : "number" - }, "availableCount" : { "type" : "integer", "format" : "int32" + }, + "unitPrice" : { + "type" : "number" } } }, @@ -33046,47 +33004,34 @@ "currencyCode" : { "type" : "string" }, - "formattedNetPrice" : { - "type" : "string" - }, - "extReference" : { - "type" : "string" - }, "status" : { "type" : "string", "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] }, + "stuck" : { + "type" : "boolean" + }, "tags" : { "type" : "array", "items" : { "type" : "string" } }, - "eventId" : { - "type" : "integer", - "format" : "int32" - }, - "vatCts" : { - "type" : "integer", - "format" : "int32" + "assigned" : { + "type" : "boolean" }, - "email" : { + "extReference" : { "type" : "string" }, - "uuid" : { + "formattedNetPrice" : { "type" : "string" }, - "assigned" : { - "type" : "boolean" - }, "transaction" : { "$ref" : "#/components/schemas/Transaction" }, - "userLanguage" : { - "type" : "string" - }, - "checkedIn" : { - "type" : "boolean" + "eventId" : { + "type" : "integer", + "format" : "int32" }, "firstName" : { "type" : "string" @@ -33094,42 +33039,62 @@ "lastName" : { "type" : "string" }, - "subscriptionId" : { + "vatStatus" : { "type" : "string", - "format" : "uuid" + "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] }, - "lockedAssignment" : { - "type" : "boolean" + "userLanguage" : { + "type" : "string" }, - "srcPriceCts" : { + "categoryId" : { "type" : "integer", "format" : "int32" }, - "transactionTimestamp" : { + "subscriptionId" : { "type" : "string", - "format" : "date-time" + "format" : "uuid" }, - "categoryId" : { + "finalPriceCts" : { "type" : "integer", "format" : "int32" }, + "vatCts" : { + "type" : "integer", + "format" : "int32" + }, + "discountCts" : { + "type" : "integer", + "format" : "int32" + }, + "email" : { + "type" : "string" + }, "ticketsReservationId" : { "type" : "string" }, - "vatStatus" : { - "type" : "string", - "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] + "srcPriceCts" : { + "type" : "integer", + "format" : "int32" }, "finalPrice" : { "type" : "number" }, - "finalPriceCts" : { - "type" : "integer", - "format" : "int32" + "transactionTimestamp" : { + "type" : "string", + "format" : "date-time" }, - "discountCts" : { - "type" : "integer", - "format" : "int32" + "uuid" : { + "type" : "string" + }, + "lockedAssignment" : { + "type" : "boolean" + }, + "publicUuid" : { + "type" : "string", + "format" : "uuid" + }, + "checkedIn" : { + "type" : "boolean" }, "creation" : { "type" : "string", @@ -33138,9 +33103,6 @@ "formattedFinalPrice" : { "type" : "string" }, - "stuck" : { - "type" : "boolean" - }, "paid" : { "type" : "boolean" }, @@ -33210,12 +33172,12 @@ "complete" : { "type" : "boolean" }, - "notes" : { - "type" : "string" - }, "potentialMatch" : { "type" : "boolean" }, + "notes" : { + "type" : "string" + }, "timestampEditable" : { "type" : "boolean" }, @@ -33298,95 +33260,25 @@ "fullName" : { "type" : "string" }, - "currencyCode" : { + "firstName" : { "type" : "string" }, - "formattedNetPrice" : { + "lastName" : { "type" : "string" }, - "extReference" : { + "userLanguage" : { "type" : "string" }, - "status" : { - "type" : "string", - "enum" : [ "FREE", "PENDING", "TO_BE_PAID", "ACQUIRED", "CANCELLED", "CHECKED_IN", "EXPIRED", "INVALIDATED", "RELEASED", "PRE_RESERVED" ] - }, - "tags" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "eventId" : { - "type" : "integer", - "format" : "int32" - }, - "vatCts" : { - "type" : "integer", - "format" : "int32" - }, "email" : { "type" : "string" }, - "uuid" : { - "type" : "string" - }, - "assigned" : { - "type" : "boolean" - }, "additionalFields" : { "type" : "object", "additionalProperties" : { "type" : "string" } }, - "userLanguage" : { - "type" : "string" - }, - "checkedIn" : { - "type" : "boolean" - }, - "firstName" : { - "type" : "string" - }, - "lastName" : { - "type" : "string" - }, - "subscriptionId" : { - "type" : "string", - "format" : "uuid" - }, - "lockedAssignment" : { - "type" : "boolean" - }, - "srcPriceCts" : { - "type" : "integer", - "format" : "int32" - }, - "categoryId" : { - "type" : "integer", - "format" : "int32" - }, - "ticketsReservationId" : { - "type" : "string" - }, - "vatStatus" : { - "type" : "string", - "enum" : [ "NONE", "INCLUDED", "NOT_INCLUDED", "INCLUDED_EXEMPT", "NOT_INCLUDED_EXEMPT", "CUSTOM_INCLUDED_EXEMPT", "CUSTOM_NOT_INCLUDED_EXEMPT", "INCLUDED_NOT_CHARGED", "NOT_INCLUDED_NOT_CHARGED" ] - }, - "finalPriceCts" : { - "type" : "integer", - "format" : "int32" - }, - "discountCts" : { - "type" : "integer", - "format" : "int32" - }, - "creation" : { - "type" : "string", - "format" : "date-time" - }, - "formattedFinalPrice" : { + "uuid" : { "type" : "string" } } @@ -33520,9 +33412,6 @@ } } }, - "overlap" : { - "type" : "boolean" - }, "duration" : { "type" : "object", "properties" : { @@ -33573,6 +33462,9 @@ "instant" : { "type" : "string", "format" : "date-time" + }, + "overlap" : { + "type" : "boolean" } } } @@ -33659,39 +33551,35 @@ "type" : "integer", "format" : "int32" }, - "subscriptionDescriptorId" : { - "type" : "string", - "format" : "uuid" - }, "status" : { "type" : "string", "enum" : [ "WAITING", "RETRY", "IN_PROCESS", "SENT", "ERROR" ] }, - "eventId" : { + "organizationId" : { "type" : "integer", "format" : "int32" }, - "cc" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "subject" : { - "type" : "string" - }, - "organizationId" : { + "eventId" : { "type" : "integer", "format" : "int32" }, + "checksum" : { + "type" : "string" + }, "attempts" : { "type" : "integer", "format" : "int32" }, - "checksum" : { + "recipient" : { "type" : "string" }, - "recipient" : { + "cc" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "subject" : { "type" : "string" }, "htmlMessage" : { @@ -33704,6 +33592,10 @@ "type" : "string", "enum" : [ "subscription", "event" ] }, + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, "requestTimestamp" : { "type" : "string", "format" : "date-time" @@ -33762,13 +33654,6 @@ "type" : "integer", "format" : "int32" }, - "subscriptionDescriptorId" : { - "type" : "string", - "format" : "uuid" - }, - "inputField" : { - "type" : "boolean" - }, "textareaField" : { "type" : "boolean" }, @@ -33778,6 +33663,9 @@ "checkboxField" : { "type" : "boolean" }, + "euVat" : { + "type" : "boolean" + }, "maxLengthDefined" : { "type" : "boolean" }, @@ -33787,14 +33675,10 @@ "textField" : { "type" : "boolean" }, - "order" : { - "type" : "integer", - "format" : "int32" - }, - "required" : { + "inputField" : { "type" : "boolean" }, - "eventId" : { + "order" : { "type" : "integer", "format" : "int32" }, @@ -33806,17 +33690,15 @@ "type" : "integer", "format" : "int32" }, + "required" : { + "type" : "boolean" + }, "inputType" : { "type" : "string" }, - "countryField" : { - "type" : "boolean" - }, - "restrictedValues" : { - "type" : "array", - "items" : { - "type" : "string" - } + "eventId" : { + "type" : "integer", + "format" : "int32" }, "additionalServiceId" : { "type" : "integer", @@ -33832,7 +33714,14 @@ "dateOfBirth" : { "type" : "boolean" }, - "disabledValues" : { + "subscriptionDescriptorId" : { + "type" : "string", + "format" : "uuid" + }, + "countryField" : { + "type" : "boolean" + }, + "restrictedValues" : { "type" : "array", "items" : { "type" : "string" @@ -33841,8 +33730,11 @@ "editable" : { "type" : "boolean" }, - "euVat" : { - "type" : "boolean" + "disabledValues" : { + "type" : "array", + "items" : { + "type" : "string" + } } } }, @@ -33919,9 +33811,6 @@ "type" : "integer", "format" : "int32" }, - "inputField" : { - "type" : "boolean" - }, "textareaField" : { "type" : "boolean" }, @@ -33931,6 +33820,9 @@ "checkboxField" : { "type" : "boolean" }, + "euVat" : { + "type" : "boolean" + }, "maxLengthDefined" : { "type" : "boolean" }, @@ -33940,16 +33832,16 @@ "textField" : { "type" : "boolean" }, + "inputField" : { + "type" : "boolean" + }, "inputType" : { "type" : "string" }, - "countryField" : { - "type" : "boolean" - }, "dateOfBirth" : { "type" : "boolean" }, - "euVat" : { + "countryField" : { "type" : "boolean" } } @@ -34057,14 +33949,14 @@ "$ref" : "#/components/schemas/PollOptionStatistics" } }, + "participationPercentage" : { + "type" : "string" + }, "optionStatistics" : { "type" : "array", "items" : { "$ref" : "#/components/schemas/StatisticDetail" } - }, - "participationPercentage" : { - "type" : "string" } } }, @@ -34708,9 +34600,6 @@ "PromoCodeDiscountWithFormattedTimeAndAmount" : { "type" : "object", "properties" : { - "dynamic" : { - "type" : "boolean" - }, "id" : { "type" : "integer", "format" : "int32" @@ -34718,6 +34607,12 @@ "currencyCode" : { "type" : "string" }, + "dynamic" : { + "type" : "boolean" + }, + "description" : { + "type" : "string" + }, "formattedStart" : { "type" : "string" }, @@ -34727,30 +34622,11 @@ "formattedDiscountAmount" : { "type" : "string" }, - "eventId" : { - "type" : "integer", - "format" : "int32" - }, - "utcEnd" : { - "type" : "string", - "format" : "date-time" - }, - "description" : { - "type" : "string" - }, - "categories" : { - "uniqueItems" : true, - "type" : "array", - "items" : { - "type" : "integer", - "format" : "int32" - } - }, "organizationId" : { "type" : "integer", "format" : "int32" }, - "maxUsage" : { + "eventId" : { "type" : "integer", "format" : "int32" }, @@ -34761,6 +34637,10 @@ "type" : "string", "format" : "date-time" }, + "utcEnd" : { + "type" : "string", + "format" : "date-time" + }, "discountAmount" : { "type" : "integer", "format" : "int32" @@ -34777,8 +34657,17 @@ "type" : "integer", "format" : "int32" }, - "fixedAmount" : { - "type" : "boolean" + "maxUsage" : { + "type" : "integer", + "format" : "int32" + }, + "categories" : { + "uniqueItems" : true, + "type" : "array", + "items" : { + "type" : "integer", + "format" : "int32" + } }, "currentlyValid" : { "type" : "boolean" @@ -34788,6 +34677,9 @@ }, "expired" : { "type" : "boolean" + }, + "fixedAmount" : { + "type" : "boolean" } } }, @@ -35090,9 +34982,6 @@ "type" : "integer", "format" : "int32" }, - "formattedEnd" : { - "type" : "string" - }, "status" : { "type" : "string", "enum" : [ "DRAFT", "PUBLIC", "DISABLED" ] @@ -35100,13 +34989,9 @@ "shortName" : { "type" : "string" }, - "fileBlobId" : { + "formattedEnd" : { "type" : "string" }, - "notAllocatedTickets" : { - "type" : "integer", - "format" : "int32" - }, "organizationId" : { "type" : "integer", "format" : "int32" @@ -35118,17 +35003,12 @@ "enum" : [ "STRIPE", "ON_SITE", "OFFLINE", "NONE", "ADMIN", "PAYPAL", "MOLLIE", "SAFERPAY" ] } }, - "visibleForCurrentUser" : { - "type" : "boolean" - }, - "displayStatistics" : { - "type" : "boolean" - }, - "formattedBegin" : { + "fileBlobId" : { "type" : "string" }, - "warningNeeded" : { - "type" : "boolean" + "notAllocatedTickets" : { + "type" : "integer", + "format" : "int32" }, "availableSeats" : { "type" : "integer", @@ -35140,6 +35020,18 @@ }, "expired" : { "type" : "boolean" + }, + "warningNeeded" : { + "type" : "boolean" + }, + "formattedBegin" : { + "type" : "string" + }, + "visibleForCurrentUser" : { + "type" : "boolean" + }, + "displayStatistics" : { + "type" : "boolean" } } }, @@ -35161,31 +35053,31 @@ "url" : { "type" : "string" }, - "end" : { - "type" : "string" - }, "begin" : { "type" : "string" }, - "imageUrl" : { + "end" : { "type" : "string" }, "latitude" : { "type" : "string" }, - "descriptions" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PublicEventDescription" - } - }, "longitude" : { "type" : "string" }, + "imageUrl" : { + "type" : "string" + }, "apiVersion" : { "type" : "integer", "format" : "int32" }, + "descriptions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PublicEventDescription" + } + }, "oneDay" : { "type" : "boolean" }