diff --git a/app/services/order_cycles/distributed_products_service.rb b/app/services/order_cycles/distributed_products_service.rb index 5f349dae37..33756748a2 100644 --- a/app/services/order_cycles/distributed_products_service.rb +++ b/app/services/order_cycles/distributed_products_service.rb @@ -4,50 +4,19 @@ # The stock-checking includes on_demand and stock level overrides from variant_overrides. module OrderCycles - class DistributedProductsService + class DistributedProductsService # rubocop:disable Metrics/ClassLength def initialize(distributor, order_cycle, customer) @distributor = distributor @order_cycle = order_cycle @customer = customer end - # TODO refactor products_taxons_relation and products_supplier_relation - # Joins on the first product variant to allow us to filter product by taxon. # This is so - # enterprise can display product sorted by category in a custom order on their shopfront. - # - # Caveat, the category sorting won't work properly if there are multiple variant with different - # category for a given product. - # - def products_taxons_relation - Spree::Product.where(id: stocked_products). - joins("LEFT JOIN ( - SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id - FROM spree_variants WHERE deleted_at IS NULL - ) first_variant ON spree_products.id = first_variant.product_id"). - select("spree_products.*, first_variant.primary_taxon_id"). - group("spree_products.id, first_variant.primary_taxon_id") - end + def products_relation(supplier_properties: nil) + @query = relation_by_sorting(supplier_properties) - # Joins on the first product variant to allow us to filter product by supplier. This is so - # enterprise can display product sorted by supplier in a custom order on their shopfront. - # - # Caveat, the supplier sorting won't work properly if there are multiple variant with different - # supplier for a given product. - # - def products_supplier_relation - Spree::Product.where(id: stocked_products). - joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id - FROM spree_variants WHERE deleted_at IS NULL) first_variant - ON spree_products.id = first_variant.product_id"). - select("spree_products.*, first_variant.supplier_id"). - group("spree_products.id, first_variant.supplier_id") - end + supplier_property_join if supplier_properties.present? - def supplier_property_join(query) - query.joins(" - JOIN enterprises ON enterprises.id = first_variant.supplier_id - LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id - ") + @query.order(Arel.sql(order)) end def variants_relation @@ -61,6 +30,72 @@ module OrderCycles attr_reader :distributor, :order_cycle, :customer + def relation_by_sorting(supplier_properties) + query = Spree::Product.where(id: stocked_products) + + if sorting == "by_producer" || supplier_properties.present? + # Joins on the first product variant to allow us to filter product by supplier. This is so + # enterprise can display product sorted by supplier in a custom order on their shopfront. + # + # Caveat, the supplier sorting won't work properly if there are multiple variant with + # different supplier for a given product. + query. + joins("LEFT JOIN (SELECT DISTINCT ON(product_id) id, product_id, supplier_id + FROM spree_variants WHERE deleted_at IS NULL) first_variant + ON spree_products.id = first_variant.product_id"). + select("spree_products.*, first_variant.supplier_id"). + group("spree_products.id, first_variant.supplier_id") + elsif sorting == "by_category" + # Joins on the first product variant to allow us to filter product by taxon. # This is so + # enterprise can display product sorted by category in a custom order on their shopfront. + # + # Caveat, the category sorting won't work properly if there are multiple variant with + # different category for a given product. + query. + joins("LEFT JOIN ( + SELECT DISTINCT ON(product_id) id, product_id, primary_taxon_id + FROM spree_variants WHERE deleted_at IS NULL + ) first_variant ON spree_products.id = first_variant.product_id"). + select("spree_products.*, first_variant.primary_taxon_id"). + group("spree_products.id, first_variant.primary_taxon_id") + else + query.group("spree_products.id") + end + end + + def sorting + distributor.preferred_shopfront_product_sorting_method + end + + def supplier_property_join + @query = @query.joins(" + JOIN enterprises ON enterprises.id = first_variant.supplier_id + LEFT OUTER JOIN producer_properties ON producer_properties.producer_id = enterprises.id + ") + end + + def order + if distributor.preferred_shopfront_product_sorting_method == "by_producer" && + distributor.preferred_shopfront_producer_order.present? + order_by_producer = distributor + .preferred_shopfront_producer_order + .split(",").map { |id| "first_variant.supplier_id=#{id} DESC" } + .join(", ") + + "#{order_by_producer}, spree_products.name ASC, spree_products.id ASC" + elsif distributor.preferred_shopfront_product_sorting_method == "by_category" && + distributor.preferred_shopfront_taxon_order.present? + order_by_category = distributor + .preferred_shopfront_taxon_order + .split(",").map { |id| "first_variant.primary_taxon_id=#{id} DESC" } + .join(", ") + + "#{order_by_category}, spree_products.name ASC, spree_products.id ASC" + else + "spree_products.name ASC, spree_products.id" + end + end + def stocked_products order_cycle. variants_distributed_by(distributor). diff --git a/spec/services/order_cycles/distributed_products_service_spec.rb b/spec/services/order_cycles/distributed_products_service_spec.rb index 259955d41a..c352214a9a 100644 --- a/spec/services/order_cycles/distributed_products_service_spec.rb +++ b/spec/services/order_cycles/distributed_products_service_spec.rb @@ -3,7 +3,11 @@ require 'spec_helper' RSpec.describe OrderCycles::DistributedProductsService do - describe "#products_supplier_relation" do + describe "#products_relation" do + subject(:products_relation) { + described_class.new(distributor, order_cycle, customer).products_relation + } + let(:distributor) { create(:distributor_enterprise) } let(:product) { create(:product) } let(:variant) { product.variants.first } @@ -16,16 +20,12 @@ RSpec.describe OrderCycles::DistributedProductsService do supplier = create(:supplier_enterprise) variant.update(supplier: ) create(:variant, product:, supplier: ) - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).to eq([product]) + expect(products_relation).to eq([product]) end describe "product distributed by distributor in the OC" do it "returns products" do - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).to eq([product]) + expect(products_relation).to eq([product]) end end @@ -39,9 +39,7 @@ RSpec.describe OrderCycles::DistributedProductsService do end it "does not return product" do - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).not_to include product + expect(products_relation).not_to include product end end @@ -52,25 +50,20 @@ RSpec.describe OrderCycles::DistributedProductsService do end it "does not return product" do - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).not_to include product + expect(products_relation).not_to include product end end describe "filtering products that are out of stock" do context "with regular variants" do it "returns product when variant is in stock" do - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).to include product + expect(products_relation).to include product end it "does not return product when variant is out of stock" do variant.update_attribute(:on_hand, 0) - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).not_to include product + + expect(products_relation).not_to include product end end @@ -80,20 +73,80 @@ RSpec.describe OrderCycles::DistributedProductsService do } it "does not return product when an override is out of stock" do - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).not_to include product + expect(products_relation).not_to include product end it "returns product when an override is in stock" do variant.update_attribute(:on_hand, 0) override.update_attribute(:count_on_hand, 10) - expect( - described_class.new(distributor, order_cycle, customer).products_supplier_relation - ).to include product + + expect(products_relation).to include product end end end + + describe "sorting" do + let(:order_cycle) do + create(:simple_order_cycle, distributors: [distributor]) + end + let(:exchange) { order_cycle.exchanges.to_enterprises(distributor).outgoing.first } + + let(:fruits) { create(:taxon) } + let(:cakes) { create(:taxon) } + let(:fruits_supplier) { create(:supplier_enterprise) } + let(:cakes_supplier) { create(:supplier_enterprise) } + let!(:product_apples) { + create(:product, name: "apples", primary_taxon_id: fruits.id, + supplier_id: fruits_supplier.id, inherits_properties: true) + } + let!(:product_banana_bread) { + create(:product, name: "banana bread", primary_taxon_id: cakes.id, + supplier_id: cakes_supplier.id, inherits_properties: true) + } + let!(:product_cherries) { + create(:product, name: "cherries", primary_taxon_id: fruits.id, + supplier_id: fruits_supplier.id, inherits_properties: true) + } + let!(:product_doughnuts) { + create(:product, name: "doughnuts", primary_taxon_id: cakes.id, + supplier_id: cakes_supplier.id, inherits_properties: true) + } + + before do + exchange.variants << product_apples.variants.first + exchange.variants << product_banana_bread.variants.first + exchange.variants << product_cherries.variants.first + exchange.variants << product_doughnuts.variants.first + end + + it "sorts products by the distributor's preferred taxon list" do + allow(distributor) + .to receive(:preferred_shopfront_product_sorting_method) { "by_category" } + allow(distributor) + .to receive(:preferred_shopfront_taxon_order) { "#{cakes.id},#{fruits.id}" } + + expect(products_relation) + .to eq([product_banana_bread, product_doughnuts, product_apples, product_cherries]) + end + + it "sorts products by the distributor's preferred producer list" do + allow(distributor) + .to receive(:preferred_shopfront_product_sorting_method) { "by_producer" } + allow(distributor).to receive(:preferred_shopfront_producer_order) { + "#{cakes_supplier.id},#{fruits_supplier.id}" + } + + expect(products_relation) + .to eq([product_banana_bread, product_doughnuts, product_apples, product_cherries]) + end + + it "alphabetizes products by name when taxon list is not set" do + allow(distributor).to receive(:preferred_shopfront_taxon_order) { "" } + + expect(products_relation) + .to eq([product_apples, product_banana_bread, product_cherries, product_doughnuts]) + end + end end describe "#variants_relation" do