diff --git a/Gemfile b/Gemfile
index 48ff4349cb..d581c8d10b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,9 +13,10 @@ gem 'spree', github: 'openfoodfoundation/spree', branch: '1-3-stable'
gem 'spree_i18n', github: 'spree/spree_i18n', branch: '1-3-stable'
gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '1-3-stable'
-# Waiting on merge of PR #117
-# https://github.com/spree-contrib/better_spree_paypal_express/pull/117
-gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "1-3-stable"
+# Our branch contains two changes
+# - Pass customer email and phone number to PayPal (merged to upstream master)
+# - Change type of password from string to password to hide it in the form
+gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "hide-password"
#gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
gem 'delayed_job_active_record'
diff --git a/Gemfile.lock b/Gemfile.lock
index fcd0633483..0e31157d96 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -14,8 +14,8 @@ GIT
GIT
remote: git://github.com/openfoodfoundation/better_spree_paypal_express.git
- revision: cdd61161ccd27cd8d183f9321422c7be113796b8
- branch: 1-3-stable
+ revision: 840d973cd5bd3250b17674a624dad494aeb09eb3
+ branch: hide-password
specs:
spree_paypal_express (2.0.3)
paypal-sdk-merchant (= 1.106.1)
diff --git a/app/assets/images/ofn-logo-footer.png b/app/assets/images/ofn-logo-footer.png
new file mode 100644
index 0000000000..f612d5aa87
Binary files /dev/null and b/app/assets/images/ofn-logo-footer.png differ
diff --git a/app/assets/images/ofn-logo-mobile.svg b/app/assets/images/ofn-logo-mobile.svg
new file mode 100644
index 0000000000..7c48b00b1b
--- /dev/null
+++ b/app/assets/images/ofn-logo-mobile.svg
@@ -0,0 +1,80 @@
+
+
diff --git a/app/assets/images/ofn-logo.png b/app/assets/images/ofn-logo.png
new file mode 100644
index 0000000000..f53680c342
Binary files /dev/null and b/app/assets/images/ofn-logo.png differ
diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js
index 1e2b6f3898..9f99dc1dcd 100644
--- a/app/assets/javascripts/admin/all.js
+++ b/app/assets/javascripts/admin/all.js
@@ -38,6 +38,7 @@
//= require ./products/products
//= require ./shipping_methods/shipping_methods
//= require ./side_menu/side_menu
+//= require ./tag_rules/tag_rules
//= require ./taxons/taxons
//= require ./utils/utils
//= require ./users/users
diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
index c475f1e4df..d2e6d58562 100644
--- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
+++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
@@ -1,5 +1,5 @@
-angular.module("admin.customers").controller "customersCtrl", ($scope, Customers, Columns, pendingChanges, shops) ->
- $scope.shop = null
+angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, Columns, pendingChanges, shops) ->
+ $scope.shop = {}
$scope.shops = shops
$scope.submitAll = pendingChanges.submitAll
@@ -8,10 +8,26 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, Customers
code: { name: "Code", visible: true }
tags: { name: "Tags", visible: true }
- $scope.$watch "shop", ->
- if $scope.shop?
- Customers.loaded = false
- $scope.customers = Customers.index(enterprise_id: $scope.shop.id)
+ $scope.$watch "shop.id", ->
+ if $scope.shop.id?
+ $scope.customers = index {enterprise_id: $scope.shop.id}
- $scope.loaded = ->
- Customers.loaded
+ $scope.add = (email) ->
+ params =
+ enterprise_id: $scope.shop.id
+ email: email
+ CustomerResource.create params, (customer) =>
+ if customer.id
+ $scope.customers.push customer
+ $scope.quickSearch = customer.email
+
+ $scope.deleteCustomer = (customer) ->
+ params = id: customer.id
+ CustomerResource.destroy params, ->
+ i = $scope.customers.indexOf customer
+ $scope.customers.splice i, 1 unless i < 0
+
+ index = (params) ->
+ $scope.loaded = false
+ CustomerResource.index params, =>
+ $scope.loaded = true
diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee
index 3733fe2eea..1e8ae9b988 100644
--- a/app/assets/javascripts/admin/customers/customers.js.coffee
+++ b/app/assets/javascripts/admin/customers/customers.js.coffee
@@ -1 +1 @@
-angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.dropdown'])
\ No newline at end of file
+angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee b/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee
deleted file mode 100644
index e15ec10342..0000000000
--- a/app/assets/javascripts/admin/customers/directives/tags_with_translation.js.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-angular.module("admin.customers").directive "tagsWithTranslation", ->
- restrict: "E"
- template: ""
- scope:
- object: "="
- link: (scope, element, attrs) ->
- scope.$watchCollection "object.tags", ->
- scope.object.tag_list = (tag.text for tag in scope.object.tags).join(",")
diff --git a/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee b/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee
index 523e0c1495..5b6c1ab205 100644
--- a/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee
+++ b/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee
@@ -1,8 +1,17 @@
angular.module("admin.customers").factory 'CustomerResource', ($resource) ->
- $resource('/admin/customers.json', {}, {
+ $resource('/admin/customers/:id.json', {}, {
'index':
method: 'GET'
isArray: true
params:
enterprise_id: '@enterprise_id'
+ 'create':
+ method: 'POST'
+ params:
+ enterprise_id: '@enterprise_id'
+ email: '@email'
+ 'destroy':
+ method: 'DELETE'
+ params:
+ id: '@id'
})
diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee
deleted file mode 100644
index 9acfa317d2..0000000000
--- a/app/assets/javascripts/admin/customers/services/customers.js.coffee
+++ /dev/null
@@ -1,16 +0,0 @@
-angular.module("admin.customers").factory 'Customers', (CustomerResource) ->
- new class Customers
- customers: []
- customers_by_id: {}
- loaded: false
-
- index: (params={}, callback=null) ->
- CustomerResource.index params, (data) =>
- for customer in data
- @customers.push customer
- @customers_by_id[customer.id] = customer
-
- @loaded = true
- (callback || angular.noop)(@customers)
-
- @customers
diff --git a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
index 7981e498b6..a0105fefa4 100644
--- a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
@@ -17,8 +17,9 @@ angular.module("admin.enterprises")
{ name: t('shipping_methods'), icon_class: "icon-truck", show: "showShippingMethods()" }
{ name: t('payment_methods'), icon_class: "icon-money", show: "showPaymentMethods()" }
{ name: t('enterprise_fees'), icon_class: "icon-tasks", show: "showEnterpriseFees()" }
- { name: t('inventory_settings'), icon_class: "icon-list-ol", show: "showInventorySettings()" }
- { name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "showShopPreferences()" }
+ { name: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" }
+ { name: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" }
+ { name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" }
]
$scope.select(0)
@@ -42,8 +43,5 @@ angular.module("admin.enterprises")
$scope.showEnterpriseFees = ->
enterprisePermissions.can_manage_enterprise_fees && ($scope.Enterprise.sells != "none" || $scope.Enterprise.is_primary_producer)
- $scope.showInventorySettings = ->
- $scope.Enterprise.sells != "none"
-
- $scope.showShopPreferences = ->
+ $scope.enterpriseIsShop = ->
$scope.Enterprise.sells != "none"
diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
index 6be7e00ffa..2074a1ea05 100644
--- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
@@ -1 +1 @@
-angular.module("admin.enterprises", [
"admin.payment_methods",
"admin.utils",
"admin.shipping_methods",
"admin.users",
"textAngular",
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
'admin.dropdown',
'pasvaz.bindonce',
'ngSanitize']
)
\ No newline at end of file
+angular.module("admin.enterprises", [
"admin.paymentMethods",
"admin.utils",
"admin.shippingMethods",
"admin.users",
"textAngular",
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
'admin.tagRules',
'admin.dropdown',
'pasvaz.bindonce',
'ngSanitize']
)
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
index ec454e9216..132480d987 100644
--- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
@@ -15,8 +15,6 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout)
element.select2
minimumResultsForSearch: scope.minSearch || 0
data: { results: scope.data, text: scope.text }
- initSelection: (element, callback) ->
- callback scope.data[0]
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->
diff --git a/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
index 092fd5bbd2..c2595faa6d 100644
--- a/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
@@ -1,4 +1,4 @@
-angular.module("admin.payment_methods")
+angular.module("admin.paymentMethods")
.controller "paymentMethodCtrl", ($scope, PaymentMethods) ->
$scope.findPaymentMethodByID = (id) ->
- $scope.PaymentMethod = PaymentMethods.findByID(id)
\ No newline at end of file
+ $scope.PaymentMethod = PaymentMethods.findByID(id)
diff --git a/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee b/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
index e75142ae0d..01553647d4 100644
--- a/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
@@ -1 +1 @@
-angular.module("admin.payment_methods", [])
\ No newline at end of file
+angular.module("admin.paymentMethods", [])
diff --git a/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee b/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee
index 21e557cac3..c31a20d96f 100644
--- a/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee
@@ -1,4 +1,4 @@
-angular.module("admin.payment_methods")
+angular.module("admin.paymentMethods")
.factory "PaymentMethods", (paymentMethods) ->
new class PaymentMethods
paymentMethods: paymentMethods
diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
index dabe52574e..cc7bd4ee3e 100644
--- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
@@ -1,4 +1,2 @@
-angular.module("admin.shipping_methods")
- .controller "shippingMethodCtrl", ($scope, ShippingMethods) ->
- $scope.findShippingMethodByID = (id) ->
- $scope.ShippingMethod = ShippingMethods.findByID(id)
\ No newline at end of file
+angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) ->
+ $scope.shippingMethod = shippingMethod
diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee
new file mode 100644
index 0000000000..91569b2256
--- /dev/null
+++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee
@@ -0,0 +1,4 @@
+angular.module("admin.shippingMethods")
+ .controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
+ $scope.findShippingMethodByID = (id) ->
+ $scope.ShippingMethod = ShippingMethods.findByID(id)
diff --git a/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee
index 556445c869..c691f5dae5 100644
--- a/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee
@@ -1,4 +1,4 @@
-angular.module("admin.shipping_methods")
+angular.module("admin.shippingMethods")
.factory "ShippingMethods", (shippingMethods) ->
new class ShippingMethods
shippingMethods: shippingMethods
diff --git a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
index 99aeb9566d..232eee7045 100644
--- a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
@@ -1 +1 @@
-angular.module("admin.shipping_methods", [])
\ No newline at end of file
+angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils'])
diff --git a/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee b/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee
new file mode 100644
index 0000000000..7209147b66
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee
@@ -0,0 +1,48 @@
+angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, enterprise) ->
+ $scope.tagGroups = enterprise.tag_groups
+
+ $scope.visibilityOptions = [ { id: "visible", name: "VISIBLE" }, { id: "hidden", name: "NOT VISIBLE" } ]
+
+ updateRuleCounts = ->
+ index = 0
+ for tagGroup in $scope.tagGroups
+ tagGroup.startIndex = index
+ index = index + tagGroup.rules.length
+
+ updateRuleCounts()
+
+ $scope.updateTagsRulesFor = (tagGroup) ->
+ for tagRule in tagGroup.rules
+ tagRule.preferred_customer_tags = (tag.text for tag in tagGroup.tags).join(",")
+
+ $scope.addNewRuleTo = (tagGroup, ruleType) ->
+ newRule =
+ id: null
+ preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
+ type: "TagRule::#{ruleType}"
+ switch ruleType
+ when "DiscountOrder"
+ newRule.calculator = { preferred_flat_percent: 0 }
+ when "FilterShippingMethods"
+ newRule.peferred_shipping_method_tags = []
+ newRule.preferred_matched_shipping_methods_visibility = "visible"
+ tagGroup.rules.push(newRule)
+ updateRuleCounts()
+
+ $scope.addNewTag = ->
+ $scope.tagGroups.push { tags: [], rules: [] }
+
+ $scope.deleteTagRule = (tagGroup, tagRule) ->
+ index = tagGroup.rules.indexOf(tagRule)
+ return unless index >= 0
+ if tagRule.id is null
+ tagGroup.rules.splice(index, 1)
+ updateRuleCounts()
+ else
+ if confirm("Are you sure?")
+ $http
+ method: "DELETE"
+ url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json"
+ .success ->
+ tagGroup.rules.splice(index, 1)
+ updateRuleCounts()
diff --git a/app/assets/javascripts/admin/tag_rules/directives/invert_number.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/invert_number.js.coffee
new file mode 100644
index 0000000000..2412eec18c
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/directives/invert_number.js.coffee
@@ -0,0 +1,11 @@
+angular.module("admin.tagRules").directive "invertNumber", ->
+ restrict: "A"
+ require: "ngModel"
+ link: (scope, element, attrs, ngModel) ->
+ ngModel.$parsers.push (viewValue) ->
+ return -parseInt(viewValue) unless isNaN(parseInt(viewValue))
+ viewValue
+
+ ngModel.$formatters.push (modelValue) ->
+ return -parseInt(modelValue) unless isNaN(parseInt(modelValue))
+ modelValue
diff --git a/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee
new file mode 100644
index 0000000000..54e33006e4
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee
@@ -0,0 +1,34 @@
+angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, $window) ->
+ restrict: 'A'
+ scope: true
+ link: (scope, element, attr) ->
+ # Compile modal template
+ template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
+
+ scope.ruleTypes = [
+ # { id: "DiscountOrder", name: 'Apply a discount to orders' }
+ { id: "FilterShippingMethods", name: 'Show/Hide shipping methods' }
+ ]
+
+ scope.ruleType = "DiscountOrder"
+
+ # Set Dialog options
+ template.dialog
+ show: { effect: "fade", duration: 400 }
+ hide: { effect: "fade", duration: 300 }
+ autoOpen: false
+ resizable: false
+ width: $window.innerWidth * 0.4;
+ modal: true
+ open: (event, ui) ->
+ $('.ui-widget-overlay').bind 'click', ->
+ $(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
+
+ # Link opening of dialog to click event on element
+ element.bind 'click', (e) ->
+ template.dialog('open')
+
+ scope.addRule = (tagGroup, ruleType) ->
+ scope.addNewRuleTo(tagGroup, ruleType)
+ template.dialog('close')
+ return
diff --git a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee
new file mode 100644
index 0000000000..b374f88782
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee
@@ -0,0 +1,4 @@
+angular.module("admin.tagRules").directive "discountOrder", ->
+ restrict: "E"
+ replace: true
+ templateUrl: "admin/tag_rules/discount_order.html"
diff --git a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee
new file mode 100644
index 0000000000..1a75cf8ff2
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee
@@ -0,0 +1,4 @@
+angular.module("admin.tagRules").directive "filterShippingMethods", ->
+ restrict: "E"
+ replace: true
+ templateUrl: "admin/tag_rules/filter_shipping_methods.html"
diff --git a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee
new file mode 100644
index 0000000000..88c7734c33
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee
@@ -0,0 +1 @@
+angular.module("admin.tagRules", ['ngTagsInput'])
diff --git a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
new file mode 100644
index 0000000000..6ce7953608
--- /dev/null
+++ b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
@@ -0,0 +1,15 @@
+angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
+ restrict: "E"
+ template: ""
+ scope:
+ object: "="
+ tagsAttr: "@?"
+ tagListAttr: "@?"
+ link: (scope, element, attrs) ->
+ $timeout ->
+ scope.tagsAttr ||= "tags"
+ scope.tagListAttr ||= "tag_list"
+
+ watchString = "object.#{scope.tagsAttr}"
+ scope.$watchCollection watchString, ->
+ scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",")
diff --git a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee
index db995170dd..fa11c0e92f 100644
--- a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee
@@ -8,6 +8,7 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.show_profiles = false
$scope.filtersActive = false
$scope.distanceMatchesShown = false
+ $scope.filterExpression = {active: true}
$scope.$watch "query", (query)->
@@ -44,8 +45,8 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.filterEnterprises = ->
es = Enterprises.hubs
$scope.nameMatches = enterpriseMatchesNameQueryFilter(es, true)
- $scope.distanceMatches = enterpriseMatchesNameQueryFilter(es, false)
- $scope.distanceMatches = distanceWithinKmFilter($scope.distanceMatches, 50)
+ noNameMatches = enterpriseMatchesNameQueryFilter(es, false)
+ $scope.distanceMatches = distanceWithinKmFilter(noNameMatches, 50)
$scope.updateVisibleMatches = ->
@@ -65,3 +66,9 @@ Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterpris
$scope.nameMatchesFiltered[0]
else
undefined
+
+ $scope.showClosedShops = ->
+ delete $scope.filterExpression['active']
+
+ $scope.hideClosedShops = ->
+ $scope.filterExpression['active'] = true
diff --git a/app/assets/javascripts/darkswarm/directives/auth.js.coffee b/app/assets/javascripts/darkswarm/directives/auth.js.coffee
new file mode 100644
index 0000000000..46ae301651
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/directives/auth.js.coffee
@@ -0,0 +1,5 @@
+Darkswarm.directive 'auth', (AuthenticationService) ->
+ restrict: 'A'
+ link: (scope, elem, attrs) ->
+ elem.bind "click", ->
+ AuthenticationService.open '/' + attrs.auth
diff --git a/app/assets/javascripts/templates/admin/new_tag_rule_dialog.html.haml b/app/assets/javascripts/templates/admin/new_tag_rule_dialog.html.haml
new file mode 100644
index 0000000000..653e0d175d
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/new_tag_rule_dialog.html.haml
@@ -0,0 +1,10 @@
+#new-tag-rule-dialog
+ .text-normal.margin-bottom-30.text-center
+ Select a rule type:
+
+ .text-center.margin-bottom-30
+ -# %select.fullwidth{ 'select2-min-search' => 5, 'ng-model' => 'newRuleType', 'ng-options' => 'ruleType.id as ruleType.name for ruleType in availableRuleTypes' }
+ %input.ofn-select2.fullwidth{ :id => 'rule_type_selector', ng: { model: "ruleType" }, data: "ruleTypes", 'min-search' => "5" }
+
+ .text-center
+ %input.button.red.icon-plus{ type: 'button', value: "Add Rule", ng: { click: 'addRule(tagGroup, ruleType)' } }
diff --git a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml b/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml
new file mode 100644
index 0000000000..358d9ce1a6
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml
@@ -0,0 +1,37 @@
+%div
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
+ ng: { value: "rule.id" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
+ value: "TagRule::DiscountOrder" }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
+ ng: { value: "rule.preferred_customer_tags" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_type",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_type]",
+ value: "Spree::Calculator::FlatPercentItemTotal" }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_id",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][id]",
+ ng: { value: "rule.calculator.id" } }
+
+ %input{ type: "hidden",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][preferred_flat_percent]",
+ ng: { value: "rule.calculator.preferred_flat_percent" } }
+
+ %span.text-normal {{ $index + 1 }}. Orders are discounted by
+ %input{ type: "number",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent",
+ min: -100,
+ max: 100,
+ ng: { model: "rule.calculator.preferred_flat_percent" }, 'invert-number' => true }
+ %span.text-normal %
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml
new file mode 100644
index 0000000000..6552d834b5
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml
@@ -0,0 +1,27 @@
+%div
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
+ ng: { value: "rule.id" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
+ value: "TagRule::FilterShippingMethods" }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
+ ng: { value: "rule.preferred_customer_tags" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_shipping_method_tags",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_shipping_method_tags]",
+ ng: { value: "rule.preferred_customer_tags" } }
+
+ %span.text-normal {{ $index + 1 }}. Shipping methods with matching tags are
+ %input.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]",
+ ng: { model: "rule.preferred_matched_shipping_methods_visibility"},
+ data: 'visibilityOptions', "min-search" => 5 }
+ -# %tags-with-translation{ object: "rule", "tags-attr" => "shipping_method_tags", "tag-list-attr" => "preferred_shipping_method_tags" }
diff --git a/app/assets/stylesheets/admin/components/jquery_dialog.scss b/app/assets/stylesheets/admin/components/jquery_dialog.scss
new file mode 100644
index 0000000000..2e36db6e33
--- /dev/null
+++ b/app/assets/stylesheets/admin/components/jquery_dialog.scss
@@ -0,0 +1,88 @@
+/**
+Main colors:
+dark: #545454
+light: #ccc
+*/
+.ui-dialog {
+ border: 2px solid #4a4a4a;
+ border-radius:3px;
+ padding:0px;
+ -moz-box-shadow: 3px 3px 4px #797979;
+ -webkit-box-shadow: 3px 3px 4px #797979;
+ box-shadow: 3px 3px 4px #797979;
+
+ /* For IE 8 */
+ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#545454')";
+
+ /* For IE 5.5 - 7 */
+ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#545454');
+}
+
+.ui-dialog .ui-dialog-titlebar{
+ border-radius: 3px;
+}
+
+.ui-dialog .ui-state-hover {
+ &.ui-dialog-titlebar-close{
+
+ }
+}
+
+/*.ui-dialog .ui-icon-closethick{background:url(/static/assets/dialogCloseButton.png);}*/
+
+.ui-dialog .ui-widget-header{
+ background-image: none;
+ background-color: #ffffff;
+ border:0px;
+ border-radius: 3px;
+ padding: 0px 5px 0px 5px;
+}
+.ui-dialog .ui-widget-content{
+ border: none;
+ border-radius: 3px;
+ padding: 0px 50px 30px 50px;
+}
+
+.ui-dialog .ui-corner-all{
+ border-radius:0px;
+}
+.ui-dialog {
+ .ui-state-hover, .ui-state-focus{
+ border: none;
+ background: none;
+ color: #545454;
+ }
+}
+
+.ui-state-hover, .ui-widget-header .ui-state-hover, .ui-widget-content .ui-state-hover {
+ background-color: #ffffff;
+ background: none;
+}
+
+.ui-dialog-titlebar-close {
+ float: right;
+ &:before {
+ color: #000000;
+ font-size: 2em;
+ font-weight: 400;
+ content: '\00d7';
+ display: inline;
+ }
+
+ &:hover {
+ &:before {
+ color: #da5354;
+ }
+ }
+
+ .ui-icon {
+ &.ui-icon-closethick {
+ display: none;
+ }
+ }
+}
+
+.ui-widget-overlay {
+ background: #e9e9e9;
+ opacity: 0.6;
+}
diff --git a/app/assets/stylesheets/admin/offsets.css.scss b/app/assets/stylesheets/admin/offsets.css.scss
index 762b7469f6..190ed49243 100644
--- a/app/assets/stylesheets/admin/offsets.css.scss
+++ b/app/assets/stylesheets/admin/offsets.css.scss
@@ -2,6 +2,10 @@
margin-bottom: 20px;
}
+.margin-bottom-30 {
+ margin-bottom: 30px;
+}
+
.margin-bottom-50 {
margin-bottom: 50px;
}
diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss
index 9a2dd4385c..761ccbc014 100644
--- a/app/assets/stylesheets/admin/orders.css.scss
+++ b/app/assets/stylesheets/admin/orders.css.scss
@@ -69,6 +69,21 @@ div#group_buy_calculation {
text-indent:1em;
}
}
+
+ &.after {
+
+ span {
+ position: absolute;
+ transform: translate(0,-55%);
+ top:50%;
+ right: 0.5em;
+ pointer-events:none;
+ }
+
+ input {
+ padding-right: 1.2em;
+ }
+ }
}
th.actions {
diff --git a/app/assets/stylesheets/admin/select2.css.scss b/app/assets/stylesheets/admin/select2.css.scss
index daba11d099..f94515a54c 100644
--- a/app/assets/stylesheets/admin/select2.css.scss
+++ b/app/assets/stylesheets/admin/select2.css.scss
@@ -1,5 +1,8 @@
.select2-container {
.select2-choice {
+ .select2-search-choice-close {
+ display: none;
+ }
.select2-arrow {
width: 22px;
border: none;
@@ -7,4 +10,36 @@
background-color: transparent;
}
}
+
+ &.light {
+ .select2-choice{
+ background-color: #ffffff;
+ font-weight: normal;
+ border: 1px solid #5498da !important;
+ color: #5498da !important;
+
+ .select2-arrow {
+ &:before {
+ color: #5498da;
+ font-size: 1rem;
+ font-weight: 400;
+ content: '\25be';
+ display: inline;
+ }
+ }
+ }
+
+ &:hover, &.select2-container-active {
+ .select2-choice{
+ color: #ffffff !important;
+ background-color: #5498da !important;
+
+ .select2-arrow {
+ &:before {
+ color: #ffffff;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/assets/stylesheets/admin/tag_rules.css.scss b/app/assets/stylesheets/admin/tag_rules.css.scss
new file mode 100644
index 0000000000..028ad6ce1c
--- /dev/null
+++ b/app/assets/stylesheets/admin/tag_rules.css.scss
@@ -0,0 +1,68 @@
+.no_tags {
+ margin-bottom: 40px;
+ color: #aeaeae;
+ font-size: 1rem;
+ font-weight: bold;
+}
+
+.customer_tag {
+ border: 1px solid #cee1f4;
+ margin-bottom: 40px;
+
+ .header {
+ padding: 8px 10px;
+ background-color: #eff5fc;
+ border-bottom: 1px solid #cee1f4;
+
+ table {
+ padding: 0px;
+ margin: 0px 0px 0px 0px;
+ tr {
+ td {
+ border: none;
+ }
+ }
+ }
+ }
+
+ .no_rules {
+ padding: 8px 10px;
+ margin-bottom: 10px;
+ color: #aeaeae;
+ font-size: 1rem;
+ font-weight: bold;
+ }
+
+ table {
+ padding: 0px;
+ margin: 0px 0px 10px 0px;
+
+ tr.tag_rule {
+ border: none;
+ padding: 0px;
+ margin: 0px;
+
+ td {
+ border: none;
+ padding: 4px 10px 10px 10px;
+ margin: 0px;
+
+ input {
+ width: auto;
+ }
+ }
+ }
+ }
+
+ .add_rule {
+ padding: 8px 10px;
+ margin-bottom: 10px;
+ }
+}
+
+#new-tag-rule-dialog{
+ .select2-chosen, .select2-result-label{
+ font-size: 1rem;
+ font-weight: lighter;
+ }
+}
diff --git a/app/assets/stylesheets/admin/typography.css.scss b/app/assets/stylesheets/admin/typography.css.scss
index 20148df3f1..761058fb1d 100644
--- a/app/assets/stylesheets/admin/typography.css.scss
+++ b/app/assets/stylesheets/admin/typography.css.scss
@@ -7,3 +7,12 @@
font-size: 1.2rem;
font-weight: 300;
}
+
+.text-red {
+ color: #DA5354;
+}
+
+
+input.text-big {
+ font-size: 1.1rem;
+}
diff --git a/app/assets/stylesheets/darkswarm/footer.sass b/app/assets/stylesheets/darkswarm/footer.sass
index 76dd0f4384..a0d2244c0e 100644
--- a/app/assets/stylesheets/darkswarm/footer.sass
+++ b/app/assets/stylesheets/darkswarm/footer.sass
@@ -30,7 +30,7 @@ footer
background-color: transparent
border: none
padding: 0
- a.big-alert
+ a.alert-cta
@include csstrans
width: 100%
border: 1px solid rgba($dark-grey, 0.35)
diff --git a/app/assets/stylesheets/darkswarm/hubs.css.sass b/app/assets/stylesheets/darkswarm/hubs.css.sass
index a351170d99..5946d6ee77 100644
--- a/app/assets/stylesheets/darkswarm/hubs.css.sass
+++ b/app/assets/stylesheets/darkswarm/hubs.css.sass
@@ -7,4 +7,7 @@
@include sidepaddingSm
.name-matches, .distance-matches
- margin-top: 4em
\ No newline at end of file
+ margin-top: 4em
+
+ .more-controls
+ text-align: center
diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass
index 5422c4463a..eab074369e 100644
--- a/app/assets/stylesheets/darkswarm/shop.css.sass
+++ b/app/assets/stylesheets/darkswarm/shop.css.sass
@@ -84,9 +84,11 @@
padding-right: 0rem
font-size: 0.8rem
- .shopfront_message, .shopfront_closed_message
+ .shopfront_message, .shopfront_closed_message, .shopfront_hidden_message
padding: 15px
border-radius: 5px
+
+ .shopfront_message, .shopfront_closed_message
border: 2px solid #eb4c46
.shopfront_message
@@ -94,3 +96,7 @@
.shopfront_closed_message
margin: 2em 0em
+
+ .shopfront_hidden_message
+ border: 2px solid #db4
+ margin: 2em 0em
diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb
index b1ceb88c2f..bd865c8130 100644
--- a/app/controllers/admin/customers_controller.rb
+++ b/app/controllers/admin/customers_controller.rb
@@ -7,13 +7,25 @@ module Admin
respond_to do |format|
format.html
format.json do
- render json: ActiveModel::ArraySerializer.new( @collection,
- each_serializer: Api::Admin::CustomerSerializer, spree_current_user: spree_current_user
- ).to_json
+ serialised = ActiveModel::ArraySerializer.new(
+ @collection,
+ each_serializer: Api::Admin::CustomerSerializer,
+ spree_current_user: spree_current_user)
+ render json: serialised.to_json
end
end
end
+ def create
+ @customer = Customer.new(params[:customer])
+ if user_can_create_customer?
+ @customer.save
+ render json: Api::Admin::CustomerSerializer.new(@customer).to_json
+ else
+ redirect_to '/unauthorized'
+ end
+ end
+
private
def collection
@@ -25,5 +37,10 @@ module Admin
def load_managed_shops
@shops = Enterprise.managed_by(spree_current_user).is_distributor
end
+
+ def user_can_create_customer?
+ spree_current_user.admin? ||
+ spree_current_user.enterprises.include?(@customer.enterprise)
+ end
end
end
diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb
index 55eaabd7d8..377186b604 100644
--- a/app/controllers/admin/enterprises_controller.rb
+++ b/app/controllers/admin/enterprises_controller.rb
@@ -4,7 +4,7 @@ module Admin
class EnterprisesController < ResourceController
before_filter :load_enterprise_set, :only => :index
before_filter :load_countries, :except => [:index, :register, :check_permalink]
- before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create]
+ before_filter :load_methods_and_fees, :only => [:edit, :update]
before_filter :load_groups, :only => [:new, :edit, :update, :create]
before_filter :load_taxons, :only => [:new, :edit, :update, :create]
before_filter :check_can_change_sells, only: :update
@@ -35,6 +35,8 @@ module Admin
def update
invoke_callbacks(:update, :before)
+ tag_rules_attributes = params[object_name].delete :tag_rules_attributes
+ update_tag_rules(tag_rules_attributes) if tag_rules_attributes.present?
if @object.update_attributes(params[object_name])
invoke_callbacks(:update, :after)
flash[:success] = flash_message_for(@object, :successfully_updated)
@@ -180,6 +182,27 @@ module Admin
@taxons = Spree::Taxon.order(:name)
end
+ def update_tag_rules(tag_rules_attributes)
+ # Due to the combination of trying to use nested attributes and type inheritance
+ # we cannot apply all attributes to tag rules in one hit because mass assignment
+ # methods that are specific to each class do not become available until after the
+ # record is persisted. This problem is compounded by the use of calculators.
+ @object.transaction do
+ tag_rules_attributes.select{ |i, attrs| attrs[:type].present? }.each do |i, attrs|
+ rule = @object.tag_rules.find_by_id(attrs.delete :id) || attrs[:type].constantize.new(enterprise: @object)
+ create_calculator_for(rule, attrs) if rule.type == "TagRule::DiscountOrder" && rule.calculator.nil?
+ rule.update_attributes(attrs)
+ end
+ end
+ end
+
+ def create_calculator_for(rule, attrs)
+ if attrs[:calculator_type].present? && attrs[:calculator_attributes].present?
+ rule.update_attributes(calculator_type: attrs[:calculator_type])
+ attrs[:calculator_attributes].merge!( { id: rule.calculator.id } )
+ end
+ end
+
def check_can_change_bulk_sells
unless spree_current_user.admin?
params[:enterprise_set][:collection_attributes].each do |i, enterprise_params|
diff --git a/app/controllers/admin/tag_rules_controller.rb b/app/controllers/admin/tag_rules_controller.rb
new file mode 100644
index 0000000000..7d60cb4888
--- /dev/null
+++ b/app/controllers/admin/tag_rules_controller.rb
@@ -0,0 +1,10 @@
+module Admin
+ class TagRulesController < ResourceController
+
+ respond_to :json
+
+ respond_override destroy: { json: {
+ success: lambda { render nothing: true, :status => 204 }
+ } }
+ end
+end
diff --git a/app/controllers/api/statuses_controller.rb b/app/controllers/api/statuses_controller.rb
new file mode 100644
index 0000000000..c8844b868b
--- /dev/null
+++ b/app/controllers/api/statuses_controller.rb
@@ -0,0 +1,17 @@
+module Api
+ class StatusesController < BaseController
+ respond_to :json
+
+ def job_queue
+ render json: {alive: job_queue_alive?}
+ end
+
+
+ private
+
+ def job_queue_alive?
+ Spree::Config.last_job_queue_heartbeat_at.present? &&
+ Time.parse(Spree::Config.last_job_queue_heartbeat_at) > 6.minutes.ago
+ end
+ end
+end
diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb
index 511eced707..0c032eaa9e 100644
--- a/app/helpers/admin/injection_helper.rb
+++ b/app/helpers/admin/injection_helper.rb
@@ -20,11 +20,15 @@ module Admin
end
def admin_inject_payment_methods
- admin_inject_json_ams_array "admin.payment_methods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
+ admin_inject_json_ams_array "admin.paymentMethods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
end
def admin_inject_shipping_methods
- admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
+ admin_inject_json_ams_array "admin.shippingMethods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
+ end
+
+ def admin_inject_shipping_method
+ admin_inject_json_ams "admin.shippingMethods", "shippingMethod", @shipping_method, Api::Admin::ShippingMethodSerializer
end
def admin_inject_shops(ngModule='admin.customers')
diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb
index 85d1167ab7..5aa36624cd 100644
--- a/app/helpers/enterprises_helper.rb
+++ b/app/helpers/enterprises_helper.rb
@@ -4,7 +4,11 @@ module EnterprisesHelper
end
def available_shipping_methods
- current_distributor.shipping_methods.uniq
+ shipping_methods = current_distributor.shipping_methods
+ if current_distributor.present?
+ current_distributor.apply_tag_rules_to(shipping_methods, customer: current_order.customer)
+ end
+ shipping_methods.uniq
end
def managed_enterprises
diff --git a/app/helpers/shop_helper.rb b/app/helpers/shop_helper.rb
index 3066bfbe05..920d41d65e 100644
--- a/app/helpers/shop_helper.rb
+++ b/app/helpers/shop_helper.rb
@@ -7,4 +7,16 @@ module ShopHelper
]
end
end
+
+ def require_customer?
+ current_distributor.require_login? && !user_is_related_to_distributor?
+ end
+
+ def user_is_related_to_distributor?
+ spree_current_user.present? && (
+ spree_current_user.admin? ||
+ spree_current_user.enterprises.include?(current_distributor) ||
+ spree_current_user.customer_of(current_distributor)
+ )
+ end
end
diff --git a/app/helpers/spree/reports_helper.rb b/app/helpers/spree/reports_helper.rb
index bbc184d800..aaed402a6f 100644
--- a/app/helpers/spree/reports_helper.rb
+++ b/app/helpers/spree/reports_helper.rb
@@ -11,11 +11,17 @@ module Spree
end
def report_payment_method_options(orders)
- orders.map { |o| o.payments.first.payment_method.andand.name }.uniq
+ orders.map do |o|
+ pm = o.payments.first.payment_method
+ [pm.andand.name, pm.andand.id]
+ end.uniq
end
def report_shipping_method_options(orders)
- orders.map { |o| o.shipping_method.andand.name }.uniq
+ orders.map do |o|
+ sm = o.shipping_method
+ [sm.andand.name, sm.andand.id]
+ end.uniq
end
def xero_report_types
diff --git a/app/jobs/heartbeat_job.rb b/app/jobs/heartbeat_job.rb
new file mode 100644
index 0000000000..93e835905f
--- /dev/null
+++ b/app/jobs/heartbeat_job.rb
@@ -0,0 +1,5 @@
+class HeartbeatJob
+ def perform
+ Spree::Config.last_job_queue_heartbeat_at = Time.now
+ end
+end
diff --git a/app/models/content_configuration.rb b/app/models/content_configuration.rb
index 2357f8dfa3..e20279f5ee 100644
--- a/app/models/content_configuration.rb
+++ b/app/models/content_configuration.rb
@@ -7,14 +7,14 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
preference :logo, :file
preference :logo_mobile, :file
preference :logo_mobile_svg, :file
- has_attached_file :logo
+ has_attached_file :logo, default_url: "/assets/ofn-logo.png"
has_attached_file :logo_mobile
- has_attached_file :logo_mobile_svg
+ has_attached_file :logo_mobile_svg, default_url: "/assets/ofn-logo-mobile.svg"
# Home page
preference :home_hero, :file
preference :home_show_stats, :boolean, default: true
- has_attached_file :home_hero
+ has_attached_file :home_hero, default_url: "/assets/home/home.jpg"
# Producer sign-up page
preference :producer_signup_pricing_table_html, :text, default: "(TODO: Pricing table)"
@@ -33,7 +33,7 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
# Footer
preference :footer_logo, :file
- has_attached_file :footer_logo
+ has_attached_file :footer_logo, default_url: "/assets/ofn-logo-footer.png"
preference :footer_facebook_url, :string, default: "https://www.facebook.com/OpenFoodNet"
preference :footer_twitter_url, :string, default: "https://twitter.com/OpenFoodNet"
preference :footer_instagram_url, :string, default: ""
diff --git a/app/models/customer.rb b/app/models/customer.rb
index bcafd3246b..34f62a6aa6 100644
--- a/app/models/customer.rb
+++ b/app/models/customer.rb
@@ -4,6 +4,8 @@ class Customer < ActiveRecord::Base
belongs_to :enterprise
belongs_to :user, class_name: Spree.user_class
+ before_validation :downcase_email
+
validates :code, uniqueness: { scope: :enterprise_id, allow_blank: true, allow_nil: true }
validates :email, presence: true, uniqueness: { scope: :enterprise_id, message: I18n.t('validation_msg_is_associated_with_an_exising_customer') }
validates :enterprise_id, presence: true
@@ -14,6 +16,10 @@ class Customer < ActiveRecord::Base
private
+ def downcase_email
+ email.andand.downcase!
+ end
+
def associate_user
self.user = user || Spree::User.find_by_email(email)
end
diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb
index 432b53bd4d..dfc5050a38 100644
--- a/app/models/enterprise.rb
+++ b/app/models/enterprise.rb
@@ -42,11 +42,13 @@ class Enterprise < ActiveRecord::Base
has_many :customers
has_many :billable_periods
has_many :inventory_items
+ has_many :tag_rules
delegate :latitude, :longitude, :city, :state_name, :to => :address
accepts_nested_attributes_for :address
accepts_nested_attributes_for :producer_properties, allow_destroy: true, reject_if: lambda { |pp| pp[:property_name].blank? }
+ accepts_nested_attributes_for :tag_rules, allow_destroy: true, reject_if: lambda { |tag_rule| tag_rule[:preferred_customer_tags].blank? }
has_attached_file :logo,
styles: { medium: "300x300>", small: "180x180>", thumb: "100x100>" },
@@ -176,17 +178,6 @@ class Enterprise < ActiveRecord::Base
end
}
- def self.find_near(suburb)
- enterprises = []
-
- unless suburb.nil?
- addresses = Spree::Address.near([suburb.latitude, suburb.longitude], ENTERPRISE_SEARCH_RADIUS, :units => :km).joins(:enterprise).limit(10)
- enterprises = addresses.collect(&:enterprise)
- end
-
- enterprises
- end
-
# Force a distinct count to work around relation count issue https://github.com/rails/rails/issues/5554
def self.distinct_count
count(distinct: true)
@@ -354,6 +345,13 @@ class Enterprise < ActiveRecord::Base
abn.present?
end
+ def apply_tag_rules_to(subject, context)
+ tag_rules.each do |rule|
+ rule.set_context(subject,context)
+ rule.apply
+ end
+ end
+
protected
def devise_mailer
diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb
index 8c93fe4b71..867d3c9e2b 100644
--- a/app/models/spree/ability_decorator.rb
+++ b/app/models/spree/ability_decorator.rb
@@ -72,6 +72,10 @@ class AbilityDecorator
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
+ can [:admin, :destroy], TagRule do |tag_rule|
+ user.enterprises.include? tag_rule.enterprise
+ end
+
can [:admin, :index, :create], Enterprise
can [:read, :edit, :update, :bulk_update, :resend_confirmation], Enterprise do |enterprise|
OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise
@@ -97,6 +101,11 @@ class AbilityDecorator
can [:print], Spree::Order do |order|
order.user == user
end
+
+ can [:create], Customer
+ can [:destroy], Customer do |customer|
+ user.enterprises.include? customer.enterprise
+ end
end
def add_product_management_abilities(user)
diff --git a/app/models/spree/app_configuration_decorator.rb b/app/models/spree/app_configuration_decorator.rb
index fc7a8171cc..6ef1e7b848 100644
--- a/app/models/spree/app_configuration_decorator.rb
+++ b/app/models/spree/app_configuration_decorator.rb
@@ -20,4 +20,7 @@ Spree::AppConfiguration.class_eval do
preference :account_invoices_monthly_rate, :decimal, default: 0
preference :account_invoices_monthly_cap, :decimal, default: 0
preference :account_invoices_tax_rate, :decimal, default: 0
+
+ # Monitoring
+ preference :last_job_queue_heartbeat_at, :string, default: nil
end
diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb
index 8f1c0381b1..f0e9324185 100644
--- a/app/models/spree/order_decorator.rb
+++ b/app/models/spree/order_decorator.rb
@@ -17,7 +17,8 @@ Spree::Order.class_eval do
attr_accessible :order_cycle_id, :distributor_id
before_validation :shipping_address_from_distributor
- before_validation :associate_customer, unless: :customer_is_valid?
+ before_validation :associate_customer, unless: :customer_id?
+ before_validation :ensure_customer, unless: :customer_is_valid?
checkout_flow do
go_to_state :address
@@ -69,13 +70,6 @@ Spree::Order.class_eval do
where("state != ?", state)
}
- scope :with_payment_method_name, lambda { |payment_method_name|
- joins(:payments => :payment_method).
- where('spree_payment_methods.name IN (?)', payment_method_name).
- select('DISTINCT spree_orders.*')
- }
-
-
# -- Methods
def products_available_from_new_distribution
# Check that the line_items in the current order are available from a newly selected distribution
@@ -179,6 +173,10 @@ Spree::Order.class_eval do
if order_cycle
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_order_adjustments_for self
end
+
+ if distributor.present? && customer.present?
+ distributor.apply_tag_rules_to(self, customer: customer)
+ end
end
end
@@ -286,17 +284,21 @@ Spree::Order.class_eval do
def customer_is_valid?
return true unless require_customer?
- customer.present? && customer.enterprise_id == distributor_id && customer.email == (user.andand.email || email)
+ customer.present? && customer.enterprise_id == distributor_id && customer.email == email_for_customer
+ end
+
+ def email_for_customer
+ (user.andand.email || email).andand.downcase
end
def associate_customer
- email_for_customer = user.andand.email || email
- existing_customer = Customer.of(distributor).find_by_email(email_for_customer)
- if existing_customer
- self.customer = existing_customer
- else
- new_customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user)
- self.customer = new_customer
+ return customer if customer.present?
+ self.customer = Customer.of(distributor).find_by_email(email_for_customer)
+ end
+
+ def ensure_customer
+ unless associate_customer
+ self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user)
end
end
end
diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb
index b8be603048..f4f1d2c999 100644
--- a/app/models/spree/shipping_method_decorator.rb
+++ b/app/models/spree/shipping_method_decorator.rb
@@ -1,10 +1,12 @@
Spree::ShippingMethod.class_eval do
+ acts_as_taggable
+
has_many :distributor_shipping_methods
has_many :distributors, through: :distributor_shipping_methods, class_name: 'Enterprise', foreign_key: 'distributor_id'
after_save :touch_distributors
attr_accessible :distributor_ids, :description
- attr_accessible :require_ship_address
+ attr_accessible :require_ship_address, :tag_list
validates :distributors, presence: { message: "^At least one hub must be selected" }
diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb
index 274330d1da..9a992ba910 100644
--- a/app/models/spree/user_decorator.rb
+++ b/app/models/spree/user_decorator.rb
@@ -15,6 +15,7 @@ Spree.user_class.class_eval do
accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true
attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit
+ after_create :associate_customers
after_create :send_signup_confirmation
validate :limit_owned_enterprises
@@ -41,6 +42,10 @@ Spree.user_class.class_eval do
customers.of(enterprise).first
end
+ def associate_customers
+ Customer.update_all({ user_id: id }, { user_id: nil, email: email })
+ end
+
def send_signup_confirmation
Delayed::Job.enqueue ConfirmSignupJob.new(id)
end
diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb
new file mode 100644
index 0000000000..6e6405a589
--- /dev/null
+++ b/app/models/tag_rule.rb
@@ -0,0 +1,42 @@
+class TagRule < ActiveRecord::Base
+ attr_accessor :subject, :context
+
+ belongs_to :enterprise
+
+ preference :customer_tags, :string, default: ""
+
+ validates :enterprise, presence: true
+
+ attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags
+
+ def set_context(subject, context)
+ @subject = subject
+ @context = context
+ end
+
+ def apply
+ if relevant?
+ if customer_tags_match?
+ apply!
+ else
+ apply_default! if respond_to?(:apply_default!,true)
+ end
+ end
+ end
+
+ private
+
+ def relevant?
+ return false unless subject_class_matches?
+ if respond_to?(:additional_requirements_met?, true)
+ return false unless additional_requirements_met?
+ end
+ true
+ end
+
+ def customer_tags_match?
+ context_customer_tags = context.andand[:customer].andand.tag_list || []
+ preferred_tags = preferred_customer_tags.split(",")
+ ( context_customer_tags & preferred_tags ).any?
+ end
+end
diff --git a/app/models/tag_rule/discount_order.rb b/app/models/tag_rule/discount_order.rb
new file mode 100644
index 0000000000..5984814289
--- /dev/null
+++ b/app/models/tag_rule/discount_order.rb
@@ -0,0 +1,23 @@
+class TagRule::DiscountOrder < TagRule
+ calculated_adjustments
+
+ private
+
+ # Warning: this should only EVER be called via TagRule#apply
+ def apply!
+ create_adjustment(I18n.t("discount"), subject, subject)
+ end
+
+ def subject_class_matches?
+ subject.class == Spree::Order
+ end
+
+ def additional_requirements_met?
+ return false if already_applied?
+ true
+ end
+
+ def already_applied?
+ subject.adjustments.where(originator_id: id, originator_type: "TagRule").any?
+ end
+end
diff --git a/app/models/tag_rule/filter_shipping_methods.rb b/app/models/tag_rule/filter_shipping_methods.rb
new file mode 100644
index 0000000000..74438e560e
--- /dev/null
+++ b/app/models/tag_rule/filter_shipping_methods.rb
@@ -0,0 +1,32 @@
+class TagRule::FilterShippingMethods < TagRule
+ preference :matched_shipping_methods_visibility, :string, default: "visible"
+ preference :shipping_method_tags, :string, default: ""
+
+ attr_accessible :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags
+
+ private
+
+ # Warning: this should only EVER be called via TagRule#apply
+ def apply!
+ unless preferred_matched_shipping_methods_visibility == "visible"
+ subject.reject!{ |sm| tags_match?(sm) }
+ end
+ end
+
+ def apply_default!
+ if preferred_matched_shipping_methods_visibility == "visible"
+ subject.reject!{ |sm| tags_match?(sm) }
+ end
+ end
+
+ def tags_match?(shipping_method)
+ shipping_method_tags = shipping_method.andand.tag_list || []
+ preferred_tags = preferred_shipping_method_tags.split(",")
+ ( shipping_method_tags & preferred_tags ).any?
+ end
+
+ def subject_class_matches?
+ subject.class == Array &&
+ subject.all? { |i| i.class == Spree::ShippingMethod }
+ end
+end
diff --git a/app/overrides/spree/admin/shared/_configuration_menu/add_accounts_and_billing.html.haml.deface b/app/overrides/spree/admin/shared/_configuration_menu/add_accounts_and_billing.html.haml.deface
index 14f4925206..37754bb40e 100644
--- a/app/overrides/spree/admin/shared/_configuration_menu/add_accounts_and_billing.html.haml.deface
+++ b/app/overrides/spree/admin/shared/_configuration_menu/add_accounts_and_billing.html.haml.deface
@@ -1,4 +1,4 @@
// insert_bottom "[data-hook='admin_configurations_sidebar_menu']"
%li
- = link_to 'Accounts & Billing', main_app.edit_admin_accounts_and_billing_settings_path
+ = link_to t(:accounts_and_billing), main_app.edit_admin_accounts_and_billing_settings_path
diff --git a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
index 190cff1a17..a2f9ed766a 100644
--- a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
+++ b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
@@ -1,6 +1,9 @@
/ replace "div[data-hook='admin_shipping_method_form_fields']"
-.alpha.eleven.columns{"data-hook" => "admin_shipping_method_form_fields"}
+=admin_inject_shipping_method
+.alpha.eleven.columns{ "data-hook" => "admin_shipping_method_form_fields",
+ "ng-app" => "admin.shippingMethods",
+ "ng-controller" => "shippingMethodCtrl" }
.row
.alpha.three.columns
= f.label :name, t(:name)
@@ -46,6 +49,13 @@
= f.label :pick_up, t(:pick_up)
+ .row
+ .alpha.three.columns
+ = f.label :tags, t(:tags)
+ .omega.eight.columns
+ = f.hidden_field :tag_list, "ng-value" => "shippingMethod.tag_list"
+ %tags-with-translation#something{ object: "shippingMethod" }
+
.row
.alpha.eleven.columns
- = render :partial => 'spree/admin/shared/calculator_fields', :locals => { :f => f }
\ No newline at end of file
+ = render :partial => 'spree/admin/shared/calculator_fields', :locals => { :f => f }
diff --git a/app/serializers/api/admin/calculator/flat_percent_item_total_serializer.rb b/app/serializers/api/admin/calculator/flat_percent_item_total_serializer.rb
new file mode 100644
index 0000000000..7662edb3f6
--- /dev/null
+++ b/app/serializers/api/admin/calculator/flat_percent_item_total_serializer.rb
@@ -0,0 +1,7 @@
+class Api::Admin::Calculator::FlatPercentItemTotalSerializer < ActiveModel::Serializer
+ attributes :id, :preferred_flat_percent
+
+ def preferred_flat_percent
+ object.preferred_flat_percent.to_i
+ end
+end
diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb
index 37a96b402f..8f70da1a0d 100644
--- a/app/serializers/api/admin/enterprise_serializer.rb
+++ b/app/serializers/api/admin/enterprise_serializer.rb
@@ -3,8 +3,25 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :producer_profile_only, :email, :long_description, :permalink
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
- attributes :owner, :users
+ attributes :owner, :users, :tag_groups
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
+
+ def tag_groups
+ tag_groups = []
+ object.tag_rules.each do |tag_rule|
+ tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } })
+ tag_groups << tag_group if tag_group[:rules].empty?
+ tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
+ end
+ tag_groups
+ end
+
+ def find_match(tag_groups, tags)
+ tag_groups.each do |tag_group|
+ return tag_group if tag_group[:tags].length == tags.length && (tag_group[:tags] & tags) == tag_group[:tags]
+ end
+ return { tags: tags, rules: [] }
+ end
end
diff --git a/app/serializers/api/admin/shipping_method_serializer.rb b/app/serializers/api/admin/shipping_method_serializer.rb
new file mode 100644
index 0000000000..9fbb864d09
--- /dev/null
+++ b/app/serializers/api/admin/shipping_method_serializer.rb
@@ -0,0 +1,7 @@
+class Api::Admin::ShippingMethodSerializer < ActiveModel::Serializer
+ attributes :id, :name, :tags
+
+ def tags
+ object.tag_list.map{ |t| { text: t } }
+ end
+end
diff --git a/app/serializers/api/admin/tag_rule_serializer.rb b/app/serializers/api/admin/tag_rule_serializer.rb
new file mode 100644
index 0000000000..ac333c23c6
--- /dev/null
+++ b/app/serializers/api/admin/tag_rule_serializer.rb
@@ -0,0 +1,27 @@
+class Api::Admin::TagRuleSerializer < ActiveModel::Serializer
+ def serializable_hash
+ rule_specific_serializer.serializable_hash
+ end
+
+ def rule_specific_serializer
+ "Api::Admin::#{object.class.to_s}Serializer".constantize.new(object)
+ end
+end
+
+module Api::Admin::TagRule
+ class BaseSerializer < ActiveModel::Serializer
+ attributes :id, :enterprise_id, :type, :preferred_customer_tags
+ end
+
+ class DiscountOrderSerializer < BaseSerializer
+ has_one :calculator, serializer: Api::Admin::Calculator::FlatPercentItemTotalSerializer
+ end
+
+ class FilterShippingMethodsSerializer < BaseSerializer
+ attributes :preferred_matched_shipping_methods_visibility, :shipping_method_tags
+
+ def shipping_method_tags
+ object.preferred_shipping_method_tags.split(",")
+ end
+ end
+end
diff --git a/app/views/admin/accounts_and_billing_settings/edit.html.haml b/app/views/admin/accounts_and_billing_settings/edit.html.haml
index 2121719e75..72ce7920a9 100644
--- a/app/views/admin/accounts_and_billing_settings/edit.html.haml
+++ b/app/views/admin/accounts_and_billing_settings/edit.html.haml
@@ -1,7 +1,7 @@
= render :partial => 'spree/admin/shared/configuration_menu'
- content_for :page_title do
- = t(:accounts_and_billing_settings)
+ = t(:accounts_and_billing)
= render 'spree/shared/error_messages', target: @settings
@@ -11,7 +11,7 @@
%legend
=t :admin_settings
= form_for @settings, as: :settings, url: main_app.admin_accounts_and_billing_settings_path, :method => :put do |f|
- .row{ ng: { app: t(:admin_accounts_and_billing) } }
+ .row{ ng: { app: 'admin.accounts_and_billing_settings' } }
.twelve.columns.alpha.omega
.field
= f.label :accounts_distributor_id, t(:accounts_administration_distributor)
diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml
index f68ad37da1..e33b871df4 100644
--- a/app/views/admin/customers/index.html.haml
+++ b/app/views/admin/customers/index.html.haml
@@ -5,7 +5,7 @@
= admin_inject_shops
%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } }
- .row{ ng: { hide: "loaded() && filteredCustomers.length > 0" } }
+ .row{ ng: { hide: "loaded && customers.length > 0" } }
.five.columns.alpha
%h3
=t :please_select_hub
@@ -13,25 +13,23 @@
%select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' }
.seven.columns.omega
- .row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' }
+ .row{ 'ng-hide' => '!loaded || customers.length == 0' }
.controls.sixteen.columns.alpha.omega
.five.columns.alpha
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
- .five.columns
- -# =render 'admin/shared/bulk_actions_dropdown'
- .three.columns
+ .eight.columns
= render 'admin/shared/columns_dropdown'
- .row{ 'ng-if' => 'shop && !loaded()' }
+ .row{ 'ng-if' => 'shop.id && !loaded' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1
=t :loading_customers
- .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded() && filteredCustomers.length == 0'}
+ .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredCustomers.length == 0'}
%h1#no_results
=t :no_customers_found
- .row{ ng: { show: "loaded() && filteredCustomers.length > 0" } }
+ .row{ ng: { show: "loaded && filteredCustomers.length > 0" } }
%form{ name: "customers" }
%table.index#customers
%col.email{ width: "20%"}
@@ -62,3 +60,11 @@
%td.actions
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
%input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' }
+
+ %form{ng: {show: "loaded", submit: 'add(newCustomerEmail)'}}
+ %h2= t '.add_new_customer'
+ .row
+ .five.columns.alpha
+ %input.fullwidth{type: "text", placeholder: t('.customer_placeholder'), ng: {model: 'newCustomerEmail'}}
+ .eleven.columns.omega
+ %input{type: "submit", value: t('.add_customer')}
diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml
index 79faeea229..e363078733 100644
--- a/app/views/admin/enterprises/_form.html.haml
+++ b/app/views/admin/enterprises/_form.html.haml
@@ -54,3 +54,7 @@
%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shop Preferences'" } }
%legend Shop Preferences
= render 'admin/enterprises/form/shop_preferences', f: f
+
+%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='Tag Rules'" } }
+ %legend Tag Rules
+ = render 'admin/enterprises/form/tag_rules', f: f
diff --git a/app/views/admin/enterprises/edit.html.haml b/app/views/admin/enterprises/edit.html.haml
index 19ae0f3b44..5d3a623f40 100644
--- a/app/views/admin/enterprises/edit.html.haml
+++ b/app/views/admin/enterprises/edit.html.haml
@@ -7,6 +7,7 @@
- content_for :page_actions do
%li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left'
+
= render 'admin/enterprises/form_data'
= render 'admin/enterprises/ng_form', action: 'edit'
diff --git a/app/views/admin/enterprises/form/_primary_details.html.haml b/app/views/admin/enterprises/form/_primary_details.html.haml
index 437a61e866..ccf59c8a12 100644
--- a/app/views/admin/enterprises/form/_primary_details.html.haml
+++ b/app/views/admin/enterprises/form/_primary_details.html.haml
@@ -22,7 +22,6 @@
%a What's this?
.five.columns.omega
= f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer'
-
= f.label :is_primary_producer, 'Producer'
- if spree_current_user.admin?
.row
@@ -33,15 +32,12 @@
%a What's this?
.two.columns
= f.radio_button :sells, "none", 'ng-model' => 'Enterprise.sells'
-
= f.label :sells, "None", value: "none"
.two.columns
= f.radio_button :sells, "own", 'ng-model' => 'Enterprise.sells'
-
= f.label :sells, "Own", value: "own"
.four.columns.omega
= f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells'
-
= f.label :sells, "Any", value: "any"
.row
.three.columns.alpha
@@ -50,12 +46,21 @@
%a What's this?
.two.columns
= f.radio_button :visible, true
-
= f.label :visible, "Visible", :value => "true"
.five.columns.omega
= f.radio_button :visible, false
-
= f.label :visible, "Not Visible", :value => "false"
+.row
+ .three.columns.alpha
+ %label= t '.shopfront_requires_login'
+ %div{'ofn-with-tip' => t('.shopfront_requires_login_tip')}
+ %a= t 'admin.whats_this'
+ .two.columns
+ = f.radio_button :require_login, false
+ = f.label :require_login, t('.shopfront_requires_login_false'), value: :false
+ .five.columns.omega
+ = f.radio_button :require_login, true
+ = f.label :require_login, t('.shopfront_requires_login_true'), value: :true
.permalink{ ng: { controller: "permalinkCtrl" } }
.row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } }
.three.columns.alpha
diff --git a/app/views/admin/enterprises/form/_shipping_methods.html.haml b/app/views/admin/enterprises/form/_shipping_methods.html.haml
index 445b026c38..5ccc22a720 100644
--- a/app/views/admin/enterprises/form/_shipping_methods.html.haml
+++ b/app/views/admin/enterprises/form/_shipping_methods.html.haml
@@ -7,7 +7,7 @@
%th
%tbody
- @shipping_methods.each do |shipping_method|
- %tr{ ng: { controller: 'shippingMethodCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } }
+ %tr{ ng: { controller: 'shippingMethodsCtrl', init: "findShippingMethodByID(#{shipping_method.id})" } }
%td= shipping_method.name
%td= f.check_box :shipping_method_ids, { :multiple => true, 'ng-model' => 'ShippingMethod.selected' }, shipping_method.id, nil
%td= link_to "Edit", edit_admin_shipping_method_path(shipping_method)
diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml
new file mode 100644
index 0000000000..1a50e4f353
--- /dev/null
+++ b/app/views/admin/enterprises/form/_tag_rules.html.haml
@@ -0,0 +1,33 @@
+.row{ ng: { controller: "TagRulesCtrl" } }
+ .eleven.columns.alpha.omega
+ .eleven.columns.alpha.omega
+ .no_tags{ ng: { show: "tagGroups.length == 0" } }
+ No tags apply to this enterprise yet
+ .customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true }
+ .header
+ %table
+ %colgroup
+ %col{width: '35%'}
+ %col{width: '65%'}
+ %tr
+ %td
+ %h5
+ For customers tagged:
+ %td
+ %tags-input{ ng: { model: 'tagGroup.tags'},
+ min: { tags: "1" },
+ on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } }
+
+ .no_rules{ ng: { show: "tagGroup.rules.length == 0" } }
+ No rules apply to this tag yet
+ %table
+ %tr.tag_rule{ id: "tr_{{rule.id}}", ng: { repeat: "rule in tagGroup.rules" } }
+ %td
+ %discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } }
+ %filter-shipping-methods{ bo: { if: "rule.type == 'TagRule::FilterShippingMethods'" } }
+ %td.actions
+ %a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" }
+ .add_rule.text-center
+ %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "new-tag-rule-dialog" => true }
+ .add_tage
+ %input.button.red.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } }
diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml
index 7330c26799..c9f3a27b32 100644
--- a/app/views/admin/variant_overrides/_filters.html.haml
+++ b/app/views/admin/variant_overrides/_filters.html.haml
@@ -1,4 +1,4 @@
-.filters.sixteen.columns.alpha
+.filters.sixteen.columns.alpha.omega
.filter.four.columns.alpha
%label{ :for => 'query', ng: {class: '{disabled: !hub_id}'} }=t('admin.quick_search')
%br
diff --git a/app/views/admin/variant_overrides/_show_more.html.haml b/app/views/admin/variant_overrides/_show_more.html.haml
index ad943c853e..8d60593ddc 100644
--- a/app/views/admin/variant_overrides/_show_more.html.haml
+++ b/app/views/admin/variant_overrides/_show_more.html.haml
@@ -1,4 +1,4 @@
-.sixteen.columns.alpha.omega.text-center{ ng: {show: 'productLimit < filteredProducts.length'}}
+.text-center
%input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } }
or
%input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } }
diff --git a/app/views/checkout/_authentication.html.haml b/app/views/checkout/_authentication.html.haml
index 2120ba6549..1c3b06e63b 100644
--- a/app/views/checkout/_authentication.html.haml
+++ b/app/views/checkout/_authentication.html.haml
@@ -1,11 +1,11 @@
%section{"ng-show" => "!enabled"}
.row
- .small-12.columns.text-center{"ng-controller" => "AuthenticationCtrl"}
+ .small-12.columns.text-center
%h3.pad-top
= t :checkout_headline
.row.pad-top
- .small-5.columns.text-center{"ng-controller" => "AuthenticationCtrl"}
- %button.primary.expand{"ng-click" => "open()"}
+ .small-5.columns.text-center
+ %button.primary.expand{"auth" => "login"}
= t :label_login
.small-2.columns.text-center
%p.pad-top= "-#{t :action_or}-"
diff --git a/app/views/enterprises/shop.html.haml b/app/views/enterprises/shop.html.haml
index f2063494b4..d635f68164 100644
--- a/app/views/enterprises/shop.html.haml
+++ b/app/views/enterprises/shop.html.haml
@@ -22,6 +22,7 @@
%select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
"ofn-change-order-cycle" => true,
+ "disabled" => require_customer?,
"ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}",
"popover-placement" => "left", "popover" => t(:enterprises_choose), "popover-trigger" => "openTrigger"}
@@ -31,7 +32,7 @@
= render partial: 'shop/messages'
- .row
- = render partial: "shop/products/form"
+ - unless require_customer?
+ .row= render partial: "shop/products/form"
= render partial: "shared/footer"
diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml
index fea1fc0a7d..e899270efc 100644
--- a/app/views/home/_hubs.html.haml
+++ b/app/views/home/_hubs.html.haml
@@ -27,3 +27,9 @@
.show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
%a{href: "", "ng-click" => "showDistanceMatches()"}
= t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
+ .more-controls
+ %a.button{href: "", ng: {click: "showClosedShops()", show: "filterExpression.active"}}
+ = t '.show_closed_shops'
+ %a.button{href: "", ng: {click: "hideClosedShops()", show: "!filterExpression.active"}}
+ = t '.hide_closed_shops'
+ %a.button{href: main_app.map_path}= t '.show_on_map'
diff --git a/app/views/home/_hubs_table.html.haml b/app/views/home/_hubs_table.html.haml
index 8842079f56..edf9eb5ec8 100644
--- a/app/views/home/_hubs_table.html.haml
+++ b/app/views/home/_hubs_table.html.haml
@@ -1,5 +1,5 @@
.active_table
- %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | visible | 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 | 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}}"}
diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml
index fd7f5faf4e..e3e1f44174 100644
--- a/app/views/shared/_footer.html.haml
+++ b/app/views/shared/_footer.html.haml
@@ -7,13 +7,7 @@
.row
.small-12.medium-8.medium-offset-2.columns.text-center
.alert-box
- %a.big-alert{href: "http://www.openfoodnetwork.org", target: "_blank"}
- %h6
- = t :alert_selling_on_ofn
-
- %strong
- = t :alert_start_here
- %i.ofn-i_054-point-right
+ = render 'shared/register_call'
.row
.small-12.medium-4.medium-offset-2.columns.text-center
%h6
diff --git a/app/views/shared/_register_call.html.haml b/app/views/shared/_register_call.html.haml
new file mode 100644
index 0000000000..8c2d95ed2e
--- /dev/null
+++ b/app/views/shared/_register_call.html.haml
@@ -0,0 +1,7 @@
+%a.alert-cta{href: registration_path, target: "_blank"}
+ %h6
+ = t '.selling_on_ofn'
+
+ %strong
+ = t '.register'
+ %i.ofn-i_054-point-right
diff --git a/app/views/shared/_signed_out.html.haml b/app/views/shared/_signed_out.html.haml
index 1a7cbcc920..1e6efdc32a 100644
--- a/app/views/shared/_signed_out.html.haml
+++ b/app/views/shared/_signed_out.html.haml
@@ -1,5 +1,5 @@
-%li#login-link{"ng-controller" => "AuthenticationCtrl"}
- %a{"ng-click" => "open()"}
+%li#login-link
+ %a{"auth" => "login"}
%i.ofn-i_017-locked
%span
= t 'label_login'
diff --git a/app/views/shared/menu/_alert.html.haml b/app/views/shared/menu/_alert.html.haml
index b6ee3acfb1..6512358cc3 100644
--- a/app/views/shared/menu/_alert.html.haml
+++ b/app/views/shared/menu/_alert.html.haml
@@ -1,10 +1,4 @@
.text-center.page-alert.fixed{ "ofn-page-alert" => true }
.alert-box
- %a.alert-cta{href: registration_path, target: "_blank"}
- %h6
- = t 'alert_selling_on_ofn'
-
- %strong
- = t 'alert_start_here'
- %i.ofn-i_054-point-right
+ = render 'shared/register_call'
%a.close{ ng: { click: "close()" } } ×
diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml
index 1e56aba468..ba35354bf3 100644
--- a/app/views/shop/_messages.html.haml
+++ b/app/views/shop/_messages.html.haml
@@ -1,5 +1,18 @@
-- if @order_cycles and @order_cycles.empty?
+- if require_customer?
+ .row.footer-pad
+ .small-12.columns
+ .shopfront_hidden_message
+ = t '.require_customer_login'
+ - if spree_current_user.nil?
+ = t '.require_login_html',
+ {login: ('' + t('.login') + '').html_safe,
+ register: ('' + t('.register') + '').html_safe}
+ - else
+ = t '.require_customer_html',
+ {contact: ('' + t('.contact') + '').html_safe,
+ enterprise: current_distributor.name}
+- elsif @order_cycles and @order_cycles.empty?
- if current_distributor.preferred_shopfront_closed_message.present?
.row
.small-12.columns
diff --git a/app/views/spree/admin/reports/customers.html.haml b/app/views/spree/admin/reports/customers.html.haml
index 70045662f9..3ab25e5c6c 100644
--- a/app/views/spree/admin/reports/customers.html.haml
+++ b/app/views/spree/admin/reports/customers.html.haml
@@ -33,7 +33,7 @@
%br
%table#listing_customers.index
%thead
- %tr{'data-hook' => "orders_header" }
+ %tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody
diff --git a/app/views/spree/admin/reports/order_cycle_management.html.haml b/app/views/spree/admin/reports/order_cycle_management.html.haml
index 0b32eabe78..808c1ead61 100644
--- a/app/views/spree/admin/reports/order_cycle_management.html.haml
+++ b/app/views/spree/admin/reports/order_cycle_management.html.haml
@@ -33,7 +33,7 @@
%br
%table#listing_ocm_orders.index
%thead
- %tr{'data-hook' => "orders_header" }
+ %tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody
diff --git a/app/views/spree/admin/reports/orders_and_distributors.html.haml b/app/views/spree/admin/reports/orders_and_distributors.html.haml
index 9d733cc995..aa710b1db6 100644
--- a/app/views/spree/admin/reports/orders_and_distributors.html.haml
+++ b/app/views/spree/admin/reports/orders_and_distributors.html.haml
@@ -10,7 +10,7 @@
%br
%table#listing_orders.index
%thead
- %tr{'data-hook' => t(:report_customers_header)}
+ %tr{'data-hook' => 'orders_header'}
- @report.header.each do |heading|
%th=heading
%tbody
@@ -21,4 +21,3 @@
- if @report.table.empty?
%tr
%td{:colspan => "2"}= t(:none)
-
diff --git a/app/views/spree/admin/shared/_trial_progress_bar.html.haml b/app/views/spree/admin/shared/_trial_progress_bar.html.haml
index 999dd9a13f..3deef097fe 100644
--- a/app/views/spree/admin/shared/_trial_progress_bar.html.haml
+++ b/app/views/spree/admin/shared/_trial_progress_bar.html.haml
@@ -1,7 +1,7 @@
- if enterprise
-if shop_trial_in_progress?(enterprise)
#trial_progress_bar
- = t(:shop_trial_in_progress)
+ = t(:shop_trial_in_progress, days: remaining_trial_days(enterprise))
-elsif shop_trial_expired?(enterprise)
#trial_progress_bar
- = t(:shop_trial_expired)
\ No newline at end of file
+ = t(:shop_trial_expired)
diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb
new file mode 100644
index 0000000000..08d8aa67fc
--- /dev/null
+++ b/config/initializers/acts_as_taggable_on.rb
@@ -0,0 +1 @@
+ActsAsTaggableOn.force_lowercase = true
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
index 3e793f88a4..131d718a87 100644
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -10,6 +10,12 @@ en-GB:
password:
confirmation: you have successfully registered
too_short: pick a longer name
+ # Overridden here due to a bug in spree i18n (Issue #870)
+ attributes:
+ spree/order:
+ payment_state: Payment State
+ shipment_state: Shipment State
+
devise:
failure:
invalid: |
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 020011ea8d..d9bd758c5a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -15,6 +15,12 @@
# See http://community.openfoodnetwork.org/t/localisation-ofn-in-your-language/397
en:
+ activerecord:
+ # Overridden here due to a bug in spree i18n (Issue #870)
+ attributes:
+ spree/order:
+ payment_state: Payment State
+ shipment_state: Shipment State
devise:
failure:
invalid: |
@@ -74,6 +80,10 @@ en:
whats_this: What's this?
+ customers:
+ index:
+ add_customer: "Add customer"
+ customer_placeholder: "customer@example.org"
inventory:
title: Inventory
description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
@@ -105,6 +115,32 @@ en:
enterprise:
select_outgoing_oc_products_from: Select outgoing OC products from
+ enterprises:
+ form:
+ primary_details:
+ shopfront_requires_login: "Shopfront requires login?"
+ shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront."
+ shopfront_requires_login_false: "Public"
+ shopfront_requires_login_true: "Require customers to login"
+
+ home:
+ hubs:
+ show_closed_shops: "Show closed shops"
+ hide_closed_shops: "Hide closed shops"
+ show_on_map: "Show all on the map"
+ shared:
+ register_call:
+ selling_on_ofn: "Interested in getting on the Open Food Network?"
+ register: "Register here"
+ shop:
+ messages:
+ login: "login"
+ register: "register"
+ contact: "contact"
+ require_customer_login: "This shop is for customers only."
+ require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer."
+ require_customer_html: "Please %{contact} %{enterprise} to become a customer."
+
# Printable Invoice Columns
invoice_column_item: "Item"
invoice_column_qty: "Qty"
@@ -145,8 +181,6 @@ en:
on_demand: On demand
none: None
- alert_selling_on_ofn: "Interested in selling food on the Open Food Network?"
- alert_start_here: "Start here"
label_shops: "Shops"
label_map: "Map"
label_producers: "Producers"
@@ -748,7 +782,7 @@ Please follow the instructions there to make your enterprise visible on the Open
shop_variant_quantity_max: "max"
contact: "Contact"
follow: "Follow"
- shop_for_products_html: "Shop for %{enterprise} products at:" #FIXME
+ shop_for_products_html: "Shop for %{enterprise} products at:"
change_shop: "Change shop to:"
shop_at: "Shop now at:"
price_breakdown: "Full price breakdown"
@@ -840,13 +874,12 @@ Please follow the instructions there to make your enterprise visible on the Open
go: "Go"
hub: "Hub"
accounts_administration_distributor: "accounts administration distributor"
- admin_accounts_and_billing: "admin.accounts_and_billing_settings" #FIXME
+ accounts_and_billing: "Accounts & Billing"
producer: "Producer"
product: "Product"
price: "Price"
on_hand: "On hand"
save_changes: "Save Changes"
- update_action: "update()" #FIXME
spree_admin_overview_enterprises_header: "My Enterprises"
spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES"
spree_admin_enterprises_hubs_name: "Name"
@@ -882,8 +915,8 @@ Please follow the instructions there to make your enterprise visible on the Open
live: "live"
manage: "Manage"
resend: "Resend"
- add_and_manage_products: "Add & manage products"
- add_and_manage_order_cycles: "Add & manage order cycles"
+ add_and_manage_products: "Add & manage products"
+ add_and_manage_order_cycles: "Add & manage order cycles"
manage_order_cycles: "Manage order cycles"
manage_products: "Manage products"
edit_profile_details: "Edit profile details"
@@ -916,14 +949,13 @@ Please follow the instructions there to make your enterprise visible on the Open
hub_sidebar_at_least: "At least one hub must be selected"
hub_sidebar_blue: "blue"
hub_sidebar_red: "red"
- shop_trial_in_progress: "Your shopfront trial expires in #{remaining_trial_days(enterprise)}." #FIXME
+ shop_trial_in_progress: "Your shopfront trial expires in %{days}."
shop_trial_expired: "Good news! We have decided to extend shopfront trials until further notice (probably around March 2015)." #FIXME
report_customers_distributor: "Distributor"
report_customers_supplier: "Supplier"
report_customers_cycle: "Order Cycle"
report_customers_type: "Report Type"
report_customers_csv: "Download as csv"
- report_customers_header: "orders header"
report_producers: "Producers: "
report_type: "Report Type: "
report_hubs: "Hubs: "
@@ -934,8 +966,6 @@ Please follow the instructions there to make your enterprise visible on the Open
report_payment_totals: 'Payment Totals'
report_all: 'all'
report_order_cycle: "Order Cycle: "
- report_product_header: "products_header"
- report_order_header: "orders_header"
report_entreprises: "Enterprises: "
report_users: "Users: "
initial_invoice_number: "Initial invoice number:"
@@ -944,6 +974,7 @@ Please follow the instructions there to make your enterprise visible on the Open
account_code: "Account code:"
equals: "Equals"
contains: "contains"
+ discount: "Discount"
filter_products: "Filter Products"
delete_product_variant: "The last variant cannot be deleted!"
progress: "progress"
@@ -972,6 +1003,7 @@ Please follow the instructions there to make your enterprise visible on the Open
payment_methods: "Payment Methods"
enterprise_fees: "Enterprise Fees"
inventory_settings: "Inventory Settings"
+ tag_rules: "Tag Rules"
shop_preferences: "Shop Preferences"
validation_msg_relationship_already_established: "^That relationship is already established."
validation_msg_at_least_one_hub: "^At least one hub must be selected"
diff --git a/config/routes.rb b/config/routes.rb
index c314747786..8d8d6d27c3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -87,6 +87,8 @@ Openfoodnetwork::Application.routes.draw do
resources :producer_properties do
post :update_positions, on: :collection
end
+
+ resources :tag_rules, only: [:destroy]
end
resources :enterprise_relationships
@@ -113,7 +115,7 @@ Openfoodnetwork::Application.routes.draw do
resources :inventory_items, only: [:create, :update]
- resources :customers, only: [:index, :update]
+ resources :customers, only: [:index, :create, :update, :destroy]
resource :content
@@ -141,6 +143,10 @@ Openfoodnetwork::Application.routes.draw do
get :managed, on: :collection
get :accessible, on: :collection
end
+
+ resource :status do
+ get :job_queue
+ end
end
namespace :open_food_network do
diff --git a/config/schedule.rb b/config/schedule.rb
index 023382330a..8cc8d7d225 100644
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -4,8 +4,11 @@ require 'whenever'
env "MAILTO", "rohan@rohanmitchell.com"
+
# If we use -e with a file containing specs, rspec interprets it and filters out our examples
job_type :run_file, "cd :path; :environment_variable=:environment bundle exec script/rails runner :task :output"
+job_type :enqueue_job, "cd :path; :environment_variable=:environment bundle exec script/enqueue :task :priority :output"
+
every 1.hour do
rake 'openfoodnetwork:cache:check_products_integrity'
@@ -23,6 +26,10 @@ every 4.hours do
rake 'db2fog:backup'
end
+every 5.minutes do
+ enqueue_job 'HeartbeatJob', priority: 0
+end
+
every 1.day, at: '1:00am' do
rake 'openfoodnetwork:billing:update_account_invoices'
end
diff --git a/db/migrate/20160303004210_create_tag_rules.rb b/db/migrate/20160303004210_create_tag_rules.rb
new file mode 100644
index 0000000000..157fee1e69
--- /dev/null
+++ b/db/migrate/20160303004210_create_tag_rules.rb
@@ -0,0 +1,10 @@
+class CreateTagRules < ActiveRecord::Migration
+ def change
+ create_table :tag_rules do |t|
+ t.references :enterprise, null: false, index: true
+ t.string :type, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20160316051131_add_require_login_to_enterprise.rb b/db/migrate/20160316051131_add_require_login_to_enterprise.rb
new file mode 100644
index 0000000000..68de642b62
--- /dev/null
+++ b/db/migrate/20160316051131_add_require_login_to_enterprise.rb
@@ -0,0 +1,5 @@
+class AddRequireLoginToEnterprise < ActiveRecord::Migration
+ def change
+ add_column :enterprises, :require_login, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20160401043927_change_value_type_of_paypal_passwords.rb b/db/migrate/20160401043927_change_value_type_of_paypal_passwords.rb
new file mode 100644
index 0000000000..e03ed25f9e
--- /dev/null
+++ b/db/migrate/20160401043927_change_value_type_of_paypal_passwords.rb
@@ -0,0 +1,15 @@
+class ChangeValueTypeOfPaypalPasswords < ActiveRecord::Migration
+ def up
+ Spree::Preference
+ .where("key like ?", "spree/gateway/pay_pal_express/password/%")
+ .where(value_type: "string")
+ .update_all(value_type: "password")
+ end
+
+ def down
+ Spree::Preference
+ .where("key like ?", "spree/gateway/pay_pal_express/password/%")
+ .where(value_type: "password")
+ .update_all(value_type: "string")
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cdab93834c..dc54bd8f3d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20160302044850) do
+ActiveRecord::Schema.define(:version => 20160401043927) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -348,6 +348,7 @@ ActiveRecord::Schema.define(:version => 20160302044850) do
t.string "permalink", :null => false
t.boolean "charges_sales_tax", :default => false, :null => false
t.string "email_address"
+ t.boolean "require_login", :default => false, :null => false
end
add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id"
@@ -1146,6 +1147,13 @@ ActiveRecord::Schema.define(:version => 20160302044850) do
t.integer "state_id"
end
+ create_table "tag_rules", :force => true do |t|
+ t.integer "enterprise_id", :null => false
+ t.string "type", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
create_table "taggings", :force => true do |t|
t.integer "tag_id"
t.integer "taggable_id"
diff --git a/lib/open_food_network/customers_report.rb b/lib/open_food_network/customers_report.rb
index 820bfff7df..d7900a16ac 100644
--- a/lib/open_food_network/customers_report.rb
+++ b/lib/open_food_network/customers_report.rb
@@ -31,7 +31,7 @@ module OpenFoodNetwork
ba.phone,
order.distributor.andand.name,
[da.andand.address1, da.andand.address2, da.andand.city].join(" "),
- order.shipping_method.andand.name
+ order.shipping_method.andand.name
]
end
end
@@ -78,4 +78,3 @@ module OpenFoodNetwork
end
end
end
-
diff --git a/lib/open_food_network/order_cycle_management_report.rb b/lib/open_food_network/order_cycle_management_report.rb
index ea238f7792..8a9c6ba60f 100644
--- a/lib/open_food_network/order_cycle_management_report.rb
+++ b/lib/open_food_network/order_cycle_management_report.rb
@@ -50,7 +50,7 @@ module OpenFoodNetwork
order.shipping_method.andand.name,
order.payments.first.andand.payment_method.andand.name,
order.payments.first.amount,
- OpenFoodNetwork::UserBalanceCalculator.new(order.user, order.distributor).balance
+ OpenFoodNetwork::UserBalanceCalculator.new(order.email, order.distributor).balance
]
end
@@ -67,23 +67,23 @@ module OpenFoodNetwork
order.shipping_method.andand.name,
order.payments.first.andand.payment_method.andand.name,
order.payments.first.amount,
- OpenFoodNetwork::UserBalanceCalculator.new(order.user, order.distributor).balance,
+ OpenFoodNetwork::UserBalanceCalculator.new(order.email, order.distributor).balance,
has_temperature_controlled_items?(order),
order.special_instructions
]
end
def filter_to_payment_method(orders)
- if params[:payment_method_name].present?
- orders.with_payment_method_name(params[:payment_method_name])
+ if params[:payment_method_in].present?
+ orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in]})
else
orders
end
end
def filter_to_shipping_method(orders)
- if params[:shipping_method_name].present?
- orders.joins(:shipping_method).where("spree_shipping_methods.name = ?", params[:shipping_method_name])
+ if params[:shipping_method_in].present?
+ orders.joins(:shipping_method).where(shipping_method_id: params[:shipping_method_in])
else
orders
end
diff --git a/lib/open_food_network/user_balance_calculator.rb b/lib/open_food_network/user_balance_calculator.rb
index 73926c1d03..32cd00c90c 100644
--- a/lib/open_food_network/user_balance_calculator.rb
+++ b/lib/open_food_network/user_balance_calculator.rb
@@ -1,32 +1,30 @@
module OpenFoodNetwork
class UserBalanceCalculator
- def initialize(user, distributor)
- @user = user
+ def initialize(email, distributor)
+ @email = email
@distributor = distributor
end
def balance
- payment_total - order_total
+ payment_total - completed_order_total
end
-
private
- def order_total
- orders.sum &:total
+ def completed_order_total
+ completed_orders.sum &:total
end
def payment_total
payments.sum &:amount
end
-
- def orders
- Spree::Order.where(distributor_id: @distributor, user_id: @user)
+ def completed_orders
+ Spree::Order.where(distributor_id: @distributor, email: @email).complete.not_state(:canceled)
end
def payments
- Spree::Payment.where(order_id: orders)
+ Spree::Payment.where(order_id: completed_orders, state: "completed")
end
end
end
diff --git a/script/enqueue b/script/enqueue
new file mode 100755
index 0000000000..2071414e4e
--- /dev/null
+++ b/script/enqueue
@@ -0,0 +1,61 @@
+#!/usr/bin/env ruby
+
+# Push a job onto the Delayed Job queue without booting the Rails stack
+# Perfect for calling via cron.
+#
+# Use like this:
+#
+# ./script/enqueue
+# ./script/enqueue Background::ImportJobs
+
+require 'erb'
+require 'yaml'
+
+ENV["RAILS_ENV"] ||= "development"
+
+DATABASE_CONFIG = File.expand_path("../../config/database.yml", __FILE__)
+
+def psql
+ raise "Missing database.yml" unless File.exists?(DATABASE_CONFIG)
+
+ file = File.read(DATABASE_CONFIG)
+ erb = ERB.new(file).result
+ env = ENV["RAILS_ENV"]
+ config = YAML.load(erb)[env]
+
+ raise "Missing config for #{env} environment" unless config
+
+ "psql".tap do |s|
+ s << " --host #{config['host']}" if config['host']
+ s << " --user #{config['username']}" if config['username']
+ s << " --port #{config['port']}" if config['port']
+ s << " #{config['database']}"
+ end
+end
+
+def enqueue_delayed_job(handler, priority=nil)
+ time = Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")
+ priority ||= 50
+
+ sql = <<-SQL
+ INSERT INTO delayed_jobs (
+ handler,
+ created_at,
+ updated_at,
+ run_at,
+ priority
+ ) VALUES (
+ '--- !ruby/object:#{handler} {}\n',
+ '#{time}',
+ '#{time}',
+ '#{time}',
+ #{priority}
+ );
+ SQL
+
+ IO.popen(psql, "w") do |io|
+ io.write sql
+ end
+end
+
+enqueue_delayed_job ARGV[0], ARGV[1]
diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb
index bb2e4888c2..f64a8057e8 100644
--- a/spec/controllers/admin/customers_controller_spec.rb
+++ b/spec/controllers/admin/customers_controller_spec.rb
@@ -94,4 +94,45 @@ describe Admin::CustomersController, type: :controller do
end
end
end
+
+ describe "create" do
+ let(:enterprise) { create(:distributor_enterprise) }
+ let(:another_enterprise) { create(:distributor_enterprise) }
+
+ def create_customer(enterprise)
+ spree_put :create, format: :json, customer: { email: 'new@example.com', enterprise_id: enterprise.id }
+ end
+
+ context "json" do
+ context "where I manage the customer's enterprise" do
+ before do
+ controller.stub spree_current_user: enterprise.owner
+ end
+
+ it "allows me to create the customer" do
+ expect { create_customer enterprise }.to change(Customer, :count).by(1)
+ end
+ end
+
+ context "where I don't manage the customer's enterprise" do
+ before do
+ controller.stub spree_current_user: another_enterprise.owner
+ end
+
+ it "prevents me from creating the customer" do
+ expect { create_customer enterprise }.to change(Customer, :count).by(0)
+ end
+ end
+
+ context "where I am the admin user" do
+ before do
+ controller.stub spree_current_user: create(:admin_user)
+ end
+
+ it "allows admins to create the customer" do
+ expect { create_customer enterprise }.to change(Customer, :count).by(1)
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb
index 8b972a3321..05498010bb 100644
--- a/spec/controllers/admin/enterprises_controller_spec.rb
+++ b/spec/controllers/admin/enterprises_controller_spec.rb
@@ -181,6 +181,58 @@ module Admin
end
end
end
+
+ describe "tag rules" do
+ let(:enterprise) { create(:distributor_enterprise) }
+ let!(:tag_rule) { create(:tag_rule, enterprise: enterprise) }
+
+ before do
+ login_as_enterprise_user [enterprise]
+ end
+
+ context "discount order rules" do
+ it "updates the existing rule with new attributes" do
+ spree_put :update, {
+ id: enterprise,
+ enterprise: {
+ tag_rules_attributes: {
+ '0' => {
+ id: tag_rule,
+ type: "TagRule::DiscountOrder",
+ preferred_customer_tags: "some,new,tags",
+ calculator_type: "Spree::Calculator::FlatPercentItemTotal",
+ calculator_attributes: { id: tag_rule.calculator.id, preferred_flat_percent: "15" }
+ }
+ }
+ }
+ }
+ tag_rule.reload
+ expect(tag_rule.preferred_customer_tags).to eq "some,new,tags"
+ expect(tag_rule.calculator.preferred_flat_percent).to eq 15
+ end
+
+ it "creates new rules with new attributes" do
+ spree_put :update, {
+ id: enterprise,
+ enterprise: {
+ tag_rules_attributes: {
+ '0' => {
+ id: "",
+ type: "TagRule::DiscountOrder",
+ preferred_customer_tags: "tags,are,awesome",
+ calculator_type: "Spree::Calculator::FlatPercentItemTotal",
+ calculator_attributes: { id: "", preferred_flat_percent: "24" }
+ }
+ }
+ }
+ }
+ expect(tag_rule.reload).to be
+ new_tag_rule = TagRule::DiscountOrder.last
+ expect(new_tag_rule.preferred_customer_tags).to eq "tags,are,awesome"
+ expect(new_tag_rule.calculator.preferred_flat_percent).to eq 24
+ end
+ end
+ end
end
context "as owner" do
diff --git a/spec/controllers/admin/tag_rules_controller_spec.rb b/spec/controllers/admin/tag_rules_controller_spec.rb
new file mode 100644
index 0000000000..fa95650479
--- /dev/null
+++ b/spec/controllers/admin/tag_rules_controller_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Admin::TagRulesController, type: :controller do
+
+ describe "destroy" do
+ context "json" do
+ let(:format) { :json }
+
+ let(:enterprise) { create(:distributor_enterprise) }
+ let!(:tag_rule) { create(:tag_rule, enterprise: enterprise) }
+ let(:params) { { format: format, id: tag_rule.id } }
+
+ context "where I don't manage the tag rule enterprise" do
+ let(:user) { create(:user) }
+
+ before do
+ user.owned_enterprises << create(:enterprise)
+ allow(controller).to receive(:spree_current_user) { user }
+ end
+
+ it "redirects to unauthorized" do
+ spree_delete :destroy, params
+ expect(response).to redirect_to spree.unauthorized_path
+ end
+ end
+
+ context "where I manage the tag rule enterprise" do
+ before do
+ allow(controller).to receive(:spree_current_user) { enterprise.owner }
+ end
+
+ it { expect{ spree_delete :destroy, params }.to change{TagRule.count}.by(-1) }
+ end
+ end
+ end
+end
diff --git a/spec/controllers/api/statuses_controller_spec.rb b/spec/controllers/api/statuses_controller_spec.rb
new file mode 100644
index 0000000000..f2427efb79
--- /dev/null
+++ b/spec/controllers/api/statuses_controller_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+module Api
+ describe StatusesController do
+ render_views
+
+ describe "job queue status" do
+ it "returns alive when up to date" do
+ Spree::Config.last_job_queue_heartbeat_at = Time.now
+ spree_get :job_queue
+ response.should be_success
+ response.body.should == {alive: true}.to_json
+ end
+
+ it "returns dead otherwise" do
+ Spree::Config.last_job_queue_heartbeat_at = 10.minutes.ago
+ spree_get :job_queue
+ response.should be_success
+ response.body.should == {alive: false}.to_json
+ end
+
+ it "returns dead when no heartbeat recorded" do
+ Spree::Config.last_job_queue_heartbeat_at = nil
+ spree_get :job_queue
+ response.should be_success
+ response.body.should == {alive: false}.to_json
+ end
+ end
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index 8b06b67cad..b4a3a1b573 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -285,6 +285,17 @@ FactoryGirl.define do
year { 2000 + rand(100) }
month { 1 + rand(12) }
end
+
+ factory :filter_shipping_methods_tag_rule, class: TagRule::FilterShippingMethods do
+ enterprise { FactoryGirl.create :distributor_enterprise }
+ end
+
+ factory :tag_rule, class: TagRule::DiscountOrder do
+ enterprise { FactoryGirl.create :distributor_enterprise }
+ before(:create) do |tr|
+ tr.calculator = Spree::Calculator::FlatPercentItemTotal.new(calculable: tr)
+ end
+ end
end
diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb
index 92ac89015e..3579421b84 100644
--- a/spec/features/admin/enterprises_spec.rb
+++ b/spec/features/admin/enterprises_spec.rb
@@ -82,6 +82,10 @@ feature %q{
page.should have_selector '.available'
choose 'Own'
+ # Require login to view shopfront
+ expect(page).to have_checked_field "enterprise_require_login_false"
+ choose "Require customers to login"
+
within (".side_menu") { click_link "Users" }
select2_search user.email, from: 'Owner'
@@ -162,6 +166,8 @@ feature %q{
page.should have_field 'enterprise_name', :with => 'Eaterprises'
@enterprise.reload
expect(@enterprise.owner).to eq user
+ expect(page).to have_checked_field "enterprise_visible_true"
+ expect(page).to have_checked_field "enterprise_require_login_true"
click_link "Business Details"
page.should have_checked_field "enterprise_charges_sales_tax_true"
@@ -276,7 +282,6 @@ feature %q{
end
end
-
context "as an Enterprise user", js: true do
let(:supplier1) { create(:supplier_enterprise, name: 'First Supplier') }
let(:supplier2) { create(:supplier_enterprise, name: 'Another Supplier') }
diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb
index caf52c08f8..abb5feaf3e 100644
--- a/spec/features/admin/payment_method_spec.rb
+++ b/spec/features/admin/payment_method_spec.rb
@@ -30,7 +30,7 @@ feature %q{
payment_method.distributors.should == [@distributors[0]]
end
- scenario "updating a payment method", retry: 3 do
+ scenario "updating a payment method" do
pm = create(:payment_method, distributors: [@distributors[0]])
login_to_admin_section
@@ -42,14 +42,33 @@ feature %q{
check "payment_method_distributor_ids_#{@distributors[1].id}"
check "payment_method_distributor_ids_#{@distributors[2].id}"
select2_select "PayPal Express", from: "payment_method_type"
+ expect(page).to have_field 'Login'
+ fill_in 'payment_method_preferred_login', with: 'testlogin'
+ fill_in 'payment_method_preferred_password', with: 'secret'
+ fill_in 'payment_method_preferred_signature', with: 'sig'
+
click_button 'Update'
- flash_message.should eq 'Payment Method has been successfully updated!'
+ expect(flash_message).to eq 'Payment Method has been successfully updated!'
payment_method = Spree::PaymentMethod.find_by_name('New PM Name')
expect(payment_method.distributors).to include @distributors[1], @distributors[2]
expect(payment_method.distributors).not_to include @distributors[0]
expect(payment_method.type).to eq "Spree::Gateway::PayPalExpress"
+ expect(payment_method.preferences[:login]).to eq 'testlogin'
+ expect(payment_method.preferences[:password]).to eq 'secret'
+ expect(payment_method.preferences[:signature]).to eq 'sig'
+
+ fill_in 'payment_method_preferred_login', with: 'otherlogin'
+ click_button 'Update'
+
+ expect(flash_message).to eq 'Payment Method has been successfully updated!'
+ expect(page).to have_field 'Password', with: ''
+
+ payment_method = Spree::PaymentMethod.find_by_name('New PM Name')
+ expect(payment_method.preferences[:login]).to eq 'otherlogin'
+ expect(payment_method.preferences[:password]).to eq 'secret'
+ expect(payment_method.preferences[:signature]).to eq 'sig'
end
end
diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb
index 9f04b6707b..646b383c21 100644
--- a/spec/features/admin/shipping_methods_spec.rb
+++ b/spec/features/admin/shipping_methods_spec.rb
@@ -93,12 +93,16 @@ feature 'shipping methods' do
fill_in 'shipping_method_name', :with => 'Teleport'
check "shipping_method_distributor_ids_#{distributor1.id}"
+ find(:css, "tags-input .tags input").set "local\n"
+
click_button 'Create'
flash_message.should == 'Shipping method "Teleport" has been successfully created!'
+ expect(first('tags-input .tag-list ti-tag-item')).to have_content "local"
shipping_method = Spree::ShippingMethod.find_by_name('Teleport')
shipping_method.distributors.should == [distributor1]
+ shipping_method.tag_list.should == ["local"]
end
it "shows me only shipping methods I have access to" do
diff --git a/spec/features/admin/tag_rules_spec.rb b/spec/features/admin/tag_rules_spec.rb
new file mode 100644
index 0000000000..e91215a21d
--- /dev/null
+++ b/spec/features/admin/tag_rules_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+feature 'Tag Rules', js: true do
+ include AuthenticationWorkflow
+ include WebHelper
+
+ let!(:enterprise) { create(:distributor_enterprise) }
+
+ context "creating" do
+ before do
+ login_to_admin_section
+ visit main_app.edit_admin_enterprise_path(enterprise)
+ end
+
+ it "allows creation of rules of each type" do
+ click_link "Tag Rules"
+
+ # Creating a new tag
+ expect(page).to_not have_selector '.customer_tag'
+ expect(page).to have_content 'No tags apply to this enterprise yet'
+ click_button '+ Add A New Tag'
+ find(:css, "tags-input .tags input").set "volunteer\n"
+
+ # New FilterShippingMethods Rule
+ click_button '+ Add A New Rule'
+ select2_select 'Show/Hide shipping methods', from: 'rule_type_selector'
+ click_button "Add Rule"
+ select2_select "NOT VISIBLE", from: "enterprise_tag_rules_attributes_0_preferred_matched_shipping_methods_visibility"
+
+ # New DiscountOrder Rule
+ # expect(page).to have_content 'No rules apply to this tag yet'
+ # click_button '+ Add A New Rule'
+ # select2_select 'Apply a discount to orders', from: 'rule_type_selector'
+ # click_button "Add Rule"
+ # fill_in "enterprise_tag_rules_attributes_1_calculator_attributes_preferred_flat_percent", with: 22
+
+ click_button 'Update'
+
+ # tag_rule = TagRule::DiscountOrder.last
+ # expect(tag_rule.preferred_customer_tags).to eq "volunteer"
+ # expect(tag_rule.calculator.preferred_flat_percent).to eq -22
+
+ tag_rule = TagRule::FilterShippingMethods.last
+ expect(tag_rule.preferred_customer_tags).to eq "volunteer"
+ expect(tag_rule.preferred_shipping_method_tags).to eq "volunteer"
+ expect(tag_rule.preferred_matched_shipping_methods_visibility).to eq "hidden"
+ end
+ end
+
+ context "updating" do
+ let!(:do_tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) }
+ let!(:fsm_tag_rule) { create(:filter_shipping_methods_tag_rule, enterprise: enterprise, preferred_matched_shipping_methods_visibility: "hidden", preferred_customer_tags: "member" ) }
+
+ before do
+ login_to_admin_section
+ visit main_app.edit_admin_enterprise_path(enterprise)
+ end
+
+ it "saves changes to rules of each type" do
+ click_link "Tag Rules"
+
+ # Tag group exists
+ expect(first('.customer_tag .header')).to have_content "For customers tagged:"
+ expect(first('tags-input .tag-list ti-tag-item')).to have_content "member"
+ find(:css, "tags-input .tags input").set "volunteer\n"
+
+ # DiscountOrder rule
+ expect(page).to have_field "enterprise_tag_rules_attributes_0_calculator_attributes_preferred_flat_percent", with: '0'
+ fill_in "enterprise_tag_rules_attributes_0_calculator_attributes_preferred_flat_percent", with: 45
+
+ # FilterShippingMethods rule
+ expect(page).to have_select2 "enterprise_tag_rules_attributes_1_preferred_matched_shipping_methods_visibility", selected: 'NOT VISIBLE'
+ select2_select 'VISIBLE', from: "enterprise_tag_rules_attributes_1_preferred_matched_shipping_methods_visibility"
+
+ click_button 'Update'
+
+ # DiscountOrder rule
+ expect(do_tag_rule.preferred_customer_tags).to eq "member,volunteer"
+ expect(do_tag_rule.calculator.preferred_flat_percent).to eq -45
+
+ # FilterShippingMethods rule
+ expect(fsm_tag_rule.preferred_customer_tags).to eq "member,volunteer"
+ expect(fsm_tag_rule.preferred_shipping_method_tags).to eq "member,volunteer"
+ expect(fsm_tag_rule.preferred_matched_shipping_methods_visibility).to eq "visible"
+ end
+ end
+
+ context "deleting" do
+ let!(:tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) }
+
+ before do
+ login_to_admin_section
+ visit main_app.edit_admin_enterprise_path(enterprise)
+ end
+
+ it "deletes rules from the database" do
+ click_link "Tag Rules"
+
+ expect(page).to have_selector "#tr_#{tag_rule.id}"
+
+ expect{
+ within "#tr_#{tag_rule.id}" do
+ first("a.delete-tag-rule").click
+ end
+ expect(page).to_not have_selector "#tr_#{tag_rule.id}"
+ }.to change{TagRule.count}.by(-1)
+ end
+ end
+end
diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb
index 1df700a1fc..74145bbe1a 100644
--- a/spec/features/consumer/shopping/checkout_spec.rb
+++ b/spec/features/consumer/shopping/checkout_spec.rb
@@ -28,6 +28,7 @@ feature "As a consumer I want to check out my cart", js: true do
describe "with shipping and payment methods" do
let(:sm1) { create(:shipping_method, require_ship_address: true, name: "Frogs", description: "yellow", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0.00)) }
let(:sm2) { create(:shipping_method, require_ship_address: false, name: "Donkeys", description: "blue", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 4.56)) }
+ let(:sm3) { create(:shipping_method, require_ship_address: false, name: "Local", tag_list: "local") }
let!(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::PaymentMethod::Check") }
let!(:pm2) { create(:payment_method, distributors: [distributor]) }
let!(:pm3) do
@@ -41,6 +42,7 @@ feature "As a consumer I want to check out my cart", js: true do
before do
distributor.shipping_methods << sm1
distributor.shipping_methods << sm2
+ distributor.shipping_methods << sm3
end
context "on the checkout page" do
@@ -68,10 +70,11 @@ feature "As a consumer I want to check out my cart", js: true do
page.should_not have_content product.tax_category.name
end
- it "shows all shipping methods, but doesn't show ship address when not needed" do
+ it "shows all shipping methods" do
toggle_shipping
page.should have_content "Frogs"
page.should have_content "Donkeys"
+ page.should have_content "Local"
end
context "when shipping method requires an address" do
@@ -84,6 +87,39 @@ feature "As a consumer I want to check out my cart", js: true do
find("#ship_address > div.visible").visible?.should be_true
end
end
+
+ context "using FilterShippingMethods" do
+ it "shows shipping methods allowed by the rule" do
+ # No rules in effect
+ toggle_shipping
+ page.should have_content "Frogs"
+ page.should have_content "Donkeys"
+ page.should have_content "Local"
+
+ create(:filter_shipping_methods_tag_rule,
+ enterprise: distributor,
+ preferred_customer_tags: "local",
+ preferred_shipping_method_tags: "local",
+ preferred_matched_shipping_methods_visibility: 'visible')
+ visit checkout_path
+ checkout_as_guest
+
+ # Rule in effect, disallows access to 'Local'
+ page.should have_content "Frogs"
+ page.should have_content "Donkeys"
+ page.should_not have_content "Local"
+
+ customer = create(:customer, enterprise: distributor, tag_list: "local")
+ order.update_attribute(:customer_id, customer.id)
+ visit checkout_path
+ checkout_as_guest
+
+ # #local Customer can access 'Local' shipping method
+ page.should have_content "Frogs"
+ page.should have_content "Donkeys"
+ page.should have_content "Local"
+ end
+ end
end
context "on the checkout page with payments open" do
diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb
index e81a792881..a8545af3db 100644
--- a/spec/features/consumer/shopping/shopping_spec.rb
+++ b/spec/features/consumer/shopping/shopping_spec.rb
@@ -253,5 +253,78 @@ feature "As a consumer I want to shop with a distributor", js: true do
page.should have_content "The next cycle opens in 10 days"
end
end
+
+ context "when shopping requires a customer" do
+ let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) }
+ let(:product) { create(:simple_product) }
+ let(:variant) { create(:variant, product: product) }
+
+ before do
+ add_product_and_variant_to_order_cycle(exchange, product, variant)
+ set_order_cycle(order, oc1)
+ distributor.require_login = true
+ distributor.save!
+ end
+
+ context "when not logged in" do
+ it "tells us to login" do
+ visit shop_path
+ expect(page).to have_content "This shop is for customers only."
+ expect(page).to have_content "Please login"
+ expect(page).to have_no_content product.name
+ end
+ end
+
+ context "when logged in" do
+ let(:address) { create(:address, firstname: "Foo", lastname: "Bar") }
+ let(:user) { create(:user, bill_address: address, ship_address: address) }
+
+ before do
+ quick_login_as user
+ end
+
+ context "as non-customer" do
+ it "tells us to contact enterprise" do
+ visit shop_path
+ expect(page).to have_content "This shop is for customers only."
+ expect(page).to have_content "Please contact #{distributor.name}"
+ expect(page).to have_no_content product.name
+ end
+ end
+
+ context "as customer" do
+ let!(:customer) { create(:customer, user: user, enterprise: distributor) }
+
+ it "shows just products" do
+ visit shop_path
+ expect(page).to have_no_content "This shop is for customers only."
+ expect(page).to have_content product.name
+ end
+ end
+
+ context "as a manager" do
+ let!(:role) { create(:enterprise_role, user: user, enterprise: distributor) }
+
+ it "shows just products" do
+ visit shop_path
+ expect(page).to have_no_content "This shop is for customers only."
+ expect(page).to have_content product.name
+ end
+ end
+
+ context "as the owner" do
+ before do
+ distributor.owner = user
+ distributor.save!
+ end
+
+ it "shows just products" do
+ visit shop_path
+ expect(page).to have_no_content "This shop is for customers only."
+ expect(page).to have_content product.name
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb
index faeff9c37d..4edaa702e0 100644
--- a/spec/features/consumer/shops_spec.rb
+++ b/spec/features/consumer/shops_spec.rb
@@ -26,9 +26,17 @@ feature 'Shops', js: true do
page.should_not have_content invisible_distributor.name
end
- it "should grey out hubs that are not in an order cycle" do
+ it "should not show hubs that are not in an order cycle" do
create(:simple_product, distributors: [d1, d2])
visit shops_path
+ page.should have_no_selector 'hub.inactive'
+ page.should have_no_selector 'hub', text: d2.name
+ end
+
+ it "should show closed shops after clicking the button" do
+ create(:simple_product, distributors: [d1, d2])
+ visit shops_path
+ click_link "Show closed shops"
page.should have_selector 'hub.inactive'
page.should have_selector 'hub.inactive', text: d2.name
end
diff --git a/spec/helpers/enterprises_helper_spec.rb b/spec/helpers/enterprises_helper_spec.rb
new file mode 100644
index 0000000000..1dc32d63b3
--- /dev/null
+++ b/spec/helpers/enterprises_helper_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe EnterprisesHelper do
+ describe "loading available shipping methods" do
+
+ context "when a FilterShippingMethods tag rule is in effect, with preferred visibility of 'visible'" do
+ let!(:distributor) { create(:distributor_enterprise) }
+ let!(:allowed_customer) { create(:customer, enterprise: distributor, tag_list: "local") }
+ let!(:disallowed_customer) { create(:customer, enterprise: distributor, tag_list: "") }
+ let!(:order) { create(:order, distributor: distributor) }
+ let!(:tag_rule) { create(:filter_shipping_methods_tag_rule,
+ enterprise: distributor,
+ preferred_customer_tags: "local",
+ preferred_shipping_method_tags: "local-delivery") }
+ let!(:tagged_sm) { create(:shipping_method, require_ship_address: false, name: "Untagged", tag_list: "local-delivery") }
+ let!(:untagged_sm) { create(:shipping_method, require_ship_address: false, name: "Tagged", tag_list: "") }
+
+ before do
+ distributor.shipping_methods = [tagged_sm, untagged_sm]
+ allow(helper).to receive(:current_order) { order }
+ end
+
+ context "with a preferred visiblity of 'visible" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible') }
+
+ context "when the customer is nil" do
+ it "applies default action (hide)" do
+ expect(helper.available_shipping_methods).to include untagged_sm
+ expect(helper.available_shipping_methods).to_not include tagged_sm
+ end
+ end
+
+ context "when the customer's tags match" do
+ before { order.update_attribute(:customer_id, allowed_customer.id) }
+
+ it "applies the action (show)" do
+ expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm
+ end
+ end
+
+ context "when the customer's tags don't match" do
+ before { order.update_attribute(:customer_id, disallowed_customer.id) }
+
+ it "applies the default action (hide)" do
+ expect(helper.available_shipping_methods).to include untagged_sm
+ expect(helper.available_shipping_methods).to_not include tagged_sm
+ end
+ end
+ end
+
+ context "with a preferred visiblity of 'hidden" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden') }
+
+ context "when the customer is nil" do
+ it "applies default action (show)" do
+ expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm
+ end
+ end
+
+ context "when the customer's tags match" do
+ before { order.update_attribute(:customer_id, allowed_customer.id) }
+
+ it "applies the action (hide)" do
+ expect(helper.available_shipping_methods).to include untagged_sm
+ expect(helper.available_shipping_methods).to_not include tagged_sm
+ end
+ end
+
+ context "when the customer's tags don't match" do
+ before { order.update_attribute(:customer_id, disallowed_customer.id) }
+
+ it "applies the default action (show)" do
+ expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb
index 0eecdeeaf9..3b62fb2660 100644
--- a/spec/helpers/injection_helper_spec.rb
+++ b/spec/helpers/injection_helper_spec.rb
@@ -27,7 +27,10 @@ describe InjectionHelper do
it "injects shipping_methods" do
sm = create(:shipping_method)
helper.stub(:current_order).and_return order = create(:order)
- helper.stub_chain(:current_distributor, :shipping_methods, :uniq).and_return [sm]
+ shipping_methods = double(:shipping_methods, uniq: [sm])
+ current_distributor = double(:distributor, shipping_methods: shipping_methods)
+ allow(helper).to receive(:current_distributor) { current_distributor }
+ allow(current_distributor).to receive(:apply_tag_rules_to).with(shipping_methods, {customer: nil} )
helper.inject_available_shipping_methods.should match sm.id.to_s
helper.inject_available_shipping_methods.should match sm.compute_amount(order).to_s
end
diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee
index 22777a6528..215f2834ed 100644
--- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee
+++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee
@@ -1,25 +1,49 @@
describe "CustomersCtrl", ->
- ctrl = null
scope = null
- Customers = null
+ http = null
beforeEach ->
- shops = "list of shops"
-
module('admin.customers')
- inject ($controller, $rootScope, _Customers_) ->
+ inject ($controller, $rootScope, _CustomerResource_, $httpBackend) ->
scope = $rootScope
- Customers = _Customers_
- ctrl = $controller 'customersCtrl', {$scope: scope, Customers: Customers, shops: shops}
+ http = $httpBackend
+ $controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: {}}
+ this.addMatchers
+ toAngularEqual: (expected) ->
+ return angular.equals(this.actual, expected)
+
+ it "has no shop pre-selected", ->
+ expect(scope.shop).toEqual {}
describe "setting the shop on scope", ->
+ customer = { id: 5, email: 'someone@email.com'}
+ customers = [customer]
+
beforeEach ->
- spyOn(Customers, "index").andReturn "list of customers"
+ http.expectGET('/admin/customers.json?enterprise_id=1').respond 200, customers
scope.$apply ->
scope.shop = {id: 1}
+ http.flush()
- it "calls Customers#index with the correct params", ->
- expect(Customers.index).toHaveBeenCalledWith({enterprise_id: 1})
+ it "retrievs the list of customers", ->
+ expect(scope.customers).toAngularEqual customers
- it "resets $scope.customers with the result of Customers#index", ->
- expect(scope.customers).toEqual "list of customers"
+ describe "scope.add", ->
+ it "creates a new customer", ->
+ email = "customer@example.org"
+ newCustomer = {id: 6, email: email}
+ customers.push(newCustomer)
+ http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=1').respond 200, newCustomer
+ scope.add(email)
+ http.flush()
+ expect(scope.customers).toAngularEqual customers
+
+ describe "scope.deleteCustomer", ->
+ it "deletes a customer", ->
+ expect(scope.customers.length).toBe 2
+ 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.toAngularEqual customer
diff --git a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee
deleted file mode 100644
index 7123055d63..0000000000
--- a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee
+++ /dev/null
@@ -1,31 +0,0 @@
-describe "Customers service", ->
- Customers = CustomerResource = customers = $httpBackend = null
-
- beforeEach ->
- module 'admin.customers'
-
- inject ($q, _$httpBackend_, _Customers_, _CustomerResource_) ->
- Customers = _Customers_
- CustomerResource = _CustomerResource_
- $httpBackend = _$httpBackend_
- $httpBackend.expectGET('/admin/customers.json?enterprise_id=2').respond 200, [{ id: 5, email: 'someone@email.com'}]
-
- describe "#index", ->
- result = null
-
- beforeEach ->
- expect(Customers.loaded).toBe false
- result = Customers.index(enterprise_id: 2)
- $httpBackend.flush()
-
- it "stores returned data in @customers, with ids as keys", ->
- # This is super weird and freaking annoying. I think resource results have extra
- # properties ($then, $promise) that cause them to not be equal to the reponse object
- # provided to the expectGET clause above.
- expect(Customers.customers).toEqual [ new CustomerResource({ id: 5, email: 'someone@email.com'}) ]
-
- it "returns @customers", ->
- expect(result).toEqual Customers.customers
-
- it "sets @loaded to true", ->
- expect(Customers.loaded).toBe true
diff --git a/spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee b/spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee
index 49ea827900..61d002f23a 100644
--- a/spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee
+++ b/spec/javascripts/unit/admin/inventory_items/services/inventory_items_spec.js.coffee
@@ -8,10 +8,6 @@ describe "InventoryItems service", ->
$provide.value 'inventoryItems', inventoryItems
null
- this.addMatchers
- toDeepEqual: (expected) ->
- return angular.equals(this.actual, expected)
-
inject ($q, _$httpBackend_, _InventoryItems_, _InventoryItemResource_) ->
InventoryItems = _InventoryItems_
InventoryItemResource = _InventoryItemResource_
diff --git a/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee
new file mode 100644
index 0000000000..1e132ec07d
--- /dev/null
+++ b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee
@@ -0,0 +1,79 @@
+describe "TagRulesCtrl", ->
+ ctrl = null
+ scope = null
+ enterprise = null
+
+ beforeEach ->
+ module('admin.tagRules')
+ enterprise =
+ id: 45
+ tag_groups: [
+ { tags: "member", rules: [{ id: 1, preferred_customer_tags: "member" }, { id: 2, preferred_customer_tags: "member" }] },
+ { tags: "volunteer", rules: [{ id: 3, preferred_customer_tags: "local" }] }
+ ]
+
+ inject ($rootScope, $controller) ->
+ scope = $rootScope
+ ctrl = $controller 'TagRulesCtrl', {$scope: scope, enterprise: enterprise}
+
+ describe "tagGroup start indices", ->
+ it "updates on initialization", ->
+ expect(scope.tagGroups[0].startIndex).toEqual 0
+ expect(scope.tagGroups[1].startIndex).toEqual 2
+
+ describe "adding a new tag group", ->
+ beforeEach ->
+ scope.addNewRuleTo(scope.tagGroups[0], "DiscountOrder")
+
+ it "adds a new rule of the specified type to the rules array for the tagGroup", ->
+ expect(scope.tagGroups[0].rules.length).toEqual 3
+ expect(scope.tagGroups[0].rules[2].type).toEqual "TagRule::DiscountOrder"
+
+ it "updates tagGroup start indices", ->
+ expect(scope.tagGroups[0].startIndex).toEqual 0
+ expect(scope.tagGroups[1].startIndex).toEqual 3
+
+ describe "deleting a tag group", ->
+ describe "where the rule is not in the rule list for the tagGroup", ->
+ beforeEach ->
+ scope.deleteTagRule(scope.tagGroups[0],scope.tagGroups[1].rules[0])
+
+ it "does not remove any rules", ->
+ expect(scope.tagGroups[0].rules.length).toEqual 2
+ expect(scope.tagGroups[1].rules.length).toEqual 1
+
+ describe "with an id", ->
+ rule = null
+
+ beforeEach inject ($httpBackend) ->
+ rule = scope.tagGroups[0].rules[0]
+ spyOn(window, "confirm").andReturn(true)
+ $httpBackend.expectDELETE('/admin/enterprises/45/tag_rules/1.json').respond(status: 204)
+ scope.deleteTagRule(scope.tagGroups[0], rule)
+ $httpBackend.flush()
+
+ it "removes the specified rule from the rules list", ->
+ expect(scope.tagGroups[0].rules.length).toEqual 1
+ expect(scope.tagGroups[1].rules.length).toEqual 1
+ expect(scope.tagGroups[0].rules.indexOf(rule)).toEqual -1
+
+ it "updates tagGroup start indices", ->
+ expect(scope.tagGroups[0].startIndex).toEqual 0
+ expect(scope.tagGroups[1].startIndex).toEqual 1
+
+ describe "without an id", ->
+ rule = null
+
+ beforeEach inject ($httpBackend) ->
+ rule = scope.tagGroups[0].rules[0]
+ rule.id = null
+ scope.deleteTagRule(scope.tagGroups[0], rule)
+
+ it "removes the specified rule from the rules list", ->
+ expect(scope.tagGroups[0].rules.length).toEqual 1
+ expect(scope.tagGroups[1].rules.length).toEqual 1
+ expect(scope.tagGroups[0].rules.indexOf(rule)).toEqual -1
+
+ it "updates tagGroup start indices", ->
+ expect(scope.tagGroups[0].startIndex).toEqual 0
+ expect(scope.tagGroups[1].startIndex).toEqual 1
diff --git a/spec/jobs/heartbeat_job_spec.rb b/spec/jobs/heartbeat_job_spec.rb
new file mode 100644
index 0000000000..3bafecd572
--- /dev/null
+++ b/spec/jobs/heartbeat_job_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe HeartbeatJob do
+ context "with time frozen" do
+ let(:run_time) { Time.zone.local(2016, 4, 13, 13, 0, 0) }
+
+ before { Spree::Config.last_job_queue_heartbeat_at = nil }
+
+ around do |example|
+ Timecop.freeze(run_time) { example.run }
+ end
+
+ it "updates the last_job_queue_heartbeat_at config var" do
+ run_job
+ Time.parse(Spree::Config.last_job_queue_heartbeat_at).should == run_time
+ end
+ end
+
+
+ private
+
+ def run_job
+ clear_jobs
+ Delayed::Job.enqueue HeartbeatJob.new
+ flush_jobs ignore_exceptions: false
+ end
+end
diff --git a/spec/lib/open_food_network/order_cycle_management_report_spec.rb b/spec/lib/open_food_network/order_cycle_management_report_spec.rb
index e0e7cb40bf..99ebf575af 100644
--- a/spec/lib/open_food_network/order_cycle_management_report_spec.rb
+++ b/spec/lib/open_food_network/order_cycle_management_report_spec.rb
@@ -86,19 +86,23 @@ module OpenFoodNetwork
it "filters to a payment method" do
pm2 = create(:payment_method, name: "PM2")
- order2 = create(:order)
- payment2 = create(:payment, order: order2, payment_method: pm2)
+ pm3 = create(:payment_method, name: "PM3")
+ order2 = create(:order, payments: [create(:payment, payment_method: pm2)])
+ order3 = create(:order, payments: [create(:payment, payment_method: pm3)])
+ # payment2 = create(:payment, order: order2, payment_method: pm2)
- subject.stub(:params).and_return(payment_method_name: pm1.name)
- subject.filter(orders).should == [order1]
+ subject.stub(:params).and_return(payment_method_in: [pm1.id, pm3.id] )
+ subject.filter(orders).should match_array [order1, order3]
end
it "filters to a shipping method" do
sm2 = create(:shipping_method, name: "ship2")
+ sm3 = create(:shipping_method, name: "ship3")
order2 = create(:order, shipping_method: sm2)
+ order3 = create(:order, shipping_method: sm3)
- subject.stub(:params).and_return(shipping_method_name: sm1.name)
- subject.filter(orders).should == [order1]
+ subject.stub(:params).and_return(shipping_method_in: [sm1.id, sm3.id])
+ expect(subject.filter(orders)).to match_array [order1, order3]
end
it "should do all the filters at once" do
diff --git a/spec/lib/open_food_network/user_balance_calculator_spec.rb b/spec/lib/open_food_network/user_balance_calculator_spec.rb
index 9588439f5e..7ff095d83a 100644
--- a/spec/lib/open_food_network/user_balance_calculator_spec.rb
+++ b/spec/lib/open_food_network/user_balance_calculator_spec.rb
@@ -9,34 +9,56 @@ module OpenFoodNetwork
let!(:user1) { create(:user) }
let!(:hub1) { create(:distributor_enterprise) }
- let!(:o1) { create(:order_with_totals_and_distribution, user: user1, distributor: hub1) } #total=10
- let!(:o2) { create(:order_with_totals_and_distribution, user: user1, distributor: hub1) } #total=10
- let!(:p1) { create(:payment, order: o1, amount: 15.00) }
- let!(:p2) { create(:payment, order: o2, amount: 10.00) }
+ let!(:o1) { create(:order_with_totals_and_distribution,
+ user: user1, distributor: hub1,
+ completed_at: 1.day.ago) } #total=10
+ let!(:o2) { create(:order_with_totals_and_distribution,
+ user: user1, distributor: hub1,
+ completed_at: 1.day.ago) } #total=10
+ let!(:p1) { create(:payment, order: o1, amount: 15.00,
+ state: "completed") }
+ let!(:p2) { create(:payment, order: o2, amount: 2.00,
+ state: "completed") }
- it "finds the user balance for this enterprise" do
- UserBalanceCalculator.new(user1, hub1).balance.should == 5
+ it "finds the correct balance for this email and enterprise" do
+ UserBalanceCalculator.new(o1.email, hub1).balance.should == -3
end
context "with another hub" do
let!(:hub2) { create(:distributor_enterprise) }
let!(:o3) { create(:order_with_totals_and_distribution,
- user: user1, distributor: hub2) } #total=10
- let!(:p3) { create(:payment, order: o3, amount: 10.00) }
+ user: user1, distributor: hub2,
+ completed_at: 1.day.ago) } #total=10
+ let!(:p3) { create(:payment, order: o3, amount: 15.00,
+ state: "completed") }
it "does not find the balance for other enterprises" do
- UserBalanceCalculator.new(user1, hub2).balance.should == 0
+ UserBalanceCalculator.new(o3.email, hub2).balance.should == 5
end
end
context "with another user" do
let!(:user2) { create(:user) }
let!(:o4) { create(:order_with_totals_and_distribution,
- user: user2, distributor: hub1) } #total=10
- let!(:p3) { create(:payment, order: o4, amount: 20.00) }
+ user: user2, distributor: hub1,
+ completed_at: 1.day.ago) } #total=10
+ let!(:p3) { create(:payment, order: o4, amount: 20.00,
+ state: "completed") }
it "does not find the balance for other users" do
- UserBalanceCalculator.new(user2, hub1).balance.should == 10
+ UserBalanceCalculator.new(o4.email, hub1).balance.should == 10
+ end
+ end
+
+ context "with canceled orders" do
+ let!(:o4) { create(:order_with_totals_and_distribution,
+ user: user1, distributor: hub1,
+ completed_at: 1.day.ago, state: "canceled") } #total=10
+ let!(:p4) { create(:payment, order: o4, amount: 20.00,
+ state: "completed") }
+
+ it "does not include canceled orders in the balance" do
+ UserBalanceCalculator.new(o4.email, hub1).balance.should == -3
end
end
end
diff --git a/spec/models/content_configuration_spec.rb b/spec/models/content_configuration_spec.rb
new file mode 100644
index 0000000000..6b7bc8a4e0
--- /dev/null
+++ b/spec/models/content_configuration_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe ContentConfiguration do
+ describe "default logos and home_hero" do
+ it "sets a default url with existing image" do
+ expect(image_exist?(ContentConfig.logo.options[:default_url])).to be_true
+ expect(image_exist?(ContentConfig.logo_mobile_svg.options[:default_url])).to be_true
+ expect(image_exist?(ContentConfig.home_hero.options[:default_url])).to be_true
+ expect(image_exist?(ContentConfig.footer_logo.options[:default_url])).to be_true
+ end
+
+ def image_exist?(default_url)
+ image_path = default_url.gsub(/\/assets\//,'/assets/images/')
+ File.exist?(File.join(Rails.root, 'app', image_path))
+ end
+ end
+end
diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb
index 0e43ce9df5..f2ac14e9b2 100644
--- a/spec/models/customer_spec.rb
+++ b/spec/models/customer_spec.rb
@@ -6,15 +6,25 @@ describe Customer, type: :model do
let!(:user2) { create(:user) }
let!(:enterprise) { create(:distributor_enterprise) }
+ it "associates no user using non-existing email" do
+ c = Customer.create(enterprise: enterprise, email: 'some-email-not-associated-with-a-user@email.com')
+ expect(c.user).to be_nil
+ end
+
it "associates an existing user using email" do
- c1 = Customer.create(enterprise: enterprise, email: 'some-email-not-associated-with-a-user@email.com')
- expect(c1.user).to be_nil
+ non_existing_email = 'some-email-not-associated-with-a-user@email.com'
+ c1 = Customer.create(enterprise: enterprise, email: non_existing_email, user: user1)
+ expect(c1.user).to eq user1
+ expect(c1.email).to eq non_existing_email
+ expect(c1.email).to_not eq user1.email
- c2 = Customer.create(enterprise: enterprise, email: 'some-email-not-associated-with-a-user@email.com', user: user1)
- expect(c2.user).to eq user1
+ c2 = Customer.create(enterprise: enterprise, email: user2.email)
+ expect(c2.user).to eq user2
+ end
- c3 = Customer.create(enterprise: enterprise, email: user2.email)
- expect(c3.user).to eq user2
+ it "associates an existing user using email case-insensitive" do
+ c = Customer.create(enterprise: enterprise, email: user2.email.upcase)
+ expect(c.user).to eq user2
end
end
end
diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb
index 4d681e01cc..ab86be1580 100644
--- a/spec/models/spree/order_spec.rb
+++ b/spec/models/spree/order_spec.rb
@@ -108,44 +108,76 @@ describe Spree::Order do
subject.update_distribution_charge!
end
- describe "looking up whether a line item can be provided by an order cycle" do
- it "returns true when the variant is provided" do
- v = double(:variant)
- line_item = double(:line_item, variant: v)
- order_cycle = double(:order_cycle, variants: [v])
- subject.stub(:order_cycle) { order_cycle }
+ context "appying tag rules" do
+ let(:enterprise) { create(:distributor_enterprise) }
+ let(:customer) { create(:customer, enterprise: enterprise, tag_list: "tagtagtag") }
+ let(:tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "tagtagtag") }
+ let(:order) { create(:order_with_totals_and_distribution, distributor: enterprise, customer: customer) }
- subject.send(:provided_by_order_cycle?, line_item).should be_true
+ before do
+ tag_rule.calculator.update_attribute(:preferred_flat_percent, -10)
end
- it "returns false otherwise" do
- v = double(:variant)
- line_item = double(:line_item, variant: v)
- order_cycle = double(:order_cycle, variants: [])
- subject.stub(:order_cycle) { order_cycle }
-
- subject.send(:provided_by_order_cycle?, line_item).should be_false
+ context "when the rule applies" do
+ it "applies the rule" do
+ order.update_distribution_charge!
+ order.reload
+ discount = order.adjustments.find_by_label("Discount")
+ expect(discount).to be_a Spree::Adjustment
+ expect(discount.amount).to eq (order.item_total / -10).round(2)
+ end
end
- it "returns false when there is no order cycle" do
- v = double(:variant)
- line_item = double(:line_item, variant: v)
- subject.stub(:order_cycle) { nil }
+ context "when the rule does not apply" do
+ before { tag_rule.update_attribute(:preferred_customer_tags, "tagtag") }
- subject.send(:provided_by_order_cycle?, line_item).should be_false
+ it "does not apply the rule" do
+ order.update_distribution_charge!
+ order.reload
+ discount = order.adjustments.find_by_label("Discount")
+ expect(discount).to be_nil
+ end
end
end
+ end
- it "looks up product distribution enterprise fees for a line item" do
- product = double(:product)
- variant = double(:variant, product: product)
- line_item = double(:line_item, variant: variant)
+ describe "looking up whether a line item can be provided by an order cycle" do
+ it "returns true when the variant is provided" do
+ v = double(:variant)
+ line_item = double(:line_item, variant: v)
+ order_cycle = double(:order_cycle, variants: [v])
+ subject.stub(:order_cycle) { order_cycle }
- product_distribution = double(:product_distribution)
- product.should_receive(:product_distribution_for).with(subject.distributor) { product_distribution }
-
- subject.send(:product_distribution_for, line_item).should == product_distribution
+ subject.send(:provided_by_order_cycle?, line_item).should be_true
end
+
+ it "returns false otherwise" do
+ v = double(:variant)
+ line_item = double(:line_item, variant: v)
+ order_cycle = double(:order_cycle, variants: [])
+ subject.stub(:order_cycle) { order_cycle }
+
+ subject.send(:provided_by_order_cycle?, line_item).should be_false
+ end
+
+ it "returns false when there is no order cycle" do
+ v = double(:variant)
+ line_item = double(:line_item, variant: v)
+ subject.stub(:order_cycle) { nil }
+
+ subject.send(:provided_by_order_cycle?, line_item).should be_false
+ end
+ end
+
+ it "looks up product distribution enterprise fees for a line item" do
+ product = double(:product)
+ variant = double(:variant, product: product)
+ line_item = double(:line_item, variant: variant)
+
+ product_distribution = double(:product_distribution)
+ product.should_receive(:product_distribution_for).with(subject.distributor) { product_distribution }
+
+ subject.send(:product_distribution_for, line_item).should == product_distribution
end
describe "getting the admin and handling charge" do
@@ -457,33 +489,6 @@ describe Spree::Order do
Spree::Order.not_state(:canceled).should_not include o
end
end
-
- describe "with payment method names" do
- let!(:o1) { create(:order) }
- let!(:o2) { create(:order) }
- let!(:pm1) { create(:payment_method, name: 'foo') }
- let!(:pm2) { create(:payment_method, name: 'bar') }
- let!(:p1) { create(:payment, order: o1, payment_method: pm1) }
- let!(:p2) { create(:payment, order: o2, payment_method: pm2) }
-
- it "returns the order with payment method name when one specified" do
- Spree::Order.with_payment_method_name('foo').should == [o1]
- end
-
- it "returns the orders with payment method name when many specified" do
- Spree::Order.with_payment_method_name(['foo', 'bar']).should include o1, o2
- end
-
- it "doesn't return rows with a different payment method name" do
- Spree::Order.with_payment_method_name('foobar').should_not include o1
- Spree::Order.with_payment_method_name('foobar').should_not include o2
- end
-
- it "doesn't return duplicate rows" do
- p2 = FactoryGirl.create(:payment, order: o1, payment_method: pm1)
- Spree::Order.with_payment_method_name('foo').length.should == 1
- end
- end
end
describe "shipping address prepopulation" do
@@ -559,39 +564,76 @@ describe Spree::Order do
end
describe "associating a customer" do
- let(:user) { create(:user) }
let(:distributor) { create(:distributor_enterprise) }
+ let!(:order) { create(:order, distributor: distributor) }
- context "when a user has been set on the order" do
- let!(:order) { create(:order, distributor: distributor, user: user) }
- context "and a customer for order.distributor and order.user.email already exists" do
- let!(:customer) { create(:customer, enterprise: distributor, email: user.email) }
- it "associates the order with the existing customer" do
- order.send(:associate_customer)
+ context "when an email address is available for the order" do
+ before { allow(order).to receive(:email_for_customer) { "existing@email.com" }}
+
+ context "and a customer for order.distributor and order#email_for_customer already exists" do
+ let!(:customer) { create(:customer, enterprise: distributor, email: "existing@email.com" ) }
+
+ it "associates the order with the existing customer, and returns the customer" do
+ result = order.send(:associate_customer)
expect(order.customer).to eq customer
+ expect(result).to eq customer
end
end
+
context "and a customer for order.distributor and order.user.email does not alread exist" do
let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') }
- it "creates a new customer" do
- expect{order.send(:associate_customer)}.to change{Customer.count}.by 1
+
+ it "does not set the customer and returns nil" do
+ result = order.send(:associate_customer)
+ expect(order.customer).to be_nil
+ expect(result).to be_nil
end
end
end
- context "when a user has not been set on the order" do
- let!(:order) { create(:order, distributor: distributor, user: nil) }
- context "and a customer for order.distributor and order.email already exists" do
- let!(:customer) { create(:customer, enterprise: distributor, email: order.email) }
- it "creates a new customer" do
- order.send(:associate_customer)
+ context "when an email address is not available for the order" do
+ let!(:customer) { create(:customer, enterprise: distributor) }
+ before { allow(order).to receive(:email_for_customer) { nil }}
+
+ it "does not set the customer and returns nil" do
+ result = order.send(:associate_customer)
+ expect(order.customer).to be_nil
+ expect(result).to be_nil
+ end
+ end
+ end
+
+ describe "ensuring a customer is linked" do
+ let(:distributor) { create(:distributor_enterprise) }
+ let!(:order) { create(:order, distributor: distributor) }
+
+ context "when a customer has already been linked to the order" do
+ let!(:customer) { create(:customer, enterprise: distributor, email: "existing@email.com" ) }
+ before { order.update_attribute(:customer_id, customer.id) }
+
+ it "does nothing" do
+ order.send(:ensure_customer)
+ expect(order.customer).to eq customer
+ end
+ end
+
+ context "when a customer not been linked to the order" do
+ context "but one matching order#email_for_customer already exists" do
+ let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') }
+ before { allow(order).to receive(:email_for_customer) { 'some-other-email@email.com' } }
+
+ it "links the customer customer to the order" do
+ expect(order.customer).to be_nil
+ expect{order.send(:ensure_customer)}.to_not change{Customer.count}
expect(order.customer).to eq customer
end
end
- context "and a customer for order.distributor and order.email does not alread exist" do
- let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') }
+
+ context "and order#email_for_customer does not match any existing customers" do
it "creates a new customer" do
- expect{order.send(:associate_customer)}.to change{Customer.count}.by 1
+ expect(order.customer).to be_nil
+ expect{order.send(:ensure_customer)}.to change{Customer.count}.by 1
+ expect(order.customer).to be_a Customer
end
end
end
diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb
index 9911c16a51..3e572ecfb1 100644
--- a/spec/models/spree/user_spec.rb
+++ b/spec/models/spree/user_spec.rb
@@ -17,11 +17,11 @@ describe Spree.user_class do
it "enforces the limit on the number of enterprise owned" do
expect(u2.owned_enterprises(:reload)).to eq []
u2.owned_enterprises << e1
- expect(u2.save!).to_not raise_error
- expect {
+ expect { u2.save! }.to_not raise_error
+ expect do
u2.owned_enterprises << e2
u2.save!
- }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: #{u2.email} is not permitted to own any more enterprises (limit is 1)."
+ end.to raise_error ActiveRecord::RecordInvalid, "Validation failed: #{u2.email} is not permitted to own any more enterprises (limit is 1)."
end
end
@@ -53,6 +53,23 @@ describe Spree.user_class do
create(:user)
end.to enqueue_job ConfirmSignupJob
end
+
+ it "should not create a customer" do
+ expect do
+ create(:user)
+ end.to change(Customer, :count).by(0)
+ end
+
+ describe "when a customer record exists" do
+ let!(:customer) { create(:customer, user: nil) }
+
+ it "should not create a customer" do
+ expect(customer.user).to be nil
+ user = create(:user, email: customer.email)
+ customer.reload
+ expect(customer.user).to eq user
+ end
+ end
end
describe "known_users" do
@@ -65,9 +82,9 @@ describe Spree.user_class do
it "returns a list of users which manage shared enterprises" do
expect(u1.known_users).to include u1, u2
expect(u1.known_users).to_not include u3
- expect(u2.known_users).to include u1,u2
+ expect(u2.known_users).to include u1, u2
expect(u2.known_users).to_not include u3
- expect(u3.known_users).to_not include u1,u2,u3
+ expect(u3.known_users).to_not include u1, u2, u3
end
end
@@ -85,14 +102,14 @@ describe Spree.user_class do
let!(:u2) { create(:user) }
let!(:distributor1) { create(:distributor_enterprise) }
let!(:distributor2) { create(:distributor_enterprise) }
- let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id)}
- let!(:d1o2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id)}
- let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id)}
- let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id)}
- let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id)}
+ let!(:d1o1) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
+ let!(:d1o2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u1.id) }
+ let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id) }
+ let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id) }
+ let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id) }
- let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed')}
- let!(:payment) { create(:payment, order: d1o2, state: 'checkout')}
+ let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed') }
+ let!(:payment) { create(:payment, order: d1o2, state: 'checkout') }
it "returns enterprises that the user has ordered from" do
expect(u1.enterprises_ordered_from).to eq [distributor1.id]
diff --git a/spec/models/tag_rule/discount_order_spec.rb b/spec/models/tag_rule/discount_order_spec.rb
new file mode 100644
index 0000000000..dab901dfbf
--- /dev/null
+++ b/spec/models/tag_rule/discount_order_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe TagRule::DiscountOrder, type: :model do
+ let!(:tag_rule) { create(:tag_rule) }
+
+ describe "determining relevance based on additional requirements" do
+ let(:subject) { double(:subject) }
+
+ before do
+ tag_rule.set_context(subject,{})
+ allow(tag_rule).to receive(:customer_tags_match?) { true }
+ allow(subject).to receive(:class) { Spree::Order }
+ end
+
+ context "when already_applied? returns false" do
+ before { expect(tag_rule).to receive(:already_applied?) { false } }
+
+ it "returns true" do
+ expect(tag_rule.send(:relevant?)).to be true
+ end
+ end
+
+ context "when already_applied? returns true" do
+ before { expect(tag_rule).to receive(:already_applied?) { true } }
+
+ it "returns false immediately" do
+ expect(tag_rule.send(:relevant?)).to be false
+ end
+ end
+ end
+
+ describe "determining whether a the rule has already been applied to an order" do
+ let!(:order) { create(:order) }
+ let!(:adjustment) { order.adjustments.create({:amount => 12.34, :source => order, :originator => tag_rule, :label => 'discount' }, :without_protection => true) }
+
+ before do
+ tag_rule.set_context(order, nil)
+ end
+
+ context "where adjustments originating from the rule already exist" do
+ it { expect(tag_rule.send(:already_applied?)).to be true}
+ end
+
+ context "where existing adjustments originate from other rules" do
+ before { adjustment.update_attribute(:originator_id,create(:tag_rule).id) }
+ it { expect(tag_rule.send(:already_applied?)).to be false}
+ end
+ end
+
+ describe "applying the rule" do
+ # Assume that all validation is done by the TagRule base class
+
+ let!(:line_item) { create(:line_item, price: 100.00) }
+ let!(:order) { line_item.order }
+
+ before do
+ order.update_distribution_charge!
+ tag_rule.calculator.update_attribute(:preferred_flat_percent, -10.00)
+ tag_rule.set_context(order, nil)
+ end
+
+ context "in a simple scenario" do
+ let(:adjustment) { order.reload.adjustments.where(originator_id: tag_rule, originator_type: "TagRule").first }
+
+ it "creates a new adjustment on the order" do
+ tag_rule.send(:apply!)
+ expect(adjustment).to be_a Spree::Adjustment
+ expect(adjustment.amount).to eq -10.00
+ expect(adjustment.label).to eq "Discount"
+ expect(order.adjustment_total).to eq -10.00
+ expect(order.total).to eq 90.00
+ end
+ end
+
+ context "when shipping charges apply" do
+ let!(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new( preferred_amount: 25.00 ) ) }
+ before do
+ shipping_method.create_adjustment("Shipping", order, order, true)
+ end
+
+ let(:adjustment) { order.reload.adjustments.where(originator_id: tag_rule, originator_type: "TagRule").first }
+
+ it "the adjustment is made on line item total, ie. ignores the shipping amount" do
+ tag_rule.send(:apply!)
+ expect(adjustment).to be_a Spree::Adjustment
+ expect(adjustment.amount).to eq -10.00
+ expect(adjustment.label).to eq "Discount"
+ expect(order.adjustment_total).to eq 15.00
+ expect(order.total).to eq 115.00
+ end
+ end
+ end
+end
diff --git a/spec/models/tag_rule/filter_shipping_methods_spec.rb b/spec/models/tag_rule/filter_shipping_methods_spec.rb
new file mode 100644
index 0000000000..539aa3c6ca
--- /dev/null
+++ b/spec/models/tag_rule/filter_shipping_methods_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe TagRule::DiscountOrder, type: :model do
+ let!(:tag_rule) { create(:filter_shipping_methods_tag_rule) }
+
+ describe "determining whether tags match for a given shipping method" do
+ context "when the shipping method is nil" do
+
+ it "returns false" do
+ expect(tag_rule.send(:tags_match?, nil)).to be false
+ end
+ end
+
+ context "when the shipping method is not nil" do
+ let(:shipping_method) { create(:shipping_method, tag_list: ["member","local","volunteer"]) }
+
+ context "when the rule has no preferred shipping method tags specified" do
+ before { allow(tag_rule).to receive(:preferred_shipping_method_tags) { "" } }
+ it { expect(tag_rule.send(:tags_match?, shipping_method)).to be false }
+ end
+
+ context "when the rule has preferred customer tags specified that match ANY of the customer tags" do
+ before { allow(tag_rule).to receive(:preferred_shipping_method_tags) { "wholesale,some_tag,member" } }
+ it { expect(tag_rule.send(:tags_match?, shipping_method)).to be true }
+ end
+
+ context "when the rule has preferred customer tags specified that match NONE of the customer tags" do
+ before { allow(tag_rule).to receive(:preferred_shipping_method_tags) { "wholesale,some_tag,some_other_tag" } }
+ it { expect(tag_rule.send(:tags_match?, shipping_method)).to be false }
+ end
+ end
+ end
+
+ describe "applying the rule" do
+ # Assume that all validation is done by the TagRule base class
+
+ let(:sm1) { create(:shipping_method, tag_list: ["tag1", "something", "somethingelse"]) }
+ let(:sm2) { create(:shipping_method, tag_list: ["tag2"]) }
+ let(:sm3) { create(:shipping_method, tag_list: ["tag3"]) }
+ let!(:shipping_methods) { [sm1, sm2, sm3] }
+
+ before do
+ tag_rule.update_attribute(:preferred_shipping_method_tags, "tag2")
+ tag_rule.set_context(shipping_methods, nil)
+ end
+
+ context "apply!" do
+ context "when showing matching shipping methods" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, "visible") }
+ it "does nothing" do
+ tag_rule.send(:apply!)
+ expect(shipping_methods).to eq [sm1, sm2, sm3]
+ end
+ end
+
+ context "when hiding matching shipping methods" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, "hidden") }
+ it "removes matching shipping methods from the list" do
+ tag_rule.send(:apply!)
+ expect(shipping_methods).to eq [sm1, sm3]
+ end
+ end
+ end
+
+ context "apply_default!" do
+ context "when showing matching shipping methods" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, "visible") }
+ it "remove matching shipping methods from the list" do
+ tag_rule.send(:apply_default!)
+ expect(shipping_methods).to eq [sm1, sm3]
+ end
+ end
+
+ context "when hiding matching shipping methods" do
+ before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, "hidden") }
+ it "does nothing" do
+ tag_rule.send(:apply_default!)
+ expect(shipping_methods).to eq [sm1, sm2, sm3]
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/tag_rule_spec.rb b/spec/models/tag_rule_spec.rb
new file mode 100644
index 0000000000..549c2f88aa
--- /dev/null
+++ b/spec/models/tag_rule_spec.rb
@@ -0,0 +1,201 @@
+require 'spec_helper'
+
+describe TagRule, type: :model do
+ let!(:tag_rule) { create(:tag_rule) }
+
+ describe "validations" do
+ it "requires a enterprise" do
+ expect(tag_rule).to validate_presence_of :enterprise
+ end
+ end
+
+ describe 'setting the context' do
+ let(:subject) { double(:subject) }
+ let(:context) { double(:context) }
+ it "stores the subject and context provided as instance variables on the model" do
+ tag_rule.set_context(subject, context)
+ expect(tag_rule.subject).to eq subject
+ expect(tag_rule.context).to eq context
+ expect(tag_rule.instance_variable_get(:@subject)).to eq subject
+ expect(tag_rule.instance_variable_get(:@context)).to eq context
+ end
+ end
+
+ describe "determining relevance based on subject and context" do
+ context "when the subject is nil" do
+ it "returns false" do
+ expect(tag_rule.send(:relevant?)).to be false
+ end
+ end
+
+ context "when the subject is not nil" do
+ let(:subject) { double(:subject) }
+
+ before do
+ tag_rule.set_context(subject,{})
+ allow(tag_rule).to receive(:customer_tags_match?) { :customer_tags_match_result }
+ allow(tag_rule).to receive(:subject_class) { Spree::Order}
+ end
+
+
+ context "when the subject class matches tag_rule#subject_class" do
+ before do
+ allow(subject).to receive(:class) { Spree::Order }
+ end
+
+ context "when the rule does not repond to #additional_requirements_met?" do
+ before { allow(tag_rule).to receive(:respond_to?).with(:additional_requirements_met?, true) { false } }
+
+ it "returns true" do
+ expect(tag_rule.send(:relevant?)).to be true
+ end
+ end
+
+ context "when the rule reponds to #additional_requirements_met?" do
+ before { allow(tag_rule).to receive(:respond_to?).with(:additional_requirements_met?, true) { true } }
+
+ context "and #additional_requirements_met? returns a truthy value" do
+ before { allow(tag_rule).to receive(:additional_requirements_met?) { "smeg" } }
+
+ it "returns true immediately" do
+ expect(tag_rule.send(:relevant?)).to be true
+ end
+ end
+
+ context "and #additional_requirements_met? returns true" do
+ before { allow(tag_rule).to receive(:additional_requirements_met?) { true } }
+
+ it "returns true immediately" do
+ expect(tag_rule.send(:relevant?)).to be true
+ end
+ end
+
+ context "and #additional_requirements_met? returns false" do
+ before { allow(tag_rule).to receive(:additional_requirements_met?) { false } }
+
+ it "returns false immediately" do
+ expect(tag_rule.send(:relevant?)).to be false
+ end
+ end
+ end
+ end
+
+ context "when the subject class does not match tag_rule#subject_class" do
+ before do
+ allow(subject).to receive(:class) { Spree::LineItem }
+ end
+
+ it "returns false immediately" do
+ expect(tag_rule.send(:relevant?)).to be false
+ expect(tag_rule).to_not have_received :customer_tags_match?
+ end
+ end
+ end
+
+ describe "determining whether specified customer tags match the given context" do
+ context "when the context is nil" do
+ before { tag_rule.set_context(nil, nil) }
+ it "returns false" do
+ expect(tag_rule.send(:customer_tags_match?)).to be false
+ end
+ end
+
+ context "when the context has no customer specified" do
+ let(:context) { { something_that_is_not_a_customer: double(:something) } }
+
+ before { tag_rule.set_context(nil, context) }
+
+ it "returns false" do
+ expect(tag_rule.send(:customer_tags_match?)).to be false
+ end
+ end
+
+ context "when the context has a customer specified" do
+ let(:context) { { customer: double(:customer, tag_list: ["member","local","volunteer"] ) } }
+
+ before { tag_rule.set_context(nil, context) }
+
+ context "when the rule has no preferred customer tags specified" do
+ before do
+ allow(tag_rule).to receive(:preferred_customer_tags) { "" }
+ end
+
+ it "returns false" do
+ expect(tag_rule.send(:customer_tags_match?)).to be false
+ end
+ end
+
+ context "when the rule has preferred customer tags specified that match ANY of the customer tags" do
+ before do
+ allow(tag_rule).to receive(:preferred_customer_tags) { "wholesale,some_tag,member" }
+ end
+
+ it "returns false" do
+ expect(tag_rule.send(:customer_tags_match?)).to be true
+ end
+ end
+
+ context "when the rule has preferred customer tags specified that match NONE of the customer tags" do
+ before do
+ allow(tag_rule).to receive(:preferred_customer_tags) { "wholesale,some_tag,some_other_tag" }
+ end
+
+ it "returns false" do
+ expect(tag_rule.send(:customer_tags_match?)).to be false
+ end
+ end
+ end
+ end
+
+ describe "applying a tag rule to a subject" do
+ before { allow(tag_rule).to receive(:apply!) }
+
+ context "when the rule is deemed to be relevant" do
+ before { allow(tag_rule).to receive(:relevant?) { true } }
+
+ context "and customer_tags_match? returns true" do
+ before { expect(tag_rule).to receive(:customer_tags_match?) { true } }
+
+ it "applies the rule" do
+ tag_rule.apply
+ expect(tag_rule).to have_received(:apply!)
+ end
+ end
+
+ context "when customer_tags_match? returns false" do
+ before { expect(tag_rule).to receive(:customer_tags_match?) { false } }
+ before { allow(tag_rule).to receive(:apply_default!) }
+
+ context "and the rule responds to #apply_default!" do
+ before { allow(tag_rule).to receive(:respond_to?).with(:apply_default!, true) { true } }
+
+ it "applies the default action" do
+ tag_rule.apply
+ expect(tag_rule).to_not have_received(:apply!)
+ expect(tag_rule).to have_received(:apply_default!)
+ end
+ end
+
+ context "and the rule does not respond to #apply_default!" do
+ before { allow(tag_rule).to receive(:respond_to?).with(:apply_default!, true) { false } }
+
+ it "does not apply the rule or the default action" do
+ tag_rule.apply
+ expect(tag_rule).to_not have_received(:apply!)
+ expect(tag_rule).to_not have_received(:apply_default!)
+ end
+ end
+ end
+ end
+
+ context "when the rule is deemed not to be relevant" do
+ before { allow(tag_rule).to receive(:relevant?) { false } }
+
+ it "does not apply the rule" do
+ tag_rule.apply
+ expect(tag_rule).to_not have_received(:apply!)
+ end
+ end
+ end
+ end
+end