Merge remote-tracking branch 'origin/master' into HEAD

This commit is contained in:
Continuous Integration
2016-12-07 17:03:27 +11:00
87 changed files with 741 additions and 402 deletions

View File

@@ -594,7 +594,7 @@ GEM
ref
thor (0.19.1)
tilt (1.4.1)
timecop (0.6.2.2)
timecop (0.8.1)
timers (1.1.0)
treetop (1.4.15)
polyglot

View File

@@ -37,6 +37,7 @@
//= require ./order_cycles/order_cycles
//= require ./payment_methods/payment_methods
//= require ./products/products
//= require ./resources/resources
//= require ./shipping_methods/shipping_methods
//= require ./side_menu/side_menu
//= require ./tag_rules/tag_rules

View File

@@ -22,7 +22,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.producers = producers
$scope.taxons = Taxons.taxons
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons

View File

@@ -3,8 +3,8 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt
$scope.availableCountries = availableCountries
$scope.RequestMonitor = RequestMonitor
$scope.submitAll = pendingChanges.submitAll
$scope.add = Customers.add
$scope.customerLimit = 20
$scope.customers = Customers.all
$scope.columns = Columns.columns
$scope.confirmRefresh = (event) ->
@@ -16,7 +16,6 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt
Customers.index({enterprise_id: $scope.shop_id}).then (data) ->
pendingChanges.removeAll()
$scope.customers_form.$setPristine()
$scope.customers = data
$scope.shop_id = shops[0].id if shops.length == 1

View File

@@ -1 +1 @@
angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) ->
angular.module("admin.indexUtils", ['admin.resources', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) ->

View File

@@ -1,4 +1,4 @@
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) ->
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) ->
$scope.initialized = false
$scope.RequestMonitor = RequestMonitor
$scope.filteredLineItems = []
@@ -22,8 +22,8 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.refreshData = ->
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == 0
$scope.startDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].first_order
$scope.endDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].last_order
$scope.startDate = OrderCycles.byID[$scope.orderCycleFilter].first_order
$scope.endDate = OrderCycles.byID[$scope.orderCycleFilter].last_order
RequestMonitor.load $scope.orders = Orders.index("q[state_not_eq]": "canceled", "q[completed_at_not_null]": "true", "q[completed_at_gt]": "#{parseDate($scope.startDate)}", "q[completed_at_lt]": "#{parseDate($scope.endDate)}")
RequestMonitor.load $scope.lineItems = LineItems.index("q[order][state_not_eq]": "canceled", "q[order][completed_at_not_null]": "true", "q[order][completed_at_gt]": "#{parseDate($scope.startDate)}", "q[order][completed_at_lt]": "#{parseDate($scope.endDate)}")
@@ -34,12 +34,12 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
RequestMonitor.load $scope.suppliers = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[is_primary_producer_eq]": "true")
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise]).then ->
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.enterprisesByID
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.orderCyclesByID
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
RequestMonitor.load $q.all([$scope.orders.$promise, $scope.suppliers.$promise, $scope.lineItems.$promise]).then ->
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.byID
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.byID
$scope.bulk_order_form.$setPristine()
StatusMessage.clear()
unless $scope.initialized

View File

@@ -1,4 +1,4 @@
angular.module("admin.lineItems").filter "selectFilter", (blankOption, RequestMonitor) ->
angular.module("admin.lineItems").filter "selectFilter", (RequestMonitor) ->
return (lineItems,selectedSupplier,selectedDistributor,selectedOrderCycle) ->
filtered = []
unless RequestMonitor.loading

View File

@@ -1,4 +1,4 @@
angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils', 'ngTagsInput'])
angular.module('admin.orderCycles', ['admin.utils', 'admin.indexUtils', 'ngTagsInput'])
.config ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')

View File

@@ -1,3 +1,3 @@
angular.module("admin.paymentMethods").controller "paymentMethodsCtrl", ($scope, PaymentMethods) ->
$scope.findPaymentMethodByID = (id) ->
$scope.PaymentMethod = PaymentMethods.findByID(id)
$scope.PaymentMethod = PaymentMethods.byID[id]

View File

@@ -1,8 +0,0 @@
angular.module("admin.paymentMethods")
.factory "PaymentMethods", (paymentMethods) ->
new class PaymentMethods
paymentMethods: paymentMethods
findByID: (id) ->
for paymentMethod in @paymentMethods
return paymentMethod if paymentMethod.id is id

View File

@@ -0,0 +1 @@
angular.module("admin.resources", ['ngResource'])

View File

@@ -1,4 +1,4 @@
angular.module("admin.customers").factory 'CustomerResource', ($resource) ->
angular.module("admin.resources").factory 'CustomerResource', ($resource) ->
$resource('/admin/customers/:id.json', {}, {
'index':
method: 'GET'

View File

@@ -1,4 +1,4 @@
angular.module("admin.enterprises").factory 'EnterpriseResource', ($resource) ->
angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
ignoredAttrs = ->
["$$hashKey", "producer", "package", "producerError", "packageError", "status"]

View File

@@ -1,4 +1,4 @@
angular.module("admin.lineItems").factory 'LineItemResource', ($resource) ->
angular.module("admin.resources").factory 'LineItemResource', ($resource) ->
$resource('/admin/:orders/:order_number/line_items/:id.json', {}, {
'index':
method: 'GET'

View File

@@ -1,4 +1,4 @@
angular.module("admin.orderCycles").factory 'OrderCycleResource', ($resource) ->
angular.module("admin.resources").factory 'OrderCycleResource', ($resource) ->
$resource('/admin/order_cycles/:id/:action.json', {}, {
'index':
method: 'GET'

View File

@@ -1,4 +1,4 @@
angular.module("admin.orders").factory 'OrderResource', ($resource) ->
angular.module("admin.resources").factory 'OrderResource', ($resource) ->
$resource('/admin/orders/:id/:action.json', {}, {
'index':
method: 'GET'

View File

@@ -1,19 +1,24 @@
angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestMonitor, CustomerResource, CurrentShop) ->
angular.module("admin.resources").factory "Customers", ($q, InfoDialog, RequestMonitor, CustomerResource, CurrentShop) ->
new class Customers
customers: []
all: []
byID: {}
pristineByID: {}
add: (email) ->
params =
enterprise_id: CurrentShop.shop.id
email: email
CustomerResource.create params, (customer) =>
@customers.unshift customer if customer.id
if customer.id
@all.unshift customer
@byID[customer.id] = customer
@pristineByID[customer.id] = angular.copy(customer)
remove: (customer) ->
params = id: customer.id
CustomerResource.destroy params, =>
i = @customers.indexOf customer
@customers.splice i, 1 unless i < 0
i = @all.indexOf customer
@all.splice i, 1 unless i < 0
, (response) =>
errors = response.data.errors
if errors?
@@ -22,10 +27,17 @@ angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestM
InfoDialog.open 'error', "Could not delete customer: #{customer.email}"
index: (params) ->
request = CustomerResource.index(params, (data) => @customers = data)
@clear()
request = CustomerResource.index(params, (data) => @load(data))
RequestMonitor.load(request.$promise)
request.$promise
load: (customers) ->
for customer in customers
@all.push customer
@byID[customer.id] = customer
@pristineByID[customer.id] = angular.copy(customer)
update: (address, customer, addressType) ->
params =
id: customer.id
@@ -33,3 +45,5 @@ angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestM
"#{addressType}_attributes": address
CustomerResource.update params
clear: ->
@all.length = 0

View File

@@ -1,16 +1,18 @@
angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource, blankOption) ->
angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource) ->
new class Enterprises
enterprisesByID: {}
byID: {}
pristineByID: {}
index: (params={}, callback=null) ->
EnterpriseResource.index(params, (data) =>
for enterprise in data
@enterprisesByID[enterprise.id] = enterprise
@pristineByID[enterprise.id] = angular.copy(enterprise)
EnterpriseResource.index params, (data) =>
@load(data)
(callback || angular.noop)(data)
data
)
load: (enterprises) ->
for enterprise in enterprises
@byID[enterprise.id] = enterprise
@pristineByID[enterprise.id] = angular.copy(enterprise)
save: (enterprise) ->
deferred = $q.defer()

View File

@@ -1,23 +1,25 @@
angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) ->
angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) ->
new class LineItems
lineItemsByID: {}
byID: {}
pristineByID: {}
index: (params={}, callback=null) ->
LineItemResource.index params, (data) =>
@resetData()
for lineItem in data
@lineItemsByID[lineItem.id] = lineItem
@pristineByID[lineItem.id] = angular.copy(lineItem)
@load(data)
(callback || angular.noop)(data)
resetData: ->
@lineItemsByID = {}
@byID = {}
@pristineByID = {}
load: (lineItems) ->
@resetData()
for lineItem in lineItems
@byID[lineItem.id] = lineItem
@pristineByID[lineItem.id] = angular.copy(lineItem)
saveAll: ->
for id, lineItem of @lineItemsByID
for id, lineItem of @byID
lineItem.errors = {} # removes errors when line_item has been returned to original state
@save(lineItem) if !@isSaved(lineItem)
@@ -34,7 +36,7 @@ angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) ->
deferred.promise
allSaved: ->
for id, lineItem of @lineItemsByID
for id, lineItem of @byID
return false unless @isSaved(lineItem)
true
@@ -54,7 +56,7 @@ angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) ->
deferred = $q.defer()
lineItem.$delete({id: lineItem.id, orders: "orders", order_number: lineItem.order.number})
.then( (data) =>
delete @lineItemsByID[lineItem.id]
delete @byID[lineItem.id]
delete @pristineByID[lineItem.id]
(callback || angular.noop)(data)
deferred.resolve(data)

View File

@@ -1,21 +1,24 @@
angular.module("admin.orderCycles").factory 'OrderCycles', ($q, OrderCycleResource, blankOption) ->
angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCycleResource) ->
new class OrderCycles
orderCyclesByID: {}
all: []
byID: {}
pristineByID: {}
constructor: ->
if $injector.has('orderCycles')
@load($injector.get('orderCycles'))
index: (params={}, callback=null) ->
includeBlank = !!params['includeBlank']
delete params['includeBlank']
OrderCycleResource.index(params, (data) =>
for orderCycle in data
@orderCyclesByID[orderCycle.id] = orderCycle
@pristineByID[orderCycle.id] = angular.copy(orderCycle)
OrderCycleResource.index params, (data) =>
@load(data)
(callback || angular.noop)(data)
data.unshift(blankOption()) if includeBlank
data
)
load: (orderCycles) ->
for orderCycle in orderCycles
@all.push orderCycle
@byID[orderCycle.id] = orderCycle
@pristineByID[orderCycle.id] = angular.copy(orderCycle)
save: (order_cycle) ->
deferred = $q.defer()

View File

@@ -1,16 +1,18 @@
angular.module("admin.orders").factory 'Orders', ($q, OrderResource) ->
angular.module("admin.resources").factory 'Orders', ($q, OrderResource) ->
new class Orders
ordersByID: {}
byID: {}
pristineByID: {}
index: (params={}, callback=null) ->
OrderResource.index params, (data) =>
for order in data
@ordersByID[order.id] = order
@pristineByID[order.id] = angular.copy(order)
@load(data)
(callback || angular.noop)(data)
load: (orders) ->
for order in orders
@byID[order.id] = order
@pristineByID[order.id] = angular.copy(order)
save: (order) ->
deferred = $q.defer()
order.$update({id: order.number})

View File

@@ -0,0 +1,16 @@
angular.module("admin.resources")
.factory "PaymentMethods", ($injector) ->
new class PaymentMethods
paymentMethods: []
byID: {}
pristineByID: {}
constructor: ->
if $injector.has('paymentMethods')
@load($injector.get('paymentMethods'))
load: (paymentMethods) ->
for paymentMethod in paymentMethods
@paymentMethods.push paymentMethod
@byID[paymentMethod.id] = paymentMethod
@pristineByID[paymentMethod.id] = angular.copy(paymentMethod)

View File

@@ -0,0 +1,16 @@
angular.module("admin.resources")
.factory "ShippingMethods", ($injector) ->
new class ShippingMethods
shippingMethods: []
byID: {}
pristineByID: {}
constructor: ->
if $injector.has('shippingMethods')
@load($injector.get('shippingMethods'))
load: (shippingMethods) ->
for shippingMethod in shippingMethods
@shippingMethods.push shippingMethod
@byID[shippingMethod.id] = shippingMethod
@pristineByID[shippingMethod.id] = angular.copy(shippingMethod)

View File

@@ -1,3 +1,3 @@
angular.module("admin.shippingMethods").controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
$scope.findShippingMethodByID = (id) ->
$scope.ShippingMethod = ShippingMethods.findByID(id)
$scope.ShippingMethod = ShippingMethods.byID[id]

View File

@@ -1,8 +0,0 @@
angular.module("admin.shippingMethods")
.factory "ShippingMethods", (shippingMethods) ->
new class ShippingMethods
shippingMethods: shippingMethods
findByID: (id) ->
for shippingMethod in @shippingMethods
return shippingMethod if shippingMethod.id is id

View File

@@ -1,19 +1,20 @@
angular.module("admin.taxons").factory "Taxons", (taxons, $filter) ->
new class Taxons
taxons: taxons
taxonsByID: {}
all: []
byID: {}
constructor: ->
for taxon in @taxons
@taxonsByID[taxon.id] = taxon
for taxon in taxons
@all.push taxon
@byID[taxon.id] = taxon
# For finding a single Taxon
findByID: (id) ->
@taxonsByID[id]
@byID[id]
# For finding multiple Taxons represented by comma delimited string
findByIDs: (ids) ->
@taxonsByID[taxon_id] for taxon_id in ids.split(",") when @taxonsByID[taxon_id]
@byID[taxon_id] for taxon_id in ids.split(",") when @byID[taxon_id]
findByTerm: (term) ->
$filter('filter')(@taxons, term)
$filter('filter')(@all, term)

View File

@@ -1,2 +0,0 @@
angular.module("admin.utils").value "blankOption", ->
{ id: "0", name: "All" }

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) ->
Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation, $location) ->
angular.extend this, $controller('TabsCtrl', {$scope: $scope})
$scope.tabs =
@@ -6,3 +6,7 @@ Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) ->
producers: { active: Navigation.isActive('/producers') }
contact: { active: Navigation.isActive('/contact') }
groups: { active: Navigation.isActive('/groups') }
$scope.$on '$locationChangeStart', (event, url) ->
tab = $location.path().replace(/^\//, '')
$scope.tabs[tab]?.active = true

View File

@@ -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

View File

@@ -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

View File

@@ -14,9 +14,6 @@
.exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' }
.exchange-product-details
%label
-# MASTER_VARIANTS: No longer required
-# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
-# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0'
%img{'ng-src' => '{{ product.image_url }}'}
.name {{ product.name }}
.supplier {{ product.supplier_name }}

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,6 @@
module SerializerHelper
def ids_to_objs(ids)
return [] if ids.blank?
ids.map { |id| {id: id} }
end
end

View File

@@ -87,6 +87,7 @@ class Enterprise < ActiveRecord::Base
before_validation :set_unused_address_fields
after_validation :geocode_address
after_touch :touch_distributors
after_create :relate_to_owners_enterprises
# TODO: Later versions of devise have a dedicated after_confirmation callback, so use that
after_update :welcome_after_confirm, if: lambda { confirmation_token_changed? && confirmation_token.nil? }
@@ -161,12 +162,7 @@ class Enterprise < ActiveRecord::Base
select('DISTINCT enterprises.*')
}
scope :distributing_product, lambda { |product|
with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer.
where('product_distributions.product_id = ? OR spree_variants.product_id = ?', product, product).
select('DISTINCT enterprises.*')
}
scope :distributing_any_product_of, lambda { |products|
scope :distributing_products, lambda { |products|
with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer.
where('product_distributions.product_id IN (?) OR spree_variants.product_id IN (?)', products, products).
select('DISTINCT enterprises.*')
@@ -469,4 +465,10 @@ class Enterprise < ActiveRecord::Base
def initialize_permalink
self.permalink = Enterprise.find_available_permalink(name)
end
def touch_distributors
Enterprise.distributing_products(self.supplied_products).
where('enterprises.id != ?', self.id).
each(&:touch)
end
end

View File

@@ -2,18 +2,18 @@ class Exchange < ActiveRecord::Base
acts_as_taggable
belongs_to :order_cycle
belongs_to :sender, :class_name => 'Enterprise'
belongs_to :receiver, :class_name => 'Enterprise'
belongs_to :payment_enterprise, :class_name => 'Enterprise'
belongs_to :sender, class_name: 'Enterprise'
belongs_to :receiver, class_name: 'Enterprise', touch: true
belongs_to :payment_enterprise, class_name: 'Enterprise'
has_many :exchange_variants, :dependent => :destroy
has_many :variants, :through => :exchange_variants
has_many :exchange_variants, dependent: :destroy
has_many :variants, through: :exchange_variants
has_many :exchange_fees, :dependent => :destroy
has_many :enterprise_fees, :through => :exchange_fees
has_many :exchange_fees, dependent: :destroy
has_many :enterprise_fees, through: :exchange_fees
validates_presence_of :order_cycle, :sender, :receiver
validates_uniqueness_of :sender_id, :scope => [:order_cycle_id, :receiver_id, :incoming]
validates_uniqueness_of :sender_id, scope: [:order_cycle_id, :receiver_id, :incoming]
after_save :refresh_products_cache
after_destroy :refresh_products_cache_from_destroy

View File

@@ -1,5 +1,5 @@
class ProducerProperty < ActiveRecord::Base
belongs_to :producer, class_name: 'Enterprise'
belongs_to :producer, class_name: 'Enterprise', touch: true
belongs_to :property, class_name: 'Spree::Property'
default_scope order("#{self.table_name}.position")
@@ -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

View File

@@ -57,6 +57,7 @@ class AbilityDecorator
def add_group_management_abilities(user)
can [:admin, :index], :overview
can [:admin, :sync], :analytic
can [:admin, :index], EnterpriseGroup
can [:read, :edit, :update], EnterpriseGroup do |group|
user.owned_groups.include? group
@@ -69,6 +70,7 @@ class AbilityDecorator
can [:create, :search], nil
can [:admin, :index], :overview
can [:admin, :sync], :analytic
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty

View File

@@ -187,9 +187,15 @@ Spree::Product.class_eval do
def delete_with_delete_from_order_cycles
transaction do
delete_without_delete_from_order_cycles
OpenFoodNetwork::ProductsCache.product_deleted(self) do
# Touch supplier and distributors as we would on #destroy
self.supplier.touch
touch_distributors
ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all
ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all
delete_without_delete_from_order_cycles
end
end
end
alias_method_chain :delete, :delete_from_order_cycles
@@ -215,7 +221,7 @@ Spree::Product.class_eval do
end
def touch_distributors
Enterprise.distributing_product(self).each(&:touch)
Enterprise.distributing_products(self).each(&:touch)
end
def add_primary_taxon_to_taxons

View File

@@ -1,8 +1,11 @@
module Spree
ProductProperty.class_eval do
belongs_to :product, class_name: "Spree::Product", touch: true
after_save :refresh_products_cache
after_destroy :refresh_products_cache
def refresh_products_cache
product.refresh_products_cache
end

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1 @@
/ disabled

View File

@@ -20,9 +20,9 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer
end
class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
include SerializerHelper
attributes :orders_close_at, :active
has_many :supplied_properties, serializer: Api::PropertySerializer
has_many :distributed_properties, serializer: Api::PropertySerializer
def orders_close_at
options[:data].earliest_closing_times[object.id]
@@ -31,26 +31,11 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
def active
options[:data].active_distributors.andand.include? object
end
def supplied_properties
# This results in 3 queries per enterprise
product_properties = Spree::Property.applied_by(object)
producer_properties = object.properties
OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties
end
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)
OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties
end
end
class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
include SerializerHelper
cached
#delegate :cache_key, to: :object
@@ -68,13 +53,9 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
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
has_many :supplied_properties, serializer: Api::PropertySerializer
has_many :distributed_properties, serializer: Api::PropertySerializer
def pickup
services = options[:data].shipping_method_services[object.id]
@@ -116,6 +97,47 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
ids_to_objs(relatives.andand[:distributors])
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
def supplied_properties
# This results in 3 queries per enterprise
product_properties = Spree::Property.applied_by(object)
producer_properties = object.properties
OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties
end
def distributed_properties
# This results in 3 queries per enterprise
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 active
options[:data].active_distributors.andand.include? object
end
# Map svg icons.
def icon
icons = {
@@ -153,12 +175,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

View File

@@ -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

View File

@@ -1,12 +1 @@
= render 'admin/producer_properties/form', f: f
// :javascript
// var properties = #{raw(@properties.to_json)};
//
// $("#producer_properties input.autocomplete").live("keydown", function() {
// already_auto_completed = $(this).is('ac_input');
// if (!already_auto_completed) {
// $(this).autocomplete({source: properties});
// $(this).focus();
// }
// });

View File

@@ -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'

View File

@@ -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
&nbsp;
= render partial: "shared/footer"
= render "shared/footer"

View File

@@ -1,22 +0,0 @@
.row
= render partial: 'shared/components/filter_controls'
-# .small-12.medium-6.columns &nbsp;
= 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'

View File

@@ -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'

View File

@@ -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}}"}

View File

@@ -1,3 +0,0 @@
-##copyright.text-center
-#%img.copyright{src: "/assets/logo.png", alt: "Open Food Network"}
-#&copy; Copyright 2013 Open Food Foundation

View File

@@ -10,7 +10,7 @@
register: ('<a auth="signup">' + t('.register') + '</a>').html_safe}
- else
= t '.require_customer_html',
{contact: ('<a href="##contact">' + t('.contact') + '</a>').html_safe,
{contact: link_to(t('.contact'), '#contact'),
enterprise: current_distributor.name}
- elsif @order_cycles and @order_cycles.empty?
- if current_distributor.preferred_shopfront_closed_message.present?

View File

@@ -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"}

View File

@@ -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 &nbsp;
= 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'

View File

@@ -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()"}

View File

@@ -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"

View File

@@ -10,5 +10,5 @@
%p.text-big
= t :shops_text
= render partial: "home/hubs"
= render partial: "shared/footer"
= render "hubs"
= render "shared/footer"

View File

@@ -0,0 +1 @@
-# This file intentionally overrides the view in the spree_paypal_express gem

View File

@@ -12,7 +12,7 @@
%tbody
- order = current_order(false)
- validator = DistributionChangeValidator.new(order)
- Enterprise.distributing_product(@product).each do |distributor|
- Enterprise.distributing_products(@product).each do |distributor|
- if !order.nil? && distributor == order.distributor
%tr.odd
%td

View File

@@ -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

View File

@@ -1,5 +1,5 @@
class DistributionChangeValidator
def initialize order
@order = order
end
@@ -29,7 +29,7 @@ class DistributionChangeValidator
end
def available_distributors_for(product)
distributors = Enterprise.distributing_product(product)
distributors = Enterprise.distributing_products(product)
if @order.andand.line_items.present?
distributors = available_distributors(distributors)

View File

@@ -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

View File

@@ -28,6 +28,15 @@ module OpenFoodNetwork
end
end
def self.product_deleted(product, &block)
exchanges = exchanges_featuring_variants(product.reload.variants).to_a
block.call
exchanges.each do |exchange|
refresh_cache exchange.receiver, exchange.order_cycle
end
end
def self.variant_override_changed(variant_override)
exchanges_featuring_variants(variant_override.variant, distributor: variant_override.hub).each do |exchange|

View File

@@ -3,31 +3,18 @@ require 'spec_helper'
feature %q{
As a backend user
I want to be given information about the state of my enterprises, products and order cycles
} , js: true do
}, js: true do
include AuthenticationWorkflow
include AuthorizationHelpers
include WebHelper
stub_authorization!
context "as an enterprise user" do
before :each do
before do
@enterprise_user = create_enterprise_user
Spree::Admin::OverviewController.any_instance.stub(:spree_current_user).and_return @enterprise_user
quick_login_as @enterprise_user
end
context "with no enterprises" do
it "prompts the user to create a new enteprise" do
visit '/admin'
page.should have_selector ".dashboard_item#enterprises h3", text: "My Enterprises"
page.should have_selector ".dashboard_item#enterprises .list-item", text: "You don't have any enterprises yet"
page.should have_selector ".dashboard_item#enterprises .button.bottom", text: "CREATE A NEW ENTERPRISE"
page.should_not have_selector ".dashboard_item#products"
page.should_not have_selector ".dashboard_item#order_cycles"
end
end
context "with an enterprise" do
let(:d1) { create(:distributor_enterprise) }
@@ -123,5 +110,45 @@ feature %q{
end
end
end
context "with the spree dash configured" do
let(:d1) { create(:distributor_enterprise) }
before do
stub_jirafe
@enterprise_user.enterprise_roles.build(enterprise: d1).save
end
around do |example|
with_dash_configured { example.run }
end
it "has permission to sync analytics" do
visit '/admin'
expect(page).to have_content d1.name
end
end
end
end
private
def stub_jirafe
stub_request(:post, "https://api.jirafe.com/v1/applications/abc123/resources?token=").
to_return(:status => 200, :body => "", :headers => {})
end
def with_dash_configured(&block)
Spree::Dash::Config.preferred_app_id = 'abc123'
Spree::Dash::Config.preferred_site_id = 'abc123'
Spree::Dash::Config.preferred_token = 'abc123'
expect(Spree::Dash::Config.configured?).to be true
block.call
ensure
Spree::Dash::Config.preferred_app_id = nil
Spree::Dash::Config.preferred_site_id = nil
Spree::Dash::Config.preferred_token = nil
expect(Spree::Dash::Config.configured?).to be false
end
end

View File

@@ -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

View File

@@ -1,7 +1,7 @@
require 'spec_helper'
feature "As a consumer I want to check out my cart", js: true do
feature "As a consumer I want to check out my cart", js: true, retry: 3 do
include AuthenticationWorkflow
include ShopWorkflow
include CheckoutWorkflow

View File

@@ -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
@@ -55,46 +55,108 @@ feature 'Shops', js: true do
follow_active_table_node distributor.name
expect(page).to have_current_path enterprise_shop_path(distributor)
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) }
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
product.set_property 'Local', 'XYZ 123'
end
before do
ex_d1.variants << p1.variants.first
ex_d2.variants << p2.variants.first
it "shows property badges" do
# Given a shop with a product with a property
# And the product's producer has a producer property
p2.set_property 'Local', 'XYZ 123'
# When I go to the shops path
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
# And I open the shop
expand_active_table_node distributor.name
# Then I should see both properties
expect(page).to have_content 'Local' # Product property
expect(page).to have_content 'Organic' # Producer property
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 "hub producer modal" do
let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) }
let!(:taxon) { create(:taxon, name: 'Fruit') }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
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
it "shows hub producer modals" do
expand_active_table_node distributor.name
expect(page).to have_content producer.name
open_enterprise_modal producer
modal_should_be_open_for producer
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) }
within ".reveal-modal" do
expect(page).to have_content 'Fruit' # Taxon
expect(page).to have_content 'Organic' # Producer property
end
before do
product.set_property 'Local', 'XYZ 123'
end
it "shows property badges" do
# Given a shop with a product with a property
# And the product's producer has a producer property
# When I go to the shops path
visit shops_path
# And I open the shop
expand_active_table_node distributor.name
# Then I should see both properties
expect(page).to have_content 'Local' # Product property
expect(page).to have_content 'Organic' # Producer property
end
end
describe "hub producer modal" do
let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) }
let!(:taxon) { create(:taxon, name: 'Fruit') }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
it "shows hub producer modals" do
visit shops_path
expand_active_table_node distributor.name
expect(page).to have_content producer.name
open_enterprise_modal producer
modal_should_be_open_for producer
within ".reveal-modal" do
expect(page).to have_content 'Fruit' # Taxon
expect(page).to have_content 'Organic' # Producer property
end
end
end

View File

@@ -62,28 +62,17 @@ describe "CustomersCtrl", ->
as = scope.findByCode('b')
expect(as).toDeepEqual []
describe "scope.add", ->
it "creates a new customer", ->
email = "customer@example.org"
newCustomer = {id: 6, email: email}
customers.unshift(newCustomer)
http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=3').respond 200, newCustomer
scope.add(email)
http.flush()
expect(scope.customers).toDeepEqual customers
describe "scope.deleteCustomer", ->
beforeEach ->
spyOn(window, 'confirm').and.returnValue(true)
it "deletes a customer", ->
expect(scope.customers.length).toBe 2
expect(scope.customers.length).toBe 1
customer = scope.customers[0]
http.expectDELETE('/admin/customers/' + customer.id + '.json').respond 200
scope.deleteCustomer(customer)
http.flush()
expect(scope.customers.length).toBe 1
expect(scope.customers[0]).not.toDeepEqual customer
expect(scope.customers.length).toBe 0
describe "scope.findTags", ->
tags = [

View File

@@ -0,0 +1,23 @@
describe "Customers", ->
Customers = CurrentShop = customers = $httpBackend = null
beforeEach ->
module 'admin.customers'
jasmine.addMatchers
toDeepEqual: (util, customEqualityTesters) ->
compare: (actual, expected) ->
{ pass: angular.equals(actual, expected) }
inject ($q, _$httpBackend_, _Customers_, _CurrentShop_) ->
Customers = _Customers_
describe "scope.add", ->
it "creates a new customer", inject ($httpBackend, CurrentShop) ->
email = "customer@example.org"
newCustomer = {id: 6, email: email}
CurrentShop.shop = { id: 3 }
$httpBackend.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=3').respond 200, newCustomer
Customers.add(email)
$httpBackend.flush()
expect(Customers.all).toDeepEqual [newCustomer]

View File

@@ -26,9 +26,9 @@ describe "Enterprises service", ->
result = Enterprises.index()
$httpBackend.flush()
it "stores returned data in @enterprisesByID, with ids as keys", ->
it "stores returned data in @byID, with ids as keys", ->
# EnterpriseResource returns instances of Resource rather than raw objects
expect(Enterprises.enterprisesByID).toDeepEqual { 5: response[0] }
expect(Enterprises.byID).toDeepEqual { 5: response[0] }
it "stores returned data in @pristineByID, with ids as keys", ->
expect(Enterprises.pristineByID).toDeepEqual { 5: response[0] }

View File

@@ -70,7 +70,7 @@ describe "LineItemsCtrl", ->
expect(scope.distributors).toDeepEqual [ distributor ]
it "stores enterprises in an list that is accessible by id", ->
expect(Enterprises.enterprisesByID[1]).toDeepEqual supplier
expect(Enterprises.byID[1]).toDeepEqual supplier
it "gets order cycles", ->
expect(scope.orderCycles).toDeepEqual [ orderCycle ]

View File

@@ -23,9 +23,9 @@ describe "LineItems service", ->
result = LineItems.index()
$httpBackend.flush()
it "stores returned data in @lineItemsByID, with ids as keys", ->
it "stores returned data in @byID, with ids as keys", ->
# LineItemResource returns instances of Resource rather than raw objects
expect(LineItems.lineItemsByID).toDeepEqual { 5: response[0] }
expect(LineItems.byID).toDeepEqual { 5: response[0] }
it "stores returned data in @pristineByID, with ids as keys", ->
expect(LineItems.pristineByID).toDeepEqual { 5: response[0] }
@@ -114,14 +114,14 @@ describe "LineItems service", ->
beforeEach ->
lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} })
LineItems.pristineByID[15] = lineItem
LineItems.lineItemsByID[15] = lineItem
LineItems.byID[15] = lineItem
$httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'}
LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true)
$httpBackend.flush()
it "updates the pristine copy of the lineItem", ->
expect(LineItems.pristineByID[15]).toBeUndefined()
expect(LineItems.lineItemsByID[15]).toBeUndefined()
expect(LineItems.byID[15]).toBeUndefined()
it "runs the callback", ->
expect(callback).toHaveBeenCalled()
@@ -139,14 +139,14 @@ describe "LineItems service", ->
beforeEach ->
lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} })
LineItems.pristineByID[15] = lineItem
LineItems.lineItemsByID[15] = lineItem
LineItems.byID[15] = lineItem
$httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 422, { error: 'obj' }
LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true)
$httpBackend.flush()
it "does not update the pristine copy of the lineItem", ->
expect(LineItems.pristineByID[15]).toBeDefined()
expect(LineItems.lineItemsByID[15]).toBeDefined()
expect(LineItems.byID[15]).toBeDefined()
it "does not run the callback", ->
expect(callback).not.toHaveBeenCalled()

View File

@@ -26,9 +26,9 @@ describe "OrderCycles service", ->
result = OrderCycles.index()
$httpBackend.flush()
it "stores returned data in @orderCyclesByID, with ids as keys", ->
it "stores returned data in @byID, with ids as keys", ->
# OrderCycleResource returns instances of Resource rather than raw objects
expect(OrderCycles.orderCyclesByID).toDeepEqual { 5: response[0] }
expect(OrderCycles.byID).toDeepEqual { 5: response[0] }
it "stores returned data in @pristineByID, with ids as keys", ->
expect(OrderCycles.pristineByID).toDeepEqual { 5: response[0] }
@@ -37,25 +37,14 @@ describe "OrderCycles service", ->
expect(result).toDeepEqual response
describe "when no params are passed", ->
describe "where includeBlank param is truthy", ->
beforeEach ->
params = {includeBlank: true, someParam: 'someVal'}
$httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response
result = OrderCycles.index(params)
$httpBackend.flush()
beforeEach ->
params = { someParam: 'someVal'}
$httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response
result = OrderCycles.index(params)
$httpBackend.flush()
it "returns an array of orderCycles", ->
expect(result).toDeepEqual [{id: '0', name: 'All'} ,{ id: 5, name: 'OrderCycle 1'}]
describe "where includeBlank param is falsey", ->
beforeEach ->
params = {includeBlank: false, someParam: 'someVal'}
$httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response
result = OrderCycles.index(params)
$httpBackend.flush()
it "returns an array of orderCycles", ->
expect(result).toDeepEqual response
it "returns an array of orderCycles", ->
expect(result).toDeepEqual response
describe "#save", ->

View File

@@ -23,9 +23,9 @@ describe "Orders service", ->
result = Orders.index()
$httpBackend.flush()
it "stores returned data in @ordersByID, with ids as keys", ->
it "stores returned data in @byID, with ids as keys", ->
# OrderResource returns instances of Resource rather than raw objects
expect(Orders.ordersByID).toDeepEqual { 5: response[0] }
expect(Orders.byID).toDeepEqual { 5: response[0] }
it "stores returned data in @pristineByID, with ids as keys", ->
expect(Orders.pristineByID).toDeepEqual { 5: response[0] }

View File

@@ -87,7 +87,7 @@ describe DistributionChangeValidator do
enterprise_with_some_variants.stub(:distributed_variants) { [variant1, variant3] } # Only some variants
enterprise_with_some_plus_extras = double(:enterprise)
enterprise_with_some_plus_extras.stub(:distributed_variants) { [variant1, variant2, variant3, variant4] } # Only some variants, plus extras
subject.available_distributors([enterprise_with_some_variants]).should_not include enterprise_with_some_variants
subject.available_distributors([enterprise_with_some_plus_extras]).should_not include enterprise_with_some_plus_extras
end
@@ -97,10 +97,10 @@ describe DistributionChangeValidator do
order.stub(:line_item_variants) { line_item_variants }
enterprise = double(:enterprise)
enterprise.stub(:distributed_variants) { [variant1, variant2, variant3, variant4, variant5] } # Excess variants
subject.available_distributors([enterprise]).should == [enterprise]
end
it "matches no enterprises when none are provided" do
subject.available_distributors([]).should == []
end
@@ -201,7 +201,7 @@ describe DistributionChangeValidator do
describe "finding available distributors for a product" do
it "returns enterprises distributing the product when there's no order" do
subject = DistributionChangeValidator.new(nil)
Enterprise.stub(:distributing_product).and_return([1, 2, 3])
Enterprise.stub(:distributing_products).and_return([1, 2, 3])
subject.should_receive(:available_distributors).never
subject.available_distributors_for(product).should == [1, 2, 3]
@@ -209,7 +209,7 @@ describe DistributionChangeValidator do
it "returns enterprises distributing the product when there's no order items" do
order.stub(:line_items) { [] }
Enterprise.stub(:distributing_product).and_return([1, 2, 3])
Enterprise.stub(:distributing_products).and_return([1, 2, 3])
subject.should_receive(:available_distributors).never
subject.available_distributors_for(product).should == [1, 2, 3]
@@ -217,7 +217,7 @@ describe DistributionChangeValidator do
it "filters by available distributors when there are order items" do
order.stub(:line_items) { [1, 2, 3] }
Enterprise.stub(:distributing_product).and_return([1, 2, 3])
Enterprise.stub(:distributing_products).and_return([1, 2, 3])
subject.should_receive(:available_distributors).and_return([2])
subject.available_distributors_for(product).should == [2]

View File

@@ -89,6 +89,26 @@ module OpenFoodNetwork
end
end
describe "when a product is deleted" do
let(:product) { create(:simple_product) }
let(:variant) { create(:variant, product: product) }
let(:distributor) { create(:distributor_enterprise) }
let!(:oc) { create(:open_order_cycle, distributors: [distributor], variants: [variant]) }
it "refreshes the cache based on exchanges the variant was in before destruction" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc)
product.delete
end
it "performs the cache refresh after the product has been removed from the order cycle" do
expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc) do
expect(product.reload.deleted_at).not_to be_nil
end
product.delete
end
end
describe "when a variant override changes" do
let(:variant) { create(:variant) }
let(:d1) { create(:distributor_enterprise) }

View File

@@ -5,50 +5,107 @@ describe Enterprise do
describe "is touched when a(n)" do
let(:enterprise) { create(:distributor_enterprise, updated_at: 1.week.ago) }
let(:taxon) { create(:taxon) }
let(:supplier2) { create(:supplier_enterprise) }
describe "with a supplied product" do
let(:product) { create(:simple_product, supplier: enterprise) }
let!(:classification) { create(:classification, taxon: taxon, product: product) }
let(:property) { product.product_properties.last }
let(:producer_property) { enterprise.producer_properties.last }
before do
product.set_property 'Organic', 'NASAA 12345'
enterprise.set_producer_property 'Biodynamic', 'ASDF 4321'
end
it "touches enterprise when a classification on that product changes" do
expect{classification.save!}.to change{enterprise.updated_at}
expect { classification.save! }.to change { enterprise.updated_at }
end
it "touches enterprise when a property on that product changes" do
expect { property.save! }.to change { enterprise.reload.updated_at }
end
it "touches enterprise when a producer property on that product changes" do
expect { producer_property.save! }.to change { enterprise.reload.updated_at }
end
it "touches enterprise when the supplier of a product changes" do
expect {
product.update_attributes!(supplier: supplier2)
}.to change { enterprise.updated_at }
end
end
describe "with a distributed product" do
let(:product) { create(:simple_product) }
let!(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.master]) }
let(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.variants.first]) }
let(:supplier) { product.supplier }
let!(:classification) { create(:classification, taxon: taxon, product: product) }
it "touches enterprise when a classification on that product changes" do
expect{classification.save!}.to change{enterprise.reload.updated_at}
let(:property) { product.product_properties.last }
let(:producer_property) { supplier.producer_properties.last }
before do
product.set_property 'Organic', 'NASAA 12345'
supplier.set_producer_property 'Biodynamic', 'ASDF 4321'
end
context "with an order cycle" do
before { oc }
it "touches enterprise when a classification on that product changes" do
expect { classification.save! }.to change { enterprise.reload.updated_at }
end
it "touches enterprise when a property on that product changes" do
expect { property.save! }.to change { enterprise.reload.updated_at }
end
it "touches enterprise when a producer property on that product changes" do
expect { producer_property.save! }.to change { enterprise.reload.updated_at }
end
it "touches enterprise when the supplier of a product changes" do
expect {
product.update_attributes!(supplier: supplier2)
}.to change { enterprise.reload.updated_at }
end
end
it "touches enterprise when the product's variant is added to order cycle" do
expect { oc }.to change { enterprise.reload.updated_at }
end
end
describe "with relatives" do
let(:child_enterprise) { create(:supplier_enterprise) }
let!(:er) { create(:enterprise_relationship, parent: enterprise, child: child_enterprise) }
it "touches enterprise when enterprise relationship is updated" do
expect{er.save!}.to change {enterprise.reload.updated_at }
expect { er.save! }.to change { enterprise.reload.updated_at }
end
end
describe "with shipping methods" do
let(:sm) { create(:shipping_method) }
before do
enterprise.shipping_methods << sm
end
it "touches enterprise when distributor_shipping_method is updated" do
expect {
enterprise.distributor_shipping_methods.first.save!
}.to change {enterprise.reload.updated_at}
}.to change { enterprise.reload.updated_at }
end
it "touches enterprise when shipping method is updated" do
expect{sm.save!}.to change {enterprise.reload.updated_at }
expect { sm.save! }.to change { enterprise.reload.updated_at }
end
end
it "touches enterprise when address is updated" do
expect{enterprise.address.save!}.to change {enterprise.reload.updated_at }
expect{ enterprise.address.save! }.to change { enterprise.reload.updated_at }
end
end
end

View File

@@ -542,40 +542,38 @@ describe Enterprise do
end
end
describe "distributing_product" do
describe "distributing_products" do
it "returns enterprises distributing via a product distribution" do
d = create(:distributor_enterprise)
p = create(:product, distributors: [d])
Enterprise.distributing_product(p).should == [d]
Enterprise.distributing_products(p).should == [d]
end
it "returns enterprises distributing via an order cycle" do
d = create(:distributor_enterprise)
p = create(:product)
oc = create(:simple_order_cycle, distributors: [d], variants: [p.master])
Enterprise.distributing_product(p).should == [d]
Enterprise.distributing_products(p).should == [d]
end
end
describe "distributing_any_product_of" do
it "returns enterprises distributing via a product distribution" do
d = create(:distributor_enterprise)
p = create(:product, distributors: [d])
Enterprise.distributing_any_product_of([p]).should == [d]
Enterprise.distributing_products([p]).should == [d]
end
it "returns enterprises distributing via an order cycle" do
d = create(:distributor_enterprise)
p = create(:product)
oc = create(:simple_order_cycle, distributors: [d], variants: [p.master])
Enterprise.distributing_any_product_of([p]).should == [d]
Enterprise.distributing_products([p]).should == [d]
end
it "does not return duplicate enterprises" do
d = create(:distributor_enterprise)
p1 = create(:product, distributors: [d])
p2 = create(:product, distributors: [d])
Enterprise.distributing_any_product_of([p1, p2]).should == [d]
Enterprise.distributing_products([p1, p2]).should == [d]
end
end

View File

@@ -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

View File

@@ -33,9 +33,10 @@ module Spree
end
it "defaults available_on to now" do
Timecop.freeze
product = Product.new
product.available_on.should == Time.zone.now
Timecop.freeze do
product = Product.new
product.available_on.should == Time.zone.now
end
end
describe "tax category" do
@@ -170,8 +171,28 @@ module Spree
product.save
end
it "refreshes the products cache on delete" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_deleted).with(product)
product.delete
end
# On destroy, all distributed variants are refreshed by a Variant around_destroy
# callback, so we don't need to do anything on the product model.
describe "touching affected enterprises when the product is deleted" do
let(:product) { create(:simple_product) }
let(:supplier) { product.supplier }
let(:distributor) { create(:distributor_enterprise) }
let!(:oc) { create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) }
it "touches the supplier" do
expect { product.delete }.to change { supplier.reload.updated_at }
end
it "touches all distributors" do
expect { product.delete }.to change { distributor.reload.updated_at }
end
end
end
describe "scopes" do

View File

@@ -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

View File

@@ -7,7 +7,7 @@ module Spree
let!(:t2) { create(:taxon) }
describe "callbacks" do
let!(:p2) { create(:simple_product, taxons: [t1]) }
let!(:p2) { create(:simple_product, taxons: [t1], primary_taxon: t2) }
it "refreshes the products cache on save" do
expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2)
@@ -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

View File

@@ -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]}}) }

1
spec/support/timecop.rb Normal file
View File

@@ -0,0 +1 @@
Timecop.safe_mode = true