Skip to content

Commit

Permalink
#29 add raw promo codes support, still WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
syjer committed Jan 11, 2015
1 parent 9e50182 commit 8ea40d4
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 33 deletions.
37 changes: 30 additions & 7 deletions src/main/java/alfio/controller/EventController.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,27 @@ public ValidationResult savePromoCode(@PathVariable("eventName") String eventNam
Optional<SpecialPrice> specialCode = maybeSpecialCode.flatMap((trimmedCode) -> optionally(() -> specialPriceRepository.getByCode(trimmedCode)));
Optional<PromoCodeDiscount> promotionCodeDiscount = maybeSpecialCode.flatMap((trimmedCode) -> optionally(() -> promoCodeRepository.findPromoCodeInEvent(event.getId(), trimmedCode)));

if (maybeSpecialCode.isPresent() && (!specialCode.isPresent() || !optionally(() -> eventManager.getTicketCategoryById(specialCode.get().getTicketCategoryId(), event.getId())).isPresent())) {
if(specialCode.isPresent()) {
if (!optionally(() -> eventManager.getTicketCategoryById(specialCode.get().getTicketCategoryId(), event.getId())).isPresent()) {
return ValidationResult.failed(new ValidationResult.ValidationError("promoCode", ""));
}

if (specialCode.get().getStatus() != SpecialPrice.Status.FREE) {
return ValidationResult.failed(new ValidationResult.ValidationError("promoCode", ""));
}

} else if (promotionCodeDiscount.isPresent() && promotionCodeDiscount.get().isExpired(event.getZoneId())) {
return ValidationResult.failed(new ValidationResult.ValidationError("promoCode", ""));
}

if (specialCode.isPresent() && specialCode.get().getStatus() != SpecialPrice.Status.FREE) {
} else if(!specialCode.isPresent() && !promotionCodeDiscount.isPresent()) {
return ValidationResult.failed(new ValidationResult.ValidationError("promoCode", ""));
}

if(maybeSpecialCode.isPresent() && !model.asMap().containsKey("hasErrors")) {
SessionUtil.saveSpecialPriceCode(maybeSpecialCode.get(), request);
if(specialCode.isPresent()) {
SessionUtil.saveSpecialPriceCode(maybeSpecialCode.get(), request);
} else if (promotionCodeDiscount.isPresent()) {
SessionUtil.savePromotionCodeDiscount(maybeSpecialCode.get(), request);
}
return ValidationResult.success();
}
return ValidationResult.failed(new ValidationResult.ValidationError("promoCode", ""));
Expand All @@ -163,6 +174,9 @@ public String showEvent(@PathVariable("eventName") String eventName,

Optional<String> maybeSpecialCode = SessionUtil.retrieveSpecialPriceCode(request);
Optional<SpecialPrice> specialCode = maybeSpecialCode.flatMap((trimmedCode) -> optionally(() -> specialPriceRepository.getByCode(trimmedCode)));

Optional<PromoCodeDiscount> promoCodeDiscount = SessionUtil.retrievePromotionCodeDiscount(request)
.flatMap((code) -> optionally(() -> promoCodeRepository.findPromoCodeInEvent(event.get().getId(), code)));

Event ev = event.get();
final ZonedDateTime now = ZonedDateTime.now(ev.getZoneId());
Expand All @@ -177,14 +191,20 @@ public String showEvent(@PathVariable("eventName") String eventName,

LocationDescriptor ld = LocationDescriptor.fromGeoData(ev.getLatLong(), TimeZone.getTimeZone(ev.getTimeZone()),
configurationManager.getStringConfigValue(MAPS_CLIENT_API_KEY));

final boolean hasAccessPromotions = ticketCategoryRepository.countAccessRestrictedRepositoryByEventId(ev.getId()).intValue() > 0 ||
promoCodeRepository.countByEventId(ev.getId()).intValue() > 0;

final EventDescriptor eventDescriptor = new EventDescriptor(ev);
model.addAttribute("event", eventDescriptor)//
.addAttribute("organizer", organizationRepository.getById(ev.getOrganizationId()))
.addAttribute("ticketCategories", t)//
.addAttribute("hasAccessRestrictedCategory", ticketCategoryRepository.countAccessRestrictedRepositoryByEventId(ev.getId()).intValue() > 0)
.addAttribute("hasAccessPromotions", hasAccessPromotions)
.addAttribute("promoCode", specialCode.map(SpecialPrice::getCode).orElse(null))
.addAttribute("locationDescriptor", ld)
.addAttribute("pageTitle", "show-event.header.title")
.addAttribute("hasPromoCodeDiscount", promoCodeDiscount.isPresent())
.addAttribute("promoCodeDiscount", promoCodeDiscount.orElse(null))
.addAttribute("forwardButtonDisabled", t.stream().noneMatch(SaleableTicketCategory::getSaleable));
model.asMap().putIfAbsent("hasErrors", false);//
return "/event/show-event";
Expand Down Expand Up @@ -218,7 +238,10 @@ public String reserveTicket(@PathVariable("eventName") String eventName,

try {
String reservationId = ticketReservationManager.createTicketReservation(event.get().getId(),
selected.get(), expiration, SessionUtil.retrieveSpecialPriceSessionId(request.getRequest()), locale);
selected.get(), expiration,
SessionUtil.retrieveSpecialPriceSessionId(request.getRequest()),
SessionUtil.retrievePromotionCodeDiscount(request.getRequest()),
locale);
return "redirect:/event/" + eventName + "/reservation/" + reservationId + "/book";
} catch (TicketReservationManager.NotEnoughTicketsException nete) {
bindingResult.reject(ErrorsCode.STEP_1_NOT_ENOUGH_TICKETS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import alfio.manager.EventManager;
import alfio.model.Event;
import alfio.model.PromoCodeDiscount;
import alfio.model.PromoCodeDiscount.DiscountType;
import alfio.model.modification.PromoCodeDiscountModification;
import alfio.model.modification.PromoCodeDiscountWithFormattedTime;
import alfio.repository.EventRepository;
Expand All @@ -56,8 +57,10 @@ public void addPromoCode(@PathVariable("eventId") int eventId, @RequestBody Prom
Event event = eventRepository.findById(eventId);
ZoneId zoneId = TimeZone.getTimeZone(event.getTimeZone()).toZoneId();

int discount = promoCode.getDiscountType() == DiscountType.FIXED_AMOUNT ? promoCode.getDiscountInCents() : promoCode.getDiscountAsPercent();

eventManager.addPromoCode(promoCode.getPromoCode(), eventId, promoCode.getStart().toZonedDateTime(zoneId),
promoCode.getEnd().toZonedDateTime(zoneId), promoCode.getDiscountAmount(), promoCode.getDiscountType());
promoCode.getEnd().toZonedDateTime(zoneId), discount, promoCode.getDiscountType());
}

@RequestMapping(value = "/events/{eventId}/promo-code", method = GET)
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/alfio/controller/support/SessionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;

import java.util.Optional;
import java.util.UUID;

public final class SessionUtil {

private static final String SPECIAL_PRICE_CODE_SESSION_ID = "SPECIAL_PRICE_CODE_SESSION_ID";
private static final String SPECIAL_PRICE_CODE = "SPECIAL_PRICE_CODE";

private static final String PROMOTIONAL_CODE_DISCOUNT = "PROMOTIONAL_CODE_DISCOUNT";

private SessionUtil() {}

Expand All @@ -37,10 +40,18 @@ public static void saveSpecialPriceCode(String specialPriceCode, HttpServletRequ
request.getSession().setAttribute(SPECIAL_PRICE_CODE, specialPriceCode);
}
}

public static void savePromotionCodeDiscount(String promoCodeDiscount, HttpServletRequest request) {
request.getSession().setAttribute(PROMOTIONAL_CODE_DISCOUNT, promoCodeDiscount);
}

public static Optional<String> retrieveSpecialPriceCode(HttpServletRequest request) {
return Optional.ofNullable((String)request.getSession().getAttribute(SPECIAL_PRICE_CODE));
}

public static Optional<String> retrievePromotionCodeDiscount(HttpServletRequest request) {
return Optional.ofNullable((String) request.getSession().getAttribute(PROMOTIONAL_CODE_DISCOUNT));
}

public static Optional<String> retrieveSpecialPriceSessionId(HttpServletRequest request) {
return Optional.ofNullable((String)request.getSession().getAttribute(SPECIAL_PRICE_CODE_SESSION_ID));
Expand All @@ -49,10 +60,10 @@ public static Optional<String> retrieveSpecialPriceSessionId(HttpServletRequest
public static void removeSpecialPriceData(HttpServletRequest request) {
request.getSession().removeAttribute(SPECIAL_PRICE_CODE_SESSION_ID);
request.getSession().removeAttribute(SPECIAL_PRICE_CODE);
request.getSession().removeAttribute(PROMOTIONAL_CODE_DISCOUNT);
}

public static void addToFlash(BindingResult bindingResult, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("error", bindingResult).addFlashAttribute("hasErrors", true);
}

}
78 changes: 72 additions & 6 deletions src/main/java/alfio/manager/TicketReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import alfio.manager.support.*;
import alfio.manager.system.ConfigurationManager;
import alfio.model.*;
import alfio.model.PromoCodeDiscount.DiscountType;
import alfio.model.SpecialPrice.Status;
import alfio.model.Ticket.TicketStatus;
import alfio.model.TicketReservation.TicketReservationStatus;
Expand All @@ -31,9 +32,12 @@
import alfio.repository.user.OrganizationRepository;
import alfio.util.MonetaryUtil;
import alfio.util.TemplateManager;

import com.lowagie.text.DocumentException;

import lombok.Data;
import lombok.extern.log4j.Log4j2;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
Expand Down Expand Up @@ -78,6 +82,7 @@ public class TicketReservationManager {
private final TicketCategoryRepository ticketCategoryRepository;
private final ConfigurationManager configurationManager;
private final PaymentManager paymentManager;
private final PromoCodeDiscountRepository promoCodeDiscountRepository;
private final SpecialPriceRepository specialPriceRepository;
private final TransactionRepository transactionRepository;
private final NotificationManager notificationManager;
Expand All @@ -100,6 +105,8 @@ public static class InvalidSpecialPriceTokenException extends RuntimeException {
public static class TotalPrice {
private final int priceWithVAT;
private final int VAT;
private final int discount;
private final int numberOfDiscountApplied;
}

@Autowired
Expand All @@ -110,6 +117,7 @@ public TicketReservationManager(EventRepository eventRepository,
TicketCategoryRepository ticketCategoryRepository,
ConfigurationManager configurationManager,
PaymentManager paymentManager,
PromoCodeDiscountRepository promoCodeDiscountRepository,
SpecialPriceRepository specialPriceRepository,
TransactionRepository transactionRepository,
NotificationManager notificationManager,
Expand All @@ -123,6 +131,7 @@ public TicketReservationManager(EventRepository eventRepository,
this.ticketCategoryRepository = ticketCategoryRepository;
this.configurationManager = configurationManager;
this.paymentManager = paymentManager;
this.promoCodeDiscountRepository = promoCodeDiscountRepository;
this.specialPriceRepository = specialPriceRepository;
this.transactionRepository = transactionRepository;
this.notificationManager = notificationManager;
Expand All @@ -140,9 +149,15 @@ public TicketReservationManager(EventRepository eventRepository,
* @param reservationExpiration
* @return
*/
public String createTicketReservation(int eventId, List<TicketReservationWithOptionalCodeModification> list, Date reservationExpiration, Optional<String> specialPriceSessionId, Locale locale) throws NotEnoughTicketsException, MissingSpecialPriceTokenException, InvalidSpecialPriceTokenException {
public String createTicketReservation(int eventId, List<TicketReservationWithOptionalCodeModification> list, Date reservationExpiration,
Optional<String> specialPriceSessionId,
Optional<String> promotionCodeDiscount,
Locale locale) throws NotEnoughTicketsException, MissingSpecialPriceTokenException, InvalidSpecialPriceTokenException {
String transactionId = UUID.randomUUID().toString();
ticketReservationRepository.createNewReservation(transactionId, reservationExpiration);

Optional<PromoCodeDiscount> discount = promotionCodeDiscount.flatMap((promoCodeDiscount) -> optionally(() -> promoCodeDiscountRepository.findPromoCodeInEvent(eventId, promoCodeDiscount)));

ticketReservationRepository.createNewReservation(transactionId, reservationExpiration, discount.map(PromoCodeDiscount::getId).orElse(null));
list.forEach(t -> reserveTicketsForCategory(eventId, specialPriceSessionId, transactionId, t, locale));
return transactionId;
}
Expand Down Expand Up @@ -398,8 +413,8 @@ public List<TicketWithStatistic> loadModifiedTickets(int eventId, int categoryId
.collect(Collectors.toList());
}

private int totalFrom(List<Ticket> tickets) {
return tickets.stream().mapToInt(Ticket::getPaidPriceInCents).sum();
private int totalFrom(List<Ticket> tickets, Event event) {
return tickets.stream().mapToInt(Ticket::getPaidPriceInCents).sum();
}

/**
Expand All @@ -409,12 +424,47 @@ private int totalFrom(List<Ticket> tickets) {
* @return
*/
public TotalPrice totalReservationCostWithVAT(String reservationId) {
TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId);

Optional<PromoCodeDiscount> promoCodeDiscount = Optional.ofNullable(reservation.getPromoCodeDiscountId()).map(promoCodeDiscountRepository::findById);

Event event = eventRepository.findByReservationId(reservationId);
List<Ticket> tickets = ticketRepository.findTicketsInReservation(reservationId);
int net = totalFrom(tickets);
int net = totalFrom(tickets, event);
int vat = totalVat(tickets, event.getVat());
return new TotalPrice(net + vat, vat);
final int amountToBeDiscounted;
final int numberOfDiscountApplied;

//take in account the discount code
if (promoCodeDiscount.isPresent()) {
PromoCodeDiscount discount = promoCodeDiscount.get();

if(discount.getDiscountType() == DiscountType.FIXED_AMOUNT) {
//we apply the fixed discount for each paid ticket
numberOfDiscountApplied = ((int) tickets.stream().filter(t -> t.getPaidPriceInCents() > 0).count());
amountToBeDiscounted = numberOfDiscountApplied * discount.getDiscountAmount();
} else {
amountToBeDiscounted = MonetaryUtil.calcPercentage(event.isVatIncluded() ? net + vat : net, new BigDecimal(discount.getDiscountAmount()));
numberOfDiscountApplied = 1;
}

// recalc the net and vat
if(event.isVatIncluded()) {
int finalPrice = net + vat - amountToBeDiscounted;
net = MonetaryUtil.removeVAT(finalPrice, event.getVat());
vat = finalPrice - net;
} else {
net = net - amountToBeDiscounted;
vat = MonetaryUtil.calcVat(net, event.getVat());
}
} else {
amountToBeDiscounted = 0;
numberOfDiscountApplied = 0;
}

return new TotalPrice(net + vat, vat, -amountToBeDiscounted, numberOfDiscountApplied);
}


private int totalVat(List<Ticket> tickets, BigDecimal vat) {
return tickets.stream().mapToInt(Ticket::getPaidPriceInCents).map(p -> MonetaryUtil.calcVat(p, vat)).sum();
Expand All @@ -423,8 +473,24 @@ private int totalVat(List<Ticket> tickets, BigDecimal vat) {
public OrderSummary orderSummaryForReservationId(String reservationId, Event event) {
TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId);
TotalPrice reservationCost = totalReservationCostWithVAT(reservationId);

List<SummaryRow> summary = extractSummary(reservationId, event);

//
boolean free = reservationCost.getPriceWithVAT() == 0;

Optional<PromoCodeDiscount> promoCodeDiscount = Optional.ofNullable(reservation.getPromoCodeDiscountId()).map(promoCodeDiscountRepository::findById);

//add the discount summary row
if(!free && promoCodeDiscount.isPresent()) {
PromoCodeDiscount promo = promoCodeDiscount.get();
summary.add(new SummaryRow(promo.getPromoCode(),
"-" + (promo.getDiscountType() == DiscountType.FIXED_AMOUNT ? formatCents(promo.getDiscountAmount()) : (promo.getDiscountAmount()+"%")),
reservationCost.numberOfDiscountApplied,
formatCents(reservationCost.discount), reservationCost.discount));
}


return new OrderSummary(reservationCost,
summary, free,
formatCents(reservationCost.getPriceWithVAT()), formatCents(reservationCost.getVAT()),
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/alfio/model/PromoCodeDiscount.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
*/
package alfio.model;

import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.ZonedDateTime;

import alfio.datamapper.ConstructorAnnotationRowMapper.Column;
import alfio.util.MonetaryUtil;
import lombok.Getter;

@Getter
Expand Down Expand Up @@ -51,4 +54,13 @@ public PromoCodeDiscount(@Column("id")int id,
this.discountAmount = discountAmount;
this.discountType = discountType;
}

public boolean isExpired(ZoneId eventZoneId) {
return ZonedDateTime.now(eventZoneId).isAfter(utcEnd.withZoneSameInstant(eventZoneId));
}

public BigDecimal getFormattedDiscountAmount() {
//TODO: apply this conversion only for some currency. Not all are cent based.
return MonetaryUtil.centsToUnit(discountAmount);
}
}
5 changes: 4 additions & 1 deletion src/main/java/alfio/model/TicketReservation.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public enum TicketReservationStatus {
private final ZonedDateTime confirmationTimestamp;
private final PaymentProxy paymentMethod;
private final Boolean reminderSent;
private final Integer promoCodeDiscountId;

public TicketReservation(@Column("id") String id,
@Column("validity") Date validity,
Expand All @@ -49,7 +50,8 @@ public TicketReservation(@Column("id") String id,
@Column("billing_address") String billingAddress,
@Column("confirmation_ts") ZonedDateTime confirmationTimestamp,
@Column("payment_method") PaymentProxy paymentMethod,
@Column("reminder_sent") Boolean reminderSent) {
@Column("reminder_sent") Boolean reminderSent,
@Column("promo_code_id_fk") Integer promoCodeDiscountId) {
this.id = id;
this.validity = validity;
this.status = status;
Expand All @@ -59,6 +61,7 @@ public TicketReservation(@Column("id") String id,
this.confirmationTimestamp = confirmationTimestamp;
this.paymentMethod = paymentMethod;
this.reminderSent = reminderSent;
this.promoCodeDiscountId = promoCodeDiscountId;
}

public boolean isStuck() {
Expand Down
Loading

0 comments on commit 8ea40d4

Please sign in to comment.