1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-29 18:52:22 +01:00

batch delete periodic events

This commit is contained in:
Sylvain 2020-01-07 17:18:49 +01:00
parent 6090d2e05a
commit 54be21729b
9 changed files with 171 additions and 25 deletions

View File

@ -4,6 +4,7 @@
- Automated setup assistant
- An administrator can delete a member
- An event reservation can be cancelled, if reservation cancellation is enabled
- Batch delete recurring events
- Ability to import iCalendar agendas in the public calendar, through URLs to ICS files (RFC 5545)
- Ability to configure the duration of a reservation slot, using `SLOT_DURATION`. Previously, only 60 minutes slots were allowed
- Ability to force the email validation when a new user registers. This is optionally configured with `USER_CONFIRMATION_NEEDED_TO_SIGN_IN`

View File

@ -183,26 +183,22 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
* @param event {$resource} angular's Event $resource
*/
$scope.deleteEvent = function (event) {
dialogs.confirm({
// open a confirmation dialog
const modalInstance = $uibModal.open({
animation: true,
templateUrl: '<%= asset_path "events/deleteRecurrent.html" %>',
size: 'md',
controller: 'DeleteRecurrentEventController',
resolve: {
object () {
return {
title: _t('app.public.events_show.confirmation_required'),
msg: _t('app.public.events_show.do_you_really_want_to_delete_this_event')
};
}
eventPromise: ['Event', function (Event) { return Event.get({ id: $scope.event.id }).$promise; }]
}
}, function () {
// the admin has confirmed, delete
event.$delete(function () {
});
// once the dialog was closed, do things depending on the result
modalInstance.result.then(function (res) {
if (res.status == 'success') {
$state.go('app.public.events_list');
return growl.info(_t('app.public.events_show.event_successfully_deleted'));
}, function (error) {
console.error(error);
growl.error(_t('app.public.events_show.unable_to_delete_the_event_because_some_users_alredy_booked_it'));
});
}
);
}
});
};
/**
@ -816,3 +812,71 @@ function __range__ (left, right, inclusive) {
}
return range;
}
/**
* Controller used in the event deletion modal window
*/
Application.Controllers.controller('DeleteRecurrentEventController', ['$scope', '$uibModalInstance', 'Event', 'eventPromise', 'growl', '_t',
function ($scope, $uibModalInstance, Event, eventPromise, growl, _t) {
// is the current event (to be deleted) recurrent?
$scope.isRecurrent = eventPromise.recurrence_events.length > 0;
// with recurrent slots: how many slots should we delete?
$scope.deleteMode = 'single';
/**
* Confirmation callback
*/
$scope.ok = function () {
const { id, start_at, end_at } = eventPromise;
// the admin has confirmed, delete the slot
Event.delete(
{ id, mode: $scope.deleteMode },
function (res) {
// delete success
if (res.deleted > 1) {
growl.success(_t(
'app.public.events_show.events_deleted',
{COUNT: res.deleted - 1}
));
} else {
growl.success(_t(
'app.public.events_show.event_successfully_deleted'
));
}
$uibModalInstance.close({
status: 'success',
events: res.details.map(function (d) { return d.event.id })
});
},
function (res) {
// not everything was deleted
const { data } = res;
if (data.total > 1) {
growl.warning(_t(
'app.public.events_show.events_not_deleted',
{TOTAL: data.total, COUNT: data.total - data.deleted}
));
} else {
growl.error(_t(
'app.public.events_show.unable_to_delete_the_event'
));
}
$uibModalInstance.close({
status: 'failed',
availabilities: data.details.filter(function (d) { return d.status }).map(function (d) { return d.event.id })
});
});
}
/**
* Cancellation callback
*/
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
}
}
]);

View File

@ -0,0 +1,26 @@
<div class="modal-header">
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
<h1 translate>{{ 'app.public.events_show.confirmation_required' }}</h1>
</div>
<div class="modal-body">
<p ng-hide="isRecurrent" translate>{{ 'app.public.events_show.do_you_really_want_to_delete_this_event' }}</p>
<p ng-show="isRecurrent" translate>{{ 'app.public.events_show.delete_recurring_event' }}</p>
<div ng-show="isRecurrent" class="form-group">
<label class="checkbox">
<input type="radio" name="delete_mode" ng-model="deleteMode" value="single" required/>
<span translate>{{ 'app.public.events_show.delete_this_event' }}</span>
</label>
<label class="checkbox">
<input type="radio" name="delete_mode" ng-model="deleteMode" value="next" required/>
<span translate>{{ 'app.public.events_show.delete_this_and_next' }}</span>
</label>
<label class="checkbox">
<input type="radio" name="delete_mode" ng-model="deleteMode" value="all" required/>
<span translate>{{ 'app.public.events_show.delete_all' }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.delete' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>

View File

@ -74,10 +74,11 @@ class API::EventsController < API::ApiController
def destroy
authorize Event
if @event.safe_destroy
head :no_content
res = EventService.delete(params[:id], params[:mode])
if res.all? { |r| r[:status] }
render json: { deleted: res.length, details: res }, status: :ok
else
head :unprocessable_entity
render json: { total: res.length, deleted: res.select { |r| r[:status] }.length, details: res }, status: :unprocessable_entity
end
end

View File

@ -41,4 +41,35 @@ class EventService
end
{ start_at: start_at, end_at: end_at }
end
# delete one or more events (if periodic)
def self.delete(event_id, mode = 'single')
results = []
event = Event.find(event_id)
events = case mode
when 'single'
[event]
when 'next'
Event.includes(:availability)
.where(
'availabilities.start_at >= ? AND events.recurrence_id = ?',
event.availability.start_at,
event.recurrence_id
)
.references(:availabilities, :events)
when 'all'
Event.where(
'recurrence_id = ?',
event.recurrence_id
)
else
[]
end
events.each do |e|
# here we use double negation because safe_destroy can return either a boolean (false) or an Availability (in case of delete success)
results.push status: !!e.safe_destroy, event: e # rubocop:disable Style/DoubleNegation
end
results
end
end

View File

@ -117,7 +117,6 @@ en:
# confirmation modal
you_will_receive_confirmation_instructions_by_email: You will receive confirmation instructions by email.
# forgotten password modal
your_email_address_is_unknown: "Your e-mail address is unknown."
you_will_receive_in_a_moment_an_email_with_instructions_to_reset_your_password: "You will receive in a moment, an e-mail with instructions to reset your password."
@ -324,8 +323,14 @@ en:
you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:"
confirmation_required: "Confirmation required"
do_you_really_want_to_delete_this_event: "Do you really want to delete this event?"
delete_recurring_event: "You're about to delete a periodic event. What do you want to do?"
delete_this_event: "Only this event"
delete_this_and_next: "This event and the following"
delete_all: "All events"
event_successfully_deleted: "Event successfully deleted"
unable_to_delete_the_event_because_some_users_alredy_booked_it: "Unable to delete this event, it may have been already reserved by some users."
events_deleted: "The event, and {COUNT, plural, =1{one other} other{{COUNT} others}}, have been deleted"
unable_to_delete_the_event: "Unable to delete the event, it may be booked by a member"
events_not_deleted: "On {TOTAL} events, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exists on {COUNT, plural, =1{it} other{them}}."
cancel_the_reservation: "Cancel the reservation"
do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets."
reservation_was_successfully_cancelled: "Reservation was successfully cancelled"

View File

@ -323,8 +323,14 @@ es:
you_can_shift_this_reservation_on_the_following_slots: "Puede cambiar la reserva en los siguientes campos:"
confirmation_required: "Confirmation required"
do_you_really_want_to_delete_this_event: "Do you really want to delete this event?"
delete_recurring_event: "You're about to delete a periodic event. What do you want to do?"
delete_this_event: "Only this event"
delete_this_and_next: "This event and the following"
delete_all: "All events"
event_successfully_deleted: "Event successfully deleted"
unable_to_delete_the_event_because_some_users_alredy_booked_it: "Unable to delete this event, it may have been already reserved by some users."
events_deleted: "The event, and {COUNT, plural, =1{one other} other{{COUNT} others}}, have been deleted"
unable_to_delete_the_event: "Unable to delete the event, it may be booked by a member"
events_not_deleted: "On {TOTAL} events, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exists on {COUNT, plural, =1{it} other{them}}."
cancel_the_reservation: "Cancel the reservation"
do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets."
reservation_was_successfully_cancelled: "Reservation was successfully cancelled"

View File

@ -323,8 +323,14 @@ fr:
you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :"
confirmation_required: "Confirmation requise"
do_you_really_want_to_delete_this_event: "Voulez-vous vraiment supprimer cet évènement ?"
delete_recurring_event: "Vous êtes sur le point de supprimer un évènement périodique. Que voulez-vous supprimer ?"
delete_this_event: "Uniquement cet évènement"
delete_this_and_next: "Cet évènement et tous les suivants"
delete_all: "Tous les évènements"
event_successfully_deleted: "L'évènement a bien été supprimé."
unable_to_delete_the_event_because_some_users_alredy_booked_it: "Impossible de supprimer l'évènement, il est peut-être déjà réservé par certains utilisateurs."
events_deleted: "L'évènement, ainsi {COUNT, plural, =1{qu'un autre} other{que {COUNT} autres}}, ont été supprimés"
unable_to_delete_the_event: "L'évènement n'a pu être supprimé, probablement car il est déjà réservé par un membre"
events_not_deleted: "Sur {TOTAL} évènements, {COUNT, plural, =1{un n'a pas pu être supprimé} other{{COUNT} n'ont pas pu être supprimés}}. Il est possible que des réservations existent sur {COUNT, plural, =1{celui-ci} other{ceux-ci}}."
cancel_the_reservation: "Annuler la réservation"
do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Êtes vous sur de vouloir annuler cette réservation? Ceci s'applique à TOUTES les places réservées."
reservation_was_successfully_cancelled: "La réservation a bien été annulée."

View File

@ -323,8 +323,14 @@ pt:
you_can_shift_this_reservation_on_the_following_slots: "Você pode alterar essa reserva nos campos a seguir:"
confirmation_required: "Confirmação obrigatória"
do_you_really_want_to_delete_this_event: "Vocêrealmente deseja remover este evento?"
delete_recurring_event: "You're about to delete a periodic event. What do you want to do?"
delete_this_event: "Only this event"
delete_this_and_next: "This event and the following"
delete_all: "All events"
event_successfully_deleted: "Evento excluído com sucesso"
unable_to_delete_the_event_because_some_users_alredy_booked_it: "Não foi possível excluir este evento, já pode ter sido reservado por alguns usuários."
events_deleted: "The event, and {COUNT, plural, =1{one other} other{{COUNT} others}}, have been deleted"
unable_to_delete_the_event: "Unable to delete the event, it may be booked by a member"
events_not_deleted: "On {TOTAL} events, {COUNT, plural, =1{one was not deleted} other{{COUNT} were not deleted}}. Some reservations may exists on {COUNT, plural, =1{it} other{them}}."
cancel_the_reservation: "Cancel the reservation"
do_you_really_want_to_cancel_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to cancel this reservation? This apply to ALL booked tickets."
reservation_was_successfully_cancelled: "Reservation was successfully cancelled"