diff --git a/app/controllers/spree/admin/base_controller_decorator.rb b/app/controllers/spree/admin/base_controller_decorator.rb new file mode 100644 index 0000000000..8e876513fa --- /dev/null +++ b/app/controllers/spree/admin/base_controller_decorator.rb @@ -0,0 +1,14 @@ +Spree::Admin::BaseController.class_eval do + # Override Spree method + # It's a shame Spree doesn't just let CanCan handle this in it's own way + def authorize_admin + if respond_to?(:model_class, true) && model_class + record = model_class + else + # this line changed to allow specificity for each non-resource controller (to be consistent with "authorize_resource :class => false", see https://github.com/ryanb/cancan/blob/60cf6a67ef59c0c9b63bc27ea0101125c4193ea6/lib/cancan/controller_resource.rb#L146) + record = self.class.to_s.sub("Controller", "").underscore.split('/').last.singularize.to_sym + end + authorize! :admin, record + authorize! action, record + end +end \ No newline at end of file diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 73339a3e10..798f2f6a44 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -27,7 +27,7 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - @search = Spree::Order.complete.search(params[:q]) + @search = Spree::Order.complete.managed_by(spree_current_user).search(params[:q]) orders = @search.result @report = OpenFoodWeb::OrderAndDistributorReport.new orders @@ -56,10 +56,10 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - @search = Spree::Order.complete.search(params[:q]) + @search = Spree::Order.complete.managed_by(spree_current_user).search(params[:q]) orders = @search.result - @distributors = Enterprise.is_distributor + @distributors = Enterprise.is_distributor.managed_by(spree_current_user) @report = OpenFoodWeb::GroupBuyReport.new orders unless params[:csv] @@ -87,11 +87,11 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - @search = Spree::Order.complete.search(params[:q]) + @search = Spree::Order.complete.managed_by(spree_current_user).search(params[:q]) orders = @search.result - line_items = orders.map { |o| o.line_items }.flatten + @line_items = orders.map { |o| o.line_items.managed_by(spree_current_user) }.flatten - @distributors = Enterprise.is_distributor + @distributors = Enterprise.is_distributor.managed_by(spree_current_user) @report_type = params[:report_type] case params[:report_type] @@ -219,7 +219,7 @@ Spree::Admin::ReportsController.class_eval do order_grouper = OpenFoodWeb::OrderGrouper.new rules, columns @header = header - @table = order_grouper.table(line_items) + @table = order_grouper.table(@line_items) csv_file_name = "bulk_coop.csv" render_report(@header, @table, params[:csv], csv_file_name) @@ -239,11 +239,11 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - @search = Spree::Order.complete.search(params[:q]) + @search = Spree::Order.complete.managed_by(spree_current_user).search(params[:q]) orders = @search.result payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments - @distributors = Enterprise.is_distributor + @distributors = Enterprise.is_distributor.managed_by(spree_current_user) @report_type = params[:report_type] case params[:report_type] @@ -343,19 +343,19 @@ Spree::Admin::ReportsController.class_eval do end params[:q][:meta_sort] ||= "completed_at.desc" - @search = Spree::Order.complete.search(params[:q]) + @search = Spree::Order.complete.managed_by(spree_current_user).search(params[:q]) orders = @search.result - line_items = orders.map { |o| o.line_items }.flatten + @line_items = orders.map { |o| o.line_items.managed_by(spree_current_user) }.flatten #payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments - - @distributors = Enterprise.is_distributor + + @distributors = Enterprise.is_distributor.managed_by(spree_current_user) #@suppliers = Enterprise.is_primary_producer @order_cycles = OrderCycle.active_or_complete.order('orders_close_at DESC') @report_type = params[:report_type] case params[:report_type] when "order_cycle_supplier_totals" - table_items = line_items + table_items = @line_items @include_blank = 'All' header = ["Supplier", "Product", "Variant", "Amount", "Cost per Unit", "Total Cost", "Status", "Incoming Transport"] @@ -377,7 +377,7 @@ Spree::Admin::ReportsController.class_eval do sort_by: proc { |variant| variant.options_text } } ] when "order_cycle_supplier_totals_by_distributor" - table_items = line_items + table_items = @line_items @include_blank = 'All' header = ["Supplier", "Product", "Variant", "To Distributor", "Amount", "Cost per Unit", "Total Cost", "Shipping Method"] @@ -409,7 +409,7 @@ Spree::Admin::ReportsController.class_eval do sort_by: proc { |distributor| distributor.name } } ] when "order_cycle_distributor_totals_by_supplier" - table_items = line_items + table_items = @line_items @include_blank = 'All' header = ["Distributor", "Supplier", "Product", "Variant", "Amount", "Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"] @@ -443,7 +443,7 @@ Spree::Admin::ReportsController.class_eval do sort_by: proc { |variant| variant.options_text } } ] when "order_cycle_customer_totals" - table_items = line_items + table_items = @line_items @include_blank = 'All' header = ["Distributor", "Customer", "Email", "Phone", "Product", "Variant", "Amount", "Item ($)", "Ship ($)", "Total ($)", "Paid?", "Packed?", "Shipped?"] @@ -485,7 +485,7 @@ Spree::Admin::ReportsController.class_eval do sort_by: proc { |variant| variant.options_text } } ] else - table_items = line_items + table_items = @line_items @include_blank = 'All' header = ["Supplier", "Product", "Variant", "Amount", "Cost per Unit", "Total Cost", "Status", "Incoming Transport"] diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index 3b7ee891b8..21715e468f 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -1,3 +1,17 @@ Spree::LineItem.class_eval do attr_accessible :max_quantity + + # -- Scopes + scope :managed_by, lambda { |user| + if user.has_spree_role?('admin') + scoped + else + # User has a distributor on the Order or supplier that supplies a LineItem + joins('LEFT OUTER JOIN spree_variants ON (spree_variants.id = spree_line_items.variant_id)'). + joins('LEFT OUTER JOIN spree_products ON (spree_products.id = spree_variants.product_id)'). + joins(:order). + where('spree_orders.distributor_id IN (?) OR spree_products.supplier_id IN (?)', user.enterprises, user.enterprises). + select('spree_line_items.*') + end + } end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 5c28f5f510..8864860bf4 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -20,7 +20,13 @@ Spree::Order.class_eval do if user.has_spree_role?('admin') scoped else - where('distributor_id IN (?)', user.enterprises) + # User has a distributor on an Order or supplier that supplies a Product to an Order + # NOTE: supplier Orders should use LineItem.managed_by to ensure they only see their own LineItems! + joins('LEFT OUTER JOIN spree_line_items ON (spree_line_items.order_id = spree_orders.id)'). + joins('LEFT OUTER JOIN spree_variants ON (spree_variants.id = spree_line_items.variant_id)'). + joins('LEFT OUTER JOIN spree_products ON (spree_products.id = spree_variants.product_id)'). + where('spree_orders.distributor_id IN (?) OR spree_products.supplier_id IN (?)', user.enterprises, user.enterprises). + select('DISTINCT spree_orders.*') end } diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb new file mode 100644 index 0000000000..9446524873 --- /dev/null +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -0,0 +1,168 @@ +require 'spec_helper' + +describe Spree::Admin::ReportsController do + + # Given two distributors and two suppliers + let(:ba) { create(:address) } + let(:si) { "pick up on thursday please" } + let(:s1) { create(:supplier_enterprise, address: create(:address)) } + let(:s2) { create(:supplier_enterprise, address: create(:address)) } + let(:s3) { create(:supplier_enterprise, address: create(:address)) } + let(:d1) { create(:distributor_enterprise, address: create(:address)) } + let(:d2) { create(:distributor_enterprise, address: create(:address)) } + let(:d3) { create(:distributor_enterprise, address: create(:address)) } + let(:p1) { create(:product, price: 12.34, distributors: [d1], supplier: s1) } + let(:p2) { create(:product, price: 23.45, distributors: [d2], supplier: s2) } + let(:p3) { create(:product, price: 34.56, distributors: [d3], supplier: s3) } + + # Given two order cycles with both distributors + let(:ocA) { create(:simple_order_cycle, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p1.master, p3.master]) } + let(:ocB) { create(:simple_order_cycle, distributors: [d1, d2], suppliers: [s1, s2, s3], variants: [p2.master]) } + + # orderA1 can only be accessed by s1, s3 and d1 + let!(:orderA1) do + order = create(:order, distributor: d1, bill_address: ba, special_instructions: si, order_cycle: ocA) + order.line_items << create(:line_item, variant: p1.master) + order.line_items << create(:line_item, variant: p3.master) + order.finalize! + order.save + order + end + # orderA2 can only be accessed by s2 and d2 + let!(:orderA2) do + order = create(:order, distributor: d2, bill_address: ba, special_instructions: si, order_cycle: ocA) + order.line_items << create(:line_item, variant: p2.master) + order.finalize! + order.save + order + end + # orderB1 can only be accessed by s1, s3 and d1 + let!(:orderB1) do + order = create(:order, distributor: d1, bill_address: ba, special_instructions: si, order_cycle: ocB) + order.line_items << create(:line_item, variant: p1.master) + order.line_items << create(:line_item, variant: p3.master) + order.finalize! + order.save + order + end + # orderB2 can only be accessed by s2 and d2 + let!(:orderB2) do + order = create(:order, distributor: d2, bill_address: ba, special_instructions: si, order_cycle: ocB) + order.line_items << create(:line_item, variant: p2.master) + order.finalize! + order.save + order + end + + # As a Distributor Enterprise user for d1 + context "Distributor Enterprise User" do + let(:user) do + user = create(:user) + user.spree_roles = [] + d1.enterprise_roles.build(user: user).save + user + end + + before :each do + controller.stub :spree_current_user => user + end + + describe 'Orders and Distributors' do + it "only shows orders that I have access to" do + spree_get :orders_and_distributors + + assigns(:search).result.should include(orderA1, orderB1) + assigns(:search).result.should_not include(orderA2) + assigns(:search).result.should_not include(orderB2) + end + end + + describe 'Group Buys' do + it "only shows orders that I have access to" do + spree_get :group_buys + + assigns(:search).result.should include(orderA1, orderB1) + assigns(:search).result.should_not include(orderA2) + assigns(:search).result.should_not include(orderB2) + end + end + + describe 'Bulk Coop' do + it "only shows orders that I have access to" do + spree_get :bulk_coop + + assigns(:search).result.should include(orderA1, orderB1) + assigns(:search).result.should_not include(orderA2) + assigns(:search).result.should_not include(orderB2) + end + end + + describe 'Payments' do + it "only shows orders that I have access to" do + spree_get :payments + + assigns(:search).result.should include(orderA1, orderB1) + assigns(:search).result.should_not include(orderA2) + assigns(:search).result.should_not include(orderB2) + end + end + + describe 'Order Cycles' do + it "only shows orders that I have access to" do + spree_get :order_cycles + + assigns(:search).result.should include(orderA1, orderB1) + assigns(:search).result.should_not include(orderA2) + assigns(:search).result.should_not include(orderB2) + end + + it "only shows the selected order cycle" do + spree_get :order_cycles, q: {order_cycle_id_eq: ocA.id} + + assigns(:search).result.should include(orderA1) + assigns(:search).result.should_not include(orderB1) + end + end + end + + # As a Supplier Enterprise user for s1 + context "Supplier" do + let(:user) do + user = create(:user) + user.spree_roles = [] + s1.enterprise_roles.build(user: user).save + user + end + + before :each do + controller.stub :spree_current_user => user + end + + describe 'Bulk Coop' do + it "only shows product line items that I am supplying" do + spree_get :bulk_coop + + assigns(:line_items).map(&:product).should include(p1) + assigns(:line_items).map(&:product).should_not include(p2) + assigns(:line_items).map(&:product).should_not include(p3) + end + end + + describe 'Order Cycles' do + it "only shows product line items that I am supplying" do + spree_get :order_cycles + + assigns(:line_items).map(&:product).should include(p1) + assigns(:line_items).map(&:product).should_not include(p2) + assigns(:line_items).map(&:product).should_not include(p3) + end + + it "only shows the selected order cycle" do + spree_get :order_cycles, q: {order_cycle_id_eq: ocA.id} + + assigns(:search).result.should include(orderA1) + assigns(:search).result.should_not include(orderB1) + end + end + end +end diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index 2a09ff29ba..7345a527b0 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -79,50 +79,4 @@ feature %q{ all('table#listing_orders tbody tr').count.should == 2 # Two rows per order end - scenario "Order cycle reports show only the selected order cycle" do - # Given two orders for two order cycles - @bill_address = create(:address) - @distributor_address = create(:address, :address1 => "distributor address", :city => 'The Shire', :zipcode => "1234") - d1 = create(:distributor_enterprise, :address => @distributor_address) - p1 = create(:product, price: 12.34) - p2 = create(:product, price: 23.45) - product_distribution = create(:product_distribution, :product => p1, :distributor => d1) - product_distribution = create(:product_distribution, :product => p2, :distributor => d1) - @shipping_instructions = "pick up on thursday please!" - - oc1 = create(:order_cycle, :distributors => [d1], :variants => [p1.master]) - oc2 = create(:order_cycle, :distributors => [d1], :variants => [p2.master]) - - # Given each order has one product, p1 for oc1; p2 for oc2 - @order11 = create(:order, :distributor => d1, :bill_address => @bill_address, :special_instructions => @shipping_instructions, :order_cycle => oc1) - @order12 = create(:order, :distributor => d1, :bill_address => @bill_address, :special_instructions => @shipping_instructions, :order_cycle => oc1) - @order21 = create(:order, :distributor => d1, :bill_address => @bill_address, :special_instructions => @shipping_instructions, :order_cycle => oc2) - @order22 = create(:order, :distributor => d1, :bill_address => @bill_address, :special_instructions => @shipping_instructions, :order_cycle => oc2) - - @order11.line_items << create(:line_item, variant: p1.master) - @order12.line_items << create(:line_item, variant: p1.master) - @order21.line_items << create(:line_item, variant: p2.master) - @order22.line_items << create(:line_item, variant: p2.master) - - @order11.finalize! - @order12.finalize! - @order21.finalize! - @order21.finalize! - - # When I select one order cycle - login_to_admin_section - click_link 'Reports' - click_link 'Order Cycle Reports' - - select oc1.name, from: 'q_order_cycle_id_eq' - select 'Order Cycle Supplier Totals', from: 'report_type' - click_button 'Search' - - # Then I should see the rows for order cycle 1 but not order cycle 2 - all('table#listing_orders tbody tr').count.should == 1 # One row per product - page.should have_content p1.price.to_s - page.should_not have_content p2.price.to_s - end - - end