API v1 group_order_articles endpoints
This commit is contained in:
parent
ed9192c47f
commit
e1d50e5b9c
11 changed files with 646 additions and 10 deletions
108
app/controllers/api/v1/user/group_order_articles_controller.rb
Normal file
108
app/controllers/api/v1/user/group_order_articles_controller.rb
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController
|
||||||
|
include Concerns::CollectionScope
|
||||||
|
|
||||||
|
before_action ->{ doorkeeper_authorize! 'group_orders:user' }
|
||||||
|
|
||||||
|
before_action :require_ordergroup
|
||||||
|
before_action :require_minimum_balance, only: [:create, :update] # destroy is ok
|
||||||
|
before_action :require_enough_apples, only: [:create, :update] # destroy is ok
|
||||||
|
# @todo allow decreasing amounts when minimum balance isn't met
|
||||||
|
|
||||||
|
def index
|
||||||
|
render_collection search_scope
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
goa = scope.find(params.require(:id))
|
||||||
|
render_goa_with_oa(goa)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
goa = nil
|
||||||
|
GroupOrderArticle.transaction do
|
||||||
|
oa = order_articles_scope_for_create.find(create_params.require(:order_article_id))
|
||||||
|
go = current_ordergroup.group_orders.find_or_create_by!(order_id: oa.order_id)
|
||||||
|
goa = go.group_order_articles.create!(order_article: oa)
|
||||||
|
goa.update_quantities((create_params[:quantity] || 0).to_i, (create_params[:tolerance] || 0).to_i)
|
||||||
|
oa.update_results!
|
||||||
|
go.update_price!
|
||||||
|
go.update_attributes! updated_by: current_user
|
||||||
|
end
|
||||||
|
render_goa_with_oa(goa)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
goa = nil
|
||||||
|
GroupOrderArticle.transaction do
|
||||||
|
goa = scope_for_update.includes(:group_order_article_quantities).find(params.require(:id))
|
||||||
|
goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, (update_params[:tolerance] || goa.tolerance).to_i)
|
||||||
|
goa.order_article.update_results!
|
||||||
|
goa.group_order.update_price!
|
||||||
|
goa.group_order.update_attributes! updated_by: current_user
|
||||||
|
end
|
||||||
|
render_goa_with_oa(goa)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
goa = nil
|
||||||
|
GroupOrderArticle.transaction do
|
||||||
|
goa = scope_for_update.find(params.require(:id))
|
||||||
|
goa.destroy!
|
||||||
|
goa.order_article.update_results!
|
||||||
|
goa.group_order.update_price!
|
||||||
|
goa.group_order.update_attributes! updated_by: current_user
|
||||||
|
end
|
||||||
|
render_goa_with_oa(nil, goa.order_article)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def max_per_page
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def scope
|
||||||
|
GroupOrderArticle.
|
||||||
|
joins(:group_order).
|
||||||
|
includes(order_article: :article, group_order: :order).
|
||||||
|
where(group_orders: { ordergroup_id: current_ordergroup.id })
|
||||||
|
end
|
||||||
|
|
||||||
|
def scope_for_update
|
||||||
|
scope.references(order_article: { group_order: :order }).merge(Order.open)
|
||||||
|
end
|
||||||
|
|
||||||
|
def order_articles_scope_for_create
|
||||||
|
OrderArticle.joins(:order).merge(Order.open)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_params
|
||||||
|
params.require(:group_order_article).permit(:order_article_id, :quantity, :tolerance)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_params
|
||||||
|
params.require(:group_order_article).permit(:quantity, :tolerance)
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_minimum_balance
|
||||||
|
minimum_balance = FoodsoftConfig[:minimum_balance] or return
|
||||||
|
if current_ordergroup.account_balance < minimum_balance
|
||||||
|
raise Api::Errors::PermissionRequired.new(t('application.controller.error_minimum_balance', min: minimum_balance))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_enough_apples
|
||||||
|
if current_ordergroup.not_enough_apples?
|
||||||
|
s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
|
||||||
|
raise Api::Errors::PermissionRequired.new(s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_goa_with_oa(goa, oa = goa.order_article)
|
||||||
|
data = {}
|
||||||
|
data[:group_order_article] = GroupOrderArticleSerializer.new(goa) if goa
|
||||||
|
data[:order_article] = OrderArticleSerializer.new(oa) if oa
|
||||||
|
|
||||||
|
render json: data, root: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,6 +21,14 @@ class GroupOrder < ApplicationRecord
|
||||||
|
|
||||||
scope :ordered, -> { includes(:ordergroup).order('groups.name') }
|
scope :ordered, -> { includes(:ordergroup).order('groups.name') }
|
||||||
|
|
||||||
|
def self.ransackable_attributes(auth_object = nil)
|
||||||
|
%w(id price)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ransackable_associations(auth_object = nil)
|
||||||
|
%w(order group_order_articles)
|
||||||
|
end
|
||||||
|
|
||||||
# Generate some data for the javascript methods in ordering view
|
# Generate some data for the javascript methods in ordering view
|
||||||
def load_data
|
def load_data
|
||||||
data = {}
|
data = {}
|
||||||
|
|
|
@ -5,16 +5,25 @@ class GroupOrderArticle < ApplicationRecord
|
||||||
|
|
||||||
belongs_to :group_order
|
belongs_to :group_order
|
||||||
belongs_to :order_article
|
belongs_to :order_article
|
||||||
has_many :group_order_article_quantities, :dependent => :destroy
|
has_many :group_order_article_quantities, dependent: :destroy
|
||||||
|
|
||||||
validates_presence_of :group_order, :order_article
|
validates_presence_of :group_order, :order_article
|
||||||
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
|
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
|
||||||
validate :check_order_not_closed # don't allow changes to closed (aka settled) orders
|
validate :check_order_not_closed # don't allow changes to closed (aka settled) orders
|
||||||
|
validates :quantity, :tolerance, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||||
|
|
||||||
scope :ordered, -> { includes(:group_order => :ordergroup).order('groups.name') }
|
scope :ordered, -> { includes(:group_order => :ordergroup).order('groups.name') }
|
||||||
|
|
||||||
localize_input_of :result
|
localize_input_of :result
|
||||||
|
|
||||||
|
def self.ransackable_attributes(auth_object = nil)
|
||||||
|
%w(id quantity tolerance result)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.ransackable_associations(auth_object = nil)
|
||||||
|
%w(order_article group_order)
|
||||||
|
end
|
||||||
|
|
||||||
# Setter used in group_order_article#new
|
# Setter used in group_order_article#new
|
||||||
# We have to create an group_order, if the ordergroup wasn't involved in the order yet
|
# We have to create an group_order, if the ordergroup wasn't involved in the order yet
|
||||||
def ordergroup_id=(id)
|
def ordergroup_id=(id)
|
||||||
|
@ -86,7 +95,7 @@ class GroupOrderArticle < ApplicationRecord
|
||||||
|
|
||||||
# Check if something went terribly wrong and quantites have not been adjusted as desired.
|
# Check if something went terribly wrong and quantites have not been adjusted as desired.
|
||||||
if (self.quantity != quantity || self.tolerance != tolerance)
|
if (self.quantity != quantity || self.tolerance != tolerance)
|
||||||
raise 'Invalid state: unable to update GroupOrderArticle/-Quantities to desired quantities!'
|
raise ActiveRecord::RecordNotSaved.new('Unable to update GroupOrderArticle/-Quantities to desired quantities!', self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove zero-only items.
|
# Remove zero-only items.
|
||||||
|
|
|
@ -30,9 +30,9 @@ class Order < ApplicationRecord
|
||||||
|
|
||||||
# Finders
|
# Finders
|
||||||
scope :started, -> { where('starts <= ?', Time.now) }
|
scope :started, -> { where('starts <= ?', Time.now) }
|
||||||
scope :closed, -> { where(state: 'closed').order('ends DESC') }
|
scope :closed, -> { where(state: 'closed').order(ends: :desc) }
|
||||||
scope :stockit, -> { where(supplier_id: nil).order('ends DESC') }
|
scope :stockit, -> { where(supplier_id: nil).order(ends: :desc) }
|
||||||
scope :recent, -> { order('starts DESC').limit(10) }
|
scope :recent, -> { order(starts: :desc).limit(10) }
|
||||||
scope :stock_group_order, -> { group_orders.where(ordergroup_id: nil).first }
|
scope :stock_group_order, -> { group_orders.where(ordergroup_id: nil).first }
|
||||||
scope :with_invoice, -> { where.not(invoice: nil) }
|
scope :with_invoice, -> { where.not(invoice: nil) }
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ class Order < ApplicationRecord
|
||||||
# So orders can
|
# So orders can
|
||||||
# 1. ...only transition in one direction (e.g. an order that has been `finished` currently cannot be reopened)
|
# 1. ...only transition in one direction (e.g. an order that has been `finished` currently cannot be reopened)
|
||||||
# 2. ...be set to `closed` when having the `finished` state. (`received` is optional)
|
# 2. ...be set to `closed` when having the `finished` state. (`received` is optional)
|
||||||
scope :open, -> { where(state: 'open').order('ends DESC') }
|
scope :open, -> { where(state: 'open').order(ends: :desc) }
|
||||||
scope :finished, -> { where(state: %w[finished received closed]).order('ends DESC') }
|
scope :finished, -> { where(state: %w[finished received closed]).order(ends: :desc) }
|
||||||
scope :finished_not_closed, -> { where(state: %w[finished received]).order('ends DESC') }
|
scope :finished_not_closed, -> { where(state: %w[finished received]).order(ends: :desc) }
|
||||||
|
|
||||||
# Allow separate inputs for date and time
|
# Allow separate inputs for date and time
|
||||||
# with workaround for https://github.com/einzige/date_time_attribute/issues/14
|
# with workaround for https://github.com/einzige/date_time_attribute/issues/14
|
||||||
|
|
8
app/serializers/group_order_article_serializer.rb
Normal file
8
app/serializers/group_order_article_serializer.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class GroupOrderArticleSerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :order_article_id
|
||||||
|
attributes :quantity, :tolerance, :result, :total_price
|
||||||
|
|
||||||
|
def total_price
|
||||||
|
object.total_price.to_f
|
||||||
|
end
|
||||||
|
end
|
|
@ -49,7 +49,7 @@ Doorkeeper.configure do
|
||||||
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
|
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
|
||||||
|
|
||||||
# default is a collection of read-only scopes
|
# default is a collection of read-only scopes
|
||||||
default_scopes 'config:user', 'finance:user', 'user:read', 'orders:read'
|
default_scopes 'config:user', 'finance:user', 'user:read', 'orders:read', 'group_orders:user'
|
||||||
|
|
||||||
optional_scopes 'config:read', 'config:write',
|
optional_scopes 'config:read', 'config:write',
|
||||||
'finance:read', 'finance:write',
|
'finance:read', 'finance:write',
|
||||||
|
|
|
@ -418,6 +418,7 @@ en:
|
||||||
error_denied_sign_in: sign in as another user
|
error_denied_sign_in: sign in as another user
|
||||||
error_feature_disabled: This feature is currently disabled.
|
error_feature_disabled: This feature is currently disabled.
|
||||||
error_members_only: This action is only available to members of the group!
|
error_members_only: This action is only available to members of the group!
|
||||||
|
error_minimum_balance: Sorry, your account balance is below the minimum of %{min}.
|
||||||
error_token: Access denied (invalid token)!
|
error_token: Access denied (invalid token)!
|
||||||
article_categories:
|
article_categories:
|
||||||
create:
|
create:
|
||||||
|
|
|
@ -266,6 +266,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]
|
||||||
|
resources :group_order_articles
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :financial_transaction_classes, only: [:index, :show]
|
resources :financial_transaction_classes, only: [:index, :show]
|
||||||
|
@ -273,6 +274,7 @@ Rails.application.routes.draw do
|
||||||
resources :financial_transactions, only: [:index, :show]
|
resources :financial_transactions, only: [:index, :show]
|
||||||
resources :orders, only: [:index, :show]
|
resources :orders, only: [:index, :show]
|
||||||
resources :order_articles, only: [:index, :show]
|
resources :order_articles, only: [:index, :show]
|
||||||
|
resources :group_order_articles
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,177 @@ paths:
|
||||||
security:
|
security:
|
||||||
- foodsoft_auth: ['finance:user']
|
- foodsoft_auth: ['finance:user']
|
||||||
|
|
||||||
|
/user/group_order_articles:
|
||||||
|
get:
|
||||||
|
summary: group order articles
|
||||||
|
tags:
|
||||||
|
- 1. User
|
||||||
|
- 2. Order
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/page'
|
||||||
|
- $ref: '#/parameters/per_page'
|
||||||
|
- $ref: '#/parameters/q_ordered'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
group_order_articles:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/GroupOrderArticle'
|
||||||
|
meta:
|
||||||
|
$ref: '#/definitions/Meta'
|
||||||
|
401:
|
||||||
|
description: not logged-in
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error401'
|
||||||
|
403:
|
||||||
|
description: user has no ordergroup or missing scope
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error403'
|
||||||
|
security:
|
||||||
|
- foodsoft_auth: ['group_orders:user']
|
||||||
|
post:
|
||||||
|
summary: create new group order article
|
||||||
|
tags:
|
||||||
|
- 1. User
|
||||||
|
- 2. Order
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: group order article to create
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/GroupOrderArticleForCreate'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
group_order_article:
|
||||||
|
$ref: '#/definitions/GroupOrderArticle'
|
||||||
|
order_article:
|
||||||
|
$ref: '#/definitions/OrderArticle'
|
||||||
|
401:
|
||||||
|
description: not logged-in
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error401'
|
||||||
|
403:
|
||||||
|
description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error403'
|
||||||
|
404:
|
||||||
|
description: order article not found in open orders
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error404'
|
||||||
|
422:
|
||||||
|
description: invalid parameter value or group order article already exists
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error422'
|
||||||
|
/user/group_order_articles/{id}:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/idInUrl'
|
||||||
|
get:
|
||||||
|
summary: find group order article by id
|
||||||
|
tags:
|
||||||
|
- 1. User
|
||||||
|
- 2. Order
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
group_order_article:
|
||||||
|
$ref: '#/definitions/GroupOrderArticle'
|
||||||
|
order_article:
|
||||||
|
$ref: '#/definitions/OrderArticle'
|
||||||
|
401:
|
||||||
|
description: not logged-in
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error401'
|
||||||
|
403:
|
||||||
|
description: user has no ordergroup or missing scope
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error403'
|
||||||
|
404:
|
||||||
|
description: not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error404'
|
||||||
|
security:
|
||||||
|
- foodsoft_auth: ['group_orders:user']
|
||||||
|
patch:
|
||||||
|
summary: update a group order article (but delete if quantity and tolerance are zero)
|
||||||
|
tags:
|
||||||
|
- 1. User
|
||||||
|
- 2. Order
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/idInUrl'
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: group order article update
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/GroupOrderArticleForUpdate'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
group_order_article:
|
||||||
|
$ref: '#/definitions/GroupOrderArticle'
|
||||||
|
order_article:
|
||||||
|
$ref: '#/definitions/OrderArticle'
|
||||||
|
401:
|
||||||
|
description: not logged-in
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error401'
|
||||||
|
403:
|
||||||
|
description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error403'
|
||||||
|
404:
|
||||||
|
description: order article not found in open orders
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error404'
|
||||||
|
422:
|
||||||
|
description: invalid parameter value
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error422'
|
||||||
|
delete:
|
||||||
|
summary: remove group order article
|
||||||
|
tags:
|
||||||
|
- 1. User
|
||||||
|
- 2. Order
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/idInUrl'
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: success
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
group_order_article:
|
||||||
|
$ref: '#/definitions/GroupOrderArticle'
|
||||||
|
order_article:
|
||||||
|
$ref: '#/definitions/OrderArticle'
|
||||||
|
401:
|
||||||
|
description: not logged-in
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error401'
|
||||||
|
403:
|
||||||
|
description: user has no ordergroup, order not open, is below minimum balance, has not enough apple points, or missing scope
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error403'
|
||||||
|
404:
|
||||||
|
description: order article not found in open orders
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error404'
|
||||||
|
|
||||||
/financial_transactions:
|
/financial_transactions:
|
||||||
get:
|
get:
|
||||||
summary: financial transactions
|
summary: financial transactions
|
||||||
|
@ -654,6 +825,39 @@ definitions:
|
||||||
article:
|
article:
|
||||||
$ref: '#/definitions/Article'
|
$ref: '#/definitions/Article'
|
||||||
|
|
||||||
|
GroupOrderArticleForUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
description: number of units ordered by the user's ordergroup
|
||||||
|
tolerance:
|
||||||
|
type: integer
|
||||||
|
description: number of extra units the user's ordergroup is willing to buy for filling a box
|
||||||
|
GroupOrderArticleForCreate:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/GroupOrderArticleForUpdate'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
order_article_id:
|
||||||
|
type: integer
|
||||||
|
description: id of order article
|
||||||
|
GroupOrderArticle:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/GroupOrderArticleForCreate'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
result:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: number of units the user's ordergroup will receive or has received
|
||||||
|
total_price:
|
||||||
|
type: number
|
||||||
|
format: float
|
||||||
|
description: total price of this group order article
|
||||||
|
|
||||||
Navigation:
|
Navigation:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
@ -721,6 +925,15 @@ definitions:
|
||||||
description: '<tt>forbidden</tt> or <tt>invalid_scope</tt>'
|
description: '<tt>forbidden</tt> or <tt>invalid_scope</tt>'
|
||||||
error_description:
|
error_description:
|
||||||
$ref: '#/definitions/Error/properties/error_description'
|
$ref: '#/definitions/Error/properties/error_description'
|
||||||
|
Error422:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: unprocessable entity
|
||||||
|
error_description:
|
||||||
|
$ref: '#/definitions/Error/properties/error_description'
|
||||||
|
|
||||||
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
foodsoft_auth:
|
foodsoft_auth:
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe 'API v1', type: :apivore, order: :defined do
|
||||||
it_handles_invalid_token_and_scope(:get, '/user')
|
it_handles_invalid_token_and_scope(:get, '/user')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'financial_transactions' do
|
context 'user/financial_transactions' do
|
||||||
let(:api_scopes) { ['finance:user'] }
|
let(:api_scopes) { ['finance:user'] }
|
||||||
let(:other_user) { create :user, :ordergroup }
|
let(:other_user) { create :user, :ordergroup }
|
||||||
let!(:other_ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup }
|
let!(:other_ft_1) { create :financial_transaction, ordergroup: other_user.ordergroup }
|
||||||
|
@ -53,6 +53,82 @@ describe 'API v1', type: :apivore, order: :defined do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'user/group_order_articles' do
|
||||||
|
let(:api_scopes) { ['group_orders:user'] }
|
||||||
|
let(:order) { create(:order, article_count: 2) }
|
||||||
|
|
||||||
|
let(:user_2) { create :user, :ordergroup }
|
||||||
|
let(:group_order_2) { create(:group_order, order: order, ordergroup: user_2.ordergroup) }
|
||||||
|
let!(:goa_2) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order_2 }
|
||||||
|
before { group_order_2.update_price!; user_2.ordergroup.update_stats! }
|
||||||
|
|
||||||
|
context 'without ordergroup' do
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles', 403, api_auth) }
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 403, api_auth({'id' => goa_2.id})) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with ordergroup' do
|
||||||
|
let(:user) { create :user, :ordergroup }
|
||||||
|
let(:group_order) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
let!(:goa) { create :group_order_article, order_article: order.order_articles[0], group_order: group_order }
|
||||||
|
before { group_order.update_price!; user.ordergroup.update_stats! }
|
||||||
|
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles', 200, api_auth) }
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) }
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({'id' => goa_2.id})) }
|
||||||
|
it { is_expected.to validate(:get, '/user/group_order_articles/{id}', 404, api_auth({'id' => GroupOrderArticle.last.id + 1})) }
|
||||||
|
|
||||||
|
let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[1].id, quantity: 1}}} }
|
||||||
|
let(:update_params) { {'id' => goa.id, '_data' => {group_order_article: {quantity: goa.quantity + 1, tolerance: 0}}} }
|
||||||
|
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 200, api_auth(create_params)) }
|
||||||
|
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 200, api_auth(update_params)) }
|
||||||
|
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) }
|
||||||
|
|
||||||
|
context 'with an existing group_order_article' do
|
||||||
|
let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[0].id, quantity: 1}}} }
|
||||||
|
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with invalid parameter values' do
|
||||||
|
let(:create_params) { {'_data' => {group_order_article: {order_article_id: order.order_articles[0].id, quantity: -1}}} }
|
||||||
|
let(:update_params) { {'id' => goa.id, '_data' => {group_order_article: {quantity: -1, tolerance: 0}}} }
|
||||||
|
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 422, api_auth(create_params)) }
|
||||||
|
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 422, api_auth(update_params)) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a closed order' do
|
||||||
|
let(:order) { create(:order, article_count: 2, state: :finished) }
|
||||||
|
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 404, api_auth(create_params)) }
|
||||||
|
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 404, api_auth(update_params)) }
|
||||||
|
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 404, api_auth({'id' => goa.id})) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough balance' do
|
||||||
|
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) }
|
||||||
|
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) }
|
||||||
|
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough apple points' do
|
||||||
|
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
|
||||||
|
it { is_expected.to validate(:post, '/user/group_order_articles', 403, api_auth(create_params)) }
|
||||||
|
it { is_expected.to validate(:patch, '/user/group_order_articles/{id}', 403, api_auth(update_params)) }
|
||||||
|
it { is_expected.to validate(:delete, '/user/group_order_articles/{id}', 200, api_auth({'id' => goa.id})) }
|
||||||
|
end
|
||||||
|
|
||||||
|
it_handles_invalid_token_and_scope(:get, '/user/group_order_articles')
|
||||||
|
it_handles_invalid_token_and_scope(:post, '/user/group_order_articles', ->{ api_auth(create_params) })
|
||||||
|
it_handles_invalid_token_and_scope(:get, '/user/group_order_articles/{id}', ->{ api_auth({'id' => goa.id}) })
|
||||||
|
it_handles_invalid_token_and_scope(:patch, '/user/group_order_articles/{id}', ->{ api_auth(update_params) })
|
||||||
|
it_handles_invalid_token_and_scope(:delete, '/user/group_order_articles/{id}', ->{ api_auth({'id' => goa.id}) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'config' do
|
context 'config' do
|
||||||
let(:api_scopes) { ['config:user'] }
|
let(:api_scopes) { ['config:user'] }
|
||||||
|
|
||||||
|
|
211
spec/api/v1/user/group_order_articles_spec.rb
Normal file
211
spec/api/v1/user/group_order_articles_spec.rb
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
# Most routes are tested in the swagger_spec, this tests endpoints that change data.
|
||||||
|
describe Api::V1::User::GroupOrderArticlesController, type: :controller do
|
||||||
|
include ApiOAuth
|
||||||
|
let(:user) { create(:user, :ordergroup) }
|
||||||
|
let(:api_scopes) { ['group_orders:user'] }
|
||||||
|
|
||||||
|
let(:order) { create(:order, article_count: 1) }
|
||||||
|
let(:oa_1) { order.order_articles.first }
|
||||||
|
|
||||||
|
let(:other_quantity) { rand(1..10) }
|
||||||
|
let(:other_tolerance) { rand(1..10) }
|
||||||
|
let(:user_other) { create(:user, :ordergroup) }
|
||||||
|
let!(:go_other) { create(:group_order, order: order, ordergroup: user_other.ordergroup ) }
|
||||||
|
let!(:goa_other) { create(:group_order_article, group_order: go_other, order_article: oa_1, quantity: other_quantity, tolerance: other_tolerance) }
|
||||||
|
before { go_other.update_price!; user_other.ordergroup.update_stats! }
|
||||||
|
|
||||||
|
let(:json_goa) { json_response['group_order_article'] }
|
||||||
|
let(:json_oa) { json_response['order_article'] }
|
||||||
|
|
||||||
|
|
||||||
|
shared_examples "group_order_articles endpoint success" do
|
||||||
|
before { request }
|
||||||
|
|
||||||
|
it "returns status 200" do
|
||||||
|
expect(response.status).to eq 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the order_article" do
|
||||||
|
expect(json_oa['id']).to eq oa_1.id
|
||||||
|
expect(json_oa['quantity']).to eq new_quantity + other_quantity
|
||||||
|
expect(json_oa['tolerance']).to eq new_tolerance + other_tolerance
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates the group_order" do
|
||||||
|
go = nil
|
||||||
|
expect {
|
||||||
|
request
|
||||||
|
go = user.ordergroup.group_orders.where(order: order).last
|
||||||
|
}.to change { go&.updated_by }.to(user)
|
||||||
|
.and change { go&.price }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples "group_order_articles create/update success" do
|
||||||
|
include_examples "group_order_articles endpoint success"
|
||||||
|
|
||||||
|
it "returns the group_order_article" do
|
||||||
|
expect(json_goa['id']).to be_present
|
||||||
|
expect(json_goa['order_article_id']).to eq oa_1.id
|
||||||
|
expect(json_goa['quantity']).to eq new_quantity
|
||||||
|
expect(json_goa['tolerance']).to eq new_tolerance
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates the group_order_article" do
|
||||||
|
resulting_goa = GroupOrderArticle.where(id: json_goa['id']).first
|
||||||
|
expect(resulting_goa).to be_present
|
||||||
|
expect(resulting_goa.quantity).to eq new_quantity
|
||||||
|
expect(resulting_goa.tolerance).to eq new_tolerance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
shared_examples "group_order_articles endpoint failure" do |status|
|
||||||
|
it "returns status #{status}" do
|
||||||
|
request
|
||||||
|
expect(response.status).to eq status
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not change the group_order" do
|
||||||
|
expect { request }.to_not change {
|
||||||
|
go = user.ordergroup.group_orders.where(order: order).last
|
||||||
|
go&.attributes
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not change the group_order_article" do
|
||||||
|
expect { request }.to_not change {
|
||||||
|
goa = GroupOrderArticle.joins(:group_order)
|
||||||
|
.where(order_article_id: oa_1.id, group_orders: { ordergroup: user.ordergroup }).last
|
||||||
|
goa&.attributes
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
describe "POST :create" do
|
||||||
|
let(:new_quantity) { rand(1..10) }
|
||||||
|
let(:new_tolerance) { rand(1..10) }
|
||||||
|
|
||||||
|
let(:goa_params) { { order_article_id: oa_1.id, quantity: new_quantity, tolerance: new_tolerance } }
|
||||||
|
let(:request) { post :create, params: { group_order_article: goa_params, foodcoop: 'f' } }
|
||||||
|
|
||||||
|
context "with no existing group_order" do
|
||||||
|
include_examples "group_order_articles create/update success"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an existing group_order" do
|
||||||
|
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
include_examples "group_order_articles create/update success"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an existing group_order_article" do
|
||||||
|
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 0, tolerance: 1) }
|
||||||
|
before { go.update_price!; user.ordergroup.update_stats! }
|
||||||
|
include_examples "group_order_articles endpoint failure", 422
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid parameter values" do
|
||||||
|
let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance} }
|
||||||
|
include_examples "group_order_articles endpoint failure", 422
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a closed order' do
|
||||||
|
let(:order) { create(:order, article_count: 1, state: :finished) }
|
||||||
|
include_examples "group_order_articles endpoint failure", 404
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough balance' do
|
||||||
|
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
||||||
|
include_examples "group_order_articles endpoint failure", 403
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough apple points' do
|
||||||
|
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
|
||||||
|
include_examples "group_order_articles endpoint failure", 403
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "PATCH :update" do
|
||||||
|
let(:new_quantity) { rand(2..10) }
|
||||||
|
let(:new_tolerance) { rand(2..10) }
|
||||||
|
|
||||||
|
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1, quantity: 1, tolerance: 0) }
|
||||||
|
before { go.update_price!; user.ordergroup.update_stats! }
|
||||||
|
|
||||||
|
let(:goa_params) { { quantity: new_quantity, tolerance: new_tolerance } }
|
||||||
|
let(:request) { patch :update, params: { id: goa.id, group_order_article: goa_params, foodcoop: 'f' } }
|
||||||
|
|
||||||
|
context "happy flow" do
|
||||||
|
include_examples "group_order_articles create/update success"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid parameter values" do
|
||||||
|
let(:goa_params) { { order_article_id: oa_1.id, quantity: -1, tolerance: new_tolerance} }
|
||||||
|
include_examples "group_order_articles endpoint failure", 422
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a closed order' do
|
||||||
|
let(:order) { create(:order, article_count: 1, state: :finished) }
|
||||||
|
include_examples "group_order_articles endpoint failure", 404
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough balance' do
|
||||||
|
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
||||||
|
include_examples "group_order_articles endpoint failure", 403
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough apple points' do
|
||||||
|
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
|
||||||
|
include_examples "group_order_articles endpoint failure", 403
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "DELETE :destroy" do
|
||||||
|
let(:new_quantity) { 0 }
|
||||||
|
let(:new_tolerance) { 0 }
|
||||||
|
|
||||||
|
let!(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) }
|
||||||
|
let!(:goa) { create(:group_order_article, group_order: go, order_article: oa_1) }
|
||||||
|
before { go.update_price!; user.ordergroup.update_stats! }
|
||||||
|
|
||||||
|
let(:request) { delete :destroy, params: { id: goa.id, foodcoop: 'f' } }
|
||||||
|
|
||||||
|
|
||||||
|
shared_examples "group_order_articles destroy success" do
|
||||||
|
include_examples "group_order_articles endpoint success"
|
||||||
|
|
||||||
|
it "does not return the group_order_article" do
|
||||||
|
expect(json_goa).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "deletes the group_order_article" do
|
||||||
|
expect(GroupOrderArticle.where(id: goa.id)).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
context "happy flow" do
|
||||||
|
include_examples "group_order_articles destroy success"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a closed order' do
|
||||||
|
let(:order) { create(:order, article_count: 1, state: :finished) }
|
||||||
|
include_examples "group_order_articles endpoint failure", 404
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough balance' do
|
||||||
|
before { FoodsoftConfig[:minimum_balance] = 1000 }
|
||||||
|
include_examples "group_order_articles destroy success"
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without enough apple points' do
|
||||||
|
before { allow_any_instance_of(Ordergroup).to receive(:not_enough_apples?).and_return(true) }
|
||||||
|
include_examples "group_order_articles destroy success"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue