diff --git a/app/assets/javascripts/admin/standing_orders/controllers/standing_order_controller.js.coffee b/app/assets/javascripts/admin/standing_orders/controllers/standing_order_controller.js.coffee index ea02dfc5f3..34f30f3d42 100644 --- a/app/assets/javascripts/admin/standing_orders/controllers/standing_order_controller.js.coffee +++ b/app/assets/javascripts/admin/standing_orders/controllers/standing_order_controller.js.coffee @@ -17,7 +17,7 @@ angular.module("admin.standingOrders").controller "StandingOrderController", ($s $scope.estimatedSubtotal = -> $scope.standingOrder.standing_line_items.reduce (subtotal, item) -> - item.price_with_fees * item.quantity + item.price_estimate * item.quantity , 0 $scope.estimatedTotal = -> diff --git a/app/assets/javascripts/admin/standing_orders/services/standing_order.js.coffee b/app/assets/javascripts/admin/standing_orders/services/standing_order.js.coffee index d1aa8e71a2..bf0c692e38 100644 --- a/app/assets/javascripts/admin/standing_orders/services/standing_order.js.coffee +++ b/app/assets/javascripts/admin/standing_orders/services/standing_order.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.standingOrders").factory "StandingOrder", ($injector, $http, StatusMessage, StandingOrderResource) -> +angular.module("admin.standingOrders").factory "StandingOrder", ($injector, $http, StatusMessage, InfoDialog, StandingOrderResource) -> new class StandingOrder standingOrder: new StandingOrderResource() errors: {} @@ -10,13 +10,11 @@ angular.module("admin.standingOrders").factory "StandingOrder", ($injector, $htt buildItem: (item) -> return false unless item.variant_id > 0 return false unless item.quantity > 0 - estimate_query = "/admin/variants/#{item.variant_id}/price_estimate?" - estimate_query += "shop_id=#{@standingOrder.shop_id};schedule_id=#{@standingOrder.schedule_id}" - $http.get(estimate_query).then (response) => - angular.extend(response.data, item) # Add variant_id and qty + data = angular.extend({}, item, { shop_id: @standingOrder.shop_id, schedule_id: @standingOrder.schedule_id }) + $http.post("/admin/standing_line_items/build", data).then (response) => @standingOrder.standing_line_items.push response.data , (response) => - alert(response.data.errors) + InfoDialog.open 'error', response.data.errors[0] save: -> StatusMessage.display 'progress', 'Saving...' diff --git a/app/controllers/admin/standing_line_items_controller.rb b/app/controllers/admin/standing_line_items_controller.rb new file mode 100644 index 0000000000..60e09f96e8 --- /dev/null +++ b/app/controllers/admin/standing_line_items_controller.rb @@ -0,0 +1,39 @@ +require 'open_food_network/permissions' +require 'open_food_network/order_cycle_permissions' + +module Admin + class StandingLineItemsController < ResourceController + before_filter :load_build_context, only: [:build] + + respond_to :json + + def build + return render json: { errors: ['Unauthorised'] }, status: :unauthorized unless @shop + if @variant + @standing_line_item.assign_attributes(params[:standing_line_item]) + fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(@shop, @order_cycle) if @order_cycle + OpenFoodNetwork::ScopeVariantToHub.new(@shop).scope(@variant) + render json: @standing_line_item, serializer: Api::Admin::StandingLineItemSerializer, fee_calculator: fee_calculator + else + render json: { errors: ["#{@shop.name} is not permitted to sell the selected product"] }, status: :unprocessable_entity + end + end + + private + + def permissions + OpenFoodNetwork::Permissions.new(spree_current_user) + end + + def load_build_context + @shop = Enterprise.managed_by(spree_current_user).find_by_id(params[:shop_id]) + @schedule = permissions.editable_schedules.find_by_id(params[:schedule_id]) + @order_cycle = @schedule.andand.current_or_next_order_cycle + @variant = Spree::Variant.stockable_by(@shop).find_by_id(params[:standing_line_item][:variant_id]) + end + + def new_actions + [:new, :create, :build] # Added build + end + end +end diff --git a/app/controllers/admin/standing_orders_controller.rb b/app/controllers/admin/standing_orders_controller.rb index 4b8d5a9b57..e28e0f10ea 100644 --- a/app/controllers/admin/standing_orders_controller.rb +++ b/app/controllers/admin/standing_orders_controller.rb @@ -5,7 +5,11 @@ module Admin respond_to :json respond_override create: { json: { - success: lambda { render_as_json @standing_order }, + success: lambda { + shop, next_oc = @standing_order.shop, @standing_order.schedule.current_or_next_order_cycle + fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(shop, next_oc) if shop && next_oc + render_as_json @standing_order, fee_calculator: fee_calculator + }, failure: lambda { render json: { errors: json_errors }, status: :unprocessable_entity } } } diff --git a/app/controllers/spree/admin/variants_controller_decorator.rb b/app/controllers/spree/admin/variants_controller_decorator.rb index 15b7ba8ee6..aaa5d914e7 100644 --- a/app/controllers/spree/admin/variants_controller_decorator.rb +++ b/app/controllers/spree/admin/variants_controller_decorator.rb @@ -1,16 +1,16 @@ -require 'open_food_network/permissions' - Spree::Admin::VariantsController.class_eval do helper 'spree/products' - before_filter :load_price_estimate_context, only: [:price_estimate] - - respond_to :json def search search_params = { :product_name_cont => params[:q], :sku_cont => params[:q] } @variants = Spree::Variant.where(is_master: false).ransack(search_params.merge(:m => 'or')).result + if params[:schedule_id].present? + schedule = Schedule.find params[:schedule_id] + @variants = @variants.in_schedule(schedule) + end + if params[:order_cycle_id].present? order_cycle = OrderCycle.find params[:order_cycle_id] @variants = @variants.in_order_cycle(order_cycle) @@ -26,16 +26,6 @@ Spree::Admin::VariantsController.class_eval do end end - def price_estimate - if @shop && @schedule && @order_cycle - fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(@shop, @order_cycle) - OpenFoodNetwork::ScopeVariantToHub.new(@shop).scope(@variant) - render json: @variant, serializer: Api::Admin::EstimatedVariantSerializer, fee_calculator: fee_calculator - else - render json: { errors: ["Unauthorized"], status: :unprocessable_entity } - end - end - def destroy @variant = Spree::Variant.find(params[:id]) @variant.delete # This line changed, as well as removal of following conditional @@ -55,22 +45,4 @@ Spree::Admin::VariantsController.class_eval do option_values.andand.each_value {|id| @object.option_values << OptionValue.find(id)} @object.save end - - private - - # Allows us to use a variant_id only to look up a price estimate - def parent_data - return super unless action == :price_estimate - nil - end - - def permissions - OpenFoodNetwork::Permissions.new(spree_current_user) - end - - def load_price_estimate_context - @shop = Enterprise.managed_by(spree_current_user).find_by_id(params[:shop_id]) - @schedule = permissions.editable_schedules.find_by_id(params[:schedule_id]) - @order_cycle = @schedule.andand.current_or_next_order_cycle - end end diff --git a/app/models/schedule.rb b/app/models/schedule.rb index f8e933b7b3..e123ea46d9 100644 --- a/app/models/schedule.rb +++ b/app/models/schedule.rb @@ -9,6 +9,6 @@ class Schedule < ActiveRecord::Base scope :with_coordinator, lambda { |enterprise| joins(:order_cycles).where('coordinator_id = ?', enterprise.id).select('DISTINCT schedules.*') } def current_or_next_order_cycle - order_cycles.order('orders_close_at ASC').first + order_cycles.where('orders_close_at > (?)', Time.now).order('orders_close_at ASC').first end end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 8b7a5f3fac..d42a7ec041 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -137,9 +137,6 @@ class AbilityDecorator end can [:create], Spree::Variant - can [:price_estimate], Spree::Variant do |variant| - OpenFoodNetwork::Permissions.new(user).visible_products.include? variant.product - end can [:admin, :index, :read, :edit, :update, :search, :delete, :destroy], Spree::Variant do |variant| OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier end @@ -255,10 +252,11 @@ class AbilityDecorator can [:create], Customer can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) - can [:admin, :new, :indicative_variant], StandingOrder + can [:admin, :new], StandingOrder can [:create], StandingOrder do |standing_order| user.enterprises.include?(standing_order.shop) end + can [:admin, :build], StandingLineItem end def add_relationship_management_abilities(user) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 34a0e68798..4ac25f02b3 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -117,6 +117,13 @@ Spree::Product.class_eval do end } + scope :stockable_by, lambda { |enterprise| + return where('1=0') unless enterprise.present? + permitted_producer_ids = EnterpriseRelationship.joins(:parent).permitting(enterprise) + .with_permission(:add_to_order_cycle).where(enterprises: { is_primary_producer: true }).pluck(:parent_id) + return where('spree_products.supplier_id IN (?)', [enterprise.id] | permitted_producer_ids) + } + # -- Methods diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index e1b81f6742..84589a7c85 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -42,6 +42,13 @@ Spree::Variant.class_eval do select('DISTINCT spree_variants.*') } + scope :in_schedule, lambda { |schedule| + joins(exchanges: { order_cycle: :schedule}). + merge(Exchange.outgoing). + where(schedules: { id: schedule}). + select('DISTINCT spree_variants.*') + } + scope :for_distribution, lambda { |order_cycle, distributor| where('spree_variants.id IN (?)', order_cycle.variants_distributed_by(distributor)) } @@ -58,6 +65,11 @@ Spree::Variant.class_eval do localize_number :price, :cost_price, :weight + scope :stockable_by, lambda { |enterprise| + return where("1=0") unless enterprise.present? + joins(:product).where(spree_products: { id: Spree::Product.stockable_by(enterprise).pluck(:id) }) + } + # Define sope as class method to allow chaining with other scopes filtering id. # In Rails 3, merging two scopes on the same column will consider only the last scope. def self.in_distributor(distributor) diff --git a/app/serializers/api/admin/estimated_variant_serializer.rb b/app/serializers/api/admin/estimated_variant_serializer.rb deleted file mode 100644 index ed7b2f3c74..0000000000 --- a/app/serializers/api/admin/estimated_variant_serializer.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Api::Admin::EstimatedVariantSerializer < ActiveModel::Serializer - attributes :variant_id, :description, :price_with_fees - - def variant_id - object.id - end - - def description - "#{object.product.name} - #{object.full_name}" - end - - def price_with_fees - (object.price + options[:fee_calculator].indexed_fees_for(object)).to_f - end -end diff --git a/app/serializers/api/admin/standing_line_item_serializer.rb b/app/serializers/api/admin/standing_line_item_serializer.rb new file mode 100644 index 0000000000..7975bd5b93 --- /dev/null +++ b/app/serializers/api/admin/standing_line_item_serializer.rb @@ -0,0 +1,15 @@ +class Api::Admin::StandingLineItemSerializer < ActiveModel::Serializer + attributes :id, :variant_id, :quantity, :description, :price_estimate + + def description + "#{object.variant.product.name} - #{object.variant.full_name}" + end + + def price_estimate + if options[:fee_calculator] + (object.variant.price + options[:fee_calculator].indexed_fees_for(object.variant)).to_f + else + "?" + end + end +end diff --git a/app/serializers/api/admin/standing_order_serializer.rb b/app/serializers/api/admin/standing_order_serializer.rb index 1d5b3d7db9..c0a66ce005 100644 --- a/app/serializers/api/admin/standing_order_serializer.rb +++ b/app/serializers/api/admin/standing_order_serializer.rb @@ -1,7 +1,7 @@ class Api::Admin::StandingOrderSerializer < ActiveModel::Serializer attributes :id, :shop_id, :customer_id, :schedule_id, :payment_method_id, :shipping_method_id, :begins_at, :ends_at - has_many :standing_line_items + has_many :standing_line_items, serializer: Api::Admin::StandingLineItemSerializer def begins_at object.begins_at.andand.strftime('%F') diff --git a/app/views/admin/standing_orders/_items.html.haml b/app/views/admin/standing_orders/_items.html.haml index 698f46bf84..1d54b24c0e 100644 --- a/app/views/admin/standing_orders/_items.html.haml +++ b/app/views/admin/standing_orders/_items.html.haml @@ -16,10 +16,10 @@ %tbody %tr.item{ ng: { repeat: 'item in standingOrder.standing_line_items', class: { even: 'even', odd: 'odd' } } } %td.description {{ item.description }} - %td.price.align-center {{ item.price_with_fees | currency }} + %td.price.align-center {{ item.price_estimate | currency }} %td.qty %input.qty{ name: 'quantity', type: 'number', min: 0, ng: { model: 'item.quantity' } } - %td.total.align-center {{ (item.price_with_fees * item.quantity) | currency }} + %td.total.align-center {{ (item.price_estimate * item.quantity) | currency }} %td.actions %a.delete-resource.icon_link.with-tip.icon-trash.no-text{ data: { confirm: "Are you sure?" }, :href => "javascript:void(0)" } %tbody#subtotal.no-border-top{"data-hook" => "admin_order_form_subtotal"} diff --git a/config/routes.rb b/config/routes.rb index 5768d155d5..4b856e86c7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -177,8 +177,10 @@ Openfoodnetwork::Application.routes.draw do resources :schedules, only: [:index, :create, :update, :destroy], format: :json - resources :standing_orders, only: [:new, :create] do - get :indicative_variant, on: :collection, format: :json + resources :standing_orders, only: [:new, :create] + + resources :standing_line_items, only: [], format: :json do + post :build, on: :collection end end @@ -292,10 +294,6 @@ Spree::Core::Engine.routes.prepend do get :print_ticket, on: :member get :managed, on: :collection end - - resources :variants, only: [], format: :json do - get :price_estimate, on: :member - end end resources :orders do diff --git a/lib/open_food_network/order_cycle_permissions.rb b/lib/open_food_network/order_cycle_permissions.rb index 45fe5eb256..3320309a19 100644 --- a/lib/open_food_network/order_cycle_permissions.rb +++ b/lib/open_food_network/order_cycle_permissions.rb @@ -118,7 +118,10 @@ module OpenFoodNetwork return Spree::Variant.where("1=0") unless @order_cycle if user_manages_coordinator_or(hub) + # TODO: Use variants_stockable_by(hub) for this? + # Any variants produced by the coordinator, for outgoing exchanges with itself + # TODO: isn't this completely redundant given the assignment of hub_variants below? coordinator_variants = [] if hub == @coordinator coordinator_variants = Spree::Variant.joins(:product).where('spree_products.supplier_id = (?)', @coordinator) diff --git a/spec/controllers/admin/standing_line_items_controller_spec.rb b/spec/controllers/admin/standing_line_items_controller_spec.rb new file mode 100644 index 0000000000..fbb51015d8 --- /dev/null +++ b/spec/controllers/admin/standing_line_items_controller_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe Admin::StandingLineItemsController, type: :controller do + include AuthenticationWorkflow + + + describe "build" do + let(:user) { create(:user) } + let!(:shop) { create(:enterprise, owner: user) } + let(:unmanaged_shop) { create(:enterprise) } + let!(:product) { create(:product) } + let!(:variant) { create(:variant, product: product, unit_value: '100', price: 15.00, option_values: []) } + let!(:enterprise_fee) { create(:enterprise_fee, amount: 3.50) } + let!(:order_cycle) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } + let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: shop, receiver: shop, variants: [variant], enterprise_fees: [enterprise_fee]) } + let!(:schedule) { create(:schedule, order_cycles: [order_cycle])} + let(:unmanaged_schedule) { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: unmanaged_shop)]) } + + context "json" do + let(:params) { { format: :json, standing_line_item: { quantity: 2, variant_id: variant.id } } } + + context 'as an enterprise user' do + before { allow(controller).to receive(:spree_current_user) { user } } + + context "but no shop_id is provided" do + it "returns an error" do + spree_post :build, params + expect(JSON.parse(response.body)['errors']).to eq ['Unauthorised'] + end + end + + context "and an unmanaged shop_id is provided" do + before { params.merge!({ shop_id: unmanaged_shop.id }) } + + it "returns an error" do + spree_post :build, params + expect(JSON.parse(response.body)['errors']).to eq ['Unauthorised'] + end + end + + context "where a managed shop_id is provided" do + before { params.merge!({ shop_id: shop.id }) } + + context "but the shop doesn't have permission to sell product in question" do + it "returns an error" do + spree_post :build, params + json_response = JSON.parse(response.body) + expect(json_response['errors']).to eq ["#{shop.name} is not permitted to sell the selected product"] + end + end + + context "and the shop has permission to sell the product in question" do + before do + product.update_attribute(:supplier_id, shop.id) + end + + context "but no schedule_id is provided" do + it "returns a serialized standing line item without a price estimate" do + spree_post :build, params + + json_response = JSON.parse(response.body) + expect(json_response['price_estimate']).to eq '?' + expect(json_response['quantity']).to eq 2 + expect(json_response['description']).to eq "#{variant.product.name} - 100g" + end + end + + context "but an unmanaged schedule_id is provided" do + before { params.merge!({ schedule_id: unmanaged_schedule.id }) } + + it "returns a serialized standing line item without a price estimate" do + spree_post :build, params + + json_response = JSON.parse(response.body) + expect(json_response['price_estimate']).to eq '?' + expect(json_response['quantity']).to eq 2 + expect(json_response['description']).to eq "#{variant.product.name} - 100g" + end + end + + context "and a managed schedule_id is provided" do + before { params.merge!({ schedule_id: schedule.id }) } + + it "returns a serialized standing line item with a price estimate" do + spree_post :build, params + + json_response = JSON.parse(response.body) + expect(json_response['price_estimate']).to eq 18.5 + expect(json_response['quantity']).to eq 2 + expect(json_response['description']).to eq "#{variant.product.name} - 100g" + end + end + end + end + end + end + end +end diff --git a/spec/controllers/spree/admin/variants_controller_spec.rb b/spec/controllers/spree/admin/variants_controller_spec.rb index 0952d489ec..36fd0463a2 100644 --- a/spec/controllers/spree/admin/variants_controller_spec.rb +++ b/spec/controllers/spree/admin/variants_controller_spec.rb @@ -35,88 +35,6 @@ module Spree assigns(:variants).should match_array [v1, v2] end end - - describe "price_estimate" do - let(:user) { create(:user) } - let!(:enterprise) { create(:enterprise, owner: user) } - let(:unmanaged_enterprise) { create(:enterprise) } - let!(:product) { create(:product) } - let!(:variant) { create(:variant, product: product, unit_value: '100', price: 15.00, option_values: []) } - let!(:enterprise_fee) { create(:enterprise_fee, amount: 3.50) } - let!(:order_cycle) { create(:simple_order_cycle, coordinator: enterprise, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } - let!(:outgoing_exchange) { order_cycle.exchanges.create(sender: enterprise, receiver: enterprise, variants: [variant], enterprise_fees: [enterprise_fee]) } - let!(:schedule) { create(:schedule, order_cycles: [order_cycle])} - let(:unmanaged_schedule) { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: unmanaged_enterprise)]) } - - context "json" do - let(:params) { { format: :json, id: variant.id } } - - context 'as an enterprise user' do - before { allow(controller).to receive(:spree_current_user) { user } } - - context "where I don't have access to the product in question" do - it "redirects to unauthorized" do - spree_get :price_estimate, params - expect(response).to redirect_to spree.unauthorized_path - end - end - - context "where I have access to the product in question" do - before do - product.update_attribute(:supplier_id, enterprise.id) - end - - context "but no shop_id is provided" do - before { params.merge!({ schedule_id: schedule.id }) } - - it "returns an error" do - spree_get :price_estimate, params - expect(JSON.parse(response.body)['errors']).to eq ['Unauthorized'] - end - end - - context "and an unmanaged shop_id is provided" do - before { params.merge!({ shop_id: unmanaged_enterprise.id, schedule_id: schedule.id }) } - - it "returns an error" do - spree_get :price_estimate, params - expect(JSON.parse(response.body)['errors']).to eq ['Unauthorized'] - end - end - - context "where no schedule_id is provided" do - before { params.merge!({ shop_id: enterprise.id }) } - - it "returns an error" do - spree_get :price_estimate, params - expect(JSON.parse(response.body)['errors']).to eq ['Unauthorized'] - end - end - - context "and an unmanaged schedule_id is provided" do - before { params.merge!({ shop_id: enterprise.id, schedule_id: unmanaged_schedule.id }) } - - it "returns an error" do - spree_get :price_estimate, params - expect(JSON.parse(response.body)['errors']).to eq ['Unauthorized'] - end - end - - context "where a managed shop_id and schedule_id are provided" do - before { params.merge!({ shop_id: enterprise.id, schedule_id: schedule.id }) } - - it "returns a price estimate for the variant" do - spree_get :price_estimate, params - - json_response = JSON.parse(response.body) - expect(json_response['price_with_fees']).to eq 18.5 - expect(json_response['description']).to eq "#{variant.product.name} - 100g" - end - end - end - end - end - end end end end diff --git a/spec/factories.rb b/spec/factories.rb index a1043df615..a52f9882bd 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -141,7 +141,7 @@ FactoryGirl.define do factory :standing_order, :class => StandingOrder do shop { FactoryGirl.create :enterprise } schedule - customer + customer { create(:customer, enterprise: shop) } payment_method shipping_method begins_at { 1.month.ago } diff --git a/spec/features/admin/standing_orders_spec.rb b/spec/features/admin/standing_orders_spec.rb index a1af6f9e6a..99bb95cdca 100644 --- a/spec/features/admin/standing_orders_spec.rb +++ b/spec/features/admin/standing_orders_spec.rb @@ -53,6 +53,14 @@ feature 'Standing Orders' do expect(page).to have_content 'Saved' }.to change(StandingOrder, :count).by(1) + # Prices are shown + within 'table#standing-line-items tr.item', match: :first do + expect(page).to have_selector 'td.description', text: "#{product.name} - #{variant.full_name}" + expect(page).to have_selector 'td.price', text: "$13.75" + expect(page).to have_input 'quantity', with: "2" + expect(page).to have_selector 'td.total', text: "$27.50" + end + # Basic properties of standing order are set standing_order = StandingOrder.last expect(standing_order.customer).to eq customer diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index fbb64ab3a7..fdf0099ae9 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -388,6 +388,26 @@ module Spree expect(products).to_not include new_variant.product, hidden_variant.product end end + + describe 'stockable_by' do + let(:shop) { create(:distributor_enterprise) } + let(:add_to_oc_producer) { create(:supplier_enterprise) } + let(:other_producer) { create(:supplier_enterprise) } + let!(:p1) { create(:simple_product, supplier: shop ) } + let!(:p2) { create(:simple_product, supplier: add_to_oc_producer ) } + let!(:p3) { create(:simple_product, supplier: other_producer ) } + + before do + create(:enterprise_relationship, parent: add_to_oc_producer, child: shop, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: other_producer, child: shop, permissions_list: [:manage_products]) + end + + it 'shows products produced by the enterprise and any producers granting P-OC' do + stockable_products = Spree::Product.stockable_by(shop) + expect(stockable_products).to include p1, p2 + expect(stockable_products).to_not include p3 + end + end end describe "finders" do diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index eae3767573..32259f547a 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -165,6 +165,26 @@ module Spree end end end + + describe 'stockable_by' do + let(:shop) { create(:distributor_enterprise) } + let(:add_to_oc_producer) { create(:supplier_enterprise) } + let(:other_producer) { create(:supplier_enterprise) } + let!(:v1) { create(:variant, product: create(:simple_product, supplier: shop ) ) } + let!(:v2) { create(:variant, product: create(:simple_product, supplier: add_to_oc_producer ) ) } + let!(:v3) { create(:variant, product: create(:simple_product, supplier: other_producer ) ) } + + before do + create(:enterprise_relationship, parent: add_to_oc_producer, child: shop, permissions_list: [:add_to_order_cycle]) + create(:enterprise_relationship, parent: other_producer, child: shop, permissions_list: [:manage_products]) + end + + it 'shows variants produced by the enterprise and any producers granting P-OC' do + stockable_variants = Spree::Variant.stockable_by(shop) + expect(stockable_variants).to include v1, v2 + expect(stockable_variants).to_not include v3 + end + end end describe "callbacks" do