From 6ea42cd3f53a135ba7991d903c40fad92531f0c2 Mon Sep 17 00:00:00 2001 From: Celestino Bellone <3385346+cbellone@users.noreply.github.com> Date: Tue, 22 May 2018 20:08:56 +0200 Subject: [PATCH] #449 invoice enhancements (#450) * #449 - add total net price * #449 - add category info to tickets in invoice add creation timestamp on reservation * #449 - mysql 5.5 compatibility * #449 - better backward compatibility * #449 - insert creation date --- .../manager/AdminReservationManager.java | 2 +- .../manager/TicketReservationManager.java | 12 ++++-- src/main/java/alfio/model/FullTicketInfo.java | 3 +- src/main/java/alfio/model/OrderSummary.java | 8 ++++ src/main/java/alfio/model/TicketCSVInfo.java | 5 ++- .../java/alfio/model/TicketReservation.java | 5 ++- .../java/alfio/model/TicketWithCategory.java | 37 +++++++++++++++++++ .../TicketWithReservationAndTransaction.java | 3 +- .../repository/TicketCategoryRepository.java | 4 ++ .../alfio/repository/TicketRepository.java | 4 +- .../TicketReservationRepository.java | 6 ++- .../repository/TicketSearchRepository.java | 2 +- .../java/alfio/util/TemplateResource.java | 18 ++++++--- .../R__RESERVATION_AND_TICKET_AND_TX.sql | 1 + .../V22_1.15.2__ADD_CREATION_TIMESTAMP.sql | 20 ++++++++++ .../R__RESERVATION_AND_TICKET_AND_TX.sql | 1 + .../V22_1.15.2__ADD_CREATION_TIMESTAMP.sql | 20 ++++++++++ .../R__RESERVATION_AND_TICKET_AND_TX.sql | 1 + .../V22_1.15.2__ADD_CREATION_TIMESTAMP.sql | 20 ++++++++++ .../manager/EventManagerIntegrationTest.java | 2 +- .../WaitingQueueProcessorIntegrationTest.java | 3 +- 21 files changed, 156 insertions(+), 21 deletions(-) create mode 100644 src/main/java/alfio/model/TicketWithCategory.java create mode 100644 src/main/resources/alfio/db/HSQLDB/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql create mode 100644 src/main/resources/alfio/db/MYSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql create mode 100644 src/main/resources/alfio/db/PGSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql diff --git a/src/main/java/alfio/manager/AdminReservationManager.java b/src/main/java/alfio/manager/AdminReservationManager.java index 6a292c0934..5bbc71d03d 100644 --- a/src/main/java/alfio/manager/AdminReservationManager.java +++ b/src/main/java/alfio/manager/AdminReservationManager.java @@ -299,7 +299,7 @@ private Result>> createReservation(Result discount = promotionCodeDiscount.flatMap((promoCodeDiscount) -> promoCodeDiscountRepository.findPromoCodeInEventOrOrganization(event.getId(), promoCodeDiscount)); - ticketReservationRepository.createNewReservation(reservationId, reservationExpiration, discount.map(PromoCodeDiscount::getId).orElse(null), locale.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded()); + ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(event.getZoneId()), reservationExpiration, discount.map(PromoCodeDiscount::getId).orElse(null), locale.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded()); list.forEach(t -> reserveTicketsForCategory(event, specialPriceSessionId, reservationId, t, locale, forWaitingQueue, discount.orElse(null))); int ticketCount = list @@ -517,13 +517,19 @@ public void deleteOfflinePayment(Event event, String reservationId, boolean expi @Transactional(readOnly = true) public Map prepareModelForReservationEmail(Event event, TicketReservation reservation, Optional vat, OrderSummary summary) { Organization organization = organizationRepository.getById(event.getOrganizationId()); - List tickets = findTicketsInReservation(reservation.getId()); String reservationUrl = reservationUrl(reservation.getId()); String reservationShortID = getShortReservationID(event, reservation.getId()); Optional invoiceAddress = configurationManager.getStringConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.INVOICE_ADDRESS)); Optional bankAccountNr = configurationManager.getStringConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.BANK_ACCOUNT_NR)); Optional bankAccountOwner = configurationManager.getStringConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.BANK_ACCOUNT_OWNER)); - return TemplateResource.prepareModelForConfirmationEmail(organization, event, reservation, vat, tickets, summary, reservationUrl, reservationShortID, invoiceAddress, bankAccountNr, bankAccountOwner); + Map> ticketsByCategory = ticketRepository.findTicketsInReservation(reservation.getId()) + .stream() + .collect(groupingBy(Ticket::getCategoryId)); + List ticketsWithCategory = ticketCategoryRepository.findByIds(ticketsByCategory.keySet()) + .stream() + .flatMap(tc -> ticketsByCategory.get(tc.getId()).stream().map(t -> new TicketWithCategory(t, tc))) + .collect(Collectors.toList()); + return TemplateResource.prepareModelForConfirmationEmail(organization, event, reservation, vat, ticketsWithCategory, summary, reservationUrl, reservationShortID, invoiceAddress, bankAccountNr, bankAccountOwner); } @Transactional(readOnly = true) diff --git a/src/main/java/alfio/model/FullTicketInfo.java b/src/main/java/alfio/model/FullTicketInfo.java index e800179304..998d9f169d 100644 --- a/src/main/java/alfio/model/FullTicketInfo.java +++ b/src/main/java/alfio/model/FullTicketInfo.java @@ -79,6 +79,7 @@ public FullTicketInfo(@Column("t_id") int id, @Column("tr_invoice_requested") boolean invoiceRequested, @Column("tr_used_vat_percent") BigDecimal usedVatPercent, @Column("tr_vat_included") Boolean vatIncluded, + @Column("tr_creation_ts") ZonedDateTime reservationCreationTimestamp, // // @Column("tc_id") int tcId, @@ -102,7 +103,7 @@ public FullTicketInfo(@Column("t_id") int id, lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference); this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, trConfirmationTimestamp, trLatestReminder, trPaymentMethod, trReminderSent, trPromoCodeDiscountId, trAutomatic, resUserLanguage, - directAssignment, invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded); + directAssignment, invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded, reservationCreationTimestamp); this.ticketCategory = new TicketCategory(tcId, tcUtcInception, tcUtcExpiration, tcMaxTickets, tcName, tcAccessRestricted, tcStatus, tcEventId, bounded, tcSrcPriceCts, code, validCheckInFrom, validCheckInTo, ticketValidityStart, ticketValidityEnd); diff --git a/src/main/java/alfio/model/OrderSummary.java b/src/main/java/alfio/model/OrderSummary.java index e48f7598b3..fc143ebdb6 100644 --- a/src/main/java/alfio/model/OrderSummary.java +++ b/src/main/java/alfio/model/OrderSummary.java @@ -16,6 +16,7 @@ */ package alfio.model; +import alfio.util.MonetaryUtil; import lombok.Data; import java.util.List; @@ -69,4 +70,11 @@ public boolean isVatExempt() { public String getRefundedAmount() { return refundedAmount; } + + public String getTotalNetPrice() { + if(free) { + return null; + } + return MonetaryUtil.formatCents(originalTotalPrice.getPriceWithVAT() - originalTotalPrice.getVAT()); + } } diff --git a/src/main/java/alfio/model/TicketCSVInfo.java b/src/main/java/alfio/model/TicketCSVInfo.java index 1dac0d1052..ef93e8a838 100644 --- a/src/main/java/alfio/model/TicketCSVInfo.java +++ b/src/main/java/alfio/model/TicketCSVInfo.java @@ -75,12 +75,13 @@ public TicketCSVInfo(@Column("t_id") int id, @Column("tr_vat_country") String vatCountry, @Column("tr_invoice_requested") boolean invoiceRequested, @Column("tr_used_vat_percent") BigDecimal usedVatPercent, - @Column("tr_vat_included") Boolean vatIncluded) { + @Column("tr_vat_included") Boolean vatIncluded, + @Column("tr_creation_ts") ZonedDateTime reservationCreationTimestamp) { this.ticket = new Ticket(id, uuid, creation, categoryId, status, eventId, ticketsReservationId, fullName, firstName, lastName, email, lockedAssignment, userLanguage, ticketSrcPriceCts, ticketFinalPriceCts, ticketVatCts, ticketDiscountCts, extReference); this.ticketReservation = new TicketReservation(trId, trValidity, trStatus, trFullName, trFirstName, trLastName, trEmail, trBillingAddress, trConfirmationTimestamp, trLatestReminder, trPaymentMethod, trReminderSent, trPromoCodeDiscountId, trAutomatic, resUserLanguage, directAssignment, - invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded); + invoiceNumber, invoiceModel, reservationVatStatus, vatNr, vatCountry, invoiceRequested, usedVatPercent, vatIncluded, reservationCreationTimestamp); } } diff --git a/src/main/java/alfio/model/TicketReservation.java b/src/main/java/alfio/model/TicketReservation.java index 301234850f..a1470ead8f 100644 --- a/src/main/java/alfio/model/TicketReservation.java +++ b/src/main/java/alfio/model/TicketReservation.java @@ -60,6 +60,7 @@ public enum TicketReservationStatus { private final boolean invoiceRequested; private final BigDecimal usedVatPercent; private final Boolean vatIncluded; + private final ZonedDateTime creationTimestamp; public TicketReservation(@Column("id") String id, @Column("validity") Date validity, @@ -84,7 +85,8 @@ public TicketReservation(@Column("id") String id, @Column("vat_country") String vatCountryCode, @Column("invoice_requested") boolean invoiceRequested, @Column("used_vat_percent") BigDecimal usedVatPercent, - @Column("vat_included") Boolean vatIncluded) { + @Column("vat_included") Boolean vatIncluded, + @Column("creation_ts") ZonedDateTime creationTimestamp) { this.id = id; this.validity = validity; this.status = status; @@ -109,6 +111,7 @@ public TicketReservation(@Column("id") String id, this.invoiceRequested = invoiceRequested; this.usedVatPercent = usedVatPercent; this.vatIncluded = vatIncluded; + this.creationTimestamp = creationTimestamp; } public boolean isStuck() { diff --git a/src/main/java/alfio/model/TicketWithCategory.java b/src/main/java/alfio/model/TicketWithCategory.java new file mode 100644 index 0000000000..6275705032 --- /dev/null +++ b/src/main/java/alfio/model/TicketWithCategory.java @@ -0,0 +1,37 @@ +/** + * 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.model; + +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; + +@RequiredArgsConstructor +public class TicketWithCategory { + + @Delegate + private final Ticket ticket; + private final TicketCategory category; + + public String getCategoryName() { + return category.getName(); + } + + public TicketCategory getCategory() { + return category; + } + +} diff --git a/src/main/java/alfio/model/TicketWithReservationAndTransaction.java b/src/main/java/alfio/model/TicketWithReservationAndTransaction.java index d60655a655..348f00ac7a 100644 --- a/src/main/java/alfio/model/TicketWithReservationAndTransaction.java +++ b/src/main/java/alfio/model/TicketWithReservationAndTransaction.java @@ -78,6 +78,7 @@ public TicketWithReservationAndTransaction(@Column("t_id") Integer id, @Column("tr_invoice_requested") boolean invoiceRequested, @Column("tr_used_vat_percent") BigDecimal usedVadPercent, @Column("tr_vat_included") Boolean vatIncluded, + @Column("tr_creation_ts") ZonedDateTime reservationCreationTimestamp, // @Column("bt_id") Integer btId, @Column("bt_gtw_tx_id") String transactionId, @@ -102,7 +103,7 @@ public TicketWithReservationAndTransaction(@Column("t_id") Integer id, billingAddress, confirmationTimestamp, latestReminder, paymentMethod, reminderSent, promoCodeDiscountId, automatic, trUserLanguage, directAssignmentRequested, invoiceNumber, invoiceModel, vatStatus, vatNr, vatCountryCode, invoiceRequested, - usedVadPercent, vatIncluded); + usedVadPercent, vatIncluded, reservationCreationTimestamp); if(btId != null) { this.transaction = Optional.of(new Transaction(btId, transactionId, paymentId, reservationId, diff --git a/src/main/java/alfio/repository/TicketCategoryRepository.java b/src/main/java/alfio/repository/TicketCategoryRepository.java index 406bec16ea..6fe081e2e7 100644 --- a/src/main/java/alfio/repository/TicketCategoryRepository.java +++ b/src/main/java/alfio/repository/TicketCategoryRepository.java @@ -21,6 +21,7 @@ import ch.digitalfondue.npjt.*; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -56,6 +57,9 @@ AffectedRowCountAndKey insert(@Bind("inception") ZonedDateTime inceptio @Query("select * from ticket_category where id = :id") TicketCategory getById(@Bind("id") int id); + @Query("select * from ticket_category where id in(:ids)") + List findByIds(@Bind("ids") Collection ids); + @Query("select * from ticket_category where event_id = :eventId and category_code = :code and tc_status = 'ACTIVE'") Optional findCodeInEvent(@Bind("eventId") int eventId, @Bind("code") String code); diff --git a/src/main/java/alfio/repository/TicketRepository.java b/src/main/java/alfio/repository/TicketRepository.java index 23f66c6047..2b6a2c2507 100644 --- a/src/main/java/alfio/repository/TicketRepository.java +++ b/src/main/java/alfio/repository/TicketRepository.java @@ -181,7 +181,7 @@ public interface TicketRepository { " tr.id tr_id, tr.validity tr_validity, tr.status tr_status, tr.full_name tr_full_name, tr.first_name tr_first_name, tr.last_name tr_last_name, tr.email_address tr_email_address, tr.billing_address tr_billing_address," + " tr.confirmation_ts tr_confirmation_ts, tr.latest_reminder_ts tr_latest_reminder_ts, tr.payment_method tr_payment_method, " + " tr.offline_payment_reminder_sent tr_offline_payment_reminder_sent, tr.promo_code_id_fk tr_promo_code_id_fk, tr.automatic tr_automatic, tr.user_language tr_user_language, tr.direct_assignment tr_direct_assignment, tr.invoice_number tr_invoice_number, tr.invoice_model tr_invoice_model, " + - " tr.vat_status tr_vat_status, tr.vat_nr tr_vat_nr, tr.vat_country tr_vat_country, tr.invoice_requested tr_invoice_requested, tr.used_vat_percent tr_used_vat_percent, tr.vat_included tr_vat_included, " + + " tr.vat_status tr_vat_status, tr.vat_nr tr_vat_nr, tr.vat_country tr_vat_country, tr.invoice_requested tr_invoice_requested, tr.used_vat_percent tr_used_vat_percent, tr.vat_included tr_vat_included, tr.creation_ts tr_creation_ts, " + " tc.id tc_id, tc.inception tc_inception, tc.expiration tc_expiration, tc.max_tickets tc_max_tickets, tc.name tc_name, tc.src_price_cts tc_src_price_cts, tc.access_restricted tc_access_restricted, tc.tc_status tc_tc_status, tc.event_id tc_event_id, tc.bounded tc_bounded, tc.category_code tc_category_code, " + " tc.valid_checkin_from tc_valid_checkin_from, tc.valid_checkin_to tc_valid_checkin_to, tc.ticket_validity_start tc_ticket_validity_start, tc.ticket_validity_end tc_ticket_validity_end" + " from ticket t " + @@ -211,7 +211,7 @@ public interface TicketRepository { " tr.id tr_id, tr.validity tr_validity, tr.status tr_status, tr.full_name tr_full_name, tr.first_name tr_first_name, tr.last_name tr_last_name, tr.email_address tr_email_address, tr.billing_address tr_billing_address," + " tr.confirmation_ts tr_confirmation_ts, tr.latest_reminder_ts tr_latest_reminder_ts, tr.payment_method tr_payment_method, tr.offline_payment_reminder_sent tr_offline_payment_reminder_sent, tr.promo_code_id_fk tr_promo_code_id_fk, tr.automatic tr_automatic, tr.user_language tr_user_language, tr.direct_assignment tr_direct_assignment, " + " tr.vat_status tr_vat_status, tr.vat_nr tr_vat_nr, tr.vat_country tr_vat_country, tr.invoice_requested tr_invoice_requested, tr.used_vat_percent tr_used_vat_percent, tr.vat_included tr_vat_included, " + - " tr.invoice_number tr_invoice_number, tr.invoice_model tr_invoice_model from ticket t, tickets_reservation tr where t.event_id = :eventId and t.status in(" + CONFIRMED + ") and t.tickets_reservation_id = tr.id order by tr.confirmation_ts") + " tr.invoice_number tr_invoice_number, tr.invoice_model tr_invoice_model, tr.creation_ts tr_creation_ts from ticket t, tickets_reservation tr where t.event_id = :eventId and t.status in(" + CONFIRMED + ") and t.tickets_reservation_id = tr.id order by tr.confirmation_ts") List findAllConfirmedForCSV(@Bind("eventId") int eventId); @Query("select a.*, b.confirmation_ts from ticket a, tickets_reservation b where a.event_id = :eventId and a.status in(" + CONFIRMED + ") and a.tickets_reservation_id = b.id order by b.confirmation_ts") diff --git a/src/main/java/alfio/repository/TicketReservationRepository.java b/src/main/java/alfio/repository/TicketReservationRepository.java index 86f8cdd1a3..f534f356b1 100644 --- a/src/main/java/alfio/repository/TicketReservationRepository.java +++ b/src/main/java/alfio/repository/TicketReservationRepository.java @@ -29,8 +29,10 @@ @QueryRepository public interface TicketReservationRepository { - @Query("insert into tickets_reservation(id, validity, promo_code_id_fk, status, user_language, event_id_fk, used_vat_percent, vat_included) values (:id, :validity, :promotionCodeDiscountId, 'PENDING', :userLanguage, :eventId, :eventVat, :vatIncluded)") - int createNewReservation(@Bind("id") String id, @Bind("validity") Date validity, + @Query("insert into tickets_reservation(id, creation_ts, validity, promo_code_id_fk, status, user_language, event_id_fk, used_vat_percent, vat_included) values (:id, :creationTimestamp, :validity, :promotionCodeDiscountId, 'PENDING', :userLanguage, :eventId, :eventVat, :vatIncluded)") + int createNewReservation(@Bind("id") String id, + @Bind("creationTimestamp") ZonedDateTime creationTimestamp, + @Bind("validity") Date validity, @Bind("promotionCodeDiscountId") Integer promotionCodeDiscountId, @Bind("userLanguage") String userLanguage, @Bind("eventId") int eventId, @Bind("eventVat") BigDecimal eventVat, diff --git a/src/main/java/alfio/repository/TicketSearchRepository.java b/src/main/java/alfio/repository/TicketSearchRepository.java index 7b42381ecc..99b14d4e48 100644 --- a/src/main/java/alfio/repository/TicketSearchRepository.java +++ b/src/main/java/alfio/repository/TicketSearchRepository.java @@ -37,7 +37,7 @@ public interface TicketSearchRepository { "tr_offline_payment_reminder_sent offline_payment_reminder_sent, tr_promo_code_id_fk promo_code_id_fk, tr_automatic automatic," + "tr_user_language user_language, tr_direct_assignment direct_assignment, tr_invoice_number invoice_number, tr_invoice_model invoice_model," + "tr_vat_status vat_status, tr_vat_nr vat_nr, tr_vat_country vat_country, tr_invoice_requested invoice_requested, tr_used_vat_percent used_vat_percent," + - "tr_vat_included vat_included"; + "tr_vat_included vat_included, tr_creation_ts creation_ts"; @Query("select * from (" + FIND_ALL_MODIFIED_TICKETS_WITH_RESERVATION_AND_TRANSACTION + " limit :pageSize offset :page) as d_tbl order by tr_confirmation_ts asc, tr_id, t_uuid") List findAllModifiedTicketsWithReservationAndTransaction(@Bind("eventId") int eventId, diff --git a/src/main/java/alfio/util/TemplateResource.java b/src/main/java/alfio/util/TemplateResource.java index 61ac8e40bc..0f68090add 100644 --- a/src/main/java/alfio/util/TemplateResource.java +++ b/src/main/java/alfio/util/TemplateResource.java @@ -23,6 +23,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.Delegate; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; @@ -205,6 +206,11 @@ private static Ticket sampleTicket() { return sampleTicket("Firstname", "Lastname", "email@email.tld"); } + private static TicketCategory sampleCategory() { + return new TicketCategory(0, ZonedDateTime.now().minusDays(1), ZonedDateTime.now().plusDays(1), 100, "test category", false, TicketCategory.Status.ACTIVE, + 0, true, 100, null, null, null, null, null); + } + private static Ticket sampleTicket(String firstName, String lastName, String email) { return new Ticket(0, "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", ZonedDateTime.now(), 0, "ACQUIRED", 0, "597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", firstName + " " + lastName, firstName, lastName, email, false, "en", @@ -214,13 +220,15 @@ private static Ticket sampleTicket(String firstName, String lastName, String ema private static TicketReservation sampleTicketReservation() { return new TicketReservation("597e7e7b-c514-4dcb-be8c-46cf7fe2c36e", new Date(), TicketReservation.TicketReservationStatus.COMPLETE, "Firstname Lastname", "FirstName", "Lastname", "email@email.tld", "billing address", ZonedDateTime.now(), ZonedDateTime.now(), - PaymentProxy.STRIPE, true, null, false, "en", false, null, null, null, "123456", "CH", false, new BigDecimal("8.00"), true); + PaymentProxy.STRIPE, true, null, false, "en", false, null, null, null, "123456", + "CH", false, new BigDecimal("8.00"), true, + ZonedDateTime.now().minusMinutes(1)); } private static Map prepareSampleDataForConfirmationEmail(Organization organization, Event event) { TicketReservation reservation = sampleTicketReservation(); Optional vat = Optional.of("VAT-NR"); - List tickets = Collections.singletonList(sampleTicket()); + List tickets = Collections.singletonList(new TicketWithCategory(sampleTicket(), sampleCategory())); OrderSummary orderSummary = new OrderSummary(new TotalPrice(1000, 80, 0, 0), Collections.singletonList(new SummaryRow("Ticket", "10.00", "9.20", 1, "9.20", "9.20", 1000, SummaryRow.SummaryType.TICKET)), false, "10.00", "0.80", false, false, "8", PriceContainer.VatStatus.INCLUDED, "1.00"); String reservationUrl = "http://your-domain.tld/reservation-url/"; @@ -240,7 +248,7 @@ public static Map prepareModelForConfirmationEmail(Organization Event event, TicketReservation reservation, Optional vat, - List tickets, + List tickets, OrderSummary orderSummary, String reservationUrl, String reservationShortID, @@ -260,8 +268,8 @@ public static Map prepareModelForConfirmationEmail(Organization model.put("hasRefund", StringUtils.isNotEmpty(orderSummary.getRefundedAmount())); - ZonedDateTime confirmationTimestamp = Optional.ofNullable(reservation.getConfirmationTimestamp()).orElseGet(ZonedDateTime::now); - model.put("confirmationDate", confirmationTimestamp.withZoneSameInstant(event.getZoneId())); + ZonedDateTime creationTimestamp = ObjectUtils.firstNonNull(reservation.getCreationTimestamp(), reservation.getConfirmationTimestamp(), ZonedDateTime.now()); + model.put("confirmationDate", creationTimestamp.withZoneSameInstant(event.getZoneId())); if (reservation.getValidity() != null) { model.put("expirationDate", ZonedDateTime.ofInstant(reservation.getValidity().toInstant(), event.getZoneId())); diff --git a/src/main/resources/alfio/db/HSQLDB/R__RESERVATION_AND_TICKET_AND_TX.sql b/src/main/resources/alfio/db/HSQLDB/R__RESERVATION_AND_TICKET_AND_TX.sql index fae62ef48b..7bec32bbe3 100644 --- a/src/main/resources/alfio/db/HSQLDB/R__RESERVATION_AND_TICKET_AND_TX.sql +++ b/src/main/resources/alfio/db/HSQLDB/R__RESERVATION_AND_TICKET_AND_TX.sql @@ -43,6 +43,7 @@ create view reservation_and_ticket_and_tx as (select tickets_reservation.used_vat_percent tr_used_vat_percent, tickets_reservation.vat_included tr_vat_included, tickets_reservation.event_id_fk tr_event_id, + tickets_reservation.creation_ts tr_creation_ts, ticket.id t_id, ticket.uuid t_uuid, diff --git a/src/main/resources/alfio/db/HSQLDB/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql b/src/main/resources/alfio/db/HSQLDB/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql new file mode 100644 index 0000000000..431e0f40fd --- /dev/null +++ b/src/main/resources/alfio/db/HSQLDB/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql @@ -0,0 +1,20 @@ +-- +-- 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 . +-- + +alter table tickets_reservation add column creation_ts timestamp with time zone; + +update tickets_reservation set creation_ts = coalesce(confirmation_ts, (select event_time from auditing where reservation_id = id and event_type = 'RESERVATION_CREATE')) \ No newline at end of file diff --git a/src/main/resources/alfio/db/MYSQL/R__RESERVATION_AND_TICKET_AND_TX.sql b/src/main/resources/alfio/db/MYSQL/R__RESERVATION_AND_TICKET_AND_TX.sql index fae62ef48b..7bec32bbe3 100644 --- a/src/main/resources/alfio/db/MYSQL/R__RESERVATION_AND_TICKET_AND_TX.sql +++ b/src/main/resources/alfio/db/MYSQL/R__RESERVATION_AND_TICKET_AND_TX.sql @@ -43,6 +43,7 @@ create view reservation_and_ticket_and_tx as (select tickets_reservation.used_vat_percent tr_used_vat_percent, tickets_reservation.vat_included tr_vat_included, tickets_reservation.event_id_fk tr_event_id, + tickets_reservation.creation_ts tr_creation_ts, ticket.id t_id, ticket.uuid t_uuid, diff --git a/src/main/resources/alfio/db/MYSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql b/src/main/resources/alfio/db/MYSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql new file mode 100644 index 0000000000..ec5c623ed6 --- /dev/null +++ b/src/main/resources/alfio/db/MYSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql @@ -0,0 +1,20 @@ +-- +-- 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 . +-- + +alter table tickets_reservation add column creation_ts timestamp null; + +update tickets_reservation set creation_ts = coalesce(confirmation_ts, (select event_time from auditing where reservation_id = id and event_type = 'RESERVATION_CREATE')) \ No newline at end of file diff --git a/src/main/resources/alfio/db/PGSQL/R__RESERVATION_AND_TICKET_AND_TX.sql b/src/main/resources/alfio/db/PGSQL/R__RESERVATION_AND_TICKET_AND_TX.sql index fae62ef48b..7bec32bbe3 100644 --- a/src/main/resources/alfio/db/PGSQL/R__RESERVATION_AND_TICKET_AND_TX.sql +++ b/src/main/resources/alfio/db/PGSQL/R__RESERVATION_AND_TICKET_AND_TX.sql @@ -43,6 +43,7 @@ create view reservation_and_ticket_and_tx as (select tickets_reservation.used_vat_percent tr_used_vat_percent, tickets_reservation.vat_included tr_vat_included, tickets_reservation.event_id_fk tr_event_id, + tickets_reservation.creation_ts tr_creation_ts, ticket.id t_id, ticket.uuid t_uuid, diff --git a/src/main/resources/alfio/db/PGSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql b/src/main/resources/alfio/db/PGSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql new file mode 100644 index 0000000000..0e81e99f82 --- /dev/null +++ b/src/main/resources/alfio/db/PGSQL/V22_1.15.2__ADD_CREATION_TIMESTAMP.sql @@ -0,0 +1,20 @@ +-- +-- 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 . +-- + +alter table tickets_reservation add column creation_ts timestamp with time zone; + +update tickets_reservation set creation_ts = coalesce(confirmation_ts, (select event_time from auditing where reservation_id = id and event_type = 'RESERVATION_CREATE' limit 1)) \ No newline at end of file diff --git a/src/test/java/alfio/manager/EventManagerIntegrationTest.java b/src/test/java/alfio/manager/EventManagerIntegrationTest.java index d56ac2359a..aaf7ec01c4 100644 --- a/src/test/java/alfio/manager/EventManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/EventManagerIntegrationTest.java @@ -531,7 +531,7 @@ public void testValidationBoundedFailedPendingTickets() { List tickets = ticketRepository.selectTicketInCategoryForUpdate(event.getId(), category.getId(), 1, Collections.singletonList(Ticket.TicketStatus.FREE.name())); String reservationId = "12345678"; - ticketReservationRepository.createNewReservation(reservationId, DateUtils.addDays(new Date(), 1), null, "en", event.getId(), event.getVat(), event.isVatIncluded()); + ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(), DateUtils.addDays(new Date(), 1), null, "en", event.getId(), event.getVat(), event.isVatIncluded()); ticketRepository.reserveTickets(reservationId, tickets, category.getId(), "en", 100); TicketCategoryModification tcm = new TicketCategoryModification(category.getId(), category.getName(), 10, DateTimeModification.fromZonedDateTime(category.getUtcInception()), diff --git a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java index 867a4e2608..78a37169fa 100644 --- a/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java +++ b/src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java @@ -52,6 +52,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.util.*; import static alfio.model.modification.DateTimeModification.fromZonedDateTime; @@ -227,7 +228,7 @@ private Pair initSoldOutEvent(boolean withUnboundedCategory) thro assertEquals(boundedCategorySize, boundedReserved.size()); List reserved = new ArrayList<>(boundedReserved); String reservationId = UUID.randomUUID().toString(); - ticketReservationRepository.createNewReservation(reservationId, DateUtils.addHours(new Date(), 1), null, Locale.ITALIAN.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded()); + ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(), DateUtils.addHours(new Date(), 1), null, Locale.ITALIAN.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded()); List reservedForUpdate = withUnboundedCategory ? reserved.subList(0, 19) : reserved; ticketRepository.reserveTickets(reservationId, reservedForUpdate, bounded.getId(), Locale.ITALIAN.getLanguage(), 0); if(withUnboundedCategory) {