diff --git a/app/assets/javascripts/darkswarm/filters/properties.js.coffee b/app/assets/javascripts/darkswarm/filters/properties.js.coffee index 1453cac053..fe7ed71fa3 100644 --- a/app/assets/javascripts/darkswarm/filters/properties.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/properties.js.coffee @@ -1,13 +1,17 @@ Darkswarm.filter 'properties', -> # Filter anything that responds to object.supplied_properties - (objects, ids) -> + (objects, ids, source) -> objects ||= [] ids ?= [] + + source ||= 'properties' + return [] unless source in ['properties', 'supplied_properties', 'distributed_properties'] + if ids.length == 0 # No properties selected, pass all objects through. objects else objects.filter (obj) -> - properties = obj.supplied_properties || obj.properties + properties = obj[source] properties.some (property) -> property.id in ids diff --git a/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee index 7eac12bf1e..f3f4f51b5e 100644 --- a/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee @@ -1,12 +1,12 @@ Darkswarm.filter 'propertiesOf', -> - (objects) -> + (objects, source) -> + source ||= 'properties' + return {} unless source in ['properties', 'supplied_properties', 'distributed_properties'] + properties = {} for object in objects - if object.supplied_properties? - for property in object.supplied_properties - properties[property.id] = property - else - for property in object.properties + if object[source]? + for property in object[source] properties[property.id] = property properties diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass b/app/assets/stylesheets/darkswarm/_shop-filters.css.sass index 3c4f9a514d..07fea7a0b1 100644 --- a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass +++ b/app/assets/stylesheets/darkswarm/_shop-filters.css.sass @@ -96,7 +96,7 @@ // content. Ensure that the dropdown appears above the content. .filter-row position: relative - z-index: 100 + z-index: 90 .filter-shopfront &.taxon-selectors, &.property-selectors diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 6db2e583bc..dad9cf11f9 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -45,7 +45,7 @@ module InjectionHelper end def inject_properties - inject_json_ams "properties", Spree::Property.all, Api::IdNameSerializer + inject_json_ams "properties", Spree::Property.all, Api::PropertySerializer end def inject_currency_config diff --git a/app/helpers/serializer_helper.rb b/app/helpers/serializer_helper.rb new file mode 100644 index 0000000000..2e48e3741d --- /dev/null +++ b/app/helpers/serializer_helper.rb @@ -0,0 +1,6 @@ +module SerializerHelper + def ids_to_objs(ids) + return [] if ids.blank? + ids.map { |id| {id: id} } + end +end diff --git a/app/models/producer_property.rb b/app/models/producer_property.rb index bf1f083936..b0bb1b6e51 100644 --- a/app/models/producer_property.rb +++ b/app/models/producer_property.rb @@ -8,14 +8,18 @@ class ProducerProperty < ActiveRecord::Base after_destroy :refresh_products_cache_from_destroy - scope :sold_by, ->(shop) { + scope :ever_sold_by, ->(shop) { joins(producer: {supplied_products: {variants: {exchanges: :order_cycle}}}). merge(Exchange.outgoing). merge(Exchange.to_enterprise(shop)). - merge(OrderCycle.active). select('DISTINCT producer_properties.*') } + scope :currently_sold_by, ->(shop) { + ever_sold_by(shop). + merge(OrderCycle.active) + } + def property_name property.name if property diff --git a/app/models/spree/property_decorator.rb b/app/models/spree/property_decorator.rb index 50e450a03b..81577cef4e 100644 --- a/app/models/spree/property_decorator.rb +++ b/app/models/spree/property_decorator.rb @@ -8,14 +8,18 @@ module Spree where('spree_product_properties.product_id IN (?)', enterprise.supplied_product_ids) } - scope :sold_by, ->(shop) { + scope :ever_sold_by, ->(shop) { joins(products: {variants: {exchanges: :order_cycle}}). merge(Exchange.outgoing). merge(Exchange.to_enterprise(shop)). - merge(OrderCycle.active). select('DISTINCT spree_properties.*') } + scope :currently_sold_by, ->(shop) { + ever_sold_by(shop). + merge(OrderCycle.active) + } + after_save :refresh_products_cache diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb index a051de98fa..1878a20e49 100644 --- a/app/models/spree/taxon_decorator.rb +++ b/app/models/spree/taxon_decorator.rb @@ -32,21 +32,24 @@ Spree::Taxon.class_eval do end # Find all the taxons of distributed products for each enterprise, indexed by enterprise. + # May return :all taxons (distributed in open and closed order cycles), + # or :current taxons (distributed in an open order cycle). + # # Format: {enterprise_id => [taxon_id, ...]} - def self.distributed_taxons - taxons = {} + def self.distributed_taxons(which_taxons=:all) + # TODO: Why can't we merge(Spree::Product.with_order_cycles_inner) here? + taxons = Spree::Taxon. + joins(products: {variants_including_master: {exchanges: :order_cycle}}). + merge(Exchange.outgoing). + select('spree_taxons.*, exchanges.receiver_id AS enterprise_id') - Spree::Taxon. - joins(:products). - merge(Spree::Product.with_order_cycles_outer). - where('o_exchanges.incoming = ?', false). - select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id'). - each do |t| - taxons[t.enterprise_id.to_i] ||= Set.new - taxons[t.enterprise_id.to_i] << t.id - end + taxons = taxons.merge(OrderCycle.active) if which_taxons == :current - taxons + taxons.inject({}) do |ts, t| + ts[t.enterprise_id.to_i] ||= Set.new + ts[t.enterprise_id.to_i] << t.id + ts + end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index f68c8be679..3537bcdd24 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -20,7 +20,10 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer end class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer + include SerializerHelper + attributes :orders_close_at, :active + attributes :taxons, :supplied_taxons has_many :supplied_properties, serializer: Api::PropertySerializer has_many :distributed_properties, serializer: Api::PropertySerializer @@ -42,15 +45,37 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer def distributed_properties # This results in 3 queries per enterprise - product_properties = Spree::Property.sold_by(object) - ids = ProducerProperty.sold_by(object).pluck(:property_id) - producer_properties = Spree::Property.where(id: ids) + + if active + product_properties = Spree::Property.currently_sold_by(object) + producer_property_ids = ProducerProperty.currently_sold_by(object).pluck(:property_id) + + else + product_properties = Spree::Property.ever_sold_by(object) + producer_property_ids = ProducerProperty.ever_sold_by(object).pluck(:property_id) + end + + producer_properties = Spree::Property.where(id: producer_property_ids) OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties end + + def taxons + if active + ids_to_objs options[:data].current_distributed_taxons[object.id] + else + ids_to_objs options[:data].all_distributed_taxons[object.id] + end + end + + def supplied_taxons + ids_to_objs options[:data].supplied_taxons[object.id] + end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer + include SerializerHelper + cached #delegate :cache_key, to: :object @@ -65,16 +90,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer :email_address, :hash, :logo, :promo_image, :path, :pickup, :delivery, :icon, :icon_font, :producer_icon_font, :category, :producers, :hubs - attributes :taxons, :supplied_taxons - has_one :address, serializer: Api::AddressSerializer - def taxons - ids_to_objs options[:data].distributed_taxons[object.id] - end - - def supplied_taxons - ids_to_objs options[:data].supplied_taxons[object.id] - end def pickup services = options[:data].shipping_method_services[object.id] @@ -153,12 +169,4 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer } icon_fonts[object.category] end - - - private - - def ids_to_objs(ids) - return [] if ids.blank? - ids.map { |id| {id: id} } - end end diff --git a/app/serializers/api/property_serializer.rb b/app/serializers/api/property_serializer.rb index 7da4fce990..cdb3f2bae3 100644 --- a/app/serializers/api/property_serializer.rb +++ b/app/serializers/api/property_serializer.rb @@ -1,3 +1,9 @@ class Api::PropertySerializer < ActiveModel::Serializer attributes :id, :name, :presentation + + # Client-side we don't care about the property name. Send the presentation + # since this is what we want to show to the user. + def name + object.presentation + end end diff --git a/app/views/groups/_hub_filters.html.haml b/app/views/groups/_hub_filters.html.haml deleted file mode 100644 index 71924d5e85..0000000000 --- a/app/views/groups/_hub_filters.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.row - = render partial: 'shared/components/filter_controls' - = render partial: 'shared/components/show_profiles' - -.row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns - .row.filter-box - .small-12.large-9.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{"selector-set" => "filterSelectors", objects: "group_hubs | searchEnterprises:query | shipping:shippingTypes | showHubProfiles:show_profiles | taxonsOf", "active-selectors" => "activeTaxons"} - .small-12.large-3.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_delivery - %shipping-type-selector - -= render partial: 'shared/components/filter_box' diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index ef9e281155..90b74c0b42 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -60,23 +60,23 @@ .small-12.columns %h1 = t :groups_producers - = render partial: "shared/components/enterprise_search" - = render partial: "producers/filters" + = render "shared/components/enterprise_search" + = render "producers/filters" .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", - "ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties)", + "ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')", "ng-controller" => "GroupEnterpriseNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} .small-12.columns - = render partial: 'producers/skinny' - = render partial: 'producers/fat' + = render "producers/skinny" + = render "producers/fat" - = render partial: 'shared/components/enterprise_no_results' + = render 'shared/components/enterprise_no_results' %tab{heading: t(:groups_hubs), active: "tabs.hubs.active", @@ -87,24 +87,24 @@ %h1 = t :groups_hubs - = render partial: "shared/components/enterprise_search" - = render partial: "hub_filters" + = render "shared/components/enterprise_search" + = render "shops/filters", resource: "group_hubs", property_filters: "| searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles" .row .small-12.columns .active_table %hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}", - "ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])", + "ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | properties:activeProperties:'distributed_properties' | orderBy:['-active', '+orders_close_at'])", "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "ng-controller" => "GroupEnterpriseNodeCtrl"} .small-12.columns - = render partial: 'home/skinny' - = render partial: 'home/fat' + = render 'shops/skinny' + = render 'shops/fat' - = render partial: 'shared/components/enterprise_no_results' + = render 'shared/components/enterprise_no_results' .small-12.medium-12.large-3.columns - = render partial: 'contact' + = render 'contact' .small-12.columns.pad-top .row.pad-top @@ -122,4 +122,4 @@ %p   -= render partial: "shared/footer" += render "shared/footer" diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml deleted file mode 100644 index bfd13fdf54..0000000000 --- a/app/views/home/_filters.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.row - = render partial: 'shared/components/filter_controls' - -# .small-12.medium-6.columns   - = render partial: 'shared/components/show_profiles' - -.row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns - .row.filter-box - .small-12.large-9.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" } - .small-12.large-3.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_delivery - %shipping-type-selector - -= render partial: 'shared/components/filter_box' diff --git a/app/views/producers/_filters.html.haml b/app/views/producers/_filters.html.haml index 771ed932c9..17f260196a 100644 --- a/app/views/producers/_filters.html.haml +++ b/app/views/producers/_filters.html.haml @@ -18,6 +18,6 @@ = t :producers_filter = t :producers_filter_property .filter-shopfront.property-selectors - %single-line-selectors{ selectors: "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | propertiesOf", "active-selectors" => "activeProperties"} + %filter-selector{ "selector-set" => "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | taxons:activeTaxons | propertiesOf:'supplied_properties'", "active-selectors" => "activeProperties"} = render partial: 'shared/components/filter_box' diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index 04048ef77e..af6c2e4fd1 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -16,7 +16,7 @@ .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", - "ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties)", + "ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')", "ng-controller" => "ProducerNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 7efaba6cc9..10c7c20deb 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,5 +1,5 @@ .filter-shopfront.taxon-selectors.text-right - %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties: activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} + %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} .filter-shopfront.property-selectors.text-right %single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"} diff --git a/app/views/home/_fat.html.haml b/app/views/shops/_fat.html.haml similarity index 100% rename from app/views/home/_fat.html.haml rename to app/views/shops/_fat.html.haml diff --git a/app/views/shops/_filters.html.haml b/app/views/shops/_filters.html.haml new file mode 100644 index 0000000000..42e2b2ec28 --- /dev/null +++ b/app/views/shops/_filters.html.haml @@ -0,0 +1,34 @@ +- resource ||= "visibleMatches" +- property_filters ||= "| filter:filterExpression | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles" + +.row + = render 'shared/components/filter_controls' + -# .small-12.medium-6.columns   + = render 'shared/components/show_profiles' + +.row.animate-show.filter-row{"ng-show" => "filtersActive"} + .small-12.columns + .row.filter-box + .small-12.large-9.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_type + + %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "#{resource} | visible | taxonsOf", "active-selectors" => "activeTaxons" } + .small-12.large-3.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_delivery + %shipping-type-selector + + .small-12.large-12.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_property + .filter-shopfront.property-selectors + %filter-selector{ "selector-set" => "filterSelectors", objects: "#{resource} #{property_filters} | propertiesOf:'distributed_properties'", "active-selectors" => "activeProperties"} + += render 'shared/components/filter_box' diff --git a/app/views/home/_hubs.html.haml b/app/views/shops/_hubs.html.haml similarity index 89% rename from app/views/home/_hubs.html.haml rename to app/views/shops/_hubs.html.haml index e899270efc..6087d2330c 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/shops/_hubs.html.haml @@ -7,14 +7,14 @@ = t :hubs_intro = render "shared/components/enterprise_search" - = render "home/filters" + = render "filters" .row .small-12.columns .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"} %h2 = t :hubs_matches - = render "home/hubs_table", enterprises: "nameMatches" + = render "hubs_table", enterprises: "nameMatches" .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"} %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"} @@ -22,7 +22,7 @@ %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}... %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}... - = render "home/hubs_table", enterprises: "distanceMatches" + = render "hubs_table", enterprises: "distanceMatches" .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"} %a{href: "", "ng-click" => "showDistanceMatches()"} diff --git a/app/views/home/_hubs_table.html.haml b/app/views/shops/_hubs_table.html.haml similarity index 67% rename from app/views/home/_hubs_table.html.haml rename to app/views/shops/_hubs_table.html.haml index edf9eb5ec8..6d5803b477 100644 --- a/app/views/home/_hubs_table.html.haml +++ b/app/views/shops/_hubs_table.html.haml @@ -1,10 +1,10 @@ .active_table - %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | filter:filterExpression | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])", + %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | filter:filterExpression | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])", "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "ng-controller" => "HubNodeCtrl", id: "{{hub.hash}}"} .small-12.columns - = render 'home/skinny' - = render 'home/fat' + = render 'skinny' + = render 'fat' = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered" diff --git a/app/views/home/_skinny.html.haml b/app/views/shops/_skinny.html.haml similarity index 100% rename from app/views/home/_skinny.html.haml rename to app/views/shops/_skinny.html.haml diff --git a/app/views/shops/index.html.haml b/app/views/shops/index.html.haml index 1b3cf547a8..7e288f4157 100644 --- a/app/views/shops/index.html.haml +++ b/app/views/shops/index.html.haml @@ -10,5 +10,5 @@ %p.text-big = t :shops_text -= render partial: "home/hubs" -= render partial: "shared/footer" += render "hubs" += render "shared/footer" diff --git a/config/locales/en.yml b/config/locales/en.yml index 2211243bda..010cd4d586 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -513,6 +513,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using hubs_filter_by: "Filter by" hubs_filter_type: "Type" hubs_filter_delivery: "Delivery" + hubs_filter_property: "Property" hubs_matches: "Did you mean?" hubs_intro: Shop in your local area hubs_distance: Closest to diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 87516007c6..938c8b3aab 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -20,8 +20,12 @@ module OpenFoodNetwork @supplied_taxons ||= Spree::Taxon.supplied_taxons end - def distributed_taxons - @distributed_taxons ||= Spree::Taxon.distributed_taxons + def all_distributed_taxons + @all_distributed_taxons ||= Spree::Taxon.distributed_taxons(:all) + end + + def current_distributed_taxons + @current_distributed_taxons ||= Spree::Taxon.distributed_taxons(:current) end end end diff --git a/spec/features/consumer/groups_spec.rb b/spec/features/consumer/groups_spec.rb index 039322b379..4a5f166d7d 100644 --- a/spec/features/consumer/groups_spec.rb +++ b/spec/features/consumer/groups_spec.rb @@ -54,4 +54,43 @@ feature 'Groups', js: true do end end end + + describe "shops" do + describe "filtering by product property" do + let!(:group) { create(:enterprise_group, enterprises: [d1, d2], on_front_page: true) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } + let(:producer) { create(:supplier_enterprise) } + let(:d1) { create(:distributor_enterprise) } + let(:d2) { create(:distributor_enterprise) } + let(:p1) { create(:simple_product, supplier: producer) } + let(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } + let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } + let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } + + before do + producer.set_producer_property 'Organic', 'NASAA 12345' + p2.set_property 'Local', 'XYZ 123' + + ex_d1.variants << p1.variants.first + ex_d2.variants << p2.variants.first + + visit group_path(group, anchor: "/hubs") + end + + it "filters" do + toggle_filters + + toggle_filter 'Organic' + + expect(page).to have_content d1.name + expect(page).not_to have_content d2.name + + toggle_filter 'Organic' + toggle_filter 'Local' + + expect(page).not_to have_content d1.name + expect(page).to have_content d2.name + end + end + end end diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 2a65a73aef..f8f54a197b 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -47,7 +47,7 @@ feature 'Shops', js: true do it "should show closed shops after clicking the button" do create(:simple_product, distributors: [d1, d2]) visit shops_path - click_link_and_ensure("Show closed shops", -> { page.has_selector? 'hub.inactive' }) + click_link_and_ensure("Show Closed Shops", -> { page.has_selector? 'hub.inactive' }) page.should have_selector 'hub.inactive', text: d2.name end @@ -56,6 +56,67 @@ feature 'Shops', js: true do expect(page).to have_current_path enterprise_shop_path(distributor) end + describe "filtering by product property" do + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } + let!(:p1) { create(:simple_product, supplier: producer) } + let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } + let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } + let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } + + before do + p2.set_property 'Local', 'XYZ 123' + + ex_d1.variants << p1.variants.first + ex_d2.variants << p2.variants.first + + visit shops_path + end + + it "filters" do + toggle_filters + + toggle_filter 'Organic' + + expect(page).to have_content d1.name + expect(page).not_to have_content d2.name + + toggle_filter 'Organic' + toggle_filter 'Local' + + expect(page).not_to have_content d1.name + expect(page).to have_content d2.name + end + end + + describe "taxon badges" do + let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } + let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } + let(:shop) { create(:distributor_enterprise) } + let(:taxon_closed) { create(:taxon, name: 'Closed') } + + describe "open shops" do + let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } + let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } + let(:taxon_open) { create(:taxon, name: 'Open') } + + it "shows taxons for open order cycles only" do + visit shops_path + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Open' + expect(page).not_to have_selector '.fat-taxons', text: 'Closed' + end + end + + describe "closed shops" do + it "shows taxons for any order cycle" do + visit shops_path + click_link 'Show Closed Shops' + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Closed' + end + end + end + describe "property badges" do let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: producer) } diff --git a/spec/models/producer_property_spec.rb b/spec/models/producer_property_spec.rb index 2d0009ce12..0de7aae19b 100644 --- a/spec/models/producer_property_spec.rb +++ b/spec/models/producer_property_spec.rb @@ -8,7 +8,7 @@ describe ProducerProperty do producer.set_producer_property 'Organic Certified', 'NASAA 54321' end - describe ".sold_by" do + describe ".currently_sold_by and .ever_sold_by" do let!(:shop) { create(:distributor_enterprise) } let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: producer) } @@ -22,7 +22,8 @@ describe ProducerProperty do describe "with an associated producer property" do it "returns the producer property" do - expect(ProducerProperty.sold_by(shop)).to eq [pp] + expect(ProducerProperty.currently_sold_by(shop)).to eq [pp] + expect(ProducerProperty.ever_sold_by(shop)).to eq [pp] end end @@ -30,7 +31,8 @@ describe ProducerProperty do let!(:exchange) { create(:exchange, order_cycle: oc, incoming: true, sender: producer_other, receiver: oc.coordinator) } it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp_other + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp_other + expect(ProducerProperty.ever_sold_by(shop)).not_to include pp_other end end @@ -40,7 +42,8 @@ describe ProducerProperty do let!(:exchange) { create(:exchange, order_cycle: oc, incoming: false, sender: oc.coordinator, receiver: shop_other, variants: [product_other.variants.first]) } it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp_other + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp_other + expect(ProducerProperty.ever_sold_by(shop)).not_to include pp_other end end @@ -49,8 +52,12 @@ describe ProducerProperty do oc.update_attributes! orders_close_at: 1.week.ago end - it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp + it "doesn't return the producer property for .currently_sold_by" do + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp + end + + it "returns the producer property for .ever_sold_by" do + expect(ProducerProperty.ever_sold_by(shop)).to include pp end end @@ -59,7 +66,8 @@ describe ProducerProperty do let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first, product2.variants.first]) } it "doesn't return duplicates" do - expect(ProducerProperty.sold_by(shop).to_a.count).to eq 1 + expect(ProducerProperty.currently_sold_by(shop).to_a.count).to eq 1 + expect(ProducerProperty.ever_sold_by(shop).to_a.count).to eq 1 end end end diff --git a/spec/models/spree/property_spec.rb b/spec/models/spree/property_spec.rb index 6e6ed2d780..29046a8782 100644 --- a/spec/models/spree/property_spec.rb +++ b/spec/models/spree/property_spec.rb @@ -31,42 +31,53 @@ module Spree end end - describe ".sold_by" do + describe ".currently_sold_by and .ever_sold_by" do let!(:shop) { create(:distributor_enterprise) } let!(:shop_other) { create(:distributor_enterprise) } let!(:product) { create(:simple_product) } let!(:product_other_ex) { create(:simple_product) } let!(:product_no_oc) { create(:simple_product) } - let!(:product_closed_oc) { create(:simple_product) } let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) } - let!(:oc_closed) { create(:closed_order_cycle, distributors: [shop], variants: [product_closed_oc.variants.first]) } let!(:exchange_other_shop) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: shop_other, variants: [product_other_ex.variants.first]) } let(:property) { product.properties.last } let(:property_other_ex) { product_other_ex.properties.last } let(:property_no_oc) { product_no_oc.properties.last } - let(:property_closed_oc) { product_closed_oc.properties.last } before do product.set_property 'Organic', 'NASAA 12345' product_other_ex.set_property 'Biodynamic', 'ASDF 12345' product_no_oc.set_property 'Shiny', 'Very' - product_closed_oc.set_property 'Spiffy', 'Ooh yeah' end it "returns the property" do - expect(Property.sold_by(shop)).to eq [property] + expect(Property.currently_sold_by(shop)).to eq [property] + expect(Property.ever_sold_by(shop)).to eq [property] end it "doesn't return the property from another exchange" do - expect(Property.sold_by(shop)).not_to include property_other_ex + expect(Property.currently_sold_by(shop)).not_to include property_other_ex + expect(Property.ever_sold_by(shop)).not_to include property_other_ex end it "doesn't return the property with no order cycle" do - expect(Property.sold_by(shop)).not_to include property_no_oc + expect(Property.currently_sold_by(shop)).not_to include property_no_oc + expect(Property.ever_sold_by(shop)).not_to include property_no_oc end - it "doesn't return the property from a closed order cycle" do - expect(Property.sold_by(shop)).not_to include property_closed_oc + describe "closed order cyces" do + let!(:product_closed_oc) { create(:simple_product) } + let!(:oc_closed) { create(:closed_order_cycle, distributors: [shop], variants: [product_closed_oc.variants.first]) } + let(:property_closed_oc) { product_closed_oc.properties.last } + + before { product_closed_oc.set_property 'Spiffy', 'Ooh yeah' } + + it "doesn't return the property for .currently_sold_by" do + expect(Property.currently_sold_by(shop)).not_to include property_closed_oc + end + + it "returns the property for .ever_sold_by" do + expect(Property.ever_sold_by(shop)).to include property_closed_oc + end end context "with another product in the order cycle" do @@ -78,7 +89,8 @@ module Spree end it "doesn't return duplicates" do - expect(Property.sold_by(shop).to_a.count).to eq 1 + expect(Property.currently_sold_by(shop).to_a.count).to eq 1 + expect(Property.ever_sold_by(shop).to_a.count).to eq 1 end end end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 3a26c9b2aa..a031823bee 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -29,13 +29,18 @@ module Spree end end - describe "finding all distributed taxons" do - let!(:oc) { create(:simple_order_cycle, distributors: [e], variants: [p1.master]) } - let!(:s) { create(:supplier_enterprise) } - let!(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } + describe "finding distributed taxons" do + let!(:oc_open) { create(:open_order_cycle, distributors: [e], variants: [p_open.variants.first]) } + let!(:oc_closed) { create(:closed_order_cycle, distributors: [e], variants: [p_closed.variants.first]) } + let!(:p_open) { create(:simple_product, primary_taxon: t1) } + let!(:p_closed) { create(:simple_product, primary_taxon: t2) } - it "finds taxons" do - Taxon.distributed_taxons.should == {e.id => Set.new(p1.taxons.map(&:id))} + it "finds all distributed taxons" do + expect(Taxon.distributed_taxons(:all)).to eq({e.id => Set.new([t1.id, t2.id])}) + end + + it "finds currently distributed taxons" do + expect(Taxon.distributed_taxons(:current)).to eq({e.id => Set.new([t1.id])}) end end end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index f5eff4f771..bea2672157 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -6,7 +6,8 @@ describe Api::EnterpriseSerializer do let(:taxon) { create(:taxon) } let(:data) { OpenStruct.new(earliest_closing_times: {}, active_distributors: [], - distributed_taxons: {enterprise.id => [123]}, + all_distributed_taxons: {enterprise.id => [123]}, + current_distributed_taxons: {enterprise.id => [123]}, supplied_taxons: {enterprise.id => [456]}, shipping_method_services: {}, relatives: {enterprise.id => {producers: [123], distributors: [456]}}) }