mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 01:35:34 +08:00
Implement stock consumption via the API [SCI-6642] (#3964)
* Implement stock consumption via the API [SCI-6642] * Remove unnecessary attribute from InventoryItemSerializer [SCI-6642] * Amend permission check, add nested transaction support to consume_stock method [SCI-6642]
This commit is contained in:
parent
ac7a6edab5
commit
229a27750f
12 changed files with 277 additions and 16 deletions
|
@ -7,6 +7,8 @@ module Api
|
|||
before_action :load_project
|
||||
before_action :load_experiment
|
||||
before_action :load_task
|
||||
before_action :load_my_module_repository_row, only: :update
|
||||
before_action :check_stock_consumption_update_permissions, only: :update
|
||||
|
||||
def index
|
||||
items =
|
||||
|
@ -16,20 +18,57 @@ module Api
|
|||
.page(params.dig(:page, :number))
|
||||
.per(params.dig(:page, :size))
|
||||
render jsonapi: items,
|
||||
each_serializer: InventoryItemSerializer,
|
||||
each_serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
include: include_params
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi: @task.repository_rows.find(params.require(:id)),
|
||||
serializer: InventoryItemSerializer,
|
||||
serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
include: %i(inventory_cells inventory)
|
||||
end
|
||||
|
||||
def update
|
||||
@my_module_repository_row.consume_stock(
|
||||
current_user,
|
||||
repository_row_params[:attributes][:stock_consumption],
|
||||
repository_row_params[:attributes][:stock_consumption_comment]
|
||||
)
|
||||
|
||||
render jsonapi: @my_module_repository_row.repository_row,
|
||||
serializer: TaskInventoryItemSerializer,
|
||||
show_repository: true,
|
||||
my_module: @task,
|
||||
include: %i(inventory_cells inventory)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_my_module_repository_row
|
||||
@my_module_repository_row = @task.repository_rows
|
||||
.find(params.require(:id))
|
||||
.my_module_repository_rows
|
||||
.find_by(my_module: @task)
|
||||
end
|
||||
|
||||
def check_stock_consumption_update_permissions
|
||||
unless can_update_my_module_stock_consumption?(@task) &&
|
||||
can_manage_repository_rows?(@my_module_repository_row.repository_row.repository)
|
||||
raise PermissionError.new(RepositoryRow, :update_stock_consumption)
|
||||
end
|
||||
end
|
||||
|
||||
def repository_row_params
|
||||
raise TypeError unless params.require(:data).require(:type) == 'inventory_items'
|
||||
|
||||
params.require(:data).require(:attributes)
|
||||
params.permit(data: { attributes: %i(stock_consumption stock_consumption_comment) })[:data]
|
||||
end
|
||||
|
||||
def permitted_includes
|
||||
%w(inventory_cells)
|
||||
end
|
||||
|
|
|
@ -159,20 +159,12 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
|
||||
def update_consumption
|
||||
module_repository_row = @my_module.my_module_repository_rows.find_by(id: params[:module_row_id])
|
||||
module_repository_row.with_lock do
|
||||
current_stock = module_repository_row.stock_consumption
|
||||
module_repository_row.assign_attributes(
|
||||
stock_consumption: params[:stock_consumption],
|
||||
repository_stock_unit_item_id:
|
||||
module_repository_row.repository_row.repository_stock_value.repository_stock_unit_item_id,
|
||||
last_modified_by: current_user,
|
||||
comment: params[:comment]
|
||||
)
|
||||
module_repository_row.save!
|
||||
|
||||
log_activity(module_repository_row,
|
||||
current_stock,
|
||||
params[:comment])
|
||||
ActiveRecord::Base.transaction do
|
||||
current_stock = module_repository_row.stock_consumption
|
||||
module_repository_row.consume_stock(current_user, params[:stock_consumption], params[:comment])
|
||||
|
||||
log_activity(module_repository_row, current_stock, params[:comment])
|
||||
end
|
||||
|
||||
render json: {}, status: :ok
|
||||
|
|
|
@ -19,6 +19,20 @@ class MyModuleRepositoryRow < ApplicationRecord
|
|||
|
||||
before_save :nulify_stock_consumption, if: :stock_consumption_changed?
|
||||
|
||||
def consume_stock(user, stock_consumption, comment = nil)
|
||||
ActiveRecord::Base.transaction(requires_new: true) do
|
||||
lock!
|
||||
assign_attributes(
|
||||
stock_consumption: stock_consumption,
|
||||
repository_stock_unit_item_id:
|
||||
repository_row.repository_stock_value.repository_stock_unit_item_id,
|
||||
last_modified_by: user,
|
||||
comment: comment
|
||||
)
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def nulify_stock_consumption
|
||||
|
|
|
@ -60,6 +60,7 @@ Canaid::Permissions.register_for(Repository) do
|
|||
# repository: create/import record
|
||||
can :create_repository_rows do |user, repository|
|
||||
next false if repository.is_a?(BmtRepository)
|
||||
next false if repository.archived?
|
||||
|
||||
if repository.shared_with?(user.current_team)
|
||||
repository.shared_with_write?(user.current_team) && user.is_normal_user_or_admin_of_team?(user.current_team)
|
||||
|
|
15
app/serializers/api/v1/task_inventory_item_serializer.rb
Normal file
15
app/serializers/api/v1/task_inventory_item_serializer.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class TaskInventoryItemSerializer < InventoryItemSerializer
|
||||
attribute :stock_consumption, if: -> { object.repository_stock_cell.present? }
|
||||
|
||||
def stock_consumption
|
||||
object.my_module_repository_rows
|
||||
.find_by(my_module: instance_options[:my_module])
|
||||
.stock_consumption
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2964,6 +2964,9 @@ en:
|
|||
read_users_permission:
|
||||
title: "Permission denied"
|
||||
detail: "You don't have permission to read users on %{model}"
|
||||
update_stock_consumption_permission:
|
||||
title: "Permission denied"
|
||||
detail: "You don't have permisson to update stock consumption on this task item."
|
||||
record_not_found:
|
||||
title: "Not found"
|
||||
detail: "%{model} record with id %{id} not found in the specified scope"
|
||||
|
|
|
@ -750,7 +750,7 @@ Rails.application.routes.draw do
|
|||
resources :user_assignments,
|
||||
only: %i(index show update),
|
||||
controller: :task_user_assignments
|
||||
resources :task_inventory_items, only: %i(index show),
|
||||
resources :task_inventory_items, only: %i(index show update),
|
||||
path: 'items',
|
||||
as: :items
|
||||
resources :task_users, only: %i(index show),
|
||||
|
|
|
@ -80,5 +80,12 @@ FactoryBot.define do
|
|||
repository_cell.value ||= build(:repository_checklist_value, repository_cell: repository_cell)
|
||||
end
|
||||
end
|
||||
|
||||
trait :stock_value do
|
||||
repository_column { create :repository_column, :stock_type, repository: repository_row.repository }
|
||||
after(:build) do |repository_cell|
|
||||
repository_cell.value ||= build(:repository_stock_value, repository_cell: repository_cell)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,5 +50,9 @@ FactoryBot.define do
|
|||
trait :checklist_type do
|
||||
data_type { :RepositoryChecklistValue }
|
||||
end
|
||||
|
||||
trait :stock_type do
|
||||
data_type { :RepositoryStockValue }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
10
spec/factories/repository_stock_unit_item.rb
Normal file
10
spec/factories/repository_stock_unit_item.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :repository_stock_unit_item do
|
||||
sequence(:data) { |n| "u-#{n}" }
|
||||
repository_column
|
||||
created_by { create :user }
|
||||
last_modified_by { created_by }
|
||||
end
|
||||
end
|
15
spec/factories/repository_stock_values.rb
Normal file
15
spec/factories/repository_stock_values.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :repository_stock_value do
|
||||
created_by { create :user }
|
||||
last_modified_by { created_by }
|
||||
repository_stock_unit_item
|
||||
amount { 1000.0 }
|
||||
after(:build) do |repository_stock_value|
|
||||
repository_stock_value.repository_cell ||= build(:repository_cell,
|
||||
:stock_value,
|
||||
repository_stock_value: repository_stock_value)
|
||||
end
|
||||
end
|
||||
end
|
161
spec/requests/api/v1/task_inventory_items_controller_spec.rb
Normal file
161
spec/requests/api/v1/task_inventory_items_controller_spec.rb
Normal file
|
@ -0,0 +1,161 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::TasksController', type: :request do
|
||||
before :all do
|
||||
ApplicationSettings.instance.update(
|
||||
values: ApplicationSettings.instance.values.merge({"stock_management_enabled" => true})
|
||||
)
|
||||
MyModuleStatusFlow.ensure_default
|
||||
|
||||
@user = create(:user)
|
||||
@team = create(:team, created_by: @user)
|
||||
create(:user_team, user: @user, team: @team, role: 1)
|
||||
@owner_role = UserRole.find_by(name: I18n.t('user_roles.predefined.owner'))
|
||||
@project = create(:project, name: Faker::Name.unique.name,
|
||||
created_by: @user, team: @team)
|
||||
|
||||
@experiment = create(:experiment, created_by: @user,
|
||||
last_modified_by: @user, project: @project)
|
||||
|
||||
@my_module = create(
|
||||
:my_module,
|
||||
:with_due_date,
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
experiment: @experiment
|
||||
)
|
||||
|
||||
@repository = create(:repository)
|
||||
create(:user_team, user: @user, team: @repository.team, role: 1)
|
||||
#@repository_stock_column = create(:repository_column, :stock_type, repository: @repository)
|
||||
@repository_row = create(:repository_row, name: 'Test row', repository: @repository)
|
||||
@repository_stock_cell = create(
|
||||
:repository_cell,
|
||||
:stock_value,
|
||||
repository_row: @repository_row
|
||||
)
|
||||
|
||||
@my_module_repository_row = create(
|
||||
:mm_repository_row,
|
||||
my_module: @my_module,
|
||||
repository_row: @repository_row,
|
||||
assigned_by: @user,
|
||||
last_modified_by: @user,
|
||||
stock_consumption: 5.0
|
||||
)
|
||||
|
||||
@valid_headers =
|
||||
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
|
||||
end
|
||||
|
||||
describe 'GET task_inventory_items, #index' do
|
||||
it 'Response with correct task inventory items' do
|
||||
get(
|
||||
api_v1_team_project_experiment_task_items_url(
|
||||
team_id: @team.id,
|
||||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id
|
||||
),
|
||||
headers: @valid_headers
|
||||
)
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['data'].map { |item| item['id'] }).to(
|
||||
eq([@my_module_repository_row.id.to_s])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET task inventory items, #show' do
|
||||
it 'When valid request, user can read task inventory item' do
|
||||
get(
|
||||
api_v1_team_project_experiment_task_item_url(
|
||||
team_id: @team.id,
|
||||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
),
|
||||
headers: @valid_headers
|
||||
)
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
expect(JSON.parse(response.body)['data']['id']).to(
|
||||
eq(@my_module_repository_row.id.to_s)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH task inventory items, #update' do
|
||||
before :all do
|
||||
@valid_headers['Content-Type'] = 'application/json'
|
||||
end
|
||||
|
||||
let(:request_body) do
|
||||
{
|
||||
data: {
|
||||
type: 'inventory_items',
|
||||
attributes: {
|
||||
stock_consumption: 100.0,
|
||||
stock_consumption_comment: 'Some comment.'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when has valid params' do
|
||||
let(:action) do
|
||||
patch(api_v1_team_project_experiment_task_item_url(
|
||||
team_id: @team.id,
|
||||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers)
|
||||
end
|
||||
|
||||
it 'updates stock consumption' do
|
||||
action
|
||||
expect(@my_module_repository_row.reload.stock_consumption).to eq(100.0)
|
||||
end
|
||||
|
||||
it 'returns well formated response' do
|
||||
action
|
||||
|
||||
expect(json).to match(
|
||||
hash_including(
|
||||
data: hash_including(
|
||||
type: 'inventory_items',
|
||||
attributes: hash_including(stock_consumption: "100.0") )
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when has not valid params' do
|
||||
let(:action) do
|
||||
patch(api_v1_team_project_experiment_task_item_url(
|
||||
team_id: @team.id,
|
||||
project_id: @project.id,
|
||||
experiment_id: @experiment.id,
|
||||
task_id: @my_module.id,
|
||||
id: @my_module_repository_row.id
|
||||
),
|
||||
params: request_body.to_json,
|
||||
headers: @valid_headers)
|
||||
end
|
||||
|
||||
it 'renders 403 when repository row is locked' do
|
||||
@repository.update(archived: true)
|
||||
action
|
||||
|
||||
expect(response).to have_http_status(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue