diff --git a/app/controllers/spree/admin/variants_controller.rb b/app/controllers/spree/admin/variants_controller.rb index 765a011dff3..deda2718675 100644 --- a/app/controllers/spree/admin/variants_controller.rb +++ b/app/controllers/spree/admin/variants_controller.rb @@ -64,7 +64,10 @@ def update end def search - scoper = OpenFoodNetwork::ScopeVariantsForSearch.new(variant_search_params) + scoper = OpenFoodNetwork::ScopeVariantsForSearch.new( + variant_search_params, + spree_current_user + ) @variants = scoper.search render json: @variants, each_serializer: ::Api::Admin::VariantSerializer end diff --git a/app/helpers/spree/admin/orders_helper.rb b/app/helpers/spree/admin/orders_helper.rb index fc4f21baa82..cd0afab2f74 100644 --- a/app/helpers/spree/admin/orders_helper.rb +++ b/app/helpers/spree/admin/orders_helper.rb @@ -142,6 +142,28 @@ def quantity_field_tag(manifest_item) end number_field_tag :quantity, manifest_item.quantity, html_options end + + def prepare_shipment_manifest(shipment) + manifest = shipment.manifest + + if distributor_allows_order_editing?(shipment.order) + supplier_ids = spree_current_user.enterprises.ids + manifest.select! { |mi| supplier_ids.include?(mi.variant.supplier_id) } + end + + manifest + end + + def distributor_allows_order_editing?(order) + order.distributor&.enable_producers_to_edit_orders && + spree_current_user.can_manage_line_items_in_orders_only? + end + + def display_value_for_producer(order, value) + return value unless distributor_allows_order_editing?(order) + + order.distributor&.show_customer_names_to_suppliers ? value : t("admin.reports.hidden") + end end end end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 77dcb2c1958..1fb2d61c8d3 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -375,6 +375,10 @@ def is_hub sells == 'any' end + def is_producer + is_primary_producer && sells == 'none' + end + # Simplify enterprise categories for frontend logic and icons, and maybe other things. def category # Make this crazy logic human readable so we can argue about it sanely. diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index 3bce37ff4d5..14a895bd7c4 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -47,7 +47,11 @@ def initialize(user) add_group_management_abilities user if can_manage_groups? user add_product_management_abilities user if can_manage_products? user add_order_cycle_management_abilities user if can_manage_order_cycles? user - add_order_management_abilities user if can_manage_orders? user + if can_manage_orders? user + add_order_management_abilities user + elsif can_manage_line_items_in_orders? user + add_manage_line_items_abilities user + end add_relationship_management_abilities user if can_manage_relationships? user end @@ -81,7 +85,13 @@ def can_manage_order_cycles?(user) # Users can manage orders if they have a sells own/any enterprise. def can_manage_orders?(user) - ( user.enterprises.map(&:sells) & %w(own any) ).any? + user.can_manage_orders? + end + + # Users can manage line items in orders if they have producer enterprise and + # any of order distributors allow them to edit their orders. + def can_manage_line_items_in_orders?(user) + user.can_manage_line_items_in_orders? end def can_manage_relationships?(user) @@ -343,6 +353,26 @@ def add_order_management_abilities(user) end end + def add_manage_line_items_abilities(user) + can_edit_order_lambda = lambda do |order| + return unless order.distributor&.enable_producers_to_edit_orders + + order.variants.any? { |variant| user.enterprises.ids.include?(variant.supplier_id) } + end + + can [:admin, :read, :index, :edit, :update, :bulk_management], Spree::Order do |order| + can_edit_order_lambda.call(order) + end + can [:admin, :index, :create, :destroy, :update], Spree::LineItem do |item| + can_edit_order_lambda.call(item.order) + end + can [:index, :create, :add, :read, :edit, :update], Spree::Shipment do |shipment| + can_edit_order_lambda.call(shipment.order) + end + + can [:visible], Enterprise + end + def add_relationship_management_abilities(user) can [:admin, :index, :create], EnterpriseRelationship can [:destroy], EnterpriseRelationship do |enterprise_relationship| diff --git a/app/models/spree/line_item.rb b/app/models/spree/line_item.rb index 0b1e0e0abbd..8174832a15c 100644 --- a/app/models/spree/line_item.rb +++ b/app/models/spree/line_item.rb @@ -108,6 +108,13 @@ class LineItem < ApplicationRecord where(spree_adjustments: { id: nil }) } + scope :editable_by_producers, ->(enterprises_ids) { + joins(:variant, order: :distributor).where( + distributor: { enable_producers_to_edit_orders: true }, + spree_variants: { supplier_id: enterprises_ids } + ) + } + def copy_price return unless variant diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 66bfdb3db33..81f0d68b61e 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -169,6 +169,14 @@ def states scope :invoiceable, -> { where(state: [:complete, :resumed]) } scope :by_state, lambda { |state| where(state:) } scope :not_state, lambda { |state| where.not(state:) } + scope :editable_by_producers, ->(enterprises) { + joins( + :distributor, line_items: :supplier + ).where( + supplier: { id: enterprises.ids }, + distributor: { enable_producers_to_edit_orders: true } + ) + } def initialize(*_args) @checkout_processing = nil diff --git a/app/models/spree/user.rb b/app/models/spree/user.rb index 72f2a3c80ae..7e09f5fa787 100644 --- a/app/models/spree/user.rb +++ b/app/models/spree/user.rb @@ -147,6 +147,25 @@ def affiliate_enterprises Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready) end + # Users can manage orders if they have a sells own/any enterprise. or is admin + def can_manage_orders? + @can_manage_orders ||= (enterprises.pluck(:sells).intersect?(%w(own any)) or admin?) + end + + # Users can manage line items in orders if they have producer enterprise and + # any of order distributors allow them to edit their orders. + def can_manage_line_items_in_orders? + @can_manage_line_items_in_orders ||= begin + has_any_producer = enterprises.any?(&:is_producer) + has_producer_editable_orders = Spree::Order.editable_by_producers(enterprises).exists? + has_any_producer && has_producer_editable_orders + end + end + + def can_manage_line_items_in_orders_only? + !can_manage_orders? && can_manage_line_items_in_orders? + end + protected def password_required? diff --git a/app/services/permissions/order.rb b/app/services/permissions/order.rb index d4c2f4b0ea5..29d63b5a2f2 100644 --- a/app/services/permissions/order.rb +++ b/app/services/permissions/order.rb @@ -23,9 +23,16 @@ def visible_orders # Any orders that the user can edit def editable_orders - orders = Spree::Order. - where(managed_orders_where_values. - or(coordinated_orders_where_values)) + orders = if @user.can_manage_line_items_in_orders_only? + Spree::Order.joins(:distributor).where( + id: produced_orders.select(:id), + distributor: { enable_producers_to_edit_orders: true } + ) + else + Spree::Order.where( + managed_orders_where_values.or(coordinated_orders_where_values) + ) + end filtered_orders(orders) end @@ -36,7 +43,13 @@ def visible_line_items # Any line items that I can edit def editable_line_items - Spree::LineItem.where(order_id: editable_orders.select(:id)) + if @user.can_manage_line_items_in_orders_only? + Spree::LineItem.editable_by_producers( + @permissions.managed_enterprises.select("enterprises.id") + ) + else + Spree::LineItem.where(order_id: editable_orders.select(:id)) + end end private @@ -79,6 +92,13 @@ def coordinated_orders_where_values reduce(:and) end + def produced_orders + Spree::Order.with_line_items_variants_and_products_outer. + where( + spree_variants: { supplier_id: @permissions.managed_enterprises.select("enterprises.id") } + ) + end + def produced_orders_where_values Spree::Order.with_line_items_variants_and_products_outer. where( diff --git a/app/services/permitted_attributes/enterprise.rb b/app/services/permitted_attributes/enterprise.rb index 1dd5b5a8385..e5bcc7aa55d 100644 --- a/app/services/permitted_attributes/enterprise.rb +++ b/app/services/permitted_attributes/enterprise.rb @@ -38,6 +38,7 @@ def self.basic_permitted_attributes :preferred_product_low_stock_display, :hide_ofn_navigation, :white_label_logo, :white_label_logo_link, :hide_groups_tab, :external_billing_id, + :enable_producers_to_edit_orders ] end end diff --git a/app/views/admin/enterprises/form/_shop_preferences.html.haml b/app/views/admin/enterprises/form/_shop_preferences.html.haml index 5b750015143..851b99deede 100644 --- a/app/views/admin/enterprises/form/_shop_preferences.html.haml +++ b/app/views/admin/enterprises/form/_shop_preferences.html.haml @@ -111,3 +111,15 @@ .five.columns.omega = radio_button :enterprise, :show_customer_names_to_suppliers, false = label :enterprise_show_customer_names_to_suppliers, t('.customer_names_false'), value: :false + +.row + .three.columns.alpha + %label= t('.producers_to_edit_orders') + %div{'ofn-with-tip' => t('.producers_to_edit_orders_tip')} + %a= t 'admin.whats_this' + .three.columns + = radio_button :enterprise, :enable_producers_to_edit_orders, true + = label :enterprise_enable_producers_to_edit_orders, t('.producers_edit_orders_true'), value: :true + .five.columns.omega + = radio_button :enterprise, :enable_producers_to_edit_orders, false + = label :enterprise_enable_producers_to_edit_orders, t('.producers_edit_orders_false'), value: :false diff --git a/app/views/spree/admin/orders/_form.html.haml b/app/views/spree/admin/orders/_form.html.haml index 02b1acf8412..52c5c708c8c 100644 --- a/app/views/spree/admin/orders/_form.html.haml +++ b/app/views/spree/admin/orders/_form.html.haml @@ -8,23 +8,24 @@ - if @order.shipments.any? = render :partial => "spree/admin/orders/shipment", :collection => @order.shipments, :locals => { :order => @order } - - if @order.line_items.exists? - = render partial: "spree/admin/orders/note", locals: { order: @order } + - if spree_current_user.can_manage_orders? + - if @order.line_items.exists? + = render partial: "spree/admin/orders/note", locals: { order: @order } - = render :partial => "spree/admin/orders/_form/adjustments", :locals => { :adjustments => @order.line_item_adjustments, :title => t(".line_item_adjustments")} - = render :partial => "spree/admin/orders/_form/adjustments", :locals => { :adjustments => order_adjustments_for_display(@order), :title => t(".order_adjustments")} + = render :partial => "spree/admin/orders/_form/adjustments", :locals => { :adjustments => @order.line_item_adjustments, :title => t(".line_item_adjustments")} + = render :partial => "spree/admin/orders/_form/adjustments", :locals => { :adjustments => order_adjustments_for_display(@order), :title => t(".order_adjustments")} - - if @order.line_items.exists? - %fieldset#order-total.no-border-bottom.order-details-total - %legend{ align: 'center' }= t(".order_total") - %span.order-total= @order.display_total + - if @order.line_items.exists? + %fieldset#order-total.no-border-bottom.order-details-total + %legend{ align: 'center' }= t(".order_total") + %span.order-total= @order.display_total - = form_for @order, url: spree.admin_order_url(@order), method: :put do |f| - = render partial: 'spree/admin/orders/_form/distribution_fields' + = form_for @order, url: spree.admin_order_url(@order), method: :put do |f| + = render partial: 'spree/admin/orders/_form/distribution_fields' - .filter-actions.actions{"ng-show" => "distributionChosen()"} - = button t(:update_and_recalculate_fees), 'icon-refresh' - = link_to_with_icon 'button icon-arrow-left', t(:back), spree.admin_orders_url + .filter-actions.actions{"ng-show" => "distributionChosen()"} + = button t(:update_and_recalculate_fees), 'icon-refresh' + = link_to_with_icon 'button icon-arrow-left', t(:back), spree.admin_orders_url = javascript_tag do var order_number = '#{@order.number}'; diff --git a/app/views/spree/admin/orders/_shipment.html.haml b/app/views/spree/admin/orders/_shipment.html.haml index 599900c295c..acb584571ae 100644 --- a/app/views/spree/admin/orders/_shipment.html.haml +++ b/app/views/spree/admin/orders/_shipment.html.haml @@ -62,7 +62,7 @@ - if shipment.fee_adjustment.present? && shipment.can_modify? %td.actions - - if can? :update, shipment + - if can? :update, shipment.shipping_method = link_to '', '', :class => 'edit-method icon_link icon-edit no-text with-tip', :data => { :action => 'edit' }, :title => Spree.t('edit') %tr.edit-tracking.hidden.total @@ -86,7 +86,7 @@ = Spree.t(:no_tracking_present) %td.actions - - if can?(:update, shipment) && shipment.can_modify? + - if spree_current_user.can_manage_orders? && can?(:update, shipment) && shipment.can_modify? = link_to '', '', :class => 'edit-tracking icon_link icon-edit no-text with-tip', :data => { :action => 'edit' }, :title => Spree.t('edit') - if shipment.tracking.present? = link_to '', '', :class => 'delete-tracking icon_link icon-trash no-text with-tip', :data => { 'shipment-number' => shipment.number, :action => 'remove' }, :title => Spree.t('delete') diff --git a/app/views/spree/admin/orders/_shipment_manifest.html.haml b/app/views/spree/admin/orders/_shipment_manifest.html.haml index fd95d95dbcb..d08cf371c90 100644 --- a/app/views/spree/admin/orders/_shipment_manifest.html.haml +++ b/app/views/spree/admin/orders/_shipment_manifest.html.haml @@ -1,4 +1,4 @@ -- shipment.manifest.each do |item| +- prepare_shipment_manifest(shipment).each do |item| - line_item = order.find_line_item_by_variant(item.variant) - if line_item.present? diff --git a/app/views/spree/admin/orders/_table_row.html.haml b/app/views/spree/admin/orders/_table_row.html.haml index 7cc4aae10ff..40605d22f97 100644 --- a/app/views/spree/admin/orders/_table_row.html.haml +++ b/app/views/spree/admin/orders/_table_row.html.haml @@ -34,10 +34,11 @@ %span.state{ class: order.shipment_state.to_s} = t('js.admin.orders.shipment_states.' + order.shipment_state.to_s) %td - %a{ href: "mailto:#{order.email}", target: "_blank" } - = order.email + - email_value = display_value_for_producer(order, order.email) + %a{ href: "mailto:#{email_value}", target: "_blank" } + = email_value %td - = order&.bill_address&.full_name_for_sorting + = display_value_for_producer(order, order.bill_address&.full_name_for_sorting) %td.align-left %span = order.display_total @@ -51,5 +52,5 @@ = render ShipOrderComponent.new(order: order) = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-road icon_link with-tip no-text", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.ship'), shipment: true} - - if order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any? + - if can?(:update, Spree::Payment) && order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any? = render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-capture icon_link no-text", button_reflex: "click->Admin::OrdersReflex#capture", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.capture')} diff --git a/app/views/spree/admin/orders/edit.html.haml b/app/views/spree/admin/orders/edit.html.haml index eb10841bbc4..7389b9aecb3 100644 --- a/app/views/spree/admin/orders/edit.html.haml +++ b/app/views/spree/admin/orders/edit.html.haml @@ -6,14 +6,16 @@ - content_for :page_actions do - if can?(:fire, @order) %li= event_links(@order) - = render partial: 'spree/admin/shared/order_links' + - if spree_current_user.can_manage_orders? + = render partial: 'spree/admin/shared/order_links' - if can?(:admin, Spree::Order) %li %a.button.icon-arrow-left{icon: 'icon-arrow-left', href: admin_orders_path } = t(:back_to_orders_list) = render partial: "spree/admin/shared/order_page_title" -= render partial: "spree/admin/shared/order_tabs", locals: { current: 'Order Details' } +- if spree_current_user.can_manage_orders? + = render partial: "spree/admin/shared/order_tabs", locals: { current: 'Order Details' } %div = render partial: "spree/shared/error_messages", locals: { target: @order } diff --git a/app/views/spree/admin/orders/index.html.haml b/app/views/spree/admin/orders/index.html.haml index 26a7965a698..0deea9b1abd 100644 --- a/app/views/spree/admin/orders/index.html.haml +++ b/app/views/spree/admin/orders/index.html.haml @@ -3,9 +3,10 @@ - content_for :minimal_js, true -- content_for :page_actions do - %li - = button_link_to t('.new_order'), spree.new_admin_order_url, icon: 'icon-plus', id: 'admin_new_order' +- if can?(:create, Spree::Order) + - content_for :page_actions do + %li + = button_link_to t('.new_order'), spree.new_admin_order_url, icon: 'icon-plus', id: 'admin_new_order' = render partial: 'spree/admin/shared/order_sub_menu' diff --git a/config/locales/en.yml b/config/locales/en.yml index c899680a2a0..5bdff6ebeab 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1339,8 +1339,12 @@ en: enable_subscriptions_true: "Enabled" customer_names_in_reports: "Customer Names in Reports" customer_names_tip: "Enable your suppliers to see your customers names in reports" + producers_to_edit_orders: "Ability for producers to edit orders" + producers_to_edit_orders_tip: "Enable your suppliers to see orders containing their products, and edit quantity and weight for their own products only." customer_names_false: "Disabled" customer_names_true: "Enabled" + producers_edit_orders_false: "Disabled" + producers_edit_orders_true: "Enabled" shopfront_message: "Shopfront Message" shopfront_message_placeholder: > An optional message to welcome customers and explain how to shop with you. If text is entered here it will be displayed in a home tab when customers first arrive at your shopfront. diff --git a/db/migrate/20250202121858_add_enable_producers_to_edit_orders_to_enterprises.rb b/db/migrate/20250202121858_add_enable_producers_to_edit_orders_to_enterprises.rb new file mode 100644 index 00000000000..842427aca42 --- /dev/null +++ b/db/migrate/20250202121858_add_enable_producers_to_edit_orders_to_enterprises.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddEnableProducersToEditOrdersToEnterprises < ActiveRecord::Migration[7.0] + def change + add_column :enterprises, :enable_producers_to_edit_orders, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index a20327a48ad..f0c9a56883c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2025_01_28_031518) do +ActiveRecord::Schema[7.0].define(version: 2025_02_02_121858) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -230,6 +230,7 @@ t.text "white_label_logo_link" t.boolean "hide_groups_tab", default: false t.string "external_billing_id", limit: 128 + t.boolean "enable_producers_to_edit_orders", default: false, null: false t.index ["address_id"], name: "index_enterprises_on_address_id" t.index ["is_primary_producer", "sells"], name: "index_enterprises_on_is_primary_producer_and_sells" t.index ["name"], name: "index_enterprises_on_name", unique: true diff --git a/lib/open_food_network/scope_variants_for_search.rb b/lib/open_food_network/scope_variants_for_search.rb index 7c51cd8b613..465e501c105 100644 --- a/lib/open_food_network/scope_variants_for_search.rb +++ b/lib/open_food_network/scope_variants_for_search.rb @@ -9,8 +9,9 @@ module OpenFoodNetwork class ScopeVariantsForSearch - def initialize(params) + def initialize(params, spree_current_user) @params = params + @spree_current_user = spree_current_user end def search @@ -20,13 +21,14 @@ def search scope_to_schedule if params[:schedule_id] scope_to_order_cycle if params[:order_cycle_id] scope_to_distributor if params[:distributor_id] + scope_to_supplier if spree_current_user.can_manage_line_items_in_orders_only? @variants end private - attr_reader :params + attr_reader :params, :spree_current_user def search_params { product_name_cont: params[:q], sku_cont: params[:q], product_sku_cont: params[:q] } @@ -96,5 +98,9 @@ def scope_variants_to_distributor(variants, distributor) # Filtering could be a problem on scoped variants. variants.each { |v| scoper.scope(v) } end + + def scope_to_supplier + @variants = @variants.where(supplier_id: spree_current_user.enterprises.ids) + end end end diff --git a/spec/controllers/admin/bulk_line_items_controller_spec.rb b/spec/controllers/admin/bulk_line_items_controller_spec.rb index 89303ca6d4a..8c62de79bd1 100644 --- a/spec/controllers/admin/bulk_line_items_controller_spec.rb +++ b/spec/controllers/admin/bulk_line_items_controller_spec.rb @@ -123,8 +123,24 @@ get :index, as: :json end - it "does not display line items for which my enterprise is a supplier" do - expect(response).to redirect_to unauthorized_path + context "with no distributor allows to edit orders" do + before { get :index, as: :json } + + it "does not display line items for which my enterprise is a supplier" do + expect(response).to redirect_to unauthorized_path + end + end + + context "with distributor allows to edit orders" do + before do + distributor1.update_columns(enable_producers_to_edit_orders: true) + get :index, as: :json + end + + it "retrieves a list of line_items from the supplier" do + keys = json_response['line_items'].first.keys.map(&:to_sym) + expect(line_item_attributes.all?{ |attr| keys.include? attr }).to eq(true) + end end end diff --git a/spec/controllers/api/v0/orders_controller_spec.rb b/spec/controllers/api/v0/orders_controller_spec.rb index a6d15774fad..558f14696b4 100644 --- a/spec/controllers/api/v0/orders_controller_spec.rb +++ b/spec/controllers/api/v0/orders_controller_spec.rb @@ -84,11 +84,25 @@ module Api context 'producer enterprise' do before do allow(controller).to receive(:spree_current_user) { supplier.owner } - get :index end - it "does not display line items for which my enterprise is a supplier" do - assert_unauthorized! + context "with no distributor allows to edit orders" do + before { get :index } + + it "does not display line items for which my enterprise is a supplier" do + assert_unauthorized! + end + end + + context "with distributor allows to edit orders" do + before do + distributor.update_columns(enable_producers_to_edit_orders: true) + get :index + end + + it "retrieves a list of orders which have my supplied products" do + returns_orders(json_response) + end end end diff --git a/spec/controllers/spree/admin/mail_methods_controller_spec.rb b/spec/controllers/spree/admin/mail_methods_controller_spec.rb index 5221204c739..25592aa25b8 100644 --- a/spec/controllers/spree/admin/mail_methods_controller_spec.rb +++ b/spec/controllers/spree/admin/mail_methods_controller_spec.rb @@ -22,7 +22,8 @@ owned_groups: nil) allow(user).to receive_messages(enterprises: [create(:enterprise)], admin?: true, - locale: nil) + locale: nil, + can_manage_orders?: true) allow(controller).to receive_messages(spree_current_user: user) expect { diff --git a/spec/controllers/spree/admin/variants_controller_spec.rb b/spec/controllers/spree/admin/variants_controller_spec.rb index a443e4d4491..dda0c01dabb 100644 --- a/spec/controllers/spree/admin/variants_controller_spec.rb +++ b/spec/controllers/spree/admin/variants_controller_spec.rb @@ -5,151 +5,199 @@ module Spree module Admin RSpec.describe VariantsController, type: :controller do - before { controller_login_as_admin } - - describe "#index" do - describe "deleted variants" do - let(:product) { create(:product, name: 'Product A') } - let(:deleted_variant) do - deleted_variant = product.variants.create( - unit_value: "2", variant_unit: "weight", variant_unit_scale: 1, price: 1, - primary_taxon: create(:taxon), supplier: create(:supplier_enterprise) - ) - deleted_variant.delete - deleted_variant - end - - it "lists only non-deleted variants with params[:deleted] == off" do - spree_get :index, product_id: product.id, deleted: "off" - expect(assigns(:variants)).to eq(product.variants) - end - - it "lists only deleted variants with params[:deleted] == on" do - spree_get :index, product_id: product.id, deleted: "on" - expect(assigns(:variants)).to eq([deleted_variant]) + context "log in as admin user" do + before { controller_login_as_admin } + + describe "#index" do + describe "deleted variants" do + let(:product) { create(:product, name: 'Product A') } + let(:deleted_variant) do + deleted_variant = product.variants.create( + unit_value: "2", variant_unit: "weight", variant_unit_scale: 1, price: 1, + primary_taxon: create(:taxon), supplier: create(:supplier_enterprise) + ) + deleted_variant.delete + deleted_variant + end + + it "lists only non-deleted variants with params[:deleted] == off" do + spree_get :index, product_id: product.id, deleted: "off" + expect(assigns(:variants)).to eq(product.variants) + end + + it "lists only deleted variants with params[:deleted] == on" do + spree_get :index, product_id: product.id, deleted: "on" + expect(assigns(:variants)).to eq([deleted_variant]) + end end end - end - describe "#update" do - let!(:variant) { create(:variant, display_name: "Tomatoes", sku: 123, supplier: producer) } - let(:producer) { create(:enterprise) } - - it "updates the variant" do - expect { - spree_put( - :update, - id: variant.id, - product_id: variant.product.id, - variant: { display_name: "Better tomatoes", sku: 456 } + describe "#update" do + let!(:variant) { + create( + :variant, + display_name: "Tomatoes", + sku: 123, + supplier: producer ) - variant.reload - }.to change { variant.display_name }.to("Better tomatoes") - .and change { variant.sku }.to(456.to_s) - end - - context "when updating supplier" do - let(:new_producer) { create(:enterprise) } + } + let(:producer) { create(:enterprise) } - it "updates the supplier" do + it "updates the variant" do expect { spree_put( :update, id: variant.id, product_id: variant.product.id, - variant: { supplier_id: new_producer.id } + variant: { display_name: "Better tomatoes", sku: 456 } ) variant.reload - }.to change { variant.supplier_id }.to(new_producer.id) + }.to change { variant.display_name }.to("Better tomatoes") + .and change { variant.sku }.to(456.to_s) end - it "removes associated product from existing Order Cycles" do - distributor = create(:distributor_enterprise) - order_cycle = create( - :simple_order_cycle, - variants: [variant], - coordinator: distributor, - distributors: [distributor] - ) + context "when updating supplier" do + let(:new_producer) { create(:enterprise) } + + it "updates the supplier" do + expect { + spree_put( + :update, + id: variant.id, + product_id: variant.product.id, + variant: { supplier_id: new_producer.id } + ) + variant.reload + }.to change { variant.supplier_id }.to(new_producer.id) + end + + it "removes associated product from existing Order Cycles" do + distributor = create(:distributor_enterprise) + order_cycle = create( + :simple_order_cycle, + variants: [variant], + coordinator: distributor, + distributors: [distributor] + ) - spree_put( - :update, - id: variant.id, - product_id: variant.product.id, - variant: { supplier_id: new_producer.id } - ) + spree_put( + :update, + id: variant.id, + product_id: variant.product.id, + variant: { supplier_id: new_producer.id } + ) - expect(order_cycle.reload.distributed_variants).not_to include variant + expect(order_cycle.reload.distributed_variants).not_to include variant + end end end - end - describe "#search" do - let(:supplier) { create(:supplier_enterprise) } - let!(:p1) { create(:simple_product, name: 'Product 1', supplier_id: supplier.id) } - let!(:p2) { create(:simple_product, name: 'Product 2', supplier_id: supplier.id) } - let!(:v1) { p1.variants.first } - let!(:v2) { p2.variants.first } - let!(:vo) { create(:variant_override, variant: v1, hub: d, count_on_hand: 44) } - let!(:d) { create(:distributor_enterprise) } - let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [v1]) } + describe "#search" do + let(:supplier) { create(:supplier_enterprise) } + let!(:p1) { create(:simple_product, name: 'Product 1', supplier_id: supplier.id) } + let!(:p2) { create(:simple_product, name: 'Product 2', supplier_id: supplier.id) } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } + let!(:vo) { create(:variant_override, variant: v1, hub: d, count_on_hand: 44) } + let!(:d) { create(:distributor_enterprise) } + let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [v1]) } + + it "filters by distributor" do + spree_get :search, q: 'Prod', distributor_id: d.id.to_s + expect(assigns(:variants)).to eq([v1]) + end - it "filters by distributor" do - spree_get :search, q: 'Prod', distributor_id: d.id.to_s - expect(assigns(:variants)).to eq([v1]) - end + it "applies variant overrides" do + spree_get :search, q: 'Prod', distributor_id: d.id.to_s + expect(assigns(:variants)).to eq([v1]) + expect(assigns(:variants).first.on_hand).to eq(44) + end - it "applies variant overrides" do - spree_get :search, q: 'Prod', distributor_id: d.id.to_s - expect(assigns(:variants)).to eq([v1]) - expect(assigns(:variants).first.on_hand).to eq(44) - end + it "filters by order cycle" do + spree_get :search, q: 'Prod', order_cycle_id: oc.id.to_s + expect(assigns(:variants)).to eq([v1]) + end - it "filters by order cycle" do - spree_get :search, q: 'Prod', order_cycle_id: oc.id.to_s - expect(assigns(:variants)).to eq([v1]) + it "does not filter when no distributor or order cycle is specified" do + spree_get :search, q: 'Prod' + expect(assigns(:variants)).to match_array [v1, v2] + end end - it "does not filter when no distributor or order cycle is specified" do - spree_get :search, q: 'Prod' - expect(assigns(:variants)).to match_array [v1, v2] + describe '#destroy' do + let(:variant) { create(:variant) } + + context 'when requesting with html' do + before do + allow(Spree::Variant).to receive(:find).with(variant.id.to_s) { variant } + allow(variant).to receive(:destroy).and_call_original + end + + it 'deletes the variant' do + spree_delete :destroy, id: variant.id, product_id: variant.product.id, + format: 'html' + expect(variant).to have_received(:destroy) + end + + it 'shows a success flash message' do + spree_delete :destroy, id: variant.id, product_id: variant.product.id, + format: 'html' + expect(flash[:success]).to be + end + + it 'redirects to admin_product_variants_url' do + spree_delete :destroy, id: variant.id, product_id: variant.product.id, + format: 'html' + expect(response).to redirect_to spree.admin_product_variants_url(variant.product.id) + end + + it 'destroys all its exchanges' do + exchange = create(:exchange) + variant.exchanges << exchange + + spree_delete :destroy, id: variant.id, product_id: variant.product.id, + format: 'html' + expect(variant.exchanges.reload).to be_empty + end + end end end - describe '#destroy' do - let(:variant) { create(:variant) } - - context 'when requesting with html' do - before do - allow(Spree::Variant).to receive(:find).with(variant.id.to_s) { variant } - allow(variant).to receive(:destroy).and_call_original - end - - it 'deletes the variant' do - spree_delete :destroy, id: variant.id, product_id: variant.product.id, - format: 'html' - expect(variant).to have_received(:destroy) - end + context "log in as supplier and distributor enable_producers_to_edit_orders" do + let(:supplier1) { create(:supplier_enterprise) } + let(:supplier2) { create(:supplier_enterprise) } + let!(:p1) { create(:simple_product, name: 'Product 1', supplier_id: supplier1.id) } + let!(:p2) { create(:simple_product, name: 'Product 2', supplier_id: supplier2.id) } + let!(:v1) { p1.variants.first } + let!(:v2) { p2.variants.first } + let!(:d) { create(:distributor_enterprise, enable_producers_to_edit_orders: true) } + let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [v1, v2]) } - it 'shows a success flash message' do - spree_delete :destroy, id: variant.id, product_id: variant.product.id, - format: 'html' - expect(flash[:success]).to be - end + before do + order = create(:order_with_line_items, distributor: d, line_items_count: 1) + order.line_items.take.variant.update_attribute(:supplier_id, supplier1.id) + controller_login_as_enterprise_user([supplier1]) + end - it 'redirects to admin_product_variants_url' do - spree_delete :destroy, id: variant.id, product_id: variant.product.id, - format: 'html' - expect(response).to redirect_to spree.admin_product_variants_url(variant.product.id) + describe "#search" do + it "filters by distributor and supplier1 products" do + spree_get :search, q: 'Prod', distributor_id: d.id.to_s + expect(assigns(:variants)).to eq([v1]) end + end - it 'destroys all its exchanges' do - exchange = create(:exchange) - variant.exchanges << exchange - - spree_delete :destroy, id: variant.id, product_id: variant.product.id, - format: 'html' - expect(variant.exchanges.reload).to be_empty + describe "#update" do + it "updates the variant" do + expect { + spree_put( + :update, + id: v1.id, + product_id: v1.product.id, + variant: { display_name: "Better tomatoes", sku: 456 } + ) + v1.reload + }.to change { v1.display_name }.to("Better tomatoes") + .and change { v1.sku }.to(456.to_s) end end end 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 fc16580d156..277aaa66802 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 @@ -19,8 +19,9 @@ let!(:oc3) { create(:simple_order_cycle, distributors: [d2], variants: [v4]) } let!(:s1) { create(:schedule, order_cycles: [oc1]) } let!(:s2) { create(:schedule, order_cycles: [oc2]) } + let(:spree_current_user) { create(:user) } - let(:scoper) { OpenFoodNetwork::ScopeVariantsForSearch.new(params) } + let(:scoper) { OpenFoodNetwork::ScopeVariantsForSearch.new(params, spree_current_user) } describe "search" do let(:result) { scoper.search } @@ -66,10 +67,20 @@ it "returns all products distributed through that distributor" do expect{ result }.to query_database [ + "TRANSACTION", + "Spree::User Exists?", + "Spree::User Create", + "Customer Load", + "Customer Load", + "Spree::Order Load", + "TRANSACTION", "Enterprise Load", "VariantOverride Load", - "SQL" + "SQL", + "Enterprise Pluck", + "Enterprise Load" ] + expect(result).to include v4 expect(result).not_to include v1, v2, v3 end diff --git a/spec/lib/reports/orders_and_distributors_report_spec.rb b/spec/lib/reports/orders_and_distributors_report_spec.rb index c4cd520f15d..2dd4acb4c48 100644 --- a/spec/lib/reports/orders_and_distributors_report_spec.rb +++ b/spec/lib/reports/orders_and_distributors_report_spec.rb @@ -140,6 +140,7 @@ subject # build context first expect { subject.table_rows }.to query_database [ + "Enterprise Pluck", "SQL", "Spree::LineItem Load", "Spree::PaymentMethod Load", diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 1339a393280..6060d7bfc54 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -240,6 +240,24 @@ it { expect(subject.can_manage_enterprises?(user)).to be true } it { expect(subject.can_manage_orders?(user)).to be false } it { expect(subject.can_manage_order_cycles?(user)).to be false } + + context "with no distributor allows me to edit orders" do + it { expect(subject.can_manage_orders?(user)).to be false } + it { expect(subject.can_manage_line_items_in_orders?(user)).to be false } + end + + context "with any distributor allows me to edit orders containing my product" do + before do + order = create( + :order_with_line_items, + line_items_count: 1, + distributor: create(:distributor_enterprise, enable_producers_to_edit_orders: true) + ) + order.line_items.first.variant.update!(supplier_id: enterprise_none_producer.id) + end + + it { expect(subject.can_manage_line_items_in_orders?(user)).to be true } + end end context "as a profile" do @@ -260,6 +278,7 @@ it { expect(subject.can_manage_products?(user)).to be false } it { expect(subject.can_manage_enterprises?(user)).to be false } it { expect(subject.can_manage_orders?(user)).to be false } + it { expect(subject.can_manage_line_items_in_orders?(user)).to be false } it { expect(subject.can_manage_order_cycles?(user)).to be false } it "can create enterprises straight off the bat" do diff --git a/spec/services/permissions/order_spec.rb b/spec/services/permissions/order_spec.rb index d85777a5830..f0cba2275bb 100644 --- a/spec/services/permissions/order_spec.rb +++ b/spec/services/permissions/order_spec.rb @@ -4,7 +4,7 @@ module Permissions RSpec.describe Order do - let(:user) { double(:user) } + 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) } diff --git a/spec/views/spree/admin/orders/edit.html.haml_spec.rb b/spec/views/spree/admin/orders/edit.html.haml_spec.rb index 644af45b888..ca22a3c2265 100644 --- a/spec/views/spree/admin/orders/edit.html.haml_spec.rb +++ b/spec/views/spree/admin/orders/edit.html.haml_spec.rb @@ -21,7 +21,7 @@ def current_ability end end - allow(view).to receive_messages spree_current_user: create(:user) + allow(view).to receive_messages spree_current_user: create(:admin_user) end context "when order is complete" do