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
Show file tree
Hide file tree
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
Expand Up @@ -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.*;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}

Expand All @@ -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
Expand All @@ -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());
Expand All @@ -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);
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/alfio/controller/form/ReservationCodeForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.Data;

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

@Data
Expand All @@ -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
Expand Up @@ -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() {
Expand Down
Loading