Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate tickets automatically for subscription owners #1036

Merged
merged 4 commits into from
Dec 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/main/java/alfio/config/DataSourceConfiguration.java
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
import alfio.config.support.JSONColumnMapper;
import alfio.config.support.PlatformProvider;
import alfio.job.Jobs;
import alfio.job.executor.AssignTicketToSubscriberJobExecutor;
import alfio.job.executor.BillingDocumentJobExecutor;
import alfio.job.executor.ReservationJobExecutor;
import alfio.manager.*;
@@ -29,6 +30,8 @@
import alfio.manager.system.ConfigurationManager;
import alfio.repository.EventDeleterRepository;
import alfio.repository.EventRepository;
import alfio.repository.SubscriptionRepository;
import alfio.repository.TicketCategoryRepository;
import alfio.repository.system.AdminJobQueueRepository;
import alfio.repository.system.ConfigurationRepository;
import alfio.repository.user.OrganizationRepository;
@@ -245,9 +248,10 @@ AdminJobManager adminJobManager(AdminJobQueueRepository adminJobQueueRepository,
PlatformTransactionManager transactionManager,
ClockProvider clockProvider,
ReservationJobExecutor reservationJobExecutor,
BillingDocumentJobExecutor billingDocumentJobExecutor) {
BillingDocumentJobExecutor billingDocumentJobExecutor,
AssignTicketToSubscriberJobExecutor assignTicketToSubscriberJobExecutor) {
return new AdminJobManager(
List.of(reservationJobExecutor, billingDocumentJobExecutor),
List.of(reservationJobExecutor, billingDocumentJobExecutor, assignTicketToSubscriberJobExecutor),
adminJobQueueRepository,
transactionManager,
clockProvider);
@@ -267,6 +271,21 @@ BillingDocumentJobExecutor billingDocumentJobExecutor(BillingDocumentManager bil
return new BillingDocumentJobExecutor(billingDocumentManager, ticketReservationManager, eventRepository, notificationManager, organizationRepository);
}

@Bean
AssignTicketToSubscriberJobExecutor assignTicketToSubscriberJobExecutor(AdminReservationRequestManager requestManager,
ConfigurationManager configurationManager,
SubscriptionRepository subscriptionRepository,
EventRepository eventRepository,
ClockProvider clockProvider,
TicketCategoryRepository ticketCategoryRepository) {
return new AssignTicketToSubscriberJobExecutor(requestManager,
configurationManager,
subscriptionRepository,
eventRepository,
clockProvider,
ticketCategoryRepository);
}

@Bean
@Profile(Initializer.PROFILE_DEMO)
DemoModeDataManager demoModeDataManager(UserRepository userRepository,
Original file line number Diff line number Diff line change
@@ -36,10 +36,8 @@
import alfio.manager.support.PaymentResult;
import alfio.manager.support.response.ValidatedResponse;
import alfio.manager.system.ConfigurationManager;
import alfio.manager.system.ReservationPriceCalculator;
import alfio.manager.user.PublicUserManager;
import alfio.model.*;
import alfio.model.TicketCategory.TicketAccessType;
import alfio.model.PurchaseContext.PurchaseContextType;
import alfio.model.subscription.Subscription;
import alfio.model.subscription.SubscriptionUsageExceeded;
@@ -58,36 +56,27 @@
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.Principal;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static alfio.model.PriceContainer.VatStatus.*;
import static alfio.model.system.ConfigurationKeys.*;
import static alfio.util.MonetaryUtil.unitToCents;
import static java.util.stream.Collectors.groupingBy;
import static alfio.model.system.ConfigurationKeys.ENABLE_ITALY_E_INVOICING;
import static alfio.model.system.ConfigurationKeys.FORCE_TICKET_OWNER_ASSIGNMENT_AT_RESERVATION;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.trimToNull;

@RestController
@AllArgsConstructor
@@ -620,7 +609,7 @@ public ResponseEntity<TransactionInitializationToken> initTransaction(@PathVaria
return ResponseEntity.badRequest().build();
}

Optional<ResponseEntity<TransactionInitializationToken>> responseEntity = getEventReservationPair(reservationId)
Optional<ResponseEntity<TransactionInitializationToken>> responseEntity = purchaseContextManager.getReservationWithPurchaseContext(reservationId)
.map(pair -> {
var event = pair.getLeft();
return ticketReservationManager.initTransaction(event, reservationId, paymentMethod, allParams)
@@ -637,7 +626,7 @@ public ResponseEntity<TransactionInitializationToken> initTransaction(@PathVaria
public ResponseEntity<Boolean> removeToken(@PathVariable("eventName") String eventName,
@PathVariable("reservationId") String reservationId) {

var res = getEventReservationPair(reservationId).map(et -> paymentManager.removePaymentTokenReservation(et.getRight().getId())).orElse(false);
var res = purchaseContextManager.getReservationWithPurchaseContext(reservationId).map(et -> paymentManager.removePaymentTokenReservation(et.getRight().getId())).orElse(false);
return ResponseEntity.ok(res);
}

@@ -647,18 +636,10 @@ public ResponseEntity<Boolean> removeToken(@PathVariable("eventName") String eve
})
public ResponseEntity<Boolean> deletePaymentAttempt(@PathVariable("reservationId") String reservationId) {

var res = getEventReservationPair(reservationId).map(et -> ticketReservationManager.cancelPendingPayment(et.getRight().getId(), et.getLeft())).orElse(false);
var res = purchaseContextManager.getReservationWithPurchaseContext(reservationId).map(et -> ticketReservationManager.cancelPendingPayment(et.getRight().getId(), et.getLeft())).orElse(false);
return ResponseEntity.ok(res);
}

//FIXME: rename ->getPurchaseContextReservationPair
private Optional<Pair<PurchaseContext, TicketReservation>> getEventReservationPair(String reservationId) {
return purchaseContextManager.findByReservationId(reservationId)
.map(event -> Pair.of(event, ticketReservationManager.findById(reservationId)))
.filter(pair -> pair.getRight().isPresent())
.map(pair -> Pair.of(pair.getLeft(), pair.getRight().orElseThrow()));
}

@GetMapping({
"/reservation/{reservationId}/payment/{method}/status",
"/event/{eventName}/reservation/{reservationId}/payment/{method}/status" //<-deprecated
@@ -673,7 +654,7 @@ public ResponseEntity<ReservationPaymentResult> getTransactionStatus(
return ResponseEntity.badRequest().build();
}

return getEventReservationPair(reservationId)
return purchaseContextManager.getReservationWithPurchaseContext(reservationId)
.flatMap(pair -> paymentManager.getTransactionStatus(pair.getRight(), paymentMethod))
.map(pr -> ResponseEntity.ok(new ReservationPaymentResult(pr.isSuccessful(), pr.isRedirect(), pr.getRedirectUrl(), pr.isFailed(), pr.getGatewayIdOrNull())))
.orElseGet(() -> ResponseEntity.notFound().build());
@@ -684,65 +665,22 @@ public ResponseEntity<ValidatedResponse<Boolean>> applyCode(@PathVariable("reser
if(reservationCodeForm.getType() != ReservationCodeForm.ReservationCodeType.SUBSCRIPTION) {
throw new IllegalStateException(reservationCodeForm.getType() + " not supported");
}
boolean res = getEventReservationPair(reservationId).map(et -> {
boolean isUUID = reservationCodeForm.isCodeUUID();
log.trace("is code UUID {}", isUUID);
var pin = reservationCodeForm.getCode();
if (!isUUID && !PinGenerator.isPinValid(pin, Subscription.PIN_LENGTH)) {
bindingResult.reject("error.restrictedValue");
return false;
}

//ensure pin length, as we will do a like concat(pin,'%'), it could be dangerous to have an empty string...
Assert.isTrue(pin.length() >= Subscription.PIN_LENGTH, "Pin must have a length of at least 8 characters");

var partialUuid = !isUUID ? PinGenerator.pinToPartialUuid(pin, Subscription.PIN_LENGTH) : pin;
var email = reservationCodeForm.getEmail();
var requireEmail = false;
int count;
if (isUUID) {
count = subscriptionRepository.countSubscriptionById(UUID.fromString(pin));
} else {
count = subscriptionRepository.countSubscriptionByPartialUuid(partialUuid);
if (count > 1) {
count = subscriptionRepository.countSubscriptionByPartialUuidAndEmail(partialUuid, email);
requireEmail = true;
}
}
log.trace("code count is {}", count);
if (count == 0) {
bindingResult.reject(isUUID ? "subscription.uuid.not.found" : "subscription.pin.not.found");
}
if (count > 1) {
bindingResult.reject("subscription.code.insert.full");
}

if (bindingResult.hasErrors()) {
return false;
}

var subscriptionId = isUUID ? UUID.fromString(pin) : requireEmail ? subscriptionRepository.getSubscriptionIdByPartialUuidAndEmail(partialUuid, email) : subscriptionRepository.getSubscriptionIdByPartialUuid(partialUuid);
var subscriptionDescriptor = subscriptionRepository.findDescriptorBySubscriptionId(subscriptionId);
var subscription = subscriptionRepository.findSubscriptionById(subscriptionId);
subscription.isValid(Optional.of(bindingResult));
if (bindingResult.hasErrors()) {
return false;
}
try {
return ticketReservationManager.applySubscriptionCode(((Event)et.getLeft()).getId(), et.getRight(), subscriptionDescriptor, subscriptionId);
} catch (SubscriptionUsageExceeded | SubscriptionUsageExceededForEvent ex) {
bindingResult.reject(ex instanceof SubscriptionUsageExceeded ? "subscription.max-usage-reached" : "subscription.max-usage-reached-per-event");
return false;
}
}).orElse(false);
boolean res = purchaseContextManager.getReservationWithPurchaseContext(reservationId)
.map(et -> ticketReservationManager.validateAndApplySubscriptionCode(et.getLeft(),
et.getRight(),
reservationCodeForm.getCodeAsUUID(),
reservationCodeForm.getCode(),
reservationCodeForm.getEmail(),
bindingResult))
.orElse(false);
return ResponseEntity.ok(ValidatedResponse.toResponse(bindingResult, res));
}

@DeleteMapping("/reservation/{reservationId}/remove-code")
public ResponseEntity<Boolean> removeCode(@PathVariable("reservationId") String reservationId, @RequestParam("type") ReservationCodeForm.ReservationCodeType type) {
boolean res = false;
if (type == ReservationCodeForm.ReservationCodeType.SUBSCRIPTION) {
res = getEventReservationPair(reservationId).map(et -> ticketReservationManager.removeSubscription(et.getRight())).orElse(false);
res = purchaseContextManager.getReservationWithPurchaseContext(reservationId).map(et -> ticketReservationManager.removeSubscription(et.getRight())).orElse(false);
}
return ResponseEntity.ok(res);
}
8 changes: 4 additions & 4 deletions src/main/java/alfio/controller/form/ReservationCodeForm.java
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
import lombok.Data;

import java.io.Serializable;
import java.util.Optional;
import java.util.UUID;

@Data
@@ -34,12 +35,11 @@ public enum ReservationCodeType {
SUBSCRIPTION
}

public boolean isCodeUUID() {
public Optional<UUID> getCodeAsUUID() {
try {
UUID.fromString(code);
return true;
return Optional.of(UUID.fromString(code));
} catch (IllegalArgumentException e) {
return false;
return Optional.empty();
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/alfio/job/Jobs.java
Original file line number Diff line number Diff line change
@@ -95,6 +95,16 @@ public void sendOfflinePaymentReminderToEventOrganizers() {
}
}

@Scheduled(cron = "#{environment.acceptsProfiles('dev') ? '0 * * * * *' : '0 0 0/1 * * ?'}")
public void assignTicketsToSubscribers() {
log.trace("running job assignTicketsToSubscribers");
try {
adminJobManager.scheduleExecution(AdminJobExecutor.JobName.ASSIGN_TICKETS_TO_SUBSCRIBERS, Map.of());
} finally {
log.trace("end job sendOfflinePaymentReminderToEventOrganizers");
}
}


@Scheduled(fixedRate = FIVE_SECONDS)
public void sendEmails() {
Loading