1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-28 09:24:24 +01:00

(feat) admin can confirm the payment of pre-registration reservation

This commit is contained in:
Du Peng 2023-06-27 17:50:47 +02:00
parent 172f09661f
commit a60e587187
10 changed files with 198 additions and 19 deletions

View File

@ -4,7 +4,7 @@
# Reservations are used for Training, Machine, Space and Event
class API::ReservationsController < API::APIController
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
def index
@ -34,6 +34,16 @@ class API::ReservationsController < API::APIController
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
def set_reservation

View File

@ -503,7 +503,7 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
};
$scope.payReservation = function (reservation) {
$uibModal.open({
const modalInstance = $uibModal.open({
templateUrl: '/admin/events/pay_reservation_modal.html',
size: 'sm',
resolve: {
@ -520,13 +520,13 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
return mkCartItems(reservation);
}
},
controller: ['$scope', '$uibModalInstance', 'reservation', 'price', 'wallet', 'cartItems', 'helpers', '$filter', '_t',
function ($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, Reservation) {
// User's wallet amount
$scope.wallet = wallet;
// Price
$scope.price = price.price;
$scope.price = price;
// Cart items
$scope.cartItems = cartItems;
@ -539,18 +539,77 @@ Application.Controllers.controller('ShowEventReservationsController', ['$scope',
$scope.coupon = { applied: null };
$scope.offered = false;
$scope.payment = false;
// Button label
if ($scope.amount > 0) {
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
} else {
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) });
$scope.setValidButtonName = function () {
if ($scope.amount > 0 && !$scope.offered) {
$scope.validButtonName = _t('app.admin.event_reservations.confirm_payment_of_html', { ROLE: $scope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
} else {
$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());
});
};
}]);

View File

@ -5,6 +5,11 @@ Application.Services.factory('Reservation', ['$resource', function ($resource) {
{ id: '@id' }, {
update: {
method: 'PUT'
},
confirm_payment: {
method: 'POST',
url: '/api/reservations/confirm_payment',
isArray: false
}
}
);

View File

@ -4,15 +4,42 @@
</div>
<div class="modal-body">
<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"
cart="cartItems"
price="price"
price="price.price"
wallet="wallet"/>
</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>
<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 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>
</div>

View File

@ -46,16 +46,16 @@
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
</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="!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) && !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="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>
<button class="btn btn-default" ng-click="validateReservation(reservation)" ng-if="!isValidated(reservation) && !isCancelled(reservation) && !reservation.is_paid" translate>
{{ 'app.admin.event_reservations.validate' }}
</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' }}
</button>
</td>

View File

@ -9,4 +9,8 @@ class ReservationPolicy < ApplicationPolicy
def update?
user.admin? || user.manager? || record.user == user
end
def confirm_payment?
user.admin? || user.manager?
end
end

View 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

View File

@ -659,6 +659,8 @@ en:
validation_failed: "Validation failed."
confirm_payment: "Confirm payment"
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:
title: "Settings"
generic_text_block: "Editorial text block"

View File

@ -659,6 +659,8 @@ fr:
validation_failed: "La validation a échoué."
confirm_payment: "Confirmer le paiement"
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:
title: "Paramètres"
generic_text_block: "Bloc de texte rédactionnel"

View File

@ -68,7 +68,9 @@ Rails.application.routes.draw do
patch ':id/update_role', action: 'update_role', on: :collection
patch ':id/validate', action: 'validate', on: :collection
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
match :update_all, path: '/', via: %i[put patch], on: :collection
get 'polling', action: 'polling', on: :collection