mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-25 05:45:15 +00:00
Merge pull request #12574 from rioug/refactor-products-renderer
[Product Refactor] Refactor products renderer
This commit is contained in:
@@ -96,7 +96,7 @@ module Api
|
||||
def distributed_products
|
||||
OrderCycles::DistributedProductsService.new(
|
||||
distributor, order_cycle, customer
|
||||
).products_supplier_relation.pluck(:id)
|
||||
).products_relation.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,50 +4,23 @@
|
||||
# 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")
|
||||
def products_relation
|
||||
relation_by_sorting.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
# 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
|
||||
def products_relation_incl_supplier_properties
|
||||
query = relation_by_sorting(supplier_properties: true)
|
||||
|
||||
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 = supplier_property_join(query)
|
||||
|
||||
query.order(Arel.sql(order))
|
||||
end
|
||||
|
||||
def variants_relation
|
||||
@@ -61,6 +34,72 @@ module OrderCycles
|
||||
|
||||
attr_reader :distributor, :order_cycle, :customer
|
||||
|
||||
def relation_by_sorting(supplier_properties: false)
|
||||
query = Spree::Product.where(id: stocked_products)
|
||||
|
||||
if sorting == "by_producer" || 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.
|
||||
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,7 @@
|
||||
require 'open_food_network/scope_product_to_hub'
|
||||
|
||||
|
||||
class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
class ProductsRenderer
|
||||
include Pagy::Backend
|
||||
|
||||
class NoProducts < RuntimeError; end
|
||||
@@ -35,8 +35,11 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
return unless order_cycle
|
||||
|
||||
@products ||= begin
|
||||
results = products_relation.
|
||||
order(Arel.sql(products_order))
|
||||
results = if supplier_properties.present?
|
||||
distributed_products.products_relation_incl_supplier_properties
|
||||
else
|
||||
distributed_products.products_relation
|
||||
end
|
||||
|
||||
results = filter(results)
|
||||
# Scope results with variant_overrides
|
||||
@@ -52,38 +55,19 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
OpenFoodNetwork::EnterpriseFeeCalculator.new distributor, order_cycle
|
||||
end
|
||||
|
||||
# TODO refactor this, distributed_products should be able to give use the relation based
|
||||
# on the sorting method, same for ordering. It would prevent the SQL implementation from
|
||||
# leaking here
|
||||
def products_relation
|
||||
if distributor.preferred_shopfront_product_sorting_method == "by_category" &&
|
||||
distributor.preferred_shopfront_taxon_order.present?
|
||||
return distributed_products.products_taxons_relation
|
||||
end
|
||||
|
||||
distributed_products.products_supplier_relation
|
||||
end
|
||||
|
||||
# TODO: refactor to address CyclomaticComplexity
|
||||
def filter(query) # rubocop:disable Metrics/CyclomaticComplexity
|
||||
supplier_properties = args[:q]&.slice("with_variants_supplier_properties")
|
||||
|
||||
def filter(query)
|
||||
ransack_results = query.ransack(args[:q]).result.to_a
|
||||
|
||||
return ransack_results if supplier_properties.blank?
|
||||
|
||||
with_properties = args[:q]&.dig("with_properties")
|
||||
supplier_properties_results = []
|
||||
|
||||
if supplier_properties.present?
|
||||
# We can't search on an association's scope with ransack, a work around is to define
|
||||
# the a scope on the parent (Spree::Product) but because we are joining on "first_variant"
|
||||
# to get the supplier it doesn't work, so we do the filtering manually here
|
||||
# see:
|
||||
# OrderCycleDistributedProducts#products_supplier_relation
|
||||
# OrderCycleDistributedProducts#supplier_property_join
|
||||
supplier_property_ids = supplier_properties["with_variants_supplier_properties"]
|
||||
supplier_properties_results = distributed_products.supplier_property_join(query).
|
||||
# OrderCycleDistributedProducts#products_relation
|
||||
supplier_properties_results = query.
|
||||
where(producer_properties: { property_id: supplier_property_ids }).
|
||||
where(inherits_properties: true)
|
||||
end
|
||||
@@ -101,6 +85,18 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
ransack_results
|
||||
end
|
||||
|
||||
def supplier_properties
|
||||
args[:q]&.slice("with_variants_supplier_properties")
|
||||
end
|
||||
|
||||
def supplier_property_ids
|
||||
supplier_properties["with_variants_supplier_properties"]
|
||||
end
|
||||
|
||||
def with_properties
|
||||
args[:q]&.dig("with_properties")
|
||||
end
|
||||
|
||||
def paginate(results)
|
||||
_pagy, paginated_results = pagy_array(
|
||||
results,
|
||||
@@ -115,27 +111,6 @@ class ProductsRenderer # rubocop:disable Metrics/ClassLength
|
||||
OrderCycles::DistributedProductsService.new(distributor, order_cycle, customer)
|
||||
end
|
||||
|
||||
# TODO refactor, see above
|
||||
def products_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 variants_for_shop
|
||||
@variants_for_shop ||= begin
|
||||
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
|
||||
|
||||
Reference in New Issue
Block a user