mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
Merge branch 'wallet_avoir' into dev
This commit is contained in:
commit
76c1412177
@ -423,11 +423,40 @@ Application.Controllers.controller "EditMemberController", ["$scope", "$state",
|
||||
templateUrl: '<%= asset_path "wallet/credit_modal.html" %>'
|
||||
controller: ['$scope', '$uibModalInstance', 'Wallet', ($scope, $uibModalInstance, Wallet) ->
|
||||
|
||||
# default: do not generate a refund invoice
|
||||
$scope.generate_avoir = false
|
||||
|
||||
# date of the generated refund invoice
|
||||
$scope.avoir_date = null
|
||||
|
||||
# optional description shown on the refund invoice
|
||||
$scope.description = ''
|
||||
|
||||
# default configuration for the avoir date selector widget
|
||||
$scope.datePicker =
|
||||
format: Fablab.uibDateFormat
|
||||
opened: false
|
||||
options:
|
||||
startingDay: Fablab.weekStartingDay
|
||||
|
||||
##
|
||||
# Callback to open/close the date picker
|
||||
##
|
||||
$scope.toggleDatePicker = ($event) ->
|
||||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
$scope.datePicker.opened = !$scope.datePicker.opened
|
||||
|
||||
##
|
||||
# Modal dialog validation callback
|
||||
##
|
||||
$scope.ok = ->
|
||||
Wallet.credit { id: wallet.id }, { amount: $scope.amount }, (_wallet)->
|
||||
Wallet.credit { id: wallet.id },
|
||||
amount: $scope.amount
|
||||
avoir: $scope.generate_avoir
|
||||
avoir_date: $scope.avoir_date
|
||||
avoir_description: $scope.description
|
||||
, (_wallet)->
|
||||
|
||||
growl.success(_t('wallet_credit_successfully'))
|
||||
$uibModalInstance.close(_wallet)
|
||||
|
@ -3,11 +3,18 @@
|
||||
<h1 translate>{{ 'credit_title' }}</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="alert alert-warning m-b-md m-b-sm">
|
||||
<i class="fa fa-warning m-sm inline" aria-hidden="true"></i>
|
||||
<div class="inline pull-right width-90 m-t-n-xs" translate>{{ 'warning_uneditable_credit' }}</div>
|
||||
</div>
|
||||
|
||||
<form name="walletForm" ng-class="{'has-error': walletForm.amount.$dirty && walletForm.amount.$invalid}">
|
||||
<div class="text-right amountGroup m-r-md">
|
||||
<span class="beforeAmount" translate>{{ 'credit_label' }}</span>
|
||||
<label for="amount" class="beforeAmount" translate>{{ 'credit_label' }}</label>
|
||||
<input class="form-control m-l"
|
||||
type="number"
|
||||
id="amount"
|
||||
name="amount"
|
||||
ng-model="amount"
|
||||
required min="1"
|
||||
@ -16,10 +23,11 @@
|
||||
<span class="help-block" ng-show="walletForm.amount.$dirty && walletForm.amount.$error.required" translate>{{'amount_is_required'}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount.$dirty && walletForm.amount.$error.min">{{ 'amount_minimum_1' | translate }} {{currencySymbol}}.</span>
|
||||
</div>
|
||||
<div class="text-right amountGroup m-t m-r-md">
|
||||
<span class="beforeAmount" translate>{{ 'confirm_credit_label' }}</span>
|
||||
<div class="text-right amountGroup m-t m-r-md" ng-class="{'has-error': walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$invalid }">
|
||||
<label for="amount_confirm" class="beforeAmount" translate>{{ 'confirm_credit_label' }}</label>
|
||||
<input class="form-control m-l"
|
||||
type="number"
|
||||
id="amount_confirm"
|
||||
name="amount_confirm"
|
||||
ng-model="amount_confirm"
|
||||
required
|
||||
@ -30,12 +38,53 @@
|
||||
<span class="help-block" ng-show="walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$error.required" translate>{{'amount_confirm_is_required'}}</span>
|
||||
<span class="help-block" ng-show="walletForm.amount_confirm.$dirty && walletForm.amount_confirm.$error.pattern">{{ 'amount_confirm_does_not_match' | translate }}</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-warning m-t-md m-b-sm">
|
||||
<i class="fa fa-warning m-sm inline" aria-hidden="true"></i>
|
||||
<div class="inline pull-right width-90 m-t-n-xs" translate>{{ 'warning_uneditable_credit' }}</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="text-right m-t">
|
||||
<label for="generate_avoir" translate>{{ 'generate_a_refund_invoice' }}</label>
|
||||
<div class="inline m-l">
|
||||
<input bs-switch
|
||||
ng-model="generate_avoir"
|
||||
id="generate_avoir"
|
||||
name="generate_avoir"
|
||||
type="checkbox"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="generate_avoir">
|
||||
<div class="m-t" ng-class="{'has-error': walletForm.avoir_date.$dirty && walletForm.avoir_date.$invalid }">
|
||||
<label for="avoir_date" translate>{{ 'creation_date_for_the_refund' }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="avoir_date"
|
||||
name="avoir_date"
|
||||
ng-model="avoir_date"
|
||||
uib-datepicker-popup="{{datePicker.format}}"
|
||||
datepicker-options="datePicker.options"
|
||||
is-open="datePicker.opened"
|
||||
placeholder="{{datePicker.format}}"
|
||||
ng-click="toggleDatePicker($event)"
|
||||
ng-required="generate_avoir"/>
|
||||
</div>
|
||||
<span class="help-block" ng-show="walletForm.avoir_date.$dirty && walletForm.avoir_date.$error.required" translate>{{ 'creation_date_is_required' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="m-t">
|
||||
<label for="description" translate>{{ 'description_(optional)' }}</label>
|
||||
<p translate>{{ 'will_appear_on_the_refund_invoice' }}</p>
|
||||
<textarea class="form-control m-t-sm"
|
||||
id="description"
|
||||
name="description"
|
||||
ng-model="description">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -57,7 +57,7 @@ class API::InvoicesController < API::ApiController
|
||||
|
||||
end
|
||||
|
||||
# only for create avoir
|
||||
# only for create refund invoices (avoir)
|
||||
def create
|
||||
authorize Invoice
|
||||
invoice = Invoice.only_invoice.find(avoir_params[:invoice_id])
|
||||
|
@ -14,13 +14,22 @@ class API::WalletController < API::ApiController
|
||||
end
|
||||
|
||||
def credit
|
||||
@wallet = Wallet.find(params[:id])
|
||||
@wallet = Wallet.find(credit_params[:id])
|
||||
authorize @wallet
|
||||
service = WalletService.new(user: current_user, wallet: @wallet)
|
||||
if service.credit(params[:amount].to_f)
|
||||
transaction = service.credit(credit_params[:amount].to_f)
|
||||
if transaction
|
||||
if credit_params[:avoir]
|
||||
service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description])
|
||||
end
|
||||
render :show
|
||||
else
|
||||
head 422
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def credit_params
|
||||
params.permit(:id, :amount, :avoir, :avoir_date, :avoir_description)
|
||||
end
|
||||
end
|
||||
|
@ -37,10 +37,12 @@ module PDF
|
||||
if Setting.find_by({name: 'invoice_code-active'}).value == 'true'
|
||||
text I18n.t('invoices.code', CODE:Setting.find_by({name: 'invoice_code-value'}).value), :leading => 3
|
||||
end
|
||||
if invoice.is_a?(Avoir)
|
||||
text I18n.t('invoices.order_number', NUMBER:invoice.invoice.order_number), :leading => 3
|
||||
else
|
||||
text I18n.t('invoices.order_number', NUMBER:invoice.order_number), :leading => 3
|
||||
if invoice.invoiced_type != WalletTransaction.name
|
||||
if invoice.is_a?(Avoir)
|
||||
text I18n.t('invoices.order_number', NUMBER:invoice.invoice.order_number), :leading => 3
|
||||
else
|
||||
text I18n.t('invoices.order_number', NUMBER:invoice.order_number), :leading => 3
|
||||
end
|
||||
end
|
||||
if invoice.is_a?(Avoir)
|
||||
text I18n.t('invoices.refund_invoice_issued_on_DATE', DATE:I18n.l(invoice.avoir_date.to_date))
|
||||
@ -68,7 +70,11 @@ module PDF
|
||||
# object
|
||||
move_down 25
|
||||
if invoice.is_a?(Avoir)
|
||||
object = I18n.t('invoices.cancellation_of_invoice_REF', REF: invoice.invoice.reference)
|
||||
if invoice.invoiced_type == WalletTransaction.name
|
||||
object = I18n.t('invoices.wallet_credit')
|
||||
else
|
||||
object = I18n.t('invoices.cancellation_of_invoice_REF', REF: invoice.invoice.reference)
|
||||
end
|
||||
else
|
||||
case invoice.invoiced_type
|
||||
when 'Reservation'
|
||||
@ -115,7 +121,7 @@ module PDF
|
||||
|
||||
|
||||
else ### Reservation
|
||||
case invoice.invoiced.reservable_type
|
||||
case invoice.invoiced.try(:reservable_type)
|
||||
### Machine reservation
|
||||
when 'Machine'
|
||||
details += I18n.t('invoices.machine_reservation_DESCRIPTION', DESCRIPTION: item.description)
|
||||
@ -130,6 +136,9 @@ module PDF
|
||||
invoice.invoiced.tickets.each do |t|
|
||||
details += "\n "+I18n.t('invoices.other_rate_ticket', count: t.booked, NAME: t.event_price_category.price_category.name)
|
||||
end
|
||||
### wallet credit
|
||||
when nil
|
||||
details = item.description
|
||||
|
||||
### Other cases (not expected)
|
||||
else
|
||||
|
@ -21,7 +21,7 @@ class WalletService
|
||||
end
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
return false
|
||||
false
|
||||
end
|
||||
|
||||
## debit an amount to wallet, if debit success then return a wallet transaction
|
||||
@ -35,6 +35,27 @@ class WalletService
|
||||
end
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
return false
|
||||
false
|
||||
end
|
||||
|
||||
## create a refund invoice associated with the given wallet transaction
|
||||
def create_avoir(wallet_transaction, avoir_date, description)
|
||||
avoir = Avoir.new
|
||||
avoir.type = 'Avoir'
|
||||
avoir.invoiced = wallet_transaction
|
||||
avoir.avoir_date = avoir_date
|
||||
avoir.created_at = avoir_date
|
||||
avoir.description = description
|
||||
avoir.avoir_mode = 'wallet'
|
||||
avoir.subscription_to_expire = false
|
||||
avoir.user_id = wallet_transaction.wallet.user_id
|
||||
avoir.total = wallet_transaction.amount * 100.0
|
||||
avoir.save!
|
||||
|
||||
ii = InvoiceItem.new
|
||||
ii.amount = wallet_transaction.amount * 100.0
|
||||
ii.description = I18n.t('invoices.wallet_credit')
|
||||
ii.invoice = avoir
|
||||
ii.save!
|
||||
end
|
||||
end
|
||||
|
@ -324,6 +324,11 @@ en:
|
||||
credit_title: 'Credit wallet'
|
||||
credit_label: 'Set the amount to be credited'
|
||||
confirm_credit_label: 'Confirm the amount to be credited'
|
||||
generate_a_refund_invoice: "Generate a refund invoice"
|
||||
creation_date_for_the_refund: "Creation date for the refund"
|
||||
creation_date_is_required: "Creation date is required."
|
||||
description_(optional): "Description (optional):"
|
||||
will_appear_on_the_refund_invoice: "Will appear on the refund invoice."
|
||||
to_credit: 'Credit'
|
||||
wallet_credit_successfully: "Wallet of user is credited successfully."
|
||||
a_problem_occurred_for_wallet_credit: "A problem is occurred while taking the credit of wallet"
|
||||
|
@ -324,6 +324,11 @@ fr:
|
||||
credit_title: 'Créditer le porte-monnaie'
|
||||
credit_label: 'Indiquez le montant à créditer'
|
||||
confirm_credit_label: 'Confirmez le montant à créditer'
|
||||
generate_a_refund_invoice: "Générer une facture d'avoir"
|
||||
creation_date_for_the_refund: "Date d'émission de l'avoir"
|
||||
creation_date_is_required: "La date d'émission est requise."
|
||||
description_(optional): "Description (optionnelle) :"
|
||||
will_appear_on_the_refund_invoice: "Apparaîtra sur la facture de remboursement."
|
||||
to_credit: 'Créditer'
|
||||
wallet_credit_successfully: "Le porte-monnaie de l'utilisateur a été chargé avec succès."
|
||||
a_problem_occurred_for_wallet_credit: "Un problème est survenu lors du chargement du porte-monnaie."
|
||||
|
@ -72,6 +72,7 @@ en:
|
||||
order_number: "Order #: %{NUMBER}"
|
||||
invoice_issued_on_DATE: "Invoice issued on %{DATE}"
|
||||
refund_invoice_issued_on_DATE: "Refund invoice issued on %{DATE}"
|
||||
wallet_credit: "Wallet credit"
|
||||
cancellation_of_invoice_REF: "Cancellation of invoice %{REF}"
|
||||
reservation_of_USER_on_DATE_at_TIME: "Reservation of %{USER} on %{DATE} at %{TIME}"
|
||||
cancellation: "Cancellation"
|
||||
|
@ -72,6 +72,7 @@ fr:
|
||||
order_number: "N° Commande : %{NUMBER}"
|
||||
invoice_issued_on_DATE: "Facture éditée le %{DATE}"
|
||||
refund_invoice_issued_on_DATE: "Avoir édité le %{DATE}"
|
||||
wallet_credit: "Crédit du porte-monnaie"
|
||||
cancellation_of_invoice_REF: "Annulation de la facture %{REF}"
|
||||
reservation_of_USER_on_DATE_at_TIME: "Réservation de %{USER} le %{DATE} à %{TIME}"
|
||||
cancellation: "Annulation"
|
||||
|
82
test/integration/prices/compute_test.rb
Normal file
82
test/integration/prices/compute_test.rb
Normal file
@ -0,0 +1,82 @@
|
||||
module Events
|
||||
class AsAdminTest < ActionDispatch::IntegrationTest
|
||||
|
||||
setup do
|
||||
admin = User.with_role(:admin).first
|
||||
login_as(admin, scope: :user)
|
||||
end
|
||||
|
||||
test 'compute price for a simple training' do
|
||||
user = User.find_by(username: 'jdupond')
|
||||
availability = Availability.find(2)
|
||||
printer_training = availability.trainings.first
|
||||
|
||||
post '/api/prices/compute',
|
||||
{
|
||||
reservation: {
|
||||
user_id: user.id,
|
||||
reservable_id: printer_training.id,
|
||||
reservable_type: printer_training.class.name,
|
||||
slots_attributes: [
|
||||
{
|
||||
availability_id: availability.id,
|
||||
end_at: availability.end_at,
|
||||
offered: false,
|
||||
start_at: availability.start_at
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json,
|
||||
default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 200, response.status, response.body
|
||||
assert_equal Mime::JSON, response.content_type
|
||||
|
||||
# Check the price was computed correctly
|
||||
price = json_response(response.body)
|
||||
assert_equal (printer_training.trainings_pricings.where(group_id: user.group_id).first.amount / 100.0), price[:price], 'Computed price did not match training price'
|
||||
end
|
||||
|
||||
|
||||
test 'compute price for a machine reservation with an offered slot and a subscription' do
|
||||
user = User.find_by(username: 'jdupond')
|
||||
availability = Availability.find(3)
|
||||
laser = availability.machines.where(id: 1).first
|
||||
plan = Plan.where(group_id: user.group_id, interval: 'month').first
|
||||
|
||||
post '/api/prices/compute',
|
||||
{
|
||||
reservation: {
|
||||
user_id: user.id,
|
||||
reservable_id: laser.id,
|
||||
reservable_type: laser.class.name,
|
||||
plan_id: plan.id,
|
||||
slots_attributes: [
|
||||
{
|
||||
availability_id: availability.id,
|
||||
end_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
|
||||
offered: true,
|
||||
start_at: availability.start_at.strftime('%Y-%m-%d %H:%M:%S.%9N Z')
|
||||
},
|
||||
{
|
||||
availability_id: availability.id,
|
||||
end_at: (availability.start_at + 2.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
|
||||
offered: false,
|
||||
start_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z')
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json,
|
||||
default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 200, response.status, response.body
|
||||
assert_equal Mime::JSON, response.content_type
|
||||
|
||||
# Check the event was created correctly
|
||||
price = json_response(response.body)
|
||||
assert_equal ((laser.prices.where(group_id: user.group_id, plan_id: plan.id).first.amount + plan.amount) / 100.0), price[:price], 'Computed price did not match machine + subscription price'
|
||||
end
|
||||
end
|
||||
end
|
@ -1,6 +1,14 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CouponTest < ActiveSupport::TestCase
|
||||
|
||||
test 'valid coupon with percentage' do
|
||||
c = Coupon.new({name: 'Hot deals', code: 'HOT15', percent_off: 15, validity_per_user: 'once', valid_until: (Time.now + 2.weeks), max_usages: 100, active: true})
|
||||
assert c.valid?
|
||||
assert_equal 'active', c.status, 'Invalid coupon status'
|
||||
assert_equal 'percent_off', c.type, 'Invalid coupon type'
|
||||
end
|
||||
|
||||
test 'coupon must have a valid percentage' do
|
||||
c = Coupon.new({name: 'Amazing deal', code: 'DISCOUNT', percent_off: 200, validity_per_user: 'once'})
|
||||
assert c.invalid?
|
||||
|
Loading…
x
Reference in New Issue
Block a user