mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-15 23:57:48 +00:00
Merge branch 'master' of github.com:openfoodfoundation/openfoodnetwork into show-order-without-current-distributor
This commit is contained in:
3
.rspec_parallel
Normal file
3
.rspec_parallel
Normal file
@@ -0,0 +1,3 @@
|
||||
--format progress
|
||||
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
|
||||
--tag ~performance
|
||||
1
Gemfile
1
Gemfile
@@ -112,4 +112,5 @@ group :development do
|
||||
gem 'guard-rails'
|
||||
gem 'guard-zeus'
|
||||
gem 'guard-rspec'
|
||||
gem 'parallel_tests'
|
||||
end
|
||||
|
||||
@@ -362,6 +362,9 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
cocaine (~> 0.5.3)
|
||||
mime-types
|
||||
parallel (1.4.1)
|
||||
parallel_tests (1.3.7)
|
||||
parallel
|
||||
paypal-sdk-core (0.2.10)
|
||||
multi_json (~> 1.0)
|
||||
xml-simple
|
||||
@@ -575,6 +578,7 @@ DEPENDENCIES
|
||||
newrelic_rpm
|
||||
oj
|
||||
paperclip
|
||||
parallel_tests
|
||||
pg
|
||||
poltergeist
|
||||
pry-debugger
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth) ->
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, tax_categories) ->
|
||||
$scope.loading = true
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
@@ -10,7 +10,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
unit: {name: "Unit", visible: true}
|
||||
price: {name: "Price", visible: true}
|
||||
on_hand: {name: "On Hand", visible: true}
|
||||
on_demand: {name: "On Demand", visible: false}
|
||||
category: {name: "Category", visible: false}
|
||||
tax_category: {name: "Tax Category", visible: false}
|
||||
inherits_properties: {name: "Inherits Properties?", visible: false}
|
||||
available_on: {name: "Available On", visible: false}
|
||||
|
||||
@@ -32,6 +34,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
|
||||
$scope.producers = producers
|
||||
$scope.taxons = Taxons.taxons
|
||||
$scope.tax_categories = tax_categories
|
||||
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
|
||||
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
|
||||
$scope.producerFilter = "0"
|
||||
@@ -106,6 +109,12 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
|
||||
window.location = "/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
|
||||
|
||||
|
||||
$scope.toggleShowAllVariants = ->
|
||||
showVariants = !DisplayProperties.showVariants 0
|
||||
$scope.filteredProducts.forEach (product) ->
|
||||
DisplayProperties.setShowVariants product.id, showVariants
|
||||
DisplayProperties.setShowVariants 0, showVariants
|
||||
|
||||
$scope.addVariant = (product) ->
|
||||
product.variants.push
|
||||
id: $scope.nextVariantId()
|
||||
@@ -312,9 +321,15 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
|
||||
filteredProduct.on_hand = product.on_hand
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present
|
||||
filteredProduct.on_demand = product.on_demand
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("category_id")
|
||||
filteredProduct.primary_taxon_id = product.category_id
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("tax_category_id")
|
||||
filteredProduct.tax_category_id = product.tax_category_id
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("inherits_properties")
|
||||
filteredProduct.inherits_properties = product.inherits_properties
|
||||
hasUpdatableProperty = true
|
||||
@@ -340,6 +355,9 @@ filterSubmitVariant = (variant) ->
|
||||
if variant.hasOwnProperty("on_hand")
|
||||
filteredVariant.on_hand = variant.on_hand
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("on_demand")
|
||||
filteredVariant.on_demand = variant.on_demand
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("price")
|
||||
filteredVariant.price = variant.price
|
||||
hasUpdatableProperty = true
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) ->
|
||||
link: (scope, element, attrs) ->
|
||||
if DisplayProperties.showVariants scope.product.id
|
||||
element.removeClass "icon-chevron-right"
|
||||
element.addClass "icon-chevron-down"
|
||||
else
|
||||
element.removeClass "icon-chevron-down"
|
||||
element.addClass "icon-chevron-right"
|
||||
|
||||
element.on "click", ->
|
||||
@@ -16,4 +14,4 @@ angular.module("ofn.admin").directive "ofnToggleVariants", (DisplayProperties) -
|
||||
else
|
||||
DisplayProperties.setShowVariants scope.product.id, true
|
||||
element.removeClass "icon-chevron-right"
|
||||
element.addClass "icon-chevron-down"
|
||||
element.addClass "icon-chevron-down"
|
||||
|
||||
@@ -19,7 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher)
|
||||
# when a respond_overrride for the clone action is used.
|
||||
id = data.product.id
|
||||
dataFetcher("/api/products/" + id + "?template=bulk_show").then (newProduct) =>
|
||||
@addProducts [newProduct]
|
||||
@insertProductAfter(product, newProduct)
|
||||
|
||||
updateVariantLists: (serverProducts, productsWithUnsavedVariants) ->
|
||||
for product in productsWithUnsavedVariants
|
||||
@@ -39,6 +39,10 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher)
|
||||
@unpackProduct product
|
||||
@products.push product
|
||||
|
||||
insertProductAfter: (product, newProduct) ->
|
||||
index = @products.indexOf(product)
|
||||
@products.splice(index + 1, 0, newProduct)
|
||||
|
||||
unpackProduct: (product) ->
|
||||
#$scope.matchProducer product
|
||||
@loadVariantUnit product
|
||||
|
||||
@@ -3,12 +3,10 @@ angular.module("ofn.admin").factory "DisplayProperties", ->
|
||||
displayProperties: {}
|
||||
|
||||
showVariants: (product_id) ->
|
||||
@initProduct product_id
|
||||
@displayProperties[product_id].showVariants
|
||||
@productProperties(product_id).showVariants
|
||||
|
||||
setShowVariants: (product_id, showVariants) ->
|
||||
@initProduct product_id
|
||||
@displayProperties[product_id].showVariants = showVariants
|
||||
@productProperties(product_id).showVariants = showVariants
|
||||
|
||||
initProduct: (product_id) ->
|
||||
productProperties: (product_id) ->
|
||||
@displayProperties[product_id] ||= {showVariants: false}
|
||||
|
||||
@@ -32,8 +32,9 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
|
||||
if product.variants
|
||||
product.variants = (Variants.register variant for variant in product.variants)
|
||||
variant.product = product for variant in product.variants
|
||||
product.master.product = product
|
||||
product.master = Variants.register product.master if product.master
|
||||
if product.master
|
||||
product.master.product = product
|
||||
product.master = Variants.register product.master
|
||||
|
||||
registerVariantsWithCart: ->
|
||||
for product in @products
|
||||
|
||||
@@ -12,9 +12,6 @@ class BaseController < ApplicationController
|
||||
|
||||
before_filter :check_order_cycle_expiry
|
||||
|
||||
def load_active_distributors
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ class CheckoutController < Spree::CheckoutController
|
||||
include EnterprisesHelper
|
||||
|
||||
def edit
|
||||
# Because this controller doesn't inherit from our BaseController
|
||||
# We need to duplicate the code here
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
end
|
||||
|
||||
def update
|
||||
|
||||
@@ -4,7 +4,7 @@ class EnterprisesController < BaseController
|
||||
include OrderCyclesHelper
|
||||
|
||||
# These prepended filters are in the reverse order of execution
|
||||
prepend_before_filter :load_active_distributors, :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
before_filter :clean_permalink, only: :check_permalink
|
||||
|
||||
respond_to :js, only: :permalink_checker
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class GroupsController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
def index
|
||||
@groups = EnterpriseGroup.on_front_page.by_position
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
class HomeController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def about_us
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class MapController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class ProducersController < BaseController
|
||||
layout 'darkswarm'
|
||||
before_filter :load_active_distributors
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,12 +10,16 @@ class ShopController < BaseController
|
||||
end
|
||||
|
||||
def products
|
||||
# Can we make this query less slow?
|
||||
#
|
||||
if @products = products_for_shop
|
||||
|
||||
render status: 200,
|
||||
json: ActiveModel::ArraySerializer.new(@products, each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: current_order_cycle, current_distributor: current_distributor).to_json
|
||||
json: ActiveModel::ArraySerializer.new(@products,
|
||||
each_serializer: Api::ProductSerializer,
|
||||
current_order_cycle: current_order_cycle,
|
||||
current_distributor: current_distributor,
|
||||
variants: variants_for_shop_by_id,
|
||||
master_variants: master_variants_for_shop_by_id).to_json
|
||||
|
||||
else
|
||||
render json: "", status: 404
|
||||
end
|
||||
@@ -56,4 +60,30 @@ class ShopController < BaseController
|
||||
"name ASC"
|
||||
end
|
||||
end
|
||||
|
||||
def all_variants_for_shop
|
||||
# We use the in_stock? method here instead of the in_stock scope because we need to
|
||||
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
|
||||
# by them.
|
||||
Spree::Variant.
|
||||
for_distribution(current_order_cycle, current_distributor).
|
||||
each { |v| v.scope_to_hub current_distributor }.
|
||||
select(&:in_stock?)
|
||||
end
|
||||
|
||||
def variants_for_shop_by_id
|
||||
index_by_product_id all_variants_for_shop.reject(&:is_master)
|
||||
end
|
||||
|
||||
def master_variants_for_shop_by_id
|
||||
index_by_product_id all_variants_for_shop.select(&:is_master)
|
||||
end
|
||||
|
||||
def index_by_product_id(variants)
|
||||
variants.inject({}) do |vs, v|
|
||||
vs[v.product_id] ||= []
|
||||
vs[v.product_id] << v
|
||||
vs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,6 +23,13 @@ Spree::Admin::OrdersController.class_eval do
|
||||
distributed_by_user(spree_current_user).
|
||||
page(params[:page]).
|
||||
per(params[:per_page] || Spree::Config[:orders_per_page])
|
||||
# Filter orders by distributor
|
||||
if params[:distributor_ids]
|
||||
@orders = @orders.where(distributor_id: params[:distributor_ids])
|
||||
end
|
||||
if params[:order_cycle_ids]
|
||||
@orders = @orders.where(order_cycle_id: params[:order_cycle_ids])
|
||||
end
|
||||
} } }
|
||||
|
||||
# Overwrite to use confirm_email_for_customer instead of confirm_email.
|
||||
|
||||
@@ -50,6 +50,10 @@ module Admin
|
||||
admin_inject_json_ams_array "ofn.admin", "products", @products, Api::Admin::ProductSerializer
|
||||
end
|
||||
|
||||
def admin_inject_tax_categories
|
||||
admin_inject_json_ams_array "ofn.admin", "tax_categories", @tax_categories, Api::Admin::TaxCategorySerializer
|
||||
end
|
||||
|
||||
def admin_inject_taxons
|
||||
admin_inject_json_ams_array "admin.taxons", "taxons", @taxons, Api::Admin::TaxonSerializer
|
||||
end
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
require 'open_food_network/enterprise_injection_data'
|
||||
|
||||
module InjectionHelper
|
||||
def inject_enterprises
|
||||
inject_json_ams "enterprises", Enterprise.activated.all, Api::EnterpriseSerializer, active_distributors: @active_distributors
|
||||
inject_json_ams "enterprises", Enterprise.activated.includes(:address).all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_current_hub
|
||||
inject_json_ams "currentHub", current_distributor, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_current_order
|
||||
@@ -53,4 +63,13 @@ module InjectionHelper
|
||||
end
|
||||
render partial: "json/injection_ams", locals: {name: name, json: json}
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def enterprise_injection_data
|
||||
@enterprise_injection_data ||= OpenFoodNetwork::EnterpriseInjectionData.new
|
||||
{data: @enterprise_injection_data}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -178,6 +178,10 @@ class Enterprise < ActiveRecord::Base
|
||||
count(distinct: true)
|
||||
end
|
||||
|
||||
def activated?
|
||||
confirmed_at.present? && sells != 'unspecified'
|
||||
end
|
||||
|
||||
def set_producer_property(property_name, property_value)
|
||||
transaction do
|
||||
property = Spree::Property.where(name: property_name).first_or_create!(presentation: property_name)
|
||||
|
||||
@@ -25,6 +25,32 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name')
|
||||
|
||||
|
||||
# Load an array of the relatives of each enterprise (ie. any enterprise related to it in
|
||||
# either direction). This array is split into distributors and producers, and has the format:
|
||||
# {enterprise_id => {distributors: [id, ...], producers: [id, ...]} }
|
||||
def self.relatives(activated_only=false)
|
||||
relationships = EnterpriseRelationship.includes(:child, :parent)
|
||||
relatives = {}
|
||||
|
||||
relationships.each do |r|
|
||||
relatives[r.parent_id] ||= {distributors: Set.new, producers: Set.new}
|
||||
relatives[r.child_id] ||= {distributors: Set.new, producers: Set.new}
|
||||
|
||||
if !activated_only || r.child.activated?
|
||||
relatives[r.parent_id][:producers] << r.child_id if r.child.is_primary_producer
|
||||
relatives[r.parent_id][:distributors] << r.child_id if r.child.is_distributor
|
||||
end
|
||||
|
||||
if !activated_only || r.parent.activated?
|
||||
relatives[r.child_id][:producers] << r.parent_id if r.parent.is_primary_producer
|
||||
relatives[r.child_id][:distributors] << r.parent_id if r.parent.is_distributor
|
||||
end
|
||||
end
|
||||
|
||||
relatives
|
||||
end
|
||||
|
||||
|
||||
def permissions_list=(perms)
|
||||
perms.andand.each { |name| permissions.build name: name }
|
||||
end
|
||||
|
||||
@@ -92,11 +92,25 @@ class OrderCycle < ActiveRecord::Base
|
||||
with_distributor(distributor).soonest_closing.first
|
||||
end
|
||||
|
||||
|
||||
def self.most_recently_closed_for(distributor)
|
||||
with_distributor(distributor).most_recently_closed.first
|
||||
end
|
||||
|
||||
# Find the earliest closing times for each distributor in an active order cycle, and return
|
||||
# them in the format {distributor_id => closing_time, ...}
|
||||
def self.earliest_closing_times
|
||||
Hash[
|
||||
Exchange.
|
||||
outgoing.
|
||||
joins(:order_cycle).
|
||||
merge(OrderCycle.active).
|
||||
group('exchanges.receiver_id').
|
||||
select('exchanges.receiver_id AS receiver_id, MIN(order_cycles.orders_close_at) AS earliest_close_at').
|
||||
map { |ex| [ex.receiver_id, ex.earliest_close_at.to_time] }
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def clone!
|
||||
oc = self.dup
|
||||
oc.name = "COPY OF #{oc.name}"
|
||||
|
||||
@@ -109,6 +109,12 @@ Spree::Product.class_eval do
|
||||
|
||||
# -- Methods
|
||||
|
||||
# Called by Spree::Product::duplicate before saving.
|
||||
def duplicate_extra(parent)
|
||||
# Spree sets the SKU to "COPY OF #{parent sku}".
|
||||
self.master.sku = ''
|
||||
end
|
||||
|
||||
def properties_including_inherited
|
||||
# Product properties override producer properties
|
||||
ps = product_properties.all
|
||||
|
||||
@@ -25,6 +25,22 @@ Spree::ShippingMethod.class_eval do
|
||||
|
||||
scope :by_name, order('spree_shipping_methods.name ASC')
|
||||
|
||||
|
||||
# Return the services (pickup, delivery) that different distributors provide, in the format:
|
||||
# {distributor_id => {pickup: true, delivery: false}, ...}
|
||||
def self.services
|
||||
Hash[
|
||||
Spree::ShippingMethod.
|
||||
joins(:distributor_shipping_methods).
|
||||
group('distributor_id').
|
||||
select("distributor_id").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 'f') AS pickup").
|
||||
select("BOOL_OR(spree_shipping_methods.require_ship_address = 't') AS delivery").
|
||||
map { |sm| [sm.distributor_id.to_i, {pickup: sm.pickup == 't', delivery: sm.delivery == 't'}] }
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
def available_to_order_with_distributor_check?(order, display_on=nil)
|
||||
available_to_order_without_distributor_check?(order, display_on) &&
|
||||
self.distributors.include?(order.distributor)
|
||||
|
||||
@@ -9,4 +9,40 @@ Spree::Taxon.class_eval do
|
||||
#fs << Spree::ProductFilters.distributor_filter if Spree::ProductFilters.respond_to? :distributor_filter
|
||||
fs
|
||||
end
|
||||
|
||||
# Find all the taxons of supplied products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.supplied_taxons
|
||||
taxons = {}
|
||||
|
||||
Spree::Taxon.
|
||||
joins(:products => :supplier).
|
||||
select('spree_taxons.*, enterprises.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
|
||||
end
|
||||
|
||||
# Find all the taxons of distributed products for each enterprise, indexed by enterprise.
|
||||
# Format: {enterprise_id => [taxon_id, ...]}
|
||||
def self.distributed_taxons
|
||||
taxons = {}
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ Spree::Variant.class_eval do
|
||||
|
||||
has_many :exchange_variants, dependent: :destroy
|
||||
has_many :exchanges, through: :exchange_variants
|
||||
has_many :variant_overrides
|
||||
|
||||
attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name
|
||||
accepts_nested_attributes_for :images
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/ insert_before "div.clearfix"
|
||||
|
||||
.field-block.alpha.eight.columns
|
||||
= label_tag nil, t(:distributors)
|
||||
= select_tag(:distributor_ids,
|
||||
options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]),
|
||||
{class: "select2 fullwidth", multiple: true})
|
||||
|
||||
.field-block.alpha.eight.columns
|
||||
= label_tag nil, t(:order_cycles)
|
||||
= select_tag(:order_cycle_ids,
|
||||
options_for_select(OrderCycle.managed_by(spree_current_user).map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]),
|
||||
{class: "select2 fullwidth", multiple: true})
|
||||
@@ -38,20 +38,26 @@
|
||||
.twelve.columns.alpha
|
||||
.six.columns.alpha
|
||||
= render 'spree/admin/products/primary_taxon_form', f: f
|
||||
.three.columns
|
||||
.two.columns
|
||||
= f.field_container :price do
|
||||
= f.label :price, t(:price)
|
||||
%span.required *
|
||||
%br/
|
||||
= f.text_field :price, class: 'fullwidth'
|
||||
= f.error_message_on :price
|
||||
.three.columns.omega
|
||||
.two.columns
|
||||
= f.field_container :on_hand do
|
||||
= f.label :on_hand, t(:on_hand)
|
||||
%span.required *
|
||||
%br/
|
||||
= f.text_field :on_hand, class: 'fullwidth'
|
||||
= f.error_message_on :on_hand
|
||||
.two.columns.omega
|
||||
= f.field_container :on_demand do
|
||||
= f.label :on_demand, t(:on_demand)
|
||||
%br/
|
||||
= f.check_box :on_demand
|
||||
= f.error_message_on :on_demand
|
||||
.twelve.columns.alpha
|
||||
.six.columns.alpha
|
||||
.three.columns
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Api::Admin::ProductSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties
|
||||
|
||||
attributes :on_hand, :price, :available_on, :permalink_live
|
||||
attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id
|
||||
|
||||
has_one :supplier, key: :producer_id, embed: :id
|
||||
has_one :primary_taxon, key: :category_id, embed: :id
|
||||
|
||||
3
app/serializers/api/admin/tax_category_serializer.rb
Normal file
3
app/serializers/api/admin/tax_category_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Api::Admin::TaxCategorySerializer < ActiveModel::Serializer
|
||||
attributes :id, :name
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
class Api::Admin::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display
|
||||
attributes :on_hand, :price
|
||||
has_many :variant_overrides
|
||||
|
||||
def on_hand
|
||||
object.on_hand.nil? ? 0 : ( object.on_hand.to_f.finite? ? object.on_hand : "On demand" )
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class Api::EnterpriseSerializer < ActiveModel::Serializer
|
||||
# We reference this here because otherwise the serializer complains about its absence
|
||||
Api::IdSerializer
|
||||
|
||||
def serializable_hash
|
||||
cached_serializer_hash.merge uncached_serializer_hash
|
||||
end
|
||||
@@ -6,11 +9,11 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer
|
||||
private
|
||||
|
||||
def cached_serializer_hash
|
||||
Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash
|
||||
Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash || {}
|
||||
end
|
||||
|
||||
def uncached_serializer_hash
|
||||
Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash
|
||||
Api::UncachedEnterpriseSerializer.new(object, @options).serializable_hash || {}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,19 +21,22 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
attributes :orders_close_at, :active
|
||||
|
||||
def orders_close_at
|
||||
OrderCycle.first_closing_for(object).andand.orders_close_at
|
||||
options[:data].earliest_closing_times[object.id]
|
||||
end
|
||||
|
||||
def active
|
||||
@options[:active_distributors].andand.include? object
|
||||
options[:data].active_distributors.andand.include? object
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
cached
|
||||
delegate :cache_key, to: :object
|
||||
#delegate :cache_key, to: :object
|
||||
|
||||
def cache_key
|
||||
object.andand.cache_key
|
||||
end
|
||||
|
||||
|
||||
attributes :name, :id, :description, :latitude, :longitude,
|
||||
:long_description, :website, :instagram, :linkedin, :twitter,
|
||||
@@ -38,17 +44,27 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
:email, :hash, :logo, :promo_image, :path, :pickup, :delivery,
|
||||
:icon, :icon_font, :producer_icon_font, :category, :producers, :hubs
|
||||
|
||||
has_many :distributed_taxons, key: :taxons, serializer: Api::IdSerializer
|
||||
has_many :supplied_taxons, serializer: Api::IdSerializer
|
||||
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
|
||||
object.shipping_methods.where(:require_ship_address => false).present?
|
||||
services = options[:data].shipping_method_services[object.id]
|
||||
services ? services[:pickup] : false
|
||||
end
|
||||
|
||||
def delivery
|
||||
object.shipping_methods.where(:require_ship_address => true).present?
|
||||
services = options[:data].shipping_method_services[object.id]
|
||||
services ? services[:delivery] : false
|
||||
end
|
||||
|
||||
def email
|
||||
@@ -72,11 +88,13 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def producers
|
||||
ActiveModel::ArraySerializer.new(object.suppliers.activated, {each_serializer: Api::IdSerializer})
|
||||
relatives = options[:data].relatives[object.id]
|
||||
relatives ? ids_to_objs(relatives[:producers]) : []
|
||||
end
|
||||
|
||||
def hubs
|
||||
ActiveModel::ArraySerializer.new(object.distributors.activated, {each_serializer: Api::IdSerializer})
|
||||
relatives = options[:data].relatives[object.id]
|
||||
relatives ? ids_to_objs(relatives[:distributors]) : []
|
||||
end
|
||||
|
||||
# Map svg icons.
|
||||
@@ -116,4 +134,11 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
|
||||
}
|
||||
icon_fonts[object.category]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def ids_to_objs(ids)
|
||||
ids.andand.map { |id| {id: id} }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,8 +30,9 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
#cached
|
||||
#delegate :cache_key, to: :object
|
||||
|
||||
attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy,
|
||||
:notes, :description, :properties_with_values
|
||||
attributes :id, :name, :permalink, :count_on_hand
|
||||
attributes :on_demand, :group_buy, :notes, :description
|
||||
attributes :properties_with_values
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
has_many :taxons, serializer: Api::IdSerializer
|
||||
@@ -46,13 +47,11 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def variants
|
||||
# We use the in_stock? method here instead of the in_stock scope because we need to
|
||||
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
|
||||
# by them.
|
||||
|
||||
object.variants.
|
||||
for_distribution(options[:current_order_cycle], options[:current_distributor]).
|
||||
each { |v| v.scope_to_hub options[:current_distributor] }.
|
||||
select(&:in_stock?)
|
||||
options[:variants][object.id] || []
|
||||
end
|
||||
|
||||
def master
|
||||
options[:master_variants][object.id].andand.first
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%shop.darkswarm
|
||||
- content_for :order_cycle_form do
|
||||
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl"}
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing{"ng-if" => "OrderCycle.selected()"}
|
||||
Next order closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
= inject_enterprises
|
||||
|
||||
-# inject enterprises in this group
|
||||
-# further hubs and producers of these enterprises can't be resoleved within this small subset
|
||||
= inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, active_distributors: @active_distributors
|
||||
-# further hubs and producers of these enterprises can't be resolved within this small subset
|
||||
= inject_group_enterprises
|
||||
|
||||
#group-page.row.pad-top{"ng-controller" => "GroupPageCtrl"}
|
||||
.small-12.columns.pad-top
|
||||
@@ -95,7 +95,7 @@
|
||||
= render partial: 'home/fat'
|
||||
|
||||
= render partial: 'shared/components/enterprise_no_results'
|
||||
|
||||
|
||||
.small-12.medium-12.large-3.columns
|
||||
= render partial: 'contact'
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
object current_distributor
|
||||
extends 'json/partials/enterprise'
|
||||
|
||||
child suppliers: :producers do
|
||||
attributes :id
|
||||
end
|
||||
@@ -24,7 +24,7 @@
|
||||
= render partial: "shared/ie_warning"
|
||||
= javascript_include_tag "iehack"
|
||||
|
||||
= inject_json "currentHub", "current_hub"
|
||||
= inject_current_hub
|
||||
= inject_json "user", "current_user"
|
||||
= inject_json "railsFlash", "flash"
|
||||
= inject_taxons
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%section.left
|
||||
%a.left-off-canvas-toggle.menu-icon
|
||||
%span
|
||||
%section.right
|
||||
%section.right{"ng-cloak" => true}
|
||||
.cart
|
||||
= render partial: "shared/menu/cart"
|
||||
%a{href: main_app.shop_path}
|
||||
@@ -11,34 +11,34 @@
|
||||
%aside.left-off-canvas-menu.show-for-medium-down
|
||||
%ul.off-canvas-list
|
||||
%li.ofn-logo
|
||||
%a{href: root_path}
|
||||
%a{href: root_path}
|
||||
%img{src: "/assets/open-food-network-beta.png", srcset: "/assets/open-food-network-beta.svg", width: "110", height: "26"}
|
||||
|
||||
|
||||
- if current_page? root_path
|
||||
%li.li-menu
|
||||
%a{"ofn-scroll-to" => "hubs"}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_040-hub
|
||||
Hubs
|
||||
- else
|
||||
%li.li-menu
|
||||
%a{href: root_path + "#/#hubs"}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_040-hub
|
||||
Hubs
|
||||
%li.li-menu
|
||||
%a{href: main_app.map_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_037-map
|
||||
Map
|
||||
%li.li-menu
|
||||
%a{href: main_app.producers_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_036-producers
|
||||
Producers
|
||||
%li.li-menu
|
||||
%a{href: main_app.groups_path}
|
||||
%span.nav-primary
|
||||
%span.nav-primary
|
||||
%i.ofn-i_035-groups
|
||||
Groups
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null",
|
||||
%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true,
|
||||
"infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1"}
|
||||
|
||||
// TODO: Needs an ng-show to slide content down
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#tabs{"ng-controller" => "TabsCtrl"}
|
||||
#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true}
|
||||
.row
|
||||
%tabset
|
||||
-# Build all tabs.
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 6],
|
||||
producers: ["Producers",2],
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 6],
|
||||
producers: ["Producers",2],
|
||||
contact: ["Contact",2],
|
||||
groups: ["Groups",2]}
|
||||
groups: ["Groups",2]}
|
||||
-# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl.
|
||||
- heading, cols = heading_cols
|
||||
%tab.columns{heading: heading,
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
= render 'spree/admin/products/bulk_edit/actions'
|
||||
= render 'spree/admin/products/bulk_edit/indicators'
|
||||
= render 'spree/admin/products/bulk_edit/products'
|
||||
= render 'spree/admin/products/bulk_edit/save_button_row'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
= admin_inject_producers
|
||||
= admin_inject_taxons
|
||||
= admin_inject_tax_categories
|
||||
= admin_inject_spree_api_key
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
%col.display_as{ ng: { show: 'columns.unit.visible' } }
|
||||
%col.price{ ng: { show: 'columns.price.visible'} }
|
||||
%col.on_hand{ ng: { show: 'columns.on_hand.visible' } }
|
||||
%col.on_demand{ ng: { show: 'columns.on_demand.visible' } }
|
||||
%col.category{ ng: { show: 'columns.category.visible' } }
|
||||
%col.tax_category{ ng: { show: 'columns.tax_category.visible' } }
|
||||
%col.inherits_properties{ ng: { show: 'columns.inherits_properties.visible' } }
|
||||
%col.available_on{ ng: { show: 'columns.available_on.visible' } }
|
||||
%col.actions
|
||||
@@ -17,6 +19,8 @@
|
||||
%thead
|
||||
%tr
|
||||
%th.left-actions
|
||||
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' }
|
||||
Expand All
|
||||
%th.producer{ 'ng-show' => 'columns.producer.visible' } Producer
|
||||
%th.sku{ 'ng-show' => 'columns.sku.visible' } SKU
|
||||
%th.name{ 'ng-show' => 'columns.name.visible' } Name
|
||||
@@ -24,7 +28,9 @@
|
||||
%th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As
|
||||
%th.price{ 'ng-show' => 'columns.price.visible' } Price
|
||||
%th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand
|
||||
%th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand
|
||||
%th.category{ 'ng-show' => 'columns.category.visible' } Category
|
||||
%th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category
|
||||
%th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties?
|
||||
%th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On
|
||||
%th.actions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%tr.product{ :id => "p_{{product.id}}" }
|
||||
%td.left-actions
|
||||
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants icon-chevron-right", 'ng-show' => 'hasVariants(product)' }
|
||||
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' }
|
||||
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
|
||||
%td.producer{ 'ng-show' => 'columns.producer.visible' }
|
||||
%select.select2.fullwidth{ 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
|
||||
@@ -20,8 +20,13 @@
|
||||
%td.on_hand{ 'ng-show' => 'columns.on_hand.visible' }
|
||||
%span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
|
||||
%input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' }
|
||||
%td.on_demand{ 'ng-show' => 'columns.on_demand.visible' }
|
||||
%input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' }
|
||||
%td.category{ 'ng-if' => 'columns.category.visible' }
|
||||
%input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' }
|
||||
%td.tax_category{ 'ng-if' => 'columns.tax_category.visible' }
|
||||
%select.select2{ name: 'product_tax_category_id', 'ofn-track-product' => 'tax_category_id', ng: {model: 'product.tax_category_id', options: 'tax_category.id as tax_category.name for tax_category in tax_categories'} }
|
||||
%option{value: ''} None
|
||||
%td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }
|
||||
%input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" }
|
||||
%td.available_on{ 'ng-show' => 'columns.available_on.visible' }
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
%td{ 'ng-show' => 'columns.on_hand.visible' }
|
||||
%input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
|
||||
%span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' }
|
||||
%td{ 'ng-show' => 'columns.on_demand.visible' }
|
||||
%input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' }
|
||||
%td{ 'ng-show' => 'columns.category.visible' }
|
||||
%td{ 'ng-show' => 'columns.tax_category.visible' }
|
||||
%td{ 'ng-show' => 'columns.inherits_properties.visible' }
|
||||
%td{ 'ng-show' => 'columns.available_on.visible' }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" }
|
||||
%td.actions
|
||||
%span.icon-warning-sign.with-tip{ 'ng-if' => 'variant.variant_overrides', title: "This variant has {{variant.variant_overrides.length}} override(s)" }
|
||||
%td.actions
|
||||
%a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text" }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" }
|
||||
%div.four.columns.alpha
|
||||
%input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'}
|
||||
@@ -10,7 +10,7 @@ development:
|
||||
test:
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: open_food_network_test
|
||||
database: open_food_network_test<%= ENV['TEST_ENV_NUMBER'] %>
|
||||
pool: 5
|
||||
host: localhost
|
||||
username: ofn
|
||||
|
||||
27
lib/open_food_network/enterprise_injection_data.rb
Normal file
27
lib/open_food_network/enterprise_injection_data.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
module OpenFoodNetwork
|
||||
class EnterpriseInjectionData
|
||||
def active_distributors
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
end
|
||||
|
||||
def earliest_closing_times
|
||||
@earliest_closing_times ||= OrderCycle.earliest_closing_times
|
||||
end
|
||||
|
||||
def shipping_method_services
|
||||
@shipping_method_services ||= Spree::ShippingMethod.services
|
||||
end
|
||||
|
||||
def relatives
|
||||
@relatives ||= EnterpriseRelationship.relatives(true)
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
@supplied_taxons ||= Spree::Taxon.supplied_taxons
|
||||
end
|
||||
|
||||
def distributed_taxons
|
||||
@distributed_taxons ||= Spree::Taxon.distributed_taxons
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -47,7 +47,7 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def variants
|
||||
filter(child_variants) + filter(master_variants)
|
||||
filter(child_variants)
|
||||
end
|
||||
|
||||
def child_variants
|
||||
@@ -57,16 +57,6 @@ module OpenFoodNetwork
|
||||
.order("spree_products.name")
|
||||
end
|
||||
|
||||
def master_variants
|
||||
Spree::Variant.where(:is_master => true)
|
||||
.joins(:product)
|
||||
.where("(select spree_variants.id from spree_variants as other_spree_variants
|
||||
WHERE other_spree_variants.product_id = spree_variants.product_id
|
||||
AND other_spree_variants.is_master = 'f' LIMIT 1) IS NULL")
|
||||
.merge(visible_products)
|
||||
.order("spree_products.name")
|
||||
end
|
||||
|
||||
def filter(variants)
|
||||
# NOTE: Ordering matters.
|
||||
# filter_to_order_cycle and filter_to_distributor return Arrays not Arel
|
||||
@@ -107,7 +97,7 @@ module OpenFoodNetwork
|
||||
def filter_to_order_cycle(variants)
|
||||
if params[:order_cycle_id].to_i > 0
|
||||
order_cycle = OrderCycle.find params[:order_cycle_id]
|
||||
variants.select! { |v| order_cycle.variants.include? v }
|
||||
variants.select { |v| order_cycle.variants.include? v }
|
||||
else
|
||||
variants
|
||||
end
|
||||
|
||||
@@ -6,5 +6,9 @@ source ./script/ci/includes.sh
|
||||
echo "--- Verifying branch is based on current master"
|
||||
exit_unless_master_merged
|
||||
|
||||
echo "--- Pushing branch"
|
||||
git push origin $BUILDKITE_COMMIT:master
|
||||
echo "--- Merging and pushing branch"
|
||||
git checkout master
|
||||
git merge origin/master
|
||||
git merge origin/$BUILDKITE_BRANCH
|
||||
git push origin master
|
||||
git checkout origin/$BUILDKITE_BRANCH
|
||||
|
||||
@@ -13,7 +13,8 @@ echo "--- Bundling"
|
||||
bundle install
|
||||
|
||||
echo "--- Loading test database"
|
||||
bundle exec rake db:test:load
|
||||
bundle exec rake db:drop db:create db:schema:load
|
||||
bundle exec rake parallel:drop parallel:create parallel:load_schema
|
||||
|
||||
echo "--- Running tests"
|
||||
bundle exec rspec spec
|
||||
bundle exec rake parallel:spec
|
||||
|
||||
@@ -24,9 +24,4 @@ describe BaseController do
|
||||
response.should redirect_to root_url
|
||||
flash[:info].should == "The order cycle you've selected has just closed. Please try again!"
|
||||
end
|
||||
|
||||
it "loads active_distributors" do
|
||||
Enterprise.stub(:distributors_with_active_order_cycles) { 'active distributors' }
|
||||
controller.load_active_distributors.should == 'active distributors'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,21 +9,9 @@ describe HomeController do
|
||||
Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] }
|
||||
end
|
||||
|
||||
it "sets active distributors" do
|
||||
get :index
|
||||
assigns[:active_distributors].should == [distributor]
|
||||
end
|
||||
|
||||
# Exclusion from actual rendered view handled in features/consumer/home
|
||||
it "shows JSON for invisible hubs" do
|
||||
get :index
|
||||
response.body.should have_content invisible_distributor.name
|
||||
end
|
||||
|
||||
# This is done inside the json/hubs Serializer
|
||||
it "gets the next order cycle for each hub" do
|
||||
OrderCycle.should_receive(:first_closing_for).twice
|
||||
get :index
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe MapController do
|
||||
it "loads active distributors" do
|
||||
active_distributors = double(:distributors)
|
||||
|
||||
Enterprise.stub(:distributors_with_active_order_cycles) { active_distributors }
|
||||
|
||||
get :index
|
||||
|
||||
assigns(:active_distributors).should == active_distributors
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe ProducersController do
|
||||
let!(:distributor) { create(:distributor_enterprise) }
|
||||
|
||||
before do
|
||||
Enterprise.stub(:distributors_with_active_order_cycles) { [distributor] }
|
||||
Enterprise.stub(:all).and_return([distributor])
|
||||
end
|
||||
|
||||
it "sets active distributors" do
|
||||
get :index
|
||||
assigns[:active_distributors].should == [distributor]
|
||||
end
|
||||
end
|
||||
@@ -176,4 +176,18 @@ describe ShopController do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "loading variants" do
|
||||
let(:hub) { create(:distributor_enterprise) }
|
||||
let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) }
|
||||
let(:p) { create(:simple_product) }
|
||||
let!(:v1) { create(:variant, product: p, unit_value: 3) }
|
||||
let!(:v2) { create(:variant, product: p, unit_value: 5) }
|
||||
|
||||
it "scopes variants to distribution" do
|
||||
controller.stub(:current_order_cycle) { oc }
|
||||
controller.stub(:current_distributor) { hub }
|
||||
controller.send(:variants_for_shop_by_id).should == {p.id => [v1]}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -205,8 +205,9 @@ feature %q{
|
||||
expect(page).to have_selector "a.edit-variant", count: 1
|
||||
|
||||
# When I remove two, they should be removed
|
||||
page.all('a.delete-variant').first.click
|
||||
page.all('a.delete-variant').first.click
|
||||
page.all('a.delete-variant', visible: true).first.click
|
||||
expect(page).to have_selector "tr.variant", count: 2
|
||||
page.all('a.delete-variant', visible: true).first.click
|
||||
expect(page).to have_selector "tr.variant", count: 1
|
||||
|
||||
# When I fill out variant details and hit update
|
||||
@@ -216,7 +217,7 @@ feature %q{
|
||||
fill_in "variant_price", with: "4.0"
|
||||
fill_in "variant_on_hand", with: "10"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
updated_variant = Spree::Variant.where(deleted_at: nil).last
|
||||
@@ -266,7 +267,7 @@ feature %q{
|
||||
fill_in "product_sku", with: "NEW SKU"
|
||||
end
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
p.reload
|
||||
@@ -292,7 +293,7 @@ feature %q{
|
||||
select "Items", from: "variant_unit_with_scale"
|
||||
fill_in "variant_unit_name", with: "loaf"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
p.reload
|
||||
@@ -326,7 +327,7 @@ feature %q{
|
||||
|
||||
expect(page).to have_selector "span[name='on_hand']", text: "10"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
v.reload
|
||||
@@ -352,7 +353,7 @@ feature %q{
|
||||
fill_in "variant_price", with: "10.0"
|
||||
end
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
v.reload
|
||||
@@ -369,21 +370,21 @@ feature %q{
|
||||
|
||||
fill_in "product_name", with: "new name 1"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
p.reload
|
||||
expect(p.name).to eq "new name 1"
|
||||
|
||||
fill_in "product_name", with: "new name 2"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
p.reload
|
||||
expect(p.name).to eq "new name 2"
|
||||
|
||||
fill_in "product_name", with: "original name"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
p.reload
|
||||
expect(p.name).to eq "original name"
|
||||
@@ -399,7 +400,7 @@ feature %q{
|
||||
|
||||
fill_in "product_name", :with => "new product name"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
p.reload
|
||||
expect(p.name).to eq "new product name"
|
||||
@@ -412,7 +413,7 @@ feature %q{
|
||||
|
||||
visit '/admin/products/bulk_edit'
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "No changes to save."
|
||||
end
|
||||
end
|
||||
@@ -431,7 +432,7 @@ feature %q{
|
||||
expect(page).to have_no_field "product_name", with: p2.name
|
||||
fill_in "product_name", :with => "new product1"
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
p1.reload
|
||||
expect(p1.name).to eq "new product1"
|
||||
@@ -709,7 +710,7 @@ feature %q{
|
||||
fill_in "variant_display_as", with: "Big Bag"
|
||||
end
|
||||
|
||||
click_button 'Save Changes'
|
||||
first(:button, 'Save Changes').click
|
||||
expect(page.find("#status-message")).to have_content "Changes saved."
|
||||
|
||||
p.reload
|
||||
|
||||
@@ -4,7 +4,7 @@ describe InjectionHelper do
|
||||
let!(:enterprise) { create(:distributor_enterprise, facebook: "roger") }
|
||||
|
||||
it "will inject via AMS" do
|
||||
helper.inject_json_ams("test", [enterprise], Api::EnterpriseSerializer).should match enterprise.name
|
||||
helper.inject_json_ams("test", [enterprise], Api::IdSerializer).should match /#{enterprise.id}/
|
||||
end
|
||||
|
||||
it "injects enterprises" do
|
||||
|
||||
@@ -61,14 +61,14 @@ describe "BulkProducts service", ->
|
||||
clonedProduct =
|
||||
id: 17
|
||||
|
||||
spyOn(BulkProducts, "addProducts")
|
||||
spyOn(BulkProducts, "insertProductAfter")
|
||||
BulkProducts.products = [originalProduct]
|
||||
$httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200,
|
||||
product: clonedProduct
|
||||
$httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200, clonedProduct
|
||||
BulkProducts.cloneProduct BulkProducts.products[0]
|
||||
$httpBackend.flush()
|
||||
expect(BulkProducts.addProducts).toHaveBeenCalledWith [clonedProduct]
|
||||
expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct
|
||||
|
||||
|
||||
describe "preparing products", ->
|
||||
|
||||
@@ -215,6 +215,7 @@ describe "filtering products for submission to database", ->
|
||||
variant_unit_scale: 1
|
||||
variant_unit_name: 'loaf'
|
||||
available_on: available_on
|
||||
tax_category_id: null
|
||||
master_attributes:
|
||||
id: 2
|
||||
unit_value: 250
|
||||
@@ -238,6 +239,7 @@ describe "AdminProductEditCtrl", ->
|
||||
module ($provide)->
|
||||
$provide.value "producers", []
|
||||
$provide.value "taxons", []
|
||||
$provide.value "tax_categories", []
|
||||
$provide.value 'SpreeApiKey', 'API_KEY'
|
||||
null
|
||||
|
||||
|
||||
17
spec/lib/open_food_network/enterprise_injection_data_spec.rb
Normal file
17
spec/lib/open_food_network/enterprise_injection_data_spec.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
require 'spec_helper'
|
||||
|
||||
module OpenFoodNetwork
|
||||
describe EnterpriseInjectionData do
|
||||
describe "relatives" do
|
||||
let!(:enterprise) { create(:distributor_enterprise) }
|
||||
let!(:producer) { create(:supplier_enterprise) }
|
||||
let!(:producer_inactive) { create(:supplier_enterprise, confirmed_at: nil) }
|
||||
let!(:er_p) { create(:enterprise_relationship, parent: producer, child: enterprise) }
|
||||
let!(:er_pi) { create(:enterprise_relationship, parent: producer_inactive, child: enterprise) }
|
||||
|
||||
it "only loads activated relatives" do
|
||||
subject.relatives[enterprise.id][:producers].should_not include producer_inactive.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -54,10 +54,8 @@ module OpenFoodNetwork
|
||||
|
||||
it "fetches variants for some params" do
|
||||
subject.should_receive(:child_variants).and_return ["children"]
|
||||
subject.should_receive(:master_variants).and_return ["masters"]
|
||||
subject.should_receive(:filter).with(['children']).and_return ["filter_children"]
|
||||
subject.should_receive(:filter).with(['masters']).and_return ["filter_masters"]
|
||||
subject.variants.should == ["filter_children", "filter_masters"]
|
||||
subject.variants.should == ["filter_children"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,14 +90,6 @@ module OpenFoodNetwork
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetching master variants" do
|
||||
it "doesn't return master variants with siblings" do
|
||||
product = create(:simple_product, supplier: supplier)
|
||||
|
||||
subject.master_variants.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "Filtering variants" do
|
||||
let(:variants) { Spree::Variant.scoped.joins(:product).where(is_master: false) }
|
||||
it "should return unfiltered variants sans-params" do
|
||||
|
||||
@@ -69,4 +69,32 @@ describe EnterpriseRelationship do
|
||||
EnterpriseRelationship.with_permission('two').should match_array [er1, er2]
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding relatives" do
|
||||
let(:e1) { create(:supplier_enterprise) }
|
||||
let(:e2) { create(:supplier_enterprise, sells: 'any') }
|
||||
let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) }
|
||||
let(:er_reverse) { create(:enterprise_relationship, parent: e2, child: e1) }
|
||||
|
||||
it "categorises enterprises into distributors and producers" do
|
||||
EnterpriseRelationship.relatives.should ==
|
||||
{e1.id => {distributors: Set.new([e2.id]), producers: Set.new([e2.id])},
|
||||
e2.id => {distributors: Set.new([]), producers: Set.new([e1.id])}}
|
||||
end
|
||||
|
||||
it "finds inactive enterprises by default" do
|
||||
e1.update_attribute :confirmed_at, nil
|
||||
EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id])
|
||||
end
|
||||
|
||||
it "does not find inactive enterprises when requested" do
|
||||
e1.update_attribute :confirmed_at, nil
|
||||
EnterpriseRelationship.relatives(true)[e2.id][:producers].should be_empty
|
||||
end
|
||||
|
||||
it "does not show duplicates" do
|
||||
er_reverse
|
||||
EnterpriseRelationship.relatives[e2.id][:producers].should == Set.new([e1.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -422,4 +422,23 @@ describe OrderCycle do
|
||||
OrderCycle.first_closing_for(distributor).should == oc
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding the earliest closing times for each distributor" do
|
||||
let(:time1) { 1.week.from_now }
|
||||
let(:time2) { 2.weeks.from_now }
|
||||
let(:time3) { 3.weeks.from_now }
|
||||
let(:e1) { create(:distributor_enterprise) }
|
||||
let(:e2) { create(:distributor_enterprise) }
|
||||
let!(:oc1) { create(:simple_order_cycle, orders_close_at: time1, distributors: [e1]) }
|
||||
let!(:oc2) { create(:simple_order_cycle, orders_close_at: time2, distributors: [e2]) }
|
||||
let!(:oc3) { create(:simple_order_cycle, orders_close_at: time3, distributors: [e2]) }
|
||||
|
||||
it "returns the closing time, indexed by enterprise id" do
|
||||
OrderCycle.earliest_closing_times[e1.id].should == time1
|
||||
end
|
||||
|
||||
it "returns the earliest closing time" do
|
||||
OrderCycle.earliest_closing_times[e2.id].should == time2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,5 +55,32 @@ module Spree
|
||||
sm.should be_available_to_order o
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding services offered by all distributors" do
|
||||
let!(:d1) { create(:distributor_enterprise) }
|
||||
let!(:d2) { create(:distributor_enterprise) }
|
||||
let!(:d3) { create(:distributor_enterprise) }
|
||||
let!(:d4) { create(:distributor_enterprise) }
|
||||
let!(:d1_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d1]) }
|
||||
let!(:d1_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d1]) }
|
||||
let!(:d2_pickup) { create(:shipping_method, require_ship_address: false, distributors: [d2]) }
|
||||
let!(:d3_delivery) { create(:shipping_method, require_ship_address: true, distributors: [d3]) }
|
||||
|
||||
it "reports when the services are available" do
|
||||
ShippingMethod.services[d1.id].should == {pickup: true, delivery: true}
|
||||
end
|
||||
|
||||
it "reports when only pickup is available" do
|
||||
ShippingMethod.services[d2.id].should == {pickup: true, delivery: false}
|
||||
end
|
||||
|
||||
it "reports when only delivery is available" do
|
||||
ShippingMethod.services[d3.id].should == {pickup: false, delivery: true}
|
||||
end
|
||||
|
||||
it "returns no entry when no service is available" do
|
||||
ShippingMethod.services[d4.id].should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
28
spec/models/spree/taxon_spec.rb
Normal file
28
spec/models/spree/taxon_spec.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require 'spec_helper'
|
||||
|
||||
module Spree
|
||||
describe Taxon do
|
||||
let(:e) { create(:supplier_enterprise) }
|
||||
let(:t0) { p1.taxons.order('id ASC').first }
|
||||
let(:t1) { create(:taxon) }
|
||||
let(:t2) { create(:taxon) }
|
||||
|
||||
describe "finding all supplied taxons" do
|
||||
let!(:p1) { create(:simple_product, supplier: e, taxons: [t1, t2]) }
|
||||
|
||||
it "finds taxons" do
|
||||
Taxon.supplied_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])}
|
||||
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]) }
|
||||
|
||||
it "finds taxons" do
|
||||
Taxon.distributed_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
spec/performance/injection_helper_spec.rb
Normal file
29
spec/performance/injection_helper_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe InjectionHelper, type: :helper, performance: true do
|
||||
let(:oc) { create(:simple_order_cycle) }
|
||||
let(:relative_supplier) { create(:supplier_enterprise) }
|
||||
let(:relative_distributor) { create(:distributor_enterprise) }
|
||||
|
||||
before do
|
||||
50.times do
|
||||
e = create(:enterprise)
|
||||
oc.distributors << e
|
||||
create(:enterprise_relationship, parent: e, child: relative_supplier)
|
||||
create(:enterprise_relationship, parent: e, child: relative_distributor)
|
||||
end
|
||||
end
|
||||
|
||||
it "is performant in injecting enterprises" do
|
||||
results = []
|
||||
4.times do |i|
|
||||
ActiveRecord::Base.connection.query_cache.clear
|
||||
Rails.cache.clear
|
||||
result = Benchmark.measure { helper.inject_enterprises }
|
||||
results << result.total if i > 0
|
||||
puts result
|
||||
end
|
||||
|
||||
puts (results.sum / results.count * 1000).round 0
|
||||
end
|
||||
end
|
||||
46
spec/performance/shop_controller_spec.rb
Normal file
46
spec/performance/shop_controller_spec.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe ShopController, type: :controller, performance: true do
|
||||
let(:d) { create(:distributor_enterprise) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee) }
|
||||
let(:order_cycle) { create(:simple_order_cycle, distributors: [d], coordinator_fees: [enterprise_fee]) }
|
||||
|
||||
before do
|
||||
controller.stub(:current_distributor) { d }
|
||||
controller.stub(:current_order_cycle) { order_cycle }
|
||||
end
|
||||
|
||||
describe "fetching products" do
|
||||
let(:exchange) { order_cycle.exchanges.to_enterprises(d).outgoing.first }
|
||||
let(:image) { File.open(File.expand_path('../../../app/assets/images/logo.jpg', __FILE__)) }
|
||||
|
||||
before do
|
||||
11.times do
|
||||
p = create(:simple_product)
|
||||
p.set_property 'Organic Certified', 'NASAA 12345'
|
||||
v1 = create(:variant, product: p)
|
||||
v2 = create(:variant, product: p)
|
||||
Spree::Image.create! viewable_id: p.master.id, viewable_type: 'Spree::Variant', attachment: image
|
||||
|
||||
exchange.variants << [v1, v2]
|
||||
end
|
||||
end
|
||||
|
||||
it "returns products via json" do
|
||||
results = []
|
||||
4.times do |i|
|
||||
ActiveRecord::Base.connection.query_cache.clear
|
||||
Rails.cache.clear
|
||||
result = Benchmark.measure do
|
||||
xhr :get, :products
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
results << result.total if i > 0
|
||||
puts result
|
||||
end
|
||||
|
||||
puts (results.sum / results.count * 1000).round 0
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,31 @@
|
||||
#require 'spec_helper'
|
||||
|
||||
describe Api::EnterpriseSerializer do
|
||||
let(:serializer) { Api::EnterpriseSerializer.new enterprise, data: data }
|
||||
let(:enterprise) { create(:distributor_enterprise) }
|
||||
let(:taxon) { create(:taxon) }
|
||||
let(:data) { OpenStruct.new(earliest_closing_times: {},
|
||||
active_distributors: [],
|
||||
distributed_taxons: {enterprise.id => [123]},
|
||||
supplied_taxons: {enterprise.id => [456]},
|
||||
shipping_method_services: {},
|
||||
relatives: {enterprise.id => {producers: [123], distributors: [456]}}) }
|
||||
|
||||
it "serializes an enterprise" do
|
||||
serializer = Api::EnterpriseSerializer.new enterprise
|
||||
serializer.to_json.should match enterprise.name
|
||||
end
|
||||
|
||||
it "includes distributed taxons" do
|
||||
enterprise.stub(:distributed_taxons).and_return [taxon]
|
||||
serializer = Api::EnterpriseSerializer.new enterprise
|
||||
serializer.to_json.should match taxon.id.to_s
|
||||
it "serializes taxons as ids only" do
|
||||
serializer.serializable_hash[:taxons].should == [{id: 123}]
|
||||
serializer.serializable_hash[:supplied_taxons].should == [{id: 456}]
|
||||
end
|
||||
|
||||
it "will render urls" do
|
||||
serializer = Api::EnterpriseSerializer.new enterprise
|
||||
it "serializes producers and hubs as ids only" do
|
||||
serializer.serializable_hash[:producers].should == [{id: 123}]
|
||||
serializer.serializable_hash[:hubs].should == [{id: 456}]
|
||||
end
|
||||
|
||||
it "serializes icons" do
|
||||
serializer.to_json.should match "map_005-hub.svg"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
describe Api::ProductSerializer do
|
||||
let(:hub) { create(:distributor_enterprise) }
|
||||
let(:oc) { create(:simple_order_cycle, distributors: [hub], variants: [v1]) }
|
||||
let(:p) { create(:simple_product) }
|
||||
let!(:v1) { create(:variant, product: p, unit_value: 3) }
|
||||
let!(:v2) { create(:variant, product: p, unit_value: 5) }
|
||||
|
||||
it "scopes variants to distribution" do
|
||||
s = Api::ProductSerializer.new p, current_distributor: hub, current_order_cycle: oc
|
||||
json = s.to_json
|
||||
json.should include v1.options_text
|
||||
json.should_not include v2.options_text
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user