diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb index ac1d98b78..44188807e 100644 --- a/app/controllers/api/children_controller.rb +++ b/app/controllers/api/children_controller.rb @@ -10,7 +10,7 @@ class API::ChildrenController < API::APIController authorize Child user_id = current_user.id user_id = params[:user_id] if current_user.privileged? && params[:user_id] - @children = Child.where(user_id: user_id).includes(:supporting_document_files).order(:created_at) + @children = Child.where(user_id: user_id).where('birthday >= ?', 18.years.ago).includes(:supporting_document_files).order(:created_at) end def show diff --git a/app/controllers/api/slots_reservations_controller.rb b/app/controllers/api/slots_reservations_controller.rb index c0f002f94..00a355735 100644 --- a/app/controllers/api/slots_reservations_controller.rb +++ b/app/controllers/api/slots_reservations_controller.rb @@ -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 validate] + before_action :set_slots_reservation, only: %i[update cancel validate invalidate] respond_to :json def update @@ -28,6 +28,11 @@ class API::SlotsReservationsController < API::APIController SlotsReservationsService.validate(@slot_reservation) end + def invalidate + authorize @slot_reservation + SlotsReservationsService.invalidate(@slot_reservation) + end + private def set_slots_reservation diff --git a/app/frontend/src/javascript/components/events/event-form.tsx b/app/frontend/src/javascript/components/events/event-form.tsx index 209f17553..1013bb0b2 100644 --- a/app/frontend/src/javascript/components/events/event-form.tsx +++ b/app/frontend/src/javascript/components/events/event-form.tsx @@ -100,7 +100,7 @@ export const EventForm: React.FC = ({ action, event, onError, on * Callback triggered when the user validates the machine form: handle create or update */ const onSubmit: SubmitHandler = (data: Event) => { - if (data.pre_registration_end_date.toString() === 'Invalid Date') { + if (data.pre_registration_end_date?.toString() === 'Invalid Date') { data.pre_registration_end_date = null; } if (action === 'update') { diff --git a/app/frontend/src/javascript/components/events/event-reservation-item.tsx b/app/frontend/src/javascript/components/events/event-reservation-item.tsx index d9cd2ca4c..12b619fae 100644 --- a/app/frontend/src/javascript/components/events/event-reservation-item.tsx +++ b/app/frontend/src/javascript/components/events/event-reservation-item.tsx @@ -44,9 +44,9 @@ export const EventReservationItem: React.FC = ({ rese * Return the pre-registration status */ const preRegistrationStatus = () => { - if (!reservation.validated_at && !reservation.canceled_at && !reservation.is_paid) { + if (!reservation.is_valid && !reservation.canceled_at && !reservation.is_paid) { return t('app.logged.event_reservation_item.in_the_process_of_validation'); - } else if (reservation.validated_at && !reservation.canceled_at && !reservation.is_paid) { + } else if (reservation.is_valid && !reservation.canceled_at && !reservation.is_paid) { return t('app.logged.event_reservation_item.settle_your_payment'); } else if (reservation.is_paid && !reservation.canceled_at) { return t('app.logged.event_reservation_item.paid'); diff --git a/app/frontend/src/javascript/controllers/admin/events.js b/app/frontend/src/javascript/controllers/admin/events.js index ebc30999b..d1288f4f2 100644 --- a/app/frontend/src/javascript/controllers/admin/events.js +++ b/app/frontend/src/javascript/controllers/admin/events.js @@ -458,7 +458,7 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope', * @returns {boolean} */ $scope.isValidated = function (reservation) { - return !!(reservation.slots_reservations_attributes[0].validated_at); + return reservation.slots_reservations_attributes[0].is_valid === true || reservation.slots_reservations_attributes[0].is_valid === 'true'; }; /** @@ -466,25 +466,30 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope', * @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')); - }); + 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].is_valid = true; + }, () => { + growl.warning(_t('app.admin.event_reservations.validation_failed')); + }); + }; + + /** + * Callback to invalidate a reservation + * @param reservation {Reservation} + */ + $scope.invalidateReservation = function (reservation) { + SlotsReservation.invalidate({ + id: reservation.slots_reservations_attributes[0].id + }, () => { // successfully validated + growl.success(_t('app.admin.event_reservations.reservation_was_successfully_invalidated')); + const index = $scope.reservations.indexOf(reservation); + $scope.reservations[index].slots_reservations_attributes[0].is_valid = false; + }, () => { + growl.warning(_t('app.admin.event_reservations.invalidation_failed')); }); }; @@ -511,6 +516,9 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope', templateUrl: '/admin/events/pay_reservation_modal.html', size: 'sm', resolve: { + event () { + return $scope.event; + }, reservation () { return reservation; }, @@ -524,8 +532,10 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope', return mkCartItems(reservation); } }, - controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t', 'Reservation', - function ($scope, $uibModalInstance, reservation, price, wallet, cartItems, helpers, $filter, _t, Reservation) { + controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t', 'Reservation', 'event', + function ($scope, $uibModalInstance, reservation, price, wallet, cartItems, helpers, $filter, _t, Reservation, event) { + $scope.event = event; + // User's wallet amount $scope.wallet = wallet; @@ -609,7 +619,11 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope', if (r.id === reservation.id) { return reservation; } - growl.success(_t('app.admin.event_reservations.reservation_was_successfully_paid')); + if ($scope.event.amount === 0) { + growl.success(_t('app.admin.event_reservations.reservation_was_successfully_present')); + } else { + growl.success(_t('app.admin.event_reservations.reservation_was_successfully_paid')); + } return r; }); }, function () { diff --git a/app/frontend/src/javascript/controllers/events.js.erb b/app/frontend/src/javascript/controllers/events.js.erb index 3fbcd3fdf..eceaacc9f 100644 --- a/app/frontend/src/javascript/controllers/events.js.erb +++ b/app/frontend/src/javascript/controllers/events.js.erb @@ -644,7 +644,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' if (!user.booked) { return false; } - if ($scope.enableChildValidationRequired && user.booked.type === 'Child' && !user.booked.validatedAt) { + if ($scope.enableChildValidationRequired && user.booked.type === 'Child' && !user.booked.validated_at) { return false; } } @@ -675,9 +675,10 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' // get the current user's reservations into $scope.reservations if ($scope.currentUser) { - getReservations($scope.event.id, 'Event', $scope.currentUser.id); - getChildren($scope.currentUser.id).then(function (children) { - updateNbReservePlaces(); + getReservations($scope.event.id, 'Event', $scope.currentUser.id).then(function (reservations) { + getChildren($scope.currentUser.id).then(function (children) { + updateNbReservePlaces(); + }); }); } @@ -696,7 +697,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' * @param user_id {number} the user's id (current or managed) */ const getReservations = function (reservable_id, reservable_type, user_id) { - Reservation.query({ + return Reservation.query({ reservable_id, reservable_type, user_id @@ -727,6 +728,14 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' } } } + for (const r of $scope.reservations) { + for (const user of r.booking_users_attributes) { + const key = user.booked_type === 'User' ? `user_${user.booked_id}` : `child_${user.booked_id}`; + if (key === userKey) { + return true; + } + } + } return false; }; @@ -748,7 +757,7 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' name: child.first_name + ' ' + child.last_name, id: child.id, type: 'Child', - validatedAt: child.validated_at, + validated_at: child.validated_at, birthday: child.birthday }); } @@ -761,7 +770,10 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' */ const updateNbReservePlaces = function () { if ($scope.event.event_type === 'family') { - const maxPlaces = $scope.children.length + 1; + const reservedPlaces = $scope.reservations.reduce((sum, reservation) => { + return sum + reservation.booking_users_attributes.length; + }, 0); + const maxPlaces = $scope.children.length + 1 - reservedPlaces; if ($scope.event.nb_free_places > maxPlaces) { $scope.reserve.nbPlaces.normal = __range__(0, maxPlaces, true); for (let evt_px_cat of Array.from($scope.event.event_price_categories_attributes)) { @@ -976,11 +988,11 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', ' Reservation.get({ id: invoice.main_object.id }, function (reservation) { $scope.event.nb_free_places = $scope.event.nb_free_places - reservation.total_booked_seats; $scope.reservations.push(reservation); + resetEventReserve(); + updateNbReservePlaces(); + $scope.reserveSuccess = true; + $scope.coupon.applied = null; }); - resetEventReserve(); - updateNbReservePlaces(); - $scope.reserveSuccess = true; - $scope.coupon.applied = null; if ($scope.currentUser.role === 'admin') { return $scope.ctrl.member = null; } diff --git a/app/frontend/src/javascript/models/reservation.ts b/app/frontend/src/javascript/models/reservation.ts index f5abfe7b9..7f2d7d415 100644 --- a/app/frontend/src/javascript/models/reservation.ts +++ b/app/frontend/src/javascript/models/reservation.ts @@ -70,7 +70,8 @@ export interface Reservation { event_type?: string, event_title?: string, event_pre_registration?: boolean - validated_at?: TDateISO, + canceled_at?: TDateISO, + is_valid?: boolean, is_paid?: boolean, } diff --git a/app/frontend/src/javascript/services/slots_reservation.js b/app/frontend/src/javascript/services/slots_reservation.js index ed833cbb5..c6d59c9cf 100644 --- a/app/frontend/src/javascript/services/slots_reservation.js +++ b/app/frontend/src/javascript/services/slots_reservation.js @@ -13,6 +13,10 @@ Application.Services.factory('SlotsReservation', ['$resource', function ($resour validate: { method: 'PUT', url: '/api/slots_reservations/:id/validate' + }, + invalidate: { + method: 'PUT', + url: '/api/slots_reservations/:id/invalidate' } } ); diff --git a/app/frontend/templates/admin/events/pay_reservation_modal.html b/app/frontend/templates/admin/events/pay_reservation_modal.html index 766d1e68f..c1866fe17 100644 --- a/app/frontend/templates/admin/events/pay_reservation_modal.html +++ b/app/frontend/templates/admin/events/pay_reservation_modal.html @@ -1,45 +1,52 @@