mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-22 11:52:21 +01:00
(feat) admin can confirm the payment of pre-registration reservation
This commit is contained in:
parent
a437fc24ee
commit
9672ab2968
@ -4,7 +4,7 @@
|
|||||||
# Reservations are used for Training, Machine, Space and Event
|
# Reservations are used for Training, Machine, Space and Event
|
||||||
class API::ReservationsController < API::APIController
|
class API::ReservationsController < API::APIController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_reservation, only: %i[show update]
|
before_action :set_reservation, only: %i[show update confirm_payment]
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@ -34,6 +34,16 @@ class API::ReservationsController < API::APIController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_payment
|
||||||
|
authorize @reservation
|
||||||
|
invoice = ReservationConfirmPaymentService.new(@reservation, current_user, params[:coupon_code], params[:offered]).call
|
||||||
|
if invoice
|
||||||
|
render :show, status: :ok, location: @reservation
|
||||||
|
else
|
||||||
|
render json: @reservation.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_reservation
|
def set_reservation
|
||||||
|
@ -503,7 +503,7 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.payReservation = function (reservation) {
|
$scope.payReservation = function (reservation) {
|
||||||
$uibModal.open({
|
const modalInstance = $uibModal.open({
|
||||||
templateUrl: '/admin/events/pay_reservation_modal.html',
|
templateUrl: '/admin/events/pay_reservation_modal.html',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -520,13 +520,13 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
|||||||
return mkCartItems(reservation);
|
return mkCartItems(reservation);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t',
|
controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t', 'Reservation',
|
||||||
function ($scope, $uibModalInstance, reservation, price, wallet, cartItems, helpers, $filter, _t) {
|
function ($scope, $uibModalInstance, reservation, price, wallet, cartItems, helpers, $filter, _t, Reservation) {
|
||||||
// User's wallet amount
|
// User's wallet amount
|
||||||
$scope.wallet = wallet;
|
$scope.wallet = wallet;
|
||||||
|
|
||||||
// Price
|
// Price
|
||||||
$scope.price = price.price;
|
$scope.price = price;
|
||||||
|
|
||||||
// Cart items
|
// Cart items
|
||||||
$scope.cartItems = cartItems;
|
$scope.cartItems = cartItems;
|
||||||
@ -539,18 +539,77 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
|
|||||||
|
|
||||||
$scope.coupon = { applied: null };
|
$scope.coupon = { applied: null };
|
||||||
|
|
||||||
|
$scope.offered = false;
|
||||||
|
|
||||||
|
$scope.payment = false;
|
||||||
|
|
||||||
// Button label
|
// Button label
|
||||||
if ($scope.amount > 0) {
|
$scope.setValidButtonName = function () {
|
||||||
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
if ($scope.amount > 0 && !$scope.offered) {
|
||||||
} else {
|
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
|
||||||
if ((price.price > 0) && ($scope.walletAmount === 0)) {
|
|
||||||
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
|
|
||||||
} else {
|
} else {
|
||||||
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
$scope.validButtonName = _t('app.shared.buttons.confirm');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the total amount for the current reservation according to the previously set parameters
|
||||||
|
*/
|
||||||
|
$scope.computeEventAmount = function () {
|
||||||
|
Price.compute(mkCartItems(reservation, $scope.coupon.applied), function (res) {
|
||||||
|
$scope.price = res;
|
||||||
|
$scope.amount = helpers.getAmountToPay($scope.price.price, wallet.amount);
|
||||||
|
$scope.setValidButtonName();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback to validate the payment
|
||||||
|
$scope.ok = function () {
|
||||||
|
$scope.attempting = true;
|
||||||
|
return Reservation.confirm_payment({
|
||||||
|
id: reservation.id,
|
||||||
|
coupon_code: $scope.coupon.applied ? $scope.coupon.applied.code : null,
|
||||||
|
offered: $scope.offered
|
||||||
|
}, function (res) {
|
||||||
|
$uibModalInstance.close(res);
|
||||||
|
return $scope.attempting = true;
|
||||||
|
}
|
||||||
|
, function (response) {
|
||||||
|
$scope.alerts = [];
|
||||||
|
angular.forEach(response, function (v, k) {
|
||||||
|
angular.forEach(v, function (err) {
|
||||||
|
$scope.alerts.push({
|
||||||
|
msg: k + ': ' + err,
|
||||||
|
type: 'danger'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return $scope.attempting = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Callback to cancel the payment
|
||||||
|
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||||
|
|
||||||
|
$scope.$watch('coupon.applied', function (newValue, oldValue) {
|
||||||
|
if ((newValue !== null) || (oldValue !== null)) {
|
||||||
|
return $scope.computeEventAmount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.setValidButtonName();
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
modalInstance.result.then(function (reservation) {
|
||||||
|
$scope.reservations = $scope.reservations.map((r) => {
|
||||||
|
if (r.id === reservation.id) {
|
||||||
|
return reservation;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
}, function () {
|
||||||
|
$log.info('Pay reservation modal dismissed at: ' + new Date());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
@ -5,6 +5,11 @@ Application.Services.factory('Reservation', ['$resource', function ($resource) {
|
|||||||
{ id: '@id' }, {
|
{ id: '@id' }, {
|
||||||
update: {
|
update: {
|
||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
|
},
|
||||||
|
confirm_payment: {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/reservations/confirm_payment',
|
||||||
|
isArray: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -4,15 +4,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
<uib-alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</uib-alert>
|
||||||
<div class="row">
|
<div class="row" ng-show="!offered">
|
||||||
<wallet-info current-user="currentUser"
|
<wallet-info current-user="currentUser"
|
||||||
cart="cartItems"
|
cart="cartItems"
|
||||||
price="price"
|
price="price.price"
|
||||||
wallet="wallet"/>
|
wallet="wallet"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row m-b">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<label for="offerSlot" class="control-label m-r" translate>{{ 'app.admin.event_reservations.offer_this_reservation' }}</label>
|
||||||
|
<input bs-switch
|
||||||
|
ng-model="offered"
|
||||||
|
id="offerSlot"
|
||||||
|
type="checkbox"
|
||||||
|
class="form-control"
|
||||||
|
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||||
|
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||||
|
switch-animate="true"
|
||||||
|
ng-change="computeEventAmount()"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<coupon show="true" coupon="coupon.applied" total="price.price_without_coupon" user-id="{{reservation.user_id}}"></coupon>
|
<coupon show="true" coupon="coupon.applied" total="price.price_without_coupon" user-id="{{reservation.user_id}}"></coupon>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-sm-12">
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<input type="checkbox"
|
||||||
|
name="paymentReceived"
|
||||||
|
id="paymentReceived"
|
||||||
|
ng-model="payment" />
|
||||||
|
<label for="paymentReceived" translate>{{ 'app.admin.event_reservations.i_have_received_the_payment' }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="attempting" ng-bind-html="validButtonName"></button>
|
<button class="btn btn-info" ng-click="ok()" ng-disabled="attempting || !payment" ng-bind-html="validButtonName"></button>
|
||||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,16 +46,16 @@
|
|||||||
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="event.pre_registration">
|
<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) && !reservation.is_paid" 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="isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" 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="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>
|
<span ng-if="isCancelled(reservation)" class="v-middle badge text-base bg-event" translate="">{{ 'app.admin.event_reservations.event_status.canceled' }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="event.pre_registration">
|
<td ng-if="event.pre_registration">
|
||||||
<button class="btn btn-default" ng-click="validateReservation(reservation)" ng-if="!isValidated(reservation) && !isCancelled(reservation)" translate>
|
<button class="btn btn-default" ng-click="validateReservation(reservation)" ng-if="!isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" translate>
|
||||||
{{ 'app.admin.event_reservations.validate' }}
|
{{ 'app.admin.event_reservations.validate' }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-default" ng-click="payReservation(reservation)" ng-if="isValidated(reservation) && !isCancelled(reservation)" translate>
|
<button class="btn btn-default" ng-click="payReservation(reservation)" ng-if="isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" translate>
|
||||||
{{ 'app.admin.event_reservations.pay' }}
|
{{ 'app.admin.event_reservations.pay' }}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -9,4 +9,8 @@ class ReservationPolicy < ApplicationPolicy
|
|||||||
def update?
|
def update?
|
||||||
user.admin? || user.manager? || record.user == user
|
user.admin? || user.manager? || record.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_payment?
|
||||||
|
user.admin? || user.manager?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
68
app/services/reservation_confirm_payment_service.rb
Normal file
68
app/services/reservation_confirm_payment_service.rb
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# confirm payment of a pre-registration reservation
|
||||||
|
class ReservationConfirmPaymentService
|
||||||
|
def initialize(reservation, operator, coupon, offered)
|
||||||
|
@reservation = reservation
|
||||||
|
@operator = operator
|
||||||
|
@offered = offered
|
||||||
|
@coupon = CartItem::Coupon.new(
|
||||||
|
customer_profile: @reservation.user.invoicing_profile,
|
||||||
|
operator_profile: @operator.invoicing_profile,
|
||||||
|
coupon: Coupon.find_by(code: coupon)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def total
|
||||||
|
slots_reservations = @reservation.slots_reservations.map do |sr|
|
||||||
|
{
|
||||||
|
slot_id: sr.slot_id,
|
||||||
|
offered: @offered
|
||||||
|
}
|
||||||
|
end
|
||||||
|
event_reservation = CartItem::EventReservation.new(customer_profile: @reservation.user.invoicing_profile,
|
||||||
|
operator_profile: @operator.invoicing_profile,
|
||||||
|
event: @reservation.reservable,
|
||||||
|
cart_item_reservation_slots_attributes: slots_reservations,
|
||||||
|
normal_tickets: @reservation.nb_reserve_places,
|
||||||
|
cart_item_event_reservation_tickets_attributes: @reservation.tickets.to_a,
|
||||||
|
cart_item_event_reservation_booking_users_attributes: @reservation.booking_users.to_a)
|
||||||
|
|
||||||
|
all_elements = {
|
||||||
|
slots: @reservation.slots_reservations.map do |sr|
|
||||||
|
{ start_at: sr.slot.start_at, end_at: sr.slot.end_at, price: event_reservation.price[:amount] }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
total_amount = event_reservation.price[:amount]
|
||||||
|
|
||||||
|
coupon_info = @coupon.price(total_amount)
|
||||||
|
|
||||||
|
# return result
|
||||||
|
{
|
||||||
|
elements: all_elements,
|
||||||
|
total: coupon_info[:total_with_coupon].to_i,
|
||||||
|
before_coupon: coupon_info[:total_without_coupon].to_i,
|
||||||
|
coupon: @coupon.coupon
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
price = total
|
||||||
|
invoice = InvoicesService.create(
|
||||||
|
price,
|
||||||
|
@operator.invoicing_profile.id,
|
||||||
|
[@reservation],
|
||||||
|
@reservation.user
|
||||||
|
)
|
||||||
|
return invoice if Setting.get('prevent_invoices_zero') && price[:total].zero?
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
WalletService.debit_user_wallet(invoice, @reservation.user)
|
||||||
|
|
||||||
|
invoice.save
|
||||||
|
invoice.post_save
|
||||||
|
end
|
||||||
|
invoice
|
||||||
|
end
|
||||||
|
end
|
@ -653,6 +653,8 @@ en:
|
|||||||
validation_failed: "Validation failed."
|
validation_failed: "Validation failed."
|
||||||
confirm_payment: "Confirm payment"
|
confirm_payment: "Confirm payment"
|
||||||
confirm_payment_of_html: "{ROLE, select, admin{Cash} other{Pay}}: {AMOUNT}" #(contexte : validate a payment of $20,00)
|
confirm_payment_of_html: "{ROLE, select, admin{Cash} other{Pay}}: {AMOUNT}" #(contexte : validate a payment of $20,00)
|
||||||
|
offer_this_reservation: "I offer this reservation"
|
||||||
|
i_have_received_the_payment: "I have received the payment"
|
||||||
events_settings:
|
events_settings:
|
||||||
title: "Settings"
|
title: "Settings"
|
||||||
generic_text_block: "Editorial text block"
|
generic_text_block: "Editorial text block"
|
||||||
|
@ -653,6 +653,8 @@ fr:
|
|||||||
validation_failed: "La validation a échoué."
|
validation_failed: "La validation a échoué."
|
||||||
confirm_payment: "Confirmer le paiement"
|
confirm_payment: "Confirmer le paiement"
|
||||||
confirm_payment_of_html: "{ROLE, select, admin{Encaisser} other{Payer}} : {AMOUNT}" #(contexte : validate a payment of $20,00)
|
confirm_payment_of_html: "{ROLE, select, admin{Encaisser} other{Payer}} : {AMOUNT}" #(contexte : validate a payment of $20,00)
|
||||||
|
offer_this_reservation: "J'offre cette réservation"
|
||||||
|
i_have_received_the_payment: "J'ai reçu le paiement"
|
||||||
events_settings:
|
events_settings:
|
||||||
title: "Paramètres"
|
title: "Paramètres"
|
||||||
generic_text_block: "Bloc de texte rédactionnel"
|
generic_text_block: "Bloc de texte rédactionnel"
|
||||||
|
@ -66,7 +66,9 @@ Rails.application.routes.draw do
|
|||||||
patch ':id/update_role', action: 'update_role', on: :collection
|
patch ':id/update_role', action: 'update_role', on: :collection
|
||||||
patch ':id/validate', action: 'validate', on: :collection
|
patch ':id/validate', action: 'validate', on: :collection
|
||||||
end
|
end
|
||||||
resources :reservations, only: %i[show index update]
|
resources :reservations, only: %i[show index update] do
|
||||||
|
post :confirm_payment, on: :collection
|
||||||
|
end
|
||||||
resources :notifications, only: %i[index show update] do
|
resources :notifications, only: %i[index show update] do
|
||||||
match :update_all, path: '/', via: %i[put patch], on: :collection
|
match :update_all, path: '/', via: %i[put patch], on: :collection
|
||||||
get 'polling', action: 'polling', on: :collection
|
get 'polling', action: 'polling', on: :collection
|
||||||
|
Loading…
x
Reference in New Issue
Block a user