Refactor #products_relation

Due to primary_taxon and supplier having moved to the variant, filtering
by_producer and by_category with a custom order involves some
complicated sql queries. So we moved the sorting logic from
ProductsRenderer to OrderCycles::DistributedProductsService so we can
keep the complicated SQL logic contained in one place
This commit is contained in:
Gaetan Craig-Riou
2024-06-17 15:52:18 +10:00
parent 17fe492bf4
commit 79a22aefc3
2 changed files with 149 additions and 61 deletions

View File

@@ -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).

View File

@@ -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