Skip to content

Commit

Permalink
fix #211 Allow multiple option purchase
Browse files Browse the repository at this point in the history
(cherry picked from commit f5cc296)
  • Loading branch information
cbellone committed Sep 26, 2016
1 parent 77d946c commit 4a66eb9
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 51 deletions.
4 changes: 1 addition & 3 deletions src/main/java/alfio/config/SpringBootInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@
import com.openhtmltopdf.util.XRLog;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.ErrorPage;
import org.springframework.boot.context.embedded.MimeMappings;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;

Expand Down Expand Up @@ -84,7 +82,7 @@ public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
container.setSessionTimeout(2, TimeUnit.HOURS);
container.setMimeMappings(new MimeMappings(mimeMappings));

container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404-not-found"), new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500-internal-server-error"), new ErrorPage("/session-expired"));
//container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404-not-found"), new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500-internal-server-error"), new ErrorPage("/session-expired"));
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public String unbindTickets(@PathVariable("eventName") String eventName, @PathVa
return OK;
}

private static final List<String> FIXED_FIELDS = Arrays.asList("ID", "creation", "category", "event", "status", "originalPrice", "paidPrice", "discount", "vat", "reservationID", "Full Name", "First name", "Last name", "E-Mail", "locked", "Language");
private static final List<String> FIXED_FIELDS = Arrays.asList("ID", "creation", "category", "event", "status", "originalPrice", "paidPrice", "discount", "vat", "reservationID", "Full Name", "First Name", "Last Name", "E-Mail", "locked", "Language");
private static final int[] BOM_MARKERS = new int[] {0xEF, 0xBB, 0xBF};

@RequestMapping("/events/{eventName}/export.csv")
Expand Down Expand Up @@ -298,7 +298,7 @@ public void downloadAllTicketsCSV(@PathVariable("eventName") String eventName, H
Map<String, String> additionalValues = ticketFieldRepository.findAllValuesForTicketId(t.getId());

fields.stream().filter(contains.negate()).forEachOrdered(field -> {
line.add(additionalValues.getOrDefault(field, ""));
line.add(additionalValues.getOrDefault(field, "").replaceAll("\"", ""));
});

return line.toArray(new String[line.size()]);
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/alfio/controller/api/support/TicketHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,19 @@ public TicketHelper(TicketReservationManager ticketReservationManager,
this.additionalServiceItemRepository = additionalServiceItemRepository;
}

public List<Pair<TicketFieldConfigurationAndDescription, String>> findTicketFieldConfigurationAndValue(int eventId, Ticket ticket, Locale locale) {
public List<TicketFieldConfigurationDescriptionAndValue> findTicketFieldConfigurationAndValue(int eventId, Ticket ticket, Locale locale) {
Map<Integer, TicketFieldDescription> descriptions = ticketFieldRepository.findTranslationsFor(locale, eventId);
Map<String, TicketFieldValue> values = ticketFieldRepository.findAllByTicketIdGroupedByName(ticket.getId());
Function<TicketFieldConfiguration, String> extractor = (f) -> Optional.ofNullable(values.get(f.getName())).map(TicketFieldValue::getValue).orElse("");
Set<Integer> additionalServiceIds = additionalServiceItemRepository.findByReservationUuid(ticket.getTicketsReservationId()).stream().map(AdditionalServiceItem::getAdditionalServiceId).collect(Collectors.toSet());
List<AdditionalServiceItem> additionalServiceItems = additionalServiceItemRepository.findByReservationUuid(ticket.getTicketsReservationId());
Set<Integer> additionalServiceIds = additionalServiceItems.stream().map(AdditionalServiceItem::getAdditionalServiceId).collect(Collectors.toSet());
return ticketFieldRepository.findAdditionalFieldsForEvent(eventId)
.stream()
.filter(f -> f.getContext() == ATTENDEE || Optional.ofNullable(f.getAdditionalServiceId()).filter(additionalServiceIds::contains).isPresent())
.map(f-> Pair.of(new TicketFieldConfigurationAndDescription(f, descriptions.getOrDefault(f.getId(), TicketFieldDescription.MISSING_FIELD)), extractor.apply(f)))
.map(f-> {
int count = Math.max(1, Optional.ofNullable(f.getAdditionalServiceId()).map(id -> (int) additionalServiceItems.stream().filter(i -> i.getAdditionalServiceId() == id).count()).orElse(1));
return new TicketFieldConfigurationDescriptionAndValue(f, descriptions.getOrDefault(f.getId(), TicketFieldDescription.MISSING_FIELD), count, extractor.apply(f));
})
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public boolean getVatApplies() {
}

public int[] getAmountOfTickets() {
return new int[]{0, 1};
return DecoratorUtil.generateRangeOfTicketQuantity(additionalService.getMaxQtyPerOrder(), 100);
}

public boolean getSoldOut() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import lombok.Data;

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

@Data
Expand All @@ -29,5 +30,5 @@ public class UpdateTicketOwnerForm {
private String userLanguage;


private Map<String, String> additional;
private Map<String, List<String>> additional;
}
13 changes: 6 additions & 7 deletions src/main/java/alfio/controller/support/TicketDecorator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@
package alfio.controller.support;

import alfio.model.Ticket;
import alfio.model.TicketFieldConfigurationAndDescription;
import alfio.model.TicketFieldConfigurationDescriptionAndValue;
import lombok.experimental.Delegate;
import org.apache.commons.lang3.tuple.Pair;

import java.util.List;
import java.util.function.Function;
Expand All @@ -32,13 +31,13 @@ public class TicketDecorator {
private final boolean freeCancellationEnabled;
private final boolean conditionsMet;
private final String urlSuffix;
private final List<Pair<TicketFieldConfigurationAndDescription, String>> ticketFieldConfiguration;
private final List<TicketFieldConfigurationDescriptionAndValue> ticketFieldConfiguration;
private final boolean forceDisplayAssignForm;

private TicketDecorator(Ticket ticket,
boolean freeCancellationEnabled,
boolean conditionsMet,
List<Pair<TicketFieldConfigurationAndDescription, String>> ticketFieldConfiguration,
List<TicketFieldConfigurationDescriptionAndValue> ticketFieldConfiguration,
boolean forceDisplayAssignForm) {
this(ticket, freeCancellationEnabled, conditionsMet, ticket.getUuid(), ticketFieldConfiguration, forceDisplayAssignForm);
}
Expand All @@ -47,7 +46,7 @@ public TicketDecorator(Ticket ticket,
boolean freeCancellationEnabled,
boolean conditionsMet,
String urlSuffix,
List<Pair<TicketFieldConfigurationAndDescription, String>> ticketFieldConfiguration,
List<TicketFieldConfigurationDescriptionAndValue> ticketFieldConfiguration,
boolean forceDisplayAssignForm) {
this.ticket = ticket;
this.freeCancellationEnabled = freeCancellationEnabled;
Expand All @@ -73,15 +72,15 @@ public boolean getCancellationEnabled() {
return isFree() && freeCancellationEnabled && conditionsMet;
}

public List<Pair<TicketFieldConfigurationAndDescription, String>> getTicketFieldConfiguration() {
public List<TicketFieldConfigurationDescriptionAndValue> getTicketFieldConfiguration() {
return ticketFieldConfiguration;
}

public boolean getDisplayAssignForm() {
return !getAssigned() || forceDisplayAssignForm;
}

public static List<TicketDecorator> decorate(List<Ticket> tickets, boolean freeCancellationEnabled, Function<Ticket, Boolean> categoryEvaluator, Function<Ticket, List<Pair<TicketFieldConfigurationAndDescription, String>>> fieldsLoader, boolean forceDisplayAssignForm) {
public static List<TicketDecorator> decorate(List<Ticket> tickets, boolean freeCancellationEnabled, Function<Ticket, Boolean> categoryEvaluator, Function<Ticket, List<TicketFieldConfigurationDescriptionAndValue>> fieldsLoader, boolean forceDisplayAssignForm) {
return tickets.stream().map(t -> new TicketDecorator(t, freeCancellationEnabled, categoryEvaluator.apply(t), fieldsLoader.apply(t), forceDisplayAssignForm)).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,29 @@
*/
package alfio.model;

import alfio.util.Json;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@AllArgsConstructor
public class TicketFieldConfigurationAndDescription {
public class TicketFieldConfigurationDescriptionAndValue {

@Delegate
private final TicketFieldConfiguration ticketFieldConfiguration;
@Delegate
private final TicketFieldDescription ticketFieldDescription;
private final int count;
private final String value;


public List<Pair<String, String>> getTranslatedRestrictedValue() {
Expand All @@ -40,4 +48,23 @@ public List<Pair<String, String>> getTranslatedRestrictedValue() {
.map(val -> Pair.of(val, description.getOrDefault(val, "MISSING_DESCRIPTION")))
.collect(Collectors.toList());
}

public List<TicketFieldValue> getFields() {
if(count == 1) {
return Collections.singletonList(new TicketFieldValue(0, 1, value));
}
List<String> values = StringUtils.isBlank(value) ? Collections.emptyList() : Json.fromJson(value, new TypeReference<List<String>>() {});
return IntStream.range(0, count)
.mapToObj(i -> new TicketFieldValue(i, i+1, i < values.size() ? values.get(i) : ""))
.collect(Collectors.toList());

}

@RequiredArgsConstructor
private static class TicketFieldValue {
private final int fieldIndex;
private final int fieldCounter;
private final String fieldValue;
}

}
14 changes: 12 additions & 2 deletions src/main/java/alfio/repository/TicketFieldRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package alfio.repository;

import alfio.model.*;
import alfio.util.Json;
import ch.digitalfondue.npjt.Bind;
import ch.digitalfondue.npjt.Query;
import ch.digitalfondue.npjt.QueryRepository;
Expand Down Expand Up @@ -56,14 +57,23 @@ public interface TicketFieldRepository extends FieldRepository {
@Query("select field_name, field_value from ticket_field_value inner join ticket_field_configuration on ticket_field_configuration_id_fk = id where ticket_id_fk = :ticketId")
List<FieldNameAndValue> findNameAndValue(@Bind("ticketId") int ticketId);

default void updateOrInsert(Map<String, String> values, Ticket ticket, Event event) {
default void updateOrInsert(Map<String, List<String>> values, Ticket ticket, Event event) {
int ticketId = ticket.getId();
int eventId = event.getId();
Map<String, TicketFieldValue> toUpdate = findAllByTicketIdGroupedByName(ticketId);
values = Optional.ofNullable(values).orElseGet(Collections::emptyMap);
Map<String, Integer> fieldNameToId = findAdditionalFieldsForEvent(eventId).stream().collect(Collectors.toMap(TicketFieldConfiguration::getName, TicketFieldConfiguration::getId));

values.forEach((fieldName, fieldValue) -> {
values.forEach((fieldName, fieldValues) -> {
String fieldValue;
if(fieldValues.size() == 1) {
fieldValue = fieldValues.get(0);
} else if(fieldValues.stream().anyMatch(StringUtils::isNotBlank)) {
fieldValue = Json.toJson(fieldValues);
} else {
fieldValue = "";
}

boolean isNotBlank = StringUtils.isNotBlank(fieldValue);
if(toUpdate.containsKey(fieldName)) {
TicketFieldValue field = toUpdate.get(fieldName);
Expand Down
15 changes: 8 additions & 7 deletions src/main/java/alfio/util/Validator.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,16 @@ public static ValidationResult validateTicketAssignment(UpdateTicketOwnerForm fo
continue;
}

String formValue = form.getAdditional().get(fieldConf.getName());
form.getAdditional().get(fieldConf.getName()).forEach(formValue -> {
if(fieldConf.isMaxLengthDefined()) {
validateMaxLength(formValue, "additional['"+fieldConf.getName()+"']", "error."+fieldConf.getName(), fieldConf.getMaxLength(), errors);
}

if(fieldConf.isMaxLengthDefined()) {
validateMaxLength(formValue, "additional['"+fieldConf.getName()+"']", "error."+fieldConf.getName(), fieldConf.getMaxLength(), errors);
}
if(!fieldConf.getRestrictedValues().isEmpty()) {
validateRestrictedValue(formValue, "additional['"+fieldConf.getName()+"']", "error."+fieldConf.getName(), fieldConf.getRestrictedValues(), errors);
}
});

if(!fieldConf.getRestrictedValues().isEmpty()) {
validateRestrictedValue(formValue, "additional['"+fieldConf.getName()+"']", "error."+fieldConf.getName(), fieldConf.getRestrictedValues(), errors);
}

//TODO: complete checks: min length, mandatory
}
Expand Down
44 changes: 23 additions & 21 deletions src/main/webapp/WEB-INF/templates/event/assign-ticket-form.ms
Original file line number Diff line number Diff line change
Expand Up @@ -84,39 +84,41 @@
</div>
<div {{#displayAssignForm}}class="collapsible"{{/displayAssignForm}}>
{{#ticketFieldConfiguration}}
{{#fields}}
<div class="form-group">
<label class="col-sm-3 control-label" for="{{key.name}}-title-{{uuid}}">{{key.labelDescription}}</label>
<label class="col-sm-3 control-label" for="{{name}}-title-{{uuid}}{{^-first}}-{{fieldCounter}}{{/-first}}">{{labelDescription}} {{^-first}}{{fieldCounter}}{{/-first}}</label>
<div class="col-sm-9">
{{#key.inputField}}
<input type="{{key.inputType}}" name="additional['{{key.name}}']" value="{{value}}" id="{{key.name}}-title-{{uuid}}" class="form-control"
{{#key.placeholderDescriptionDefined}}placeholder="{{key.placeholderDescription}}"{{/key.placeholderDescriptionDefined}}
{{#key.maxLengthDefined}}maxlength="{{key.maxLength}}"{{/key.maxLengthDefined}}
{{#key.minLengthDefined}}minlength="{{key.minLength}}"{{/key.minLengthDefined}}
{{#inputField}}
<input type="{{inputType}}" name="additional['{{name}}'][{{fieldIndex}}]" value="{{fieldValue}}" id="{{name}}-title-{{uuid}}{{^-first}}-{{fieldCounter}}{{/-first}}" class="form-control"
{{#placeholderDescriptionDefined}}placeholder="{{placeholderDescription}}"{{/placeholderDescriptionDefined}}
{{#maxLengthDefined}}maxlength="{{maxLength}}"{{/maxLengthDefined}}
{{#minLengthDefined}}minlength="{{minLength}}"{{/minLengthDefined}}
>
{{/key.inputField}}
{{#key.textareaField}}
<textarea name="additional['{{key.name}}']" id="{{key.name}}-{{uuid}}" class="form-control"
{{#key.placeholderDescriptionDefined}}placeholder="{{key.placeholderDescription}}"{{/key.placeholderDescriptionDefined}}
{{#key.maxLengthDefined}}maxlength="{{key.maxLength}}"{{/key.maxLengthDefined}}
{{#key.minLengthDefined}}minlength="{{key.minLength}}"{{/key.minLengthDefined}}
>{{value}}</textarea>
{{/key.textareaField}}
{{#key.countryField}}
<select name="additional['{{key.name}}']" value="{{value}}" id="{{key.name}}-{{uuid}}" class="form-control">
{{/inputField}}
{{#textareaField}}
<textarea name="additional['{{name}}'][{{fieldIndex}}]" id="{{name}}-title-{{uuid}}{{^-first}}-{{fieldCounter}}{{/-first}}" class="form-control"
{{#placeholderDescriptionDefined}}placeholder="{{placeholderDescription}}"{{/placeholderDescriptionDefined}}
{{#maxLengthDefined}}maxlength="{{maxLength}}"{{/maxLengthDefined}}
{{#minLengthDefined}}minlength="{{minLength}}"{{/minLengthDefined}}
>{{fieldValue}}</textarea>
{{/textareaField}}
{{#countryField}}
<select name="additional['{{name}}'][{{fieldIndex}}]" value="{{fieldValue}}" id="{{name}}-title-{{uuid}}{{^-first}}-{{fieldCounter}}{{/-first}}" class="form-control">
<option value=""></option>
{{#countries}}
<option value="{{left}}">{{right}}</option>
{{/countries}}
</select>
{{/key.countryField}}
{{#key.selectField}}
<select name="additional['{{key.name}}']" value="{{value}}" id="{{key.name}}-{{uuid}}" class="form-control">
{{/countryField}}
{{#selectField}}
<select name="additional['{{name}}'][{{fieldIndex}}]" value="{{fieldValue}}" id="{{name}}-title-{{uuid}}{{^-first}}-{{fieldCounter}}{{/-first}}" class="form-control">
<option value=""></option>
{{#key.translatedRestrictedValue}}<option value="{{key}}">{{value}}</option>{{/key.translatedRestrictedValue}}
{{#translatedRestrictedValue}}<option value="{{key}}">{{value}}</option>{{/translatedRestrictedValue}}
</select>
{{/key.selectField}}
{{/selectField}}
</div>
</div>
{{/fields}}
{{/ticketFieldConfiguration}}
<div class="form-group">
<label class="col-sm-3 control-label" for="language-{{uuid}}">{{#i18n}}reservation-page-complete.language{{/i18n}}</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ <h3><i class="fa fa-money"></i> Donation options</h3>
<span data-ng-if="item.fixPrice">{{item.price | currency : (item.currency || "")}}</span>
</div>
</div>
<div class="row" data-ng-if="!event.freeOfCharge && item.fixPrice">
<div class="col-sm-4"><strong>Final price</strong></div>
<div class="col-sm-8">{{item.finalPrice | currency : (item.currency || "")}}</div>
<div class="row" data-ng-if="item.fixPrice">
<div class="col-sm-4"><strong>Max Qty per order</strong></div>
<div class="col-sm-8">{{item.maxQtyPerOrder}}</div>
</div>
</div>
<div class="hidden-xs col-md-7">
Expand Down
Loading

0 comments on commit 4a66eb9

Please sign in to comment.