diff --git a/app/services/search_orders.rb b/app/services/search_orders.rb index e8992d1b90..fe839fafe0 100644 --- a/app/services/search_orders.rb +++ b/app/services/search_orders.rb @@ -28,8 +28,7 @@ class SearchOrders end def search_query - base_query = ::Permissions::Order.new(current_user).editable_orders.not_empty - .or(::Permissions::Order.new(current_user).editable_orders.finalized) + base_query = ::Permissions::Order.new(current_user).editable_orders.not_empty.or(::Permissions::Order.new(current_user).editable_orders.finalized) return base_query if params[:shipping_method_id].blank? diff --git a/app/views/spree/admin/orders/index.html.haml b/app/views/spree/admin/orders/index.html.haml index 0deea9b1ab..26d9c002ac 100644 --- a/app/views/spree/admin/orders/index.html.haml +++ b/app/views/spree/admin/orders/index.html.haml @@ -3,7 +3,7 @@ - content_for :minimal_js, true -- if can?(:create, Spree::Order) +- if can?(:create, Spree::Order) && spree_current_user.can_manage_orders? - content_for :page_actions do %li = button_link_to t('.new_order'), spree.new_admin_order_url, icon: 'icon-plus', id: 'admin_new_order' diff --git a/spec/lib/open_food_network/scope_variants_for_search_spec.rb b/spec/lib/open_food_network/scope_variants_for_search_spec.rb index 277aaa6680..5c72fc4012 100644 --- a/spec/lib/open_food_network/scope_variants_for_search_spec.rb +++ b/spec/lib/open_food_network/scope_variants_for_search_spec.rb @@ -190,6 +190,22 @@ RSpec.describe OpenFoodNetwork::ScopeVariantsForSearch do to eq(["Product 1", "Product a", "Product b", "Product c"]) end end + + context "when search is done by the producer allowing to edit orders" do + let(:params) { { q: "product" } } + let(:producer) { create(:supplier_enterprise) } + let(:spree_current_user) { instance_double('Spree::User', enterprises: Enterprise.where(id: producer.id), can_manage_line_items_in_orders_only?: true) } + + it "returns all products distributed through distributors allowing producers to edit orders" do + v1.supplier_id = producer.id + v2.supplier_id = producer.id + v1.save! + v2.save! + + expect(result).to include v1, v2 + expect(result).not_to include v3, v4 + end + end end private diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index 1d05f99bd6..4c57b9d69c 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -1012,6 +1012,29 @@ RSpec.describe Enterprise do expect(expected).to include(sender) end end + + describe "#is_producer" do + context "when enterprise is_primary_producer and sells none" do + it "returns true" do + enterprise = build(:supplier_enterprise) + expect(enterprise.is_producer).to be true + end + end + + context "when enterprise is_primary_producer and sells any" do + it "returns false" do + enterprise = build(:enterprise, is_primary_producer: true, sells: "any") + expect(enterprise.is_producer).to be false + end + end + + context "when enterprise is_primary_producer and sells own" do + it "returns false" do + enterprise = build(:enterprise, is_primary_producer: true, sells: "own") + expect(enterprise.is_producer).to be false + end + end + end end def enterprise_name_error(owner_email) diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index ea9531f707..dc69933211 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -298,4 +298,39 @@ RSpec.describe Spree::User do end end end + + describe "#can_manage_line_items_in_orders_only?" do + let(:producer) { create(:supplier_enterprise) } + let(:order) { create(:order, distributor: distributor) } + + subject { user.can_manage_line_items_in_orders_only? } + + context "when user has producer" do + let(:user) { create(:user, enterprises: [producer]) } + + context "order containing their product" do + before do + order.line_items << create(:line_item, product: create(:product, supplier_id: producer.id)) + end + context "order distributor allow producer to edit orders" do + let(:distributor) do + create(:distributor_enterprise, enable_producers_to_edit_orders: true) + end + + it { is_expected.to be_truthy } + end + + context "order distributor doesn't allow producer to edit orders" do + let(:distributor) { create(:distributor_enterprise) } + it { is_expected.to be_falsey } + end + end + end + + context "no order containing their product" do + let(:user) { create(:user, enterprises: [create(:distributor_enterprise)]) } + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/services/permissions/order_spec.rb b/spec/services/permissions/order_spec.rb index f0cba2275b..fbf727b792 100644 --- a/spec/services/permissions/order_spec.rb +++ b/spec/services/permissions/order_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' module Permissions RSpec.describe Order do - let(:user) { double(:user, can_manage_line_items_in_orders_only?: false) } let(:permissions) { Permissions::Order.new(user) } let!(:basic_permissions) { OpenFoodNetwork::Permissions.new(user) } let(:distributor) { create(:distributor_enterprise) } @@ -28,68 +27,24 @@ module Permissions before { allow(OpenFoodNetwork::Permissions).to receive(:new) { basic_permissions } } - describe "finding orders that are visible in reports" do - let(:random_enterprise) { create(:distributor_enterprise) } - let(:order) { create(:order, order_cycle:, distributor: ) } - let!(:line_item) { create(:line_item, order:) } - let!(:producer) { create(:supplier_enterprise) } + context "with user cannot only manage line_items in orders" do + let(:user) { instance_double('Spree::User', can_manage_line_items_in_orders_only?: false) } - before do - allow(basic_permissions).to receive(:coordinated_order_cycles) { Enterprise.where("1=0") } - end + describe "finding orders that are visible in reports" do + let(:random_enterprise) { create(:distributor_enterprise) } + let(:order) { create(:order, order_cycle:, distributor: ) } + let!(:line_item) { create(:line_item, order:) } + let!(:producer) { create(:supplier_enterprise) } - context "as the hub through which the order was placed" do before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: distributor) - } + allow(basic_permissions).to receive(:coordinated_order_cycles) { Enterprise.where("1=0") } end - it "should let me see the order" do - expect(permissions.visible_orders).to include order - end - end - - context "as the coordinator of the order cycle through which the order was placed" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: coordinator) - } - allow(basic_permissions).to receive(:coordinated_order_cycles) { - OrderCycle.where(id: order_cycle) - } - end - - it "should let me see the order" do - expect(permissions.visible_orders).to include order - end - - context "with search params" do - let(:search_params) { { completed_at_gt: Time.zone.now.yesterday.strftime('%Y-%m-%d') } } - let(:permissions) { Permissions::Order.new(user, search_params) } - - it "only returns completed, non-cancelled orders within search filter range" do - expect(permissions.visible_orders).to include order_completed - expect(permissions.visible_orders).not_to include order_cancelled - expect(permissions.visible_orders).not_to include order_cart - expect(permissions.visible_orders).not_to include order_from_last_year - end - end - end - - context "as a producer which has granted P-OC to the distributor of an order" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: producer) - } - create(:enterprise_relationship, parent: producer, child: distributor, - permissions_list: [:add_to_order_cycle]) - end - - context "which contains my products" do + context "as the hub through which the order was placed" do before do - line_item.variant.supplier = producer - line_item.variant.save + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: distributor) + } end it "should let me see the order" do @@ -97,118 +52,206 @@ module Permissions end end - context "which does not contain my products" do + context "as the coordinator of the order cycle through which the order was placed" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: coordinator) + } + allow(basic_permissions).to receive(:coordinated_order_cycles) { + OrderCycle.where(id: order_cycle) + } + end + + it "should let me see the order" do + expect(permissions.visible_orders).to include order + end + + context "with search params" do + let(:search_params) { { completed_at_gt: Time.zone.now.yesterday.strftime('%Y-%m-%d') } } + let(:permissions) { Permissions::Order.new(user, search_params) } + + it "only returns completed, non-cancelled orders within search filter range" do + expect(permissions.visible_orders).to include order_completed + expect(permissions.visible_orders).not_to include order_cancelled + expect(permissions.visible_orders).not_to include order_cart + expect(permissions.visible_orders).not_to include order_from_last_year + end + end + end + + context "as a producer which has granted P-OC to the distributor of an order" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: producer) + } + create(:enterprise_relationship, parent: producer, child: distributor, + permissions_list: [:add_to_order_cycle]) + end + + context "which contains my products" do + before do + line_item.variant.supplier = producer + line_item.variant.save + end + + it "should let me see the order" do + expect(permissions.visible_orders).to include order + end + end + + context "which does not contain my products" do + it "should not let me see the order" do + expect(permissions.visible_orders).not_to include order + end + end + end + + context "as an enterprise that is a distributor in the order cycle, " \ + "but not the distributor of the order" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: random_enterprise) + } + end + it "should not let me see the order" do expect(permissions.visible_orders).not_to include order end end end - context "as an enterprise that is a distributor in the order cycle, " \ - "but not the distributor of the order" do + describe "finding line items that are visible in reports" do + let(:random_enterprise) { create(:distributor_enterprise) } + let(:order) { create(:order, order_cycle:, distributor: ) } + let!(:line_item1) { create(:line_item, order:) } + let!(:line_item2) { create(:line_item, order:) } + let!(:producer) { create(:supplier_enterprise) } + before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: random_enterprise) - } + allow(basic_permissions).to receive(:coordinated_order_cycles) { Enterprise.where("1=0") } end - it "should not let me see the order" do - expect(permissions.visible_orders).not_to include order + context "as the hub through which the parent order was placed" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: distributor) + } + end + + it "should let me see the line_items" do + expect(permissions.visible_line_items).to include line_item1, line_item2 + end + end + + context "as the coordinator of the order cycle through which the parent order was placed" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: coordinator) + } + allow(basic_permissions).to receive(:coordinated_order_cycles) { + OrderCycle.where(id: order_cycle) + } + end + + it "should let me see the line_items" do + expect(permissions.visible_line_items).to include line_item1, line_item2 + end + end + + context "as the manager producer which has granted P-OC to the distributor " \ + "of the parent order" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: producer) + } + create(:enterprise_relationship, parent: producer, child: distributor, + permissions_list: [:add_to_order_cycle]) + + line_item1.variant.supplier = producer + line_item1.variant.save + end + + it "should let me see the line_items pertaining to variants I produce" do + ps = permissions.visible_line_items + expect(ps).to include line_item1 + expect(ps).not_to include line_item2 + end + end + + context "as an enterprise that is a distributor in the order cycle, " \ + "but not the distributor of the parent order" do + before do + allow(basic_permissions).to receive(:managed_enterprises) { + Enterprise.where(id: random_enterprise) + } + end + + it "should not let me see the line_items" do + expect(permissions.visible_line_items).not_to include line_item1, line_item2 + end + end + + context "with search params" do + let!(:line_item3) { create(:line_item, order: order_completed) } + let!(:line_item4) { create(:line_item, order: order_cancelled) } + let!(:line_item5) { create(:line_item, order: order_cart) } + let!(:line_item6) { create(:line_item, order: order_from_last_year) } + + let(:search_params) { { completed_at_gt: Time.zone.now.yesterday.strftime('%Y-%m-%d') } } + let(:permissions) { Permissions::Order.new(user, search_params) } + + before do + allow(user).to receive(:admin?) { "admin" } + end + + it "only returns line items from completed, " \ + "non-cancelled orders within search filter range" do + expect(permissions.visible_line_items).to include order_completed.line_items.first + expect(permissions.visible_line_items).not_to include order_cancelled.line_items.first + expect(permissions.visible_line_items).not_to include order_cart.line_items.first + expect(permissions.visible_line_items) + .not_to include order_from_last_year.line_items.first + end end end end - describe "finding line items that are visible in reports" do - let(:random_enterprise) { create(:distributor_enterprise) } - let(:order) { create(:order, order_cycle:, distributor: ) } - let!(:line_item1) { create(:line_item, order:) } - let!(:line_item2) { create(:line_item, order:) } - let!(:producer) { create(:supplier_enterprise) } - - before do - allow(basic_permissions).to receive(:coordinated_order_cycles) { Enterprise.where("1=0") } + context "with user can only manage line_items in orders" do + let(:producer) { create(:supplier_enterprise) } + let(:user) do + create(:user, enterprises: [producer]) end + let!(:order_by_distributor_allow_edits) do + order = create( + :order_with_line_items, + distributor: create( + :distributor_enterprise, + enable_producers_to_edit_orders: true + ), + line_items_count: 1 + ) + order.line_items.first.variant.update_attribute(:supplier_id, producer.id) - context "as the hub through which the parent order was placed" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: distributor) - } - end - - it "should let me see the line_items" do - expect(permissions.visible_line_items).to include line_item1, line_item2 + order + end + let!(:order_by_distributor_disallow_edits) do + create( + :order_with_line_items, + distributor: create(:distributor_enterprise), + line_items_count: 1 + ) + end + describe "#editable_orders" do + it "returns orders where the distributor allows producers to edit" do + expect(permissions.editable_orders.count).to eq 1 + expect(permissions.editable_orders).to include order_by_distributor_allow_edits end end - context "as the coordinator of the order cycle through which the parent order was placed" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: coordinator) - } - allow(basic_permissions).to receive(:coordinated_order_cycles) { - OrderCycle.where(id: order_cycle) - } - end - - it "should let me see the line_items" do - expect(permissions.visible_line_items).to include line_item1, line_item2 - end - end - - context "as the manager producer which has granted P-OC to the distributor " \ - "of the parent order" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: producer) - } - create(:enterprise_relationship, parent: producer, child: distributor, - permissions_list: [:add_to_order_cycle]) - - line_item1.variant.supplier = producer - line_item1.variant.save - end - - it "should let me see the line_items pertaining to variants I produce" do - ps = permissions.visible_line_items - expect(ps).to include line_item1 - expect(ps).not_to include line_item2 - end - end - - context "as an enterprise that is a distributor in the order cycle, " \ - "but not the distributor of the parent order" do - before do - allow(basic_permissions).to receive(:managed_enterprises) { - Enterprise.where(id: random_enterprise) - } - end - - it "should not let me see the line_items" do - expect(permissions.visible_line_items).not_to include line_item1, line_item2 - end - end - - context "with search params" do - let!(:line_item3) { create(:line_item, order: order_completed) } - let!(:line_item4) { create(:line_item, order: order_cancelled) } - let!(:line_item5) { create(:line_item, order: order_cart) } - let!(:line_item6) { create(:line_item, order: order_from_last_year) } - - let(:search_params) { { completed_at_gt: Time.zone.now.yesterday.strftime('%Y-%m-%d') } } - let(:permissions) { Permissions::Order.new(user, search_params) } - - before do - allow(user).to receive(:admin?) { "admin" } - end - - it "only returns line items from completed, " \ - "non-cancelled orders within search filter range" do - expect(permissions.visible_line_items).to include order_completed.line_items.first - expect(permissions.visible_line_items).not_to include order_cancelled.line_items.first - expect(permissions.visible_line_items).not_to include order_cart.line_items.first - expect(permissions.visible_line_items) - .not_to include order_from_last_year.line_items.first + describe "#editable_line_items" do + it "returns line items from orders where the distributor allows producers to edit" do + expect(permissions.editable_line_items.count).to eq 1 + expect(permissions.editable_line_items.first.order).to eq order_by_distributor_allow_edits end end end diff --git a/spec/system/admin/orders/producer_actions_spec.rb b/spec/system/admin/orders/producer_actions_spec.rb new file mode 100644 index 0000000000..026bae260a --- /dev/null +++ b/spec/system/admin/orders/producer_actions_spec.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe 'As a producer who have the ability to update orders' do + include AdminHelper + include AuthenticationHelper + include WebHelper + + let!(:supplier1) { create(:supplier_enterprise, name: 'My supplier1') } + let!(:supplier2) { create(:supplier_enterprise, name: 'My supplier2') } + let!(:supplier1_v1) { create(:variant, supplier_id: supplier1.id) } + let!(:supplier1_v2) { create(:variant, supplier_id: supplier1.id) } + let!(:supplier2_v1) { create(:variant, supplier_id: supplier2.id) } + let(:order_cycle) do + create(:simple_order_cycle, distributors: [distributor], variants: [supplier1_v1, supplier1_v2]) + end + let!(:order_containing_supplier1_products) do + o = create( + :completed_order_with_totals, + distributor:, order_cycle:, + user: supplier1_ent_user, line_items_count: 1 + ) + o.line_items.first.update_columns(variant_id: supplier1_v1.id) + o + end + let!(:order_containing_supplier2_v1_products) do + o = create( + :completed_order_with_totals, + distributor:, order_cycle:, + user: supplier2_ent_user, line_items_count: 1 + ) + o.line_items.first.update_columns(variant_id: supplier2_v1.id) + o + end + let(:supplier1_ent_user) { create(:user, enterprises: [supplier1]) } + let(:supplier2_ent_user) { create(:user, enterprises: [supplier2]) } + + context "As supplier1 enterprise user" do + before { login_as(supplier1_ent_user) } + let(:order) { order_containing_supplier1_products } + let(:user) { supplier1_ent_user } + + describe 'orders index page' do + before { visit spree.admin_orders_path } + + context "when no distributor allow the producer to edit orders" do + let(:distributor) { create(:distributor_enterprise) } + + it "should not allow producer to view orders page" do + expect(page).to have_content 'Unauthorized' + end + end + + context "when distributor allows the producer to edit orders" do + let(:distributor) { create(:distributor_enterprise, enable_producers_to_edit_orders: true) } + it "should not allow to add new orders" do + expect(page).not_to have_link('New Order') + end + + context "when distributor doesn't allow to view customer details" do + it "should allow producer to view orders page with HIDDEN customer details" do + within('#listing_orders tbody') do + expect(page).to have_selector('tr', count: 1) # Only one order + # One for Email, one for Name + expect(page).to have_selector('td', text: 'HIDDEN', count: 2) + end + end + end + + context "when distributor allows to view customer details" do + let(:distributor) do + create( + :distributor_enterprise, + enable_producers_to_edit_orders: true, + show_customer_names_to_suppliers: true + ) + end + it "should allow producer to view orders page with customer details" do + within('#listing_orders tbody') do + name = order.bill_address&.full_name_for_sorting + email = order.email + expect(page).to have_selector('tr', count: 1) # Only one order + expect(page).to have_selector('td', text: name, count: 1) + expect(page).to have_selector('td', text: email, count: 1) + end + end + end + end + end + + describe 'orders edit page' do + before { visit spree.edit_admin_order_path(order) } + + context "when no distributor allow the producer to edit orders" do + let(:distributor) { create(:distributor_enterprise) } + + it "should not allow producer to view orders page" do + expect(page).to have_content 'Unauthorized' + end + end + + context "when distributor allows to edit orders" do + let(:distributor) { create(:distributor_enterprise, enable_producers_to_edit_orders: true) } + let(:product) { supplier1_v2.product } + + it "should allow me to manage my products in the order" do + expect(page).to have_content 'Add Product' + + # Add my product + add_product(product) + expect_product_change(product, :add) + + # Edit my product + edit_product(product) + expect_product_change(product, :update, 2) + + # Delete my product + delete_product(product) + expect_product_change(product, :remove) + end + end + + + def expect_product_change(product, action, expected_qty = 0) + within('table.index') do + # JS for this page sometimes take more than 2 seconds (default timeout for cappybara) + timeout = 5 + item_name_selector = 'tbody tr td.item-name' + quantity_selector = 'tbody tr td.item-qty-show' + + case action + when :add + expect(page).to have_selector item_name_selector, text: product.name, wait: timeout + when :update + expect(page).to have_selector quantity_selector, text: expected_qty, wait: timeout + when :remove + expect(page).not_to have_selector item_name_selector, text: product.name, wait: timeout + else + raise 'Invalid action' + end + end + end + + def add_product(product) + select2_select product.name, from: 'add_variant_id', search: true + find('button.add_variant').click + end + + def edit_product(product) + find('a.edit-item.icon_link.icon-edit.no-text.with-tip').click + fill_in 'quantity', with: 2 + find("a[data-variant-id='#{product.variants.last.id}'][data-action='save']").click + end + + def delete_product(product) + find("a[data-variant-id='#{product.variants.last.id}'][data-action='remove']").click + click_button 'OK' + end + + end + end + +end