From 9b5f743fa5d4ec087d41b763478d3cdc3bf4bc6a Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 19:36:04 +0100 Subject: [PATCH 01/69] Extract out a OrderCycle#simple? method so it can be used in models --- app/helpers/order_cycles_helper.rb | 4 ---- app/models/order_cycle.rb | 4 ++++ app/views/admin/order_cycles/edit.html.haml | 6 +++--- app/views/admin/order_cycles/new.html.haml | 6 +++--- spec/models/order_cycle_spec.rb | 14 ++++++++++++++ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/helpers/order_cycles_helper.rb b/app/helpers/order_cycles_helper.rb index 741613e246..c97e0c1a96 100644 --- a/app/helpers/order_cycles_helper.rb +++ b/app/helpers/order_cycles_helper.rb @@ -56,10 +56,6 @@ module OrderCyclesHelper @simple_index ||= !OpenFoodNetwork::Permissions.new(spree_current_user).can_manage_complex_order_cycles? end - def order_cycles_simple_form - @order_cycles_simple_form ||= @order_cycle.coordinator.sells == 'own' - end - def pickup_time(order_cycle = current_order_cycle) order_cycle.exchanges.to_enterprises(current_distributor).outgoing.first.pickup_time end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 26fe34eec7..91c9124a21 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -274,6 +274,10 @@ class OrderCycle < ApplicationRecord items.each { |li| scoper.scope(li.variant) } end + def simple? + coordinator.sells == 'own' + end + private def opening? diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index fdad0e3599..56151db299 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -13,20 +13,20 @@ - content_for :page_title do = t :edit_order_cycle -- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' +- ng_controller = @order_cycle.simple? ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = admin_inject_order_cycle_instance = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } %input.red{ type: "button", value: t('.save'), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } - - if order_cycles_simple_form + - if @order_cycle.simple? %input.red{ type: "button", value: t('.save_and_back_to_list'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } - else %input.red{ type: "button", value: t('.save_and_next'), ng: { click: "submit($event, '#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } %input{ type: "button", value: t('.next'), ng: { click: "cancel('#{main_app.admin_order_cycle_incoming_path(@order_cycle)}')", disabled: "order_cycle_form.$dirty" } } %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } - - if order_cycles_simple_form + - if @order_cycle.simple? = render 'simple_form', f: f - else = render 'form', f: f diff --git a/app/views/admin/order_cycles/new.html.haml b/app/views/admin/order_cycles/new.html.haml index e01896ab8c..e40ee808ef 100644 --- a/app/views/admin/order_cycles/new.html.haml +++ b/app/views/admin/order_cycles/new.html.haml @@ -1,18 +1,18 @@ - content_for :page_title do =t('new_order_cycle') -- ng_controller = order_cycles_simple_form ? 'AdminSimpleCreateOrderCycleCtrl' : 'AdminCreateOrderCycleCtrl' +- ng_controller = @order_cycle.simple? ? 'AdminSimpleCreateOrderCycleCtrl' : 'AdminCreateOrderCycleCtrl' = admin_inject_order_cycle_instance = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - - if order_cycles_simple_form + - if @order_cycle.simple? - custom_redirect_path = main_app.admin_order_cycles_path %input.red{ type: "button", value: t('.create'), ng: { click: "submit($event, '#{custom_redirect_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } - - if order_cycles_simple_form + - if @order_cycle.simple? = render 'simple_form', f: f - else = render 'form', f: f diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 4ebb79a9a1..27cea63be5 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -610,6 +610,20 @@ describe OrderCycle do end end + describe "#simple?" do + it "returns true if the coordinator sells their own products i.e. shops" do + order_cycle = build(:simple_order_cycle, coordinator: build(:enterprise, sells: "own")) + + expect(order_cycle).to be_simple + end + + it "returns false if the coordinator can sell other people's products i.e. hubs" do + order_cycle = build(:simple_order_cycle, coordinator: build(:enterprise, sells: "any")) + + expect(order_cycle).not_to be_simple + end + end + def core_exchange_attributes(exchange) exterior_attribute_keys = %w(id order_cycle_id created_at updated_at) exchange.attributes. From 37617f63ea884a3aa22749f51234a2a0910f705e Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 19:54:28 +0100 Subject: [PATCH 02/69] Remove non-functional :backend scope from Spree::ShippingRate It finds shipping methods where 'display_on != front_end' but :front_end values were removed in https://github.com/openfoodfoundation/openfoodnetwork/blob/a1317be19b76d1953e38f9e47cbc8e5304d275e6/db/migrate/20200508101630_convert_frontend_shipping_method_to_both.rb so it will always return true. --- app/models/spree/shipping_rate.rb | 7 ------- app/views/spree/admin/orders/_shipment.html.haml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/models/spree/shipping_rate.rb b/app/models/spree/shipping_rate.rb index 710a8a25b4..ce94b5f808 100644 --- a/app/models/spree/shipping_rate.rb +++ b/app/models/spree/shipping_rate.rb @@ -12,13 +12,6 @@ module Spree references(:shipping_method). order("cost ASC") } - scope :backend, - -> { - includes(:shipping_method). - where(ShippingMethod.on_backend_query). - references(:shipping_method). - order("cost ASC") - } delegate :order, :currency, to: :shipment delegate :name, to: :shipping_method diff --git a/app/views/spree/admin/orders/_shipment.html.haml b/app/views/spree/admin/orders/_shipment.html.haml index e9fcb169de..653f93ea5c 100644 --- a/app/views/spree/admin/orders/_shipment.html.haml +++ b/app/views/spree/admin/orders/_shipment.html.haml @@ -38,7 +38,7 @@ %td{ :colspan => "5" } %div.field.alpha.five.columns = label_tag 'selected_shipping_rate_id', Spree.t(:shipping_method) - = select_tag :selected_shipping_rate_id, options_for_select(shipment.shipping_rates.backend.map { |sr| ["#{sr.name} #{sr.display_price}", sr.id] }, shipment.selected_shipping_rate_id), { :class => 'select2 fullwidth', :data => { 'shipment-number' => shipment.number } } + = select_tag :selected_shipping_rate_id, options_for_select(shipment.shipping_rates.map { |sr| ["#{sr.name} #{sr.display_price}", sr.id] }, shipment.selected_shipping_rate_id), { :class => 'select2 fullwidth', :data => { 'shipment-number' => shipment.number } } %td.actions - if can? :update, shipment From a5daee39e3d039bde8e58f9a35efa7a3b6dcc038 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 20:00:04 +0100 Subject: [PATCH 03/69] Extract out a DISPLAY_ON_OPTIONS constant in Spree::ShippingMethod instead of magic strings --- app/models/spree/shipping_method.rb | 14 +++++++++----- app/models/spree/shipping_rate.rb | 2 +- .../admin/shipping_methods/_form.html.haml | 2 +- spec/models/spree/shipping_method_spec.rb | 17 +++++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 53d77d60c2..042d7d07a0 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -3,7 +3,10 @@ module Spree class ShippingMethod < ApplicationRecord include CalculatedAdjustments - DISPLAY = [:both, :front_end, :back_end].freeze + DISPLAY_ON_OPTIONS = { + both: "", + back_end: "back_end" + }.freeze acts_as_paranoid acts_as_taggable @@ -27,6 +30,7 @@ module Spree validates :name, presence: true validate :distributor_validation validate :at_least_one_shipping_category + validates :display_on, inclusion: { in: DISPLAY_ON_OPTIONS.values }, allow_nil: true after_save :touch_distributors @@ -101,12 +105,12 @@ module Spree ] end - def self.on_backend_query - "#{table_name}.display_on != 'front_end' OR #{table_name}.display_on IS NULL" + def self.backend + where("spree_shipping_methods.display_on = ?", DISPLAY_ON_OPTIONS[:back_end]) end - def self.on_frontend_query - "#{table_name}.display_on != 'back_end' OR #{table_name}.display_on IS NULL" + def self.frontend + where("spree_shipping_methods.display_on IS NULL OR spree_shipping_methods.display_on = ''") end private diff --git a/app/models/spree/shipping_rate.rb b/app/models/spree/shipping_rate.rb index ce94b5f808..5116a2de6b 100644 --- a/app/models/spree/shipping_rate.rb +++ b/app/models/spree/shipping_rate.rb @@ -8,7 +8,7 @@ module Spree scope :frontend, -> { includes(:shipping_method). - where(ShippingMethod.on_frontend_query). + merge(ShippingMethod.frontend). references(:shipping_method). order("cost ASC") } diff --git a/app/views/spree/admin/shipping_methods/_form.html.haml b/app/views/spree/admin/shipping_methods/_form.html.haml index 2148576ca4..5ca959f927 100644 --- a/app/views/spree/admin/shipping_methods/_form.html.haml +++ b/app/views/spree/admin/shipping_methods/_form.html.haml @@ -19,7 +19,7 @@ .alpha.four.columns = f.label :display_on, t(:display) .omega.twelve.columns - = select(:shipping_method, :display_on, [[t(".both"), nil], [t(".back_end"), "back_end"]], {}, {class: 'select2 fullwidth'}) + = select(:shipping_method, :display_on, Spree::ShippingMethod::DISPLAY_ON_OPTIONS.map { |key, value| [t(".#{key}"), value] }, {}, {class: 'select2 fullwidth'}) = error_message_on :shipping_method, :display_on .row diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index b505a3a808..14d7333120 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -143,6 +143,23 @@ module Spree expect(shipping_method.errors[:name].first).to eq "can't be blank" end + describe "#display_on" do + it "is valid when it's set to nil, an empty string or 'back_end'" do + shipping_method = build_stubbed(:shipping_method) + [nil, "", "back_end"].each do |display_on_option| + shipping_method.display_on = display_on_option + shipping_method.valid? + expect(shipping_method.errors[:display_on]).to be_empty + end + end + + it "is not valid when it's set to an unknown value" do + shipping_method = build_stubbed(:shipping_method, display_on: "front_end") + expect(shipping_method).not_to be_valid + expect(shipping_method.errors[:display_on]).to eq ["is not included in the list"] + end + end + context "shipping category" do it "validates presence of at least one" do shipping_method = build_stubbed( From 94d71b8dcea7e251191305329fe05d607dd766e7 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 20:02:43 +0100 Subject: [PATCH 04/69] Remove extra unnecessary :display_on_checkout scope and reuse :frontend scope instead --- app/helpers/enterprises_helper.rb | 2 +- app/models/spree/shipping_method.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb index a93eb56b24..e535e9a7ba 100644 --- a/app/helpers/enterprises_helper.rb +++ b/app/helpers/enterprises_helper.rb @@ -16,7 +16,7 @@ module EnterprisesHelper def available_shipping_methods return [] if current_distributor.blank? - shipping_methods = current_distributor.shipping_methods.display_on_checkout.to_a + shipping_methods = current_distributor.shipping_methods.frontend.to_a applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterShippingMethods", current_customer&.tag_list) diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 042d7d07a0..4e9fe38825 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -55,9 +55,6 @@ module Spree } scope :by_name, -> { order('spree_shipping_methods.name ASC') } - scope :display_on_checkout, -> { - where("spree_shipping_methods.display_on is null OR spree_shipping_methods.display_on = ''") - } # Here we allow checkout with shipping methods without zones (see issue #3928 for details) # and also checkout with addresses outside of the zones of the selected shipping method From 9a6e8a111357aab4d62552886bbc8d1d0b9932cd Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 20:10:34 +0100 Subject: [PATCH 05/69] Add an OrderCycleShippingMethod model to handle attaching shipping methods to order cycles --- app/models/order_cycle_shipping_method.rb | 58 ++++++++ config/locales/en.yml | 10 ++ ...052_create_order_cycle_shipping_methods.rb | 98 +++++++++++++ db/schema.rb | 11 ++ spec/factories/order_cycle_factory.rb | 12 ++ .../order_cycle_shipping_method_spec.rb | 130 ++++++++++++++++++ 6 files changed, 319 insertions(+) create mode 100644 app/models/order_cycle_shipping_method.rb create mode 100644 db/migrate/20220429092052_create_order_cycle_shipping_methods.rb create mode 100644 spec/models/order_cycle_shipping_method_spec.rb diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb new file mode 100644 index 0000000000..dbdd0ccd0d --- /dev/null +++ b/app/models/order_cycle_shipping_method.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class OrderCycleShippingMethod < ActiveRecord::Base + belongs_to :order_cycle + belongs_to :shipping_method, class_name: "Spree::ShippingMethod" + + validate :shipping_method_belongs_to_order_cycle_distributor + validate :shipping_method_available_at_checkout + validate :order_cycle_not_simple + validate :order_cycle_shipping_methods_customisable + validates_uniqueness_of :shipping_method, scope: :order_cycle_id + + before_destroy :check_shipping_method_not_selected_on_any_orders + + private + + def shipping_method_not_selected_on_any_orders? + !Spree::Order.joins(shipments: :shipping_rates).where( + "order_cycle_id = ? AND spree_shipping_rates.shipping_method_id = ?", + order_cycle_id, shipping_method_id + ).exists? + end + + def check_shipping_method_not_selected_on_any_orders + return if order_cycle.nil? || + shipping_method.nil? || + shipping_method_not_selected_on_any_orders? + + errors.add(:base, :shipping_method_already_used_in_order_cycle) + throw :abort + end + + def order_cycle_not_simple + return if order_cycle.nil? || !order_cycle.simple? + + errors.add(:order_cycle, :must_not_be_simple) + end + + def order_cycle_shipping_methods_customisable + return if order_cycle.nil? || order_cycle.shipping_methods_customisable? + + errors.add(:order_cycle, :must_support_customisable_shipping_methods) + end + + def shipping_method_available_at_checkout + return if shipping_method.nil? || shipping_method.frontend? + + errors.add(:shipping_method, :must_be_available_at_checkout) + end + + def shipping_method_belongs_to_order_cycle_distributor + return if order_cycle.nil? || + shipping_method.nil? || + shipping_method.distributors.where(id: order_cycle.distributor_ids).exists? + + errors.add(:shipping_method, :must_belong_to_order_cycle_distributor) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index be87cc9fab..1feb920654 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -77,6 +77,16 @@ en: attributes: orders_close_at: after_orders_open_at: must be after open date + order_cycle_shipping_method: + attributes: + base: + shipping_method_already_used_in_order_cycle: "This shipping method has already been selected on orders in this order cycle and cannot be removed" + order_cycle: + must_not_be_simple: "is simple, all shipping methods are available by default and cannot be customised" + must_support_customisable_shipping_methods: "shipping methods cannot be customised, all shipping methods are available by default" + shipping_method: + must_be_available_at_checkout: "must be available at checkout" + must_belong_to_order_cycle_distributor: "must be from a distributor on the order cycle" variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" diff --git a/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb new file mode 100644 index 0000000000..ae78acc94a --- /dev/null +++ b/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb @@ -0,0 +1,98 @@ +class CreateOrderCycleShippingMethods < ActiveRecord::Migration[6.1] + # Before this migration every available shipping method was available to customers on order + # cycles by default. However this migration only populates :order_cycles_shipping_methods records + # for active or upcoming order cycles because retroactively calculating which shipping methods + # should be attached to past, closed order cycles is probaby tricky so skipping that because it + # may not be even necessary. Instead this adds a :shipping_methods_customisable flag to order + # cycles so we have a record of order cycles created before this feature was deployed. + # + # Note: Redefining the Spree::ShippingMethod class in this migration as suggested by the + # :good_migrations gem was not passing Good Migrations checks. This redefines the classes inside + # a Migration class to to bypass this problem. + + class Migration + class DistributorShippingMethod < ActiveRecord::Base + self.table_name = "distributors_shipping_methods" + belongs_to :shipping_method, class_name: "Migration::ShippingMethod", touch: true + belongs_to :distributor, class_name: "Enterprise", touch: true + end + + class Enterprise < ActiveRecord::Base + end + + class Exchange < ActiveRecord::Base + self.table_name = "exchanges" + belongs_to :receiver, class_name: 'Migration::Enterprise' + end + + class OrderCycle < ActiveRecord::Base + self.table_name = "order_cycles" + has_many :order_cycle_shipping_methods, class_name: "Migration::OrderCycleShippingMethod" + has_many :shipping_methods, class_name: "Migration::ShippingMethod", through: :order_cycle_shipping_methods + + has_many :cached_outgoing_exchanges, -> { where incoming: false }, class_name: "Migration::Exchange" + has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges + + belongs_to :coordinator, class_name: 'Migration::Enterprise' + + scope :active, lambda { + where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?', + Time.zone.now, + Time.zone.now) + } + scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) } + end + + class OrderCycleShippingMethod < ActiveRecord::Base + self.table_name = "order_cycle_shipping_methods" + belongs_to :shipping_method, class_name: "Migration::ShippingMethod" + end + + class ShippingMethod < ActiveRecord::Base + self.table_name = "spree_shipping_methods" + end + + def self.attach_all_shipping_methods_to_non_simple_active_or_upcoming_order_cycles + non_simple_active_or_upcoming_order_cycles.find_each do |order_cycle| + order_cycle.shipping_method_ids = DistributorShippingMethod. + where("display_on != 'back_end'"). + where(distributor_id: order_cycle.distributor_ids). + joins(:shipping_method). + pluck(:shipping_method_id) + end + end + + def self.set_shipping_methods_customisable_to_false_on_past_order_cycles + OrderCycle.update_all(shipping_methods_customisable: false) + active_or_upcoming_order_cycles.update_all(shipping_methods_customisable: true) + end + + private + + def self.active_or_upcoming_order_cycles + OrderCycle.active.or(OrderCycle.upcoming) + end + + def self.non_simple_active_or_upcoming_order_cycles + active_or_upcoming_order_cycles.joins(:coordinator).where("sells != 'own'") + end + end + + def up + create_table :order_cycle_shipping_methods do |t| + t.references :order_cycle + t.references :shipping_method, foreign_key: { to_table: :spree_shipping_methods } + t.timestamps + end + + add_column :order_cycles, :shipping_methods_customisable, :boolean, default: true + + Migration.set_shipping_methods_customisable_to_false_on_past_order_cycles + Migration.attach_all_shipping_methods_to_non_simple_active_or_upcoming_order_cycles + end + + def down + remove_column :order_cycles, :shipping_methods_customisable + drop_table :order_cycle_shipping_methods + end +end diff --git a/db/schema.rb b/db/schema.rb index 6c9b1b00fe..bca8dabe52 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -318,6 +318,15 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.index ["schedule_id"], name: "index_order_cycle_schedules_on_schedule_id" end + create_table "order_cycle_shipping_methods", force: :cascade do |t| + t.bigint "order_cycle_id" + t.bigint "shipping_method_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["order_cycle_id"], name: "index_order_cycle_shipping_methods_on_order_cycle_id" + t.index ["shipping_method_id"], name: "index_order_cycle_shipping_methods_on_shipping_method_id" + end + create_table "order_cycles", id: :serial, force: :cascade do |t| t.string "name", limit: 255 t.datetime "orders_open_at" @@ -328,6 +337,7 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.datetime "processed_at" t.boolean "automatic_notifications", default: false t.boolean "mails_sent", default: false + t.boolean "shipping_methods_customisable", default: true end create_table "producer_properties", id: :serial, force: :cascade do |t| @@ -1253,6 +1263,7 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do add_foreign_key "exchanges", "order_cycles", name: "exchanges_order_cycle_id_fk" add_foreign_key "order_cycle_schedules", "order_cycles", name: "oc_schedules_order_cycle_id_fk" add_foreign_key "order_cycle_schedules", "schedules", name: "oc_schedules_schedule_id_fk" + add_foreign_key "order_cycle_shipping_methods", "spree_shipping_methods", column: "shipping_method_id" add_foreign_key "order_cycles", "enterprises", column: "coordinator_id", name: "order_cycles_coordinator_id_fk" add_foreign_key "producer_properties", "enterprises", column: "producer_id", name: "producer_properties_producer_id_fk" add_foreign_key "producer_properties", "spree_properties", column: "property_id", name: "producer_properties_property_id_fk" diff --git a/spec/factories/order_cycle_factory.rb b/spec/factories/order_cycle_factory.rb index 8dbfcc6acd..469fb94019 100644 --- a/spec/factories/order_cycle_factory.rb +++ b/spec/factories/order_cycle_factory.rb @@ -59,6 +59,10 @@ FactoryBot.define do end end + # Note: Order cycles are sometimes referred to as 'simple if they are for a shop selling their + # own produce i.e. :sells = 'own'. However the 'simple_order_cycle' name does not mean this + # and may need to be renamed to avoid potential confusion because it actually can create + # 'non-simple' order cycles too for distributors selling produce from other enterprises. factory :simple_order_cycle, class: OrderCycle do sequence(:name) { |n| "Order Cycle #{n}" } @@ -116,4 +120,12 @@ FactoryBot.define do orders_open_at { 2.weeks.ago } orders_close_at { 1.week.ago } end + + factory :distributor_order_cycle, parent: :simple_order_cycle do + coordinator { create(:distributor_enterprise) } + end + + factory :sells_own_order_cycle, parent: :simple_order_cycle do + coordinator { create(:enterprise, sells: "own") } + end end diff --git a/spec/models/order_cycle_shipping_method_spec.rb b/spec/models/order_cycle_shipping_method_spec.rb new file mode 100644 index 0000000000..fcc5fd5290 --- /dev/null +++ b/spec/models/order_cycle_shipping_method_spec.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe OrderCycleShippingMethod do + it "is valid when the shipping method is available at checkout" do + shipping_method = create(:shipping_method, display_on: nil) + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).to be_valid + + shipping_method.display_on = "both" + + expect(order_cycle_shipping_method).to be_valid + end + + it "is not valid when the shipping method is only available in the backoffice" do + shipping_method = create(:shipping_method, display_on: "back_end") + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).to_not be_valid + expect(order_cycle_shipping_method.errors.to_a).to include( + "Shipping method must be available at checkout" + ) + end + + it "is not valid if the order cycle is simple i.e. :sells is 'own'" do + order_cycle = create(:sells_own_order_cycle) + shipping_method = create(:shipping_method, distributors: [order_cycle.coordinator]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).to_not be_valid + expect(order_cycle_shipping_method.errors.to_a).to include( + "Order cycle is simple, all shipping methods are available by default and cannot be customised" + ) + end + + it "is not valid if order cycle doesn't support customised shipping methods + e.g. the order cycle was created before the custom shipping methods feature was available" do + order_cycle = create(:distributor_order_cycle, shipping_methods_customisable: false) + shipping_method = create(:shipping_method, distributors: [order_cycle.coordinator]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).to_not be_valid + expect(order_cycle_shipping_method.errors.to_a).to include( + "Order cycle shipping methods cannot be customised, all shipping methods are available by default" + ) + end + + it "is valid if the shipping method belongs to one of the order cycle distributors" do + shipping_method = create(:shipping_method) + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).to be_valid + end + + it "is not valid if the shipping method does not belong to one of the order cycle distributors" do + shipping_method = create(:shipping_method) + enterprise = create(:enterprise) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + + order_cycle_shipping_method = OrderCycleShippingMethod.new( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + + expect(order_cycle_shipping_method).not_to be_valid + expect(order_cycle_shipping_method.errors.to_a).to eq [ + "Shipping method must be from a distributor on the order cycle" + ] + end + + it "can be destroyed if the shipping method hasn't been used on any orders in the order cycle" do + shipping_method = create(:shipping_method) + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + + order_cycle_shipping_method = OrderCycleShippingMethod.create!( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + order_cycle_shipping_method.destroy + + expect(order_cycle_shipping_method).to be_destroyed + end + + it "cannot be destroyed if the shipping method has been used on some orders in the order cycle" do + shipping_method = create(:shipping_method) + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:simple_order_cycle, distributors: [enterprise]) + order = create(:order_ready_for_payment, distributor: enterprise, order_cycle: order_cycle) + + order_cycle_shipping_method = OrderCycleShippingMethod.create!( + order_cycle: order_cycle, + shipping_method: shipping_method + ) + order_cycle_shipping_method.destroy + + expect(order_cycle_shipping_method).not_to be_destroyed + expect(order_cycle_shipping_method.errors.to_a).to eq [ + "This shipping method has already been selected on orders in this order cycle and cannot be removed" + ] + end +end From ca97a7f52dd20d898477f0a5211a9b6a3e08dd5b Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 20:25:46 +0100 Subject: [PATCH 06/69] Add :shipping_methods association to OrderCyle and validations --- app/models/order_cycle.rb | 35 ++++++++++- config/locales/en.yml | 2 + spec/factories/order_cycle_factory.rb | 27 +++++++++ spec/models/order_cycle_spec.rb | 83 ++++++++++++++++++++++++--- 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 91c9124a21..86f1a14a54 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -22,17 +22,23 @@ class OrderCycle < ApplicationRecord has_many :suppliers, -> { distinct }, source: :sender, through: :cached_incoming_exchanges has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges - has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules + has_many :order_cycle_shipping_methods + has_many :shipping_methods, class_name: "Spree::ShippingMethod", + through: :order_cycle_shipping_methods has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges + attribute :validate_shipping_methods, :boolean, default: true + before_update :reset_processed_at, if: :will_save_change_to_orders_close_at? after_save :sync_subscriptions, if: :opening? validates :name, :coordinator_id, presence: true + validate :at_least_one_shipping_method_selected_for_each_distributor + validate :no_invalid_shipping_methods validate :orders_close_at_after_orders_open_at? preference :product_selection_from_coordinator_inventory_only, :boolean, default: false @@ -159,8 +165,12 @@ class OrderCycle < ApplicationRecord oc.preferred_product_selection_from_coordinator_inventory_only = preferred_product_selection_from_coordinator_inventory_only # rubocop:enable Layout/LineLength oc.schedule_ids = schedule_ids + oc.shipping_methods_customisable = true oc.save! + oc.validate_shipping_methods = false exchanges.each { |e| e.clone!(oc) } + oc.validate_shipping_methods = true + oc.shipping_method_ids = shipping_method_ids sync_subscriptions oc.reload end @@ -280,6 +290,29 @@ class OrderCycle < ApplicationRecord private + def all_distributors_have_at_least_one_shipping_method? + distributors.all? { |distributor| (distributor.shipping_method_ids & shipping_method_ids).any? } + end + + def at_least_one_shipping_method_selected_for_each_distributor + return if !validate_shipping_methods? || + coordinator.nil? || + simple? || + !shipping_methods_customisable? || + all_distributors_have_at_least_one_shipping_method? + + errors.add(:base, :at_least_one_shipping_method_per_distributor) + end + + def no_invalid_shipping_methods + return if order_cycle_shipping_methods.all?(&:valid?) + + errors.add( + :base, + order_cycle_shipping_methods.map(&:errors).map(&:to_a).flatten.uniq.to_sentence + ) + end + def opening? (open? || upcoming?) && saved_change_to_orders_close_at? && was_closed? end diff --git a/config/locales/en.yml b/config/locales/en.yml index 1feb920654..de8bdb19ac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -75,6 +75,8 @@ en: card_expired: "has expired" order_cycle: attributes: + base: + at_least_one_shipping_method_per_distributor: "You need to select at least one shipping method for each distributor" orders_close_at: after_orders_open_at: must be after open date order_cycle_shipping_method: diff --git a/spec/factories/order_cycle_factory.rb b/spec/factories/order_cycle_factory.rb index 469fb94019..3ad85ef7e5 100644 --- a/spec/factories/order_cycle_factory.rb +++ b/spec/factories/order_cycle_factory.rb @@ -72,12 +72,15 @@ FactoryBot.define do coordinator { Enterprise.is_distributor.first || FactoryBot.create(:distributor_enterprise) } transient do + shipping_methods { [] } suppliers { [] } distributors { [] } variants { [] } + with_distributor_and_shipping_method { false } end after(:create) do |oc, proxy| + # Incoming Exchanges proxy.suppliers.each.with_index do |supplier, i| ex = create(:exchange, order_cycle: oc, @@ -88,6 +91,10 @@ FactoryBot.define do proxy.variants.each { |v| ex.variants << v } end + if proxy.with_distributor_and_shipping_method + proxy.distributors << oc.coordinator if proxy.distributors.empty? + end + # Outgoing Exchanges proxy.distributors.each.with_index do |distributor, i| ex = create(:exchange, order_cycle: oc, @@ -98,6 +105,26 @@ FactoryBot.define do pickup_instructions: "instructions #{i}") proxy.variants.each { |v| ex.variants << v } end + + if proxy.with_distributor_and_shipping_method || proxy.shipping_methods.any? + oc.reload # so outgoing exchanges/distributors attached above are present + distributor = oc.distributors.first + if proxy.shipping_methods.empty? + proxy.shipping_methods << create(:shipping_method, distributors: [distributor]) + else + proxy.shipping_methods.each do |shipping_method| + # ensure shipping methods belong to a distributor on the order cycle + if !shipping_method.distributors.include?(distributor) + shipping_method.distributors << distributor + end + end + end + oc.shipping_methods = proxy.shipping_methods + end + + if proxy.distributors.any? || proxy.shipping_methods.any? + oc.reload # so shipping methods attached above are present + end end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 27cea63be5..16d21fdabd 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -20,6 +20,72 @@ describe OrderCycle do expect(oc).to_not be_valid end + describe "#at_least_one_shipping_method_selected_for_each_distributor" do + context "distributor order cycle i.e. :sells is 'any'" do + context "when multiple distributors have been added to the order cycle already" do + it "is valid when adding a shipping method for *all* distributors" do + distributor_i = create(:distributor_enterprise) + distributor_ii = create(:distributor_enterprise) + distributor_i_shipping_method = create(:shipping_method, distributors: [distributor_i]) + distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_ii]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) + + order_cycle.shipping_method_ids = [distributor_i_shipping_method.id, distributor_ii_shipping_method.id] + + expect(order_cycle).to be_valid + end + end + + it "is not valid when adding a shipping method for *some but not all* distributors" do + distributor_i = create(:distributor_enterprise) + distributor_ii = create(:distributor_enterprise) + distributor_i_shipping_method = create(:shipping_method, distributors: [distributor_i]) + distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_i]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) + + order_cycle.shipping_method_ids = [distributor_i_shipping_method.id] + + expect(order_cycle).to be_invalid + expect(order_cycle.errors.to_a).to eq ["You need to select at least one shipping method for each distributor"] + end + end + end + + describe "#no_invalid_shipping_methods" do + context "when a shipping method is not valid" do + it "adds a validation error, and it is more meaningful than the default 'Order cycle shipping methods is invalid'" do + order_cycle = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) + shipping_method = order_cycle.shipping_methods.first + + shipping_method.update_column(:display_on, "back_end") + + expect(order_cycle).to be_invalid + expect(order_cycle.errors.to_a).to eq ["Shipping method must be available at checkout"] + + shipping_method.update_column(:display_on, "") + + expect(order_cycle.reload).to be_valid + end + end + end + + describe "#validate_shipping_methods" do + it "doesn't skip shipping method validations by default" do + order_cycle = create(:distributor_order_cycle, distributors: [create(:distributor_enterprise)]) + + expect(order_cycle).to be_invalid + expect(order_cycle.errors.to_a).to eq ["You need to select at least one shipping method for each distributor"] + end + + it "allows shipping method validations to be skipped because distributors need to be saved before shipping methods" do + order_cycle = create(:distributor_order_cycle, distributors: [create(:distributor_enterprise)]) + + order_cycle.validate_shipping_methods = false + + expect(order_cycle).to be_valid + end + end + it "has a coordinator and associated fees" do oc = create(:simple_order_cycle) @@ -132,7 +198,7 @@ describe OrderCycle do end it "reports its distributors" do - oc = create(:simple_order_cycle) + oc = create(:simple_order_cycle, validate_shipping_methods: false) e1 = create(:exchange, incoming: false, order_cycle: oc, sender: oc.coordinator, receiver: create(:enterprise)) @@ -143,7 +209,7 @@ describe OrderCycle do end it "checks for existance of distributors" do - oc = create(:simple_order_cycle) + oc = create(:simple_order_cycle, validate_shipping_methods: false) d1 = create(:distributor_enterprise) d2 = create(:distributor_enterprise) create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1, incoming: false) @@ -506,7 +572,7 @@ describe OrderCycle do end describe "version tracking", versioning: true do - let!(:oc) { create(:order_cycle, name: "Original") } + let!(:oc) { create(:sells_own_order_cycle, name: "Original") } it "remembers old versions" do expect { @@ -551,11 +617,12 @@ describe OrderCycle do end describe "syncing subscriptions" do - let!(:oc) { - create(:simple_order_cycle, orders_open_at: 1.week.ago, orders_close_at: 1.day.ago) - } - let(:schedule) { create(:schedule, order_cycles: [oc]) } - let!(:subscription) { create(:subscription, schedule: schedule, with_items: true) } + let!(:subscription) { create(:subscription, with_items: true) } + let!(:oc) { subscription.order_cycles.first } + + before do + oc.update_columns(orders_open_at: 1.week.ago, orders_close_at: 1.day.ago) + end it "syncs subscriptions when transitioning from closed to open" do expect(OrderManagement::Subscriptions::ProxyOrderSyncer).to receive(:new).and_call_original From 48c2e48b24fae141ce0960d163585e4aa2d2632d Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 20:29:21 +0100 Subject: [PATCH 07/69] Update the OrderCycleForm service so it supports attaching shipping methods --- app/services/order_cycle_form.rb | 20 +++- spec/services/order_cycle_form_spec.rb | 140 +++++++++++++++++++++---- 2 files changed, 141 insertions(+), 19 deletions(-) diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 6e96b9af68..ba6aa590e7 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -11,11 +11,13 @@ class OrderCycleForm @user = user @permissions = OpenFoodNetwork::Permissions.new(user) @schedule_ids = order_cycle_params.delete(:schedule_ids) + @shipping_method_ids = order_cycle_params.delete(:shipping_method_ids) end def save schedule_ids = build_schedule_ids order_cycle.assign_attributes(order_cycle_params) + order_cycle.validate_shipping_methods = false return false unless order_cycle.valid? order_cycle.transaction do @@ -23,10 +25,12 @@ class OrderCycleForm order_cycle.schedule_ids = schedule_ids order_cycle.save! apply_exchange_changes + attach_shipping_methods sync_subscriptions true end - rescue ActiveRecord::RecordInvalid + rescue ActiveRecord::RecordInvalid => e + add_exception_to_order_cycle_errors(e) false end @@ -34,12 +38,26 @@ class OrderCycleForm attr_accessor :order_cycle, :order_cycle_params, :user, :permissions + def add_exception_to_order_cycle_errors(exception) + error = exception.message.split(":").last.strip + order_cycle.errors.add(:base, error) if !order_cycle.errors.to_a.include?(error) + end + def apply_exchange_changes return if exchanges_unchanged? OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go! end + def attach_shipping_methods + return if @shipping_method_ids.nil? + + order_cycle.reload # so outgoing exchanges are up-to-date for shipping method validations + order_cycle.validate_shipping_methods = true + order_cycle.shipping_method_ids = @shipping_method_ids + order_cycle.save! + end + def exchanges_unchanged? [:incoming_exchanges, :outgoing_exchanges].all? do |direction| order_cycle_params[direction].nil? diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index 4d5bce792c..c7b472257d 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -119,34 +119,138 @@ describe OrderCycleForm do end end - describe "updating exchanges" do - let(:user) { instance_double(Spree::User) } - let(:order_cycle) { create(:simple_order_cycle) } - let(:form_applicator_mock) { instance_double(OpenFoodNetwork::OrderCycleFormApplicator) } - let(:form) { OrderCycleForm.new(order_cycle, params, user) } + context "distributor order cycle" do + let(:order_cycle) { create(:distributor_order_cycle) } + let(:distributor) { order_cycle.coordinator } + let(:supplier) { create(:supplier_enterprise) } + let(:user) { distributor.owner } + let(:shipping_method) { create(:shipping_method, distributors: [distributor]) } + let(:variant) { create(:variant, product: create(:product, supplier: supplier)) } let(:params) { { name: 'Some new name' } } - - before do - allow(OpenFoodNetwork::OrderCycleFormApplicator).to receive(:new) { form_applicator_mock } - allow(form_applicator_mock).to receive(:go!) + let(:form) { OrderCycleForm.new(order_cycle, params, user) } + let(:outgoing_exchange_params) do + { + enterprise_id: distributor.id, + incoming: false, + active: true, + variants: { variant.id => true }, + pickup_time: "Saturday morning", + enterprise_fee_ids: [] + } end - context "when exchange params are provided" do - let(:exchange_params) { { incoming_exchanges: [], outgoing_exchanges: [] } } - before { params.merge!(exchange_params) } - - it "runs the OrderCycleFormApplicator, and saves other changes" do + context "basic update i.e. without exchanges or shipping methods" do + it do expect(form.save).to be true - expect(form_applicator_mock).to have_received(:go!) expect(order_cycle.name).to eq 'Some new name' end end - context "when no exchange params are provided" do - it "does not run the OrderCycleFormApplicator, but saves other changes" do + context "updating basics, incoming and outcoming exchanges, shipping methods simultaneously" do + before do + params.merge!( + incoming_exchanges: [{ + enterprise_id: supplier.id, + incoming: true, + active: true, + variants: { variant.id => true }, + receival_instructions: "Friday evening", + enterprise_fee_ids: [] + }], + outgoing_exchanges: [outgoing_exchange_params], + shipping_method_ids: [shipping_method.id] + ) + end + + it "saves everything i.e. the basics, incoming and outgoing exchanges and shipping methods" do expect(form.save).to be true - expect(form_applicator_mock).to_not have_received(:go!) expect(order_cycle.name).to eq 'Some new name' + expect(order_cycle.cached_incoming_exchanges.count).to eq 1 + expect(order_cycle.cached_outgoing_exchanges.count).to eq 1 + expect(order_cycle.shipping_methods).to eq [shipping_method] + end + end + + context "updating outgoing exchanges without specifying any shipping methods" do + before do + params.merge!( + outgoing_exchanges: [outgoing_exchange_params], + shipping_method_ids: nil + ) + end + + it "saves the outgoing exchanges, + it doesn't return a validation error because no shipping methods are present yet" do + expect(form.save).to be true + expect(order_cycle.cached_outgoing_exchanges.count).to eq 1 + end + end + + context "updating outgoing exchanges but specifying an invalid shipping method" do + let(:other_distributor_shipping_method) do + create(:shipping_method, distributors: [create(:distributor_enterprise)]) + end + + before do + params.merge!( + outgoing_exchanges: [outgoing_exchange_params], + shipping_method_ids: [other_distributor_shipping_method.id] + ) + end + + it "returns a validation error" do + expect(form.save).to be false + expect(order_cycle.errors.to_a).to eq [ + "Shipping method must be from a distributor on the order cycle" + ] + end + end + + context "when shipping methods already exist + and doing an update without the :shipping_methods_id parameter" do + it "doesn't return a validation error on shipping methods" do + order_cycle = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) + + form = OrderCycleForm.new( + order_cycle, + params.except(:shipping_method_ids), + order_cycle.coordinator + ) + + expect(form.save).to be true + end + end + + context "updating shipping methods" do + context "and it's valid" do + it "saves the changes" do + distributor = create(:distributor_enterprise) + shipping_method = create(:shipping_method, distributors: [distributor]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new(order_cycle, + { shipping_method_ids: [shipping_method.id] }, + order_cycle.coordinator) + + expect(form.save).to be true + expect(order_cycle.shipping_methods).to eq [shipping_method] + end + end + + context "and it's invalid" do + it "returns a validation error" do + distributor = create(:distributor_enterprise) + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new(order_cycle, + { shipping_method_ids: [] }, + order_cycle.coordinator) + + expect(form.save).to be false + expect(order_cycle.errors.to_a).to eq [ + "You need to select at least one shipping method for each distributor" + ] + end end end end From 1e817af5aac0fe778e06608bab7bfd99148d2199 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:04:37 +0100 Subject: [PATCH 08/69] Validate deleting a shipping method or switching it to backoffice only doesn't invalidate any order cycles --- app/models/spree/shipping_method.rb | 31 ++++++++++ config/locales/en.yml | 5 ++ spec/models/spree/shipping_method_spec.rb | 69 +++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 4e9fe38825..7a7eb44487 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -30,8 +30,11 @@ module Spree validates :name, presence: true validate :distributor_validation validate :at_least_one_shipping_category + validate :switching_to_backoffice_only_wont_leave_order_cycles_without_shipping_methods validates :display_on, inclusion: { in: DISPLAY_ON_OPTIONS.values }, allow_nil: true + before_destroy :check_destroy_wont_leave_order_cycles_without_shipping_methods + after_save :touch_distributors scope :managed_by, lambda { |user| @@ -112,6 +115,20 @@ module Spree private + def no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + return true if new_record? + + OrderCycle.active.or(OrderCycle.upcoming).joins(:coordinator, :shipping_methods).where(" + sells != 'own' AND + spree_shipping_methods.id = ? AND + NOT EXISTS( + SELECT 1 + FROM order_cycle_shipping_methods + WHERE order_cycle_id = order_cycles.id AND + shipping_method_id != ? + )", id, id).none? + end + def at_least_one_shipping_category return unless shipping_categories.empty? @@ -127,5 +144,19 @@ module Spree def distributor_validation validates_with DistributorsValidator end + + def check_destroy_wont_leave_order_cycles_without_shipping_methods + return if no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + + errors.add(:base, :destroy_leaves_order_cycles_without_shipping_methods) + throw :abort + end + + def switching_to_backoffice_only_wont_leave_order_cycles_without_shipping_methods + return if frontend? || + no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + + errors.add(:base, :switching_to_backoffice_only_leaves_order_cycles_without_shipping_methods) + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index de8bdb19ac..7c25622af2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -73,6 +73,11 @@ en: attributes: base: card_expired: "has expired" + spree/shipping_method: + attributes: + base: + switching_to_backoffice_only_leaves_order_cycles_without_shipping_methods: Unable to switch to backoffice only, some open or upcoming order cycles would be left without any shipping methods + destroy_leaves_order_cycles_without_shipping_methods: Unable to delete, some open or upcoming order cycles would be left without any shipping methods order_cycle: attributes: base: diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index 14d7333120..8c030fa02d 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -180,6 +180,31 @@ module Spree it { expect(shipping_method).to be_valid } end end + + context "when it is being changed to backoffice only" do + let!(:order_cycle) { create(:distributor_order_cycle, with_distributor_and_shipping_method: true) } + let(:distributor) { order_cycle.distributors.first } + let(:shipping_method) { order_cycle.shipping_methods.first } + + context "when the shipping method is the only shipping method on a distributor order cycle" do + it "should not be valid" do + shipping_method.display_on = "back_end" + + expect(shipping_method).not_to be_valid + expect(shipping_method.errors.to_a).to eq ["Unable to switch to backoffice only, some open or upcoming order cycles would be left without any shipping methods"] + end + end + + context "when the order cycles the shipping method is attached to has other valid shipping methods" do + it "is is valid" do + order_cycle.shipping_methods << create(:shipping_method, distributors: [distributor]) + + shipping_method.display_on = "back_end" + + expect(shipping_method).to be_valid + end + end + end end # Regression test for Spree #4320 @@ -213,5 +238,49 @@ module Spree expect(shipping_method.shipments).to include(shipment) end end + + context "#destroy" do + let(:shipping_method) { create(:shipping_method) } + let(:distributor) { create(:distributor_enterprise, shipping_methods: [shipping_method]) } + let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) } + + context "when the shipping method is the only shipping method on a distributor order cycle" do + it "can be deleted if the order cycle is closed" do + order_cycle.update!(orders_close_at: 1.minute.ago) + + shipping_method.destroy + + expect(shipping_method).to be_deleted + end + + it "cannot be deleted if the order cycle is active" do + order_cycle.update!(orders_open_at: 1.day.ago, orders_close_at: 1.week.from_now) + + shipping_method.destroy + + expect(shipping_method).not_to be_deleted + expect(shipping_method.errors.to_a).to eq ["Unable to delete, some open or upcoming order cycles would be left without any shipping methods"] + end + + it "cannot be deleted if the order cycle is upcoming" do + order_cycle.update!(orders_open_at: 1.day.from_now, orders_close_at: 1.week.from_now) + + shipping_method.destroy + + expect(shipping_method).not_to be_deleted + expect(shipping_method.errors.to_a).to eq ["Unable to delete, some open or upcoming order cycles would be left without any shipping methods"] + end + end + + context "when the order cycles the shipping method is attached to has other valid shipping methods" do + it "can be deleted" do + order_cycle.shipping_methods << create(:shipping_method, distributors: [distributor]) + + shipping_method.destroy + + expect(shipping_method).to be_deleted + end + end + end end end From 855ec1a70871e7dfbe5ff0cd96ff04b71a6c832e Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:15:38 +0100 Subject: [PATCH 09/69] Add new 'Checkout options' step to the edit order cycle form so people can attach shipping methods --- .../admin/order_cycles_controller.rb | 20 +- app/helpers/admin/order_cycles_helper.rb | 17 ++ app/models/order_cycle.rb | 17 ++ app/models/spree/ability.rb | 4 +- .../order_available_shipping_methods.rb | 29 ++ .../permitted_attributes/order_cycle.rb | 2 +- .../order_cycles/_wizard_progress.html.haml | 5 + .../order_cycles/checkout_options.html.haml | 83 ++++++ .../admin/order_cycles/outgoing.html.haml | 3 +- .../css/admin/components/save_bar.scss | 4 + app/webpacker/css/admin/openfoodnetwork.scss | 8 + config/locales/en.yml | 16 +- config/routes/admin.rb | 1 + spec/models/order_cycle_spec.rb | 18 ++ .../order_available_shipping_methods_spec.rb | 269 ++++++++++++++++++ .../complex_creating_specific_time_spec.rb | 11 + 16 files changed, 499 insertions(+), 8 deletions(-) create mode 100644 app/helpers/admin/order_cycles_helper.rb create mode 100644 app/services/order_available_shipping_methods.rb create mode 100644 app/views/admin/order_cycles/checkout_options.html.haml create mode 100644 spec/services/order_available_shipping_methods_spec.rb diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 866fd94334..5812d2395c 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -2,10 +2,10 @@ module Admin class OrderCyclesController < Admin::ResourceController - include OrderCyclesHelper + include ::OrderCyclesHelper include PaperTrailLogging - prepend_before_action :set_order_cycle_id, only: [:incoming, :outgoing] + prepend_before_action :set_order_cycle_id, only: [:incoming, :outgoing, :checkout_options] before_action :load_data_for_index, only: :index before_action :require_coordinator, only: :new before_action :remove_protected_attrs, only: [:update] @@ -67,10 +67,12 @@ module Admin update_nil_subscription_line_items_price_estimate(@order_cycle) respond_to do |format| flash[:notice] = I18n.t(:order_cycles_update_notice) if params[:reloading] == '1' - format.html { redirect_back(fallback_location: root_path) } + format.html { redirect_to_after_update_path } format.json { render json: { success: true } } end - else + elsif request.format.html? + render :checkout_options + elsif request.format.json? render json: { errors: @order_cycle.errors.full_messages }, status: :unprocessable_entity end end @@ -190,6 +192,16 @@ module Admin end end + def redirect_to_after_update_path + if params[:context] == "checkout_options" && params[:save] + redirect_to main_app.admin_order_cycle_checkout_options_path(@order_cycle) + elsif params[:context] == "checkout_options" && params[:save_and_back_to_list] + redirect_to main_app.admin_order_cycles_path + else + redirect_back(fallback_location: root_path) + end + end + def require_coordinator @order_cycle.coordinator = permitted_coordinating_enterprises_for(@order_cycle).find_by(id: params[:coordinator_id]) diff --git a/app/helpers/admin/order_cycles_helper.rb b/app/helpers/admin/order_cycles_helper.rb new file mode 100644 index 0000000000..c046ded504 --- /dev/null +++ b/app/helpers/admin/order_cycles_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Admin + module OrderCyclesHelper + def order_cycle_shared_payment_methods(order_cycle) + order_cycle.attachable_payment_methods.select do |payment_method| + (payment_method.distributor_ids & order_cycle.distributor_ids).many? + end + end + + def order_cycle_shared_shipping_methods(order_cycle) + order_cycle.attachable_shipping_methods.select do |shipping_method| + (shipping_method.distributor_ids & order_cycle.distributor_ids).many? + end + end + end +end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 86f1a14a54..84800086c9 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -156,6 +156,23 @@ class OrderCycle < ApplicationRecord ] end + def attachable_payment_methods + Spree::PaymentMethod.available(:both). + joins("INNER JOIN distributors_payment_methods + ON payment_method_id = spree_payment_methods.id"). + where("distributor_id IN (?)", distributor_ids). + distinct + end + + def attachable_shipping_methods + return Spree::ShippingMethod.none if simple? || !shipping_methods_customisable? + + Spree::ShippingMethod.frontend. + joins(:distributor_shipping_methods). + where("distributor_id IN (?)", distributor_ids). + distinct + end + def clone! oc = dup oc.name = I18n.t("models.order_cycle.cloned_order_cycle_name", order_cycle: oc.name) diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index 2e9aec849f..63857559cd 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -243,7 +243,9 @@ module Spree end def add_order_cycle_management_abilities(user) - can [:admin, :index, :read, :edit, :update, :incoming, :outgoing], OrderCycle do |order_cycle| + can [ + :admin, :index, :read, :edit, :update, :incoming, :outgoing, :checkout_options + ], OrderCycle do |order_cycle| OrderCycle.visible_by(user).include? order_cycle end can [:admin, :index, :create], Schedule diff --git a/app/services/order_available_shipping_methods.rb b/app/services/order_available_shipping_methods.rb new file mode 100644 index 0000000000..3c815d19a0 --- /dev/null +++ b/app/services/order_available_shipping_methods.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class OrderAvailableShippingMethods < Struct.new(:order, :customer) + delegate :distributor, + :order_cycle, + to: :order + + def to_a + return [] if distributor.blank? + + shipping_methods = shipping_methods_before_tag_rules_applied + + applicator = OpenFoodNetwork::TagRuleApplicator.new(distributor, + "FilterShippingMethods", customer&.tag_list) + applicator.filter!(shipping_methods) + + shipping_methods.uniq + end + + private + + def shipping_methods_before_tag_rules_applied + if order_cycle.nil? || order_cycle.simple? + distributor.shipping_methods + else + distributor.shipping_methods.where(id: order_cycle.shipping_methods.select(:id)) + end.frontend.to_a + end +end diff --git a/app/services/permitted_attributes/order_cycle.rb b/app/services/permitted_attributes/order_cycle.rb index dd74607168..97282acabf 100644 --- a/app/services/permitted_attributes/order_cycle.rb +++ b/app/services/permitted_attributes/order_cycle.rb @@ -17,7 +17,7 @@ module PermittedAttributes :name, :orders_open_at, :orders_close_at, :coordinator_id, :preferred_product_selection_from_coordinator_inventory_only, :automatic_notifications, - { schedule_ids: [], coordinator_fee_ids: [] } + { schedule_ids: [], shipping_method_ids: [], coordinator_fee_ids: [] } ] end diff --git a/app/views/admin/order_cycles/_wizard_progress.html.haml b/app/views/admin/order_cycles/_wizard_progress.html.haml index 3224324ca6..cc13adcebf 100644 --- a/app/views/admin/order_cycles/_wizard_progress.html.haml +++ b/app/views/admin/order_cycles/_wizard_progress.html.haml @@ -6,6 +6,8 @@ = t("admin.order_cycles.wizard_progress.incoming") %li = t("admin.order_cycles.wizard_progress.outgoing") + %li + = t("admin.order_cycles.wizard_progress.checkout_options") - else %li{ class: "#{'current' if action_name == 'edit'}" } %a{ href: main_app.edit_admin_order_cycle_path(@order_cycle) } @@ -16,3 +18,6 @@ %li{ class: "#{'current' if action_name == 'outgoing'}" } %a{ href: main_app.admin_order_cycle_outgoing_path(@order_cycle) } = t("admin.order_cycles.wizard_progress.outgoing") + %li{ class: "#{'current' if action_name == 'checkout_options'}" } + %a{ href: main_app.admin_order_cycle_checkout_options_path(@order_cycle) } + = t("admin.order_cycles.wizard_progress.checkout_options") diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml new file mode 100644 index 0000000000..97f436308c --- /dev/null +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -0,0 +1,83 @@ += render partial: "/admin/order_cycles/order_cycle_top_buttons" + +- content_for :page_title do + = t :edit_order_cycle + +- shared_payment_methods = order_cycle_shared_payment_methods(@order_cycle) +- shared_shipping_methods = order_cycle_shared_shipping_methods(@order_cycle) + += form_for [main_app, :admin, @order_cycle], html: { class: "order_cycle" } do |f| + + = render 'wizard_progress' + + %fieldset.no-border-bottom + %legend{ align: 'center'}= t('.checkout_options') + + %table.checkout-options + %thead + %tr + %th= t('.distributor') + %th= t('.shipping_methods') + %th= t('.payment_methods') + - @order_cycle.distributors.each do |distributor| + - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id).reject { |payment_method| shared_payment_methods.include?(payment_method) } + - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id).reject { |shipping_method| shared_shipping_methods.include?(shipping_method) } + %tr + %td= distributor.name + %td + - shipping_methods.each do |shipping_method| + %p + %label + = check_box_tag "order_cycle[preferred_shipping_method_ids][]", + shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), + id: "order_cycle_preferred_shipping_method_ids_#{shipping_method.id}" + = shipping_method.name + - distributor.shipping_methods.backend.each do |shipping_method| + %label.disabled + = check_box_tag nil, nil, false, disabled: true + = shipping_method.name + = "(#{t('.back_end')})" + - if shipping_methods.none? && distributor.shipping_methods.backend.none? + %p.text-center + = t('.no_shipping_methods') + %td + - if payment_methods.any? + %ul + - payment_methods.each do |payment_method| + %li= payment_method.name + - else + %p.text-center + = t('.no_payment_methods') + - if shared_payment_methods.any? || shared_shipping_methods.any? + %tr + %td= t('.shared') + %td + - if shared_shipping_methods.any? + = f.collection_check_boxes :shipping_method_ids, shared_shipping_methods, :id, :name do |input| + - shared_shipping_method = input.object + %p + = input.check_box + = input.label + %p + = "—#{shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ")}".html_safe + %td + - if shared_payment_methods.any? + %ul + - shared_payment_methods.each do |shared_payment_method| + %li + = shared_payment_method.name + %p + = "—#{shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ")}".html_safe + + %div#save-bar + %div.container + %div.seven.columns.alpha + - if @order_cycle.errors.any? + %h5#status-message.error + = @order_cycle.errors.to_a.to_sentence + %div.nine.columns.omega.text-right + = hidden_field_tag :context, :checkout_options + = f.submit t('.save'), class: "red", name: :save + = f.submit t('.save_and_back_to_list'), class: "red", name: :save_and_back_to_list + %a.button.cancel{ href: main_app.admin_order_cycles_path } + = t('.cancel') diff --git a/app/views/admin/order_cycles/outgoing.html.haml b/app/views/admin/order_cycles/outgoing.html.haml index 9924e3dae9..e7756a713d 100644 --- a/app/views/admin/order_cycles/outgoing.html.haml +++ b/app/views/admin/order_cycles/outgoing.html.haml @@ -10,7 +10,8 @@ %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } %input.red{ type: "button", value: t('.save'), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } - %input.red{ type: "button", value: t('.save_and_back_to_list'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } + %input.red{ type: "button", value: t('.save_and_next'), ng: { click: "submit($event, '#{main_app.admin_order_cycle_checkout_options_path(@order_cycle)}')", disabled: "!order_cycle_form.$dirty || order_cycle_form.$invalid" } } + %input{ type: "button", value: t('.next'), ng: { click: "cancel('#{main_app.admin_order_cycle_checkout_options_path(@order_cycle)}')", disabled: "order_cycle_form.$dirty" } } %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? '#{t('.cancel')}' : '#{t('.back_to_list')}'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } %fieldset.no-border-bottom diff --git a/app/webpacker/css/admin/components/save_bar.scss b/app/webpacker/css/admin/components/save_bar.scss index 70f906119f..b60482de11 100644 --- a/app/webpacker/css/admin/components/save_bar.scss +++ b/app/webpacker/css/admin/components/save_bar.scss @@ -11,6 +11,10 @@ h5 { color: $spree-blue; + + &.error { + color: $red-500; + } } input { diff --git a/app/webpacker/css/admin/openfoodnetwork.scss b/app/webpacker/css/admin/openfoodnetwork.scss index 636b938d6d..aa22632fd0 100644 --- a/app/webpacker/css/admin/openfoodnetwork.scss +++ b/app/webpacker/css/admin/openfoodnetwork.scss @@ -96,6 +96,14 @@ form.order_cycle { .icon-question-sign { font-size: 18px; } + table.checkout-options { + ul { + margin-left: 1em; + } + p, li { + margin: 0.5em 0; + } + } table.exchanges { tr td.active { width: 20px; diff --git a/config/locales/en.yml b/config/locales/en.yml index 7c25622af2..ef52faec39 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1173,15 +1173,29 @@ en: tags: "Tags" delivery_details: "Delivery Details" fees: "Fees" + next: "Next" previous: "Previous" save: "Save" - save_and_back_to_list: "Save and Back to List" + save_and_next: "Save and Next" cancel: "Cancel" back_to_list: "Back To List" + checkout_options: + back_end: "Back office only" + cancel: "Cancel" + checkout_options: "Checkout options" + distributor: "Distributor" + no_payment_methods: Each distributor on this order cycle requires at least one payment method. + no_shipping_methods: Each distributor on this order cycle requires at least one shipping method. + payment_methods: "Payment Methods" + save: "Save" + save_and_back_to_list: "Save and Back to List" + shared: "Shared" + shipping_methods: "Shipping Methods" wizard_progress: edit: "1. General Settings" incoming: "2. Incoming Products" outgoing: "3. Outgoing Products" + checkout_options: "4. Checkout Options" exchange_form: pickup_time_tip: When orders from this OC will be ready for the customer pickup_instructions_placeholder: "Pick-up instructions" diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 7fc4afcd8f..99beb534d0 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -15,6 +15,7 @@ Openfoodnetwork::Application.routes.draw do post :bulk_update, on: :collection, as: :bulk_update get :incoming get :outgoing + get :checkout_options member do get :clone diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 16d21fdabd..06443fb7ef 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -677,6 +677,24 @@ describe OrderCycle do end end + describe "#attachable_shipping_methods" do + it "includes shipping methods from the distributors on the order cycle" do + shipping_method = create(:shipping_method) + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + oc = create(:simple_order_cycle, distributors: [enterprise]) + + expect(oc.attachable_shipping_methods).to eq([shipping_method]) + end + + it "does not include backoffice only shipping methods" do + shipping_method = create(:shipping_method, display_on: "back_end") + enterprise = create(:enterprise, shipping_methods: [shipping_method]) + oc = create(:simple_order_cycle, distributors: [enterprise]) + + expect(oc.attachable_shipping_methods).to be_empty + end + end + describe "#simple?" do it "returns true if the coordinator sells their own products i.e. shops" do order_cycle = build(:simple_order_cycle, coordinator: build(:enterprise, sells: "own")) diff --git a/spec/services/order_available_shipping_methods_spec.rb b/spec/services/order_available_shipping_methods_spec.rb new file mode 100644 index 0000000000..814853d6eb --- /dev/null +++ b/spec/services/order_available_shipping_methods_spec.rb @@ -0,0 +1,269 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe OrderAvailableShippingMethods do + context "when the order has no current_distributor" do + it "returns an empty array" do + order_cycle = create(:sells_own_order_cycle) + order = build(:order, distributor: nil, order_cycle: order_cycle) + + expect(OrderAvailableShippingMethods.new(order).to_a).to eq [] + end + end + + it "does not return 'back office only' shipping method" do + distributor = create(:distributor_enterprise) + frontend_shipping_method = create(:shipping_method, distributors: [distributor]) + backoffice_only_shipping_method = create(:shipping_method, + distributors: [distributor], display_on: 'back_end') + order_cycle = create(:sells_own_order_cycle) + order = build(:order, distributor: distributor, order_cycle: order_cycle) + + available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a + + expect(available_shipping_methods).to eq [frontend_shipping_method] + end + + context "when no tag rules are in effect" do + context "order cycle selling own produce only i.e. shipping methods cannot be customised" do + it "returns all shipping methods belonging to the enterprise" do + order_cycle = create(:sells_own_order_cycle) + enterprise = order_cycle.coordinator + shipping_method = create(:shipping_method, distributors: [enterprise]) + other_enterprise = create(:enterprise) + other_enterprise_shipping_method = create(:shipping_method, + distributors: [other_enterprise]) + order = build(:order, distributor: enterprise, order_cycle: order_cycle) + + available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a + + expect(order_cycle.shipping_methods).to be_empty + expect(available_shipping_methods).to eq [shipping_method] + end + end + + context "distributor order cycle" do + it "only returns shipping methods which belong to the order distributor + and have been added to the order cycle" do + distributor = create(:distributor_enterprise) + shipping_method_i = create(:shipping_method, distributors: [distributor]) + shipping_method_ii = create(:shipping_method, distributors: [distributor]) + order_cycle = create(:simple_order_cycle, + distributors: [distributor], shipping_methods: [shipping_method_i]) + order = build(:order, distributor: distributor, order_cycle: order_cycle) + + available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a + + expect(available_shipping_methods).to eq order_cycle.shipping_methods + expect(available_shipping_methods).to eq [shipping_method_i] + end + + it "doesn't return shipping methods which have been added to the order cycle + when they don't belong to the order distributor" do + distributor_i = create(:distributor_enterprise) + distributor_ii = create(:distributor_enterprise) + shipping_method_i = create(:shipping_method, distributors: [distributor_i]) + shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) + order_cycle = create(:simple_order_cycle, + distributors: [distributor_i, distributor_ii], + shipping_methods: [shipping_method_i, shipping_method_ii]) + order = build(:order, distributor: distributor_ii, order_cycle: order_cycle) + + available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a + + expect(available_shipping_methods).not_to eq order_cycle.shipping_methods + expect(available_shipping_methods).to eq [shipping_method_ii] + end + end + end + + context "when FilterShippingMethods tag rules are in effect" do + let(:user) { create(:user) } + let(:distributor) { create(:distributor_enterprise) } + let(:other_distributor) { create(:distributor_enterprise) } + let!(:distributor_shipping_method) { create(:shipping_method, distributors: [distributor]) } + let!(:other_distributor_shipping_method) do + create(:shipping_method, distributors: [other_distributor]) + end + let(:customer) { create(:customer, user: user, enterprise: distributor) } + let!(:tag_rule) { + create(:filter_shipping_methods_tag_rule, + enterprise: distributor, + preferred_customer_tags: "local", + preferred_shipping_method_tags: "local-delivery") + } + let!(:default_tag_rule) { + create(:filter_shipping_methods_tag_rule, + enterprise: distributor, + is_default: true, + preferred_shipping_method_tags: "local-delivery") + } + let!(:tagged_sm) { distributor_shipping_method } + let!(:untagged_sm) { other_distributor_shipping_method } + + before do + tagged_sm.update_attribute(:tag_list, 'local-delivery') + distributor.shipping_methods = [tagged_sm, untagged_sm] + end + + context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do + before { + tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible') + } + before { + default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, + 'hidden') + } + + context "order cycle selling own produce only" do + let(:order_cycle) { create(:sells_own_order_cycle) } + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + + context "when the customer is nil" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } + + it "applies default action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + + context "when a customer is present" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + + context "and the customer's tags match" do + before do + customer.update_attribute(:tag_list, 'local') + end + + it "applies the action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm + end + end + + context "and the customer's tags don't match" do + before do + customer.update_attribute(:tag_list, 'something') + end + + it "applies the default action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + end + end + + context "distributor order cycle" do + context "when the shipping method without the tag rule is attached to the order cycle + and the shipping method with the tag rule is not" do + let(:order_cycle) do + create(:distributor_order_cycle, + distributors: [distributor], shipping_methods: [untagged_sm]) + end + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + + context "when the customer's tags match" do + before do + customer.update_attribute(:tag_list, 'local') + end + + it "doesn't display the shipping method with the prefered visibility 'visible' tag + even though the customer's tags match + because it hasn't been attached to the order cycle" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + end + end + end + + context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do + before { + tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden') + } + before { + default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, + 'visible') + } + + context "order cycle selling own produce only" do + let(:order_cycle) { create(:sells_own_order_cycle) } + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + + context "when the customer is nil" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } + + it "applies default action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm + end + end + + context "when a customer is present" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + + context "and the customer's tags match" do + before do + customer.update_attribute(:tag_list, 'local') + end + + it "applies the action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + + context "and the customer's tags don't match" do + before do + customer.update_attribute(:tag_list, 'something') + end + + it "applies the default action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm + end + end + end + end + + context "distributor order cycle" do + context "when the shipping method without the tag rule is attached to the order cycle + and the shipping method with the tag rule is not" do + let(:order_cycle) do + create(:distributor_order_cycle, + distributors: [distributor], shipping_methods: [untagged_sm]) + end + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + + context "when the customer is nil" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } + + it "doesn't display the shipping method tagged to be visible by default + because it is not attached to the order cycle" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + + context "when a customer is present" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + + context "when the customer's tags don't match" do + before do + customer.update_attribute(:tag_list, 'something') + end + + it "doesn't display the shipping method tagged to be visible by default + because it is not attached to the order cycle" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end + end + end + end + end + end +end diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index e16d86796f..19ab92a0bb 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -22,6 +22,8 @@ describe ' v2 = create(:variant, product: product) distributor = create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) + shipping_method_i = distributor.shipping_methods.first + shipping_method_ii = create(:shipping_method, distributors: [distributor]) # Relationships required for interface to work create(:enterprise_relationship, parent: supplier, child: coordinator, @@ -129,6 +131,12 @@ describe ' select 'Distributor fee', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id' + click_button 'Save and Next' + + # And I select preferred shipping methods + check "order_cycle_preferred_shipping_method_ids_#{shipping_method_i.id}" + uncheck "order_cycle_preferred_shipping_method_ids_#{shipping_method_ii.id}" + click_button 'Save and Back to List' oc = OrderCycle.last @@ -161,5 +169,8 @@ describe ' expect(exchange.pickup_time).to eq('pickup time') expect(exchange.pickup_instructions).to eq('pickup instructions') expect(exchange.tag_list).to eq(['wholesale']) + + # And the shipping method should be attached + expect(oc.shipping_methods).to eq([shipping_method_i]) end end From bff7650b35570f586e2222caf15c9acf4f9aa1cd Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:19:04 +0100 Subject: [PATCH 10/69] Change :available_shipping_methods helper to use new OrderAvailableShippingMethods service. --- app/helpers/enterprises_helper.rb | 10 +- spec/helpers/enterprises_helper_spec.rb | 134 ------------------------ 2 files changed, 1 insertion(+), 143 deletions(-) diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb index e535e9a7ba..701f736180 100644 --- a/app/helpers/enterprises_helper.rb +++ b/app/helpers/enterprises_helper.rb @@ -14,15 +14,7 @@ module EnterprisesHelper end def available_shipping_methods - return [] if current_distributor.blank? - - shipping_methods = current_distributor.shipping_methods.frontend.to_a - - applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, - "FilterShippingMethods", current_customer&.tag_list) - applicator.filter!(shipping_methods) - - shipping_methods.uniq + OrderAvailableShippingMethods.new(current_order, current_customer).to_a end def available_payment_methods diff --git a/spec/helpers/enterprises_helper_spec.rb b/spec/helpers/enterprises_helper_spec.rb index 7029ede7c3..9bea500126 100644 --- a/spec/helpers/enterprises_helper_spec.rb +++ b/spec/helpers/enterprises_helper_spec.rb @@ -9,140 +9,6 @@ describe EnterprisesHelper, type: :helper do before { allow(helper).to receive(:spree_current_user) { user } } - describe "loading available shipping methods" do - let!(:distributor_shipping_method) { - create(:shipping_method, require_ship_address: false, distributors: [distributor]) - } - let!(:other_distributor_shipping_method) { - create(:shipping_method, require_ship_address: false, distributors: [some_other_distributor]) - } - - context "when the order has no current_distributor" do - before do - allow(helper).to receive(:current_distributor) { nil } - end - - it "returns an empty array" do - expect(helper.available_shipping_methods).to eq [] - end - end - - context "when no tag rules are in effect" do - before { allow(helper).to receive(:current_distributor) { distributor } } - - it "finds the shipping methods for the current distributor" do - expect(helper.available_shipping_methods).to_not include other_distributor_shipping_method - expect(helper.available_shipping_methods).to include distributor_shipping_method - end - - it "does not return 'back office only' shipping method" do - backoffice_only_shipping_method = create(:shipping_method, require_ship_address: false, - distributors: [distributor], display_on: 'back_end') - - expect(helper.available_shipping_methods).to_not include backoffice_only_shipping_method - expect(helper.available_shipping_methods).to_not include other_distributor_shipping_method - expect(helper.available_shipping_methods).to include distributor_shipping_method - end - end - - context "when FilterShippingMethods tag rules are in effect" do - let(:customer) { create(:customer, user: user, enterprise: distributor) } - let!(:tag_rule) { - create(:filter_shipping_methods_tag_rule, - enterprise: distributor, - preferred_customer_tags: "local", - preferred_shipping_method_tags: "local-delivery") - } - let!(:default_tag_rule) { - create(:filter_shipping_methods_tag_rule, - enterprise: distributor, - is_default: true, - preferred_shipping_method_tags: "local-delivery") - } - let!(:tagged_sm) { distributor_shipping_method } - let!(:untagged_sm) { other_distributor_shipping_method } - - before do - tagged_sm.update_attribute(:tag_list, 'local-delivery') - distributor.shipping_methods = [tagged_sm, untagged_sm] - allow(helper).to receive(:current_distributor) { distributor } - end - - context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do - before { - tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible') - } - before { - default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, - 'hidden') - } - - context "when the customer is nil" do - it "applies default action (hide)" do - expect(helper.current_customer).to be nil - expect(helper.available_shipping_methods).to include untagged_sm - expect(helper.available_shipping_methods).to_not include tagged_sm - end - end - - context "when the customer's tags match" do - before { customer.update_attribute(:tag_list, 'local') } - - it "applies the action (show)" do - expect(helper.current_customer).to eq customer - expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm - end - end - - context "when the customer's tags don't match" do - before { customer.update_attribute(:tag_list, 'something') } - - it "applies the default action (hide)" do - expect(helper.current_customer).to eq customer - expect(helper.available_shipping_methods).to include untagged_sm - expect(helper.available_shipping_methods).to_not include tagged_sm - end - end - end - - context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do - before { - tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden') - } - before { - default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, - 'visible') - } - - context "when the customer is nil" do - it "applies default action (show)" do - expect(helper.current_customer).to be nil - expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm - end - end - - context "when the customer's tags match" do - before { customer.update_attribute(:tag_list, 'local') } - - it "applies the action (hide)" do - expect(helper.current_customer).to eq customer - expect(helper.available_shipping_methods).to include untagged_sm - expect(helper.available_shipping_methods).to_not include tagged_sm - end - end - - context "when the customer's tags don't match" do - before { customer.update_attribute(:tag_list, 'something') } - - it "applies the default action (show)" do - expect(helper.current_customer).to eq customer - expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm - end - end - end - end - end - describe "loading available payment methods" do let!(:pm1) { create(:payment_method, distributors: [distributor]) } let!(:pm2) { create(:payment_method, distributors: [some_other_distributor]) } From 5a4bc79c7430a9a47231e05e0736c11361c66194 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:22:06 +0100 Subject: [PATCH 11/69] Update sample data task so distributor order cycles have shipping methods attached i.e. are valid --- lib/tasks/sample_data/order_cycle_factory.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/tasks/sample_data/order_cycle_factory.rb b/lib/tasks/sample_data/order_cycle_factory.rb index 0f9dc258e8..d6701d5177 100644 --- a/lib/tasks/sample_data/order_cycle_factory.rb +++ b/lib/tasks/sample_data/order_cycle_factory.rb @@ -57,6 +57,9 @@ module SampleData log "- #{name}" cycle = create_order_cycle_with_fee(name, coordinator) create_exchanges(cycle, supplier_names, distributor_names, data) + return if cycle.reload.attachable_shipping_methods.none? + + cycle.shipping_method_ids = cycle.attachable_shipping_methods.pluck(:id) end def create_order_cycle_with_fee(name, coordinator) From a46b77d10c12db330e200a033b09dd6a9ab7fdb0 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:26:12 +0100 Subject: [PATCH 12/69] Ensure hack for showing products on a shop with closed order cycles also works if an order cycle has no shipping methods If a distributor order cycle has no shipping methods it will be invalid. This is a bit confusing because adding a check for :invalid seems like it would be better if it was done in the Shop::OrderCyclesList service but the hack doesn't work if you put it in there, it seems like something needs a refactor. --- app/controllers/base_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 7816066b8b..626307db49 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -15,8 +15,12 @@ class BaseController < ApplicationController private + def all_distributor_order_cycles_invalid? + OrderCycle.with_distributor(@distributor).active.all?(&:invalid?) + end + def set_order_cycles - unless @distributor.ready_for_checkout? + if !@distributor.ready_for_checkout? || all_distributor_order_cycles_invalid? @order_cycles = OrderCycle.where('false') return end From 4e0bf75ecfa11ecf46ee14942dcb2e18018c8a8b Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 8 Jun 2022 21:28:37 +0100 Subject: [PATCH 13/69] Get tests broken because of new order cycle shipping method validations working again --- .../enterprises_controller_spec.rb | 19 +++-- spec/controllers/shop_controller_spec.rb | 33 +++++--- spec/factories/order_factory.rb | 2 +- spec/factories/subscription_factory.rb | 8 +- .../order_cycle_permissions_spec.rb | 6 +- spec/models/order_cycle_spec.rb | 84 ++++++++++++------- spec/models/spree/order_spec.rb | 10 +-- .../consumer/caching/shops_caching_spec.rb | 5 +- .../consumer/shopping/checkout_auth_spec.rb | 4 +- .../consumer/shopping/checkout_paypal_spec.rb | 1 + .../system/consumer/shopping/checkout_spec.rb | 6 +- .../consumer/shopping/checkout_stripe_spec.rb | 4 +- .../system/consumer/shopping/products_spec.rb | 4 +- .../system/consumer/shopping/shopping_spec.rb | 8 +- .../consumer/shopping/unit_price_spec.rb | 4 +- .../shopping/variant_overrides_spec.rb | 3 +- spec/system/consumer/split_checkout_spec.rb | 3 +- 17 files changed, 134 insertions(+), 70 deletions(-) diff --git a/spec/controllers/enterprises_controller_spec.rb b/spec/controllers/enterprises_controller_spec.rb index c9ee7b4437..13f3209436 100644 --- a/spec/controllers/enterprises_controller_spec.rb +++ b/spec/controllers/enterprises_controller_spec.rb @@ -9,13 +9,17 @@ describe EnterprisesController, type: :controller do let(:line_item) { create(:line_item) } let!(:current_distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:shipping_method) { distributor.shipping_methods.first } let!(:order_cycle1) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 2.days.ago, - orders_close_at: 3.days.from_now, variants: [line_item.variant] ) + orders_close_at: 3.days.from_now, + shipping_methods: [shipping_method], + variants: [line_item.variant] ) } let!(:order_cycle2) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, - orders_close_at: 4.days.from_now ) + orders_close_at: 4.days.from_now, + shipping_methods: [shipping_method]) } before do @@ -55,8 +59,10 @@ describe EnterprisesController, type: :controller do context "using FilterOrderCycles tag rules" do let!(:order_cycle3) { - create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, - orders_close_at: 4.days.from_now) + create(:simple_order_cycle, distributors: [distributor], + orders_open_at: 3.days.ago, + orders_close_at: 4.days.from_now, + shipping_methods: [shipping_method]) } let!(:oc3_exchange) { order_cycle3.exchanges.outgoing.to_enterprise(distributor).first } let(:customer) { create(:customer, user: user, enterprise: distributor) } @@ -116,7 +122,10 @@ describe EnterprisesController, type: :controller do let(:variant) { create(:variant, on_demand: false, on_hand: 10) } let(:line_item) { create(:line_item, variant: variant) } let(:order_cycle) { - create(:simple_order_cycle, distributors: [current_distributor], variants: [variant]) + create(:simple_order_cycle, + distributors: [current_distributor], + shipping_methods: [current_distributor.shipping_methods.first], + variants: [variant]) } before do diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index b072152e72..d55d88b603 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -21,21 +21,21 @@ describe ShopController, type: :controller do describe "selecting an order cycle" do it "should select an order cycle when only one order cycle is open" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) get :show expect(controller.current_order_cycle).to eq(oc1) end it "should not set an order cycle when multiple order cycles are open" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) - oc2 = create(:simple_order_cycle, distributors: [distributor]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) get :show expect(controller.current_order_cycle).to be_nil end it "should allow the user to post to select the current order cycle" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) - oc2 = create(:simple_order_cycle, distributors: [distributor]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) spree_post :order_cycle, order_cycle_id: oc2.id expect(response.status).to eq 200 @@ -46,8 +46,8 @@ describe ShopController, type: :controller do render_views it "should return the order cycle details when the OC is selected" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) - oc2 = create(:simple_order_cycle, distributors: [distributor]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) spree_post :order_cycle, order_cycle_id: oc2.id expect(response.status).to eq 200 @@ -55,15 +55,19 @@ describe ShopController, type: :controller do end it "should return the current order cycle when hit with GET" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) allow(controller).to receive(:current_order_cycle).and_return oc1 get :order_cycle expect(response.body).to have_content oc1.id end context "when the order cycle has already been set" do - let(:oc1) { create(:simple_order_cycle, distributors: [distributor]) } - let(:oc2) { create(:simple_order_cycle, distributors: [distributor]) } + let(:oc1) do + create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + end + let(:oc2) do + create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + end let(:order) { create(:order, order_cycle: oc1) } before { allow(controller).to receive(:current_order) { order } } @@ -77,9 +81,12 @@ describe ShopController, type: :controller do end it "should not allow the user to select an invalid order cycle" do - oc1 = create(:simple_order_cycle, distributors: [distributor]) - oc2 = create(:simple_order_cycle, distributors: [distributor]) - oc3 = create(:simple_order_cycle, distributors: [create(:distributor_enterprise)]) + oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + other_distributor = create(:distributor_enterprise, with_payment_and_shipping: true) + oc3 = create(:simple_order_cycle, + distributors: [other_distributor], + shipping_methods: [other_distributor.shipping_methods.first]) spree_post :order_cycle, order_cycle_id: oc3.id expect(response.status).to eq(404) diff --git a/spec/factories/order_factory.rb b/spec/factories/order_factory.rb index 6f38184372..b1b2b3e66c 100644 --- a/spec/factories/order_factory.rb +++ b/spec/factories/order_factory.rb @@ -13,7 +13,7 @@ FactoryBot.define do factory :order_ready_for_details do distributor { create(:distributor_enterprise, with_payment_and_shipping: true) } - order_cycle { create(:order_cycle, distributors: [distributor]) } + order_cycle { create(:order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) } after(:create) do |order| order.line_items << build(:line_item, order: order) diff --git a/spec/factories/subscription_factory.rb b/spec/factories/subscription_factory.rb index f0b67c0631..5bccb7eaf0 100644 --- a/spec/factories/subscription_factory.rb +++ b/spec/factories/subscription_factory.rb @@ -3,7 +3,13 @@ FactoryBot.define do factory :subscription, class: Subscription do shop { create :enterprise } - schedule { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: shop)]) } + schedule do + order_cycle = create(:distributor_order_cycle, + coordinator: shop, + distributors: [shop], + shipping_methods: [shipping_method]) + create(:schedule, order_cycles: [order_cycle]) + end customer { create(:customer, enterprise: shop) } bill_address { create(:address, :randomized) } ship_address { create(:address, :randomized) } diff --git a/spec/lib/open_food_network/order_cycle_permissions_spec.rb b/spec/lib/open_food_network/order_cycle_permissions_spec.rb index 4cabc5d3ac..cc7a33c91a 100644 --- a/spec/lib/open_food_network/order_cycle_permissions_spec.rb +++ b/spec/lib/open_food_network/order_cycle_permissions_spec.rb @@ -10,7 +10,7 @@ module OpenFoodNetwork let(:producer) { create(:supplier_enterprise) } let(:user) { double(:user) } let(:oc) { create(:simple_order_cycle, coordinator: coordinator) } - let(:permissions) { OrderCyclePermissions.new(user, oc) } + let(:permissions) { OrderCyclePermissions.new(user, oc.reload) } describe "finding enterprises that can be viewed in the order cycle interface" do context "when permissions are initialized without an order_cycle" do @@ -53,7 +53,7 @@ module OpenFoodNetwork end context "where the coordinator sells 'own'" do - before { allow(coordinator).to receive(:sells) { 'own' } } + before { allow(oc.coordinator).to receive(:sells) { 'own' } } it "returns just the coordinator" do enterprises = permissions.visible_enterprises expect(enterprises).to_not include hub, producer @@ -80,7 +80,7 @@ module OpenFoodNetwork end context "where the coordinator sells 'own'" do - before { allow(coordinator).to receive(:sells) { 'own' } } + before { allow(oc.coordinator).to receive(:sells) { 'own' } } it "returns just the coordinator" do enterprises = permissions.visible_enterprises expect(enterprises).to_not include hub, producer diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 06443fb7ef..ab66327504 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -434,39 +434,65 @@ describe OrderCycle do end end - it "clones itself" do - coordinator = create(:enterprise); - oc = create(:simple_order_cycle, - coordinator_fees: [create(:enterprise_fee, enterprise: coordinator)], - preferred_product_selection_from_coordinator_inventory_only: true, - automatic_notifications: true, processed_at: Time.zone.now, mails_sent: true) - schedule = create(:schedule, order_cycles: [oc]) - ex1 = create(:exchange, order_cycle: oc) - ex2 = create(:exchange, order_cycle: oc) - oc.clone! + describe "clone!" do + it "clones itself" do + coordinator = create(:enterprise); + oc = create(:simple_order_cycle, + coordinator_fees: [create(:enterprise_fee, enterprise: coordinator)], + preferred_product_selection_from_coordinator_inventory_only: true, + automatic_notifications: true, processed_at: Time.zone.now, mails_sent: true) + schedule = create(:schedule, order_cycles: [oc]) + ex1 = create(:exchange, order_cycle: oc) + ex2 = create(:exchange, order_cycle: oc) + oc.clone! - occ = OrderCycle.last - expect(occ.name).to eq("COPY OF #{oc.name}") - expect(occ.orders_open_at).to be_nil - expect(occ.orders_close_at).to be_nil - expect(occ.coordinator).not_to be_nil - expect(occ.preferred_product_selection_from_coordinator_inventory_only).to be true - expect(occ.automatic_notifications).to eq(oc.automatic_notifications) - expect(occ.processed_at).to eq(nil) - expect(occ.mails_sent).to eq(nil) - expect(occ.coordinator).to eq(oc.coordinator) + occ = OrderCycle.last + expect(occ.name).to eq("COPY OF #{oc.name}") + expect(occ.orders_open_at).to be_nil + expect(occ.orders_close_at).to be_nil + expect(occ.coordinator).not_to be_nil + expect(occ.preferred_product_selection_from_coordinator_inventory_only).to be true + expect(occ.automatic_notifications).to eq(oc.automatic_notifications) + expect(occ.processed_at).to eq(nil) + expect(occ.mails_sent).to eq(nil) + expect(occ.coordinator).to eq(oc.coordinator) - expect(occ.coordinator_fee_ids).not_to be_empty - expect(occ.coordinator_fee_ids).to eq(oc.coordinator_fee_ids) - expect(occ.preferred_product_selection_from_coordinator_inventory_only).to eq(oc.preferred_product_selection_from_coordinator_inventory_only) - expect(occ.schedule_ids).not_to be_empty - expect(occ.schedule_ids).to eq(oc.schedule_ids) + expect(occ.coordinator_fee_ids).not_to be_empty + expect(occ.coordinator_fee_ids).to eq(oc.coordinator_fee_ids) + expect(occ.preferred_product_selection_from_coordinator_inventory_only).to eq(oc.preferred_product_selection_from_coordinator_inventory_only) + expect(occ.schedule_ids).not_to be_empty + expect(occ.schedule_ids).to eq(oc.schedule_ids) - # Check that the exchanges have been cloned. - original_exchange_attributes = oc.exchanges.map { |ex| core_exchange_attributes(ex) } - cloned_exchange_attributes = occ.exchanges.map { |ex| core_exchange_attributes(ex) } + # Check that the exchanges have been cloned. + original_exchange_attributes = oc.exchanges.map { |ex| core_exchange_attributes(ex) } + cloned_exchange_attributes = occ.exchanges.map { |ex| core_exchange_attributes(ex) } - expect(cloned_exchange_attributes).to match_array original_exchange_attributes + expect(cloned_exchange_attributes).to match_array original_exchange_attributes + end + + context "distributor order cycle created before the customisable shipping methods feature was available" do + it "allows the clone to have customisable shipping methods" do + order_cycle = create(:distributor_order_cycle, shipping_methods_customisable: false) + + order_cycle.clone! + + order_cycle_clone = OrderCycle.last + expect(order_cycle_clone.shipping_methods_customisable).to eq(true) + end + end + + context "when it has shipping methods which can longer be applied validly e.g. shipping method is backoffice only" do + it "raises an error (TODO: display a message to user explaining why clone failed)" do + distributor = create(:distributor_enterprise) + shipping_method = create(:shipping_method, distributors: [distributor]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) + shipping_method.update_column(:display_on, "back_end") + + expect { + order_cycle.clone! + }.to raise_error ActiveRecord::RecordInvalid + end + end end describe "finding recently closed order cycles" do diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index c490457aa3..43738baa40 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -711,9 +711,8 @@ describe Spree::Order do end it "keeps the order cycle when it is available at the new distributor" do - d = create(:distributor_enterprise) - oc = create(:simple_order_cycle) - create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false) + oc = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) + d = oc.distributors.first subject.order_cycle = oc subject.set_distributor! d @@ -759,7 +758,7 @@ describe Spree::Order do end describe "setting the order cycle" do - let(:oc) { create(:simple_order_cycle) } + let(:oc) { create(:distributor_order_cycle, with_distributor_and_shipping_method: true) } it "empties the cart when changing the order cycle" do expect(subject).to receive(:empty!) @@ -777,8 +776,7 @@ describe Spree::Order do end it "keeps the distributor when it is available in the new order cycle" do - d = create(:distributor_enterprise) - create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false) + d = oc.distributors.first subject.distributor = d subject.set_order_cycle! oc diff --git a/spec/system/consumer/caching/shops_caching_spec.rb b/spec/system/consumer/caching/shops_caching_spec.rb index aa5bc79141..89d70edb5b 100644 --- a/spec/system/consumer/caching/shops_caching_spec.rb +++ b/spec/system/consumer/caching/shops_caching_spec.rb @@ -10,7 +10,10 @@ describe "Shops caching", js: true, caching: true do create(:distributor_enterprise, with_payment_and_shipping: true, is_primary_producer: true) } let!(:order_cycle) { - create(:open_order_cycle, distributors: [distributor], coordinator: distributor) + create(:open_order_cycle, + distributors: [distributor], + coordinator: distributor, + shipping_methods: [distributor.shipping_methods.first]) } describe "caching enterprises AMS data" do diff --git a/spec/system/consumer/shopping/checkout_auth_spec.rb b/spec/system/consumer/shopping/checkout_auth_spec.rb index 747a6d2413..1ab5e93436 100644 --- a/spec/system/consumer/shopping/checkout_auth_spec.rb +++ b/spec/system/consumer/shopping/checkout_auth_spec.rb @@ -14,7 +14,9 @@ describe "As a consumer I want to check out my cart", js: true do let(:supplier) { create(:supplier_enterprise) } let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), variants: [product.variants.first]) + coordinator: create(:distributor_enterprise), + shipping_methods: [distributor.shipping_methods.first], + variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: supplier) } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } diff --git a/spec/system/consumer/shopping/checkout_paypal_spec.rb b/spec/system/consumer/shopping/checkout_paypal_spec.rb index 950d2153b7..0f3e46556f 100644 --- a/spec/system/consumer/shopping/checkout_paypal_spec.rb +++ b/spec/system/consumer/shopping/checkout_paypal_spec.rb @@ -42,6 +42,7 @@ describe "Check out with Paypal", js: true do before do distributor.shipping_methods << free_shipping + order_cycle.shipping_methods << free_shipping set_order order add_product_to_cart order, product end diff --git a/spec/system/consumer/shopping/checkout_spec.rb b/spec/system/consumer/shopping/checkout_spec.rb index 04015ae715..249b76d694 100644 --- a/spec/system/consumer/shopping/checkout_spec.rb +++ b/spec/system/consumer/shopping/checkout_spec.rb @@ -65,9 +65,9 @@ describe "As a consumer I want to check out my cart", js: true do set_order order add_product_to_cart order, product - distributor.shipping_methods << free_shipping - distributor.shipping_methods << shipping_with_fee - distributor.shipping_methods << tagged_shipping + shipping_methods = [free_shipping, shipping_with_fee, tagged_shipping] + distributor.shipping_methods << shipping_methods + order_cycle.shipping_methods << shipping_methods end describe "when I have an out of stock product in my cart" do diff --git a/spec/system/consumer/shopping/checkout_stripe_spec.rb b/spec/system/consumer/shopping/checkout_stripe_spec.rb index c91d64de10..f1ea41192b 100644 --- a/spec/system/consumer/shopping/checkout_stripe_spec.rb +++ b/spec/system/consumer/shopping/checkout_stripe_spec.rb @@ -34,7 +34,9 @@ describe "Check out with Stripe", js: true do setup_stripe set_order order add_product_to_cart order, product - distributor.shipping_methods << [shipping_with_fee, free_shipping] + shipping_methods = [shipping_with_fee, free_shipping] + distributor.shipping_methods << shipping_methods + order_cycle.shipping_methods << shipping_methods end describe "using Stripe SCA" do diff --git a/spec/system/consumer/shopping/products_spec.rb b/spec/system/consumer/shopping/products_spec.rb index c310d6ef3e..52e2787036 100644 --- a/spec/system/consumer/shopping/products_spec.rb +++ b/spec/system/consumer/shopping/products_spec.rb @@ -18,7 +18,9 @@ describe "As a consumer I want to view products", js: true do let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) + coordinator: create(:distributor_enterprise), + orders_close_at: 2.days.from_now, + shipping_methods: [distributor.shipping_methods.first]) } let(:product) { create(:simple_product, supplier: supplier, primary_taxon: taxon, properties: [property], name: "Beans") diff --git a/spec/system/consumer/shopping/shopping_spec.rb b/spec/system/consumer/shopping/shopping_spec.rb index 823018143b..57867455a4 100644 --- a/spec/system/consumer/shopping/shopping_spec.rb +++ b/spec/system/consumer/shopping/shopping_spec.rb @@ -14,11 +14,15 @@ describe "As a consumer I want to shop with a distributor", js: true do let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) + coordinator: create(:distributor_enterprise), + orders_close_at: 2.days.from_now, + shipping_methods: [distributor.shipping_methods.first]) } let(:oc2) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), orders_close_at: 3.days.from_now) + coordinator: create(:distributor_enterprise), + orders_close_at: 3.days.from_now, + shipping_methods: [distributor.shipping_methods.first]) } let(:product) { create(:simple_product, supplier: supplier, meta_keywords: "Domestic") } let(:variant) { product.variants.first } diff --git a/spec/system/consumer/shopping/unit_price_spec.rb b/spec/system/consumer/shopping/unit_price_spec.rb index c134dd4f4d..a1c998887e 100644 --- a/spec/system/consumer/shopping/unit_price_spec.rb +++ b/spec/system/consumer/shopping/unit_price_spec.rb @@ -12,7 +12,9 @@ describe "As a consumer, I want to check unit price information for a product", let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) + coordinator: create(:distributor_enterprise), + orders_close_at: 2.days.from_now, + shipping_methods: [distributor.shipping_methods.first]) } let(:product) { create(:simple_product, supplier: supplier) } let(:variant) { product.variants.first } diff --git a/spec/system/consumer/shopping/variant_overrides_spec.rb b/spec/system/consumer/shopping/variant_overrides_spec.rb index 5fbb24de3e..26ed5c2955 100644 --- a/spec/system/consumer/shopping/variant_overrides_spec.rb +++ b/spec/system/consumer/shopping/variant_overrides_spec.rb @@ -12,7 +12,8 @@ describe "shopping with variant overrides defined", js: true do let(:hub) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:producer) { create(:supplier_enterprise) } let(:oc) { - create(:simple_order_cycle, suppliers: [producer], coordinator: hub, distributors: [hub]) + create(:simple_order_cycle, + suppliers: [producer], coordinator: hub, distributors: [hub], shipping_methods: [sm]) } let(:outgoing_exchange) { oc.exchanges.outgoing.first } let(:sm) { hub.shipping_methods.first } diff --git a/spec/system/consumer/split_checkout_spec.rb b/spec/system/consumer/split_checkout_spec.rb index ab6568c2f0..cfd01afb2f 100644 --- a/spec/system/consumer/split_checkout_spec.rb +++ b/spec/system/consumer/split_checkout_spec.rb @@ -71,7 +71,8 @@ describe "As a consumer, I want to checkout my order", js: true do add_enterprise_fee enterprise_fee set_order order - distributor.shipping_methods.push(shipping_methods) + distributor.shipping_methods << shipping_methods + order_cycle.shipping_methods << shipping_methods end context "guest checkout when distributor doesn't allow guest orders" do From fc4f951a1aa2d20b3062d904dce15f2b06c3ec43 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 17 Jun 2022 10:48:36 +0100 Subject: [PATCH 14/69] Only require OrderCycleShippingMethod records if people want to override the default shipping methods It makes things much simpler if we return all shipping methods by default without needing OrderCycleShippingMethod records to be added to the database. Co-authored-by: Maikel --- app/models/order_cycle.rb | 33 +-- app/models/order_cycle_shipping_method.rb | 7 - app/models/spree/shipping_method.rb | 20 +- app/services/order_cycle_form.rb | 22 +- .../permitted_attributes/order_cycle.rb | 2 +- .../order_cycles/checkout_options.html.haml | 2 + config/locales/en.yml | 1 - ...052_create_order_cycle_shipping_methods.rb | 89 +------ db/schema.rb | 2 +- lib/tasks/sample_data/order_cycle_factory.rb | 3 - spec/factories/subscription_factory.rb | 8 +- .../order_cycle_shipping_method_spec.rb | 16 -- spec/models/order_cycle_spec.rb | 98 ++++---- spec/models/spree/shipping_method_spec.rb | 63 +++-- .../order_available_shipping_methods_spec.rb | 219 ++++++------------ spec/services/order_cycle_form_spec.rb | 27 ++- 16 files changed, 243 insertions(+), 369 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 84800086c9..c552c4e45c 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -25,20 +25,19 @@ class OrderCycle < ApplicationRecord has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules has_many :order_cycle_shipping_methods - has_many :shipping_methods, class_name: "Spree::ShippingMethod", - through: :order_cycle_shipping_methods + has_many :preferred_shipping_methods, class_name: "Spree::ShippingMethod", + through: :order_cycle_shipping_methods, + source: :shipping_method has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges - attribute :validate_shipping_methods, :boolean, default: true - before_update :reset_processed_at, if: :will_save_change_to_orders_close_at? after_save :sync_subscriptions, if: :opening? validates :name, :coordinator_id, presence: true validate :at_least_one_shipping_method_selected_for_each_distributor - validate :no_invalid_shipping_methods + validate :no_invalid_order_cycle_shipping_methods validate :orders_close_at_after_orders_open_at? preference :product_selection_from_coordinator_inventory_only, :boolean, default: false @@ -165,8 +164,6 @@ class OrderCycle < ApplicationRecord end def attachable_shipping_methods - return Spree::ShippingMethod.none if simple? || !shipping_methods_customisable? - Spree::ShippingMethod.frontend. joins(:distributor_shipping_methods). where("distributor_id IN (?)", distributor_ids). @@ -182,12 +179,9 @@ class OrderCycle < ApplicationRecord oc.preferred_product_selection_from_coordinator_inventory_only = preferred_product_selection_from_coordinator_inventory_only # rubocop:enable Layout/LineLength oc.schedule_ids = schedule_ids - oc.shipping_methods_customisable = true oc.save! - oc.validate_shipping_methods = false exchanges.each { |e| e.clone!(oc) } - oc.validate_shipping_methods = true - oc.shipping_method_ids = shipping_method_ids + oc.preferred_shipping_method_ids = preferred_shipping_method_ids sync_subscriptions oc.reload end @@ -301,6 +295,14 @@ class OrderCycle < ApplicationRecord items.each { |li| scoper.scope(li.variant) } end + def shipping_methods + if simple? || preferred_shipping_methods.none? + attachable_shipping_methods + else + preferred_shipping_methods + end + end + def simple? coordinator.sells == 'own' end @@ -308,20 +310,21 @@ class OrderCycle < ApplicationRecord private def all_distributors_have_at_least_one_shipping_method? - distributors.all? { |distributor| (distributor.shipping_method_ids & shipping_method_ids).any? } + distributors.all? do |distributor| + (distributor.shipping_method_ids & preferred_shipping_method_ids).any? + end end def at_least_one_shipping_method_selected_for_each_distributor - return if !validate_shipping_methods? || + return if preferred_shipping_methods.none? || coordinator.nil? || simple? || - !shipping_methods_customisable? || all_distributors_have_at_least_one_shipping_method? errors.add(:base, :at_least_one_shipping_method_per_distributor) end - def no_invalid_shipping_methods + def no_invalid_order_cycle_shipping_methods return if order_cycle_shipping_methods.all?(&:valid?) errors.add( diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb index dbdd0ccd0d..ebb0420660 100644 --- a/app/models/order_cycle_shipping_method.rb +++ b/app/models/order_cycle_shipping_method.rb @@ -7,7 +7,6 @@ class OrderCycleShippingMethod < ActiveRecord::Base validate :shipping_method_belongs_to_order_cycle_distributor validate :shipping_method_available_at_checkout validate :order_cycle_not_simple - validate :order_cycle_shipping_methods_customisable validates_uniqueness_of :shipping_method, scope: :order_cycle_id before_destroy :check_shipping_method_not_selected_on_any_orders @@ -36,12 +35,6 @@ class OrderCycleShippingMethod < ActiveRecord::Base errors.add(:order_cycle, :must_not_be_simple) end - def order_cycle_shipping_methods_customisable - return if order_cycle.nil? || order_cycle.shipping_methods_customisable? - - errors.add(:order_cycle, :must_support_customisable_shipping_methods) - end - def shipping_method_available_at_checkout return if shipping_method.nil? || shipping_method.frontend? diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 7a7eb44487..b7bd7e4092 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -115,18 +115,14 @@ module Spree private - def no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + def no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method? return true if new_record? - OrderCycle.active.or(OrderCycle.upcoming).joins(:coordinator, :shipping_methods).where(" - sells != 'own' AND - spree_shipping_methods.id = ? AND - NOT EXISTS( - SELECT 1 - FROM order_cycle_shipping_methods - WHERE order_cycle_id = order_cycles.id AND - shipping_method_id != ? - )", id, id).none? + distributors. + with_order_cycles_as_distributor_outer. + merge(OrderCycle.active.or(OrderCycle.upcoming)).none? do |distributor| + distributor.shipping_method_ids.one? + end end def at_least_one_shipping_category @@ -146,7 +142,7 @@ module Spree end def check_destroy_wont_leave_order_cycles_without_shipping_methods - return if no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + return if no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method? errors.add(:base, :destroy_leaves_order_cycles_without_shipping_methods) throw :abort @@ -154,7 +150,7 @@ module Spree def switching_to_backoffice_only_wont_leave_order_cycles_without_shipping_methods return if frontend? || - no_active_or_upcoming_non_simple_order_cycles_with_only_one_shipping_method? + no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method? errors.add(:base, :switching_to_backoffice_only_leaves_order_cycles_without_shipping_methods) end diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index ba6aa590e7..18e07553a8 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -11,13 +11,12 @@ class OrderCycleForm @user = user @permissions = OpenFoodNetwork::Permissions.new(user) @schedule_ids = order_cycle_params.delete(:schedule_ids) - @shipping_method_ids = order_cycle_params.delete(:shipping_method_ids) + @preferred_shipping_method_ids = order_cycle_params.delete(:preferred_shipping_method_ids) end def save schedule_ids = build_schedule_ids order_cycle.assign_attributes(order_cycle_params) - order_cycle.validate_shipping_methods = false return false unless order_cycle.valid? order_cycle.transaction do @@ -25,7 +24,7 @@ class OrderCycleForm order_cycle.schedule_ids = schedule_ids order_cycle.save! apply_exchange_changes - attach_shipping_methods + attach_preferred_shipping_methods sync_subscriptions true end @@ -49,12 +48,11 @@ class OrderCycleForm OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go! end - def attach_shipping_methods - return if @shipping_method_ids.nil? + def attach_preferred_shipping_methods + return if @preferred_shipping_method_ids.nil? order_cycle.reload # so outgoing exchanges are up-to-date for shipping method validations - order_cycle.validate_shipping_methods = true - order_cycle.shipping_method_ids = @shipping_method_ids + order_cycle.preferred_shipping_method_ids = preferred_shipping_method_ids order_cycle.save! end @@ -64,6 +62,16 @@ class OrderCycleForm end end + def preferred_shipping_method_ids + @preferred_shipping_method_ids = @preferred_shipping_method_ids.reject(&:blank?).map(&:to_i) + + if order_cycle.attachable_shipping_methods.map(&:id).sort == @preferred_shipping_method_ids.sort + @preferred_shipping_method_ids = [] + end + + @preferred_shipping_method_ids + end + def schedule_ids? @schedule_ids.present? end diff --git a/app/services/permitted_attributes/order_cycle.rb b/app/services/permitted_attributes/order_cycle.rb index 97282acabf..8f5998b70d 100644 --- a/app/services/permitted_attributes/order_cycle.rb +++ b/app/services/permitted_attributes/order_cycle.rb @@ -17,7 +17,7 @@ module PermittedAttributes :name, :orders_open_at, :orders_close_at, :coordinator_id, :preferred_product_selection_from_coordinator_inventory_only, :automatic_notifications, - { schedule_ids: [], shipping_method_ids: [], coordinator_fee_ids: [] } + { schedule_ids: [], preferred_shipping_method_ids: [], coordinator_fee_ids: [] } ] end diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 97f436308c..ca76377eda 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -13,6 +13,8 @@ %fieldset.no-border-bottom %legend{ align: 'center'}= t('.checkout_options') + = hidden_field_tag "order_cycle[preferred_shipping_method_ids][]", "" + %table.checkout-options %thead %tr diff --git a/config/locales/en.yml b/config/locales/en.yml index ef52faec39..1321a077f4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -90,7 +90,6 @@ en: shipping_method_already_used_in_order_cycle: "This shipping method has already been selected on orders in this order cycle and cannot be removed" order_cycle: must_not_be_simple: "is simple, all shipping methods are available by default and cannot be customised" - must_support_customisable_shipping_methods: "shipping methods cannot be customised, all shipping methods are available by default" shipping_method: must_be_available_at_checkout: "must be available at checkout" must_belong_to_order_cycle_distributor: "must be from a distributor on the order cycle" diff --git a/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb index ae78acc94a..cc16a501d2 100644 --- a/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb +++ b/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb @@ -1,98 +1,17 @@ class CreateOrderCycleShippingMethods < ActiveRecord::Migration[6.1] - # Before this migration every available shipping method was available to customers on order - # cycles by default. However this migration only populates :order_cycles_shipping_methods records - # for active or upcoming order cycles because retroactively calculating which shipping methods - # should be attached to past, closed order cycles is probaby tricky so skipping that because it - # may not be even necessary. Instead this adds a :shipping_methods_customisable flag to order - # cycles so we have a record of order cycles created before this feature was deployed. - # - # Note: Redefining the Spree::ShippingMethod class in this migration as suggested by the - # :good_migrations gem was not passing Good Migrations checks. This redefines the classes inside - # a Migration class to to bypass this problem. - - class Migration - class DistributorShippingMethod < ActiveRecord::Base - self.table_name = "distributors_shipping_methods" - belongs_to :shipping_method, class_name: "Migration::ShippingMethod", touch: true - belongs_to :distributor, class_name: "Enterprise", touch: true - end - - class Enterprise < ActiveRecord::Base - end - - class Exchange < ActiveRecord::Base - self.table_name = "exchanges" - belongs_to :receiver, class_name: 'Migration::Enterprise' - end - - class OrderCycle < ActiveRecord::Base - self.table_name = "order_cycles" - has_many :order_cycle_shipping_methods, class_name: "Migration::OrderCycleShippingMethod" - has_many :shipping_methods, class_name: "Migration::ShippingMethod", through: :order_cycle_shipping_methods - - has_many :cached_outgoing_exchanges, -> { where incoming: false }, class_name: "Migration::Exchange" - has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges - - belongs_to :coordinator, class_name: 'Migration::Enterprise' - - scope :active, lambda { - where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?', - Time.zone.now, - Time.zone.now) - } - scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) } - end - - class OrderCycleShippingMethod < ActiveRecord::Base - self.table_name = "order_cycle_shipping_methods" - belongs_to :shipping_method, class_name: "Migration::ShippingMethod" - end - - class ShippingMethod < ActiveRecord::Base - self.table_name = "spree_shipping_methods" - end - - def self.attach_all_shipping_methods_to_non_simple_active_or_upcoming_order_cycles - non_simple_active_or_upcoming_order_cycles.find_each do |order_cycle| - order_cycle.shipping_method_ids = DistributorShippingMethod. - where("display_on != 'back_end'"). - where(distributor_id: order_cycle.distributor_ids). - joins(:shipping_method). - pluck(:shipping_method_id) - end - end - - def self.set_shipping_methods_customisable_to_false_on_past_order_cycles - OrderCycle.update_all(shipping_methods_customisable: false) - active_or_upcoming_order_cycles.update_all(shipping_methods_customisable: true) - end - - private - - def self.active_or_upcoming_order_cycles - OrderCycle.active.or(OrderCycle.upcoming) - end - - def self.non_simple_active_or_upcoming_order_cycles - active_or_upcoming_order_cycles.joins(:coordinator).where("sells != 'own'") - end - end - def up create_table :order_cycle_shipping_methods do |t| t.references :order_cycle t.references :shipping_method, foreign_key: { to_table: :spree_shipping_methods } t.timestamps end - - add_column :order_cycles, :shipping_methods_customisable, :boolean, default: true - - Migration.set_shipping_methods_customisable_to_false_on_past_order_cycles - Migration.attach_all_shipping_methods_to_non_simple_active_or_upcoming_order_cycles + add_index :order_cycle_shipping_methods, + [:order_cycle_id, :shipping_method_id], + name: "order_cycle_shipping_methods_join_index", + unique: true end def down - remove_column :order_cycles, :shipping_methods_customisable drop_table :order_cycle_shipping_methods end end diff --git a/db/schema.rb b/db/schema.rb index bca8dabe52..3f866caefd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -323,6 +323,7 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.bigint "shipping_method_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.index ["order_cycle_id", "shipping_method_id"], name: "order_cycle_shipping_methods_join_index", unique: true t.index ["order_cycle_id"], name: "index_order_cycle_shipping_methods_on_order_cycle_id" t.index ["shipping_method_id"], name: "index_order_cycle_shipping_methods_on_shipping_method_id" end @@ -337,7 +338,6 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.datetime "processed_at" t.boolean "automatic_notifications", default: false t.boolean "mails_sent", default: false - t.boolean "shipping_methods_customisable", default: true end create_table "producer_properties", id: :serial, force: :cascade do |t| diff --git a/lib/tasks/sample_data/order_cycle_factory.rb b/lib/tasks/sample_data/order_cycle_factory.rb index d6701d5177..0f9dc258e8 100644 --- a/lib/tasks/sample_data/order_cycle_factory.rb +++ b/lib/tasks/sample_data/order_cycle_factory.rb @@ -57,9 +57,6 @@ module SampleData log "- #{name}" cycle = create_order_cycle_with_fee(name, coordinator) create_exchanges(cycle, supplier_names, distributor_names, data) - return if cycle.reload.attachable_shipping_methods.none? - - cycle.shipping_method_ids = cycle.attachable_shipping_methods.pluck(:id) end def create_order_cycle_with_fee(name, coordinator) diff --git a/spec/factories/subscription_factory.rb b/spec/factories/subscription_factory.rb index 5bccb7eaf0..f0b67c0631 100644 --- a/spec/factories/subscription_factory.rb +++ b/spec/factories/subscription_factory.rb @@ -3,13 +3,7 @@ FactoryBot.define do factory :subscription, class: Subscription do shop { create :enterprise } - schedule do - order_cycle = create(:distributor_order_cycle, - coordinator: shop, - distributors: [shop], - shipping_methods: [shipping_method]) - create(:schedule, order_cycles: [order_cycle]) - end + schedule { create(:schedule, order_cycles: [create(:simple_order_cycle, coordinator: shop)]) } customer { create(:customer, enterprise: shop) } bill_address { create(:address, :randomized) } ship_address { create(:address, :randomized) } diff --git a/spec/models/order_cycle_shipping_method_spec.rb b/spec/models/order_cycle_shipping_method_spec.rb index fcc5fd5290..bb503182fb 100644 --- a/spec/models/order_cycle_shipping_method_spec.rb +++ b/spec/models/order_cycle_shipping_method_spec.rb @@ -51,22 +51,6 @@ describe OrderCycleShippingMethod do ) end - it "is not valid if order cycle doesn't support customised shipping methods - e.g. the order cycle was created before the custom shipping methods feature was available" do - order_cycle = create(:distributor_order_cycle, shipping_methods_customisable: false) - shipping_method = create(:shipping_method, distributors: [order_cycle.coordinator]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).to_not be_valid - expect(order_cycle_shipping_method.errors.to_a).to include( - "Order cycle shipping methods cannot be customised, all shipping methods are available by default" - ) - end - it "is valid if the shipping method belongs to one of the order cycle distributors" do shipping_method = create(:shipping_method) enterprise = create(:enterprise, shipping_methods: [shipping_method]) diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index ab66327504..0da971b524 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -28,9 +28,13 @@ describe OrderCycle do distributor_ii = create(:distributor_enterprise) distributor_i_shipping_method = create(:shipping_method, distributors: [distributor_i]) distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_ii]) - order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) + order_cycle = create(:distributor_order_cycle, + distributors: [distributor_i, distributor_ii]) - order_cycle.shipping_method_ids = [distributor_i_shipping_method.id, distributor_ii_shipping_method.id] + order_cycle.preferred_shipping_method_ids = [ + distributor_i_shipping_method.id, + distributor_ii_shipping_method.id + ] expect(order_cycle).to be_valid end @@ -43,19 +47,24 @@ describe OrderCycle do distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_i]) order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - order_cycle.shipping_method_ids = [distributor_i_shipping_method.id] + order_cycle.preferred_shipping_method_ids = [distributor_i_shipping_method.id] expect(order_cycle).to be_invalid - expect(order_cycle.errors.to_a).to eq ["You need to select at least one shipping method for each distributor"] + expect(order_cycle.errors.to_a).to eq [ + "You need to select at least one shipping method for each distributor" + ] end end end - describe "#no_invalid_shipping_methods" do - context "when a shipping method is not valid" do - it "adds a validation error, and it is more meaningful than the default 'Order cycle shipping methods is invalid'" do - order_cycle = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) - shipping_method = order_cycle.shipping_methods.first + describe "#no_invalid_order_cycle_shipping_methods" do + context "when a order cycle shipping method is not valid" do + it "adds a validation error, + and it is more meaningful than the default 'Order cycle shipping methods is invalid'" do + shipping_method = create(:shipping_method) + distributor = create(:distributor_enterprise, shipping_methods: [shipping_method]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + order_cycle.preferred_shipping_methods << shipping_method shipping_method.update_column(:display_on, "back_end") @@ -69,23 +78,6 @@ describe OrderCycle do end end - describe "#validate_shipping_methods" do - it "doesn't skip shipping method validations by default" do - order_cycle = create(:distributor_order_cycle, distributors: [create(:distributor_enterprise)]) - - expect(order_cycle).to be_invalid - expect(order_cycle.errors.to_a).to eq ["You need to select at least one shipping method for each distributor"] - end - - it "allows shipping method validations to be skipped because distributors need to be saved before shipping methods" do - order_cycle = create(:distributor_order_cycle, distributors: [create(:distributor_enterprise)]) - - order_cycle.validate_shipping_methods = false - - expect(order_cycle).to be_valid - end - end - it "has a coordinator and associated fees" do oc = create(:simple_order_cycle) @@ -198,7 +190,7 @@ describe OrderCycle do end it "reports its distributors" do - oc = create(:simple_order_cycle, validate_shipping_methods: false) + oc = create(:simple_order_cycle) e1 = create(:exchange, incoming: false, order_cycle: oc, sender: oc.coordinator, receiver: create(:enterprise)) @@ -209,7 +201,7 @@ describe OrderCycle do end it "checks for existance of distributors" do - oc = create(:simple_order_cycle, validate_shipping_methods: false) + oc = create(:simple_order_cycle) d1 = create(:distributor_enterprise) d2 = create(:distributor_enterprise) create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1, incoming: false) @@ -470,22 +462,14 @@ describe OrderCycle do expect(cloned_exchange_attributes).to match_array original_exchange_attributes end - context "distributor order cycle created before the customisable shipping methods feature was available" do - it "allows the clone to have customisable shipping methods" do - order_cycle = create(:distributor_order_cycle, shipping_methods_customisable: false) - - order_cycle.clone! - - order_cycle_clone = OrderCycle.last - expect(order_cycle_clone.shipping_methods_customisable).to eq(true) - end - end - - context "when it has shipping methods which can longer be applied validly e.g. shipping method is backoffice only" do + context "when it has preferred shipping methods which can longer be applied validly + e.g. shipping method is backoffice only" do it "raises an error (TODO: display a message to user explaining why clone failed)" do distributor = create(:distributor_enterprise) shipping_method = create(:shipping_method, distributors: [distributor]) - order_cycle = create(:distributor_order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + order_cycle.preferred_shipping_methods << shipping_method + shipping_method.update_column(:display_on, "back_end") expect { @@ -721,6 +705,38 @@ describe OrderCycle do end end + describe "#shipping_methods" do + let(:distributor) { create(:distributor_enterprise) } + + it "returns all attachable shipping methods if the order cycle is simple" do + oc = create(:sells_own_order_cycle, distributors: [distributor]) + + shipping_method = create(:shipping_method, distributors: [distributor]) + + expect(oc.shipping_methods).to eq [shipping_method] + end + + context "distributor order cycle i.e. non-simple" do + let(:oc) { create(:distributor_order_cycle, distributors: [distributor]) } + + it "returns all attachable shipping methods if no preferred shipping methods have been chosen" do + shipping_method = create(:shipping_method, distributors: [distributor]) + + expect(oc.preferred_shipping_methods).to be_empty + expect(oc.shipping_methods).to eq [shipping_method] + end + + it "returns preferred shipping methods if they have been specified" do + shipping_method_i = create(:shipping_method, distributors: [distributor]) + shipping_method_ii = create(:shipping_method, distributors: [distributor]) + + oc.preferred_shipping_methods << shipping_method_ii + + expect(oc.shipping_methods).to eq [shipping_method_ii] + end + end + end + describe "#simple?" do it "returns true if the coordinator sells their own products i.e. shops" do order_cycle = build(:simple_order_cycle, coordinator: build(:enterprise, sells: "own")) diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index 8c030fa02d..ba4b5d84fb 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -182,22 +182,38 @@ module Spree end context "when it is being changed to backoffice only" do - let!(:order_cycle) { create(:distributor_order_cycle, with_distributor_and_shipping_method: true) } - let(:distributor) { order_cycle.distributors.first } - let(:shipping_method) { order_cycle.shipping_methods.first } + let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor]) } + let(:distributor) { create(:distributor_enterprise, shipping_methods: [shipping_method]) } + let(:shipping_method) { create(:shipping_method) } - context "when the shipping method is the only shipping method on a distributor order cycle" do + context "when one of its distributors has no other shipping methods available + on an active or upcoming order cycle" do it "should not be valid" do shipping_method.display_on = "back_end" expect(shipping_method).not_to be_valid - expect(shipping_method.errors.to_a).to eq ["Unable to switch to backoffice only, some open or upcoming order cycles would be left without any shipping methods"] + expect(shipping_method.errors.to_a).to eq [ + "Unable to switch to backoffice only, some open or upcoming order cycles would be " \ + "left without any shipping methods" + ] end end - context "when the order cycles the shipping method is attached to has other valid shipping methods" do - it "is is valid" do - order_cycle.shipping_methods << create(:shipping_method, distributors: [distributor]) + context "when one of its distributors has no other shipping methods available + on an order cycle which isn't active or upcoming" do + it "is valid" do + order_cycle.update!(orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago) + + shipping_method.display_on = "back_end" + + expect(shipping_method).to be_valid + end + end + + context "when one of its distributors has other shipping methods available + on an active or upcoming order cycle" do + it "is valid" do + create(:shipping_method, distributors: [distributor]) shipping_method.display_on = "back_end" @@ -242,9 +258,10 @@ module Spree context "#destroy" do let(:shipping_method) { create(:shipping_method) } let(:distributor) { create(:distributor_enterprise, shipping_methods: [shipping_method]) } - let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) } + let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor]) } - context "when the shipping method is the only shipping method on a distributor order cycle" do + context "when its distributors are part of some order cycles + and have no other shipping methods available" do it "can be deleted if the order cycle is closed" do order_cycle.update!(orders_close_at: 1.minute.ago) @@ -259,7 +276,10 @@ module Spree shipping_method.destroy expect(shipping_method).not_to be_deleted - expect(shipping_method.errors.to_a).to eq ["Unable to delete, some open or upcoming order cycles would be left without any shipping methods"] + expect(shipping_method.errors.to_a).to eq [ + "Unable to delete, some open or upcoming order cycles would be left without any " \ + "shipping methods" + ] end it "cannot be deleted if the order cycle is upcoming" do @@ -268,13 +288,28 @@ module Spree shipping_method.destroy expect(shipping_method).not_to be_deleted - expect(shipping_method.errors.to_a).to eq ["Unable to delete, some open or upcoming order cycles would be left without any shipping methods"] + expect(shipping_method.errors.to_a).to eq [ + "Unable to delete, some open or upcoming order cycles would be left without any " \ + "shipping methods" + ] end end - context "when the order cycles the shipping method is attached to has other valid shipping methods" do + context "when its distributors are part of some active or upcoming order cycles + and have other shipping methods available" do it "can be deleted" do - order_cycle.shipping_methods << create(:shipping_method, distributors: [distributor]) + create(:shipping_method, distributors: [distributor]) + + shipping_method.destroy + + expect(shipping_method).to be_deleted + end + end + + context "when its distributors have no other shipping methods available + but aren't part of active/upcoming order cycles" do + it "can be deleted" do + order_cycle.update!(orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago) shipping_method.destroy diff --git a/spec/services/order_available_shipping_methods_spec.rb b/spec/services/order_available_shipping_methods_spec.rb index 814853d6eb..5cfdde3e21 100644 --- a/spec/services/order_available_shipping_methods_spec.rb +++ b/spec/services/order_available_shipping_methods_spec.rb @@ -26,54 +26,41 @@ describe OrderAvailableShippingMethods do end context "when no tag rules are in effect" do - context "order cycle selling own produce only i.e. shipping methods cannot be customised" do - it "returns all shipping methods belonging to the enterprise" do - order_cycle = create(:sells_own_order_cycle) - enterprise = order_cycle.coordinator - shipping_method = create(:shipping_method, distributors: [enterprise]) - other_enterprise = create(:enterprise) - other_enterprise_shipping_method = create(:shipping_method, - distributors: [other_enterprise]) - order = build(:order, distributor: enterprise, order_cycle: order_cycle) + context "sells own order cycle i.e. simple" do + it "only returns the shipping methods which are available on the order cycle + and belong to the order distributor" do + distributor_i = create(:distributor_enterprise) + distributor_ii = create(:distributor_enterprise) + distributor_iii = create(:distributor_enterprise) + shipping_method_i = create(:shipping_method, distributors: [distributor_i]) + shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) + shipping_method_iii = create(:shipping_method, distributors: [distributor_iii]) + order_cycle = create(:sells_own_order_cycle, distributors: [distributor_i, distributor_ii]) + order = build(:order, distributor: distributor_i, order_cycle: order_cycle) available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a - expect(order_cycle.shipping_methods).to be_empty - expect(available_shipping_methods).to eq [shipping_method] + expect(available_shipping_methods).to eq [shipping_method_i] end end - context "distributor order cycle" do - it "only returns shipping methods which belong to the order distributor - and have been added to the order cycle" do - distributor = create(:distributor_enterprise) - shipping_method_i = create(:shipping_method, distributors: [distributor]) - shipping_method_ii = create(:shipping_method, distributors: [distributor]) - order_cycle = create(:simple_order_cycle, - distributors: [distributor], shipping_methods: [shipping_method_i]) - order = build(:order, distributor: distributor, order_cycle: order_cycle) - - available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a - - expect(available_shipping_methods).to eq order_cycle.shipping_methods - expect(available_shipping_methods).to eq [shipping_method_i] - end - - it "doesn't return shipping methods which have been added to the order cycle - when they don't belong to the order distributor" do + context "distributor order cycle i.e. not simple" do + it "only returns the shipping methods which are available on the order cycle + and belong to the order distributor" do distributor_i = create(:distributor_enterprise) distributor_ii = create(:distributor_enterprise) shipping_method_i = create(:shipping_method, distributors: [distributor_i]) - shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) - order_cycle = create(:simple_order_cycle, - distributors: [distributor_i, distributor_ii], - shipping_methods: [shipping_method_i, shipping_method_ii]) - order = build(:order, distributor: distributor_ii, order_cycle: order_cycle) + shipping_method_ii = create(:shipping_method, distributors: [distributor_i]) + shipping_method_iii = create(:shipping_method, distributors: [distributor_ii]) + shipping_method_iv = create(:shipping_method, distributors: [distributor_ii]) + order_cycle = create(:distributor_order_cycle, + distributors: [distributor_i, distributor_ii]) + order_cycle.preferred_shipping_methods << [shipping_method_i, shipping_method_iii] + order = build(:order, distributor: distributor_i, order_cycle: order_cycle) available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a - expect(available_shipping_methods).not_to eq order_cycle.shipping_methods - expect(available_shipping_methods).to eq [shipping_method_ii] + expect(available_shipping_methods).to eq [shipping_method_i] end end end @@ -116,66 +103,39 @@ describe OrderAvailableShippingMethods do 'hidden') } - context "order cycle selling own produce only" do - let(:order_cycle) { create(:sells_own_order_cycle) } - let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + let(:order_cycle) { create(:sells_own_order_cycle) } + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } - context "when the customer is nil" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } + context "when the customer is nil" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } - it "applies default action (hide)" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end - end - - context "when a customer is present" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } - - context "and the customer's tags match" do - before do - customer.update_attribute(:tag_list, 'local') - end - - it "applies the action (show)" do - expect(available_shipping_methods).to include tagged_sm, untagged_sm - end - end - - context "and the customer's tags don't match" do - before do - customer.update_attribute(:tag_list, 'something') - end - - it "applies the default action (hide)" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end - end + it "applies default action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm end end - context "distributor order cycle" do - context "when the shipping method without the tag rule is attached to the order cycle - and the shipping method with the tag rule is not" do - let(:order_cycle) do - create(:distributor_order_cycle, - distributors: [distributor], shipping_methods: [untagged_sm]) + context "when a customer is present" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + + context "and the customer's tags match" do + before do + customer.update_attribute(:tag_list, 'local') end - let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } - context "when the customer's tags match" do - before do - customer.update_attribute(:tag_list, 'local') - end + it "applies the action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm + end + end - it "doesn't display the shipping method with the prefered visibility 'visible' tag - even though the customer's tags match - because it hasn't been attached to the order cycle" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end + context "and the customer's tags don't match" do + before do + customer.update_attribute(:tag_list, 'something') + end + + it "applies the default action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm end end end @@ -190,77 +150,38 @@ describe OrderAvailableShippingMethods do 'visible') } - context "order cycle selling own produce only" do - let(:order_cycle) { create(:sells_own_order_cycle) } - let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + let(:order_cycle) { create(:sells_own_order_cycle) } + let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } - context "when the customer is nil" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } + context "when the customer is nil" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } - it "applies default action (show)" do - expect(available_shipping_methods).to include tagged_sm, untagged_sm - end - end - - context "when a customer is present" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } - - context "and the customer's tags match" do - before do - customer.update_attribute(:tag_list, 'local') - end - - it "applies the action (hide)" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end - end - - context "and the customer's tags don't match" do - before do - customer.update_attribute(:tag_list, 'something') - end - - it "applies the default action (show)" do - expect(available_shipping_methods).to include tagged_sm, untagged_sm - end - end + it "applies default action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm end end - context "distributor order cycle" do - context "when the shipping method without the tag rule is attached to the order cycle - and the shipping method with the tag rule is not" do - let(:order_cycle) do - create(:distributor_order_cycle, - distributors: [distributor], shipping_methods: [untagged_sm]) - end - let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) } + context "when a customer is present" do + let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } - context "when the customer is nil" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order).to_a } - - it "doesn't display the shipping method tagged to be visible by default - because it is not attached to the order cycle" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end + context "and the customer's tags match" do + before do + customer.update_attribute(:tag_list, 'local') end - context "when a customer is present" do - let(:available_shipping_methods) { OrderAvailableShippingMethods.new(order, customer).to_a } + it "applies the action (hide)" do + expect(available_shipping_methods).to include untagged_sm + expect(available_shipping_methods).to_not include tagged_sm + end + end - context "when the customer's tags don't match" do - before do - customer.update_attribute(:tag_list, 'something') - end + context "and the customer's tags don't match" do + before do + customer.update_attribute(:tag_list, 'something') + end - it "doesn't display the shipping method tagged to be visible by default - because it is not attached to the order cycle" do - expect(available_shipping_methods).to include untagged_sm - expect(available_shipping_methods).to_not include tagged_sm - end - end + it "applies the default action (show)" do + expect(available_shipping_methods).to include tagged_sm, untagged_sm end end end diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index c7b472257d..f2edeacbce 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -146,7 +146,8 @@ describe OrderCycleForm do end end - context "updating basics, incoming and outcoming exchanges, shipping methods simultaneously" do + context "updating basics, incoming exchanges, outcoming exchanges + and preferred shipping methods simultaneously" do before do params.merge!( incoming_exchanges: [{ @@ -158,7 +159,7 @@ describe OrderCycleForm do enterprise_fee_ids: [] }], outgoing_exchanges: [outgoing_exchange_params], - shipping_method_ids: [shipping_method.id] + preferred_shipping_method_ids: [shipping_method.id] ) end @@ -175,7 +176,7 @@ describe OrderCycleForm do before do params.merge!( outgoing_exchanges: [outgoing_exchange_params], - shipping_method_ids: nil + preferred_shipping_method_ids: nil ) end @@ -194,7 +195,7 @@ describe OrderCycleForm do before do params.merge!( outgoing_exchanges: [outgoing_exchange_params], - shipping_method_ids: [other_distributor_shipping_method.id] + preferred_shipping_method_ids: [other_distributor_shipping_method.id] ) end @@ -209,11 +210,13 @@ describe OrderCycleForm do context "when shipping methods already exist and doing an update without the :shipping_methods_id parameter" do it "doesn't return a validation error on shipping methods" do - order_cycle = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) + distributor = create(:distributor_enterprise) + shipping_method = create(:shipping_method, distributors: [distributor]) + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) form = OrderCycleForm.new( order_cycle, - params.except(:shipping_method_ids), + params.except(:preferred_shipping_method_ids), order_cycle.coordinator ) @@ -229,7 +232,7 @@ describe OrderCycleForm do order_cycle = create(:distributor_order_cycle, distributors: [distributor]) form = OrderCycleForm.new(order_cycle, - { shipping_method_ids: [shipping_method.id] }, + { preferred_shipping_method_ids: [shipping_method.id] }, order_cycle.coordinator) expect(form.save).to be true @@ -239,11 +242,15 @@ describe OrderCycleForm do context "and it's invalid" do it "returns a validation error" do - distributor = create(:distributor_enterprise) - order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + distributor_i = create(:distributor_enterprise) + distributor_ii = create(:distributor_enterprise) + shipping_method_i = create(:shipping_method, distributors: [distributor_i]) + shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) + order_cycle = create(:distributor_order_cycle, + distributors: [distributor_i, distributor_ii]) form = OrderCycleForm.new(order_cycle, - { shipping_method_ids: [] }, + { preferred_shipping_method_ids: [shipping_method_i.id] }, order_cycle.coordinator) expect(form.save).to be false From f6c754839bb46ce7a52c6e4a0542bb0e640d44e9 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 17 Jun 2022 10:51:58 +0100 Subject: [PATCH 15/69] Make OrderCycleShippingMethod inherit from ApplicationRecord --- app/models/order_cycle_shipping_method.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb index ebb0420660..fa9c362edb 100644 --- a/app/models/order_cycle_shipping_method.rb +++ b/app/models/order_cycle_shipping_method.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class OrderCycleShippingMethod < ActiveRecord::Base +class OrderCycleShippingMethod < ApplicationRecord belongs_to :order_cycle belongs_to :shipping_method, class_name: "Spree::ShippingMethod" From 929668638409d5eb4bd2cfa0c6e890ba774cb633 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 17 Jun 2022 11:04:26 +0100 Subject: [PATCH 16/69] Make backend, frontend scopes on Spree::ShippingMethod neater Co-authored-by: Co-authored-by: Maikel --- app/models/spree/shipping_method.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index b7bd7e4092..ad1f831fa8 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -106,11 +106,11 @@ module Spree end def self.backend - where("spree_shipping_methods.display_on = ?", DISPLAY_ON_OPTIONS[:back_end]) + where(display_on: DISPLAY_ON_OPTIONS[:back_end]) end def self.frontend - where("spree_shipping_methods.display_on IS NULL OR spree_shipping_methods.display_on = ''") + where(display_on: [nil, ""]) end private From 0671e52a2921e681d8af69c6ca787d5e2eab2ed6 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 17 Jun 2022 11:28:53 +0100 Subject: [PATCH 17/69] Undo changes to tests, now that order cycle returns all shipping methods by default and doesn't explicitly require OrderCycleShippingMethods anymore --- .../enterprises_controller_spec.rb | 19 +++-------- spec/controllers/shop_controller_spec.rb | 33 ++++++++----------- spec/factories/order_cycle_factory.rb | 25 -------------- spec/factories/order_factory.rb | 2 +- .../order_cycle_permissions_spec.rb | 6 ++-- spec/models/order_cycle_spec.rb | 13 ++++---- spec/models/spree/order_spec.rb | 10 +++--- .../consumer/caching/shops_caching_spec.rb | 5 +-- .../consumer/shopping/checkout_auth_spec.rb | 4 +-- .../consumer/shopping/checkout_paypal_spec.rb | 1 - .../system/consumer/shopping/checkout_spec.rb | 6 ++-- .../consumer/shopping/checkout_stripe_spec.rb | 4 +-- .../system/consumer/shopping/products_spec.rb | 4 +-- .../system/consumer/shopping/shopping_spec.rb | 8 ++--- .../consumer/shopping/unit_price_spec.rb | 4 +-- .../shopping/variant_overrides_spec.rb | 3 +- spec/system/consumer/split_checkout_spec.rb | 3 +- 17 files changed, 46 insertions(+), 104 deletions(-) diff --git a/spec/controllers/enterprises_controller_spec.rb b/spec/controllers/enterprises_controller_spec.rb index 13f3209436..c9ee7b4437 100644 --- a/spec/controllers/enterprises_controller_spec.rb +++ b/spec/controllers/enterprises_controller_spec.rb @@ -9,17 +9,13 @@ describe EnterprisesController, type: :controller do let(:line_item) { create(:line_item) } let!(:current_distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } - let!(:shipping_method) { distributor.shipping_methods.first } let!(:order_cycle1) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 2.days.ago, - orders_close_at: 3.days.from_now, - shipping_methods: [shipping_method], - variants: [line_item.variant] ) + orders_close_at: 3.days.from_now, variants: [line_item.variant] ) } let!(:order_cycle2) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, - orders_close_at: 4.days.from_now, - shipping_methods: [shipping_method]) + orders_close_at: 4.days.from_now ) } before do @@ -59,10 +55,8 @@ describe EnterprisesController, type: :controller do context "using FilterOrderCycles tag rules" do let!(:order_cycle3) { - create(:simple_order_cycle, distributors: [distributor], - orders_open_at: 3.days.ago, - orders_close_at: 4.days.from_now, - shipping_methods: [shipping_method]) + create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, + orders_close_at: 4.days.from_now) } let!(:oc3_exchange) { order_cycle3.exchanges.outgoing.to_enterprise(distributor).first } let(:customer) { create(:customer, user: user, enterprise: distributor) } @@ -122,10 +116,7 @@ describe EnterprisesController, type: :controller do let(:variant) { create(:variant, on_demand: false, on_hand: 10) } let(:line_item) { create(:line_item, variant: variant) } let(:order_cycle) { - create(:simple_order_cycle, - distributors: [current_distributor], - shipping_methods: [current_distributor.shipping_methods.first], - variants: [variant]) + create(:simple_order_cycle, distributors: [current_distributor], variants: [variant]) } before do diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index d55d88b603..b072152e72 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -21,21 +21,21 @@ describe ShopController, type: :controller do describe "selecting an order cycle" do it "should select an order cycle when only one order cycle is open" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) get :show expect(controller.current_order_cycle).to eq(oc1) end it "should not set an order cycle when multiple order cycles are open" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc2 = create(:simple_order_cycle, distributors: [distributor]) get :show expect(controller.current_order_cycle).to be_nil end it "should allow the user to post to select the current order cycle" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc2 = create(:simple_order_cycle, distributors: [distributor]) spree_post :order_cycle, order_cycle_id: oc2.id expect(response.status).to eq 200 @@ -46,8 +46,8 @@ describe ShopController, type: :controller do render_views it "should return the order cycle details when the OC is selected" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc2 = create(:simple_order_cycle, distributors: [distributor]) spree_post :order_cycle, order_cycle_id: oc2.id expect(response.status).to eq 200 @@ -55,19 +55,15 @@ describe ShopController, type: :controller do end it "should return the current order cycle when hit with GET" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) allow(controller).to receive(:current_order_cycle).and_return oc1 get :order_cycle expect(response.body).to have_content oc1.id end context "when the order cycle has already been set" do - let(:oc1) do - create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - end - let(:oc2) do - create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - end + let(:oc1) { create(:simple_order_cycle, distributors: [distributor]) } + let(:oc2) { create(:simple_order_cycle, distributors: [distributor]) } let(:order) { create(:order, order_cycle: oc1) } before { allow(controller).to receive(:current_order) { order } } @@ -81,12 +77,9 @@ describe ShopController, type: :controller do end it "should not allow the user to select an invalid order cycle" do - oc1 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - oc2 = create(:simple_order_cycle, distributors: [distributor], shipping_methods: [sm]) - other_distributor = create(:distributor_enterprise, with_payment_and_shipping: true) - oc3 = create(:simple_order_cycle, - distributors: [other_distributor], - shipping_methods: [other_distributor.shipping_methods.first]) + oc1 = create(:simple_order_cycle, distributors: [distributor]) + oc2 = create(:simple_order_cycle, distributors: [distributor]) + oc3 = create(:simple_order_cycle, distributors: [create(:distributor_enterprise)]) spree_post :order_cycle, order_cycle_id: oc3.id expect(response.status).to eq(404) diff --git a/spec/factories/order_cycle_factory.rb b/spec/factories/order_cycle_factory.rb index 3ad85ef7e5..1962475a8d 100644 --- a/spec/factories/order_cycle_factory.rb +++ b/spec/factories/order_cycle_factory.rb @@ -76,7 +76,6 @@ FactoryBot.define do suppliers { [] } distributors { [] } variants { [] } - with_distributor_and_shipping_method { false } end after(:create) do |oc, proxy| @@ -91,10 +90,6 @@ FactoryBot.define do proxy.variants.each { |v| ex.variants << v } end - if proxy.with_distributor_and_shipping_method - proxy.distributors << oc.coordinator if proxy.distributors.empty? - end - # Outgoing Exchanges proxy.distributors.each.with_index do |distributor, i| ex = create(:exchange, order_cycle: oc, @@ -105,26 +100,6 @@ FactoryBot.define do pickup_instructions: "instructions #{i}") proxy.variants.each { |v| ex.variants << v } end - - if proxy.with_distributor_and_shipping_method || proxy.shipping_methods.any? - oc.reload # so outgoing exchanges/distributors attached above are present - distributor = oc.distributors.first - if proxy.shipping_methods.empty? - proxy.shipping_methods << create(:shipping_method, distributors: [distributor]) - else - proxy.shipping_methods.each do |shipping_method| - # ensure shipping methods belong to a distributor on the order cycle - if !shipping_method.distributors.include?(distributor) - shipping_method.distributors << distributor - end - end - end - oc.shipping_methods = proxy.shipping_methods - end - - if proxy.distributors.any? || proxy.shipping_methods.any? - oc.reload # so shipping methods attached above are present - end end end diff --git a/spec/factories/order_factory.rb b/spec/factories/order_factory.rb index b1b2b3e66c..6f38184372 100644 --- a/spec/factories/order_factory.rb +++ b/spec/factories/order_factory.rb @@ -13,7 +13,7 @@ FactoryBot.define do factory :order_ready_for_details do distributor { create(:distributor_enterprise, with_payment_and_shipping: true) } - order_cycle { create(:order_cycle, distributors: [distributor], shipping_methods: [shipping_method]) } + order_cycle { create(:order_cycle, distributors: [distributor]) } after(:create) do |order| order.line_items << build(:line_item, order: order) diff --git a/spec/lib/open_food_network/order_cycle_permissions_spec.rb b/spec/lib/open_food_network/order_cycle_permissions_spec.rb index cc7a33c91a..4cabc5d3ac 100644 --- a/spec/lib/open_food_network/order_cycle_permissions_spec.rb +++ b/spec/lib/open_food_network/order_cycle_permissions_spec.rb @@ -10,7 +10,7 @@ module OpenFoodNetwork let(:producer) { create(:supplier_enterprise) } let(:user) { double(:user) } let(:oc) { create(:simple_order_cycle, coordinator: coordinator) } - let(:permissions) { OrderCyclePermissions.new(user, oc.reload) } + let(:permissions) { OrderCyclePermissions.new(user, oc) } describe "finding enterprises that can be viewed in the order cycle interface" do context "when permissions are initialized without an order_cycle" do @@ -53,7 +53,7 @@ module OpenFoodNetwork end context "where the coordinator sells 'own'" do - before { allow(oc.coordinator).to receive(:sells) { 'own' } } + before { allow(coordinator).to receive(:sells) { 'own' } } it "returns just the coordinator" do enterprises = permissions.visible_enterprises expect(enterprises).to_not include hub, producer @@ -80,7 +80,7 @@ module OpenFoodNetwork end context "where the coordinator sells 'own'" do - before { allow(oc.coordinator).to receive(:sells) { 'own' } } + before { allow(coordinator).to receive(:sells) { 'own' } } it "returns just the coordinator" do enterprises = permissions.visible_enterprises expect(enterprises).to_not include hub, producer diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 0da971b524..e6b3ff54a1 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -582,7 +582,7 @@ describe OrderCycle do end describe "version tracking", versioning: true do - let!(:oc) { create(:sells_own_order_cycle, name: "Original") } + let!(:oc) { create(:order_cycle, name: "Original") } it "remembers old versions" do expect { @@ -627,12 +627,11 @@ describe OrderCycle do end describe "syncing subscriptions" do - let!(:subscription) { create(:subscription, with_items: true) } - let!(:oc) { subscription.order_cycles.first } - - before do - oc.update_columns(orders_open_at: 1.week.ago, orders_close_at: 1.day.ago) - end + let!(:oc) { + create(:simple_order_cycle, orders_open_at: 1.week.ago, orders_close_at: 1.day.ago) + } + let(:schedule) { create(:schedule, order_cycles: [oc]) } + let!(:subscription) { create(:subscription, schedule: schedule, with_items: true) } it "syncs subscriptions when transitioning from closed to open" do expect(OrderManagement::Subscriptions::ProxyOrderSyncer).to receive(:new).and_call_original diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 43738baa40..c490457aa3 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -711,8 +711,9 @@ describe Spree::Order do end it "keeps the order cycle when it is available at the new distributor" do - oc = create(:distributor_order_cycle, with_distributor_and_shipping_method: true) - d = oc.distributors.first + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle) + create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false) subject.order_cycle = oc subject.set_distributor! d @@ -758,7 +759,7 @@ describe Spree::Order do end describe "setting the order cycle" do - let(:oc) { create(:distributor_order_cycle, with_distributor_and_shipping_method: true) } + let(:oc) { create(:simple_order_cycle) } it "empties the cart when changing the order cycle" do expect(subject).to receive(:empty!) @@ -776,7 +777,8 @@ describe Spree::Order do end it "keeps the distributor when it is available in the new order cycle" do - d = oc.distributors.first + d = create(:distributor_enterprise) + create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d, incoming: false) subject.distributor = d subject.set_order_cycle! oc diff --git a/spec/system/consumer/caching/shops_caching_spec.rb b/spec/system/consumer/caching/shops_caching_spec.rb index 89d70edb5b..aa5bc79141 100644 --- a/spec/system/consumer/caching/shops_caching_spec.rb +++ b/spec/system/consumer/caching/shops_caching_spec.rb @@ -10,10 +10,7 @@ describe "Shops caching", js: true, caching: true do create(:distributor_enterprise, with_payment_and_shipping: true, is_primary_producer: true) } let!(:order_cycle) { - create(:open_order_cycle, - distributors: [distributor], - coordinator: distributor, - shipping_methods: [distributor.shipping_methods.first]) + create(:open_order_cycle, distributors: [distributor], coordinator: distributor) } describe "caching enterprises AMS data" do diff --git a/spec/system/consumer/shopping/checkout_auth_spec.rb b/spec/system/consumer/shopping/checkout_auth_spec.rb index 1ab5e93436..747a6d2413 100644 --- a/spec/system/consumer/shopping/checkout_auth_spec.rb +++ b/spec/system/consumer/shopping/checkout_auth_spec.rb @@ -14,9 +14,7 @@ describe "As a consumer I want to check out my cart", js: true do let(:supplier) { create(:supplier_enterprise) } let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), - shipping_methods: [distributor.shipping_methods.first], - variants: [product.variants.first]) + coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: supplier) } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } diff --git a/spec/system/consumer/shopping/checkout_paypal_spec.rb b/spec/system/consumer/shopping/checkout_paypal_spec.rb index 0f3e46556f..950d2153b7 100644 --- a/spec/system/consumer/shopping/checkout_paypal_spec.rb +++ b/spec/system/consumer/shopping/checkout_paypal_spec.rb @@ -42,7 +42,6 @@ describe "Check out with Paypal", js: true do before do distributor.shipping_methods << free_shipping - order_cycle.shipping_methods << free_shipping set_order order add_product_to_cart order, product end diff --git a/spec/system/consumer/shopping/checkout_spec.rb b/spec/system/consumer/shopping/checkout_spec.rb index 249b76d694..04015ae715 100644 --- a/spec/system/consumer/shopping/checkout_spec.rb +++ b/spec/system/consumer/shopping/checkout_spec.rb @@ -65,9 +65,9 @@ describe "As a consumer I want to check out my cart", js: true do set_order order add_product_to_cart order, product - shipping_methods = [free_shipping, shipping_with_fee, tagged_shipping] - distributor.shipping_methods << shipping_methods - order_cycle.shipping_methods << shipping_methods + distributor.shipping_methods << free_shipping + distributor.shipping_methods << shipping_with_fee + distributor.shipping_methods << tagged_shipping end describe "when I have an out of stock product in my cart" do diff --git a/spec/system/consumer/shopping/checkout_stripe_spec.rb b/spec/system/consumer/shopping/checkout_stripe_spec.rb index f1ea41192b..c91d64de10 100644 --- a/spec/system/consumer/shopping/checkout_stripe_spec.rb +++ b/spec/system/consumer/shopping/checkout_stripe_spec.rb @@ -34,9 +34,7 @@ describe "Check out with Stripe", js: true do setup_stripe set_order order add_product_to_cart order, product - shipping_methods = [shipping_with_fee, free_shipping] - distributor.shipping_methods << shipping_methods - order_cycle.shipping_methods << shipping_methods + distributor.shipping_methods << [shipping_with_fee, free_shipping] end describe "using Stripe SCA" do diff --git a/spec/system/consumer/shopping/products_spec.rb b/spec/system/consumer/shopping/products_spec.rb index 52e2787036..c310d6ef3e 100644 --- a/spec/system/consumer/shopping/products_spec.rb +++ b/spec/system/consumer/shopping/products_spec.rb @@ -18,9 +18,7 @@ describe "As a consumer I want to view products", js: true do let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), - orders_close_at: 2.days.from_now, - shipping_methods: [distributor.shipping_methods.first]) + coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) } let(:product) { create(:simple_product, supplier: supplier, primary_taxon: taxon, properties: [property], name: "Beans") diff --git a/spec/system/consumer/shopping/shopping_spec.rb b/spec/system/consumer/shopping/shopping_spec.rb index 57867455a4..823018143b 100644 --- a/spec/system/consumer/shopping/shopping_spec.rb +++ b/spec/system/consumer/shopping/shopping_spec.rb @@ -14,15 +14,11 @@ describe "As a consumer I want to shop with a distributor", js: true do let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), - orders_close_at: 2.days.from_now, - shipping_methods: [distributor.shipping_methods.first]) + coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) } let(:oc2) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), - orders_close_at: 3.days.from_now, - shipping_methods: [distributor.shipping_methods.first]) + coordinator: create(:distributor_enterprise), orders_close_at: 3.days.from_now) } let(:product) { create(:simple_product, supplier: supplier, meta_keywords: "Domestic") } let(:variant) { product.variants.first } diff --git a/spec/system/consumer/shopping/unit_price_spec.rb b/spec/system/consumer/shopping/unit_price_spec.rb index a1c998887e..c134dd4f4d 100644 --- a/spec/system/consumer/shopping/unit_price_spec.rb +++ b/spec/system/consumer/shopping/unit_price_spec.rb @@ -12,9 +12,7 @@ describe "As a consumer, I want to check unit price information for a product", let(:supplier) { create(:supplier_enterprise) } let(:oc1) { create(:simple_order_cycle, distributors: [distributor], - coordinator: create(:distributor_enterprise), - orders_close_at: 2.days.from_now, - shipping_methods: [distributor.shipping_methods.first]) + coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) } let(:product) { create(:simple_product, supplier: supplier) } let(:variant) { product.variants.first } diff --git a/spec/system/consumer/shopping/variant_overrides_spec.rb b/spec/system/consumer/shopping/variant_overrides_spec.rb index 26ed5c2955..5fbb24de3e 100644 --- a/spec/system/consumer/shopping/variant_overrides_spec.rb +++ b/spec/system/consumer/shopping/variant_overrides_spec.rb @@ -12,8 +12,7 @@ describe "shopping with variant overrides defined", js: true do let(:hub) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:producer) { create(:supplier_enterprise) } let(:oc) { - create(:simple_order_cycle, - suppliers: [producer], coordinator: hub, distributors: [hub], shipping_methods: [sm]) + create(:simple_order_cycle, suppliers: [producer], coordinator: hub, distributors: [hub]) } let(:outgoing_exchange) { oc.exchanges.outgoing.first } let(:sm) { hub.shipping_methods.first } diff --git a/spec/system/consumer/split_checkout_spec.rb b/spec/system/consumer/split_checkout_spec.rb index cfd01afb2f..ab6568c2f0 100644 --- a/spec/system/consumer/split_checkout_spec.rb +++ b/spec/system/consumer/split_checkout_spec.rb @@ -71,8 +71,7 @@ describe "As a consumer, I want to checkout my order", js: true do add_enterprise_fee enterprise_fee set_order order - distributor.shipping_methods << shipping_methods - order_cycle.shipping_methods << shipping_methods + distributor.shipping_methods.push(shipping_methods) end context "guest checkout when distributor doesn't allow guest orders" do From 9a511e9e940e8d97d20cc357823085fa23563445 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 17 Jun 2022 12:55:17 +0100 Subject: [PATCH 18/69] Rubocop adjustments --- app/models/order_cycle_shipping_method.rb | 2 +- app/services/order_available_shipping_methods.rb | 8 +++++++- spec/factories/order_cycle_factory.rb | 2 -- spec/models/order_cycle_shipping_method_spec.rb | 6 ++++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb index fa9c362edb..4d53a2f3bf 100644 --- a/app/models/order_cycle_shipping_method.rb +++ b/app/models/order_cycle_shipping_method.rb @@ -7,7 +7,7 @@ class OrderCycleShippingMethod < ApplicationRecord validate :shipping_method_belongs_to_order_cycle_distributor validate :shipping_method_available_at_checkout validate :order_cycle_not_simple - validates_uniqueness_of :shipping_method, scope: :order_cycle_id + validates :shipping_method, uniqueness: { scope: :order_cycle_id } before_destroy :check_shipping_method_not_selected_on_any_orders diff --git a/app/services/order_available_shipping_methods.rb b/app/services/order_available_shipping_methods.rb index 3c815d19a0..36fd37f2c8 100644 --- a/app/services/order_available_shipping_methods.rb +++ b/app/services/order_available_shipping_methods.rb @@ -1,10 +1,16 @@ # frozen_string_literal: true -class OrderAvailableShippingMethods < Struct.new(:order, :customer) +class OrderAvailableShippingMethods + attr_reader :order, :customer + delegate :distributor, :order_cycle, to: :order + def initialize(order, customer = nil) + @order, @customer = order, customer + end + def to_a return [] if distributor.blank? diff --git a/spec/factories/order_cycle_factory.rb b/spec/factories/order_cycle_factory.rb index 1962475a8d..469fb94019 100644 --- a/spec/factories/order_cycle_factory.rb +++ b/spec/factories/order_cycle_factory.rb @@ -72,14 +72,12 @@ FactoryBot.define do coordinator { Enterprise.is_distributor.first || FactoryBot.create(:distributor_enterprise) } transient do - shipping_methods { [] } suppliers { [] } distributors { [] } variants { [] } end after(:create) do |oc, proxy| - # Incoming Exchanges proxy.suppliers.each.with_index do |supplier, i| ex = create(:exchange, order_cycle: oc, diff --git a/spec/models/order_cycle_shipping_method_spec.rb b/spec/models/order_cycle_shipping_method_spec.rb index bb503182fb..352cee2565 100644 --- a/spec/models/order_cycle_shipping_method_spec.rb +++ b/spec/models/order_cycle_shipping_method_spec.rb @@ -47,7 +47,8 @@ describe OrderCycleShippingMethod do expect(order_cycle_shipping_method).to_not be_valid expect(order_cycle_shipping_method.errors.to_a).to include( - "Order cycle is simple, all shipping methods are available by default and cannot be customised" + "Order cycle is simple, all shipping methods are available by default and cannot be " \ + "customised" ) end @@ -108,7 +109,8 @@ describe OrderCycleShippingMethod do expect(order_cycle_shipping_method).not_to be_destroyed expect(order_cycle_shipping_method.errors.to_a).to eq [ - "This shipping method has already been selected on orders in this order cycle and cannot be removed" + "This shipping method has already been selected on orders in this order cycle and cannot " \ + "be removed" ] end end From f25de984d2ab45f7f530115eaee2097ecda2fc26 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Mon, 20 Jun 2022 21:06:16 +0100 Subject: [PATCH 19/69] Use .empty? instead of !exists? in app/models/order_cycle_shipping_method.rb Co-authored-by: Maikel --- app/models/order_cycle_shipping_method.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb index 4d53a2f3bf..e2df849ad6 100644 --- a/app/models/order_cycle_shipping_method.rb +++ b/app/models/order_cycle_shipping_method.rb @@ -14,10 +14,10 @@ class OrderCycleShippingMethod < ApplicationRecord private def shipping_method_not_selected_on_any_orders? - !Spree::Order.joins(shipments: :shipping_rates).where( + Spree::Order.joins(shipments: :shipping_rates).where( "order_cycle_id = ? AND spree_shipping_rates.shipping_method_id = ?", order_cycle_id, shipping_method_id - ).exists? + ).empty? end def check_shipping_method_not_selected_on_any_orders From cc5daf51b1a65a7f8eac67c641af4cd88a4f1cf0 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Mon, 20 Jun 2022 21:09:48 +0100 Subject: [PATCH 20/69] Expect to be_valid instead of checking presence of specific error spec/models/spree/shipping_method_spec.rb Co-authored-by: Maikel --- spec/models/spree/shipping_method_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index ba4b5d84fb..e806dc568f 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -148,8 +148,7 @@ module Spree shipping_method = build_stubbed(:shipping_method) [nil, "", "back_end"].each do |display_on_option| shipping_method.display_on = display_on_option - shipping_method.valid? - expect(shipping_method.errors[:display_on]).to be_empty + expect(shipping_method).to be_valid end end From 67d4c38550e848d283597feb00dd5eb62c49c8f5 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Mon, 20 Jun 2022 21:24:41 +0100 Subject: [PATCH 21/69] Rename :preferred_shipping_methods to :selected_shipping_methods on OrderCycle It's a clearer name because 'preferred' implies there could be other unpreferred shipping methods available as well. Co-authored-by: Maikel --- app/models/order_cycle.rb | 12 +++++------ app/services/order_cycle_form.rb | 20 +++++++++---------- .../permitted_attributes/order_cycle.rb | 2 +- .../order_cycles/checkout_options.html.haml | 6 +++--- spec/models/order_cycle_spec.rb | 12 +++++------ .../order_available_shipping_methods_spec.rb | 2 +- spec/services/order_cycle_form_spec.rb | 12 +++++------ .../complex_creating_specific_time_spec.rb | 4 ++-- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index c552c4e45c..8c354e5e97 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -25,7 +25,7 @@ class OrderCycle < ApplicationRecord has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules has_many :order_cycle_shipping_methods - has_many :preferred_shipping_methods, class_name: "Spree::ShippingMethod", + has_many :selected_shipping_methods, class_name: "Spree::ShippingMethod", through: :order_cycle_shipping_methods, source: :shipping_method has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } @@ -181,7 +181,7 @@ class OrderCycle < ApplicationRecord oc.schedule_ids = schedule_ids oc.save! exchanges.each { |e| e.clone!(oc) } - oc.preferred_shipping_method_ids = preferred_shipping_method_ids + oc.selected_shipping_method_ids = selected_shipping_method_ids sync_subscriptions oc.reload end @@ -296,10 +296,10 @@ class OrderCycle < ApplicationRecord end def shipping_methods - if simple? || preferred_shipping_methods.none? + if simple? || selected_shipping_methods.none? attachable_shipping_methods else - preferred_shipping_methods + selected_shipping_methods end end @@ -311,12 +311,12 @@ class OrderCycle < ApplicationRecord def all_distributors_have_at_least_one_shipping_method? distributors.all? do |distributor| - (distributor.shipping_method_ids & preferred_shipping_method_ids).any? + (distributor.shipping_method_ids & selected_shipping_method_ids).any? end end def at_least_one_shipping_method_selected_for_each_distributor - return if preferred_shipping_methods.none? || + return if selected_shipping_methods.none? || coordinator.nil? || simple? || all_distributors_have_at_least_one_shipping_method? diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 18e07553a8..926c49fbc3 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -11,7 +11,7 @@ class OrderCycleForm @user = user @permissions = OpenFoodNetwork::Permissions.new(user) @schedule_ids = order_cycle_params.delete(:schedule_ids) - @preferred_shipping_method_ids = order_cycle_params.delete(:preferred_shipping_method_ids) + @selected_shipping_method_ids = order_cycle_params.delete(:selected_shipping_method_ids) end def save @@ -24,7 +24,7 @@ class OrderCycleForm order_cycle.schedule_ids = schedule_ids order_cycle.save! apply_exchange_changes - attach_preferred_shipping_methods + attach_selected_shipping_methods sync_subscriptions true end @@ -48,11 +48,11 @@ class OrderCycleForm OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go! end - def attach_preferred_shipping_methods - return if @preferred_shipping_method_ids.nil? + def attach_selected_shipping_methods + return if @selected_shipping_method_ids.nil? order_cycle.reload # so outgoing exchanges are up-to-date for shipping method validations - order_cycle.preferred_shipping_method_ids = preferred_shipping_method_ids + order_cycle.selected_shipping_method_ids = selected_shipping_method_ids order_cycle.save! end @@ -62,14 +62,14 @@ class OrderCycleForm end end - def preferred_shipping_method_ids - @preferred_shipping_method_ids = @preferred_shipping_method_ids.reject(&:blank?).map(&:to_i) + def selected_shipping_method_ids + @selected_shipping_method_ids = @selected_shipping_method_ids.reject(&:blank?).map(&:to_i) - if order_cycle.attachable_shipping_methods.map(&:id).sort == @preferred_shipping_method_ids.sort - @preferred_shipping_method_ids = [] + if order_cycle.attachable_shipping_methods.map(&:id).sort == @selected_shipping_method_ids.sort + @selected_shipping_method_ids = [] end - @preferred_shipping_method_ids + @selected_shipping_method_ids end def schedule_ids? diff --git a/app/services/permitted_attributes/order_cycle.rb b/app/services/permitted_attributes/order_cycle.rb index 8f5998b70d..e1b7555d6d 100644 --- a/app/services/permitted_attributes/order_cycle.rb +++ b/app/services/permitted_attributes/order_cycle.rb @@ -17,7 +17,7 @@ module PermittedAttributes :name, :orders_open_at, :orders_close_at, :coordinator_id, :preferred_product_selection_from_coordinator_inventory_only, :automatic_notifications, - { schedule_ids: [], preferred_shipping_method_ids: [], coordinator_fee_ids: [] } + { schedule_ids: [], selected_shipping_method_ids: [], coordinator_fee_ids: [] } ] end diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index ca76377eda..0e27b35394 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -13,7 +13,7 @@ %fieldset.no-border-bottom %legend{ align: 'center'}= t('.checkout_options') - = hidden_field_tag "order_cycle[preferred_shipping_method_ids][]", "" + = hidden_field_tag "order_cycle[selected_shipping_method_ids][]", "" %table.checkout-options %thead @@ -30,9 +30,9 @@ - shipping_methods.each do |shipping_method| %p %label - = check_box_tag "order_cycle[preferred_shipping_method_ids][]", + = check_box_tag "order_cycle[selected_shipping_method_ids][]", shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - id: "order_cycle_preferred_shipping_method_ids_#{shipping_method.id}" + id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" = shipping_method.name - distributor.shipping_methods.backend.each do |shipping_method| %label.disabled diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index e6b3ff54a1..6d6c392399 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -31,7 +31,7 @@ describe OrderCycle do order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - order_cycle.preferred_shipping_method_ids = [ + order_cycle.selected_shipping_method_ids = [ distributor_i_shipping_method.id, distributor_ii_shipping_method.id ] @@ -47,7 +47,7 @@ describe OrderCycle do distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_i]) order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - order_cycle.preferred_shipping_method_ids = [distributor_i_shipping_method.id] + order_cycle.selected_shipping_method_ids = [distributor_i_shipping_method.id] expect(order_cycle).to be_invalid expect(order_cycle.errors.to_a).to eq [ @@ -64,7 +64,7 @@ describe OrderCycle do shipping_method = create(:shipping_method) distributor = create(:distributor_enterprise, shipping_methods: [shipping_method]) order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - order_cycle.preferred_shipping_methods << shipping_method + order_cycle.selected_shipping_methods << shipping_method shipping_method.update_column(:display_on, "back_end") @@ -468,7 +468,7 @@ describe OrderCycle do distributor = create(:distributor_enterprise) shipping_method = create(:shipping_method, distributors: [distributor]) order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - order_cycle.preferred_shipping_methods << shipping_method + order_cycle.selected_shipping_methods << shipping_method shipping_method.update_column(:display_on, "back_end") @@ -721,7 +721,7 @@ describe OrderCycle do it "returns all attachable shipping methods if no preferred shipping methods have been chosen" do shipping_method = create(:shipping_method, distributors: [distributor]) - expect(oc.preferred_shipping_methods).to be_empty + expect(oc.selected_shipping_methods).to be_empty expect(oc.shipping_methods).to eq [shipping_method] end @@ -729,7 +729,7 @@ describe OrderCycle do shipping_method_i = create(:shipping_method, distributors: [distributor]) shipping_method_ii = create(:shipping_method, distributors: [distributor]) - oc.preferred_shipping_methods << shipping_method_ii + oc.selected_shipping_methods << shipping_method_ii expect(oc.shipping_methods).to eq [shipping_method_ii] end diff --git a/spec/services/order_available_shipping_methods_spec.rb b/spec/services/order_available_shipping_methods_spec.rb index 5cfdde3e21..8157e2156f 100644 --- a/spec/services/order_available_shipping_methods_spec.rb +++ b/spec/services/order_available_shipping_methods_spec.rb @@ -55,7 +55,7 @@ describe OrderAvailableShippingMethods do shipping_method_iv = create(:shipping_method, distributors: [distributor_ii]) order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - order_cycle.preferred_shipping_methods << [shipping_method_i, shipping_method_iii] + order_cycle.selected_shipping_methods << [shipping_method_i, shipping_method_iii] order = build(:order, distributor: distributor_i, order_cycle: order_cycle) available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index f2edeacbce..9d831daba5 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -159,7 +159,7 @@ describe OrderCycleForm do enterprise_fee_ids: [] }], outgoing_exchanges: [outgoing_exchange_params], - preferred_shipping_method_ids: [shipping_method.id] + selected_shipping_method_ids: [shipping_method.id] ) end @@ -176,7 +176,7 @@ describe OrderCycleForm do before do params.merge!( outgoing_exchanges: [outgoing_exchange_params], - preferred_shipping_method_ids: nil + selected_shipping_method_ids: nil ) end @@ -195,7 +195,7 @@ describe OrderCycleForm do before do params.merge!( outgoing_exchanges: [outgoing_exchange_params], - preferred_shipping_method_ids: [other_distributor_shipping_method.id] + selected_shipping_method_ids: [other_distributor_shipping_method.id] ) end @@ -216,7 +216,7 @@ describe OrderCycleForm do form = OrderCycleForm.new( order_cycle, - params.except(:preferred_shipping_method_ids), + params.except(:selected_shipping_method_ids), order_cycle.coordinator ) @@ -232,7 +232,7 @@ describe OrderCycleForm do order_cycle = create(:distributor_order_cycle, distributors: [distributor]) form = OrderCycleForm.new(order_cycle, - { preferred_shipping_method_ids: [shipping_method.id] }, + { selected_shipping_method_ids: [shipping_method.id] }, order_cycle.coordinator) expect(form.save).to be true @@ -250,7 +250,7 @@ describe OrderCycleForm do distributors: [distributor_i, distributor_ii]) form = OrderCycleForm.new(order_cycle, - { preferred_shipping_method_ids: [shipping_method_i.id] }, + { selected_shipping_method_ids: [shipping_method_i.id] }, order_cycle.coordinator) expect(form.save).to be false diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 19ab92a0bb..ad279fd799 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -134,8 +134,8 @@ describe ' click_button 'Save and Next' # And I select preferred shipping methods - check "order_cycle_preferred_shipping_method_ids_#{shipping_method_i.id}" - uncheck "order_cycle_preferred_shipping_method_ids_#{shipping_method_ii.id}" + check "order_cycle_selected_shipping_method_ids_#{shipping_method_i.id}" + uncheck "order_cycle_selected_shipping_method_ids_#{shipping_method_ii.id}" click_button 'Save and Back to List' From d9de35799d5389d7972c0bac3064be5443aefedc Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Mon, 20 Jun 2022 21:39:47 +0100 Subject: [PATCH 22/69] Array difference operator is neater than reject in app/views/admin/order_cycles/checkout_options.html.haml Co-authored-by: Maikel --- app/views/admin/order_cycles/checkout_options.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 0e27b35394..01ebd9e041 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -22,8 +22,8 @@ %th= t('.shipping_methods') %th= t('.payment_methods') - @order_cycle.distributors.each do |distributor| - - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id).reject { |payment_method| shared_payment_methods.include?(payment_method) } - - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id).reject { |shipping_method| shared_shipping_methods.include?(shipping_method) } + - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) - shared_payment_methods + - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) - shared_shipping_methods %tr %td= distributor.name %td From 7bd56007bde5d417df73aa74c9d8bb12ad50993b Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Mon, 20 Jun 2022 21:48:04 +0100 Subject: [PATCH 23/69] Don't use :html_safe in case shipping method name contains something malicious in app/views/admin/order_cycles/checkout_options.html.haml Co-authored-by: Maikel --- app/views/admin/order_cycles/checkout_options.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 01ebd9e041..c3f2711900 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -61,7 +61,9 @@ = input.check_box = input.label %p - = "—#{shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ")}".html_safe + &mdash + %em> + = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %td - if shared_payment_methods.any? %ul From 81730f725db564758df1e98327c5f7ff8d5e5c3b Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 15:15:19 +0100 Subject: [PATCH 24/69] Enterprises shouldn't be considered ready for checkout if it only has backend shipping methods --- app/models/enterprise.rb | 3 ++- spec/models/enterprise_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index e08d508ad2..8d62a1cc94 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -120,6 +120,7 @@ class Enterprise < ApplicationRecord joins(:shipping_methods). joins(:payment_methods). merge(Spree::PaymentMethod.available). + merge(Spree::ShippingMethod.frontend). select('DISTINCT enterprises.*') } scope :not_ready_for_checkout, lambda { @@ -387,7 +388,7 @@ class Enterprise < ApplicationRecord end def ready_for_checkout? - shipping_methods.any? && payment_methods.available.any? + shipping_methods.frontend.any? && payment_methods.available.any? end def self.find_available_permalink(test_permalink) diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index bc35b51f4a..47b7d41c0f 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -276,6 +276,13 @@ describe Enterprise do expect(Enterprise.ready_for_checkout).not_to include e end + it "does not show enterprises wchich only have backend shipping methods" do + create(:shipping_method, distributors: [e], + display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end]) + create(:payment_method, distributors: [e]) + expect(Enterprise.ready_for_checkout).not_to include e + end + it "shows enterprises with available payment and shipping methods" do create(:shipping_method, distributors: [e]) create(:payment_method, distributors: [e]) @@ -302,6 +309,13 @@ describe Enterprise do expect(Enterprise.not_ready_for_checkout).to include e end + it "shows enterprises which only have backend shipping methods" do + create(:shipping_method, distributors: [e], + display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end]) + create(:payment_method, distributors: [e]) + expect(Enterprise.not_ready_for_checkout).to include e + end + it "does not show enterprises with available payment and shipping methods" do create(:shipping_method, distributors: [e]) create(:payment_method, distributors: [e]) @@ -328,6 +342,13 @@ describe Enterprise do expect(e.reload).not_to be_ready_for_checkout end + it "returns false for enterprises which only have backend shipping methods" do + create(:shipping_method, distributors: [e], + display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end]) + create(:payment_method, distributors: [e]) + expect(e.reload).not_to be_ready_for_checkout + end + it "returns true for enterprises with available payment and shipping methods" do create(:shipping_method, distributors: [e]) create(:payment_method, distributors: [e]) From f4a4f7c9ff768f4a99b30c8541b430b19a0ac808 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 15:33:15 +0100 Subject: [PATCH 25/69] Remove validations checking order cycle has shipping methods Instead we will make sure the order cycle is not available on the shopfront if it is doesn't have valid shipping methods. This will preven the issue where if one distributor deletes their shipping method, we don't want to invalidate the order cycle for all other distributors. Co-authored-by: Maikel --- app/controllers/base_controller.rb | 6 +--- app/models/order_cycle.rb | 26 -------------- spec/models/order_cycle_spec.rb | 58 ------------------------------ 3 files changed, 1 insertion(+), 89 deletions(-) diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 626307db49..a04725416a 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -15,12 +15,8 @@ class BaseController < ApplicationController private - def all_distributor_order_cycles_invalid? - OrderCycle.with_distributor(@distributor).active.all?(&:invalid?) - end - def set_order_cycles - if !@distributor.ready_for_checkout? || all_distributor_order_cycles_invalid? + if !@distributor.ready_for_checkout? @order_cycles = OrderCycle.where('false') return end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 8c354e5e97..ed72e8912f 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -36,8 +36,6 @@ class OrderCycle < ApplicationRecord after_save :sync_subscriptions, if: :opening? validates :name, :coordinator_id, presence: true - validate :at_least_one_shipping_method_selected_for_each_distributor - validate :no_invalid_order_cycle_shipping_methods validate :orders_close_at_after_orders_open_at? preference :product_selection_from_coordinator_inventory_only, :boolean, default: false @@ -309,30 +307,6 @@ class OrderCycle < ApplicationRecord private - def all_distributors_have_at_least_one_shipping_method? - distributors.all? do |distributor| - (distributor.shipping_method_ids & selected_shipping_method_ids).any? - end - end - - def at_least_one_shipping_method_selected_for_each_distributor - return if selected_shipping_methods.none? || - coordinator.nil? || - simple? || - all_distributors_have_at_least_one_shipping_method? - - errors.add(:base, :at_least_one_shipping_method_per_distributor) - end - - def no_invalid_order_cycle_shipping_methods - return if order_cycle_shipping_methods.all?(&:valid?) - - errors.add( - :base, - order_cycle_shipping_methods.map(&:errors).map(&:to_a).flatten.uniq.to_sentence - ) - end - def opening? (open? || upcoming?) && saved_change_to_orders_close_at? && was_closed? end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 6d6c392399..5d708d7751 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -20,64 +20,6 @@ describe OrderCycle do expect(oc).to_not be_valid end - describe "#at_least_one_shipping_method_selected_for_each_distributor" do - context "distributor order cycle i.e. :sells is 'any'" do - context "when multiple distributors have been added to the order cycle already" do - it "is valid when adding a shipping method for *all* distributors" do - distributor_i = create(:distributor_enterprise) - distributor_ii = create(:distributor_enterprise) - distributor_i_shipping_method = create(:shipping_method, distributors: [distributor_i]) - distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_ii]) - order_cycle = create(:distributor_order_cycle, - distributors: [distributor_i, distributor_ii]) - - order_cycle.selected_shipping_method_ids = [ - distributor_i_shipping_method.id, - distributor_ii_shipping_method.id - ] - - expect(order_cycle).to be_valid - end - end - - it "is not valid when adding a shipping method for *some but not all* distributors" do - distributor_i = create(:distributor_enterprise) - distributor_ii = create(:distributor_enterprise) - distributor_i_shipping_method = create(:shipping_method, distributors: [distributor_i]) - distributor_ii_shipping_method = create(:shipping_method, distributors: [distributor_i]) - order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - - order_cycle.selected_shipping_method_ids = [distributor_i_shipping_method.id] - - expect(order_cycle).to be_invalid - expect(order_cycle.errors.to_a).to eq [ - "You need to select at least one shipping method for each distributor" - ] - end - end - end - - describe "#no_invalid_order_cycle_shipping_methods" do - context "when a order cycle shipping method is not valid" do - it "adds a validation error, - and it is more meaningful than the default 'Order cycle shipping methods is invalid'" do - shipping_method = create(:shipping_method) - distributor = create(:distributor_enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - order_cycle.selected_shipping_methods << shipping_method - - shipping_method.update_column(:display_on, "back_end") - - expect(order_cycle).to be_invalid - expect(order_cycle.errors.to_a).to eq ["Shipping method must be available at checkout"] - - shipping_method.update_column(:display_on, "") - - expect(order_cycle.reload).to be_valid - end - end - end - it "has a coordinator and associated fees" do oc = create(:simple_order_cycle) From 747c88fb35d0df40a37f6a406fe7406cf29080ca Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:11:29 +0100 Subject: [PATCH 26/69] Update the Shop::OrderCyclesList service so it's clearer what kind of order cycles are being fetched Before the Shop::OrderCyclesList service would return order cycles even if they are not ready for checkout and we had a check before calling the service in BaseController which would return OrderCycle.where('false'). It seems like this check should be part of the service too. --- app/controllers/base_controller.rb | 7 +- app/services/order_cart_reset.rb | 2 +- app/services/shop/order_cycles_list.rb | 12 +++- spec/services/shop/order_cycles_list_spec.rb | 67 ++++++++++++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 spec/services/shop/order_cycles_list_spec.rb diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index a04725416a..952c66afee 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -16,12 +16,7 @@ class BaseController < ApplicationController private def set_order_cycles - if !@distributor.ready_for_checkout? - @order_cycles = OrderCycle.where('false') - return - end - - @order_cycles = Shop::OrderCyclesList.new(@distributor, current_customer).call + @order_cycles = Shop::OrderCyclesList.ready_for_checkout_for(@distributor, current_customer) set_order_cycle end diff --git a/app/services/order_cart_reset.rb b/app/services/order_cart_reset.rb index 4faec18a30..9401d60c4c 100644 --- a/app/services/order_cart_reset.rb +++ b/app/services/order_cart_reset.rb @@ -34,7 +34,7 @@ class OrderCartReset end def reset_order_cycle(current_customer) - listed_order_cycles = Shop::OrderCyclesList.new(distributor, current_customer).call + listed_order_cycles = Shop::OrderCyclesList.active_for(distributor, current_customer) if order_cycle_not_listed?(order.order_cycle, listed_order_cycles) order.order_cycle = nil diff --git a/app/services/shop/order_cycles_list.rb b/app/services/shop/order_cycles_list.rb index 1afed9acd8..3a453aa7ac 100644 --- a/app/services/shop/order_cycles_list.rb +++ b/app/services/shop/order_cycles_list.rb @@ -3,12 +3,22 @@ # Lists available order cycles for a given customer in a given distributor module Shop class OrderCyclesList + def self.active_for(distributor, customer) + new(distributor, customer).call(ready_for_checkout: false) + end + + def self.ready_for_checkout_for(distributor, customer) + new(distributor, customer).call(ready_for_checkout: true) + end + def initialize(distributor, customer) @distributor = distributor @customer = customer end - def call + def call(ready_for_checkout:) + return OrderCycle.none if ready_for_checkout && !@distributor.ready_for_checkout? + order_cycles = OrderCycle.with_distributor(@distributor).active .order(@distributor.preferred_shopfront_order_cycle_order).to_a diff --git a/spec/services/shop/order_cycles_list_spec.rb b/spec/services/shop/order_cycles_list_spec.rb new file mode 100644 index 0000000000..3b0e5fe045 --- /dev/null +++ b/spec/services/shop/order_cycles_list_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Shop::OrderCyclesList do + describe ".active_for" do + let(:customer) { nil } + + context "when the order cycle is open and the distributor belongs to the order cycle" do + context "and the distributor is ready for checkout" do + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + + it "returns the order cycle" do + open_order_cycle = create(:open_order_cycle, distributors: [distributor]) + + expect(Shop::OrderCyclesList.active_for(distributor, customer)).to eq [open_order_cycle] + end + end + + context "and the distributor is not ready for checkout" do + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: false) } + + it "returns the order cycle" do + open_order_cycle = create(:open_order_cycle, distributors: [distributor]) + + expect(Shop::OrderCyclesList.active_for(distributor, customer)).to eq [open_order_cycle] + end + end + end + + it "doesn't returns closed order cycles or ones belonging to other distributors" do + distributor = create(:distributor_enterprise) + closed_order_cycle = create(:closed_order_cycle, distributors: [distributor]) + other_distributor_order_cycle = create(:open_order_cycle) + + expect(Shop::OrderCyclesList.active_for(distributor, customer)).to be_empty + end + end + + describe ".ready_for_checkout_for" do + let(:customer) { nil } + + context "when the order cycle is open and belongs to the distributor" do + context "and the distributor is ready for checkout" do + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + + it "returns the order cycle" do + open_order_cycle = create(:open_order_cycle, distributors: [distributor]) + + expect(Shop::OrderCyclesList.ready_for_checkout_for(distributor, customer)).to eq [ + open_order_cycle + ] + end + end + + context "but the distributor not is ready for checkout" do + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: false) } + + it "doesn't return the order cycle" do + open_order_cycle = create(:open_order_cycle, distributors: [distributor]) + + expect(Shop::OrderCyclesList.ready_for_checkout_for(distributor, customer)).to be_empty + end + end + end + end +end From 564e4d802cc9564f0544e0715fea66b455997dfb Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:20:10 +0100 Subject: [PATCH 27/69] Don't use :html_safe with payment method name in checkout options in case it contains something malicious Co-authored-by: Maikel --- app/views/admin/order_cycles/checkout_options.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index c3f2711900..31add9af5a 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -61,7 +61,7 @@ = input.check_box = input.label %p - &mdash + — %em> = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %td @@ -71,7 +71,9 @@ %li = shared_payment_method.name %p - = "—#{shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ")}".html_safe + — + %em + = shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %div#save-bar %div.container From ed13ee6cbc391c4d5ade48495d32ff4700c4affa Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:22:20 +0100 Subject: [PATCH 28/69] Fix association alignment in OrderCycle --- app/models/order_cycle.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index ed72e8912f..0d49dd8d13 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -26,8 +26,8 @@ class OrderCycle < ApplicationRecord has_many :schedules, through: :order_cycle_schedules has_many :order_cycle_shipping_methods has_many :selected_shipping_methods, class_name: "Spree::ShippingMethod", - through: :order_cycle_shipping_methods, - source: :shipping_method + through: :order_cycle_shipping_methods, + source: :shipping_method has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges From a3a52a07b7ddba482e584e7a572e95f47f811c0e Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:35:22 +0100 Subject: [PATCH 29/69] Build shipping method with a category so it's valid in the test --- spec/models/spree/shipping_method_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index e806dc568f..fb8c2651e2 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -145,7 +145,10 @@ module Spree describe "#display_on" do it "is valid when it's set to nil, an empty string or 'back_end'" do - shipping_method = build_stubbed(:shipping_method) + shipping_method = build_stubbed( + :shipping_method, + shipping_categories: [Spree::ShippingCategory.new(name: 'Test')] + ) [nil, "", "back_end"].each do |display_on_option| shipping_method.display_on = display_on_option expect(shipping_method).to be_valid From 593da4996f3b933c3e772aa6895048d686cdbb71 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:39:37 +0100 Subject: [PATCH 30/69] A shop won't be shown as open if it doesn't have useable shipping method so these shipping method validations are really necessary --- app/models/order_cycle.rb | 3 +- app/models/order_cycle_shipping_method.rb | 32 ------ app/models/spree/shipping_method.rb | 17 --- config/locales/en.yml | 12 -- .../order_cycle_shipping_method_spec.rb | 82 -------------- spec/models/order_cycle_spec.rb | 17 +-- spec/models/spree/shipping_method_spec.rb | 104 ------------------ spec/services/order_cycle_form_spec.rb | 6 +- 8 files changed, 15 insertions(+), 258 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 0d49dd8d13..424b477e5f 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -179,7 +179,8 @@ class OrderCycle < ApplicationRecord oc.schedule_ids = schedule_ids oc.save! exchanges.each { |e| e.clone!(oc) } - oc.selected_shipping_method_ids = selected_shipping_method_ids + oc.selected_shipping_method_ids = attachable_shipping_methods.map(&:id) & + selected_shipping_method_ids sync_subscriptions oc.reload end diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb index e2df849ad6..8b8f08d7ef 100644 --- a/app/models/order_cycle_shipping_method.rb +++ b/app/models/order_cycle_shipping_method.rb @@ -5,42 +5,10 @@ class OrderCycleShippingMethod < ApplicationRecord belongs_to :shipping_method, class_name: "Spree::ShippingMethod" validate :shipping_method_belongs_to_order_cycle_distributor - validate :shipping_method_available_at_checkout - validate :order_cycle_not_simple validates :shipping_method, uniqueness: { scope: :order_cycle_id } - before_destroy :check_shipping_method_not_selected_on_any_orders - private - def shipping_method_not_selected_on_any_orders? - Spree::Order.joins(shipments: :shipping_rates).where( - "order_cycle_id = ? AND spree_shipping_rates.shipping_method_id = ?", - order_cycle_id, shipping_method_id - ).empty? - end - - def check_shipping_method_not_selected_on_any_orders - return if order_cycle.nil? || - shipping_method.nil? || - shipping_method_not_selected_on_any_orders? - - errors.add(:base, :shipping_method_already_used_in_order_cycle) - throw :abort - end - - def order_cycle_not_simple - return if order_cycle.nil? || !order_cycle.simple? - - errors.add(:order_cycle, :must_not_be_simple) - end - - def shipping_method_available_at_checkout - return if shipping_method.nil? || shipping_method.frontend? - - errors.add(:shipping_method, :must_be_available_at_checkout) - end - def shipping_method_belongs_to_order_cycle_distributor return if order_cycle.nil? || shipping_method.nil? || diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index ad1f831fa8..589bc8ce8c 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -30,11 +30,8 @@ module Spree validates :name, presence: true validate :distributor_validation validate :at_least_one_shipping_category - validate :switching_to_backoffice_only_wont_leave_order_cycles_without_shipping_methods validates :display_on, inclusion: { in: DISPLAY_ON_OPTIONS.values }, allow_nil: true - before_destroy :check_destroy_wont_leave_order_cycles_without_shipping_methods - after_save :touch_distributors scope :managed_by, lambda { |user| @@ -140,19 +137,5 @@ module Spree def distributor_validation validates_with DistributorsValidator end - - def check_destroy_wont_leave_order_cycles_without_shipping_methods - return if no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method? - - errors.add(:base, :destroy_leaves_order_cycles_without_shipping_methods) - throw :abort - end - - def switching_to_backoffice_only_wont_leave_order_cycles_without_shipping_methods - return if frontend? || - no_active_or_upcoming_order_cycle_distributors_with_only_one_shipping_method? - - errors.add(:base, :switching_to_backoffice_only_leaves_order_cycles_without_shipping_methods) - end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 1321a077f4..014d8dd07f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -73,25 +73,13 @@ en: attributes: base: card_expired: "has expired" - spree/shipping_method: - attributes: - base: - switching_to_backoffice_only_leaves_order_cycles_without_shipping_methods: Unable to switch to backoffice only, some open or upcoming order cycles would be left without any shipping methods - destroy_leaves_order_cycles_without_shipping_methods: Unable to delete, some open or upcoming order cycles would be left without any shipping methods order_cycle: attributes: - base: - at_least_one_shipping_method_per_distributor: "You need to select at least one shipping method for each distributor" orders_close_at: after_orders_open_at: must be after open date order_cycle_shipping_method: attributes: - base: - shipping_method_already_used_in_order_cycle: "This shipping method has already been selected on orders in this order cycle and cannot be removed" - order_cycle: - must_not_be_simple: "is simple, all shipping methods are available by default and cannot be customised" shipping_method: - must_be_available_at_checkout: "must be available at checkout" must_belong_to_order_cycle_distributor: "must be from a distributor on the order cycle" variant_override: count_on_hand: diff --git a/spec/models/order_cycle_shipping_method_spec.rb b/spec/models/order_cycle_shipping_method_spec.rb index 352cee2565..4914313be7 100644 --- a/spec/models/order_cycle_shipping_method_spec.rb +++ b/spec/models/order_cycle_shipping_method_spec.rb @@ -3,55 +3,6 @@ require 'spec_helper' describe OrderCycleShippingMethod do - it "is valid when the shipping method is available at checkout" do - shipping_method = create(:shipping_method, display_on: nil) - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).to be_valid - - shipping_method.display_on = "both" - - expect(order_cycle_shipping_method).to be_valid - end - - it "is not valid when the shipping method is only available in the backoffice" do - shipping_method = create(:shipping_method, display_on: "back_end") - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).to_not be_valid - expect(order_cycle_shipping_method.errors.to_a).to include( - "Shipping method must be available at checkout" - ) - end - - it "is not valid if the order cycle is simple i.e. :sells is 'own'" do - order_cycle = create(:sells_own_order_cycle) - shipping_method = create(:shipping_method, distributors: [order_cycle.coordinator]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).to_not be_valid - expect(order_cycle_shipping_method.errors.to_a).to include( - "Order cycle is simple, all shipping methods are available by default and cannot be " \ - "customised" - ) - end - it "is valid if the shipping method belongs to one of the order cycle distributors" do shipping_method = create(:shipping_method) enterprise = create(:enterprise, shipping_methods: [shipping_method]) @@ -80,37 +31,4 @@ describe OrderCycleShippingMethod do "Shipping method must be from a distributor on the order cycle" ] end - - it "can be destroyed if the shipping method hasn't been used on any orders in the order cycle" do - shipping_method = create(:shipping_method) - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - - order_cycle_shipping_method = OrderCycleShippingMethod.create!( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - order_cycle_shipping_method.destroy - - expect(order_cycle_shipping_method).to be_destroyed - end - - it "cannot be destroyed if the shipping method has been used on some orders in the order cycle" do - shipping_method = create(:shipping_method) - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - order = create(:order_ready_for_payment, distributor: enterprise, order_cycle: order_cycle) - - order_cycle_shipping_method = OrderCycleShippingMethod.create!( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - order_cycle_shipping_method.destroy - - expect(order_cycle_shipping_method).not_to be_destroyed - expect(order_cycle_shipping_method.errors.to_a).to eq [ - "This shipping method has already been selected on orders in this order cycle and cannot " \ - "be removed" - ] - end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 5d708d7751..9035b853e1 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -406,17 +406,20 @@ describe OrderCycle do context "when it has preferred shipping methods which can longer be applied validly e.g. shipping method is backoffice only" do - it "raises an error (TODO: display a message to user explaining why clone failed)" do + it "only attaches the valid ones to the clone" do distributor = create(:distributor_enterprise) - shipping_method = create(:shipping_method, distributors: [distributor]) + shipping_method_i = create(:shipping_method, distributors: [distributor]) + shipping_method_ii = create( + :shipping_method, + distributors: [distributor], + display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end] + ) order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - order_cycle.selected_shipping_methods << shipping_method + order_cycle.selected_shipping_methods = [shipping_method_i, shipping_method_ii] - shipping_method.update_column(:display_on, "back_end") + cloned_order_cycle = order_cycle.clone! - expect { - order_cycle.clone! - }.to raise_error ActiveRecord::RecordInvalid + expect(cloned_order_cycle.shipping_methods).to eq [shipping_method_i] end end end diff --git a/spec/models/spree/shipping_method_spec.rb b/spec/models/spree/shipping_method_spec.rb index fb8c2651e2..d6ba024989 100644 --- a/spec/models/spree/shipping_method_spec.rb +++ b/spec/models/spree/shipping_method_spec.rb @@ -182,47 +182,6 @@ module Spree it { expect(shipping_method).to be_valid } end end - - context "when it is being changed to backoffice only" do - let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor]) } - let(:distributor) { create(:distributor_enterprise, shipping_methods: [shipping_method]) } - let(:shipping_method) { create(:shipping_method) } - - context "when one of its distributors has no other shipping methods available - on an active or upcoming order cycle" do - it "should not be valid" do - shipping_method.display_on = "back_end" - - expect(shipping_method).not_to be_valid - expect(shipping_method.errors.to_a).to eq [ - "Unable to switch to backoffice only, some open or upcoming order cycles would be " \ - "left without any shipping methods" - ] - end - end - - context "when one of its distributors has no other shipping methods available - on an order cycle which isn't active or upcoming" do - it "is valid" do - order_cycle.update!(orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago) - - shipping_method.display_on = "back_end" - - expect(shipping_method).to be_valid - end - end - - context "when one of its distributors has other shipping methods available - on an active or upcoming order cycle" do - it "is valid" do - create(:shipping_method, distributors: [distributor]) - - shipping_method.display_on = "back_end" - - expect(shipping_method).to be_valid - end - end - end end # Regression test for Spree #4320 @@ -256,68 +215,5 @@ module Spree expect(shipping_method.shipments).to include(shipment) end end - - context "#destroy" do - let(:shipping_method) { create(:shipping_method) } - let(:distributor) { create(:distributor_enterprise, shipping_methods: [shipping_method]) } - let!(:order_cycle) { create(:distributor_order_cycle, distributors: [distributor]) } - - context "when its distributors are part of some order cycles - and have no other shipping methods available" do - it "can be deleted if the order cycle is closed" do - order_cycle.update!(orders_close_at: 1.minute.ago) - - shipping_method.destroy - - expect(shipping_method).to be_deleted - end - - it "cannot be deleted if the order cycle is active" do - order_cycle.update!(orders_open_at: 1.day.ago, orders_close_at: 1.week.from_now) - - shipping_method.destroy - - expect(shipping_method).not_to be_deleted - expect(shipping_method.errors.to_a).to eq [ - "Unable to delete, some open or upcoming order cycles would be left without any " \ - "shipping methods" - ] - end - - it "cannot be deleted if the order cycle is upcoming" do - order_cycle.update!(orders_open_at: 1.day.from_now, orders_close_at: 1.week.from_now) - - shipping_method.destroy - - expect(shipping_method).not_to be_deleted - expect(shipping_method.errors.to_a).to eq [ - "Unable to delete, some open or upcoming order cycles would be left without any " \ - "shipping methods" - ] - end - end - - context "when its distributors are part of some active or upcoming order cycles - and have other shipping methods available" do - it "can be deleted" do - create(:shipping_method, distributors: [distributor]) - - shipping_method.destroy - - expect(shipping_method).to be_deleted - end - end - - context "when its distributors have no other shipping methods available - but aren't part of active/upcoming order cycles" do - it "can be deleted" do - order_cycle.update!(orders_open_at: 2.weeks.ago, orders_close_at: 1.week.ago) - - shipping_method.destroy - - expect(shipping_method).to be_deleted - end - end - end end end diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index 9d831daba5..fc3a137030 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -247,15 +247,15 @@ describe OrderCycleForm do shipping_method_i = create(:shipping_method, distributors: [distributor_i]) shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) order_cycle = create(:distributor_order_cycle, - distributors: [distributor_i, distributor_ii]) + distributors: [distributor_i]) form = OrderCycleForm.new(order_cycle, - { selected_shipping_method_ids: [shipping_method_i.id] }, + { selected_shipping_method_ids: [shipping_method_ii.id] }, order_cycle.coordinator) expect(form.save).to be false expect(order_cycle.errors.to_a).to eq [ - "You need to select at least one shipping method for each distributor" + "Shipping method must be from a distributor on the order cycle" ] end end From 541a340dcf1cba92f182e17fa9711462559585d0 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 24 Jun 2022 16:52:27 +0100 Subject: [PATCH 31/69] If an OrderCycle or Spree::ShippingMethod is destroyed then remove it's associated OrderCycleShippingMethod records too --- app/models/order_cycle.rb | 2 +- app/models/spree/shipping_method.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 424b477e5f..fa0a5f2292 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -24,7 +24,7 @@ class OrderCycle < ApplicationRecord has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules - has_many :order_cycle_shipping_methods + has_many :order_cycle_shipping_methods, dependent: :destroy has_many :selected_shipping_methods, class_name: "Spree::ShippingMethod", through: :order_cycle_shipping_methods, source: :shipping_method diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 589bc8ce8c..7d42b4d0af 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -13,6 +13,7 @@ module Spree default_scope -> { where(deleted_at: nil) } + has_many :order_cycle_shipping_methods, dependent: :destroy has_many :shipping_rates, inverse_of: :shipping_method has_many :shipments, through: :shipping_rates has_many :shipping_method_categories From 6453bbcc1c6800aa726fcae1969bba9c53e0d510 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Sat, 25 Jun 2022 11:58:54 +0100 Subject: [PATCH 32/69] Fix :selected_shipping_method_ids param for shared shipping methods. --- app/views/admin/order_cycles/checkout_options.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 31add9af5a..ecc0333c6d 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -55,7 +55,7 @@ %td= t('.shared') %td - if shared_shipping_methods.any? - = f.collection_check_boxes :shipping_method_ids, shared_shipping_methods, :id, :name do |input| + = f.collection_check_boxes :selected_shipping_method_ids, shared_shipping_methods, :id, :name do |input| - shared_shipping_method = input.object %p = input.check_box From da3eea60050fa7b7e3f86c08b2a195fb8174207e Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Sat, 25 Jun 2022 12:44:43 +0100 Subject: [PATCH 33/69] Fetch shared shipping/payment methods for order cycle checkout options in single query --- app/helpers/admin/order_cycles_helper.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/helpers/admin/order_cycles_helper.rb b/app/helpers/admin/order_cycles_helper.rb index c046ded504..6bc3fea258 100644 --- a/app/helpers/admin/order_cycles_helper.rb +++ b/app/helpers/admin/order_cycles_helper.rb @@ -3,15 +3,17 @@ module Admin module OrderCyclesHelper def order_cycle_shared_payment_methods(order_cycle) - order_cycle.attachable_payment_methods.select do |payment_method| - (payment_method.distributor_ids & order_cycle.distributor_ids).many? - end + order_cycle.attachable_payment_methods. + where("distributor_id IN (?)", order_cycle.distributors.select(:id)). + group("spree_payment_methods.id"). + having("COUNT(DISTINCT(distributor_id)) > 1") end def order_cycle_shared_shipping_methods(order_cycle) - order_cycle.attachable_shipping_methods.select do |shipping_method| - (shipping_method.distributor_ids & order_cycle.distributor_ids).many? - end + order_cycle.attachable_shipping_methods. + where("distributor_id IN (?)", order_cycle.distributors.select(:id)). + group("spree_shipping_methods.id"). + having("COUNT(DISTINCT(distributor_id)) > 1") end end end From 61bbf0714efadf929f3fdcc221f7f236d8c4acff Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 29 Jun 2022 11:48:55 +0100 Subject: [PATCH 34/69] Remove flag argument anti-pattern from Shop::OrderCyclesList Co-authored-by: Maikel --- app/services/shop/order_cycles_list.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/services/shop/order_cycles_list.rb b/app/services/shop/order_cycles_list.rb index 3a453aa7ac..e51d3aed5f 100644 --- a/app/services/shop/order_cycles_list.rb +++ b/app/services/shop/order_cycles_list.rb @@ -4,11 +4,13 @@ module Shop class OrderCyclesList def self.active_for(distributor, customer) - new(distributor, customer).call(ready_for_checkout: false) + new(distributor, customer).call end def self.ready_for_checkout_for(distributor, customer) - new(distributor, customer).call(ready_for_checkout: true) + return OrderCycle.none if !distributor.ready_for_checkout? + + new(distributor, customer).call end def initialize(distributor, customer) @@ -16,9 +18,7 @@ module Shop @customer = customer end - def call(ready_for_checkout:) - return OrderCycle.none if ready_for_checkout && !@distributor.ready_for_checkout? - + def call order_cycles = OrderCycle.with_distributor(@distributor).active .order(@distributor.preferred_shopfront_order_cycle_order).to_a From 80b7a5d39ab272045b69080488aa44e7cc893b4c Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 29 Jun 2022 12:50:22 +0100 Subject: [PATCH 35/69] Remove OrderCycleShippingMethod model and use a :has_and_belongs_to_many association instead Before the OrderCycleShippingMethod had a validation which checked the shipping method belonged to the order cycle distributor. Instead of this validation this just ignores shipping methods which don't belong to one of the order cycle's distributors when they are being attached in the OrderCycleForm service. This pattern is already being used in the OrderCycleForm service for ignoring Schedules that the person doesn't own. Co-authored-by: Maikel --- .rubocop_todo.yml | 3 +- app/models/order_cycle.rb | 7 ++-- app/models/order_cycle_shipping_method.rb | 19 ----------- app/models/spree/shipping_method.rb | 1 - app/services/order_cycle_form.rb | 9 +++-- config/locales/en.yml | 4 --- ...2_create_order_cycles_shipping_methods.rb} | 9 +++-- db/schema.rb | 20 +++++------ .../order_cycle_shipping_method_spec.rb | 34 ------------------- spec/services/order_cycle_form_spec.rb | 22 ++++++------ 10 files changed, 35 insertions(+), 93 deletions(-) delete mode 100644 app/models/order_cycle_shipping_method.rb rename db/migrate/{20220429092052_create_order_cycle_shipping_methods.rb => 20220429092052_create_order_cycles_shipping_methods.rb} (57%) delete mode 100644 spec/models/order_cycle_shipping_method_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 72e4621f40..c8e4d81993 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -732,7 +732,7 @@ Rails/FilePath: - 'spec/models/content_configuration_spec.rb' - 'spec/support/downloads_helper.rb' -# Offense count: 11 +# Offense count: 12 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/HasAndBelongsToMany: @@ -740,6 +740,7 @@ Rails/HasAndBelongsToMany: - 'app/models/concerns/payment_method_distributors.rb' - 'app/models/enterprise.rb' - 'app/models/enterprise_group.rb' + - 'app/models/order_cycle.rb' - 'app/models/spree/line_item.rb' - 'app/models/spree/option_value.rb' - 'app/models/spree/role.rb' diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index fa0a5f2292..923dd659f1 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -24,10 +24,9 @@ class OrderCycle < ApplicationRecord has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules - has_many :order_cycle_shipping_methods, dependent: :destroy - has_many :selected_shipping_methods, class_name: "Spree::ShippingMethod", - through: :order_cycle_shipping_methods, - source: :shipping_method + has_and_belongs_to_many :selected_shipping_methods, + class_name: 'Spree::ShippingMethod', + join_table: 'order_cycles_shipping_methods' has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges diff --git a/app/models/order_cycle_shipping_method.rb b/app/models/order_cycle_shipping_method.rb deleted file mode 100644 index 8b8f08d7ef..0000000000 --- a/app/models/order_cycle_shipping_method.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -class OrderCycleShippingMethod < ApplicationRecord - belongs_to :order_cycle - belongs_to :shipping_method, class_name: "Spree::ShippingMethod" - - validate :shipping_method_belongs_to_order_cycle_distributor - validates :shipping_method, uniqueness: { scope: :order_cycle_id } - - private - - def shipping_method_belongs_to_order_cycle_distributor - return if order_cycle.nil? || - shipping_method.nil? || - shipping_method.distributors.where(id: order_cycle.distributor_ids).exists? - - errors.add(:shipping_method, :must_belong_to_order_cycle_distributor) - end -end diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 7d42b4d0af..589bc8ce8c 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -13,7 +13,6 @@ module Spree default_scope -> { where(deleted_at: nil) } - has_many :order_cycle_shipping_methods, dependent: :destroy has_many :shipping_rates, inverse_of: :shipping_method has_many :shipments, through: :shipping_rates has_many :shipping_method_categories diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 926c49fbc3..88280984d5 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -56,6 +56,10 @@ class OrderCycleForm order_cycle.save! end + def attachable_shipping_method_ids + @attachable_shipping_method_ids ||= order_cycle.attachable_shipping_methods.map(&:id) + end + def exchanges_unchanged? [:incoming_exchanges, :outgoing_exchanges].all? do |direction| order_cycle_params[direction].nil? @@ -63,9 +67,10 @@ class OrderCycleForm end def selected_shipping_method_ids - @selected_shipping_method_ids = @selected_shipping_method_ids.reject(&:blank?).map(&:to_i) + @selected_shipping_method_ids = attachable_shipping_method_ids & + @selected_shipping_method_ids.reject(&:blank?).map(&:to_i) - if order_cycle.attachable_shipping_methods.map(&:id).sort == @selected_shipping_method_ids.sort + if attachable_shipping_method_ids.sort == @selected_shipping_method_ids.sort @selected_shipping_method_ids = [] end diff --git a/config/locales/en.yml b/config/locales/en.yml index 014d8dd07f..9d2171d249 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -77,10 +77,6 @@ en: attributes: orders_close_at: after_orders_open_at: must be after open date - order_cycle_shipping_method: - attributes: - shipping_method: - must_belong_to_order_cycle_distributor: "must be from a distributor on the order cycle" variant_override: count_on_hand: using_producer_stock_settings_but_count_on_hand_set: "must be blank because using producer stock settings" diff --git a/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb similarity index 57% rename from db/migrate/20220429092052_create_order_cycle_shipping_methods.rb rename to db/migrate/20220429092052_create_order_cycles_shipping_methods.rb index cc16a501d2..a4596d69d4 100644 --- a/db/migrate/20220429092052_create_order_cycle_shipping_methods.rb +++ b/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb @@ -1,17 +1,16 @@ class CreateOrderCycleShippingMethods < ActiveRecord::Migration[6.1] def up - create_table :order_cycle_shipping_methods do |t| + create_table :order_cycles_shipping_methods, id: false do |t| t.references :order_cycle t.references :shipping_method, foreign_key: { to_table: :spree_shipping_methods } - t.timestamps end - add_index :order_cycle_shipping_methods, + add_index :order_cycles_shipping_methods, [:order_cycle_id, :shipping_method_id], - name: "order_cycle_shipping_methods_join_index", + name: "order_cycles_shipping_methods_join_index", unique: true end def down - drop_table :order_cycle_shipping_methods + drop_table :order_cycles_shipping_methods end end diff --git a/db/schema.rb b/db/schema.rb index 3f866caefd..1ae02469f3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -318,16 +318,6 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.index ["schedule_id"], name: "index_order_cycle_schedules_on_schedule_id" end - create_table "order_cycle_shipping_methods", force: :cascade do |t| - t.bigint "order_cycle_id" - t.bigint "shipping_method_id" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["order_cycle_id", "shipping_method_id"], name: "order_cycle_shipping_methods_join_index", unique: true - t.index ["order_cycle_id"], name: "index_order_cycle_shipping_methods_on_order_cycle_id" - t.index ["shipping_method_id"], name: "index_order_cycle_shipping_methods_on_shipping_method_id" - end - create_table "order_cycles", id: :serial, force: :cascade do |t| t.string "name", limit: 255 t.datetime "orders_open_at" @@ -340,6 +330,14 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.boolean "mails_sent", default: false end + create_table "order_cycles_shipping_methods", id: false, force: :cascade do |t| + t.bigint "order_cycle_id" + t.bigint "shipping_method_id" + t.index ["order_cycle_id", "shipping_method_id"], name: "order_cycles_shipping_methods_join_index", unique: true + t.index ["order_cycle_id"], name: "index_order_cycles_shipping_methods_on_order_cycle_id" + t.index ["shipping_method_id"], name: "index_order_cycles_shipping_methods_on_shipping_method_id" + end + create_table "producer_properties", id: :serial, force: :cascade do |t| t.string "value", limit: 255 t.integer "producer_id" @@ -1263,8 +1261,8 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do add_foreign_key "exchanges", "order_cycles", name: "exchanges_order_cycle_id_fk" add_foreign_key "order_cycle_schedules", "order_cycles", name: "oc_schedules_order_cycle_id_fk" add_foreign_key "order_cycle_schedules", "schedules", name: "oc_schedules_schedule_id_fk" - add_foreign_key "order_cycle_shipping_methods", "spree_shipping_methods", column: "shipping_method_id" add_foreign_key "order_cycles", "enterprises", column: "coordinator_id", name: "order_cycles_coordinator_id_fk" + add_foreign_key "order_cycles_shipping_methods", "spree_shipping_methods", column: "shipping_method_id" add_foreign_key "producer_properties", "enterprises", column: "producer_id", name: "producer_properties_producer_id_fk" add_foreign_key "producer_properties", "spree_properties", column: "property_id", name: "producer_properties_property_id_fk" add_foreign_key "proxy_orders", "order_cycles", name: "proxy_orders_order_cycle_id_fk" diff --git a/spec/models/order_cycle_shipping_method_spec.rb b/spec/models/order_cycle_shipping_method_spec.rb deleted file mode 100644 index 4914313be7..0000000000 --- a/spec/models/order_cycle_shipping_method_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe OrderCycleShippingMethod do - it "is valid if the shipping method belongs to one of the order cycle distributors" do - shipping_method = create(:shipping_method) - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).to be_valid - end - - it "is not valid if the shipping method does not belong to one of the order cycle distributors" do - shipping_method = create(:shipping_method) - enterprise = create(:enterprise) - order_cycle = create(:simple_order_cycle, distributors: [enterprise]) - - order_cycle_shipping_method = OrderCycleShippingMethod.new( - order_cycle: order_cycle, - shipping_method: shipping_method - ) - - expect(order_cycle_shipping_method).not_to be_valid - expect(order_cycle_shipping_method.errors.to_a).to eq [ - "Shipping method must be from a distributor on the order cycle" - ] - end -end diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index fc3a137030..d92f64aeef 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -187,7 +187,8 @@ describe OrderCycleForm do end end - context "updating outgoing exchanges but specifying an invalid shipping method" do + context "updating outgoing exchanges and shipping methods simultaneously but the shipping + method doesn't belong to the new or any existing order cycle distributor" do let(:other_distributor_shipping_method) do create(:shipping_method, distributors: [create(:distributor_enterprise)]) end @@ -199,11 +200,10 @@ describe OrderCycleForm do ) end - it "returns a validation error" do - expect(form.save).to be false - expect(order_cycle.errors.to_a).to eq [ - "Shipping method must be from a distributor on the order cycle" - ] + it "saves the outgoing exchange but ignores the shipping method" do + expect(form.save).to be true + expect(order_cycle.distributors).to eq [distributor] + expect(order_cycle.shipping_methods).to be_empty end end @@ -240,8 +240,8 @@ describe OrderCycleForm do end end - context "and it's invalid" do - it "returns a validation error" do + context "with a shipping method which doesn't belong to one of the order cycle's distributors" do + it "ignores it" do distributor_i = create(:distributor_enterprise) distributor_ii = create(:distributor_enterprise) shipping_method_i = create(:shipping_method, distributors: [distributor_i]) @@ -253,10 +253,8 @@ describe OrderCycleForm do { selected_shipping_method_ids: [shipping_method_ii.id] }, order_cycle.coordinator) - expect(form.save).to be false - expect(order_cycle.errors.to_a).to eq [ - "Shipping method must be from a distributor on the order cycle" - ] + expect(form.save).to be true + expect(order_cycle.shipping_methods).to eq [shipping_method_i] end end end From 94b96e18ede0b23762d4a7667792f0e6abbdb92c Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 29 Jun 2022 13:54:55 +0100 Subject: [PATCH 36/69] Remove OrderCycleForm specs that are no longer necessary These were added for a previous approach which is no longer used. --- spec/services/order_cycle_form_spec.rb | 34 +------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index d92f64aeef..b135f9a7ce 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -147,7 +147,7 @@ describe OrderCycleForm do end context "updating basics, incoming exchanges, outcoming exchanges - and preferred shipping methods simultaneously" do + and shipping methods simultaneously" do before do params.merge!( incoming_exchanges: [{ @@ -172,21 +172,6 @@ describe OrderCycleForm do end end - context "updating outgoing exchanges without specifying any shipping methods" do - before do - params.merge!( - outgoing_exchanges: [outgoing_exchange_params], - selected_shipping_method_ids: nil - ) - end - - it "saves the outgoing exchanges, - it doesn't return a validation error because no shipping methods are present yet" do - expect(form.save).to be true - expect(order_cycle.cached_outgoing_exchanges.count).to eq 1 - end - end - context "updating outgoing exchanges and shipping methods simultaneously but the shipping method doesn't belong to the new or any existing order cycle distributor" do let(:other_distributor_shipping_method) do @@ -207,23 +192,6 @@ describe OrderCycleForm do end end - context "when shipping methods already exist - and doing an update without the :shipping_methods_id parameter" do - it "doesn't return a validation error on shipping methods" do - distributor = create(:distributor_enterprise) - shipping_method = create(:shipping_method, distributors: [distributor]) - order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - - form = OrderCycleForm.new( - order_cycle, - params.except(:selected_shipping_method_ids), - order_cycle.coordinator - ) - - expect(form.save).to be true - end - end - context "updating shipping methods" do context "and it's valid" do it "saves the changes" do From efb1a326b41727afe6e081860d90cac7b16663f1 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 29 Jun 2022 15:23:26 +0100 Subject: [PATCH 37/69] Fix line length in OrderCycleForm spec for Rubocop --- spec/services/order_cycle_form_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index b135f9a7ce..be3143fbc9 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -208,7 +208,7 @@ describe OrderCycleForm do end end - context "with a shipping method which doesn't belong to one of the order cycle's distributors" do + context "with a shipping method which doesn't belong to any distributor on the order cycle" do it "ignores it" do distributor_i = create(:distributor_enterprise) distributor_ii = create(:distributor_enterprise) From af87943fd0d1c77031056b49a6ec22586bfc002c Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 11:23:30 +0100 Subject: [PATCH 38/69] Change 'if !' to 'unless' in OrderCyclesList Co-authored-by: Maikel --- app/services/shop/order_cycles_list.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/shop/order_cycles_list.rb b/app/services/shop/order_cycles_list.rb index e51d3aed5f..af4b9cc63f 100644 --- a/app/services/shop/order_cycles_list.rb +++ b/app/services/shop/order_cycles_list.rb @@ -8,7 +8,7 @@ module Shop end def self.ready_for_checkout_for(distributor, customer) - return OrderCycle.none if !distributor.ready_for_checkout? + return OrderCycle.none unless distributor.ready_for_checkout? new(distributor, customer).call end From 472bd150bb94873df78eedabef52e55ad9276389 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 11:52:33 +0100 Subject: [PATCH 39/69] For consistency with other '' don't strip whitespace. Although both '%em' and '%em>' are rendering the same markup, perhaps because the tag is on it's own new line. Co-authored-by: Maikel --- app/views/admin/order_cycles/checkout_options.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index ecc0333c6d..7552c5f3a5 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -62,7 +62,7 @@ = input.label %p — - %em> + %em = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %td - if shared_payment_methods.any? From ad51b41bb3789dbb5d52b798e01d972e9fdbaed4 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 11:55:26 +0100 Subject: [PATCH 40/69] Use :belongs_to instead of :references in :order_cycles_shipping_methods Also fix migration class name. --- ...29092052_create_order_cycles_shipping_methods.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb index a4596d69d4..7f184a8571 100644 --- a/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb +++ b/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb @@ -1,13 +1,12 @@ -class CreateOrderCycleShippingMethods < ActiveRecord::Migration[6.1] +class CreateOrderCyclesShippingMethods < ActiveRecord::Migration[6.1] def up create_table :order_cycles_shipping_methods, id: false do |t| - t.references :order_cycle - t.references :shipping_method, foreign_key: { to_table: :spree_shipping_methods } + t.belongs_to :order_cycle + t.belongs_to :shipping_method, foreign_key: { to_table: :spree_shipping_methods } + t.index [:order_cycle_id, :shipping_method_id], + name: "order_cycles_shipping_methods_join_index", + unique: true end - add_index :order_cycles_shipping_methods, - [:order_cycle_id, :shipping_method_id], - name: "order_cycles_shipping_methods_join_index", - unique: true end def down From 8b59b7a79622b5cd0ba5a65d546d168b3d5a307a Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 12:07:19 +0100 Subject: [PATCH 41/69] Use if exclude instead of if !include in OrderCycleForm Co-authored-by: Maikel --- app/services/order_cycle_form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 88280984d5..d4334db495 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -39,7 +39,7 @@ class OrderCycleForm def add_exception_to_order_cycle_errors(exception) error = exception.message.split(":").last.strip - order_cycle.errors.add(:base, error) if !order_cycle.errors.to_a.include?(error) + order_cycle.errors.add(:base, error) if order_cycle.errors.to_a.exclude?(error) end def apply_exchange_changes From 464a9b95a4911427ecf5b24e70ffec499c6d1356 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 12:40:31 +0100 Subject: [PATCH 42/69] On order cycle checkout options shipping methods shared between distributors should be checked by default too --- .../order_cycles/checkout_options.html.haml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 7552c5f3a5..6612ba85c7 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -54,16 +54,17 @@ %tr %td= t('.shared') %td - - if shared_shipping_methods.any? - = f.collection_check_boxes :selected_shipping_method_ids, shared_shipping_methods, :id, :name do |input| - - shared_shipping_method = input.object - %p - = input.check_box - = input.label - %p - — - %em - = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") + - shared_shipping_methods.each do |shared_shipping_method| + %p + %label + = check_box_tag "order_cycle[selected_shipping_method_ids][]", + shared_shipping_method.id, @order_cycle.shipping_methods.include?(shared_shipping_method), + id: "order_cycle_selected_shipping_method_ids_#{shared_shipping_method.id}" + = shared_shipping_method.name + %p + — + %em + = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %td - if shared_payment_methods.any? %ul From 1a70cc0c4c35e2c0b1ad1394737ff2eeba7318cf Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 12:41:02 +0100 Subject: [PATCH 43/69] Don't display no shipping/payment method warnings on order cycle checkout options if the distributor has some that are shared with other distributors --- app/views/admin/order_cycles/checkout_options.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 6612ba85c7..9f15616ae6 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -39,11 +39,11 @@ = check_box_tag nil, nil, false, disabled: true = shipping_method.name = "(#{t('.back_end')})" - - if shipping_methods.none? && distributor.shipping_methods.backend.none? + - if distributor.shipping_methods.frontend.none? %p.text-center = t('.no_shipping_methods') %td - - if payment_methods.any? + - if distributor.payment_methods.available(:both).any? %ul - payment_methods.each do |payment_method| %li= payment_method.name From 8cb2767f9a79de82bf1e0fea3077f267e43a0d6d Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Thu, 30 Jun 2022 12:41:35 +0100 Subject: [PATCH 44/69] The no shipping/payment method warnings on order cycle checkout options looks better left aligned --- app/views/admin/order_cycles/checkout_options.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 9f15616ae6..9b87b807c0 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -40,7 +40,7 @@ = shipping_method.name = "(#{t('.back_end')})" - if distributor.shipping_methods.frontend.none? - %p.text-center + %p = t('.no_shipping_methods') %td - if distributor.payment_methods.available(:both).any? @@ -48,7 +48,7 @@ - payment_methods.each do |payment_method| %li= payment_method.name - else - %p.text-center + %p = t('.no_payment_methods') - if shared_payment_methods.any? || shared_shipping_methods.any? %tr From 0673f9a5ae968f68e1ae83b6125ebae8a5337e1b Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 15 Jul 2022 15:42:19 +0100 Subject: [PATCH 45/69] Use a more simple layout on the order cycle checkout options form Before there was a row for each distributor and a 'shared' row for shipping methods which were shared among more than one distributor. This layout displays a single list of shipping methods with the distributor or distributors it belongs to beside it as suggested by @lin-d-hop --- app/helpers/admin/order_cycles_helper.rb | 21 ++-- app/models/spree/shipping_method.rb | 4 + .../order_cycles/checkout_options.html.haml | 114 ++++++++---------- .../controllers/select_all_controller.js | 15 +++ config/locales/en.yml | 2 - .../stimulus/select_all_controller_test.js | 82 +++++++++++++ 6 files changed, 165 insertions(+), 73 deletions(-) create mode 100644 app/webpacker/controllers/select_all_controller.js create mode 100644 spec/javascripts/stimulus/select_all_controller_test.js diff --git a/app/helpers/admin/order_cycles_helper.rb b/app/helpers/admin/order_cycles_helper.rb index 6bc3fea258..9ce47e6f4c 100644 --- a/app/helpers/admin/order_cycles_helper.rb +++ b/app/helpers/admin/order_cycles_helper.rb @@ -2,18 +2,19 @@ module Admin module OrderCyclesHelper - def order_cycle_shared_payment_methods(order_cycle) - order_cycle.attachable_payment_methods. - where("distributor_id IN (?)", order_cycle.distributors.select(:id)). - group("spree_payment_methods.id"). - having("COUNT(DISTINCT(distributor_id)) > 1") + def order_cycle_distributors_payment_methods(order_cycle) + Spree::PaymentMethod. + joins(:distributors). + includes(:distributors). + available(:both). + where("distributor_id IN (?)", order_cycle.distributors.select(:id)) end - def order_cycle_shared_shipping_methods(order_cycle) - order_cycle.attachable_shipping_methods. - where("distributor_id IN (?)", order_cycle.distributors.select(:id)). - group("spree_shipping_methods.id"). - having("COUNT(DISTINCT(distributor_id)) > 1") + def order_cycle_distributors_shipping_methods(order_cycle) + Spree::ShippingMethod. + joins(:distributors). + includes(:distributors). + where("distributor_id IN (?)", order_cycle.distributors.select(:id)) end end end diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 589bc8ce8c..44cac5eab0 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -71,6 +71,10 @@ module Spree spree_calculators.__send__ model_name_without_spree_namespace end + def backend? + !frontend? + end + # Some shipping methods are only meant to be set via backend def frontend? display_on != "back_end" diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 9b87b807c0..dd5841a020 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -3,8 +3,8 @@ - content_for :page_title do = t :edit_order_cycle -- shared_payment_methods = order_cycle_shared_payment_methods(@order_cycle) -- shared_shipping_methods = order_cycle_shared_shipping_methods(@order_cycle) +- payment_methods = order_cycle_distributors_payment_methods(@order_cycle) +- shipping_methods = order_cycle_distributors_shipping_methods(@order_cycle) = form_for [main_app, :admin, @order_cycle], html: { class: "order_cycle" } do |f| @@ -15,66 +15,58 @@ = hidden_field_tag "order_cycle[selected_shipping_method_ids][]", "" - %table.checkout-options - %thead - %tr - %th= t('.distributor') - %th= t('.shipping_methods') - %th= t('.payment_methods') - - @order_cycle.distributors.each do |distributor| - - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) - shared_payment_methods - - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) - shared_shipping_methods - %tr - %td= distributor.name - %td - - shipping_methods.each do |shipping_method| - %p - %label - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" - = shipping_method.name - - distributor.shipping_methods.backend.each do |shipping_method| - %label.disabled - = check_box_tag nil, nil, false, disabled: true - = shipping_method.name - = "(#{t('.back_end')})" - - if distributor.shipping_methods.frontend.none? - %p - = t('.no_shipping_methods') - %td - - if distributor.payment_methods.available(:both).any? - %ul - - payment_methods.each do |payment_method| - %li= payment_method.name - - else - %p - = t('.no_payment_methods') - - if shared_payment_methods.any? || shared_shipping_methods.any? - %tr - %td= t('.shared') - %td - - shared_shipping_methods.each do |shared_shipping_method| - %p - %label - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shared_shipping_method.id, @order_cycle.shipping_methods.include?(shared_shipping_method), - id: "order_cycle_selected_shipping_method_ids_#{shared_shipping_method.id}" - = shared_shipping_method.name - %p - — - %em - = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") - %td - - if shared_payment_methods.any? - %ul - - shared_payment_methods.each do |shared_payment_method| - %li - = shared_payment_method.name - %p + .row + .three.columns +   + .ten.columns + %table.checkout-options + %thead + %tr + %th{ colspan: 2 }= t('.shipping_methods') + %tr{ "data-controller": "select-all" } + %td.text-center + %label + = check_box_tag "bla", nil, shipping_methods == @order_cycle.shipping_methods, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } + = "Select all" + %td + - if shipping_methods.any? + - shipping_methods.each do |shipping_method| + %p + %label{ class: ("disabled" if shipping_method.backend?) } + = check_box_tag "order_cycle[selected_shipping_method_ids][]", + shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), + disabled: shipping_method.backend?, + id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}", + data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } unless shipping_method.backend?) + = shipping_method.name + - if shipping_method.backend? + = "(#{t('.back_end')})" + - if @order_cycle.distributors.many? — - %em - = shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") + %small + %em + = shipping_method.distributors.map(&:name).join(", ") + - else + %p + = t('.no_shipping_methods') + %tr + %th{ colspan: 2 }= t('.payment_methods') + %tr + %td + %td + - if payment_methods.any? + %ul + - payment_methods.each do |payment_method| + %li + = payment_method.name + - if @order_cycle.distributors.many? + — + %small + %em + = payment_method.distributors.map(&:name).join(", ") + - else + %p + = t('.no_payment_methods') %div#save-bar %div.container diff --git a/app/webpacker/controllers/select_all_controller.js b/app/webpacker/controllers/select_all_controller.js new file mode 100644 index 0000000000..84c1cc2e63 --- /dev/null +++ b/app/webpacker/controllers/select_all_controller.js @@ -0,0 +1,15 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["all", "checkbox"]; + + toggleAll() { + this.checkboxTargets.forEach(checkbox => { + checkbox.checked = this.allTarget.checked; + }); + } + + toggleCheckbox() { + this.allTarget.checked = this.checkboxTargets.every(checkbox => checkbox.checked); + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 9d2171d249..bc9be69a68 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1166,13 +1166,11 @@ en: back_end: "Back office only" cancel: "Cancel" checkout_options: "Checkout options" - distributor: "Distributor" no_payment_methods: Each distributor on this order cycle requires at least one payment method. no_shipping_methods: Each distributor on this order cycle requires at least one shipping method. payment_methods: "Payment Methods" save: "Save" save_and_back_to_list: "Save and Back to List" - shared: "Shared" shipping_methods: "Shipping Methods" wizard_progress: edit: "1. General Settings" diff --git a/spec/javascripts/stimulus/select_all_controller_test.js b/spec/javascripts/stimulus/select_all_controller_test.js new file mode 100644 index 0000000000..01731cb238 --- /dev/null +++ b/spec/javascripts/stimulus/select_all_controller_test.js @@ -0,0 +1,82 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import select_all_controller from "../../../app/webpacker/controllers/select_all_controller"; + +describe("SelectAllController", () => { + beforeAll(() => { + const application = Application.start(); + application.register("select-all", select_all_controller); + }); + + beforeEach(() => { + document.body.innerHTML = ` +
+ + + +
+ `; + }); + + describe("#toggleAll", () => { + it("checks all checkboxes when it's checked and unchecks them all when unchecked", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + const checkboxA = document.getElementById("checkboxA"); + const checkboxB = document.getElementById("checkboxB"); + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(false); + expect(checkboxB.checked).toBe(false); + + selectAllCheckbox.click() + + expect(selectAllCheckbox.checked).toBe(true); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(true); + + selectAllCheckbox.click() + + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(false); + expect(checkboxB.checked).toBe(false); + }); + }); + + describe("#toggleCheckbox", () => { + it("checks the individual checkbox and checks the select all checkbox if all checkboxes are checked and vice versa", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + const checkboxA = document.getElementById("checkboxA"); + const checkboxB = document.getElementById("checkboxB"); + checkboxA.click() + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(false); + + checkboxB.click() + + expect(selectAllCheckbox.checked).toBe(true); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(true); + + checkboxB.click() + + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(false); + }); + }); +}); From 87431c188ed5c79677c9d353c2f58eb4e70e7dea Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 15 Jul 2022 15:51:23 +0100 Subject: [PATCH 46/69] Use ShippingMethod :frontend method and drop :backend to avoid ClassLength Rubocop error --- app/models/spree/shipping_method.rb | 4 ---- app/views/admin/order_cycles/checkout_options.html.haml | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/models/spree/shipping_method.rb b/app/models/spree/shipping_method.rb index 44cac5eab0..589bc8ce8c 100644 --- a/app/models/spree/shipping_method.rb +++ b/app/models/spree/shipping_method.rb @@ -71,10 +71,6 @@ module Spree spree_calculators.__send__ model_name_without_spree_namespace end - def backend? - !frontend? - end - # Some shipping methods are only meant to be set via backend def frontend? display_on != "back_end" diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index dd5841a020..d95b231187 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -32,14 +32,14 @@ - if shipping_methods.any? - shipping_methods.each do |shipping_method| %p - %label{ class: ("disabled" if shipping_method.backend?) } + %label{ class: ("disabled" unless shipping_method.frontend?) } = check_box_tag "order_cycle[selected_shipping_method_ids][]", shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - disabled: shipping_method.backend?, + disabled: !shipping_method.frontend?, id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}", - data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } unless shipping_method.backend?) + data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if shipping_method.frontend?) = shipping_method.name - - if shipping_method.backend? + - unless shipping_method.frontend? = "(#{t('.back_end')})" - if @order_cycle.distributors.many? — From da2ef3fae2238f30c21e80d9876316ec7622c504 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 12:38:08 +1000 Subject: [PATCH 47/69] Give order cycle spec some structure --- .../complex_creating_specific_time_spec.rb | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index ad279fd799..f632d69ee9 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -13,18 +13,24 @@ describe ' let(:order_cycle_opening_time) { 1.day.from_now(Time.zone.now) } let(:order_cycle_closing_time) { 2.days.from_now(Time.zone.now) } - it "creating an order cycle with full interface", js: true do - # Given coordinating, supplying and distributing enterprises with some products with variants - coordinator = create(:distributor_enterprise, name: 'My coordinator') - supplier = create(:supplier_enterprise, name: 'My supplier') - product = create(:product, supplier: supplier) - v1 = create(:variant, product: product) - v2 = create(:variant, product: product) - distributor = create(:distributor_enterprise, name: 'My distributor', - with_payment_and_shipping: true) - shipping_method_i = distributor.shipping_methods.first - shipping_method_ii = create(:shipping_method, distributors: [distributor]) + # Given coordinating, supplying and distributing enterprises with some products with variants + let!(:coordinator) { create(:distributor_enterprise, name: 'My coordinator') } + let!(:supplier) { create(:supplier_enterprise, name: 'My supplier') } + let!(:product) { create(:product, supplier: supplier) } + let!(:v1) { create(:variant, product: product) } + let!(:v2) { create(:variant, product: product) } + let!(:distributor) { + create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true) + } + let!(:shipping_method_i) { distributor.shipping_methods.first } + let!(:shipping_method_ii) { create(:shipping_method, distributors: [distributor]) } + # And some enterprise fees + let!(:supplier_fee) { create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') } + let!(:coordinator_fee) { create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee') } + let!(:distributor_fee) { create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') } + + before do # Relationships required for interface to work create(:enterprise_relationship, parent: supplier, child: coordinator, permissions_list: [:add_to_order_cycle]) @@ -32,13 +38,10 @@ describe ' permissions_list: [:add_to_order_cycle]) create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) + end - # And some enterprise fees - supplier_fee = create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') - coordinator_fee = create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee') - distributor_fee = create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') - - # When I go to the new order cycle page + it "creating an order cycle with full interface", js: true do + ## CREATE login_as_admin_and_visit admin_order_cycles_path click_link 'New Order Cycle' @@ -46,13 +49,33 @@ describe ' select2_select 'My coordinator', from: 'coordinator_id' click_button "Continue >" + fill_in_order_cycle_name + select_opening_and_closing_times + + click_button 'Add coordinator fee' + select 'Coord fee', from: 'order_cycle_coordinator_fee_0_id' + + click_button 'Create' + expect(page).to have_content 'Your order cycle has been created.' + + ## UPDATE + add_supplier_with_fees + add_distributor_with_fees + select_shipping_methods + + expect_all_data_saved + end + + def fill_in_order_cycle_name # I cannot save before filling in the required fields expect(page).to have_button("Create", disabled: true) # The Create button is enabled once Name is entered - fill_in 'order_cycle_name', with: 'Plums & Avos' + fill_in 'order_cycle_name', with: "Plums & Avos" expect(page).to have_button("Create", disabled: false) + end + def select_opening_and_closing_times # If I fill in the basic fields find('#order_cycle_orders_open_at').click # select date @@ -75,14 +98,9 @@ describe ' end # hide the datetimepicker find("body").send_keys(:escape) + end - # And I add a coordinator fee - click_button 'Add coordinator fee' - select 'Coord fee', from: 'order_cycle_coordinator_fee_0_id' - - click_button 'Create' - expect(page).to have_content 'Your order cycle has been created.' - + def add_supplier_with_fees # I should not be able to add a blank supplier expect(page).to have_select 'new_supplier_id', selected: '' expect(page).to have_button 'Add supplier', disabled: true @@ -107,7 +125,9 @@ describe ' from: 'order_cycle_incoming_exchange_0_enterprise_fees_0_enterprise_fee_id' click_button 'Save and Next' + end + def add_distributor_with_fees # And I add a distributor with the same products select 'My distributor', from: 'new_distributor_id' click_button 'Add distributor' @@ -132,13 +152,16 @@ describe ' from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_fee_id' click_button 'Save and Next' + end - # And I select preferred shipping methods + def select_shipping_methods check "order_cycle_selected_shipping_method_ids_#{shipping_method_i.id}" uncheck "order_cycle_selected_shipping_method_ids_#{shipping_method_ii.id}" click_button 'Save and Back to List' + end + def expect_all_data_saved oc = OrderCycle.last toggle_columns "Producers", "Shops" From 758030e81a9d83a4d79470a2b02335aec392b656 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 13:03:01 +1000 Subject: [PATCH 48/69] Test with readable labels instead of ids --- .../complex_creating_specific_time_spec.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index f632d69ee9..f70bbb7d61 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -38,6 +38,9 @@ describe ' permissions_list: [:add_to_order_cycle]) create(:enterprise_relationship, parent: supplier, child: distributor, permissions_list: [:add_to_order_cycle]) + + shipping_method_i.update!(name: "Pickup - always available") + shipping_method_ii.update!(name: "Delivery - sometimes available") end it "creating an order cycle with full interface", js: true do @@ -155,8 +158,10 @@ describe ' end def select_shipping_methods - check "order_cycle_selected_shipping_method_ids_#{shipping_method_i.id}" - uncheck "order_cycle_selected_shipping_method_ids_#{shipping_method_ii.id}" + expect(page).to have_checked_field "Pickup - always available" + expect(page).to have_checked_field "Delivery - sometimes available" + + uncheck "Delivery - sometimes available" click_button 'Save and Back to List' end @@ -194,6 +199,6 @@ describe ' expect(exchange.tag_list).to eq(['wholesale']) # And the shipping method should be attached - expect(oc.shipping_methods).to eq([shipping_method_i]) + expect(oc.shipping_methods.map(&:name)).to eq(["Pickup - always available"]) end end From c5bb2f52e14dffe6d84533a6de2cb81d4b752155 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 13:05:47 +1000 Subject: [PATCH 49/69] Check "Select all" to start with Reflect the state of the shipping method checkboxes. --- app/views/admin/order_cycles/checkout_options.html.haml | 2 +- .../admin/order_cycles/complex_creating_specific_time_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index d95b231187..345f950ab2 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -26,7 +26,7 @@ %tr{ "data-controller": "select-all" } %td.text-center %label - = check_box_tag "bla", nil, shipping_methods == @order_cycle.shipping_methods, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } + = check_box_tag "bla", nil, (shipping_methods - @order_cycle.shipping_methods).empty?, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } = "Select all" %td - if shipping_methods.any? diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index f70bbb7d61..3b9942890d 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -158,11 +158,15 @@ describe ' end def select_shipping_methods + expect(page).to have_checked_field "Select all" + expect(page).to have_checked_field "Pickup - always available" expect(page).to have_checked_field "Delivery - sometimes available" uncheck "Delivery - sometimes available" + expect(page).to have_unchecked_field "Select all" + click_button 'Save and Back to List' end From 3518d1ba92970af069ebd23790a734a56d6668b3 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 14:37:05 +1000 Subject: [PATCH 50/69] Spec "Select all" shipping methods field --- .../complex_creating_specific_time_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 3b9942890d..e745b9831e 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -167,6 +167,19 @@ describe ' expect(page).to have_unchecked_field "Select all" + # Now test that the "Select all" input is doing what it's supposed to: + check "Select all" + + expect(page).to have_checked_field "Pickup - always available" + expect(page).to have_checked_field "Delivery - sometimes available" + + uncheck "Select all" + + expect(page).to have_unchecked_field "Pickup - always available" + expect(page).to have_unchecked_field "Delivery - sometimes available" + + # Our final selection: + check "Pickup - always available" click_button 'Save and Back to List' end From 26d5ea6cdc476794571e1416b1ea092967a177af Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 14:37:46 +1000 Subject: [PATCH 51/69] Remove unused input name --- app/views/admin/order_cycles/checkout_options.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 345f950ab2..e8823d4148 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -26,7 +26,7 @@ %tr{ "data-controller": "select-all" } %td.text-center %label - = check_box_tag "bla", nil, (shipping_methods - @order_cycle.shipping_methods).empty?, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } + = check_box_tag nil, nil, (shipping_methods - @order_cycle.shipping_methods).empty?, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } = "Select all" %td - if shipping_methods.any? From 195f1e12376f7e26b4c11b40cfdee1737840e37e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 14:39:43 +1000 Subject: [PATCH 52/69] Make "Select all" translatable --- app/views/admin/order_cycles/checkout_options.html.haml | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index e8823d4148..c298bd86c2 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -27,7 +27,7 @@ %td.text-center %label = check_box_tag nil, nil, (shipping_methods - @order_cycle.shipping_methods).empty?, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } - = "Select all" + = t(".select_all") %td - if shipping_methods.any? - shipping_methods.each do |shipping_method| diff --git a/config/locales/en.yml b/config/locales/en.yml index bc9be69a68..0739d192c9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1171,6 +1171,7 @@ en: payment_methods: "Payment Methods" save: "Save" save_and_back_to_list: "Save and Back to List" + select_all: "Select all" shipping_methods: "Shipping Methods" wizard_progress: edit: "1. General Settings" From 512394862b6ebfa0a3d7ca2cfbc8cb8b8822b992 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 19 Jul 2022 14:53:04 +1000 Subject: [PATCH 53/69] Wait for input field before filling it The spec was failing from time to time. I hope that this will fix it. ``` Failures: 1) As an administrator I want to create/update complex order cycles with a specific time creating an order cycle with full interface Failure/Error: fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' Capybara::ElementNotFound: Unable to find field "order_cycle_outgoing_exchange_0_pickup_time" that is not disabled # ./spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb:138:in `add_distributor_with_fees' # ./spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb:66:in `block (2 levels) in
' # ./spec/system/support/cuprite_setup.rb:39:in `block (2 levels) in
' # -e:1:in `
' ``` --- .../admin/order_cycles/complex_creating_specific_time_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index e745b9831e..5c41022952 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -135,6 +135,7 @@ describe ' select 'My distributor', from: 'new_distributor_id' click_button 'Add distributor' + expect(page).to have_field "order_cycle_outgoing_exchange_0_pickup_time" fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' From 8e47949260ebc5e1340f96bd5c379ab192d3b0d0 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 29 Jul 2022 10:24:09 +0100 Subject: [PATCH 54/69] Check 'Select all' checkboxes on page load if all its checkboxes are checked This means the code to set the initial value in the view template isn't needed. --- .../order_cycles/checkout_options.html.haml | 2 +- .../controllers/select_all_controller.js | 4 +++ .../stimulus/select_all_controller_test.js | 31 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index c298bd86c2..c3380c444a 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -26,7 +26,7 @@ %tr{ "data-controller": "select-all" } %td.text-center %label - = check_box_tag nil, nil, (shipping_methods - @order_cycle.shipping_methods).empty?, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } + = check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } = t(".select_all") %td - if shipping_methods.any? diff --git a/app/webpacker/controllers/select_all_controller.js b/app/webpacker/controllers/select_all_controller.js index 84c1cc2e63..30a00aee79 100644 --- a/app/webpacker/controllers/select_all_controller.js +++ b/app/webpacker/controllers/select_all_controller.js @@ -3,6 +3,10 @@ import { Controller } from "stimulus"; export default class extends Controller { static targets = ["all", "checkbox"]; + connect() { + this.toggleCheckbox() + } + toggleAll() { this.checkboxTargets.forEach(checkbox => { checkbox.checked = this.allTarget.checked; diff --git a/spec/javascripts/stimulus/select_all_controller_test.js b/spec/javascripts/stimulus/select_all_controller_test.js index 01731cb238..f7a17b884f 100644 --- a/spec/javascripts/stimulus/select_all_controller_test.js +++ b/spec/javascripts/stimulus/select_all_controller_test.js @@ -79,4 +79,35 @@ describe("SelectAllController", () => { expect(checkboxB.checked).toBe(false); }); }); + + describe("#connect", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+ + + +
+ `; + }); + + it("checks the select all checkbox on page load if all checkboxes are checked", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + expect(selectAllCheckbox.checked).toBe(true); + }); + }); }); From 32d16eacd2bd45afbae8d4956d1e286ed07aaca7 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 29 Jul 2022 10:58:29 +0100 Subject: [PATCH 55/69] Fix Rubocop Metrics/AbcSize and Layout/LineLength errors in complex_creating_specific_time_spec.rb Not sure if this is correct but it removes the Rubocop violations. --- .../complex_creating_specific_time_spec.rb | 120 +++++++++++------- 1 file changed, 71 insertions(+), 49 deletions(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 5c41022952..25ee70aed4 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -28,7 +28,9 @@ describe ' # And some enterprise fees let!(:supplier_fee) { create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') } let!(:coordinator_fee) { create(:enterprise_fee, enterprise: coordinator, name: 'Coord fee') } - let!(:distributor_fee) { create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') } + let!(:distributor_fee) do + create(:enterprise_fee, enterprise: distributor, name: 'Distributor fee') + end before do # Relationships required for interface to work @@ -79,34 +81,26 @@ describe ' end def select_opening_and_closing_times - # If I fill in the basic fields - find('#order_cycle_orders_open_at').click - # select date - select_date_from_datepicker Time.zone.at(order_cycle_opening_time) - # select time - within(".flatpickr-calendar.open .flatpickr-time") do - find('.flatpickr-hour').set('%02d' % order_cycle_opening_time.hour) - find('.flatpickr-minute').set('%02d' % order_cycle_opening_time.min) - end - # hide the datetimepicker - find("body").send_keys(:escape) + select_time("#order_cycle_orders_open_at", order_cycle_opening_time) + select_time("#order_cycle_orders_close_at", order_cycle_opening_time) + end - find('#order_cycle_orders_close_at').click + def select_time(selector, time) + # If I fill in the basic fields + find(selector).click # select date - select_date_from_datepicker Time.zone.at(order_cycle_closing_time) + select_date_from_datepicker Time.zone.at(time) # select time within(".flatpickr-calendar.open .flatpickr-time") do - find('.flatpickr-hour').set('%02d' % order_cycle_closing_time.hour) - find('.flatpickr-minute').set('%02d' % order_cycle_closing_time.min) + find('.flatpickr-hour').set('%02d' % time.hour) + find('.flatpickr-minute').set('%02d' % time.min) end # hide the datetimepicker find("body").send_keys(:escape) end def add_supplier_with_fees - # I should not be able to add a blank supplier - expect(page).to have_select 'new_supplier_id', selected: '' - expect(page).to have_button 'Add supplier', disabled: true + expect_not_able_to_add_blank_supplier # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' @@ -116,10 +110,7 @@ describe ' check "order_cycle_incoming_exchange_0_variants_#{v1.id}" check "order_cycle_incoming_exchange_0_variants_#{v2.id}" - # I should not be able to re-add the supplier - expect(page).not_to have_select 'new_supplier_id', with_options: ['My supplier'] - expect(page).to have_button 'Add supplier', disabled: true - expect(page.all("td.supplier_name").map(&:text)).to eq(['My supplier']) + expect_not_able_to_readd_supplier('My supplier') # And I add a supplier fee within("tr.supplier-#{supplier.id}") { click_button 'Add fee' } @@ -130,6 +121,17 @@ describe ' click_button 'Save and Next' end + def expect_not_able_to_add_blank_supplier + expect(page).to have_select 'new_supplier_id', selected: '' + expect(page).to have_button 'Add supplier', disabled: true + end + + def expect_not_able_to_readd_supplier(supplier_name) + expect(page).not_to have_select 'new_supplier_id', with_options: [supplier_name] + expect(page).to have_button 'Add supplier', disabled: true + expect(page.all("td.supplier_name").map(&:text)).to eq([supplier_name]) + end + def add_distributor_with_fees # And I add a distributor with the same products select 'My distributor', from: 'new_distributor_id' @@ -168,20 +170,27 @@ describe ' expect(page).to have_unchecked_field "Select all" + expect_checking_select_all_shipping_methods_works + expect_unchecking_select_all_shipping_methods_works + + # Our final selection: + check "Pickup - always available" + click_button 'Save and Back to List' + end + + def expect_checking_select_all_shipping_methods_works # Now test that the "Select all" input is doing what it's supposed to: check "Select all" expect(page).to have_checked_field "Pickup - always available" expect(page).to have_checked_field "Delivery - sometimes available" + end + def expect_unchecking_select_all_shipping_methods_works uncheck "Select all" expect(page).to have_unchecked_field "Pickup - always available" expect(page).to have_unchecked_field "Delivery - sometimes available" - - # Our final selection: - check "Pickup - always available" - click_button 'Save and Back to List' end def expect_all_data_saved @@ -189,34 +198,47 @@ describe ' toggle_columns "Producers", "Shops" expect(page).to have_input "oc#{oc.id}[name]", value: "Plums & Avos" - expect(page).to have_input "oc#{oc.id}[orders_open_at]", - value: Time.zone.at(order_cycle_opening_time), visible: false - expect(page).to have_input "oc#{oc.id}[orders_close_at]", - value: Time.zone.at(order_cycle_closing_time), visible: false expect(page).to have_content "My coordinator" - + expect_opening_and_closing_times_saved expect(page).to have_selector 'td.producers', text: 'My supplier' expect(page).to have_selector 'td.shops', text: 'My distributor' - # And it should have some fees - expect(oc.exchanges.incoming.first.enterprise_fees).to eq([supplier_fee]) - expect(oc.coordinator_fees).to eq([coordinator_fee]) - expect(oc.exchanges.outgoing.first.enterprise_fees).to eq([distributor_fee]) - - # And it should have some variants selected - expect(oc.exchanges.first.variants.count).to eq(2) - expect(oc.exchanges.last.variants.count).to eq(2) - - # And my receival and pickup time and instructions should have been saved - exchange = oc.exchanges.incoming.first - expect(exchange.receival_instructions).to eq('receival instructions') - - exchange = oc.exchanges.outgoing.first - expect(exchange.pickup_time).to eq('pickup time') - expect(exchange.pickup_instructions).to eq('pickup instructions') - expect(exchange.tag_list).to eq(['wholesale']) + expect_fees_saved + expect_variants_saved + expect_receival_instructions_saved + expect_pickup_time_and_instructions_saved # And the shipping method should be attached expect(oc.shipping_methods.map(&:name)).to eq(["Pickup - always available"]) end + + def expect_opening_and_closing_times_saved + expect(page).to have_input "oc#{oc.id}[orders_open_at]", + value: Time.zone.at(order_cycle_opening_time), visible: false + expect(page).to have_input "oc#{oc.id}[orders_close_at]", + value: Time.zone.at(order_cycle_closing_time), visible: false + end + + def expect_fees_saved + expect(oc.exchanges.incoming.first.enterprise_fees).to eq([supplier_fee]) + expect(oc.coordinator_fees).to eq([coordinator_fee]) + expect(oc.exchanges.outgoing.first.enterprise_fees).to eq([distributor_fee]) + end + + def expect_variants_saved + expect(oc.exchanges.first.variants.count).to eq(2) + expect(oc.exchanges.last.variants.count).to eq(2) + end + + def expect_receival_instructions_saved + exchange = oc.exchanges.incoming.first + expect(exchange.receival_instructions).to eq('receival instructions') + end + + def expect_pickup_time_and_instructions_saved + exchange = oc.exchanges.outgoing.first + expect(exchange.pickup_time).to eq('pickup time') + expect(exchange.pickup_instructions).to eq('pickup instructions') + expect(exchange.tag_list).to eq(['wholesale']) + end end From 4b0b4ad991127d7cc787d1b936c09585b6fb9113 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 10:31:41 +0100 Subject: [PATCH 56/69] Revert "Use a more simple layout on the order cycle checkout options form" This reverts commit 0dddaa6c2b9f1925adca6ff8deac8181691ae119. --- app/helpers/admin/order_cycles_helper.rb | 21 ++-- .../order_cycles/checkout_options.html.haml | 114 ++++++++++-------- .../controllers/select_all_controller.js | 19 --- config/locales/en.yml | 1 + .../stimulus/select_all_controller_test.js | 113 ----------------- 5 files changed, 72 insertions(+), 196 deletions(-) delete mode 100644 app/webpacker/controllers/select_all_controller.js delete mode 100644 spec/javascripts/stimulus/select_all_controller_test.js diff --git a/app/helpers/admin/order_cycles_helper.rb b/app/helpers/admin/order_cycles_helper.rb index 9ce47e6f4c..6bc3fea258 100644 --- a/app/helpers/admin/order_cycles_helper.rb +++ b/app/helpers/admin/order_cycles_helper.rb @@ -2,19 +2,18 @@ module Admin module OrderCyclesHelper - def order_cycle_distributors_payment_methods(order_cycle) - Spree::PaymentMethod. - joins(:distributors). - includes(:distributors). - available(:both). - where("distributor_id IN (?)", order_cycle.distributors.select(:id)) + def order_cycle_shared_payment_methods(order_cycle) + order_cycle.attachable_payment_methods. + where("distributor_id IN (?)", order_cycle.distributors.select(:id)). + group("spree_payment_methods.id"). + having("COUNT(DISTINCT(distributor_id)) > 1") end - def order_cycle_distributors_shipping_methods(order_cycle) - Spree::ShippingMethod. - joins(:distributors). - includes(:distributors). - where("distributor_id IN (?)", order_cycle.distributors.select(:id)) + def order_cycle_shared_shipping_methods(order_cycle) + order_cycle.attachable_shipping_methods. + where("distributor_id IN (?)", order_cycle.distributors.select(:id)). + group("spree_shipping_methods.id"). + having("COUNT(DISTINCT(distributor_id)) > 1") end end end diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index c3380c444a..9b87b807c0 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -3,8 +3,8 @@ - content_for :page_title do = t :edit_order_cycle -- payment_methods = order_cycle_distributors_payment_methods(@order_cycle) -- shipping_methods = order_cycle_distributors_shipping_methods(@order_cycle) +- shared_payment_methods = order_cycle_shared_payment_methods(@order_cycle) +- shared_shipping_methods = order_cycle_shared_shipping_methods(@order_cycle) = form_for [main_app, :admin, @order_cycle], html: { class: "order_cycle" } do |f| @@ -15,58 +15,66 @@ = hidden_field_tag "order_cycle[selected_shipping_method_ids][]", "" - .row - .three.columns -   - .ten.columns - %table.checkout-options - %thead - %tr - %th{ colspan: 2 }= t('.shipping_methods') - %tr{ "data-controller": "select-all" } - %td.text-center - %label - = check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } - = t(".select_all") - %td - - if shipping_methods.any? - - shipping_methods.each do |shipping_method| - %p - %label{ class: ("disabled" unless shipping_method.frontend?) } - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - disabled: !shipping_method.frontend?, - id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}", - data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if shipping_method.frontend?) - = shipping_method.name - - unless shipping_method.frontend? - = "(#{t('.back_end')})" - - if @order_cycle.distributors.many? + %table.checkout-options + %thead + %tr + %th= t('.distributor') + %th= t('.shipping_methods') + %th= t('.payment_methods') + - @order_cycle.distributors.each do |distributor| + - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) - shared_payment_methods + - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) - shared_shipping_methods + %tr + %td= distributor.name + %td + - shipping_methods.each do |shipping_method| + %p + %label + = check_box_tag "order_cycle[selected_shipping_method_ids][]", + shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), + id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" + = shipping_method.name + - distributor.shipping_methods.backend.each do |shipping_method| + %label.disabled + = check_box_tag nil, nil, false, disabled: true + = shipping_method.name + = "(#{t('.back_end')})" + - if distributor.shipping_methods.frontend.none? + %p + = t('.no_shipping_methods') + %td + - if distributor.payment_methods.available(:both).any? + %ul + - payment_methods.each do |payment_method| + %li= payment_method.name + - else + %p + = t('.no_payment_methods') + - if shared_payment_methods.any? || shared_shipping_methods.any? + %tr + %td= t('.shared') + %td + - shared_shipping_methods.each do |shared_shipping_method| + %p + %label + = check_box_tag "order_cycle[selected_shipping_method_ids][]", + shared_shipping_method.id, @order_cycle.shipping_methods.include?(shared_shipping_method), + id: "order_cycle_selected_shipping_method_ids_#{shared_shipping_method.id}" + = shared_shipping_method.name + %p + — + %em + = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") + %td + - if shared_payment_methods.any? + %ul + - shared_payment_methods.each do |shared_payment_method| + %li + = shared_payment_method.name + %p — - %small - %em - = shipping_method.distributors.map(&:name).join(", ") - - else - %p - = t('.no_shipping_methods') - %tr - %th{ colspan: 2 }= t('.payment_methods') - %tr - %td - %td - - if payment_methods.any? - %ul - - payment_methods.each do |payment_method| - %li - = payment_method.name - - if @order_cycle.distributors.many? - — - %small - %em - = payment_method.distributors.map(&:name).join(", ") - - else - %p - = t('.no_payment_methods') + %em + = shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %div#save-bar %div.container diff --git a/app/webpacker/controllers/select_all_controller.js b/app/webpacker/controllers/select_all_controller.js deleted file mode 100644 index 30a00aee79..0000000000 --- a/app/webpacker/controllers/select_all_controller.js +++ /dev/null @@ -1,19 +0,0 @@ -import { Controller } from "stimulus"; - -export default class extends Controller { - static targets = ["all", "checkbox"]; - - connect() { - this.toggleCheckbox() - } - - toggleAll() { - this.checkboxTargets.forEach(checkbox => { - checkbox.checked = this.allTarget.checked; - }); - } - - toggleCheckbox() { - this.allTarget.checked = this.checkboxTargets.every(checkbox => checkbox.checked); - } -} diff --git a/config/locales/en.yml b/config/locales/en.yml index 0739d192c9..63e4d3f830 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1166,6 +1166,7 @@ en: back_end: "Back office only" cancel: "Cancel" checkout_options: "Checkout options" + distributor: "Distributor" no_payment_methods: Each distributor on this order cycle requires at least one payment method. no_shipping_methods: Each distributor on this order cycle requires at least one shipping method. payment_methods: "Payment Methods" diff --git a/spec/javascripts/stimulus/select_all_controller_test.js b/spec/javascripts/stimulus/select_all_controller_test.js deleted file mode 100644 index f7a17b884f..0000000000 --- a/spec/javascripts/stimulus/select_all_controller_test.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import { Application } from "stimulus"; -import select_all_controller from "../../../app/webpacker/controllers/select_all_controller"; - -describe("SelectAllController", () => { - beforeAll(() => { - const application = Application.start(); - application.register("select-all", select_all_controller); - }); - - beforeEach(() => { - document.body.innerHTML = ` -
- - - -
- `; - }); - - describe("#toggleAll", () => { - it("checks all checkboxes when it's checked and unchecks them all when unchecked", () => { - const selectAllCheckbox = document.getElementById("selectAllCheckbox"); - const checkboxA = document.getElementById("checkboxA"); - const checkboxB = document.getElementById("checkboxB"); - expect(selectAllCheckbox.checked).toBe(false); - expect(checkboxA.checked).toBe(false); - expect(checkboxB.checked).toBe(false); - - selectAllCheckbox.click() - - expect(selectAllCheckbox.checked).toBe(true); - expect(checkboxA.checked).toBe(true); - expect(checkboxB.checked).toBe(true); - - selectAllCheckbox.click() - - expect(selectAllCheckbox.checked).toBe(false); - expect(checkboxA.checked).toBe(false); - expect(checkboxB.checked).toBe(false); - }); - }); - - describe("#toggleCheckbox", () => { - it("checks the individual checkbox and checks the select all checkbox if all checkboxes are checked and vice versa", () => { - const selectAllCheckbox = document.getElementById("selectAllCheckbox"); - const checkboxA = document.getElementById("checkboxA"); - const checkboxB = document.getElementById("checkboxB"); - checkboxA.click() - expect(selectAllCheckbox.checked).toBe(false); - expect(checkboxA.checked).toBe(true); - expect(checkboxB.checked).toBe(false); - - checkboxB.click() - - expect(selectAllCheckbox.checked).toBe(true); - expect(checkboxA.checked).toBe(true); - expect(checkboxB.checked).toBe(true); - - checkboxB.click() - - expect(selectAllCheckbox.checked).toBe(false); - expect(checkboxA.checked).toBe(true); - expect(checkboxB.checked).toBe(false); - }); - }); - - describe("#connect", () => { - beforeEach(() => { - document.body.innerHTML = ` -
- - - -
- `; - }); - - it("checks the select all checkbox on page load if all checkboxes are checked", () => { - const selectAllCheckbox = document.getElementById("selectAllCheckbox"); - expect(selectAllCheckbox.checked).toBe(true); - }); - }); -}); From a73b608f8a3ee35af41038e83acf55bbf064f631 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 10:36:41 +0100 Subject: [PATCH 57/69] Remove 'shared' shipping method sections from order cycle checkout options form, any shared shipping methods are to be displayed for each distributor e.g. if they belong to multiple distributors they will be displayed multiple times --- app/helpers/admin/order_cycles_helper.rb | 19 ----------- .../order_cycles/checkout_options.html.haml | 32 ++----------------- 2 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 app/helpers/admin/order_cycles_helper.rb diff --git a/app/helpers/admin/order_cycles_helper.rb b/app/helpers/admin/order_cycles_helper.rb deleted file mode 100644 index 6bc3fea258..0000000000 --- a/app/helpers/admin/order_cycles_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Admin - module OrderCyclesHelper - def order_cycle_shared_payment_methods(order_cycle) - order_cycle.attachable_payment_methods. - where("distributor_id IN (?)", order_cycle.distributors.select(:id)). - group("spree_payment_methods.id"). - having("COUNT(DISTINCT(distributor_id)) > 1") - end - - def order_cycle_shared_shipping_methods(order_cycle) - order_cycle.attachable_shipping_methods. - where("distributor_id IN (?)", order_cycle.distributors.select(:id)). - group("spree_shipping_methods.id"). - having("COUNT(DISTINCT(distributor_id)) > 1") - end - end -end diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 9b87b807c0..97a2a9abef 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -3,9 +3,6 @@ - content_for :page_title do = t :edit_order_cycle -- shared_payment_methods = order_cycle_shared_payment_methods(@order_cycle) -- shared_shipping_methods = order_cycle_shared_shipping_methods(@order_cycle) - = form_for [main_app, :admin, @order_cycle], html: { class: "order_cycle" } do |f| = render 'wizard_progress' @@ -22,8 +19,8 @@ %th= t('.shipping_methods') %th= t('.payment_methods') - @order_cycle.distributors.each do |distributor| - - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) - shared_payment_methods - - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) - shared_shipping_methods + - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) + - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) %tr %td= distributor.name %td @@ -50,31 +47,6 @@ - else %p = t('.no_payment_methods') - - if shared_payment_methods.any? || shared_shipping_methods.any? - %tr - %td= t('.shared') - %td - - shared_shipping_methods.each do |shared_shipping_method| - %p - %label - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shared_shipping_method.id, @order_cycle.shipping_methods.include?(shared_shipping_method), - id: "order_cycle_selected_shipping_method_ids_#{shared_shipping_method.id}" - = shared_shipping_method.name - %p - — - %em - = shared_shipping_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") - %td - - if shared_payment_methods.any? - %ul - - shared_payment_methods.each do |shared_payment_method| - %li - = shared_payment_method.name - %p - — - %em - = shared_payment_method.distributors.where(id: @order_cycle.distributor_ids).map(&:name).join(", ") %div#save-bar %div.container From 65ee9e148601c5bec86e41e67a4831d9664186ae Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 10:48:33 +0100 Subject: [PATCH 58/69] Implement new design with shipping and payment methods on different rows instead of same one, the select all checkboxes still need to be added back later This is the new design from https://github.com/openfoodfoundation/openfoodnetwork/pull/9262#issuecomment-1206673689 --- .../order_cycles/checkout_options.html.haml | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 97a2a9abef..79cfe59d20 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -12,41 +12,47 @@ = hidden_field_tag "order_cycle[selected_shipping_method_ids][]", "" - %table.checkout-options - %thead - %tr - %th= t('.distributor') - %th= t('.shipping_methods') - %th= t('.payment_methods') - - @order_cycle.distributors.each do |distributor| - - payment_methods = @order_cycle.attachable_payment_methods.where("distributor_id = ?", distributor.id) - - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) - %tr - %td= distributor.name - %td - - shipping_methods.each do |shipping_method| - %p - %label - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" - = shipping_method.name - - distributor.shipping_methods.backend.each do |shipping_method| - %label.disabled - = check_box_tag nil, nil, false, disabled: true - = shipping_method.name - = "(#{t('.back_end')})" - - if distributor.shipping_methods.frontend.none? - %p - = t('.no_shipping_methods') - %td - - if distributor.payment_methods.available(:both).any? - %ul - - payment_methods.each do |payment_method| - %li= payment_method.name - - else - %p - = t('.no_payment_methods') + .row + .three.columns +   + .ten.columns + %table.checkout-options + %thead + %tr + %th{ colspan: 2 }= t('.shipping_methods') + - @order_cycle.distributors.each do |distributor| + - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) + %tr + %td + %td + %em= distributor.name + - shipping_methods.each do |shipping_method| + %p + %label + = check_box_tag "order_cycle[selected_shipping_method_ids][]", + shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), + id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" + = shipping_method.name + - distributor.shipping_methods.backend.each do |shipping_method| + %label.disabled + = check_box_tag nil, nil, false, disabled: true + = shipping_method.name + = "(#{t('.back_end')})" + - if distributor.shipping_methods.frontend.none? + %p + = t('.no_shipping_methods') + %tr + %th{ colspan: 2 }= t('.payment_methods') + %tr + %td + %td + - if @order_cycle.attachable_payment_methods.available(:both).any? + %ul + - @order_cycle.attachable_payment_methods.available(:both).each do |payment_method| + %li= payment_method.name + - else + %p + = t('.no_payment_methods') %div#save-bar %div.container From 8c483f2eabe951814ead5adc67411550dbc5ebcf Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 11:21:14 +0100 Subject: [PATCH 59/69] Change join table in migration from order cycles to distributor shipping methods instead of order cycles to shipping methods --- ...order_cycles_distributor_shipping_methods.rb | 17 +++++++++++++++++ ...2052_create_order_cycles_shipping_methods.rb | 15 --------------- db/schema.rb | 11 +++++------ 3 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20220429092052_create_order_cycles_distributor_shipping_methods.rb delete mode 100644 db/migrate/20220429092052_create_order_cycles_shipping_methods.rb diff --git a/db/migrate/20220429092052_create_order_cycles_distributor_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycles_distributor_shipping_methods.rb new file mode 100644 index 0000000000..e2d5224235 --- /dev/null +++ b/db/migrate/20220429092052_create_order_cycles_distributor_shipping_methods.rb @@ -0,0 +1,17 @@ +class CreateOrderCyclesDistributorShippingMethods < ActiveRecord::Migration[6.1] + def up + create_table :order_cycles_distributor_shipping_methods, id: false do |t| + t.belongs_to :order_cycle, + index: { name: "index_oc_id_on_order_cycles_distributor_shipping_methods" } + t.belongs_to :distributor_shipping_method, + index: { name: "index_dsm_id_on_order_cycles_distributor_shipping_methods" } + t.index [:order_cycle_id, :distributor_shipping_method_id], + name: "order_cycles_distributor_shipping_methods_join_index", + unique: true + end + end + + def down + drop_table :order_cycles_distributor_shipping_methods + end +end diff --git a/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb b/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb deleted file mode 100644 index 7f184a8571..0000000000 --- a/db/migrate/20220429092052_create_order_cycles_shipping_methods.rb +++ /dev/null @@ -1,15 +0,0 @@ -class CreateOrderCyclesShippingMethods < ActiveRecord::Migration[6.1] - def up - create_table :order_cycles_shipping_methods, id: false do |t| - t.belongs_to :order_cycle - t.belongs_to :shipping_method, foreign_key: { to_table: :spree_shipping_methods } - t.index [:order_cycle_id, :shipping_method_id], - name: "order_cycles_shipping_methods_join_index", - unique: true - end - end - - def down - drop_table :order_cycles_shipping_methods - end -end diff --git a/db/schema.rb b/db/schema.rb index 1ae02469f3..1925baa7fa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -330,12 +330,12 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do t.boolean "mails_sent", default: false end - create_table "order_cycles_shipping_methods", id: false, force: :cascade do |t| + create_table "order_cycles_distributor_shipping_methods", id: false, force: :cascade do |t| t.bigint "order_cycle_id" - t.bigint "shipping_method_id" - t.index ["order_cycle_id", "shipping_method_id"], name: "order_cycles_shipping_methods_join_index", unique: true - t.index ["order_cycle_id"], name: "index_order_cycles_shipping_methods_on_order_cycle_id" - t.index ["shipping_method_id"], name: "index_order_cycles_shipping_methods_on_shipping_method_id" + t.bigint "distributor_shipping_method_id" + t.index ["distributor_shipping_method_id"], name: "index_dsm_id_on_order_cycles_distributor_shipping_methods" + t.index ["order_cycle_id", "distributor_shipping_method_id"], name: "order_cycles_distributor_shipping_methods_join_index", unique: true + t.index ["order_cycle_id"], name: "index_oc_id_on_order_cycles_distributor_shipping_methods" end create_table "producer_properties", id: :serial, force: :cascade do |t| @@ -1262,7 +1262,6 @@ ActiveRecord::Schema.define(version: 2022_09_07_055044) do add_foreign_key "order_cycle_schedules", "order_cycles", name: "oc_schedules_order_cycle_id_fk" add_foreign_key "order_cycle_schedules", "schedules", name: "oc_schedules_schedule_id_fk" add_foreign_key "order_cycles", "enterprises", column: "coordinator_id", name: "order_cycles_coordinator_id_fk" - add_foreign_key "order_cycles_shipping_methods", "spree_shipping_methods", column: "shipping_method_id" add_foreign_key "producer_properties", "enterprises", column: "producer_id", name: "producer_properties_producer_id_fk" add_foreign_key "producer_properties", "spree_properties", column: "property_id", name: "producer_properties_property_id_fk" add_foreign_key "proxy_orders", "order_cycles", name: "proxy_orders_order_cycle_id_fk" From a53a3259a81e16bb307e6a569cbbee9f4c9ec6b7 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 14:00:11 +0100 Subject: [PATCH 60/69] Connect DistributorShippingMethods to OrderCycles instead of Spree::ShippingMethods Before if a shipping method was shared between multiple distributors it could only be disabled/enabled on that order cycle for all the distributors which have that shipping method e.g. you couldn't select that shipping method for one distributor but disable it for another. --- app/models/order_cycle.rb | 28 ++++---- .../order_available_shipping_methods.rb | 4 +- app/services/order_cycle_form.rb | 30 ++++---- .../permitted_attributes/order_cycle.rb | 2 +- .../order_cycles/checkout_options.html.haml | 16 ++--- spec/models/order_cycle_spec.rb | 69 ++++++++++++------- .../order_available_shipping_methods_spec.rb | 9 ++- spec/services/order_cycle_form_spec.rb | 49 ++++++++----- .../complex_creating_specific_time_spec.rb | 4 +- 9 files changed, 128 insertions(+), 83 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 923dd659f1..9b706172c8 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -24,9 +24,9 @@ class OrderCycle < ApplicationRecord has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges has_many :order_cycle_schedules has_many :schedules, through: :order_cycle_schedules - has_and_belongs_to_many :selected_shipping_methods, - class_name: 'Spree::ShippingMethod', - join_table: 'order_cycles_shipping_methods' + has_and_belongs_to_many :selected_distributor_shipping_methods, + class_name: 'DistributorShippingMethod', + join_table: 'order_cycles_distributor_shipping_methods' has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } } attr_accessor :incoming_exchanges, :outgoing_exchanges @@ -160,11 +160,10 @@ class OrderCycle < ApplicationRecord distinct end - def attachable_shipping_methods - Spree::ShippingMethod.frontend. - joins(:distributor_shipping_methods). - where("distributor_id IN (?)", distributor_ids). - distinct + def attachable_distributor_shipping_methods + DistributorShippingMethod.joins(:shipping_method). + merge(Spree::ShippingMethod.frontend). + where("distributor_id IN (?)", distributor_ids) end def clone! @@ -178,8 +177,9 @@ class OrderCycle < ApplicationRecord oc.schedule_ids = schedule_ids oc.save! exchanges.each { |e| e.clone!(oc) } - oc.selected_shipping_method_ids = attachable_shipping_methods.map(&:id) & - selected_shipping_method_ids + oc.selected_distributor_shipping_method_ids = ( + attachable_distributor_shipping_methods.map(&:id) & selected_distributor_shipping_method_ids + ) sync_subscriptions oc.reload end @@ -293,11 +293,11 @@ class OrderCycle < ApplicationRecord items.each { |li| scoper.scope(li.variant) } end - def shipping_methods - if simple? || selected_shipping_methods.none? - attachable_shipping_methods + def distributor_shipping_methods + if simple? || selected_distributor_shipping_methods.none? + attachable_distributor_shipping_methods else - selected_shipping_methods + selected_distributor_shipping_methods end end diff --git a/app/services/order_available_shipping_methods.rb b/app/services/order_available_shipping_methods.rb index 36fd37f2c8..96d1b99559 100644 --- a/app/services/order_available_shipping_methods.rb +++ b/app/services/order_available_shipping_methods.rb @@ -29,7 +29,9 @@ class OrderAvailableShippingMethods if order_cycle.nil? || order_cycle.simple? distributor.shipping_methods else - distributor.shipping_methods.where(id: order_cycle.shipping_methods.select(:id)) + distributor.shipping_methods.where( + id: order_cycle.distributor_shipping_methods.select(:shipping_method_id) + ) end.frontend.to_a end end diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index d4334db495..68308dd9bb 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -11,7 +11,9 @@ class OrderCycleForm @user = user @permissions = OpenFoodNetwork::Permissions.new(user) @schedule_ids = order_cycle_params.delete(:schedule_ids) - @selected_shipping_method_ids = order_cycle_params.delete(:selected_shipping_method_ids) + @selected_distributor_shipping_method_ids = order_cycle_params.delete( + :selected_distributor_shipping_method_ids + ) end def save @@ -24,7 +26,7 @@ class OrderCycleForm order_cycle.schedule_ids = schedule_ids order_cycle.save! apply_exchange_changes - attach_selected_shipping_methods + attach_selected_distributor_shipping_methods sync_subscriptions true end @@ -48,16 +50,16 @@ class OrderCycleForm OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go! end - def attach_selected_shipping_methods - return if @selected_shipping_method_ids.nil? + def attach_selected_distributor_shipping_methods + return if @selected_distributor_shipping_method_ids.nil? order_cycle.reload # so outgoing exchanges are up-to-date for shipping method validations - order_cycle.selected_shipping_method_ids = selected_shipping_method_ids + order_cycle.selected_distributor_shipping_method_ids = selected_distributor_shipping_method_ids order_cycle.save! end - def attachable_shipping_method_ids - @attachable_shipping_method_ids ||= order_cycle.attachable_shipping_methods.map(&:id) + def attachable_distributor_shipping_method_ids + @attachable_distributor_shipping_method_ids ||= order_cycle.attachable_distributor_shipping_methods.map(&:id) end def exchanges_unchanged? @@ -66,15 +68,17 @@ class OrderCycleForm end end - def selected_shipping_method_ids - @selected_shipping_method_ids = attachable_shipping_method_ids & - @selected_shipping_method_ids.reject(&:blank?).map(&:to_i) + def selected_distributor_shipping_method_ids + @selected_distributor_shipping_method_ids = ( + attachable_distributor_shipping_method_ids & + @selected_distributor_shipping_method_ids.reject(&:blank?).map(&:to_i) + ) - if attachable_shipping_method_ids.sort == @selected_shipping_method_ids.sort - @selected_shipping_method_ids = [] + if attachable_distributor_shipping_method_ids.sort == @selected_distributor_shipping_method_ids.sort + @selected_distributor_shipping_method_ids = [] end - @selected_shipping_method_ids + @selected_distributor_shipping_method_ids end def schedule_ids? diff --git a/app/services/permitted_attributes/order_cycle.rb b/app/services/permitted_attributes/order_cycle.rb index e1b7555d6d..15d652268c 100644 --- a/app/services/permitted_attributes/order_cycle.rb +++ b/app/services/permitted_attributes/order_cycle.rb @@ -17,7 +17,7 @@ module PermittedAttributes :name, :orders_open_at, :orders_close_at, :coordinator_id, :preferred_product_selection_from_coordinator_inventory_only, :automatic_notifications, - { schedule_ids: [], selected_shipping_method_ids: [], coordinator_fee_ids: [] } + { schedule_ids: [], selected_distributor_shipping_method_ids: [], coordinator_fee_ids: [] } ] end diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 79cfe59d20..4c3dc61cbc 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -10,7 +10,7 @@ %fieldset.no-border-bottom %legend{ align: 'center'}= t('.checkout_options') - = hidden_field_tag "order_cycle[selected_shipping_method_ids][]", "" + = hidden_field_tag "order_cycle[selected_distributor_shipping_method_ids][]", "" .row .three.columns @@ -21,18 +21,18 @@ %tr %th{ colspan: 2 }= t('.shipping_methods') - @order_cycle.distributors.each do |distributor| - - shipping_methods = @order_cycle.attachable_shipping_methods.where("distributor_id = ?", distributor.id) + - distributor_shipping_methods = @order_cycle.attachable_distributor_shipping_methods.where("distributor_id = ?", distributor.id).includes(:shipping_method) %tr %td %td %em= distributor.name - - shipping_methods.each do |shipping_method| + - distributor_shipping_methods.each do |distributor_shipping_method| %p - %label - = check_box_tag "order_cycle[selected_shipping_method_ids][]", - shipping_method.id, @order_cycle.shipping_methods.include?(shipping_method), - id: "order_cycle_selected_shipping_method_ids_#{shipping_method.id}" - = shipping_method.name + %label{ class: ("disabled" unless distributor_shipping_method.shipping_method.frontend?) } + = check_box_tag "order_cycle[selected_distributor_shipping_method_ids][]", + distributor_shipping_method.id, @order_cycle.distributor_shipping_methods.include?(distributor_shipping_method), + id: "order_cycle_selected_distributor_shipping_method_ids_#{distributor_shipping_method.id}" + = distributor_shipping_method.shipping_method.name - distributor.shipping_methods.backend.each do |shipping_method| %label.disabled = check_box_tag nil, nil, false, disabled: true diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 9035b853e1..91b212ca89 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -408,18 +408,24 @@ describe OrderCycle do e.g. shipping method is backoffice only" do it "only attaches the valid ones to the clone" do distributor = create(:distributor_enterprise) - shipping_method_i = create(:shipping_method, distributors: [distributor]) - shipping_method_ii = create( + distributor_shipping_method_i = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first + distributor_shipping_method_ii = create( :shipping_method, distributors: [distributor], display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end] - ) + ).distributor_shipping_methods.first order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - order_cycle.selected_shipping_methods = [shipping_method_i, shipping_method_ii] + order_cycle.selected_distributor_shipping_methods = [ + distributor_shipping_method_i, + distributor_shipping_method_ii + ] cloned_order_cycle = order_cycle.clone! - expect(cloned_order_cycle.shipping_methods).to eq [shipping_method_i] + expect(cloned_order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method_i] end end end @@ -631,52 +637,65 @@ describe OrderCycle do end end - describe "#attachable_shipping_methods" do - it "includes shipping methods from the distributors on the order cycle" do + describe "#attachable_distributor_shipping_methods" do + it "includes distributor shipping methods from the distributors on the order cycle" do shipping_method = create(:shipping_method) - enterprise = create(:enterprise, shipping_methods: [shipping_method]) - oc = create(:simple_order_cycle, distributors: [enterprise]) + oc = create(:simple_order_cycle, distributors: [shipping_method.distributors.first]) + distributor_shipping_method = shipping_method.distributor_shipping_methods.first - expect(oc.attachable_shipping_methods).to eq([shipping_method]) + expect(oc.attachable_distributor_shipping_methods).to eq([distributor_shipping_method]) end - it "does not include backoffice only shipping methods" do + it "does not include backoffice only distributor shipping methods" do shipping_method = create(:shipping_method, display_on: "back_end") enterprise = create(:enterprise, shipping_methods: [shipping_method]) oc = create(:simple_order_cycle, distributors: [enterprise]) - expect(oc.attachable_shipping_methods).to be_empty + expect(oc.attachable_distributor_shipping_methods).to be_empty end end - describe "#shipping_methods" do + describe "#distributor_shipping_methods" do let(:distributor) { create(:distributor_enterprise) } - it "returns all attachable shipping methods if the order cycle is simple" do + it "returns all attachable distributor shipping methods if the order cycle is simple" do oc = create(:sells_own_order_cycle, distributors: [distributor]) - shipping_method = create(:shipping_method, distributors: [distributor]) + distributor_shipping_method = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first - expect(oc.shipping_methods).to eq [shipping_method] + expect(oc.distributor_shipping_methods).to eq [distributor_shipping_method] end context "distributor order cycle i.e. non-simple" do let(:oc) { create(:distributor_order_cycle, distributors: [distributor]) } - it "returns all attachable shipping methods if no preferred shipping methods have been chosen" do - shipping_method = create(:shipping_method, distributors: [distributor]) + it "returns all attachable distributor shipping methods if no preferred shipping methods have + been chosen" do + distributor_shipping_method = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first - expect(oc.selected_shipping_methods).to be_empty - expect(oc.shipping_methods).to eq [shipping_method] + expect(oc.selected_distributor_shipping_methods).to be_empty + expect(oc.distributor_shipping_methods).to eq [distributor_shipping_method] end - it "returns preferred shipping methods if they have been specified" do - shipping_method_i = create(:shipping_method, distributors: [distributor]) - shipping_method_ii = create(:shipping_method, distributors: [distributor]) + it "returns selected distributor shipping methods if they have been specified" do + distributor_shipping_method_i = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first + distributor_shipping_method_ii = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first - oc.selected_shipping_methods << shipping_method_ii + oc.selected_distributor_shipping_methods << distributor_shipping_method_ii - expect(oc.shipping_methods).to eq [shipping_method_ii] + expect(oc.distributor_shipping_methods).to eq [distributor_shipping_method_ii] end end end diff --git a/spec/services/order_available_shipping_methods_spec.rb b/spec/services/order_available_shipping_methods_spec.rb index 8157e2156f..59acb35dc6 100644 --- a/spec/services/order_available_shipping_methods_spec.rb +++ b/spec/services/order_available_shipping_methods_spec.rb @@ -47,15 +47,18 @@ describe OrderAvailableShippingMethods do context "distributor order cycle i.e. not simple" do it "only returns the shipping methods which are available on the order cycle and belong to the order distributor" do - distributor_i = create(:distributor_enterprise) - distributor_ii = create(:distributor_enterprise) + distributor_i = create(:distributor_enterprise, shipping_methods: []) + distributor_ii = create(:distributor_enterprise, shipping_methods: []) shipping_method_i = create(:shipping_method, distributors: [distributor_i]) shipping_method_ii = create(:shipping_method, distributors: [distributor_i]) shipping_method_iii = create(:shipping_method, distributors: [distributor_ii]) shipping_method_iv = create(:shipping_method, distributors: [distributor_ii]) order_cycle = create(:distributor_order_cycle, distributors: [distributor_i, distributor_ii]) - order_cycle.selected_shipping_methods << [shipping_method_i, shipping_method_iii] + order_cycle.selected_distributor_shipping_methods << [ + distributor_i.distributor_shipping_methods.first, + distributor_ii.distributor_shipping_methods.first, + ] order = build(:order, distributor: distributor_i, order_cycle: order_cycle) available_shipping_methods = OrderAvailableShippingMethods.new(order).to_a diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index be3143fbc9..612a90a058 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -125,6 +125,7 @@ describe OrderCycleForm do let(:supplier) { create(:supplier_enterprise) } let(:user) { distributor.owner } let(:shipping_method) { create(:shipping_method, distributors: [distributor]) } + let(:distributor_shipping_method) { shipping_method.distributor_shipping_methods.first } let(:variant) { create(:variant, product: create(:product, supplier: supplier)) } let(:params) { { name: 'Some new name' } } let(:form) { OrderCycleForm.new(order_cycle, params, user) } @@ -159,7 +160,7 @@ describe OrderCycleForm do enterprise_fee_ids: [] }], outgoing_exchanges: [outgoing_exchange_params], - selected_shipping_method_ids: [shipping_method.id] + selected_distributor_shipping_method_ids: [distributor_shipping_method.id] ) end @@ -168,27 +169,30 @@ describe OrderCycleForm do expect(order_cycle.name).to eq 'Some new name' expect(order_cycle.cached_incoming_exchanges.count).to eq 1 expect(order_cycle.cached_outgoing_exchanges.count).to eq 1 - expect(order_cycle.shipping_methods).to eq [shipping_method] + expect(order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method] end end context "updating outgoing exchanges and shipping methods simultaneously but the shipping method doesn't belong to the new or any existing order cycle distributor" do let(:other_distributor_shipping_method) do - create(:shipping_method, distributors: [create(:distributor_enterprise)]) + create( + :shipping_method, + distributors: [create(:distributor_enterprise)] + ).distributor_shipping_methods.first end before do params.merge!( outgoing_exchanges: [outgoing_exchange_params], - selected_shipping_method_ids: [other_distributor_shipping_method.id] + selected_distributor_shipping_method_ids: [other_distributor_shipping_method.id] ) end it "saves the outgoing exchange but ignores the shipping method" do expect(form.save).to be true expect(order_cycle.distributors).to eq [distributor] - expect(order_cycle.shipping_methods).to be_empty + expect(order_cycle.distributor_shipping_methods).to be_empty end end @@ -196,15 +200,20 @@ describe OrderCycleForm do context "and it's valid" do it "saves the changes" do distributor = create(:distributor_enterprise) - shipping_method = create(:shipping_method, distributors: [distributor]) + distributor_shipping_method = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - form = OrderCycleForm.new(order_cycle, - { selected_shipping_method_ids: [shipping_method.id] }, - order_cycle.coordinator) + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method.id] }, + order_cycle.coordinator + ) expect(form.save).to be true - expect(order_cycle.shipping_methods).to eq [shipping_method] + expect(order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method] end end @@ -212,17 +221,25 @@ describe OrderCycleForm do it "ignores it" do distributor_i = create(:distributor_enterprise) distributor_ii = create(:distributor_enterprise) - shipping_method_i = create(:shipping_method, distributors: [distributor_i]) - shipping_method_ii = create(:shipping_method, distributors: [distributor_ii]) + distributor_shipping_method_i = create( + :shipping_method, + distributors: [distributor_i] + ).distributor_shipping_methods.first + distributor_shipping_method_ii = create( + :shipping_method, + distributors: [distributor_ii] + ).distributor_shipping_methods.first order_cycle = create(:distributor_order_cycle, distributors: [distributor_i]) - form = OrderCycleForm.new(order_cycle, - { selected_shipping_method_ids: [shipping_method_ii.id] }, - order_cycle.coordinator) + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method_ii.id] }, + order_cycle.coordinator + ) expect(form.save).to be true - expect(order_cycle.shipping_methods).to eq [shipping_method_i] + expect(order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method_i] end end end diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 25ee70aed4..31f3ce70bc 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -66,7 +66,7 @@ describe ' ## UPDATE add_supplier_with_fees add_distributor_with_fees - select_shipping_methods + select_distributor_shipping_methods expect_all_data_saved end @@ -160,7 +160,7 @@ describe ' click_button 'Save and Next' end - def select_shipping_methods + def select_distributor_shipping_methods expect(page).to have_checked_field "Select all" expect(page).to have_checked_field "Pickup - always available" From 7e40ad39cb77a02e2aa65e173eb4a96cb0280472 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 14:33:23 +0100 Subject: [PATCH 61/69] Bring back select all checkboxes on order cycle checkout options form. --- .../order_cycles/checkout_options.html.haml | 16 ++- .../controllers/select_all_controller.js | 19 +++ .../stimulus/select_all_controller_test.js | 113 ++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 app/webpacker/controllers/select_all_controller.js create mode 100644 spec/javascripts/stimulus/select_all_controller_test.js diff --git a/app/views/admin/order_cycles/checkout_options.html.haml b/app/views/admin/order_cycles/checkout_options.html.haml index 4c3dc61cbc..32eb731895 100644 --- a/app/views/admin/order_cycles/checkout_options.html.haml +++ b/app/views/admin/order_cycles/checkout_options.html.haml @@ -22,16 +22,22 @@ %th{ colspan: 2 }= t('.shipping_methods') - @order_cycle.distributors.each do |distributor| - distributor_shipping_methods = @order_cycle.attachable_distributor_shipping_methods.where("distributor_id = ?", distributor.id).includes(:shipping_method) - %tr - %td + %tr{ "data-controller": "select-all" } + %td.text-center + - if distributor_shipping_methods.many? + %label + = check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" } + = t(".select_all") %td %em= distributor.name - distributor_shipping_methods.each do |distributor_shipping_method| %p - %label{ class: ("disabled" unless distributor_shipping_method.shipping_method.frontend?) } + %label{ class: ("disabled" if distributor_shipping_methods.one? || !distributor_shipping_method.shipping_method.frontend?) } = check_box_tag "order_cycle[selected_distributor_shipping_method_ids][]", - distributor_shipping_method.id, @order_cycle.distributor_shipping_methods.include?(distributor_shipping_method), - id: "order_cycle_selected_distributor_shipping_method_ids_#{distributor_shipping_method.id}" + distributor_shipping_method.id, + @order_cycle.distributor_shipping_methods.include?(distributor_shipping_method), + id: "order_cycle_selected_distributor_shipping_method_ids_#{distributor_shipping_method.id}", + data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if distributor_shipping_method.shipping_method.frontend?) = distributor_shipping_method.shipping_method.name - distributor.shipping_methods.backend.each do |shipping_method| %label.disabled diff --git a/app/webpacker/controllers/select_all_controller.js b/app/webpacker/controllers/select_all_controller.js new file mode 100644 index 0000000000..30a00aee79 --- /dev/null +++ b/app/webpacker/controllers/select_all_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["all", "checkbox"]; + + connect() { + this.toggleCheckbox() + } + + toggleAll() { + this.checkboxTargets.forEach(checkbox => { + checkbox.checked = this.allTarget.checked; + }); + } + + toggleCheckbox() { + this.allTarget.checked = this.checkboxTargets.every(checkbox => checkbox.checked); + } +} diff --git a/spec/javascripts/stimulus/select_all_controller_test.js b/spec/javascripts/stimulus/select_all_controller_test.js new file mode 100644 index 0000000000..f7a17b884f --- /dev/null +++ b/spec/javascripts/stimulus/select_all_controller_test.js @@ -0,0 +1,113 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import select_all_controller from "../../../app/webpacker/controllers/select_all_controller"; + +describe("SelectAllController", () => { + beforeAll(() => { + const application = Application.start(); + application.register("select-all", select_all_controller); + }); + + beforeEach(() => { + document.body.innerHTML = ` +
+ + + +
+ `; + }); + + describe("#toggleAll", () => { + it("checks all checkboxes when it's checked and unchecks them all when unchecked", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + const checkboxA = document.getElementById("checkboxA"); + const checkboxB = document.getElementById("checkboxB"); + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(false); + expect(checkboxB.checked).toBe(false); + + selectAllCheckbox.click() + + expect(selectAllCheckbox.checked).toBe(true); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(true); + + selectAllCheckbox.click() + + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(false); + expect(checkboxB.checked).toBe(false); + }); + }); + + describe("#toggleCheckbox", () => { + it("checks the individual checkbox and checks the select all checkbox if all checkboxes are checked and vice versa", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + const checkboxA = document.getElementById("checkboxA"); + const checkboxB = document.getElementById("checkboxB"); + checkboxA.click() + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(false); + + checkboxB.click() + + expect(selectAllCheckbox.checked).toBe(true); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(true); + + checkboxB.click() + + expect(selectAllCheckbox.checked).toBe(false); + expect(checkboxA.checked).toBe(true); + expect(checkboxB.checked).toBe(false); + }); + }); + + describe("#connect", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+ + + +
+ `; + }); + + it("checks the select all checkbox on page load if all checkboxes are checked", () => { + const selectAllCheckbox = document.getElementById("selectAllCheckbox"); + expect(selectAllCheckbox.checked).toBe(true); + }); + }); +}); From 5207dbf8c67a87ae3df79efb8b798dcc1087a6cb Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 15:23:20 +0100 Subject: [PATCH 62/69] OrderCycle#distributor_shipping_methods should return all attachable ones by default per distributor, not per all distributors --- app/models/order_cycle.rb | 9 +++++++-- spec/models/order_cycle_spec.rb | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 9b706172c8..f722cbbcf4 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -294,10 +294,15 @@ class OrderCycle < ApplicationRecord end def distributor_shipping_methods - if simple? || selected_distributor_shipping_methods.none? + if simple? attachable_distributor_shipping_methods else - selected_distributor_shipping_methods + attachable_distributor_shipping_methods.reject do |distributor_shipping_method| + selected_distributor_shipping_methods. + map(&:distributor_id). + include?(distributor_shipping_method.distributor_id) && + !selected_distributor_shipping_methods.include?(distributor_shipping_method) + end end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 91b212ca89..3789a4aa1a 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -672,8 +672,8 @@ describe OrderCycle do context "distributor order cycle i.e. non-simple" do let(:oc) { create(:distributor_order_cycle, distributors: [distributor]) } - it "returns all attachable distributor shipping methods if no preferred shipping methods have - been chosen" do + it "returns all attachable distributor shipping methods if no distributor shipping methods + have been selected specifically" do distributor_shipping_method = create( :shipping_method, distributors: [distributor] @@ -697,6 +697,35 @@ describe OrderCycle do expect(oc.distributor_shipping_methods).to eq [distributor_shipping_method_ii] end + + context "with multiple distributors" do + let(:other_distributor) { create(:distributor_enterprise) } + let(:oc) { create(:distributor_order_cycle, distributors: [distributor, other_distributor]) } + + it "returns all attachable distributor shipping methods for a distributor if no distributor + shipping methods have been selected specifically for that distributor, even if + distributor shipping methods have been selected specifically for a different distributor + on the order cycle" do + distributor_shipping_method = create( + :shipping_method, + distributors: [distributor] + ).distributor_shipping_methods.first + other_distributor_shipping_method_i = create( + :shipping_method, + distributors: [other_distributor] + ).distributor_shipping_methods.first + other_distributor_shipping_method_ii = create( + :shipping_method, + distributors: [other_distributor] + ).distributor_shipping_methods.first + oc.selected_distributor_shipping_methods << other_distributor_shipping_method_i + + expect(oc.distributor_shipping_methods).to eq [ + distributor_shipping_method, + other_distributor_shipping_method_i + ] + end + end end end From db92e9d91c8d68bc5eb336d6e95654a6533abcc3 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 9 Sep 2022 16:29:23 +0100 Subject: [PATCH 63/69] Fix error from OrderCycle#distributor_shipping methods return ActiveRecord::Relation sometimes and Array other times Now it will return ActiveRecord::Relation consistently. --- app/models/order_cycle.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index f722cbbcf4..e31fe4bf82 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -294,15 +294,14 @@ class OrderCycle < ApplicationRecord end def distributor_shipping_methods - if simple? + if simple? || selected_distributor_shipping_methods.none? attachable_distributor_shipping_methods else - attachable_distributor_shipping_methods.reject do |distributor_shipping_method| - selected_distributor_shipping_methods. - map(&:distributor_id). - include?(distributor_shipping_method.distributor_id) && - !selected_distributor_shipping_methods.include?(distributor_shipping_method) - end + attachable_distributor_shipping_methods.where( + "distributors_shipping_methods.id IN (?) OR distributor_id NOT IN (?)", + selected_distributor_shipping_methods.map(&:id), + selected_distributor_shipping_methods.map(&:distributor_id) + ) end end From 23f6901fb4585cf4819942f255d2d4505f9709e1 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 21 Sep 2022 21:40:02 +0100 Subject: [PATCH 64/69] Adjust order_cycles/simple_spec.rb to take new Checkout Options step into account Note, this doesn't test checking/unchecking some distributor shipping methods and not others because that is tested in order_cycles/complex_creating_time_specific_spec.rb. --- spec/system/admin/order_cycles/simple_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/system/admin/order_cycles/simple_spec.rb b/spec/system/admin/order_cycles/simple_spec.rb index b696134b6e..0d2aac2561 100644 --- a/spec/system/admin/order_cycles/simple_spec.rb +++ b/spec/system/admin/order_cycles/simple_spec.rb @@ -260,6 +260,11 @@ describe ' find(:css, "tags-input .tags input").set "wholesale\n" end + click_button 'Save and Next' + + expect_shipping_methods_to_be_checked_for(distributor_managed) + expect_shipping_methods_to_be_checked_for(distributor_permitted) + click_button 'Save and Back to List' order_cycle = OrderCycle.find_by(name: 'My order cycle') expect(page).to have_input "oc#{order_cycle.id}[name]", value: order_cycle.name @@ -270,6 +275,9 @@ describe ' expect(order_cycle.schedules).to eq([schedule]) exchange = order_cycle.exchanges.outgoing.to_enterprise(distributor_managed).first expect(exchange.tag_list).to eq(["wholesale"]) + expect(order_cycle.distributor_shipping_methods).to match_array( + order_cycle.attachable_distributor_shipping_methods + ) end context "editing an order cycle" do @@ -308,6 +316,7 @@ describe ' # And I remove all outgoing exchanges page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + click_button 'Save and Next' click_button 'Save and Back to List' expect(page).to have_input "oc#{oc.id}[name]", value: oc.name @@ -706,6 +715,14 @@ describe ' private + def expect_shipping_methods_to_be_checked_for(distributor) + distributor.distributor_shipping_method_ids.each do |distributor_shipping_method_id| + expect(page).to have_checked_field( + "order_cycle_selected_distributor_shipping_method_ids_#{distributor_shipping_method_id}" + ) + end + end + def wait_for_edit_form_to_load_order_cycle(order_cycle) expect(page).to have_field "order_cycle_name", with: order_cycle.name end From 2827e07da6ab1ca97028068271fd1e8b31766c2c Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 21 Sep 2022 21:44:01 +0100 Subject: [PATCH 65/69] Define :oc with let so it is available globally to other methods such as :expect_fees_saved --- .../admin/order_cycles/complex_creating_specific_time_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 31f3ce70bc..e1d87b2faf 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -24,6 +24,7 @@ describe ' } let!(:shipping_method_i) { distributor.shipping_methods.first } let!(:shipping_method_ii) { create(:shipping_method, distributors: [distributor]) } + let(:oc) { OrderCycle.last } # And some enterprise fees let!(:supplier_fee) { create(:enterprise_fee, enterprise: supplier, name: 'Supplier fee') } @@ -194,7 +195,6 @@ describe ' end def expect_all_data_saved - oc = OrderCycle.last toggle_columns "Producers", "Shops" expect(page).to have_input "oc#{oc.id}[name]", value: "Plums & Avos" From 6542b47e72564b7d18dac4674e96217340b6cf49 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 21 Sep 2022 21:46:18 +0100 Subject: [PATCH 66/69] Adjust order_cycles/complex_creating_specific_time_spec to take into account that DistributorShippingMethods instead of ShippingMethods are connected to OrderCycles now --- .../admin/order_cycles/complex_creating_specific_time_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index e1d87b2faf..ff6c490e47 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -208,8 +208,8 @@ describe ' expect_receival_instructions_saved expect_pickup_time_and_instructions_saved - # And the shipping method should be attached - expect(oc.shipping_methods.map(&:name)).to eq(["Pickup - always available"]) + # And the distributor shipping method should be attached + expect(oc.distributor_shipping_methods).to eq(shipping_method_i.distributor_shipping_methods) end def expect_opening_and_closing_times_saved From def87485c0e334b6cb093c145b5a43aae87b5a61 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 21 Sep 2022 21:51:21 +0100 Subject: [PATCH 67/69] Don't delete schedules on the order_cycles#update action just because the :schedule_ids was not specified in the parameters Before this schedules were being deleted when the Checkout Options step of editing order cycles was saved because this step wasn't sending the :schedule_ids parameter. It's more awkard for API users if they always need to explicity send the :schedule_ids parameter even if they don't want to add/remove any of the schedules, for example if they just want to update an order cycle name. --- app/services/order_cycle_form.rb | 15 ++++++++------- spec/services/order_cycle_form_spec.rb | 11 +++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 68308dd9bb..252c3bc783 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -8,6 +8,7 @@ class OrderCycleForm def initialize(order_cycle, order_cycle_params, user) @order_cycle = order_cycle @order_cycle_params = order_cycle_params + @specified_params = order_cycle_params.keys @user = user @permissions = OpenFoodNetwork::Permissions.new(user) @schedule_ids = order_cycle_params.delete(:schedule_ids) @@ -23,7 +24,7 @@ class OrderCycleForm order_cycle.transaction do order_cycle.save! - order_cycle.schedule_ids = schedule_ids + order_cycle.schedule_ids = schedule_ids if parameter_specified?(:schedule_ids) order_cycle.save! apply_exchange_changes attach_selected_distributor_shipping_methods @@ -81,12 +82,8 @@ class OrderCycleForm @selected_distributor_shipping_method_ids end - def schedule_ids? - @schedule_ids.present? - end - def build_schedule_ids - return unless schedule_ids? + return unless parameter_specified?(:schedule_ids) result = existing_schedule_ids result |= (requested_schedule_ids & permitted_schedule_ids) # Add permitted and requested @@ -95,7 +92,7 @@ class OrderCycleForm end def sync_subscriptions - return unless schedule_ids? + return unless parameter_specified?(:schedule_ids) return unless schedule_sync_required? OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions_to_sync).sync! @@ -113,6 +110,10 @@ class OrderCycleForm @schedule_ids.map(&:to_i) end + def parameter_specified?(key) + @specified_params.map(&:to_s).include?(key.to_s) + end + def permitted_schedule_ids Schedule.where(id: requested_schedule_ids | existing_schedule_ids) .merge(permissions.editable_schedules).pluck(:id) diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index 612a90a058..0117a1537a 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -55,6 +55,17 @@ describe OrderCycleForm do end.to_not change{ order_cycle.reload.name } end end + + context "when schedules are present but updating something other than the :schedule_ids" do + let(:params) { { name: "New Order Cycle Name" } } + before { create(:schedule, order_cycles: [order_cycle]) } + + it "doesn't delete the schedules" do + expect(order_cycle.schedules).to be_present + form.save + expect(order_cycle.schedules).to be_present + end + end end end From f9e29f3a78266a63f85f98fbdf7db895527142d8 Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Wed, 21 Sep 2022 22:05:43 +0100 Subject: [PATCH 68/69] Fix Metrics/AbcSize violation in order_cycles/complex_creating_specific_time_spec.rb --- .../order_cycles/complex_creating_specific_time_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index ff6c490e47..9cfdae3061 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -207,9 +207,7 @@ describe ' expect_variants_saved expect_receival_instructions_saved expect_pickup_time_and_instructions_saved - - # And the distributor shipping method should be attached - expect(oc.distributor_shipping_methods).to eq(shipping_method_i.distributor_shipping_methods) + expect_distributor_shipping_methods_saved end def expect_opening_and_closing_times_saved @@ -241,4 +239,8 @@ describe ' expect(exchange.pickup_instructions).to eq('pickup instructions') expect(exchange.tag_list).to eq(['wholesale']) end + + def expect_distributor_shipping_methods_saved + expect(oc.distributor_shipping_methods).to eq(shipping_method_i.distributor_shipping_methods) + end end From acd4706890af54774a472fe415918b4ed0d872bc Mon Sep 17 00:00:00 2001 From: Cillian O'Ruanaidh Date: Fri, 30 Sep 2022 14:22:11 +0100 Subject: [PATCH 69/69] Set closing time correctly in order_cycles/complex_creating_specific_time_spec.rb --- .../admin/order_cycles/complex_creating_specific_time_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb index 9cfdae3061..4654b12109 100644 --- a/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb +++ b/spec/system/admin/order_cycles/complex_creating_specific_time_spec.rb @@ -83,7 +83,7 @@ describe ' def select_opening_and_closing_times select_time("#order_cycle_orders_open_at", order_cycle_opening_time) - select_time("#order_cycle_orders_close_at", order_cycle_opening_time) + select_time("#order_cycle_orders_close_at", order_cycle_closing_time) end def select_time(selector, time)