Merge pull request #7712 from guidoDutra/1861-enable-shopfront-sorting-by-producer

Enable sorting by producer in shopfront
This commit is contained in:
Andy Brett
2021-07-06 09:27:42 -07:00
committed by GitHub
14 changed files with 176 additions and 40 deletions

View File

@@ -1,12 +1,14 @@
angular.module("admin.enterprises")
.controller "enterpriseCtrl", ($scope, $http, $window, NavigationCheck, enterprise, Enterprises, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) ->
.controller "enterpriseCtrl", ($scope, $http, $window, NavigationCheck, enterprise, Enterprises, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage, RequestMonitor) ->
$scope.Enterprise = enterprise
$scope.Enterprises = Enterprises
$scope.PaymentMethods = EnterprisePaymentMethods.paymentMethods
$scope.ShippingMethods = EnterpriseShippingMethods.shippingMethods
$scope.navClear = NavigationCheck.clear
$scope.menu = SideMenu
$scope.newManager = { id: null, email: (t('add_manager')) }
$scope.StatusMessage = StatusMessage
$scope.RequestMonitor = RequestMonitor
$scope.$watch 'enterprise_form.$dirty', (newValue) ->
StatusMessage.display 'notice', t('admin.unsaved_changes') if newValue
@@ -90,3 +92,6 @@ angular.module("admin.enterprises")
$scope.translation = (key) ->
t('js.admin.enterprises.form.images.' + key)
$scope.loadSuppliers = ->
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "visible", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")

View File

@@ -0,0 +1,18 @@
angular.module("admin.enterprises").directive "ofnProducerAutocomplete", (Enterprises, AutocompleteSelect2) ->
scope: true
link: (scope,element,attrs) ->
scope.loadSuppliers() if !scope.suppliers
multiple = scope.$eval attrs.multipleSelection
placeholder = attrs.placeholder
initialSelection = scope.$eval attrs.ngModel
setTimeout ->
scope.suppliers.$promise.then (data) ->
AutocompleteSelect2.autocomplete(
multiple,
placeholder,
element,
(-> Enterprises.findByID(initialSelection)),
(-> Enterprises.findByIDs(initialSelection)),
((term) -> Enterprises.findByTerm(scope.suppliers, term))
)

View File

@@ -1,4 +1,4 @@
angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource) ->
angular.module("admin.resources").factory 'Enterprises', ($q, $filter, EnterpriseResource) ->
new class Enterprises
byID: {}
pristineByID: {}
@@ -50,6 +50,17 @@ angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource
))
deferred.promise
findByID: (id) ->
@byID[id]
# For finding multiple Enterprises represented by comma delimited string
findByIDs: (ids) ->
@byID[id] for id in ids.split(",") when @byID[id]
findByTerm: (enterprises, term) ->
$filter('filter')(enterprises, term)
removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo)
removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage)
removeTermsAndConditions: performActionOnEnterpriseResource(EnterpriseResource.removeTermsAndConditions)

View File

@@ -1,30 +1,17 @@
angular.module("admin.taxons").directive "ofnTaxonAutocomplete", (Taxons, $sanitize) ->
angular.module("admin.taxons").directive "ofnTaxonAutocomplete", (Taxons, AutocompleteSelect2) ->
# Adapted from Spree's existing taxon autocompletion
scope: true
link: (scope,element,attrs) ->
multiple = scope.$eval attrs.multipleSelection
placeholder = attrs.placeholder
initalSelection = scope.$eval attrs.ngModel
initialSelection = scope.$eval attrs.ngModel
setTimeout ->
element.select2
placeholder: placeholder
multiple: multiple
initSelection: (element, callback) ->
if multiple
callback Taxons.findByIDs(initalSelection)
else
callback Taxons.findByID(initalSelection)
query: (query) ->
query.callback { results: Taxons.findByTerm(query.term) }
formatResult: (taxon) ->
$sanitize(taxon.name)
formatSelection: (taxon) ->
taxon.name
#Allows drag and drop
if multiple
element.select2("container").find("ul.select2-choices").sortable
containment: 'parent'
start: -> element.select2("onSortStart")
update: -> element.select2("onSortEnd")
AutocompleteSelect2.autocomplete(
multiple,
placeholder,
element,
(-> Taxons.findByID(initialSelection)),
(-> Taxons.findByIDs(initialSelection)),
((term) -> Taxons.findByTerm(term))
)

View File

@@ -0,0 +1,31 @@
angular.module("admin.utils").factory 'AutocompleteSelect2', ($sanitize) ->
scope: true
autocomplete: (
multiple,
placeholder,
element,
findByID,
findByIDs,
findByTerm
) ->
element.select2
placeholder: placeholder
multiple: multiple
initSelection: (element, callback) ->
if multiple
callback findByIDs()
else
callback findByID()
query: (query) ->
query.callback { results: findByTerm(query.term) }
formatResult: (item) ->
$sanitize(item.name)
formatSelection: (item) ->
item.name
#Allows drag and drop
if multiple
element.select2("container").find("ul.select2-choices").sortable
containment: 'parent'
start: -> element.select2("onSortStart")
update: -> element.select2("onSortEnd")

View File

@@ -1,11 +1,13 @@
$disabled-background: #c3c3c3;
label.disabled {
color: #c3c3c3;
color: $disabled-background;
pointer-events: none;
}
input[type='button'], input[type='submit'] {
&:disabled {
background-color: #c3c3c3;
background-color: $disabled-background;
color: #ffffff;
}
}
@@ -16,4 +18,8 @@ input[type='button'], input[type='submit'] {
.select2-choice > .select2-chosen {
color: #a1a1a1;
}
&.select2-container-multi .select2-choices .select2-search-choice {
background-color: $disabled-background;
}
}

View File

@@ -9,8 +9,10 @@ class Enterprise < ApplicationRecord
preference :shopfront_message, :text, default: ""
preference :shopfront_closed_message, :text, default: ""
preference :shopfront_taxon_order, :string, default: ""
preference :shopfront_producer_order, :string, default: ""
preference :shopfront_order_cycle_order, :string, default: "orders_close_at"
preference :show_customer_names_to_suppliers, :boolean, default: false
preference :shopfront_product_sorting_method, :string, default: "by_category"
# Allow hubs to restrict visible variants to only those in their inventory
preference :product_selection_from_inventory_only, :boolean, default: false
@@ -95,6 +97,7 @@ class Enterprise < ApplicationRecord
validates :owner, presence: true
validates :permalink, uniqueness: true, presence: true
validate :shopfront_taxons
validate :shopfront_producers
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
before_validation :initialize_permalink, if: lambda { permalink.nil? }
@@ -458,6 +461,12 @@ class Enterprise < ApplicationRecord
end
end
def shopfront_producers
unless preferred_shopfront_producer_order =~ /\A((\d+,)*\d+)?\z/
errors.add(:shopfront_category_ordering, "must contain a list of producers.")
end
end
def restore_permalink
# If the permalink has errors, reset it to it's original value, so we can update the form
self.permalink = permalink_was if permalink_changed? && errors[:permalink].present?

View File

@@ -7,8 +7,9 @@ module Api
:payment_method_ids, :shipping_method_ids, :producer_profile_only,
:long_description, :preferred_product_selection_from_inventory_only,
:preferred_shopfront_message, :preferred_shopfront_closed_message,
:preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order,
:preferred_show_customer_names_to_suppliers, :owner, :contact, :users, :tag_groups,
:preferred_shopfront_taxon_order, :preferred_shopfront_producer_order,
:preferred_shopfront_order_cycle_order, :preferred_show_customer_names_to_suppliers,
:preferred_shopfront_product_sorting_method, :owner, :contact, :users, :tag_groups,
:default_tag_group, :require_login, :allow_guest_orders, :allow_order_changes,
:logo, :promo_image, :terms_and_conditions,
:terms_and_conditions_file_name, :terms_and_conditions_updated_at

View File

@@ -30,7 +30,8 @@ module PermittedAttributes
:abn, :acn, :charges_sales_tax, :display_invoice_logo, :invoice_text,
:preferred_product_selection_from_inventory_only, :preferred_shopfront_message,
:preferred_shopfront_closed_message, :preferred_shopfront_taxon_order,
:preferred_shopfront_order_cycle_order, :preferred_show_customer_names_to_suppliers
:preferred_shopfront_producer_order, :preferred_shopfront_order_cycle_order,
:preferred_show_customer_names_to_suppliers, :preferred_shopfront_product_sorting_method,
]
end
end

View File

@@ -34,7 +34,7 @@ class ProductsRenderer
@products ||= begin
results = distributed_products.
products_relation.
order(Arel.sql(taxon_order))
order(Arel.sql(products_order))
filter_and_paginate(results).
each { |product| product_scoper.scope(product) } # Scope results with variant_overrides
@@ -61,8 +61,13 @@ class ProductsRenderer
OrderCycleDistributedProducts.new(distributor, order_cycle, customer)
end
def taxon_order
if distributor.preferred_shopfront_taxon_order.present?
def products_order
if distributor.preferred_shopfront_product_sorting_method == "by_producer" && distributor.preferred_shopfront_producer_order.present?
distributor
.preferred_shopfront_producer_order
.split(",").map { |id| "spree_products.supplier_id=#{id} DESC" }
.join(", ") + ", spree_products.name ASC, spree_products.id ASC"
elsif distributor.preferred_shopfront_product_sorting_method == "by_category" && distributor.preferred_shopfront_taxon_order.present?
distributor
.preferred_shopfront_taxon_order
.split(",").map { |id| "spree_products.primary_taxon_id=#{id} DESC" }

View File

@@ -12,13 +12,28 @@
%text-angular{'ng-model' => 'Enterprise.preferred_shopfront_closed_message', 'id' => 'enterprise_preferred_shopfront_closed_message', 'name' => 'enterprise[preferred_shopfront_closed_message]', 'class' => 'text-angular textangular-strip', 'ta-paste' => "stripFormatting($html)", "textangular-links-target-blank" => true,
'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]",
'placeholder' => t('.shopfront_closed_message_placeholder')}
.row
.three.columns.alpha
= f.label "enterprise_preferred_shopfront_taxon_order", t('.shopfront_category_ordering')
.text-normal
= t('.display_ordering_in_shopfront')
%br
= t('.shopfront_category_ordering_note')
.row
.three.columns.alpha
= radio_button :enterprise, :preferred_shopfront_product_sorting_method, :by_category, { 'ng-model' => 'Enterprise.preferred_shopfront_product_sorting_method' }
= label :enterprise, :preferred_shopfront_product_sorting_method_by_category, t('.shopfront_sort_by_category')
.eight.columns.omega
%textarea.fullwidth{ id: 'enterprise_preferred_shopfront_taxon_order', name: 'enterprise[preferred_shopfront_taxon_order]', rows: 6, 'ng-model' => 'Enterprise.preferred_shopfront_taxon_order', 'ofn-taxon-autocomplete' => '', 'multiple-selection' => 'true', placeholder: 'Category' }
%textarea.fullwidth{ id: 'enterprise_preferred_shopfront_taxon_order', name: 'enterprise[preferred_shopfront_taxon_order]', rows: 6,
'ofn-taxon-autocomplete' => '', 'multiple-selection' => 'true', placeholder: 'Category',
ng: { model: 'Enterprise.preferred_shopfront_taxon_order', readonly: "Enterprise.preferred_shopfront_product_sorting_method != 'by_category'" }}
.row
.three.columns.alpha
= radio_button :enterprise, :preferred_shopfront_product_sorting_method, :by_producer, { 'ng-model' => 'Enterprise.preferred_shopfront_product_sorting_method' }
= label :enterprise, :preferred_shopfront_product_sorting_method_by_producer, t('.shopfront_sort_by_producer')
.eight.columns.omega
%textarea.fullwidth{ id: 'enterprise_preferred_shopfront_producer_order', name: 'enterprise[preferred_shopfront_producer_order]', rows: 6,
'ofn-producer-autocomplete' => '', 'multiple-selection' => 'true', placeholder: 'Producer',
ng: { model: 'Enterprise.preferred_shopfront_producer_order', readonly: "Enterprise.preferred_shopfront_product_sorting_method != 'by_producer'" }}
.row
.three.columns.alpha

View File

@@ -876,6 +876,9 @@ en:
shopfront_category_ordering_note: "(top to bottom)"
open_date: "Open Date"
close_date: "Close Date"
display_ordering_in_shopfront: "Display ordering in shopfront:"
shopfront_sort_by_category: "By category"
shopfront_sort_by_producer: "By producer"
social:
twitter_placeholder: "eg. @the_prof"
instagram_placeholder: "eg. the_prof"

View File

@@ -188,6 +188,41 @@ describe Enterprise do
expect(enterprise).to be_invalid
end
end
describe "preferred_shopfront_producer_order" do
it "empty strings are valid" do
enterprise = build(:enterprise, preferred_shopfront_producer_order: "")
expect(enterprise).to be_valid
end
it "a single integer is valid" do
enterprise = build(:enterprise, preferred_shopfront_producer_order: "11")
expect(enterprise).to be_valid
end
it "comma delimited integers are valid" do
enterprise = build(:enterprise, preferred_shopfront_producer_order: "1,2,3")
expect(enterprise).to be_valid
enterprise = build(:enterprise, preferred_shopfront_producer_order: "1,22,333")
expect(enterprise).to be_valid
end
it "commas at the beginning and end are disallowed" do
enterprise = build(:enterprise, preferred_shopfront_producer_order: ",1,2,3")
expect(enterprise).to be_invalid
enterprise = build(:enterprise, preferred_shopfront_producer_order: "1,2,3,")
expect(enterprise).to be_invalid
end
it "any other characters are invalid" do
enterprise = build(:enterprise, preferred_shopfront_producer_order: "a1,2,3")
expect(enterprise).to be_invalid
enterprise = build(:enterprise, preferred_shopfront_producer_order: ".1,2,3")
expect(enterprise).to be_invalid
enterprise = build(:enterprise, preferred_shopfront_producer_order: " 1,2,3")
expect(enterprise).to be_invalid
end
end
end
describe "callbacks" do

View File

@@ -12,10 +12,12 @@ describe ProductsRenderer do
describe "sorting" do
let(:t1) { create(:taxon) }
let(:t2) { create(:taxon) }
let!(:p1) { create(:product, name: "abc", primary_taxon_id: t2.id) }
let!(:p2) { create(:product, name: "def", primary_taxon_id: t1.id) }
let!(:p3) { create(:product, name: "ghi", primary_taxon_id: t2.id) }
let!(:p4) { create(:product, name: "jkl", primary_taxon_id: t1.id) }
let(:s1) { create(:supplier_enterprise) }
let(:s2) { create(:supplier_enterprise) }
let!(:p1) { create(:product, name: "abc", primary_taxon_id: t2.id, supplier_id: s1.id) }
let!(:p2) { create(:product, name: "def", primary_taxon_id: t1.id, supplier_id: s2.id) }
let!(:p3) { create(:product, name: "ghi", primary_taxon_id: t2.id, supplier_id: s1.id) }
let!(:p4) { create(:product, name: "jkl", primary_taxon_id: t1.id, supplier_id: s2.id) }
before do
exchange.variants << p1.variants.first
@@ -30,6 +32,13 @@ describe ProductsRenderer do
expect(products).to eq([p2, p4, p1, p3])
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) { "#{s2.id},#{s1.id}" }
products = products_renderer.send(:products)
expect(products).to eq([p2, p4, p1, p3])
end
it "alphabetizes products by name when taxon list is not set" do
allow(distributor).to receive(:preferred_shopfront_taxon_order) { "" }
products = products_renderer.send(:products)