Skip to content

Commit

Permalink
Manage additional service quantity (#1308)
Browse files Browse the repository at this point in the history
* handle limited availability on additional items
* save values in a dedicated, previously unused table
* move additional item(s) between tickets
* add additional services to PDF
  • Loading branch information
cbellone authored Dec 17, 2023
1 parent 63c5925 commit 1355106
Show file tree
Hide file tree
Showing 81 changed files with 1,809 additions and 580 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ targetCompatibility=11
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=6828f03d3a
alfioPublicFrontendVersion=21b82f85cb
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,7 @@ public ResponseEntity<EventModification.AdditionalService> update(@PathVariable(
Validate.isTrue(additionalServiceId == additionalService.getId(), "wrong input");
return eventRepository.findOptionalById(eventId)
.map(event -> {
int result = additionalServiceManager.update(additionalServiceId, additionalService.isFixPrice(),
additionalService.getOrdinal(), additionalService.getAvailableQuantity(), additionalService.getMaxQtyPerOrder(), additionalService.getInception().toZonedDateTime(event.getZoneId()),
additionalService.getExpiration().toZonedDateTime(event.getZoneId()), additionalService.getVat(), additionalService.getVatType(), Optional.ofNullable(additionalService.getPrice()).map(p -> MonetaryUtil.unitToCents(p, event.getCurrency())).orElse(0));
int result = additionalServiceManager.update(additionalServiceId, event, additionalService);
Validate.isTrue(result <= 1, "too many records updated");
Stream.concat(additionalService.getTitle().stream(), additionalService.getDescription().stream()).
forEach(t -> {
Expand All @@ -122,7 +120,7 @@ public ResponseEntity<EventModification.AdditionalService> insert(@PathVariable(
ValidationResult validationResult = Validator.validateAdditionalService(additionalService, bindingResult);
Validate.isTrue(validationResult.isSuccess(), "validation failed");
return eventRepository.findOptionalById(eventId)
.map(event -> ResponseEntity.ok(eventManager.insertAdditionalService(event, additionalService)))
.map(event -> ResponseEntity.ok(additionalServiceManager.insertAdditionalService(event, additionalService)))
.orElseThrow(IllegalArgumentException::new);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.math.BigDecimal;
import java.security.Principal;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -276,7 +277,7 @@ public ResponseEntity<BookingInfoTicket> loadFullTicketData(@PathVariable("purch

return ResponseEntity.of(
adminReservationManager.loadFullTicketInfo(reservationId, publicIdentifier, ticketUUID)
.map(eventAndTicket -> bookingInfoTicketLoader.toBookingInfoTicket(eventAndTicket.getRight(), eventAndTicket.getLeft()))
.map(eventAndTicket -> bookingInfoTicketLoader.toBookingInfoTicket(eventAndTicket.getRight(), eventAndTicket.getLeft(), EnumSet.allOf(TicketFieldConfiguration.Context.class)))
);
}

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/alfio/controller/api/support/AdditionalField.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package alfio.controller.api.support;

import alfio.model.TicketFieldConfiguration;
import lombok.AllArgsConstructor;
import lombok.Getter;

Expand All @@ -36,4 +37,22 @@ public class AdditionalField {
private final List<Field> fields;
private final boolean beforeStandardFields;
private final Map<String, Description> description;

public static AdditionalField fromFieldConfiguration(TicketFieldConfiguration tfc,
String value,
List<Field> fields,
boolean isBeforeStandardFields,
Map<String, Description> description) {
return new AdditionalField(tfc.getName(),
value,
tfc.getType(),
tfc.isRequired(),
tfc.isEditable(),
tfc.getMinLength(),
tfc.getMaxLength(),
tfc.getRestrictedValues(),
fields,
isBeforeStandardFields,
description);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package alfio.controller.api.support;

import java.util.List;
import java.util.Map;


public class AdditionalServiceWithData {
private final Map<String, String> title;
private final int itemId;
private final int serviceId;
private final String ticketUUID;
private final List<AdditionalField> ticketFieldConfiguration;

public AdditionalServiceWithData(Map<String, String> title,
int itemId,
Integer serviceId,
String ticketUUID,
List<AdditionalField> ticketFieldConfiguration) {
this.title = title;
this.itemId = itemId;
this.serviceId = serviceId;
this.ticketUUID = ticketUUID;
this.ticketFieldConfiguration = ticketFieldConfiguration;
}

public Map<String, String> getTitle() {
return title;
}

public int getItemId() {
return itemId;
}

public int getServiceId() {
return serviceId;
}

public String getTicketUUID() {
return ticketUUID;
}

public List<AdditionalField> getTicketFieldConfiguration() {
return ticketFieldConfiguration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static alfio.model.system.ConfigurationKeys.*;

Expand All @@ -51,7 +51,7 @@ public class BookingInfoTicketLoader {
private final ClockProvider clockProvider;


public BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) {
public BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event, Set<TicketFieldConfiguration.Context> contexts) {
var descriptionsByTicketFieldId = ticketFieldRepository.findDescriptions(event.getShortName())
.stream()
.collect(Collectors.groupingBy(TicketFieldDescription::getTicketFieldConfigurationId));
Expand All @@ -60,7 +60,7 @@ public BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) {
.stream()
.collect(Collectors.groupingBy(TicketFieldValue::getTicketId));

boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(ticket.getTicketsReservationId());
boolean hasPaidSupplement = ticketReservationManager.hasPaidSupplements(ticket.getEventId(), ticket.getTicketsReservationId());
Map<String, String> formattedDates = Map.of();
boolean onlineEventStarted = false;
if(event.isOnline()) {
Expand All @@ -81,7 +81,8 @@ public BookingInfoTicket toBookingInfoTicket(Ticket ticket, Event event) {
descriptionsByTicketFieldId,
valuesByTicketIds,
formattedDates,
onlineEventStarted);
onlineEventStarted,
contexts);
}

public BookingInfoTicket toBookingInfoTicket(Ticket t,
Expand All @@ -91,7 +92,8 @@ public BookingInfoTicket toBookingInfoTicket(Ticket t,
Map<Integer, List<TicketFieldDescription>> descriptionsByTicketFieldId,
Map<Integer, List<TicketFieldValue>> valuesByTicketIds,
Map<String, String> formattedOnlineCheckInDate,
boolean onlineEventStarted) {
boolean onlineEventStarted,
Set<TicketFieldConfiguration.Context> contexts) {
// TODO: n+1, should be cleaned up! see TicketDecorator.getCancellationEnabled
var configuration = configurationManager.getFor(EnumSet.of(ALLOW_FREE_TICKETS_CANCELLATION, SEND_TICKETS_AUTOMATICALLY, ALLOW_TICKET_DOWNLOAD), ConfigurationLevel.ticketCategory(event, t.getCategoryId()));
boolean cancellationEnabled = t.getFinalPriceCts() == 0 &&
Expand All @@ -103,18 +105,19 @@ public BookingInfoTicket toBookingInfoTicket(Ticket t,
cancellationEnabled,
configuration.get(SEND_TICKETS_AUTOMATICALLY).getValueAsBooleanOrDefault(),
configuration.get(ALLOW_TICKET_DOWNLOAD).getValueAsBooleanOrDefault(),
ticketFieldsFilterer.getFieldsForTicket(t.getUuid()),
ticketFieldsFilterer.getFieldsForTicket(t.getUuid(), contexts),
descriptionsByTicketFieldId,
valuesByTicketIds.getOrDefault(t.getId(), Collections.emptyList()),
formattedOnlineCheckInDate,
onlineEventStarted);
}

public Validator.TicketFieldsFilterer getTicketFieldsFilterer(String reservationId, EventAndOrganizationId event) {
public Validator.TicketFieldsFilterer getTicketFieldsFilterer(String reservationId, Event event) {
var fields = ticketFieldRepository.findAdditionalFieldsForEvent(event.getId());
return new Validator.TicketFieldsFilterer(fields, ticketHelper.getTicketUUIDToCategoryId(),
new HashSet<>(additionalServiceItemRepository.findAdditionalServiceIdsByReservationUuid(reservationId)),
ticketReservationManager.findFirstInReservation(reservationId));
return new Validator.TicketFieldsFilterer(fields,
ticketReservationManager.findTicketsInReservation(reservationId),
event.supportsLinkedAdditionalServices(),
additionalServiceItemRepository.findByReservationUuid(event.getId(), reservationId));
}

private static BookingInfoTicket toBookingInfoTicket(Ticket ticket,
Expand All @@ -128,17 +131,27 @@ private static BookingInfoTicket toBookingInfoTicket(Ticket ticket,
boolean onlineEventStarted) {


var valueById = ticketFieldValues.stream().collect(Collectors.toMap(TicketFieldValue::getTicketFieldConfigurationId, Function.identity()));
var valuesById = ticketFieldValues.stream()
.collect(Collectors.groupingBy(TicketFieldValue::getTicketFieldConfigurationId));


var ticketFieldsAdditional = ticketFields.stream()
// hide additional service related fields
.filter(ticketFieldConfiguration -> ticketFieldConfiguration.getAdditionalServiceId() != null)
.sorted(Comparator.comparing(TicketFieldConfiguration::getOrder))
.map(tfc -> {
.flatMap(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);
var fieldValues = valuesById.get(tfc.getId());
var descs = fromFieldDescriptions(descriptionsByTicketFieldId.get(tfc.getId()));
if (fieldValues == null) {
var t = new TicketFieldConfigurationDescriptionAndValue(tfc, tfd, tfc.getCount(), null);
return Stream.of(toAdditionalField(t, descs));
}
return fieldValues.stream()
.map(fieldValue -> {
var t = new TicketFieldConfigurationDescriptionAndValue(tfc, tfd, tfc.getCount(), fieldValue.getValue());
return toAdditionalField(t, descs);
});
}).collect(Collectors.toList());

return new BookingInfoTicket(ticket.getUuid(),
Expand Down Expand Up @@ -168,7 +181,7 @@ private static AdditionalField toAdditionalField(TicketFieldConfigurationDescrip
fields, t.isBeforeStandardFields(), description);
}

private static Map<String, Description> fromFieldDescriptions(List<TicketFieldDescription> descs) {
public static Map<String, Description> fromFieldDescriptions(List<TicketFieldDescription> descs) {
return descs.stream().collect(Collectors.toMap(TicketFieldDescription::getLocale,
d -> new Description(d.getLabelDescription(), d.getPlaceholderDescription(), d.getRestrictedValuesDescription())));
}
Expand Down
Loading

0 comments on commit 1355106

Please sign in to comment.