diff --git a/app/controllers/api/v1/user/financial_transactions_controller.rb b/app/controllers/api/v1/user/financial_transactions_controller.rb
index c042db32..947bc4aa 100644
--- a/app/controllers/api/v1/user/financial_transactions_controller.rb
+++ b/app/controllers/api/v1/user/financial_transactions_controller.rb
@@ -3,6 +3,8 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
before_action ->{ doorkeeper_authorize! 'finance:user' }
before_action :require_ordergroup
+ before_action :require_minimum_balance, only: [:create]
+ before_action -> { require_config_enabled :use_self_service }, only: [:create]
def index
render_collection search_scope
@@ -12,10 +14,20 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
render json: scope.find(params.require(:id))
end
+ def create
+ transaction_type = FinancialTransactionType.find(create_params[:financial_transaction_type_id])
+ ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user, transaction_type)
+ render json: ft
+ end
+
private
def scope
current_ordergroup.financial_transactions.includes(:user, :financial_transaction_type)
end
+ def create_params
+ params.require(:financial_transaction).permit(:amount, :financial_transaction_type_id, :note)
+ end
+
end
diff --git a/app/models/ordergroup.rb b/app/models/ordergroup.rb
index e65bd1a0..da16db07 100644
--- a/app/models/ordergroup.rb
+++ b/app/models/ordergroup.rb
@@ -90,6 +90,7 @@ class Ordergroup < Group
if t.amount < 0 && self.account_balance < 0 && self.account_balance - t.amount >= 0
Resque.enqueue(UserNotifier, FoodsoftConfig.scope, 'negative_balance', self.id, t.id)
end
+ t
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 676e8c36..07d32a4f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -265,7 +265,7 @@ Rails.application.routes.draw do
namespace :user do
root to: 'users#show'
- resources :financial_transactions, only: [:index, :show]
+ resources :financial_transactions, only: [:index, :show, :create]
resources :group_order_articles
end
diff --git a/doc/swagger.v1.yml b/doc/swagger.v1.yml
index 1c5c4cc2..ce9cdb91 100644
--- a/doc/swagger.v1.yml
+++ b/doc/swagger.v1.yml
@@ -89,6 +89,42 @@ paths:
$ref: '#/definitions/Error403'
security:
- foodsoft_auth: ['finance:user']
+ post:
+ summary: create new financial transaction
+ tags:
+ - 1. User
+ - 6. FinancialTransaction
+ parameters:
+ - in: body
+ name: body
+ description: financial transaction to create
+ required: true
+ schema:
+ $ref: '#/definitions/FinancialTransactionForCreate'
+ responses:
+ 200:
+ description: success
+ schema:
+ type: object
+ properties:
+ financial_transaction:
+ $ref: '#/definitions/FinancialTransaction'
+ 401:
+ description: not logged-in
+ schema:
+ $ref: '#/definitions/Error401'
+ 403:
+ description: user has no ordergroup, is below minimum balance, or missing scope
+ schema:
+ $ref: '#/definitions/Error403'
+ 404:
+ description: financial transaction type not found
+ schema:
+ $ref: '#/definitions/Error404'
+ 422:
+ description: invalid parameter value
+ schema:
+ $ref: '#/definitions/Error422'
/user/financial_transactions/{id}:
parameters:
- $ref: '#/parameters/idInUrl'
@@ -719,34 +755,40 @@ definitions:
description: language code
required: ['id', 'name', 'email']
- FinancialTransaction:
+ FinancialTransactionForCreate:
type: object
properties:
- id:
- type: integer
- user_id:
- type: ['integer', 'null']
- description: id of user who entered the transaction (may be null for deleted users or 0 for a system user)
- user_name:
- type: ['string', 'null']
- description: name of user who entered the transaction (may be null or empty string for deleted users or system users)
amount:
type: number
description: amount credited (negative for a debit transaction)
financial_transaction_type_id:
type: integer
description: id of the type of the transaction
- financial_transaction_type_name:
- type: string
- description: name of the type of the transaction
note:
type: string
description: note entered with the transaction
- created_at:
- type: string
- format: date-time
- description: when the transaction was entered
- required: ['id', 'user_id', 'user_name', 'amount', 'financial_transaction_type_id', 'financial_transaction_type_name', 'note', 'created_at']
+ required: ['amount', 'financial_transaction_type_id', 'note']
+ FinancialTransaction:
+ allOf:
+ - $ref: '#/definitions/FinancialTransactionForCreate'
+ - type: object
+ properties:
+ id:
+ type: integer
+ user_id:
+ type: ['integer', 'null']
+ description: id of user who entered the transaction (may be null for deleted users or 0 for a system user)
+ user_name:
+ type: ['string', 'null']
+ description: name of user who entered the transaction (may be null or empty string for deleted users or system users)
+ financial_transaction_type_name:
+ type: string
+ description: name of the type of the transaction
+ created_at:
+ type: string
+ format: date-time
+ description: when the transaction was entered
+ required: ['id', 'user_id', 'user_name', 'financial_transaction_type_name', 'created_at']
FinancialTransactionClass:
type: object
diff --git a/spec/api/v1/swagger_spec.rb b/spec/api/v1/swagger_spec.rb
index 15b8311b..382f609d 100644
--- a/spec/api/v1/swagger_spec.rb
+++ b/spec/api/v1/swagger_spec.rb
@@ -48,7 +48,38 @@ describe 'API v1', type: :apivore, order: :defined do
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({'id' => other_ft_1.id})) }
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({'id' => FinancialTransaction.last.id + 1})) }
+ let(:create_params) { {'_data' => {financial_transaction: {amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id, note: 'note'}}} }
+
+
+ context 'without using self service' do
+ it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
+ end
+
+ context 'with using self service' do
+ before { FoodsoftConfig[:use_self_service] = true }
+
+ it { is_expected.to validate(:post, '/user/financial_transactions', 200, api_auth(create_params)) }
+
+ context 'with invalid financial transaction type' do
+ let(:create_params) { {'_data' => {financial_transaction: {amount: 1, financial_transaction_type_id: -1, note: 'note'}}} }
+
+ it { is_expected.to validate(:post, '/user/financial_transactions', 404, api_auth(create_params)) }
+ end
+
+ context 'without note' do
+ let(:create_params) { {'_data' => {financial_transaction: {amount: 1, financial_transaction_type_id: ft_1.financial_transaction_type.id}}} }
+
+ it { is_expected.to validate(:post, '/user/financial_transactions', 422, api_auth(create_params)) }
+ end
+
+ context 'without enough balance' do
+ before { FoodsoftConfig[:minimum_balance] = 1000 }
+ it { is_expected.to validate(:post, '/user/financial_transactions', 403, api_auth(create_params)) }
+ end
+ end
+
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions')
+ it_handles_invalid_token_and_scope(:post, '/user/financial_transactions', ->{ api_auth(create_params) })
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions/{id}', ->{ api_auth('id' => ft_2.id) })
end
end
diff --git a/spec/api/v1/user/financial_transactions_spec.rb b/spec/api/v1/user/financial_transactions_spec.rb
new file mode 100644
index 00000000..d7f0c6b3
--- /dev/null
+++ b/spec/api/v1/user/financial_transactions_spec.rb
@@ -0,0 +1,107 @@
+require 'spec_helper'
+
+# Most routes are tested in the swagger_spec, this tests endpoints that change data.
+describe Api::V1::User::FinancialTransactionsController, type: :controller do
+ include ApiOAuth
+ let(:user) { create(:user, :ordergroup) }
+ let(:api_scopes) { ['finance:user'] }
+
+ let(:ftc1) { create :financial_transaction_class }
+ let(:ftc2) { create :financial_transaction_class }
+ let(:ftt1) { create :financial_transaction_type, financial_transaction_class: ftc1 }
+ let(:ftt2) { create :financial_transaction_type, financial_transaction_class: ftc2 }
+ let(:ftt3) { create :financial_transaction_type, financial_transaction_class: ftc2 }
+
+ let(:amount) { rand(-100..100) }
+ let(:note) { Faker::Lorem.sentence }
+
+ let(:json_ft) { json_response['financial_transaction'] }
+
+ shared_examples "financial_transactions endpoint success" do
+ before { request }
+
+ it "returns status 200" do
+ expect(response.status).to eq 200
+ end
+ end
+
+ shared_examples "financial_transactions create/update success" do
+ include_examples "financial_transactions endpoint success"
+
+ it "returns the financial_transaction" do
+ expect(json_ft['id']).to be_present
+ expect(json_ft['financial_transaction_type_id']).to eq ftt1.id
+ expect(json_ft['financial_transaction_type_name']).to eq ftt1.name
+ expect(json_ft['amount']).to eq amount
+ expect(json_ft['note']).to eq note
+ expect(json_ft['user_id']).to eq user.id
+ end
+
+ it "updates the financial_transaction" do
+ resulting_ft = FinancialTransaction.where(id: json_ft['id']).first
+ expect(resulting_ft).to be_present
+ expect(resulting_ft.financial_transaction_type).to eq ftt1
+ expect(resulting_ft.amount).to eq amount
+ expect(resulting_ft.note).to eq note
+ expect(resulting_ft.user).to eq user
+ end
+ end
+
+
+ shared_examples "financial_transactions endpoint failure" do |status|
+ it "returns status #{status}" do
+ request
+ expect(response.status).to eq status
+ end
+
+ it "does not change the ordergroup" do
+ expect { request }.to_not change {
+ user.ordergroup.attributes
+ }
+ end
+
+ it "does not change the financial_transactions of ordergroup" do
+ expect { request }.to_not change {
+ user.ordergroup.financial_transactions.count
+ }
+ end
+ end
+
+
+ describe "POST :create" do
+ let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id, note: note } }
+ let(:request) { post :create, params: { financial_transaction: ft_params, foodcoop: 'f' } }
+
+ context 'without using self service' do
+ include_examples "financial_transactions endpoint failure", 403
+ end
+
+ context 'with using self service' do
+ before { FoodsoftConfig[:use_self_service] = true }
+
+ context "with no existing financial transaction" do
+ include_examples "financial_transactions create/update success"
+ end
+
+ context "with existing financial transaction" do
+ before { user.ordergroup.add_financial_transaction! 5000, 'for ordering', user, ftt3 }
+ include_examples "financial_transactions create/update success"
+ end
+
+ context "with invalid financial transaction type" do
+ let(:ft_params) { { amount: amount, financial_transaction_type_id: -1, note: note } }
+ include_examples "financial_transactions endpoint failure", 404
+ end
+
+ context "without note" do
+ let(:ft_params) { { amount: amount, financial_transaction_type_id: ftt1.id } }
+ include_examples "financial_transactions endpoint failure", 422
+ end
+
+ context 'without enough balance' do
+ before { FoodsoftConfig[:minimum_balance] = 1000 }
+ include_examples "financial_transactions endpoint failure", 403
+ end
+ end
+ end
+end