Skip to content

Commit

Permalink
#541 - send tickets to one or more attendees
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone committed Jan 9, 2019
1 parent d2d2d9e commit 037b331
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ public Result<Boolean> notifyReservation(@PathVariable("eventName") String event
return adminReservationManager.notify(eventName, reservationId, arm, principal.getName());
}

@PutMapping("/event/{eventName}/{reservationId}/notify-attendees")
public Result<Boolean> notifyAttendees(@PathVariable("eventName") String eventName,
@PathVariable("reservationId") String reservationId,
@RequestBody List<Integer> ids,
Principal principal) {
return adminReservationManager.notifyAttendees(eventName, reservationId, ids, principal.getName());
}

@RequestMapping(value = "/event/{eventName}/{reservationId}/audit", method = RequestMethod.GET)
public Result<List<Audit>> getAudit(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, Principal principal) {
return adminReservationManager.getAudit(eventName, reservationId, principal.getName());
Expand Down
44 changes: 32 additions & 12 deletions src/main/java/alfio/manager/AdminReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -182,34 +183,53 @@ public Result<Pair<TicketReservation, List<Ticket>>> createReservation(AdminRese

//end - the public / package protected methods below must be annotated with @Transactional

@Transactional
public Result<Boolean> notifyAttendees(String eventName, String reservationId, List<Integer> ids, String username) {
return getEventTicketReservationPair(eventName, reservationId, username)
.map(pair -> {
Event event = pair.getLeft();
TicketReservation reservation = pair.getRight();
sendTicketToAttendees(event, reservation, t -> t.getAssigned() && ids.contains(t.getId()));
return Result.success(true);
}).orElseGet(() -> Result.error(ErrorCode.EventError.NOT_FOUND));
}

@Transactional
public Result<Boolean> notify(String eventName, String reservationId, AdminReservationModification arm, String username) {
AdminReservationModification.Notification notification = arm.getNotification();
return eventRepository.findOptionalByShortName(eventName)
.flatMap(e -> optionally(() -> {
eventManager.checkOwnership(e, username, e.getOrganizationId());
return e;
}).flatMap(ev -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r))))
return getEventTicketReservationPair(eventName, reservationId, username)
.map(pair -> {
Event event = pair.getLeft();
TicketReservation reservation = pair.getRight();
if(notification.isCustomer()){
ticketReservationManager.sendConfirmationEmail(event, reservation, Locale.forLanguageTag(reservation.getUserLanguage()));
}
if(notification.isAttendees()) {
ticketRepository.findTicketsInReservation(reservationId)
.stream()
.filter(Ticket::getAssigned)
.forEach(t -> {
Locale locale = Locale.forLanguageTag(t.getUserLanguage());
ticketReservationManager.sendTicketByEmail(t, locale, event, ticketReservationManager.getTicketEmailGenerator(event, reservation, locale));
});
sendTicketToAttendees(event, reservation, Ticket::getAssigned);
}
return Result.success(true);
}).orElseGet(() -> Result.error(ErrorCode.EventError.NOT_FOUND));

}

private Optional<Pair<Event, TicketReservation>> getEventTicketReservationPair(String eventName, String reservationId, String username) {
return eventRepository.findOptionalByShortName(eventName)
.flatMap(e -> optionally(() -> {
eventManager.checkOwnership(e, username, e.getOrganizationId());
return e;
}).flatMap(ev -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r))));
}

private void sendTicketToAttendees(Event event, TicketReservation reservation, Predicate<Ticket> matcher) {
ticketRepository.findTicketsInReservation(reservation.getId())
.stream()
.filter(matcher)
.forEach(t -> {
Locale locale = Locale.forLanguageTag(t.getUserLanguage());
ticketReservationManager.sendTicketByEmail(t, locale, event, ticketReservationManager.getTicketEmailGenerator(event, reservation, locale));
});
}

private Result<Boolean> performUpdate(String reservationId, Event event, TicketReservation r, AdminReservationModification arm, String username) {
ticketReservationManager.ensureBillingDocumentIsPresent(event, r, username);
ticketReservationRepository.updateValidity(reservationId, Date.from(arm.getExpiration().toZonedDateTime(event.getZoneId()).toInstant()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
notify: function(eventName, reservationId, notification) {
return $http['put']('/admin/api/reservation/event/'+eventName+'/'+reservationId+'/notify', notification).error(HttpErrorHandler.handle);
},
notifyAttendees: function(eventName, reservationId, ids) {
return $http['put']('/admin/api/reservation/event/'+eventName+'/'+reservationId+'/notify-attendees', ids).error(HttpErrorHandler.handle);
},
load: function(eventName, reservationId) {
return $http.get('/admin/api/reservation/event/'+eventName+'/'+reservationId+'/').error(HttpErrorHandler.handle);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
onClose: '<',
onConfirm: '<'
},
controller: ['AdminReservationService', 'EventService', '$window', '$stateParams', 'NotificationHandler', 'CountriesService', ReservationViewCtrl],
controller: ['AdminReservationService', 'EventService', '$window', '$stateParams', 'NotificationHandler', 'CountriesService', '$uibModal', ReservationViewCtrl],
templateUrl: '../resources/js/admin/feature/reservation/view/reservation-view.html'
});


function ReservationViewCtrl(AdminReservationService, EventService, $window, $stateParams, NotificationHandler, CountriesService) {
function ReservationViewCtrl(AdminReservationService, EventService, $window, $stateParams, NotificationHandler, CountriesService, $uibModal) {
var ctrl = this;

ctrl.notification = {
Expand Down Expand Up @@ -178,23 +178,26 @@
}
};

var notifyError = function(message) {
ctrl.loading = false;
NotificationHandler.showError(message || 'An unexpected error has occurred. Please retry');
};

var evaluateNotificationResponse = function(r) {
var result = r.data;
ctrl.loading = false;
if(result.success) {
NotificationHandler.showSuccess('Success!');
} else {
notifyError(result.errors.map(function (e) {
return e.description;
}).join(', '));
}
};

var notify = function(customer) {
ctrl.loading = true;
var notifyError = function(message) {
ctrl.loading = false;
NotificationHandler.showError(message || 'An unexpected error has occurred. Please retry');
};
AdminReservationService.notify(ctrl.event.shortName, ctrl.reservation.id, {notification: {customer: customer, attendees:(!customer)}}).then(function(r) {
var result = r.data;
ctrl.loading = false;
if(result.success) {
NotificationHandler.showSuccess('Success!');
} else {
notifyError(result.errors.map(function (e) {
return e.description;
}).join(', '));
}
}, function() {
AdminReservationService.notify(ctrl.event.shortName, ctrl.reservation.id, {notification: {customer: customer, attendees:(!customer)}}).then(evaluateNotificationResponse, function() {
notifyError();
});
};
Expand All @@ -204,7 +207,49 @@
};

ctrl.notifyAttendees = function() {
notify(false);
var m = $uibModal.open({
size: 'lg',
templateUrl: '../resources/js/admin/feature/reservation/view/send-ticket-email.html',
backdrop: 'static',
controller: function($scope) {
$scope.cancel = function() {$scope.$dismiss('canceled');};
$scope.ticketsInfo = ctrl.reservation.ticketsInfo.map(function(ti) {
var nTi = _.cloneDeep(ti);
_.forEach(nTi.attendees, function(a) { a.selected = true; });
return nTi;
});
$scope.sendEmail = function() {
var flatten = _.flatten(_.map($scope.ticketsInfo, 'attendees'));
$scope.$close(_.pluck(_.filter(flatten, {'selected': true}), 'ticketId'));
}

var updateSelection = function(select) {
$scope.ticketsInfo.forEach(function(ti) {
_.forEach(ti.attendees, function(a) {
a.selected = select;
});
});
};

$scope.selectAll = function() {
updateSelection(true);
};

$scope.selectNone = function() {
updateSelection(false);
};



}
});
m.result.then(function(ids) {
if(ids.length > 0) {
AdminReservationService.notifyAttendees(ctrl.event.shortName, ctrl.reservation.id, ids).then(evaluateNotificationResponse, function() {
notifyError();
});
}
});
};

ctrl.confirm = function() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<div class="modal-header">
<h3>Select Attendees to notify</h3>
</div>

<div class="modal-body">
<div class="panel panel-default" ng-repeat="ticketInfo in ticketsInfo">
<div class="panel-heading">
<div class="panel-title">Attendees for Category {{ticketInfo.category.name}}</div>
</div>
<div class="table-responsive">
<table class="table table-bordered">
<tbody>
<tr ng-repeat="attendee in ticketInfo.attendees">
<td width="10%">
<input type="checkbox" ng-model="attendee.selected">
</td>
<td width="30%">
{{attendee.firstName}}
</td>
<td width="30%">
{{attendee.lastName}}
</td>
<td width="30%">
{{attendee.emailAddress}}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-sm btn-default btn-block-xs" ng-click="selectAll()"><i class="fa fa-eye"></i> Select all</button>
<button class="btn btn-sm btn-default btn-block-xs" ng-click="selectNone()"><i class="fa fa-eye-slash"></i> Select none</button>
</div>
</div>
</div>

<div class="modal-footer">
<div class="row">
<div class="col-md-4 col-md-push-8 col-xs-12">
<button class="btn btn-lg btn-warning btn-block" data-ng-click="sendEmail()" style="margin-bottom: 10px">Send</button>
</div>
<div class="col-md-4 col-md-pull-4 col-xs-12">
<button type="button" class="btn btn-lg btn-default btn-block" data-ng-click="cancel()" style="margin-bottom: 10px">Cancel</button>
</div>
</div>
<div>
</div>
</div>

0 comments on commit 037b331

Please sign in to comment.