Skip to content

Commit

Permalink
New reservation flow (#484)
Browse files Browse the repository at this point in the history
* #464 import initial model changes

* #464 import stubs for new flow

* #464 make the free flow work: still WIP

* #464 add text/breadcrumb

* #464 handle back, fix test

* #464 remove payment form from the reservation-page.ms

* #464 enable stripe, refactor

* #464 re-enable paypal

* #464 remove term and condition links from reservation-page + handle them correctly after paypal redirect

* #464 fix test

* #464 re enable validation for step2, highlight errors

* #464 select label

* #464 reset form before cancel

* #464 split PaymentForm

* #464 initial work for mandatory invoice ui/ux

* #464 hide billing fields for _not_ invoice only mode

* #464 invoice available mode: show checkbox

* #464 invoice available mode: invoice details section, initial work

* #464 invoice details form

* #464 translations

* #464 translations

* #464 add vat nr input, wire up select country to vat nr label

* #464 initial billing validation
  • Loading branch information
syjer authored and cbellone committed Jul 2, 2018
1 parent 9707a98 commit 25ff546
Show file tree
Hide file tree
Showing 28 changed files with 1,445 additions and 719 deletions.
301 changes: 196 additions & 105 deletions src/main/java/alfio/controller/ReservationController.java

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions src/main/java/alfio/controller/api/ReservationApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package alfio.controller.api;

import alfio.controller.api.support.TicketHelper;
import alfio.controller.form.PaymentForm;
import alfio.controller.form.ContactAndTicketsForm;
import alfio.controller.form.UpdateTicketOwnerForm;
import alfio.manager.EuVatChecker;
import alfio.manager.TicketReservationManager;
Expand Down Expand Up @@ -128,18 +128,18 @@ public ResponseEntity<Boolean> resetVat(@PathVariable("eventName") String eventN
@Transactional
public ResponseEntity<VatDetail> validateEUVat(@PathVariable("eventName") String eventName,
@PathVariable("reservationId") String reservationId,
PaymentForm paymentForm,
ContactAndTicketsForm contactAndTicketsForm,
Locale locale,
HttpServletRequest request) {

String country = paymentForm.getVatCountryCode();
/*String country = contactAndTicketsForm.getVatCountryCode();
Optional<Triple<Event, TicketReservation, VatDetail>> vatDetail;
try {
vatDetail = eventRepository.findOptionalByShortName(eventName)
.flatMap(e -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r)))
.filter(e -> EnumSet.of(INCLUDED, NOT_INCLUDED).contains(e.getKey().getVatStatus()))
.filter(e -> vatChecker.isVatCheckingEnabledFor(e.getKey().getOrganizationId()))
.flatMap(e -> vatChecker.checkVat(paymentForm.getVatNr(), country, e.getKey().getOrganizationId()).map(vd -> Triple.of(e.getLeft(), e.getRight(), vd)));
.flatMap(e -> vatChecker.checkVat(contactAndTicketsForm.getVatNr(), country, e.getKey().getOrganizationId()).map(vd -> Triple.of(e.getLeft(), e.getRight(), vd)));
} catch (IllegalStateException e) {
return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
}
Expand All @@ -151,13 +151,13 @@ public ResponseEntity<VatDetail> validateEUVat(@PathVariable("eventName") String
VatDetail vd = t.getRight();
String billingAddress = vd.getName() + "\n" + vd.getAddress();
PriceContainer.VatStatus vatStatus = determineVatStatus(t.getLeft().getVatStatus(), t.getRight().isVatExempt());
ticketReservationRepository.updateBillingData(vatStatus, vd.getVatNr(), country, paymentForm.isInvoiceRequested(), reservationId);
ticketReservationRepository.updateBillingData(vatStatus, vd.getVatNr(), country, contactAndTicketsForm.isInvoiceRequested(), reservationId);
OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, t.getLeft(), Locale.forLanguageTag(t.getMiddle().getUserLanguage()));
ticketReservationRepository.addReservationInvoiceOrReceiptModel(reservationId, Json.toJson(orderSummary));
ticketReservationRepository.updateTicketReservation(reservationId, t.getMiddle().getStatus().name(), paymentForm.getEmail(),
paymentForm.getFullName(), paymentForm.getFirstName(), paymentForm.getLastName(), locale.getLanguage(), billingAddress, null,
Optional.ofNullable(paymentForm.getPaymentMethod()).map(PaymentProxy::name).orElse(null), paymentForm.getCustomerReference());
paymentForm.getTickets().forEach((ticketId, owner) -> {
ticketReservationRepository.updateTicketReservation(reservationId, t.getMiddle().getStatus().name(), contactAndTicketsForm.getEmail(),
contactAndTicketsForm.getFullName(), contactAndTicketsForm.getFirstName(), contactAndTicketsForm.getLastName(), locale.getLanguage(), billingAddress, null,
Optional.ofNullable(contactAndTicketsForm.getPaymentMethod()).map(PaymentProxy::name).orElse(null), contactAndTicketsForm.getCustomerReference());
contactAndTicketsForm.getTickets().forEach((ticketId, owner) -> {
if(isNotEmpty(owner.getEmail()) && ((isNotEmpty(owner.getFirstName()) && isNotEmpty(owner.getLastName())) || isNotEmpty(owner.getFullName()))) {
ticketHelper.preAssignTicket(eventName, reservationId, ticketId, owner, Optional.empty(), request, (tr) -> {}, Optional.empty());
}
Expand All @@ -173,7 +173,8 @@ public ResponseEntity<VatDetail> validateEUVat(@PathVariable("eventName") String
return new ResponseEntity<VatDetail>(HttpStatus.BAD_REQUEST);
}
})
.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));*/
return null;
}

private static PriceContainer.VatStatus determineVatStatus(PriceContainer.VatStatus current, boolean isVatExempt) {
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/alfio/controller/api/support/TicketHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ public Optional<Triple<ValidationResult, Event, Ticket>> preAssignTicket(String
}

public Optional<Triple<ValidationResult, Event, Ticket>> assignTicket(String eventName,
String reservationId,
String ticketIdentifier,
UpdateTicketOwnerForm updateTicketOwner,
Optional<Errors> bindingResult,
Expand Down Expand Up @@ -171,7 +170,7 @@ public Optional<Triple<ValidationResult, Event, Ticket>> directTicketAssignment(
form.setFirstName(firstName);
form.setLastName(lastName);
form.setUserLanguage(userLanguage);
return assignTicket(eventName, reservationId, ticketUuid, form, bindingResult, request, model);
return assignTicket(eventName, ticketUuid, form, bindingResult, request, model);
}

public static List<Pair<String, String>> getLocalizedCountries(Locale locale) {
Expand Down
176 changes: 176 additions & 0 deletions src/main/java/alfio/controller/form/ContactAndTicketsForm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* 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.form;

import alfio.manager.EuVatChecker;
import alfio.model.*;
import alfio.model.result.ValidationResult;
import alfio.util.ErrorsCode;
import alfio.util.Validator;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ValidationUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

import static alfio.util.ErrorsCode.STEP_2_INVALID_VAT;
import static alfio.util.ErrorsCode.STEP_2_MISSING_ATTENDEE_DATA;

// step 2 : contact/claim tickets
//
@Data
public class ContactAndTicketsForm implements Serializable {

private String email;
private String fullName;
private String firstName;
private String lastName;
private String billingAddress;
private String customerReference;
private Boolean cancelReservation;

private Boolean expressCheckoutRequested;
private boolean postponeAssignment = false;
private String vatCountryCode;
private String vatNr;
private boolean invoiceRequested = false;
private Map<String, UpdateTicketOwnerForm> tickets = new HashMap<>();
//
private String billingAddressCompany;
private String billingAddressLine1;
private String billingAddressLine2;
private String billingAddressZip;
private String billingAddressCity;

private boolean addCompanyBillingDetails = false;
private Boolean backFromOverview;

private static void rejectIfOverLength(BindingResult bindingResult, String field, String errorCode,
String value, int maxLength) {
if (value != null && value.length() > maxLength) {
bindingResult.rejectValue(field, errorCode);
}
}



public void validate(BindingResult bindingResult, Event event, List<TicketFieldConfiguration> fieldConf, EuVatChecker.SameCountryValidator vatValidator) {



email = StringUtils.trim(email);

fullName = StringUtils.trim(fullName);
firstName = StringUtils.trim(firstName);
lastName = StringUtils.trim(lastName);

billingAddress = StringUtils.trim(billingAddress);

ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "email", ErrorsCode.STEP_2_EMPTY_EMAIL);
rejectIfOverLength(bindingResult, "email", ErrorsCode.STEP_2_MAX_LENGTH_EMAIL, email, 255);

if(event.mustUseFirstAndLastName()) {
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "firstName", ErrorsCode.STEP_2_EMPTY_FIRSTNAME);
rejectIfOverLength(bindingResult, "firstName", ErrorsCode.STEP_2_MAX_LENGTH_FIRSTNAME, fullName, 255);
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "lastName", ErrorsCode.STEP_2_EMPTY_LASTNAME);
rejectIfOverLength(bindingResult, "lastName", ErrorsCode.STEP_2_MAX_LENGTH_LASTNAME, fullName, 255);
} else {
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "fullName", ErrorsCode.STEP_2_EMPTY_FULLNAME);
rejectIfOverLength(bindingResult, "fullName", ErrorsCode.STEP_2_MAX_LENGTH_FULLNAME, fullName, 255);
}



if(invoiceRequested) {
/*if(companyVatChecked) {
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "billingAddressCompany", "error.emptyField");
rejectIfOverLength(bindingResult, "billingAddressCompany", "error.tooLong", billingAddressCompany, 256);
}*/

ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "billingAddressLine1", "error.emptyField");
rejectIfOverLength(bindingResult, "billingAddressLine1", "error.tooLong", billingAddressLine1, 256);

rejectIfOverLength(bindingResult, "billingAddressLine2", "error.tooLong", billingAddressLine2, 256);

ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "billingAddressZip", "error.emptyField");
rejectIfOverLength(bindingResult, "billingAddressZip", "error.tooLong", billingAddressZip, 51);

ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "billingAddressCity", "error.emptyField");
rejectIfOverLength(bindingResult, "billingAddressCity", "error.tooLong", billingAddressCity, 256);
}

if (email != null && !email.contains("@") && !bindingResult.hasFieldErrors("email")) {
bindingResult.rejectValue("email", ErrorsCode.STEP_2_INVALID_EMAIL);
}

if(!postponeAssignment) {
Optional<List<ValidationResult>> validationResults = Optional.ofNullable(tickets)
.filter(m -> !m.isEmpty())
.map(m -> m.entrySet().stream().map(e -> Validator.validateTicketAssignment(e.getValue(),
fieldConf, Optional.of(bindingResult), event, "tickets[" + e.getKey() + "]", vatValidator)))
.map(s -> s.collect(Collectors.toList()));

boolean success = validationResults
.filter(l -> l.stream().allMatch(ValidationResult::isSuccess))
.isPresent();
if(!success) {
String errorCode = validationResults.filter(this::containsVatValidationError).isPresent() ? STEP_2_INVALID_VAT : STEP_2_MISSING_ATTENDEE_DATA;
bindingResult.reject(errorCode);
}
}
}

private boolean containsVatValidationError(List<ValidationResult> l) {
return l.stream().anyMatch(v -> !v.isSuccess() && v.getErrorDescriptors().stream().anyMatch(ed -> ed.getCode().equals(STEP_2_INVALID_VAT)));
}

public Boolean shouldCancelReservation() {
return Optional.ofNullable(cancelReservation).orElse(false);
}

public static ContactAndTicketsForm fromExistingReservation(TicketReservation reservation, TicketReservationAdditionalInfo additionalInfo) {
ContactAndTicketsForm form = new ContactAndTicketsForm();
form.setFirstName(reservation.getFirstName());
form.setLastName(reservation.getLastName());
form.setBillingAddress(reservation.getBillingAddress());
form.setEmail(reservation.getEmail());
form.setFullName(reservation.getFullName());
form.setVatCountryCode(reservation.getVatCountryCode());
form.setVatNr(reservation.getVatNr());
form.setInvoiceRequested(reservation.isInvoiceRequested());
form.setCustomerReference(reservation.getCustomerReference());


form.setBillingAddressCompany(additionalInfo.getBillingAddressCompany());
form.setBillingAddressLine1(additionalInfo.getBillingAddressLine1());
form.setBillingAddressLine2(additionalInfo.getBillingAddressLine2());
form.setBillingAddressZip(additionalInfo.getBillingAddressZip());
form.setBillingAddressCity(additionalInfo.getBillingAddressCity());
return form;
}

public boolean getHasVatCountryCode() {
return !StringUtils.isEmpty(vatCountryCode);
}

public boolean isBackFromOverview() {
return Optional.ofNullable(backFromOverview).orElse(false);
}
}
Loading

0 comments on commit 25ff546

Please sign in to comment.