Skip to content

Commit

Permalink
Block negative credit orders (#779)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilco375 authored Sep 29, 2022
1 parent 86a3e22 commit d45d9d3
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 29 deletions.
9 changes: 3 additions & 6 deletions app/controllers/activities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,9 @@ def create_invoices
def users_hash
users_credits = User.calculate_credits
User.active.order(:name).map do |user|
hash = user.attributes
hash[:minor] = user.minor
hash[:insufficient_credit] = user.insufficient_credit
hash[:avatar_thumb_or_default_url] = user.avatar_thumb_or_default_url
hash[:credit] = users_credits.fetch(user.id, 0)
hash
user.current_activity = @activity
user.as_json(methods: %i[avatar_thumb_or_default_url minor insufficient_credit can_order])
.merge(credit: users_credits.fetch(user.id, 0))
end
end

Expand Down
4 changes: 3 additions & 1 deletion app/controllers/credit_mutations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def create # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
if @mutation.save
NewCreditMutationNotificationJob.perform_later(@mutation) if Rails.env.production? || Rails.env.staging?
format.html { redirect_to which_redirect?, flash: { success: 'Inleg of mutatie aangemaakt' } }
format.json { render json: @mutation, include: { user: { methods: %i[credit avatar_thumb_or_default_url] } } }
format.json do
render json: @mutation, include: { user: { methods: %i[credit avatar_thumb_or_default_url minor insufficient_credit can_order] } }
end

else
format.html { redirect_to which_redirect?, flash: { error: @mutation.errors.full_messages.join(', ') } }
Expand Down
10 changes: 6 additions & 4 deletions app/controllers/orders_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ def index
render json: @orders.to_json(proper_json)
end

def create
def create # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@order = Order.new(permitted_attributes.merge(created_by: current_user))
authorize @order

if @order.save
render json: Order.includes(
order_data = Order.includes(
:order_rows, user: { orders: :order_rows }
).find(@order.id).to_json(include: json_includes)
).find(@order.id)
order_data.user.current_activity = order_data.activity unless order_data.user.nil?
render json: order_data.as_json(include: json_includes)
else
render json: @order.errors, status: :unprocessable_entity
end
Expand Down Expand Up @@ -69,7 +71,7 @@ def proper_json
end

def json_includes
{ user: { methods: %i[credit avatar_thumb_or_default_url minor insufficient_credit] },
{ user: { methods: %i[credit avatar_thumb_or_default_url minor insufficient_credit can_order] },
activity: { only: %i[id title] },
order_rows: { only: [:id, :product_count, { product: { only: %i[id name credit] } }] } }
end
Expand Down
23 changes: 14 additions & 9 deletions app/javascript/packs/order_screen.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ document.addEventListener('turbolinks:load', () => {
},

setUser(user = null) {
this.orderRows = [];
if (this.selectedUser === null || user === null || this.selectedUser.id != user.id) {
this.orderRows = [];
}

this.payWithCash = false;
this.payWithPin = false;
this.selectedUser = user;
Expand Down Expand Up @@ -103,11 +106,11 @@ document.addEventListener('turbolinks:load', () => {
orderRow.amount++;
},

maybeConfirmOrder(e) {
if (this.selectedUser && this.selectedUser.insufficient_credit) {
this.$root.$emit('bv::show::modal', 'insufficient-credit-modal', e.target);
} else {
maybeConfirmOrder() {
if (!this.selectedUser || this.selectedUser.can_order) {
this.confirmOrder();
} else {
this.$refs.cannotOrderModal.show();
}
},

Expand Down Expand Up @@ -160,7 +163,6 @@ document.addEventListener('turbolinks:load', () => {

if (user) {
this.$set(this.users, this.users.indexOf(this.selectedUser), response.body.user);
this.$emit('updateusers');
}

this.sendFlash('Bestelling geplaatst.', additionalInfo, 'success');
Expand Down Expand Up @@ -200,9 +202,8 @@ document.addEventListener('turbolinks:load', () => {
}
}).then((response) => {
this.$set(this.users, this.users.indexOf(this.selectedUser), response.body.user);
this.$emit('updateusers');

if(!this.keepUserSelected) {
if(!this.keepUserSelected && this.orderRows.length === 0){
this.setUser(null);
} else {
// re-set user to update credit
Expand Down Expand Up @@ -266,7 +267,11 @@ document.addEventListener('turbolinks:load', () => {
},

showOrderWarning() {
return this.showInsufficientCreditWarning || this.showAgeWarning;
return this.showCannotOrderWarning || this.showInsufficientCreditWarning || this.showAgeWarning;
},

showCannotOrderWarning() {
return this.selectedUser && !this.selectedUser.can_order;
},

showInsufficientCreditWarning() {
Expand Down
5 changes: 5 additions & 0 deletions app/models/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ class Order < ApplicationRecord
validate :user_or_cash_or_pin
validate :activity_not_locked

before_create :can_user_create_order?
before_destroy -> { throw(:abort) }

scope :orders_for, (->(user) { where(user: user) })

def can_user_create_order?
throw(:abort) unless user.nil? || user.can_order(activity)
end

def order_total
@sum ||= order_rows.sum('product_count * price_per_product')
end
Expand Down
13 changes: 12 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class User < ApplicationRecord
scope :inactive, (-> { where(deactivated: true) })
scope :treasurer, (-> { joins(:roles).merge(Role.treasurer) })

attr_accessor :current_activity

def credit
credit_mutations.sum('amount') - order_rows.sum('product_count * price_per_product')
end
Expand All @@ -43,7 +45,16 @@ def minor
end

def insufficient_credit
provider == 'amber_oauth2' and credit < -5
provider == 'amber_oauth2' and credit.negative?
end

def can_order(activity = nil)
activity ||= current_activity
if activity.nil?
!insufficient_credit
else
!insufficient_credit or activity.orders.select { |order| order.user_id == id }.any?
end
end

def treasurer?
Expand Down
22 changes: 14 additions & 8 deletions app/views/activities/order_screen.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,14 @@
</template>
</b-modal>

<b-modal id="insufficient-credit-modal" ref="creditMutationModal" title="Negatief saldo" @ok="confirmOrder">
<b-modal id="cannot-order-modal" ref="cannotOrderModal" title="Negatief saldo" @ok="confirmOrder">
<div class="modal-body" v-if="selectedUser">
{{selectedUser.name}} staat <b class="text-danger">{{doubleToCurrency(-selectedUser.credit)}}</b> negatief! Vraag de gebruiker om zijn of haar saldo op te waarderen via iDEAL of contante inleg.
{{selectedUser.name}} staat <b class="text-danger">{{doubleToCurrency(-selectedUser.credit)}}</b> negatief en moet eerst inleggen om te kunnen bestellen!
Vraag de gebruiker om zijn of haar saldo op te waarderen via iDEAL of contante inleg.
</div>
<template #modal-footer="{ ok, cancel }">
<button aria-label="Close" class="btn btn-secondary mr-1" @click="cancel()" type="button">
Annuleren
</button>
<button class="btn btn-warning" :disabled="orderConfirmButtonDisabled" @click="ok()" type="button">
Bestelling toch plaatsen
Oké
</button>
</template>
</b-modal>
Expand Down Expand Up @@ -184,15 +182,23 @@
</div>

<button @click="maybeConfirmOrder" class="btn btn-block btn-lg btn-success"
:class="{ 'btn-warning' : showOrderWarning }"
:class="{ 'btn-warning' : showOrderWarning && !showCannotOrderWarning, 'btn-danger' : showCannotOrderWarning }"
:disabled="orderConfirmButtonDisabled"
v-if="!payWithPin || !<%= @sumup_enabled.to_s %> || !isMobile">
<span class="text-uppercase">
Bestelling plaatsen
</span>
<br/>
<small v-if="showOrderWarning">
<span v-if="showInsufficientCreditWarning" class="d-block">
<span v-if="showCannotOrderWarning" class="d-block">
<span>
<%= fa_icon 'exclamation-triangle ' %>
</span>
<span>
{{selectedUser.name}} staat <b>{{doubleToCurrency(-selectedUser.credit)}}</b> negatief en moet eerst inleggen!
</span>
</span>
<span v-else-if="showInsufficientCreditWarning" class="d-block">
<span>
<%= fa_icon 'exclamation-triangle ' %>
</span>
Expand Down
48 changes: 48 additions & 0 deletions spec/models/order_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,54 @@
it { expect(count.find { |item| item[:category] == 'wine' }[:amount]).to eq 9 }
end

describe '#create' do
let(:activity) { create(:activity) }

context 'when no user' do
let(:order) { build(:order, paid_with_cash: true, activity: activity) }

it { expect(order.save).to be true }
end

context 'when non-amber user without credit' do
let(:user) { create(:user) }
let(:order) { build(:order, user: user, activity: activity) }

before do
create(:credit_mutation, user: user, amount: -1)
end

it { expect(order.save).to be true }
end

context 'when amber user' do
let(:user) { create(:user, provider: 'amber_oauth2') }
let(:order) { build(:order, user: user, activity: activity) }

context 'with credit' do
# Note that a credit of 0 counts as non-negative credit
it { expect(order.save).to be true }
end

context 'without credit with activity order' do
before do
create(:order, user: user, activity: activity)
create(:credit_mutation, user: user, amount: -1)
end

it { expect(order.save).to be true }
end

context 'without credit without activity order' do
before do
create(:credit_mutation, user: user, amount: -1)
end

it { expect(order.save).to be false }
end
end
end

describe '#destroy' do
let(:order) { create(:order) }

Expand Down

0 comments on commit d45d9d3

Please sign in to comment.