Add API v1 financial_transactions POST endpoint
In a first step we allow members to create financial transactions for their own ordergroup.
This commit is contained in:
parent
9d68abd908
commit
e6061e6760
6 changed files with 211 additions and 18 deletions
|
@ -3,6 +3,8 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
|
||||||
|
|
||||||
before_action ->{ doorkeeper_authorize! 'finance:user' }
|
before_action ->{ doorkeeper_authorize! 'finance:user' }
|
||||||
before_action :require_ordergroup
|
before_action :require_ordergroup
|
||||||
|
before_action :require_minimum_balance, only: [:create]
|
||||||
|
before_action -> { require_config_enabled :use_self_service }, only: [:create]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render_collection search_scope
|
render_collection search_scope
|
||||||
|
@ -12,10 +14,20 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
|
||||||
render json: scope.find(params.require(:id))
|
render json: scope.find(params.require(:id))
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def scope
|
def scope
|
||||||
current_ordergroup.financial_transactions.includes(:user, :financial_transaction_type)
|
current_ordergroup.financial_transactions.includes(:user, :financial_transaction_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_params
|
||||||
|
params.require(:financial_transaction).permit(:amount, :financial_transaction_type_id, :note)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,6 +90,7 @@ class Ordergroup < Group
|
||||||
if t.amount < 0 && self.account_balance < 0 && self.account_balance - t.amount >= 0
|
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)
|
Resque.enqueue(UserNotifier, FoodsoftConfig.scope, 'negative_balance', self.id, t.id)
|
||||||
end
|
end
|
||||||
|
t
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -265,7 +265,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
namespace :user do
|
namespace :user do
|
||||||
root to: 'users#show'
|
root to: 'users#show'
|
||||||
resources :financial_transactions, only: [:index, :show]
|
resources :financial_transactions, only: [:index, :show, :create]
|
||||||
resources :group_order_articles
|
resources :group_order_articles
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,42 @@ paths:
|
||||||
$ref: '#/definitions/Error403'
|
$ref: '#/definitions/Error403'
|
||||||
security:
|
security:
|
||||||
- foodsoft_auth: ['finance:user']
|
- 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}:
|
/user/financial_transactions/{id}:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/idInUrl'
|
- $ref: '#/parameters/idInUrl'
|
||||||
|
@ -719,8 +755,23 @@ definitions:
|
||||||
description: language code
|
description: language code
|
||||||
required: ['id', 'name', 'email']
|
required: ['id', 'name', 'email']
|
||||||
|
|
||||||
FinancialTransaction:
|
FinancialTransactionForCreate:
|
||||||
type: object
|
type: object
|
||||||
|
properties:
|
||||||
|
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
|
||||||
|
note:
|
||||||
|
type: string
|
||||||
|
description: note entered with the transaction
|
||||||
|
required: ['amount', 'financial_transaction_type_id', 'note']
|
||||||
|
FinancialTransaction:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/FinancialTransactionForCreate'
|
||||||
|
- type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -730,23 +781,14 @@ definitions:
|
||||||
user_name:
|
user_name:
|
||||||
type: ['string', 'null']
|
type: ['string', 'null']
|
||||||
description: name of user who entered the transaction (may be <tt>null</tt> or empty string for deleted users or system users)
|
description: name of user who entered the transaction (may be <tt>null</tt> 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:
|
financial_transaction_type_name:
|
||||||
type: string
|
type: string
|
||||||
description: name of the type of the transaction
|
description: name of the type of the transaction
|
||||||
note:
|
|
||||||
type: string
|
|
||||||
description: note entered with the transaction
|
|
||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: when the transaction was entered
|
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: ['id', 'user_id', 'user_name', 'financial_transaction_type_name', 'created_at']
|
||||||
|
|
||||||
FinancialTransactionClass:
|
FinancialTransactionClass:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -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' => other_ft_1.id})) }
|
||||||
it { is_expected.to validate(:get, '/user/financial_transactions/{id}', 404, api_auth({'id' => FinancialTransaction.last.id + 1})) }
|
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(: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) })
|
it_handles_invalid_token_and_scope(:get, '/user/financial_transactions/{id}', ->{ api_auth('id' => ft_2.id) })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
107
spec/api/v1/user/financial_transactions_spec.rb
Normal file
107
spec/api/v1/user/financial_transactions_spec.rb
Normal file
|
@ -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
|
Loading…
Reference in a new issue