diff --git a/gradle.properties b/gradle.properties index 0e06629ed3..380dee5f94 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ junitVersion=5.1.0 systemProp.jdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2" # https://jitpack.io/#alfio-event/alf.io-public-frontend -> go to commit tab, set the version -alfioPublicFrontendVersion=03e5d06c5b \ No newline at end of file +alfioPublicFrontendVersion=290339eb1b \ No newline at end of file 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 d42b9780ad..b8cabc7796 100644 --- a/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/ReservationApiV2Controller.java @@ -22,6 +22,7 @@ import alfio.controller.api.v2.model.ReservationInfo.TicketsByTicketCategory; import alfio.controller.api.v2.model.ReservationPaymentResult; import alfio.controller.api.v2.model.ReservationStatusInfo; +import alfio.controller.api.v2.user.support.BookingInfoTicketLoader; import alfio.controller.form.ContactAndTicketsForm; import alfio.controller.form.PaymentForm; import alfio.controller.support.TemplateProcessor; @@ -65,7 +66,6 @@ import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.BiFunction; -import java.util.function.Function; import java.util.stream.Collectors; import static alfio.model.PriceContainer.VatStatus.*; @@ -93,7 +93,7 @@ public class ReservationApiV2Controller { private final TicketHelper ticketHelper; private final EuVatChecker vatChecker; private final RecaptchaService recaptchaService; - private final AdditionalServiceItemRepository additionalServiceItemRepository; + private final BookingInfoTicketLoader bookingInfoTicketLoader; /** * Note: now it will return for any states of the reservation. @@ -126,7 +126,7 @@ public ResponseEntity getReservationInfo(@PathVariable("eventNa boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(reservationId); // - var ticketFieldsFilterer = getTicketFieldsFilterer(reservationId, event); + var ticketFieldsFilterer = bookingInfoTicketLoader.getTicketFieldsFilterer(reservationId, event); var ticketsByCategory = tickets.stream().collect(Collectors.groupingBy(Ticket::getCategoryId)); //TODO: cleanup this transformation, we most likely don't need to fully load the ticket category @@ -135,16 +135,9 @@ public ResponseEntity getReservationInfo(@PathVariable("eventNa .stream() .map(e -> { var tc = eventManager.getTicketCategoryById(e.getKey(), event.getId()); - var ts = e.getValue().stream().map(t -> {// - - - // TODO: n+1, should be cleaned up! see TicketDecorator.getCancellationEnabled - boolean cancellationEnabled = t.getFinalPriceCts() == 0 && - (!hasPaidSupplement && configurationManager.getFor(ALLOW_FREE_TICKETS_CANCELLATION, ConfigurationLevel.ticketCategory(event, t.getCategoryId())).getValueAsBooleanOrDefault(false)) && // freeCancellationEnabled - eventManager.checkTicketCancellationPrerequisites().apply(t); // cancellationPrerequisitesMet - // - return toBookingInfoTicket(t, cancellationEnabled, ticketFieldsFilterer.getFieldsForTicket(t.getUuid()), descriptionsByTicketFieldId, valuesByTicketIds.getOrDefault(t.getId(), Collections.emptyList())); - }).collect(Collectors.toList()); + var ts = e.getValue().stream() + .map(t -> bookingInfoTicketLoader.toBookingInfoTicket(t, hasPaidSupplement, event, ticketFieldsFilterer, descriptionsByTicketFieldId, valuesByTicketIds)) + .collect(Collectors.toList()); return new TicketsByTicketCategory(tc.getName(), ts); }) .collect(Collectors.toList()); @@ -211,13 +204,6 @@ private Map getActivePaymentMethods(E } } - private Validator.TicketFieldsFilterer getTicketFieldsFilterer(String reservationId, EventAndOrganizationId event) { - var fields = ticketFieldRepository.findAdditionalFieldsForEvent(event.getId()); - return new Validator.TicketFieldsFilterer(fields, ticketHelper.getTicketUUIDToCategoryId(), - new HashSet<>(additionalServiceItemRepository.findAdditionalServiceIdsByReservationUuid(reservationId)), - ticketReservationManager.findFirstInReservation(reservationId)); - } - @GetMapping("/event/{eventName}/reservation/{reservationId}/status") public ResponseEntity getReservationStatus(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId) { @@ -389,7 +375,7 @@ public ResponseEntity> validateToOverview(@PathVariab Map formValidationParameters = Collections.singletonMap(ENABLE_ITALY_E_INVOICING, italyEInvoicing); - var ticketFieldFilterer = getTicketFieldsFilterer(reservationId, event); + var ticketFieldFilterer = bookingInfoTicketLoader.getTicketFieldsFilterer(reservationId, event); // contactAndTicketsForm.validate(bindingResult, event, new SameCountryValidator(configurationManager, extensionManager, event, reservationId, vatChecker), @@ -630,45 +616,6 @@ public ResponseEntity getTransactionStatus(@PathVariab } - private static ReservationInfo.AdditionalField toAdditionalField(TicketFieldConfigurationDescriptionAndValue t, Map description) { - var fields = t.getFields().stream().map(f -> new ReservationInfo.Field(f.getFieldIndex(), f.getFieldValue())).collect(Collectors.toList()); - return new ReservationInfo.AdditionalField(t.getName(), t.getValue(), t.getType(), t.isRequired(), t.isEditable(), - t.getMinLength(), t.getMaxLength(), t.getRestrictedValues(), - fields, t.isBeforeStandardFields(), description); - } - - private static Map fromFieldDescriptions(List descs) { - return descs.stream().collect(Collectors.toMap(TicketFieldDescription::getLocale, - d -> new ReservationInfo.Description(d.getLabelDescription(), d.getPlaceholderDescription(), d.getRestrictedValuesDescription()))); - } - - private static ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, - boolean cancellationEnabled, - List ticketFields, - Map> descriptionsByTicketFieldId, - List ticketFieldValues) { - - - var valueById = ticketFieldValues.stream().collect(Collectors.toMap(TicketFieldValue::getTicketFieldConfigurationId, Function.identity())); - - - var tfcdav = ticketFields.stream() - .sorted(Comparator.comparing(TicketFieldConfiguration::getOrder)) - .map(tfc -> { - var tfd = descriptionsByTicketFieldId.get(tfc.getId()).get(0);//take first, temporary! - var fieldValue = valueById.get(tfc.getId()); - var t = new TicketFieldConfigurationDescriptionAndValue(tfc, tfd, tfc.getCount(), fieldValue == null ? null : fieldValue.getValue()); - var descs = fromFieldDescriptions(descriptionsByTicketFieldId.get(t.getTicketFieldConfigurationId())); - return toAdditionalField(t, descs); - }).collect(Collectors.toList()); - - return new ReservationInfo.BookingInfoTicket(ticket.getUuid(), - ticket.getFirstName(), ticket.getLastName(), - ticket.getEmail(), ticket.getFullName(), - ticket.getUserLanguage(), - ticket.getAssigned(), ticket.getLockedAssignment(), ticket.getStatus() == Ticket.TicketStatus.ACQUIRED, cancellationEnabled, tfcdav); - } - private Map formatDateForLocales(Event event, ZonedDateTime date, String formattingCode) { var messageSource = messageSourceManager.getMessageSourceForEvent(event); 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 84ac516733..138eb72754 100644 --- a/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java +++ b/src/main/java/alfio/controller/api/v2/user/TicketApiV2Controller.java @@ -18,27 +18,26 @@ import alfio.controller.api.support.TicketHelper; import alfio.controller.api.v2.model.DatesWithTimeZoneOffset; +import alfio.controller.api.v2.model.ReservationInfo; import alfio.controller.api.v2.model.TicketInfo; +import alfio.controller.api.v2.user.support.BookingInfoTicketLoader; import alfio.controller.form.UpdateTicketOwnerForm; import alfio.controller.support.Formatters; import alfio.controller.support.TemplateProcessor; -import alfio.manager.ExtensionManager; -import alfio.manager.FileUploadManager; -import alfio.manager.NotificationManager; -import alfio.manager.TicketReservationManager; +import alfio.manager.*; import alfio.manager.i18n.MessageSourceManager; import alfio.manager.support.response.ValidatedResponse; -import alfio.model.Event; -import alfio.model.Ticket; -import alfio.model.TicketCategory; -import alfio.model.TicketReservation; +import alfio.manager.system.ConfigurationManager; +import alfio.model.*; import alfio.model.transaction.PaymentProxy; import alfio.model.user.Organization; import alfio.repository.TicketCategoryRepository; +import alfio.repository.TicketFieldRepository; import alfio.repository.user.OrganizationRepository; import alfio.util.ImageUtil; import alfio.util.LocaleUtil; import alfio.util.TemplateManager; +import alfio.util.Validator; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -51,8 +50,10 @@ import java.io.IOException; import java.io.OutputStream; import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.stream.Collectors; @RestController @AllArgsConstructor @@ -67,6 +68,7 @@ public class TicketApiV2Controller { private final OrganizationRepository organizationRepository; private final TemplateManager templateManager; private final NotificationManager notificationManager; + private final BookingInfoTicketLoader bookingInfoTicketLoader; @GetMapping(value = { @@ -157,6 +159,19 @@ public ResponseEntity releaseTicket(@PathVariable("eventName") String e } } + @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}/full") + public ResponseEntity getTicket(@PathVariable("eventName") String eventName, + @PathVariable("ticketIdentifier") String ticketIdentifier) { + + var optionalTicket = ticketReservationManager.fetchCompleteAndAssigned(eventName, ticketIdentifier) + .map(complete -> { + var ticket = complete.getRight(); + var event = complete.getLeft(); + var categoryName = ticketCategoryRepository.getByIdAndActive(ticket.getCategoryId(), event.getId()).getName(); + return new ReservationInfo.TicketsByTicketCategory(categoryName, List.of(bookingInfoTicketLoader.toBookingInfoTicket(ticket, event))); + }); + return ResponseEntity.of(optionalTicket); + } @GetMapping("/api/v2/public/event/{eventName}/ticket/{ticketIdentifier}") public ResponseEntity getTicketInfo(@PathVariable("eventName") String eventName, diff --git a/src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java b/src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java new file mode 100644 index 0000000000..ca62bb9ee0 --- /dev/null +++ b/src/main/java/alfio/controller/api/v2/user/support/BookingInfoTicketLoader.java @@ -0,0 +1,128 @@ +/** + * 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 . + */ +package alfio.controller.api.v2.user.support; + +import alfio.controller.api.support.TicketHelper; +import alfio.controller.api.v2.model.ReservationInfo; +import alfio.manager.EventManager; +import alfio.manager.TicketReservationManager; +import alfio.manager.system.ConfigurationLevel; +import alfio.manager.system.ConfigurationManager; +import alfio.model.*; +import alfio.repository.AdditionalServiceItemRepository; +import alfio.repository.TicketFieldRepository; +import alfio.util.Validator; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static alfio.model.system.ConfigurationKeys.ALLOW_FREE_TICKETS_CANCELLATION; + +@Component +@AllArgsConstructor +public class BookingInfoTicketLoader { + + private final EventManager eventManager; + private final ConfigurationManager configurationManager; + private final TicketFieldRepository ticketFieldRepository; + private final TicketHelper ticketHelper; + private final AdditionalServiceItemRepository additionalServiceItemRepository; + private final TicketReservationManager ticketReservationManager; + + + public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) { + var descriptionsByTicketFieldId = ticketFieldRepository.findDescriptions(event.getShortName()) + .stream() + .collect(Collectors.groupingBy(TicketFieldDescription::getTicketFieldConfigurationId)); + + var valuesByTicketIds = ticketFieldRepository.findAllValuesByTicketIds(List.of(ticket.getId())) + .stream() + .collect(Collectors.groupingBy(TicketFieldValue::getTicketId)); + + boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(ticket.getTicketsReservationId()); + + return toBookingInfoTicket(ticket, + hasPaidSupplement, + event, + getTicketFieldsFilterer(ticket.getTicketsReservationId(), event), + descriptionsByTicketFieldId, + valuesByTicketIds); + } + + public ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket t, + boolean hasPaidSupplement, + Event event, + Validator.TicketFieldsFilterer ticketFieldsFilterer, + Map> descriptionsByTicketFieldId, + Map> valuesByTicketIds) { + // TODO: n+1, should be cleaned up! see TicketDecorator.getCancellationEnabled + boolean cancellationEnabled = t.getFinalPriceCts() == 0 && + (!hasPaidSupplement && configurationManager.getFor(ALLOW_FREE_TICKETS_CANCELLATION, ConfigurationLevel.ticketCategory(event, t.getCategoryId())).getValueAsBooleanOrDefault(false)) && // freeCancellationEnabled + eventManager.checkTicketCancellationPrerequisites().apply(t); // cancellationPrerequisitesMet + // + return toBookingInfoTicket(t, cancellationEnabled, ticketFieldsFilterer.getFieldsForTicket(t.getUuid()), descriptionsByTicketFieldId, valuesByTicketIds.getOrDefault(t.getId(), Collections.emptyList())); + } + + public Validator.TicketFieldsFilterer getTicketFieldsFilterer(String reservationId, EventAndOrganizationId event) { + var fields = ticketFieldRepository.findAdditionalFieldsForEvent(event.getId()); + return new Validator.TicketFieldsFilterer(fields, ticketHelper.getTicketUUIDToCategoryId(), + new HashSet<>(additionalServiceItemRepository.findAdditionalServiceIdsByReservationUuid(reservationId)), + ticketReservationManager.findFirstInReservation(reservationId)); + } + + private static ReservationInfo.BookingInfoTicket toBookingInfoTicket(Ticket ticket, + boolean cancellationEnabled, + List ticketFields, + Map> descriptionsByTicketFieldId, + List ticketFieldValues) { + + + var valueById = ticketFieldValues.stream().collect(Collectors.toMap(TicketFieldValue::getTicketFieldConfigurationId, Function.identity())); + + + var tfcdav = ticketFields.stream() + .sorted(Comparator.comparing(TicketFieldConfiguration::getOrder)) + .map(tfc -> { + var tfd = descriptionsByTicketFieldId.get(tfc.getId()).get(0);//take first, temporary! + var fieldValue = valueById.get(tfc.getId()); + var t = new TicketFieldConfigurationDescriptionAndValue(tfc, tfd, tfc.getCount(), fieldValue == null ? null : fieldValue.getValue()); + var descs = fromFieldDescriptions(descriptionsByTicketFieldId.get(t.getTicketFieldConfigurationId())); + return toAdditionalField(t, descs); + }).collect(Collectors.toList()); + + return new ReservationInfo.BookingInfoTicket(ticket.getUuid(), + ticket.getFirstName(), ticket.getLastName(), + ticket.getEmail(), ticket.getFullName(), + ticket.getUserLanguage(), + ticket.getAssigned(), ticket.getLockedAssignment(), ticket.getStatus() == Ticket.TicketStatus.ACQUIRED, cancellationEnabled, tfcdav); + } + + private static ReservationInfo.AdditionalField toAdditionalField(TicketFieldConfigurationDescriptionAndValue t, Map description) { + var fields = t.getFields().stream().map(f -> new ReservationInfo.Field(f.getFieldIndex(), f.getFieldValue())).collect(Collectors.toList()); + return new ReservationInfo.AdditionalField(t.getName(), t.getValue(), t.getType(), t.isRequired(), t.isEditable(), + t.getMinLength(), t.getMaxLength(), t.getRestrictedValues(), + fields, t.isBeforeStandardFields(), description); + } + + private static Map fromFieldDescriptions(List descs) { + return descs.stream().collect(Collectors.toMap(TicketFieldDescription::getLocale, + d -> new ReservationInfo.Description(d.getLabelDescription(), d.getPlaceholderDescription(), d.getRestrictedValuesDescription()))); + } +}