mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(feat) validate pre-registration reservation
This commit is contained in:
parent
35d1a7b183
commit
0f9a5ff8af
@ -5,7 +5,7 @@
|
||||
# availability by Availability.slot_duration, or otherwise globally by Setting.get('slot_duration')
|
||||
class API::SlotsReservationsController < API::APIController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_slots_reservation, only: %i[update cancel]
|
||||
before_action :set_slots_reservation, only: %i[update cancel validate]
|
||||
respond_to :json
|
||||
|
||||
def update
|
||||
@ -23,6 +23,11 @@ class API::SlotsReservationsController < API::APIController
|
||||
SlotsReservationsService.cancel(@slot_reservation)
|
||||
end
|
||||
|
||||
def validate
|
||||
authorize @slot_reservation
|
||||
SlotsReservationsService.validate(@slot_reservation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_slots_reservation
|
||||
|
@ -436,7 +436,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state',
|
||||
/**
|
||||
* Controller used in the reservations listing page for a specific event
|
||||
*/
|
||||
Application.Controllers.controller('ShowEventReservationsController', ['$scope', 'eventPromise', 'reservationsPromise', function ($scope, eventPromise, reservationsPromise) {
|
||||
Application.Controllers.controller('ShowEventReservationsController', ['$scope', 'eventPromise', 'reservationsPromise', 'dialogs', 'SlotsReservation', 'growl', '_t', function ($scope, eventPromise, reservationsPromise, dialogs, SlotsReservation, growl, _t) {
|
||||
// retrieve the event from the ID provided in the current URL
|
||||
$scope.event = eventPromise;
|
||||
|
||||
@ -451,6 +451,42 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
||||
$scope.isCancelled = function (reservation) {
|
||||
return !!(reservation.slots_reservations_attributes[0].canceled_at);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the provided reservation has been validated
|
||||
* @param reservation {Reservation}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isValidated = function (reservation) {
|
||||
return !!(reservation.slots_reservations_attributes[0].validated_at);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to validate a reservation
|
||||
* @param reservation {Reservation}
|
||||
*/
|
||||
$scope.validateReservation = function (reservation) {
|
||||
dialogs.confirm({
|
||||
resolve: {
|
||||
object: function () {
|
||||
return {
|
||||
title: _t('app.admin.event_reservations.validate_the_reservation'),
|
||||
msg: _t('app.admin.event_reservations.do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets')
|
||||
};
|
||||
}
|
||||
}
|
||||
}, function () { // validate confirmed
|
||||
SlotsReservation.validate({
|
||||
id: reservation.slots_reservations_attributes[0].id
|
||||
}, () => { // successfully validated
|
||||
growl.success(_t('app.admin.event_reservations.reservation_was_successfully_validated'));
|
||||
const index = $scope.reservations.indexOf(reservation);
|
||||
$scope.reservations[index].slots_reservations_attributes[0].validated_at = new Date();
|
||||
}, () => {
|
||||
growl.warning(_t('app.admin.event_reservations.validation_failed'));
|
||||
});
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,10 @@ Application.Services.factory('SlotsReservation', ['$resource', function ($resour
|
||||
cancel: {
|
||||
method: 'PUT',
|
||||
url: '/api/slots_reservations/:id/cancel'
|
||||
},
|
||||
validate: {
|
||||
method: 'PUT',
|
||||
url: '/api/slots_reservations/:id/validate'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -12,8 +12,9 @@
|
||||
<tr>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.title' }}</th>
|
||||
<th style="width:30%" translate>{{ 'app.admin.events.dates' }}</th>
|
||||
<th style="width:10%" translate>{{ 'app.admin.events.booking' }}</th>
|
||||
<th style="width:30%"></th>
|
||||
<th style="width:15%" translate>{{ 'app.admin.events.types' }}</th>
|
||||
<th style="width:10%" translate>{{ 'app.admin.events.booking' }}</th>
|
||||
<th style="width:15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -48,6 +49,13 @@
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span ng-if="event.event_type === 'standard'" class="v-middle badge text-base bg-stage" translate="">{{ 'app.admin.events.event_type.standard' }}</span>
|
||||
<span ng-if="event.event_type === 'nominative'" class="v-middle badge text-base bg-event" translate="">{{ 'app.admin.events.event_type.nominative' }}</span>
|
||||
<span ng-if="event.event_type === 'family'" class="v-middle badge text-base bg-atelier" translate="">{{ 'app.admin.events.event_type.family' }}</span>
|
||||
<span ng-if="event.pre_registration" class="v-middle badge text-base bg-info" translate="">{{ 'app.admin.events.pre_registration' }}</span>
|
||||
</td>
|
||||
|
||||
<td style="vertical-align:middle">
|
||||
<span class="ng-binding" ng-if="event.nb_total_places > 0">{{ event.nb_total_places - event.nb_free_places }} / {{ event.nb_total_places }}</span>
|
||||
<span class="badge font-sbold cancelled" ng-if="event.nb_total_places == -1" translate>{{ 'app.admin.events.cancelled' }}</span>
|
||||
@ -57,10 +65,10 @@
|
||||
<td style="vertical-align:middle">
|
||||
<div class="buttons">
|
||||
<a class="btn btn-default" ui-sref="app.admin.event_reservations({id: event.id})">
|
||||
<i class="fa fa-bookmark"></i> {{ 'app.admin.events.view_reservations' | translate }}
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
<a class="btn btn-default" ui-sref="app.admin.events_edit({id: event.id})">
|
||||
<i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -20,31 +20,49 @@
|
||||
<table class="table" ng-if="reservations.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.user' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.payment_date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.booked_by' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.reservations' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.date' }}</th>
|
||||
<th style="width:25%" translate>{{ 'app.admin.event_reservations.reserved_tickets' }}</th>
|
||||
<th style="width:25%"></th>
|
||||
<th ng-if="event.pre_registration" style="width:25%" translate>{{ 'app.admin.event_reservations.status' }}</th>
|
||||
<th ng-if="event.pre_registration" style="width:25%" translate>{{ 'app.admin.event_reservations.gestion' }}</th>
|
||||
<th style="width:5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="reservation in reservations" ng-class="{'disabled': isCancelled(reservation)}">
|
||||
<td class="text-c">
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})" ng-if="event.event_type === 'standard'">{{ reservation.user_full_name }} </a>
|
||||
<a ui-sref="app.logged.members_show({id: reservation.user_id})">{{ reservation.user_full_name }} </a>
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="event.event_type === 'standard'">{{ reservation.user_full_name }} </span>
|
||||
<div ng-repeat="bu in reservation.booking_users_attributes">
|
||||
<span ng-if="bu.booked_type !== 'User'">{{bu.name}}</span>
|
||||
<a ui-sref="app.logged.members_show({id: bu.booked_id})" ng-if="bu.booked_type === 'User'">{{bu.name}}</a>
|
||||
<span>{{bu.name}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ reservation.created_at | amDateFormat:'LL LTS' }}</td>
|
||||
<td>
|
||||
<span ng-if="reservation.nb_reserve_places > 0">{{ 'app.admin.event_reservations.full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
|
||||
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
||||
<div ng-show="isCancelled(reservation)" class="canceled-marker" translate>{{ 'app.admin.event_reservations.canceled' }}</div>
|
||||
</td>
|
||||
<td ng-if="event.pre_registration">
|
||||
<span ng-if="!isValidated(reservation) && !isCancelled(reservation)" class="v-middle badge text-base bg-info" translate="">{{ 'app.admin.event_reservations.event_status.pre_registered' }}</span>
|
||||
<span ng-if="isValidated(reservation) && !isCancelled(reservation)" class="v-middle badge text-base bg-stage" translate="">{{ 'app.admin.event_reservations.event_status.to_pay' }}</span>
|
||||
<span ng-if="reservation.is_paid && !isCancelled(reservation)" class="v-middle badge text-base bg-success" translate="">{{ 'app.admin.event_reservations.event_status.paid' }}</span>
|
||||
<span ng-if="isCancelled(reservation)" class="v-middle badge text-base bg-event" translate="">{{ 'app.admin.event_reservations.event_status.canceled' }}</span>
|
||||
</td>
|
||||
<td ng-if="event.pre_registration">
|
||||
<button class="btn btn-default" ng-click="validateReservation(reservation)" ng-if="!isValidated(reservation) && !isCancelled(reservation)" translate>
|
||||
{{ 'app.admin.event_reservations.validate' }}
|
||||
</button>
|
||||
<button class="btn btn-default" ng-if="isValidated(reservation) && !isCancelled(reservation)" translate>
|
||||
{{ 'app.admin.event_reservations.pay' }}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ui-sref="app.public.events_show({id: event.id})">
|
||||
<i class="fa fa-tag"></i> {{ 'app.admin.event_reservations.show_the_event' | translate }}
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -15,4 +15,8 @@ class SlotsReservationPolicy < ApplicationPolicy
|
||||
def cancel?
|
||||
user.admin? || user.manager? || record.reservation.user == user
|
||||
end
|
||||
|
||||
def validate?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
end
|
||||
|
@ -21,5 +21,18 @@ class SlotsReservationsService
|
||||
rescue Faraday::ConnectionFailed
|
||||
warn 'Unable to update data in elasticsearch'
|
||||
end
|
||||
|
||||
def validate(slot_reservation)
|
||||
if slot_reservation.update(validated_at: Time.current)
|
||||
NotificationCenter.call type: 'notify_member_reservation_validated',
|
||||
receiver: slot_reservation.reservation.user,
|
||||
attached_object: slot_reservation.reservation
|
||||
NotificationCenter.call type: 'notify_admin_reservation_validated',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: slot_reservation.reservation
|
||||
return true
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,4 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.a_RESERVABLE_reservation_was_validated_html',
|
||||
RESERVABLE: notification.attached_object.reservable.name,
|
||||
NAME: notification.attached_object.user&.profile&.full_name || t('api.notifications.deleted_user'))
|
@ -0,0 +1,3 @@
|
||||
json.title notification.notification_type
|
||||
json.description t('.your_reservation_RESERVABLE_was_validated_html',
|
||||
RESERVABLE: notification.attached_object.reservable.name)
|
@ -7,6 +7,7 @@ json.message reservation.message
|
||||
json.slots_reservations_attributes reservation.slots_reservations do |sr|
|
||||
json.id sr.id
|
||||
json.canceled_at sr.canceled_at&.iso8601
|
||||
json.validated_at sr.validated_at&.iso8601
|
||||
json.slot_attributes do
|
||||
json.id sr.slot_id
|
||||
json.start_at sr.slot.start_at.iso8601
|
||||
@ -39,3 +40,4 @@ json.booking_users_attributes reservation.booking_users.order(booked_type: :desc
|
||||
json.booked_id bu.booked_id
|
||||
json.booked_type bu.booked_type
|
||||
end
|
||||
json.is_paid reservation.invoice_items.count.positive?
|
||||
|
@ -0,0 +1,19 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p>
|
||||
<%= t('.body.reservation_validated_html',
|
||||
NAME: @attached_object.user&.profile&.full_name || t('api.notifications.deleted_user'),
|
||||
RESERVABLE: @attached_object.reservable.name) %>
|
||||
</p>
|
||||
<p><%= t('.body.reserved_slots') %></p>
|
||||
<ul>
|
||||
<% @attached_object.slots.each do |slot| %>
|
||||
<% if @attached_object.reservable_type == 'Event' %>
|
||||
<% (slot.start_at.to_date..slot.end_at.to_date).each do |d| %>
|
||||
<li><%= "#{I18n.l d, format: :long} #{I18n.l slot.start_at, format: :hour_minute} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<li><%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
@ -0,0 +1,16 @@
|
||||
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||
|
||||
<p><%= t('.body.reservation_validated_html', RESERVATION: @attached_object.reservable.name) %></p>
|
||||
|
||||
<p><%= t('.body.your_reserved_slots') %> </p>
|
||||
<ul>
|
||||
<% @attached_object.slots.each do |slot| %>
|
||||
<% if @attached_object.reservable_type == 'Event' %>
|
||||
<% (slot.start_at.to_date..slot.end_at.to_date).each do |d| %>
|
||||
<li><%= "#{I18n.l d, format: :long} #{I18n.l slot.start_at, format: :hour_minute} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<li><%= "#{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
@ -601,6 +601,12 @@ en:
|
||||
do_you_really_want_to_delete_this_price_category: "Do you really want to delete this price category?"
|
||||
price_category_successfully_deleted: "Price category successfully deleted."
|
||||
price_category_deletion_failed: "Price category deletion failed."
|
||||
types: "Types"
|
||||
event_type:
|
||||
standard: "Standard"
|
||||
family: "Family"
|
||||
nominative: "Nominative"
|
||||
pre_registration: "Pre-registration"
|
||||
#add a new event
|
||||
events_new:
|
||||
add_an_event: "Add an event"
|
||||
@ -635,6 +641,22 @@ en:
|
||||
no_reservations_for_now: "No reservation for now."
|
||||
back_to_monitoring: "Back to monitoring"
|
||||
canceled: "Canceled"
|
||||
date: "Date"
|
||||
booked_by: "Booked by"
|
||||
reservations: "Reservations"
|
||||
status: "Status"
|
||||
gestion: "Gestion"
|
||||
event_status:
|
||||
pre_registered: "Pre-registered"
|
||||
to_pay: "To pay"
|
||||
paid: "Paid"
|
||||
canceled: "Canceled"
|
||||
validate: "Validate"
|
||||
pay: "Pay"
|
||||
validate_the_reservation: "Validate the reservation"
|
||||
do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets: "Do you really want to validate this reservation? This apply to ALL booked tickets."
|
||||
reservation_was_successfully_validated: "Reservation was successfully validated."
|
||||
validation_failed: "Validation failed."
|
||||
events_settings:
|
||||
title: "Settings"
|
||||
generic_text_block: "Editorial text block"
|
||||
|
@ -601,6 +601,12 @@ fr:
|
||||
do_you_really_want_to_delete_this_price_category: "Êtes vous sur de vouloir supprimer cette catégorie tarifaire ?"
|
||||
price_category_successfully_deleted: "Catégorie tarifaire supprimée avec succès."
|
||||
price_category_deletion_failed: "Échec de la suppression de la catégorie tarifaire."
|
||||
types: 'Types'
|
||||
event_type:
|
||||
standard: 'Standard'
|
||||
family: "Famille"
|
||||
nominative: "Nominatif"
|
||||
pre_registration: "Pré-inscription"
|
||||
#add a new event
|
||||
events_new:
|
||||
add_an_event: "Ajouter un événement"
|
||||
@ -635,6 +641,22 @@ fr:
|
||||
no_reservations_for_now: "Aucune réservation pour le moment."
|
||||
back_to_monitoring: "Retour au suivi"
|
||||
canceled: "Annulée"
|
||||
date: "Date"
|
||||
booked_by: "Réservé par"
|
||||
reservations: "Réservations"
|
||||
status: "Statut"
|
||||
gestion: "Gestion"
|
||||
event_status:
|
||||
pre_registered: "Pré-inscrit"
|
||||
to_pay: "À payer"
|
||||
paid: "Payé"
|
||||
canceled: "Annulée"
|
||||
validate: "Valider"
|
||||
pay: "Payer"
|
||||
validate_the_reservation: "Valider la réservation"
|
||||
do_you_really_want_to_validate_this_reservation_this_apply_to_all_booked_tickets: "Êtes vous sur de vouloir valider cette réservation? Ceci s'applique à TOUTES les places réservées."
|
||||
reservation_was_successfully_validated: "La réservation a bien été validé."
|
||||
validation_failed: "La validation a échoué."
|
||||
events_settings:
|
||||
title: "Paramètres"
|
||||
generic_text_block: "Bloc de texte rédactionnel"
|
||||
|
@ -469,6 +469,10 @@ en:
|
||||
order_canceled: "Your command %{REFERENCE} is canceled"
|
||||
notify_user_order_is_refunded:
|
||||
order_refunded: "Your command %{REFERENCE} is refunded"
|
||||
notify_member_reservation_validated:
|
||||
your_reservation_RESERVABLE_was_validated_html: "Your reservation <strong><em>%{RESERVABLE}</em></strong> was successfully validated."
|
||||
notify_admin_reservation_validated:
|
||||
a_RESERVABLE_reservation_was_validated_html: "A <strong><em>%{RESERVABLE}</em></strong> reservation of <strong><em>%{USER}</em></strong> was validated."
|
||||
#statistics tools for admins
|
||||
statistics:
|
||||
subscriptions: "Subscriptions"
|
||||
|
@ -469,6 +469,10 @@ fr:
|
||||
order_canceled: "Votre commande %{REFERENCE} est annulée"
|
||||
notify_user_order_is_refunded:
|
||||
order_refunded: "Votre commande %{REFERENCE} est remboursée"
|
||||
notify_member_reservation_validated:
|
||||
your_reservation_RESERVABLE_was_validated_html: "Votre réservation de <strong><em>%{RESERVABLE}</em></strong> a été validée."
|
||||
notify_admin_reservation_validated:
|
||||
a_RESERVABLE_reservation_was_validated_html: "La réservation de <strong><em>%{RESERVABLE}</em></strong> de <strong><em>%{NAME}</em></strong> a été validée."
|
||||
#statistics tools for admins
|
||||
statistics:
|
||||
subscriptions: "Abonnements"
|
||||
|
@ -451,3 +451,13 @@ en:
|
||||
subject: "Your command was refunded"
|
||||
body:
|
||||
notify_user_order_is_refunded: "Your command %{REFERENCE} was refunded."
|
||||
notify_member_reservation_validated:
|
||||
subject: "Your reservation was validated"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> was validated."
|
||||
your_reserved_slots: "Your reserved slots are:"
|
||||
notify_admin_reservation_validated:
|
||||
subject: "Réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> of %{NAME} was validated."
|
||||
reserved_slots: "Reserved slots are:"
|
||||
|
@ -451,3 +451,13 @@ fr:
|
||||
subject: "Votre commande est remboursée"
|
||||
body:
|
||||
notify_user_order_is_refunded: "Votre commande %{REFERENCE} est remboursée :"
|
||||
notify_member_reservation_validated:
|
||||
subject: "Votre réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "Votre réservation <strong><em>%{RESERVATION}</em></strong> a bien été validé."
|
||||
your_reserved_slots: "Les créneaux que vous avez réservés sont :"
|
||||
notify_admin_reservation_validated:
|
||||
subject: "Réservation a bien été validé"
|
||||
body:
|
||||
reservation_validated_html: "<strong><em>%{RESERVABLE}</em></strong> du membre %{NAME} a bien été validé."
|
||||
reserved_slots: "Les créneaux réservés sont :"
|
||||
|
@ -123,6 +123,7 @@ Rails.application.routes.draw do
|
||||
end
|
||||
resources :slots_reservations, only: [:update] do
|
||||
put 'cancel', on: :member
|
||||
put 'validate', on: :member
|
||||
end
|
||||
|
||||
resources :events do
|
||||
|
@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# add validated_at to slots_reservations
|
||||
class AddValidatedAtToSlotsReservations < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :slots_reservations, :validated_at, :datetime
|
||||
end
|
||||
end
|
@ -3272,7 +3272,8 @@ CREATE TABLE public.slots_reservations (
|
||||
ex_start_at timestamp without time zone,
|
||||
ex_end_at timestamp without time zone,
|
||||
canceled_at timestamp without time zone,
|
||||
offered boolean DEFAULT false
|
||||
offered boolean DEFAULT false,
|
||||
validated_at timestamp(6) without time zone
|
||||
);
|
||||
|
||||
|
||||
@ -9062,9 +9063,10 @@ INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20230524080448'),
|
||||
('20230524083558'),
|
||||
('20230524110215');
|
||||
('20230525101006');
|
||||
('20230525101006'),
|
||||
('20230626122844'),
|
||||
('20230626122947');
|
||||
('20230612123250');
|
||||
('20230626103314');
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user