mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-31 21:37:16 +00:00
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:
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user