diff --git a/.codeclimate.yml b/.codeclimate.yml index d737c0b3c9..dc2ebef051 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,6 +1,9 @@ engines: rubocop: enabled: true + exclude_fingerprints: + - ac41db8d4ec4cbf508c353d9b65a024f + - 8e3b6322aef5be9f38700b3fd0cd347e scss-lint: enabled: true ratings: diff --git a/app/assets/images/browser-logos/chrome.png b/app/assets/images/browser-logos/chrome.png index 79a0b8e064..a55fcfec96 100644 Binary files a/app/assets/images/browser-logos/chrome.png and b/app/assets/images/browser-logos/chrome.png differ diff --git a/app/assets/images/browser-logos/firefox.png b/app/assets/images/browser-logos/firefox.png index c77db6ebbf..91c1f85a95 100644 Binary files a/app/assets/images/browser-logos/firefox.png and b/app/assets/images/browser-logos/firefox.png differ diff --git a/app/assets/images/browser-logos/internet-explorer.png b/app/assets/images/browser-logos/internet-explorer.png index c67631a65e..af4739dd89 100644 Binary files a/app/assets/images/browser-logos/internet-explorer.png and b/app/assets/images/browser-logos/internet-explorer.png differ diff --git a/app/assets/images/case-studies/South_East_Food_Hub.png b/app/assets/images/case-studies/South_East_Food_Hub.png index 1760a68d72..1eaed1a657 100644 Binary files a/app/assets/images/case-studies/South_East_Food_Hub.png and b/app/assets/images/case-studies/South_East_Food_Hub.png differ diff --git a/app/assets/images/case-studies/baw-baw.png b/app/assets/images/case-studies/baw-baw.png index 56dd2c94f1..aac409ea35 100644 Binary files a/app/assets/images/case-studies/baw-baw.png and b/app/assets/images/case-studies/baw-baw.png differ diff --git a/app/assets/images/case-studies/bfc_logo_square.png b/app/assets/images/case-studies/bfc_logo_square.png index 90f54df507..30d0c4e935 100644 Binary files a/app/assets/images/case-studies/bfc_logo_square.png and b/app/assets/images/case-studies/bfc_logo_square.png differ diff --git a/app/assets/images/case-studies/bonnie-beef-growers.png b/app/assets/images/case-studies/bonnie-beef-growers.png index ad9f5b14ad..9845874aea 100644 Binary files a/app/assets/images/case-studies/bonnie-beef-growers.png and b/app/assets/images/case-studies/bonnie-beef-growers.png differ diff --git a/app/assets/images/case-studies/jindivick.jpg b/app/assets/images/case-studies/jindivick.jpg index b94ab00166..be3fac1851 100644 Binary files a/app/assets/images/case-studies/jindivick.jpg and b/app/assets/images/case-studies/jindivick.jpg differ diff --git a/app/assets/images/case-studies/jonai.png b/app/assets/images/case-studies/jonai.png index 6cdf7dd20b..0364e26dad 100644 Binary files a/app/assets/images/case-studies/jonai.png and b/app/assets/images/case-studies/jonai.png differ diff --git a/app/assets/images/case-studies/longley.png b/app/assets/images/case-studies/longley.png index 0d20ae93df..ead3b3d2f9 100644 Binary files a/app/assets/images/case-studies/longley.png and b/app/assets/images/case-studies/longley.png differ diff --git a/app/assets/images/case-studies/mt-alexander.png b/app/assets/images/case-studies/mt-alexander.png index efb86d2463..4bb0e54652 100644 Binary files a/app/assets/images/case-studies/mt-alexander.png and b/app/assets/images/case-studies/mt-alexander.png differ diff --git a/app/assets/images/case-studies/wandiful.png b/app/assets/images/case-studies/wandiful.png index f7c4469022..d50cca0d49 100644 Binary files a/app/assets/images/case-studies/wandiful.png and b/app/assets/images/case-studies/wandiful.png differ diff --git a/app/assets/images/collapse.png b/app/assets/images/collapse.png index 1ac2122d8a..d2f5fc73a9 100644 Binary files a/app/assets/images/collapse.png and b/app/assets/images/collapse.png differ diff --git a/app/assets/images/countdown.png b/app/assets/images/countdown.png index 31e2ae2193..12c4877a78 100644 Binary files a/app/assets/images/countdown.png and b/app/assets/images/countdown.png differ diff --git a/app/assets/images/eaterprises_logo.jpg b/app/assets/images/eaterprises_logo.jpg index 1e2bee0a2e..a38c7b72c5 100644 Binary files a/app/assets/images/eaterprises_logo.jpg and b/app/assets/images/eaterprises_logo.jpg differ diff --git a/app/assets/images/enterprise-type.png b/app/assets/images/enterprise-type.png index 0c5741a82f..bee6ee1a13 100644 Binary files a/app/assets/images/enterprise-type.png and b/app/assets/images/enterprise-type.png differ diff --git a/app/assets/images/expand.png b/app/assets/images/expand.png index aa3fc20f39..012b3ea8fa 100644 Binary files a/app/assets/images/expand.png and b/app/assets/images/expand.png differ diff --git a/app/assets/images/gray_jean.png b/app/assets/images/gray_jean.png index 1de7cafdda..d911ae617e 100644 Binary files a/app/assets/images/gray_jean.png and b/app/assets/images/gray_jean.png differ diff --git a/app/assets/images/gray_jean_light.png b/app/assets/images/gray_jean_light.png index ba0f0dc240..2ee033a20a 100644 Binary files a/app/assets/images/gray_jean_light.png and b/app/assets/images/gray_jean_light.png differ diff --git a/app/assets/images/home/background-blurred-oranges.jpg b/app/assets/images/home/background-blurred-oranges.jpg index 61019198e3..611a466132 100644 Binary files a/app/assets/images/home/background-blurred-oranges.jpg and b/app/assets/images/home/background-blurred-oranges.jpg differ diff --git a/app/assets/images/home/home-apples.jpg b/app/assets/images/home/home-apples.jpg index 5cf22d4a50..91bfd4cfa5 100644 Binary files a/app/assets/images/home/home-apples.jpg and b/app/assets/images/home/home-apples.jpg differ diff --git a/app/assets/images/home/home-oranges.jpg b/app/assets/images/home/home-oranges.jpg index 6cd547c97f..671f02d979 100644 Binary files a/app/assets/images/home/home-oranges.jpg and b/app/assets/images/home/home-oranges.jpg differ diff --git a/app/assets/images/home/home-strawberries.jpg b/app/assets/images/home/home-strawberries.jpg index e837c69065..cb2bf15c0e 100644 Binary files a/app/assets/images/home/home-strawberries.jpg and b/app/assets/images/home/home-strawberries.jpg differ diff --git a/app/assets/images/home/home.jpg b/app/assets/images/home/home.jpg index cb59fbef31..0c5c22c6f1 100644 Binary files a/app/assets/images/home/home.jpg and b/app/assets/images/home/home.jpg differ diff --git a/app/assets/images/home/home1.jpg b/app/assets/images/home/home1.jpg index 0e37397127..015b203bcf 100644 Binary files a/app/assets/images/home/home1.jpg and b/app/assets/images/home/home1.jpg differ diff --git a/app/assets/images/home/home2.jpg b/app/assets/images/home/home2.jpg index fa548cb519..4e5cf61b08 100644 Binary files a/app/assets/images/home/home2.jpg and b/app/assets/images/home/home2.jpg differ diff --git a/app/assets/images/home/home3.jpg b/app/assets/images/home/home3.jpg index 26fdf1037c..307ad38c1b 100644 Binary files a/app/assets/images/home/home3.jpg and b/app/assets/images/home/home3.jpg differ diff --git a/app/assets/images/home/macbook.png b/app/assets/images/home/macbook.png index b322681273..ab0d7dfe07 100644 Binary files a/app/assets/images/home/macbook.png and b/app/assets/images/home/macbook.png differ diff --git a/app/assets/images/hubs-bg.jpg b/app/assets/images/hubs-bg.jpg index 2d1c49e230..021ead1074 100644 Binary files a/app/assets/images/hubs-bg.jpg and b/app/assets/images/hubs-bg.jpg differ diff --git a/app/assets/images/icon-mask-apple.png b/app/assets/images/icon-mask-apple.png index 87bfa91bb8..236e0a78e8 100644 Binary files a/app/assets/images/icon-mask-apple.png and b/app/assets/images/icon-mask-apple.png differ diff --git a/app/assets/images/icon-mask-bread.png b/app/assets/images/icon-mask-bread.png index d5c29d96ff..5b7e3a1c79 100644 Binary files a/app/assets/images/icon-mask-bread.png and b/app/assets/images/icon-mask-bread.png differ diff --git a/app/assets/images/icon-mask-magnifier.png b/app/assets/images/icon-mask-magnifier.png index 7f53b7db9d..701f3918f7 100644 Binary files a/app/assets/images/icon-mask-magnifier.png and b/app/assets/images/icon-mask-magnifier.png differ diff --git a/app/assets/images/icon-mask-truck.png b/app/assets/images/icon-mask-truck.png index 40550f98c2..e15c54a3b3 100644 Binary files a/app/assets/images/icon-mask-truck.png and b/app/assets/images/icon-mask-truck.png differ diff --git a/app/assets/images/logo-australia.png b/app/assets/images/logo-australia.png index 46b8ef071a..b4e8dce42b 100644 Binary files a/app/assets/images/logo-australia.png and b/app/assets/images/logo-australia.png differ diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png index 31ec1422f1..2a33dc3610 100644 Binary files a/app/assets/images/logo-black.png and b/app/assets/images/logo-black.png differ diff --git a/app/assets/images/logo-color.png b/app/assets/images/logo-color.png index e464781a54..fd709b46e9 100644 Binary files a/app/assets/images/logo-color.png and b/app/assets/images/logo-color.png differ diff --git a/app/assets/images/logo-global-white.png b/app/assets/images/logo-global-white.png index 8761bafe9d..c24b525bd2 100644 Binary files a/app/assets/images/logo-global-white.png and b/app/assets/images/logo-global-white.png differ diff --git a/app/assets/images/logo-white-notext.png b/app/assets/images/logo-white-notext.png index bfd590c621..c8f2fb0098 100644 Binary files a/app/assets/images/logo-white-notext.png and b/app/assets/images/logo-white-notext.png differ diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png index ffd1c6f73a..87cbfa518a 100644 Binary files a/app/assets/images/logo-white.png and b/app/assets/images/logo-white.png differ diff --git a/app/assets/images/matte.png b/app/assets/images/matte.png index 845642cab2..0f6a26429c 100644 Binary files a/app/assets/images/matte.png and b/app/assets/images/matte.png differ diff --git a/app/assets/images/noimage/group.png b/app/assets/images/noimage/group.png index db71856deb..e061134891 100644 Binary files a/app/assets/images/noimage/group.png and b/app/assets/images/noimage/group.png differ diff --git a/app/assets/images/noimage/large.png b/app/assets/images/noimage/large.png index 166a74488a..b345fbf004 100644 Binary files a/app/assets/images/noimage/large.png and b/app/assets/images/noimage/large.png differ diff --git a/app/assets/images/noimage/mini.png b/app/assets/images/noimage/mini.png index db94c3ce7f..1bc901a176 100644 Binary files a/app/assets/images/noimage/mini.png and b/app/assets/images/noimage/mini.png differ diff --git a/app/assets/images/noimage/product.png b/app/assets/images/noimage/product.png index 6ca94eadb2..beeb0b3072 100644 Binary files a/app/assets/images/noimage/product.png and b/app/assets/images/noimage/product.png differ diff --git a/app/assets/images/noimage/small.png b/app/assets/images/noimage/small.png index 02088d8da0..aa17b73ed8 100644 Binary files a/app/assets/images/noimage/small.png and b/app/assets/images/noimage/small.png differ diff --git a/app/assets/images/ofn-logo-footer.png b/app/assets/images/ofn-logo-footer.png index f612d5aa87..8fba44b4a6 100644 Binary files a/app/assets/images/ofn-logo-footer.png and b/app/assets/images/ofn-logo-footer.png differ diff --git a/app/assets/images/ofn-logo.png b/app/assets/images/ofn-logo.png index f53680c342..6058b26a75 100644 Binary files a/app/assets/images/ofn-logo.png and b/app/assets/images/ofn-logo.png differ diff --git a/app/assets/images/ofn-o.png b/app/assets/images/ofn-o.png index d6498ddb4d..8a5d24f41b 100644 Binary files a/app/assets/images/ofn-o.png and b/app/assets/images/ofn-o.png differ diff --git a/app/assets/images/potatoes.png b/app/assets/images/potatoes.png index c0be04200e..baea478c9b 100644 Binary files a/app/assets/images/potatoes.png and b/app/assets/images/potatoes.png differ diff --git a/app/assets/images/producers/producers-pg-bg.jpg b/app/assets/images/producers/producers-pg-bg.jpg index 2df73f0641..612e2d9404 100644 Binary files a/app/assets/images/producers/producers-pg-bg.jpg and b/app/assets/images/producers/producers-pg-bg.jpg differ diff --git a/app/assets/images/select2.png b/app/assets/images/select2.png old mode 100755 new mode 100644 index 9790e029f0..7cef1c04f8 Binary files a/app/assets/images/select2.png and b/app/assets/images/select2.png differ diff --git a/app/assets/images/select2x2.png b/app/assets/images/select2x2.png old mode 100755 new mode 100644 index 7e737c98cf..62628d1302 Binary files a/app/assets/images/select2x2.png and b/app/assets/images/select2x2.png differ diff --git a/app/assets/images/tile-wide.png b/app/assets/images/tile-wide.png index f4e348b73c..b802d0a334 100644 Binary files a/app/assets/images/tile-wide.png and b/app/assets/images/tile-wide.png differ diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 751734a899..7a9d2677b9 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) -> +angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, $window, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) -> $scope.loading = true $scope.StatusMessage = StatusMessage @@ -206,6 +206,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout else $scope.displayFailure t("products_update_error_data") + status + $scope.cancel = (destination) -> + $window.location = destination $scope.packProduct = (product) -> if product.variant_unit_with_scale @@ -247,6 +249,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.displaySuccess = -> StatusMessage.display 'success',t("products_changes_saved") + $scope.bulk_product_form.$setPristine() $scope.displayFailure = (failMessage) -> 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 23bfb37738..2721c92961 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,6 +1,5 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) -> +angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filter, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) -> $scope.shops = shops - $scope.CurrentShop = CurrentShop $scope.RequestMonitor = RequestMonitor $scope.submitAll = pendingChanges.submitAll $scope.add = Customers.add @@ -8,15 +7,37 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Custo $scope.customerLimit = 20 $scope.columns = Columns.columns - $scope.$watch "CurrentShop.shop", -> - if $scope.CurrentShop.shop.id? - Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) -> + $scope.confirmRefresh = (event) -> + event.preventDefault() unless pendingChanges.unsavedCount() == 0 || confirm(t("unsaved_changes_warning")) + + $scope.$watch "shop_id", -> + if $scope.shop_id? + CurrentShop.shop = $filter('filter')($scope.shops, {id: $scope.shop_id})[0] + Customers.index({enterprise_id: $scope.shop_id}).then (data) -> + pendingChanges.removeAll() + $scope.customers_form.$setPristine() $scope.customers = data + $scope.shop_id = shops[0].id if shops.length == 1 + + $scope.checkForDuplicateCodes = -> + delete this.customer.code unless this.customer.code + this.duplicate = $scope.isDuplicateCode(this.customer.code) + + $scope.isDuplicateCode = (code) -> + return false unless code + customers = $scope.findByCode(code) + customers.length > 1 + + $scope.findByCode = (code) -> + if $scope.customers + $scope.customers.filter (customer) -> + customer.code == code + $scope.findTags = (query) -> defer = $q.defer() params = - enterprise_id: $scope.CurrentShop.shop.id + enterprise_id: $scope.shop_id TagRuleResource.mapByTag params, (data) => filtered = data.filter (tag) -> tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 diff --git a/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee index 02276875b9..f2dae5836b 100644 --- a/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee +++ b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee @@ -1,20 +1,21 @@ -angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, $window, CurrentShop, Customers) -> +angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, DialogDefaults, CurrentShop, Customers) -> restrict: 'A' scope: true link: (scope, element, attr) -> scope.CurrentShop = CurrentShop - scope.submitted = null + scope.submitted = false scope.email = "" scope.errors = [] - scope.addCustomer = (valid) -> - scope.submitted = scope.email + scope.addCustomer = -> + scope.new_customer_form.$setPristine() + scope.submitted = true scope.errors = [] - if valid + if scope.new_customer_form.$valid Customers.add(scope.email).$promise.then (data) -> if data.id scope.email = "" - scope.submitted = null + scope.submitted = false template.dialog('close') , (response) -> if response.data.errors @@ -27,16 +28,7 @@ angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $inj template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope) # 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') + template.dialog(DialogDefaults) # Link opening of dialog to click event on element element.bind 'click', (e) -> diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee index a9f4f0102f..cd4c9bbb44 100644 --- a/app/assets/javascripts/admin/customers/services/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) -> +angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestMonitor, CustomerResource, CurrentShop) -> new class Customers customers: [] @@ -14,6 +14,12 @@ angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, Cust CustomerResource.destroy params, => i = @customers.indexOf customer @customers.splice i, 1 unless i < 0 + , (response) => + errors = response.data.errors + if errors? + InfoDialog.open 'error', errors[0] + else + InfoDialog.open 'error', "Could not delete customer: #{customer.email}" index: (params) -> request = CustomerResource.index(params, (data) => @customers = data) diff --git a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee index 45ca04bfe7..f69c5365d5 100644 --- a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee @@ -4,12 +4,12 @@ angular.module("admin.enterprise_groups") $scope.select = SideMenu.select $scope.menu.setItems [ - { name: 'Primary Details', icon_class: "icon-user" } - { name: (t('users')), icon_class: "icon-user" } - { name: (t('about')), icon_class: "icon-pencil" } - { name: (t('images')), icon_class: "icon-picture" } - { name: (t('contact')), icon_class: "icon-phone" } - { name: (t('web')), icon_class: "icon-globe" } + { name: 'primary_details', label: t('primary_details'), icon_class: "icon-user" } + { name: 'users', label: t('users'), icon_class: "icon-user" } + { name: 'about', label: t('about'), icon_class: "icon-pencil" } + { name: 'images', label: t('images'), icon_class: "icon-picture" } + { name: 'contact', label: t('admin_entreprise_groups_contact'), icon_class: "icon-phone" } + { name: 'web', label: t('admin_entreprise_groups_web'), icon_class: "icon-globe" } ] $scope.select(0) diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index a806691465..f21c96a27e 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -1,5 +1,5 @@ angular.module("admin.enterprises") - .controller "enterpriseCtrl", ($scope, NavigationCheck, enterprise, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu) -> + .controller "enterpriseCtrl", ($scope, $window, NavigationCheck, enterprise, EnterprisePaymentMethods, EnterpriseShippingMethods, SideMenu, StatusMessage) -> $scope.Enterprise = enterprise $scope.PaymentMethods = EnterprisePaymentMethods.paymentMethods $scope.ShippingMethods = EnterpriseShippingMethods.shippingMethods @@ -8,6 +8,23 @@ angular.module("admin.enterprises") $scope.menu = SideMenu $scope.newManager = { id: '', email: (t('add_manager')) } + $scope.StatusMessage = StatusMessage + + $scope.$watch 'enterprise_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + + $scope.setFormDirty = -> + $scope.$apply -> + $scope.enterprise_form.$setDirty() + + $scope.cancel = (destination) -> + $window.location = destination + + $scope.submit = -> + $scope.navClear() + enterprise_form.submit() + + # Provide a callback for generating warning messages displayed before leaving the page. This is passed in # from a directive "nav-check" in the page - if we pass it here it will be called in the test suite, # and on all new uses of this contoller, and we might not want that . diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee deleted file mode 100644 index 63b8daf07c..0000000000 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee +++ /dev/null @@ -1,49 +0,0 @@ -angular.module("admin.enterprises").controller "EnterpriseIndexRowCtrl", ($scope) -> - $scope.status = -> - if $scope.enterprise.issues.length > 0 - "issue" - else if $scope.enterprise.warnings.length > 0 - "warning" - else - "ok" - - - $scope.producerText = -> - switch $scope.enterprise.is_primary_producer - when true - "Producer" - else - "Non-Producer" - - $scope.packageText = -> - switch $scope.enterprise.is_primary_producer - when true - switch $scope.enterprise.sells - when "none" - "Profile" - when "own" - "Shop" - when "any" - "Hub" - else - "Choose" - else - switch $scope.enterprise.sells - when "none" - "Profile" - when "any" - "Hub" - else - "Choose" - - $scope.updateRowText = -> - $scope.producer = $scope.producerText() - $scope.package = $scope.packageText() - $scope.producerError = ($scope.producer == "Choose") - $scope.packageError = ($scope.package == "Choose") - - - $scope.updateRowText() - - $scope.$on "enterprise:updated", -> - $scope.updateRowText() diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee index d6dda4a118..02553a822b 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee @@ -3,6 +3,54 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E requests.push ($scope.allEnterprises = Enterprises.index(ams_prefix: "index")).$promise $q.all(requests).then -> + $scope.updateStaticFieldsFor(enterprise) for enterprise in $scope.allEnterprises $scope.loaded = true $scope.columns = Columns.columns + + $scope.updateStaticFieldsFor = (enterprise) -> + enterprise.producer = $scope.producerTextFor(enterprise) + enterprise.package = $scope.packageTextFor(enterprise) + enterprise.producerError = (enterprise.producer == "Choose") + enterprise.packageError = (enterprise.package == "Choose") + enterprise.status = $scope.statusFor(enterprise) + + $scope.$on "enterprise:updated", (event, enterprise) -> + $scope.updateStaticFieldsFor(enterprise) + + $scope.statusFor = (enterprise) -> + if enterprise.issues.length > 0 + "issue" + else if enterprise.warnings.length > 0 + "warning" + else + "ok" + + + $scope.producerTextFor = (enterprise) -> + switch enterprise.is_primary_producer + when true + "Producer" + else + "Non-Producer" + + $scope.packageTextFor = (enterprise) -> + switch enterprise.is_primary_producer + when true + switch enterprise.sells + when "none" + "Profile" + when "own" + "Shop" + when "any" + "Hub" + else + "Choose" + else + switch enterprise.sells + when "none" + "Profile" + when "any" + "Hub" + else + "Choose" diff --git a/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee index 6f568ca5ea..1e207884d3 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee @@ -9,7 +9,7 @@ angular.module("admin.enterprises").controller 'indexPanelCtrl', ($scope, Enterp unless $scope.saved() $scope.saving = true Enterprises.save($scope.enterprise).then (data) -> - $scope.$emit("enterprise:updated") + $scope.$emit("enterprise:updated", $scope.enterprise) $scope.saving = false , (response) -> $scope.saving = false 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 a0105fefa4..913ff59d3e 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 @@ -5,21 +5,21 @@ angular.module("admin.enterprises") $scope.select = SideMenu.select $scope.menu.setItems [ - { name: t('primary_details'), icon_class: "icon-home" } - { name: t('users'), icon_class: "icon-user" } - { name: t('address'), icon_class: "icon-map-marker" } - { name: t('contact'), icon_class: "icon-phone" } - { name: t('social'), icon_class: "icon-twitter" } - { name: t('about'), icon_class: "icon-pencil" } - { name: t('business_details'), icon_class: "icon-briefcase" } - { name: t('images'), icon_class: "icon-picture" } - { name: t('properties'), icon_class: "icon-tags", show: "showProperties()" } - { 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: "enterpriseIsShop()" } - { name: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" } - { name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" } + { name: 'primary_details', label: t('primary_details'), icon_class: "icon-home" } + { name: 'users', label: t('users'), icon_class: "icon-user" } + { name: 'address', label: t('address'), icon_class: "icon-map-marker" } + { name: 'contact', label: t('contact'), icon_class: "icon-phone" } + { name: 'social', label: t('social'), icon_class: "icon-twitter" } + { name: 'about', label: t('about'), icon_class: "icon-pencil" } + { name: 'business_details', label: t('business_details'), icon_class: "icon-briefcase" } + { name: 'images', label: t('images'), icon_class: "icon-picture" } + { name: 'properties', label: t('properties'), icon_class: "icon-tags", show: "showProperties()" } + { name: 'shipping_methods', label: t('shipping_methods'), icon_class: "icon-truck", show: "showShippingMethods()" } + { name: 'payment_methods', label: t('payment_methods'), icon_class: "icon-money", show: "showPaymentMethods()" } + { name: 'enterprise_fees', label: t('enterprise_fees'), icon_class: "icon-tasks", show: "showEnterpriseFees()" } + { name: 'inventory_settings', label: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" } + { name: 'tag_rules', label: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" } + { name: 'shop_preferences', label: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" } ] $scope.select(0) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee index 023f33d86c..b522f5be3b 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee @@ -1,4 +1,7 @@ angular.module("admin.enterprises").factory 'EnterpriseResource', ($resource) -> + ignoredAttrs = -> + ["$$hashKey", "producer", "package", "producerError", "packageError", "status"] + $resource('/admin/enterprises/:id/:action.json', {}, { 'index': method: 'GET' diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee index b159816709..80db943689 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee @@ -33,8 +33,11 @@ angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResour diff: (enterprise) -> changed = [] for attr, value of enterprise when not angular.equals(value, @pristineByID[enterprise.id][attr]) - changed.push attr unless attr is "$$hashKey" + changed.push attr unless attr in @ignoredAttrs() changed + ignoredAttrs: -> + ["$$hashKey", "producer", "package", "producerError", "packageError", "status"] + resetAttribute: (enterprise, attribute) -> enterprise[attribute] = @pristineByID[enterprise.id][attribute] diff --git a/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee b/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee index 254fa5c438..81cf58fd1e 100644 --- a/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/obj_for_update.js.coffee @@ -15,7 +15,7 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi object: scope.object() type: scope.type attr: scope.attr - value: value + value: if value? then value else "" scope: scope scope.pending() pendingChanges.add(scope.object().id, scope.attr, change) 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 bbb5221682..d65341e6fe 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 @@ -7,6 +7,7 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, text: "@?" blank: "=?" filter: "=?" + onSelecting: "=?" link: (scope, element, attrs, ngModel) -> $timeout -> scope.text ||= 'name' @@ -24,6 +25,8 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, formatResult: (item) -> item[scope.text] + element.on "select2-opening", scope.onSelecting || angular.noop + attrs.$observe 'disabled', (value) -> element.select2('enable', !value) diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee new file mode 100644 index 0000000000..4259ce63e2 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee @@ -0,0 +1,21 @@ +angular.module("admin.indexUtils").directive "panelCtrl", (Panels) -> + restrict: "C" + scope: + object: "=" + selected: "@?" + controller: ($scope, $element) -> + this.toggle = (name) -> + Panels.toggle($scope.object, name) + + this.select = (selection) -> + $scope.$broadcast("selection:changed", selection) + $element.toggleClass("expanded", selection?) + + this.registerSelectionListener = (callback) -> + $scope.$on "selection:changed", (event, selection) -> + callback(selection) + + this + + link: (scope, element, attrs, ctrl) -> + Panels.register(ctrl, scope.object, scope.selected) diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee index eb5a4171f4..8b1e8d4b24 100644 --- a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee @@ -1,37 +1,24 @@ angular.module("admin.indexUtils").directive "panelRow", (Panels, Columns) -> restrict: "C" + require: "^^panelCtrl" templateUrl: "admin/panel.html" scope: object: "=" panels: "=" - link: (scope, element, attrs) -> - scope.template = "" - selected = null - scope.columnCount = Columns.visibleCount + colspan: "=?" + locals: '@?' + link: (scope, element, attrs, ctrl) -> + scope.template = null + scope.columnCount = (scope.colspan || Columns.visibleCount) + + if scope.locals + scope[local] = scope.$parent.$eval(local.trim()) for local in scope.locals.split(',') scope.$on "columnCount:changed", (event, count) -> scope.columnCount = count - setTemplate = -> - if selected? - scope.template = 'admin/panels/' + scope.panels[selected] + '.html' + ctrl.registerSelectionListener (selection) -> + if selection? + scope.template = "admin/panels/#{scope.panels[selection]}.html" else - scope.template = "" - - scope.getSelected = -> - selected - - scope.setSelected = (name) -> - scope.$apply -> - selected = name - setTemplate() - - scope.open = (name) -> - element.show 0, -> - scope.setSelected name - - scope.close = -> - element.hide 0, -> - scope.setSelected null - - Panels.register(scope.object.id, scope) + scope.template = null diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee index df81328905..cdd15d1d62 100644 --- a/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee @@ -2,11 +2,13 @@ angular.module("admin.indexUtils").directive "panelToggle", -> restrict: "C" transclude: true template: '
' - require: "^panelToggleRow" + require: "^^panelCtrl" scope: name: "@" link: (scope, element, attrs, ctrl) -> - scope.selected = ctrl.register(scope.name, element) - element.on "click", -> - scope.selected = ctrl.select(scope.name) + scope.$apply -> + ctrl.toggle(scope.name) + + ctrl.registerSelectionListener (selection) -> + element.toggleClass('selected', selection == scope.name) diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee deleted file mode 100644 index d2d9c90ff8..0000000000 --- a/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee +++ /dev/null @@ -1,29 +0,0 @@ -angular.module("admin.indexUtils").directive "panelToggleRow", (Panels) -> - restrict: "C" - scope: - object: "=" - selected: "@?" - controller: ($scope) -> - panelToggles = {} - - this.register = (name, element) -> - panelToggles[name] = element - panelToggles[name].addClass("selected") if $scope.selected == name - $scope.selected == name - - this.select = (name) -> - panelToggle.removeClass("selected") for panelName, panelToggle of panelToggles - - switch $scope.selected = Panels.toggle($scope.object.id, name) - when null - panelToggles[name].parent(".panel-toggle-row").removeClass("expanded") - else - panelToggles[$scope.selected].addClass("selected") - panelToggles[$scope.selected].parent(".panel-toggle-row").addClass("expanded") - - $scope.selected == name - - this - # - # link: (scope, element, attrs) -> - # Panels.registerInitialSelection(scope.object.id, scope.selected) diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee index 45bea7c680..fbc5149a3f 100644 --- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) -> +angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injector) -> new class Columns savedColumns: {} columns: {} @@ -6,11 +6,17 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, column constructor: -> @columns = {} - for column in columns + for column in @injectColumns() @columns[column.column_name] = column @savedColumns[column.column_name] = angular.copy(column) @calculateVisibleCount() + injectColumns: -> + if $injector.has('columns') + $injector.get('columns') + else + [] + toggleColumn: (column) => column.visible = !column.visible @calculateVisibleCount() diff --git a/app/assets/javascripts/admin/index_utils/services/panels.js.coffee b/app/assets/javascripts/admin/index_utils/services/panels.js.coffee index 27852bed12..d020b88264 100644 --- a/app/assets/javascripts/admin/index_utils/services/panels.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/panels.js.coffee @@ -1,19 +1,22 @@ angular.module("admin.indexUtils").factory 'Panels', -> new class Panels - panels: {} + panels: [] - register: (id, scope) -> - if id? && scope? - @panels[id] = scope + register: (ctrl, object, selected=null) -> + if ctrl? && object? + @panels.push { ctrl: ctrl, object: object, selected: selected } + ctrl.select(selected) if selected? - toggle: (id, name) -> - scope = @panels[id] - selected = scope.getSelected() - switch selected - when name - scope.close() - when null - scope.open(name) - else - scope.setSelected(name) - scope.getSelected() + toggle: (object, name, state=null) -> + panel = @findPanelByObject(object) + if panel.selected == name + @select(panel, null) unless state == "open" + else + @select(panel, name) unless state == "closed" + + select: (panel, name) -> + panel.selected = name + panel.ctrl.select(name) + + findPanelByObject: (object) -> + (panel for panel in @panels when panel.object == object)[0] diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 15626b1479..a3d842c16d 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -10,6 +10,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta removeAll: => @pendingChanges = {} + StatusMessage.clear() + remove: (id, attr) => if @pendingChanges.hasOwnProperty("#{id}") @@ -40,5 +42,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta @errors.push error change.scope.error() + unsavedCount: -> + Object.keys(@pendingChanges).length + changeCount: (objectChanges) -> Object.keys(objectChanges).length diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee index 3077aa8a11..e0f7cf96b6 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -1,5 +1,5 @@ angular.module('admin.orderCycles') - .controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> + .controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, $window, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) -> $scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id) $scope.supplier_enterprises = Enterprise.producer_enterprises $scope.distributor_enterprises = Enterprise.hub_enterprises @@ -11,6 +11,9 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -41,10 +44,6 @@ angular.module('admin.orderCycles') $scope.enterprisesWithFees = -> $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> EnterpriseFee.forEnterprise(parseInt(enterprise_id)) @@ -59,6 +58,7 @@ angular.module('admin.orderCycles') $scope.removeExchange = ($event, exchange) -> $event.preventDefault() OrderCycle.removeExchange(exchange) + $scope.order_cycle_form.$dirty = true $scope.addCoordinatorFee = ($event) -> $event.preventDefault() @@ -81,4 +81,9 @@ angular.module('admin.orderCycles') $scope.submit = ($event, destination) -> $event.preventDefault() + StatusMessage.display 'progress', "Saving..." OrderCycle.create(destination) + + $scope.cancel = (destination) -> + $window.location = destination + diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index 2f3b2db5e0..c0818acafa 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -45,10 +45,6 @@ angular.module('admin.orderCycles') $scope.enterprisesWithFees = -> $scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0 - $scope.toggleProducts = ($event, exchange) -> - $event.preventDefault() - OrderCycle.toggleProducts(exchange) - $scope.enterpriseFeesForEnterprise = (enterprise_id) -> EnterpriseFee.forEnterprise(parseInt(enterprise_id)) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee index dfa8011167..7a0a03f2bf 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, ocInstance) -> +angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, ocInstance) -> $scope.StatusMessage = StatusMessage $scope.OrderCycle = OrderCycle $scope.order_cycle = OrderCycle.new {coordinator_id: ocInstance.coordinator_id}, => @@ -7,6 +7,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" $scope.init(enterprises) $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.init = (enterprises) -> enterprise = enterprises[Object.keys(enterprises)[0]] OrderCycle.addSupplier enterprise.id @@ -43,5 +46,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" $scope.submit = ($event, destination) -> $event.preventDefault() + StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.create(destination) + + $scope.cancel = (destination) -> + $window.location = destination diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index f5bae5a4d8..7df7ddff34 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> +angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> $scope.orderCycleId = -> $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] @@ -42,3 +42,6 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination, $scope.order_cycle_form) + + $scope.cancel = (destination) -> + $window.location = destination diff --git a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee index a75fdad58c..6ea3f76984 100644 --- a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils']) +angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils', 'ngTagsInput']) .config ($httpProvider) -> $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 662201c9f5..65f0f67b71 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage) -> +angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage, Panels) -> OrderCycleResource = $resource '/admin/order_cycles/:action_name/:order_cycle_id.json', {}, { 'index': { method: 'GET', isArray: true} 'new' : { method: 'GET', params: { action_name: "new" } } @@ -30,17 +30,19 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S exchangeDirection: (exchange) -> if this.order_cycle.incoming_exchanges.indexOf(exchange) == -1 then 'outgoing' else 'incoming' - toggleProducts: (exchange) -> - exchange.showProducts = !exchange.showProducts - toggleAllProducts: (direction) -> this.showProducts[direction] = !this.showProducts[direction] - exchange.showProducts = this.showProducts[direction] for exchange in this.exchangesByDirection(direction) + state = if this.showProducts[direction] then "open" else "closed" + exchanges = this.exchangesByDirection(direction) + Panels.toggle(exchange,'products',state) for exchange in exchanges setExchangeVariants: (exchange, variants, selected) -> direction = if exchange.incoming then "incoming" else "outgoing" editable = @order_cycle["editable_variants_for_#{direction}_exchanges"][exchange.enterprise_id] || [] - exchange.variants[variant] = selected for variant in variants when variant in editable + for variant in variants when variant in editable + exchange.variants[variant] = selected + @removeDistributionOfVariant(variant.id) if exchange.incoming + addSupplier: (new_supplier_id) -> this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []}) 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 c2595faa6d..6e26b39d17 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,2 @@ -angular.module("admin.paymentMethods") - .controller "paymentMethodCtrl", ($scope, PaymentMethods) -> - $scope.findPaymentMethodByID = (id) -> - $scope.PaymentMethod = PaymentMethods.findByID(id) +angular.module("admin.paymentMethods").controller "paymentMethodCtrl", ($scope, paymentMethod) -> + $scope.paymentMethod = paymentMethod diff --git a/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee new file mode 100644 index 0000000000..ddad6cd259 --- /dev/null +++ b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee @@ -0,0 +1,3 @@ +angular.module("admin.paymentMethods").controller "paymentMethodsCtrl", ($scope, PaymentMethods) -> + $scope.findPaymentMethodByID = (id) -> + $scope.PaymentMethod = PaymentMethods.findByID(id) diff --git a/app/assets/javascripts/admin/controllers/providers_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee similarity index 61% rename from app/assets/javascripts/admin/controllers/providers_controller.js.coffee rename to app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee index 3b3378d013..6b9669c9e7 100644 --- a/app/assets/javascripts/admin/controllers/providers_controller.js.coffee +++ b/app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee @@ -1,7 +1,7 @@ -angular.module("ofn.admin").controller "ProvidersCtrl", ($scope, paymentMethod) -> +angular.module("admin.paymentMethods").controller "ProvidersCtrl", ($scope, paymentMethod) -> if paymentMethod.type $scope.include_html = "/admin/payment_methods/show_provider_preferences?" + "provider_type=#{paymentMethod.type};" + "pm_id=#{paymentMethod.id};" else - $scope.include_html = "" \ No newline at end of file + $scope.include_html = "" diff --git a/app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee b/app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee similarity index 64% rename from app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee rename to app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee index 467bad4e5f..2a0fc50f6f 100644 --- a/app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee +++ b/app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee @@ -1,7 +1,7 @@ -angular.module("ofn.admin").directive "providerPrefsFor", ($http) -> +angular.module("admin.paymentMethods").directive "providerPrefsFor", ($http) -> link: (scope, element, attrs) -> element.on "change blur load", -> scope.$apply -> scope.include_html = "/admin/payment_methods/show_provider_preferences?" + "provider_type=#{element.val()};" + - "pm_id=#{attrs.providerPrefsFor};" \ No newline at end of file + "pm_id=#{attrs.providerPrefsFor};" 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 01553647d4..fb4fec1ff9 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.paymentMethods", []) +angular.module("admin.paymentMethods", ['ngTagsInput', 'admin.utils', 'templates']) 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 index 91569b2256..9efd2a5fea 100644 --- 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 @@ -1,4 +1,3 @@ -angular.module("admin.shippingMethods") - .controller "shippingMethodsCtrl", ($scope, ShippingMethods) -> - $scope.findShippingMethodByID = (id) -> - $scope.ShippingMethod = ShippingMethods.findByID(id) +angular.module("admin.shippingMethods").controller "shippingMethodsCtrl", ($scope, ShippingMethods) -> + $scope.findShippingMethodByID = (id) -> + $scope.ShippingMethod = ShippingMethods.findByID(id) 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 index 7209147b66..460d8565eb 100644 --- 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 @@ -1,15 +1,16 @@ -angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, enterprise) -> +angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, $filter, enterprise) -> $scope.tagGroups = enterprise.tag_groups + $scope.defaultTagGroup = enterprise.default_tag_group $scope.visibilityOptions = [ { id: "visible", name: "VISIBLE" }, { id: "hidden", name: "NOT VISIBLE" } ] - updateRuleCounts = -> - index = 0 - for tagGroup in $scope.tagGroups + $scope.updateRuleCounts = -> + index = $scope.defaultTagGroup.rules.length + for tagGroup in $filter('orderBy')($scope.tagGroups, 'position') tagGroup.startIndex = index index = index + tagGroup.rules.length - updateRuleCounts() + $scope.updateRuleCounts() $scope.updateTagsRulesFor = (tagGroup) -> for tagRule in tagGroup.rules @@ -18,6 +19,7 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente $scope.addNewRuleTo = (tagGroup, ruleType) -> newRule = id: null + is_default: tagGroup == $scope.defaultTagGroup preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",") type: "TagRule::#{ruleType}" switch ruleType @@ -26,18 +28,28 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente when "FilterShippingMethods" newRule.peferred_shipping_method_tags = [] newRule.preferred_matched_shipping_methods_visibility = "visible" + when "FilterPaymentMethods" + newRule.peferred_payment_method_tags = [] + newRule.preferred_matched_payment_methods_visibility = "visible" + when "FilterProducts" + newRule.peferred_variant_tags = [] + newRule.preferred_matched_variants_visibility = "visible" + when "FilterOrderCycles" + newRule.peferred_exchange_tags = [] + newRule.preferred_matched_order_cycles_visibility = "visible" tagGroup.rules.push(newRule) - updateRuleCounts() + $scope.enterprise_form.$setDirty() + $scope.updateRuleCounts() $scope.addNewTag = -> - $scope.tagGroups.push { tags: [], rules: [] } + $scope.tagGroups.push { tags: [], rules: [], position: $scope.tagGroups.length + 1 } $scope.deleteTagRule = (tagGroup, tagRule) -> index = tagGroup.rules.indexOf(tagRule) return unless index >= 0 if tagRule.id is null tagGroup.rules.splice(index, 1) - updateRuleCounts() + $scope.updateRuleCounts() else if confirm("Are you sure?") $http @@ -45,4 +57,4 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json" .success -> tagGroup.rules.splice(index, 1) - updateRuleCounts() + $scope.updateRuleCounts() 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 index 54e33006e4..1d3cb02f7a 100644 --- 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 @@ -1,28 +1,24 @@ -angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, $window) -> +angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, DialogDefaults) -> restrict: 'A' - scope: true + scope: + tagGroup: '=' + addNewRuleTo: '=' 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' } + { id: "FilterProducts", name: 'Show or Hide variants in my shopfront' } + { id: "FilterShippingMethods", name: 'Show or Hide shipping methods at checkout' } + { id: "FilterPaymentMethods", name: 'Show or Hide payment methods at checkout' } + { id: "FilterOrderCycles", name: 'Show or Hide order cycles in my shopfront' } ] - scope.ruleType = "DiscountOrder" + scope.ruleType = scope.ruleTypes[0].id # 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') + template.dialog(DialogDefaults) # Link opening of dialog to click event on element element.bind 'click', (e) -> 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 deleted file mode 100644 index b374f88782..0000000000 --- a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 1a75cf8ff2..0000000000 --- a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee +++ /dev/null @@ -1,4 +0,0 @@ -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/directives/tag_rules/tag_rule.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/tag_rule.js.coffee new file mode 100644 index 0000000000..11c7ed014b --- /dev/null +++ b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/tag_rule.js.coffee @@ -0,0 +1,41 @@ +angular.module("admin.tagRules").directive "tagRule", -> + restrict: "C" + templateUrl: "admin/tag_rules/tag_rule.html" + link: (scope, element, attrs) -> + scope.opt = + "TagRule::FilterShippingMethods": + textTop: "Shipping methods tagged" + textBottom: "are:" + taggable: "shipping_method" + tagsAttr: "shipping_method_tags" + tagListAttr: "preferred_shipping_method_tags" + inputTemplate: "admin/tag_rules/filter_shipping_methods_input.html" + tagListFor: (rule) -> + rule.preferred_shipping_method_tags + "TagRule::FilterPaymentMethods": + textTop: "Payment methods tagged" + textBottom: "are:" + taggable: "payment_method" + tagsAttr: "payment_method_tags" + tagListAttr: "preferred_payment_method_tags" + inputTemplate: "admin/tag_rules/filter_payment_methods_input.html" + tagListFor: (rule) -> + rule.preferred_payment_method_tags + "TagRule::FilterOrderCycles": + textTop: "Order Cycles tagged" + textBottom: "are:" + taggable: "exchange" + tagsAttr: "exchange_tags" + tagListAttr: "preferred_exchange_tags" + inputTemplate: "admin/tag_rules/filter_order_cycles_input.html" + tagListFor: (rule) -> + rule.preferred_exchange_tags + "TagRule::FilterProducts": + textTop: "Inventory variants tagged" + textBottom: "are:" + taggable: "variant" + tagsAttr: "variant_tags" + tagListAttr: "preferred_variant_tags" + inputTemplate: "admin/tag_rules/filter_products_input.html" + tagListFor: (rule) -> + rule.preferred_variant_tags diff --git a/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee b/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee new file mode 100644 index 0000000000..b458f85179 --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee @@ -0,0 +1,17 @@ +angular.module("admin.utils").directive 'helpModal', ($compile, $templateCache, $window, DialogDefaults) -> + restrict: 'C' + scope: + template: '@' + link: (scope, element, attr) -> + # Compile modal template + template = $compile($templateCache.get(scope.template))(scope) + + # Load Dialog Options + template.dialog(DialogDefaults) + + # Link opening of dialog to click event on element + element.bind 'click', (e) -> template.dialog('open') + + scope.close = -> + template.dialog('close') + return diff --git a/app/assets/javascripts/admin/utils/directives/sortable.js.coffee b/app/assets/javascripts/admin/utils/directives/sortable.js.coffee new file mode 100644 index 0000000000..19f527bc0f --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/sortable.js.coffee @@ -0,0 +1,37 @@ +angular.module("admin.utils").directive "ofnSortable", ($timeout, $parse) -> + restrict: "E" + scope: + items: '@' + position: '@' + afterSort: '&' + handle: "@" + axis: "@" + link: (scope, element, attrs) -> + $timeout -> + scope.axis ||= "y" + scope.handle ||= ".handle" + getScopePos = $parse(scope.position) + setScopePos = getScopePos.assign + + element.sortable + handle: scope.handle + helper: 'clone' + axis: scope.axis + items: scope.items + appendTo: element + update: (event, ui) -> + scope.$apply -> + sortableSiblings = ($(ss) for ss in ui.item.siblings(scope.items)) + offset = Math.min(ui.item.index(), sortableSiblings[0].index()) + newPos = ui.item.index() - offset + 1 + oldPos = getScopePos(ui.item.scope()) + if newPos < oldPos + for sibScope in sortableSiblings.map((ss) -> ss.scope()) + pos = getScopePos(sibScope) + setScopePos(sibScope, pos + 1) if pos >= newPos && pos < oldPos + else if newPos > oldPos + for sibScope in sortableSiblings.map((ss) -> ss.scope()) + pos = getScopePos(sibScope) + setScopePos(sibScope, pos - 1) if pos > oldPos && pos <= newPos + setScopePos(ui.item.scope(), newPos) + scope.afterSort() 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 index 7a6ae9afff..9722ab26af 100644 --- a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee @@ -6,11 +6,32 @@ angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) -> tagsAttr: "@?" tagListAttr: "@?" findTags: "&" + form: '=?' + onTagAdded: "&" + onTagRemoved: "&" + max: "=" link: (scope, element, attrs) -> + scope.findTags = undefined unless attrs.hasOwnProperty("findTags") + scope.limitReached = false + + compileTagList = -> + scope.limitReached = scope.object[scope.tagsAttr].length >= scope.max if scope.max != undefined + scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",") + $timeout -> + # Initialize properties if necessary scope.tagsAttr ||= "tags" scope.tagListAttr ||= "tag_list" + scope.object[scope.tagsAttr] ||= [] + compileTagList() - watchString = "object.#{scope.tagsAttr}" - scope.$watchCollection watchString, -> - scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",") + scope.tagAdded = -> + scope.onTagAdded() + compileTagList() + + scope.tagRemoved = -> + # For some reason the tags input doesn't mark the form + # as dirty when a tag is removed, which breaks the save bar + scope.form.$setDirty(true) if typeof scope.form isnt 'undefined' + scope.onTagRemoved() + compileTagList() diff --git a/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee b/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee new file mode 100644 index 0000000000..7afb65e82d --- /dev/null +++ b/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee @@ -0,0 +1,10 @@ +angular.module("admin.utils").factory "DialogDefaults", ($window) -> + 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') diff --git a/app/assets/javascripts/admin/utils/services/info_dialog.js.coffee b/app/assets/javascripts/admin/utils/services/info_dialog.js.coffee new file mode 100644 index 0000000000..bb63eb7d20 --- /dev/null +++ b/app/assets/javascripts/admin/utils/services/info_dialog.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.customers").factory 'InfoDialog', ($rootScope, $compile, $injector, $templateCache, DialogDefaults) -> + new class InfoDialog + open: (type, message) -> + scope = $rootScope.$new() + scope.message = message + scope.dialog_class = type + template = $compile($templateCache.get('admin/info_dialog.html'))(scope) + template.dialog(DialogDefaults) + template.dialog('open') + scope.close = -> + template.dialog('close') + null diff --git a/app/assets/javascripts/admin/utils/utils.js.coffee b/app/assets/javascripts/admin/utils/utils.js.coffee index 094d3a5849..0b04480ff3 100644 --- a/app/assets/javascripts/admin/utils/utils.js.coffee +++ b/app/assets/javascripts/admin/utils/utils.js.coffee @@ -1 +1 @@ -angular.module("admin.utils", ["ngSanitize"]) \ No newline at end of file +angular.module("admin.utils", ["templates", "ngSanitize"]) \ No newline at end of file diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee index e0a67eb7d0..ecc1cdac29 100644 --- a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee @@ -6,7 +6,6 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO ngModel.$parsers.push (viewValue) -> if ngModel.$dirty && viewValue - variantOverride = VariantOverrides.inherit(scope.hub_id, scope.variant.id) - DirtyVariantOverrides.add variantOverride + DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id scope.displayDirty() viewValue diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee new file mode 100644 index 0000000000..096ffc24ff --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee @@ -0,0 +1,9 @@ +angular.module("admin.variantOverrides").directive "trackTagList", (VariantOverrides, DirtyVariantOverrides) -> + link: (scope, element, attrs) -> + watchString = "variantOverrides[#{scope.hub_id}][#{scope.variant.id}].tag_list" + scope.$watch watchString, (newValue, oldValue) -> + if typeof newValue isnt 'undefined' && newValue != oldValue + scope.inherit = false + vo_id = scope.variantOverrides[scope.hub_id][scope.variant.id].id + DirtyVariantOverrides.set scope.hub_id, scope.variant.id, vo_id, 'tag_list', newValue + scope.displayDirty() diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee index 184b7af232..db4c30dacb 100644 --- a/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee @@ -3,8 +3,8 @@ angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (D link: (scope, element, attrs, ngModel) -> ngModel.$parsers.push (viewValue) -> if ngModel.$dirty - variantOverride = scope.variantOverrides[scope.hub_id][scope.variant.id] scope.inherit = false - DirtyVariantOverrides.add variantOverride + vo_id = scope.variantOverrides[scope.hub_id][scope.variant.id].id + DirtyVariantOverrides.set scope.hub_id, scope.variant.id, vo_id, attrs.ofnTrackVariantOverride, viewValue scope.displayDirty() viewValue diff --git a/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee index 053c6cbfa1..537fb484dc 100644 --- a/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee @@ -1,10 +1,22 @@ -angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) -> +angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http, VariantOverrides) -> new class DirtyVariantOverrides dirtyVariantOverrides: {} - add: (vo) -> - @dirtyVariantOverrides[vo.hub_id] ||= {} - @dirtyVariantOverrides[vo.hub_id][vo.variant_id] = vo + add: (hub_id, variant_id, vo_id) -> + @dirtyVariantOverrides[hub_id] ||= {} + @dirtyVariantOverrides[hub_id][variant_id] ||= + { id: vo_id, variant_id: variant_id, hub_id: hub_id } + + set: (hub_id, variant_id, vo_id, attr, value) -> + if attr in @requiredAttrs() + @add(hub_id, variant_id, vo_id) + @dirtyVariantOverrides[hub_id][variant_id][attr] = value + + inherit: (hub_id, variant_id, vo_id) -> + @add(hub_id, variant_id, vo_id) + blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id)) + delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs() + @dirtyVariantOverrides[hub_id][variant_id] = blankVo count: -> count = 0 @@ -27,3 +39,6 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http url: "/admin/variant_overrides/bulk_update" data: variant_overrides: @all() + + requiredAttrs: -> + ['id','hub_id','variant_id','sku','price','count_on_hand','on_demand','default_stock','resettable','tag_list'] diff --git a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee index 4e1128572e..94a4d093eb 100644 --- a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee @@ -30,6 +30,8 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve on_demand: null default_stock: null resettable: false + tag_list: '' + tags: [] updateIds: (updatedVos) -> for vo in updatedVos diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee index 2af7ba1c16..4d875cbd33 100644 --- a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -1 +1 @@ -angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"]) +angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput']) diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index 34cae22fcc..0ab94f61a4 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -62,8 +62,11 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h shippingPrice: -> @shippingMethod()?.price || 0.0 + paymentPrice: -> + @paymentMethod()?.price || 0.0 + paymentMethod: -> PaymentMethods.payment_methods_by_id[@order.payment_method_id] cartTotal: -> - @shippingPrice() + @order.display_total + @order.display_total + @shippingPrice() + @paymentPrice() diff --git a/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee b/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee index 8b44d3e80a..423ceb57e2 100644 --- a/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee +++ b/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee @@ -4,5 +4,6 @@ Darkswarm.factory "PaymentMethods", (paymentMethods)-> payment_methods_by_id: {} constructor: -> for method in @payment_methods + method.price = parseFloat(method.price) @payment_methods_by_id[method.id] = method diff --git a/app/assets/javascripts/templates/admin/info_dialog.html.haml b/app/assets/javascripts/templates/admin/info_dialog.html.haml new file mode 100644 index 0000000000..2755715e46 --- /dev/null +++ b/app/assets/javascripts/templates/admin/info_dialog.html.haml @@ -0,0 +1,9 @@ +#info-dialog{ ng: { class: "dialog_class" } } + .message.clearfix.margin-bottom-30 + .icon.text-center + %i.icon-exclamation-sign + .text + {{ message }} + .action-buttons.text-center + %button{ ng: { click: "close()" } } + OK diff --git a/app/assets/javascripts/templates/admin/links_dropdown.html.haml b/app/assets/javascripts/templates/admin/links_dropdown.html.haml index 1f44f2418c..6d0ba060e2 100644 --- a/app/assets/javascripts/templates/admin/links_dropdown.html.haml +++ b/app/assets/javascripts/templates/admin/links_dropdown.html.haml @@ -3,8 +3,8 @@ %i.icon-check Actions %i{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded", style: 'width: 200px' } - %a.menu_item{ 'ng-repeat' => "link in links", href: '{{link.url}}', target: "{{link.target || '_self'}}", data: { method: "{{ link.method || 'get' }}", confirm: "{{link.confirm}}" }, style: 'display: inline-block; width: 100%' } - %span{ :style => 'text-align: center; display: inline-block; width: 20%'} + %div.menu{ 'ng-show' => "expanded" } + %a.menu_item{ 'ng-repeat' => "link in links", href: '{{link.url}}', target: "{{link.target || '_self'}}", data: { method: "{{ link.method || 'get' }}", confirm: "{{link.confirm}}" } } + %span %i{ ng: { class: "link.icon" } } - %span{ style: "display: inline-block; width: auto"} {{ link.name }} + %span {{ link.name }} diff --git a/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml b/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml new file mode 100644 index 0000000000..6a0d720a5f --- /dev/null +++ b/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml @@ -0,0 +1,18 @@ +#tag-rule-help + .margin-bottom-30.text-center + .text-big Tag Rules + + .margin-bottom-30 + .text-normal Overview + %p Tag rules provide a way to describe which items are visible or otherwise to which customers. Items can be Shipping Methods, Payment Methods, Products and Order Cycles. + + .margin-bottom-30 + .text-normal 'By Default...' Rules + %p Default rules allow you to hide items so that they are not visible by default. This behaviour can then be overriden by non-default rules for customers with particular tags. + + .margin-bottom-30 + .text-normal 'Customers Tagged...' Rules + %p By creating rules related to a specific customer tag, you can override the default behaviour (whether it be to show or to hide items) for customers with the specified tag. + + .text-center + %input.button.red.icon-plus{ type: 'button', value: 'Got it', ng: { click: 'close()' } } diff --git a/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml index e30cfcd607..525e777eac 100644 --- a/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml +++ b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml @@ -2,14 +2,14 @@ .text-normal.margin-bottom-30.text-center = t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:") - %form{ name: 'new_customer_form', novalidate: true } + %form{ name: 'new_customer_form', novalidate: true, ng: { submit: "addCustomer()" }} .text-center.margin-bottom-30 %input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } } - %div{ ng: { show: "email == submitted" } } + %div{ ng: { show: "submitted && new_customer_form.$pristine" } } .error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } } = t('admin.customers.index.valid_email_error') .error{ ng: { repeat: "error in errors", bind: "error" } } .text-center - %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } } + %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer') } diff --git a/app/assets/javascripts/templates/admin/panel.html.haml b/app/assets/javascripts/templates/admin/panel.html.haml index be0c98109f..4d3780aed0 100644 --- a/app/assets/javascripts/templates/admin/panel.html.haml +++ b/app/assets/javascripts/templates/admin/panel.html.haml @@ -1,2 +1,2 @@ -%td{ colspan: "{{columnCount}}" } +%td{ colspan: "{{columnCount}}", ng: { if: "template" } } .panel{ ng: { include: "template" } } diff --git a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml new file mode 100644 index 0000000000..4c6a86e926 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml @@ -0,0 +1,31 @@ +.row.exchange-distributed-products + .sixteen.columns.alpha.omega + .exchange-select-all-variants + %label + %input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', + value: 1, + 'ng-model' => 'exchange.select_all_variants', + 'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)', + 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants' } + Select all + + .exchange-products + -# Scope product list based on permissions the current user has to view variants in this exchange + .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' } + .exchange-product-details + %label + -# MASTER_VARIANTS: No longer required + -# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', + -# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' + %img{'ng-src' => '{{ product.image_url }}'} + .name {{ product.name }} + .supplier {{ product.supplier_name }} + + .exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'} + %label + %input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + value: 1, + 'ng-model' => 'exchange.variants[variant.id]', + 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + 'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' } + {{ variant.label }} diff --git a/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml new file mode 100644 index 0000000000..04e191fcf0 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml @@ -0,0 +1,49 @@ +.row.exchange-supplied-products + .sixteen.columns.alpha.omega + .exchange-select-all-variants + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants', + value: 1, + 'ng-model' => 'exchange.select_all_variants', + 'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)', + 'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' } + Select all + + .exchange-products + -# No need to scope product list based on permissions, because if an incoming exchange is visible, + -# then all of the variants within it should be visible. May change in the future? + .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'} + + .exchange-product-details + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', + value: 1, + 'ng-hide' => 'product.variants.length > 0', + 'ng-model' => 'exchange.variants[product.master_id]', + 'ofn-sync-distributions' => '{{ product.master_id }}', + 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', + 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' } + %img{'ng-src' => '{{ product.image_url }}'} + {{ product.name }} + + -# When the master variant is in the order cycle but the product has variants, we want to + -# be able to remove the master variant, since it serves no purpose. Display a checkbox to do so. + .exchange-product-variant{'ng-show' => 'exchange.variants[product.master_id] && product.variants.length > 0'} + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', + value: 1, + 'ng-model' => 'exchange.variants[product.master_id]', + 'ofn-sync-distributions' => '{{ product.master_id }}', + 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', + 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' } + Obsolete master + + .exchange-product-variant{'ng-repeat' => 'variant in product.variants'} + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + value: 1, + 'ng-model' => 'exchange.variants[variant.id]', + 'ofn-sync-distributions' => '{{ variant.id }}', + 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' } + {{ variant.label }} diff --git a/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml new file mode 100644 index 0000000000..0963c480c0 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml @@ -0,0 +1,5 @@ +.row.exchange-tags + .sixteen.columns.alpha.omega + %span.text-normal Tags + %br + %tags-with-translation.fullwidth{ object: 'object' } 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 deleted file mode 100644 index 358d9ce1a6..0000000000 --- a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml +++ /dev/null @@ -1,37 +0,0 @@ -%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/discount_order_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/discount_order_input.html.haml new file mode 100644 index 0000000000..abad41ea62 --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/discount_order_input.html.haml @@ -0,0 +1,7 @@ +%div + %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_order_cycles_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_order_cycles_input.html.haml new file mode 100644 index 0000000000..fd8dac63c7 --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/filter_order_cycles_input.html.haml @@ -0,0 +1,10 @@ +%div + %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", + ng: { model: "rule.preferred_matched_order_cycles_visibility", if: "!rule.is_default" }, + data: 'visibilityOptions', "min-search" => 5 } + %input{ type: "hidden", + id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]", + ng: { value: "'hidden'", if: "rule.is_default" } } + %span.text-normal{ ng: { if: "rule.is_default" } } not visible diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml new file mode 100644 index 0000000000..0dd533235d --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml @@ -0,0 +1,10 @@ +%div + %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", + ng: { model: "rule.preferred_matched_payment_methods_visibility", if: "!rule.is_default" }, + data: 'visibilityOptions', "min-search" => 5 } + %input{ type: "hidden", + id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]", + ng: { value: "'hidden'", if: "rule.is_default" } } + %span.text-normal{ ng: { if: "rule.is_default" } } not visible diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml new file mode 100644 index 0000000000..da112c9db2 --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml @@ -0,0 +1,10 @@ +%div + %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", + ng: { model: "rule.preferred_matched_variants_visibility", if: "!rule.is_default" }, + data: 'visibilityOptions', "min-search" => 5 } + %input{ type: "hidden", + id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]", + ng: { value: "'hidden'", if: "rule.is_default" } } + %span.text-normal{ ng: { if: "rule.is_default" } } not visible 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 deleted file mode 100644 index 6552d834b5..0000000000 --- a/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -%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/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml new file mode 100644 index 0000000000..a46b9abd14 --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml @@ -0,0 +1,10 @@ +%div + %input.fullwidth.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", if: "!rule.is_default" }, + data: 'visibilityOptions', "min-search" => 5 } + %input{ type: "hidden", + 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: { value: "'hidden'", if: "rule.is_default" } } + %span.text-normal{ ng: { if: "rule.is_default" } } not visible diff --git a/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml b/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml new file mode 100644 index 0000000000..7907b9dfda --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml @@ -0,0 +1,50 @@ +%div{ id: "tr_{{tagGroup.startIndex + $index}}" } + %table + %colgroup + %col.text{ width: "35%" } + %col.inputs{ width: "55%" } + %col.actions{ width: "10%" } + %tr + %td + %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]", + ng: { value: "rule.type" } } + + %input{ type: "hidden", + id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_priority", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][priority]", + ng: { value: "tagGroup.startIndex + $index" } } + + %input{ type: "hidden", + id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_is_default", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][is_default]", + ng: { value: "rule.is_default" } } + + %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_{{opt[rule.type].taggable}}_tags", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_{{opt[rule.type].taggable}}_tags]", + ng: { value: "opt[rule.type].tagListFor(rule)" } } + + %span.text-normal {{ opt[rule.type].textTop }} + %td + %tags-with-translation{ object: "rule", max: 1, "tags-attr" => "{{opt[rule.type].tagsAttr}}", "tag-list-attr" => "{{opt[rule.type].tagListAttr}}" } + %td.actions{ rowspan: 2 } + %a{ ng: { click: "deleteTagRule(tagGroup || defaultTagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" } + %tr + %td + %span.text-normal {{ opt[rule.type].textBottom }} + %td + %div{ ng: { include: "opt[rule.type].inputTemplate"} } + + %hr diff --git a/app/assets/javascripts/templates/admin/tags_input.html.haml b/app/assets/javascripts/templates/admin/tags_input.html.haml index 2dd75ce5ac..3c604b4b15 100644 --- a/app/assets/javascripts/templates/admin/tags_input.html.haml +++ b/app/assets/javascripts/templates/admin/tags_input.html.haml @@ -1,5 +1,7 @@ -%tags-input{ template: 'admin/tag.html', ng: { model: 'object[tagsAttr]' } } - %auto-complete{source: "findTags({query: $query})", +%tags-input{ template: 'admin/tag.html', + ng: { model: 'object[tagsAttr]', class: "{'limit-reached': limitReached}"}, + on: { tag: { added: 'tagAdded()', removed:'tagRemoved()' } } } + %auto-complete{ ng: { if: "findTags" }, source: "findTags({query: $query})", template: "admin/tag_autocomplete.html", "min-length" => "0", "load-on-focus" => "true", diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml index d1d208c5c4..46a37050e2 100644 --- a/app/assets/javascripts/templates/partials/enterprise_details.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml @@ -1,26 +1,18 @@ .row .small-12.large-8.columns - / TODO: Rob add logic for taxons and properties too: - / %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"} %div %p.modal-header {{'label_about' | t}} - / TODO: Rob - add in taxons and properties and property pop-overs + %div + %span.filter-shopfront.taxon-selectors + %ul.inline-block + %li{"ng-repeat" => "taxon in enterprise.supplied_taxons"} + %a.button.tiny.disabled{"ng-bind" => "taxon.name"} + + %span.filter-shopfront.property-selectors.pad-top + %ul.inline-block + %li{"ng-repeat" => "property in enterprise.properties"} + %a.button.tiny{"ng-bind" => "property.presentation"} - -# TODO: Add producer taxons and properties here - -# %div - -# %span.filter-shopfront.taxon-selectors - -# %ul.inline-block - -# %li - -# %a.button.tiny.disabled Grains - -# %li - -# %a.button.tiny.disabled Dairy - -# - -# %span.filter-shopfront.property-selectors.pad-top - -# %ul.inline-block - -# %li - -# %a.button.tiny Organic certified - -# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/ - -# .about-container.pad-top %img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"} %p.text-small{"ng-bind-html" => "::enterprise.long_description"} diff --git a/app/assets/stylesheets/admin/components/info_dialog.css.scss b/app/assets/stylesheets/admin/components/info_dialog.css.scss new file mode 100644 index 0000000000..d8295c6813 --- /dev/null +++ b/app/assets/stylesheets/admin/components/info_dialog.css.scss @@ -0,0 +1,28 @@ +#info-dialog { + .message { + .text, .icon { + position: relative; + float:left; + display: inline; + } + + .text { + padding-top: 10px; + width: 80%; + font-size: 1rem; + } + + .icon { + width: 20%; + font-size: 2rem; + } + } + + &.error { + .message { + .icon { + color: #da5354; + } + } + } +} diff --git a/app/assets/stylesheets/admin/components/jquery_dialog.scss b/app/assets/stylesheets/admin/components/jquery_dialog.scss index 2e36db6e33..f3fe08da60 100644 --- a/app/assets/stylesheets/admin/components/jquery_dialog.scss +++ b/app/assets/stylesheets/admin/components/jquery_dialog.scss @@ -4,8 +4,8 @@ dark: #545454 light: #ccc */ .ui-dialog { - border: 2px solid #4a4a4a; - border-radius:3px; + border: 0px solid #4a4a4a; + border-radius:5px; padding:0px; -moz-box-shadow: 3px 3px 4px #797979; -webkit-box-shadow: 3px 3px 4px #797979; @@ -18,10 +18,6 @@ light: #ccc 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{ @@ -34,17 +30,17 @@ light: #ccc background-image: none; background-color: #ffffff; border:0px; - border-radius: 3px; + border-radius: 8px; padding: 0px 5px 0px 5px; } .ui-dialog .ui-widget-content{ border: none; - border-radius: 3px; + border-radius: 8px; padding: 0px 50px 30px 50px; } .ui-dialog .ui-corner-all{ - border-radius:0px; + border-radius: 8px; } .ui-dialog { .ui-state-hover, .ui-state-focus{ @@ -83,6 +79,6 @@ light: #ccc } .ui-widget-overlay { - background: #e9e9e9; - opacity: 0.6; + background: #000000; + opacity: 0.5; } diff --git a/app/assets/stylesheets/admin/dialog.css.scss b/app/assets/stylesheets/admin/dialog.css.scss new file mode 100644 index 0000000000..832270d07d --- /dev/null +++ b/app/assets/stylesheets/admin/dialog.css.scss @@ -0,0 +1,5 @@ +.ui-dialog { + p { + line-height: 2; + } +} diff --git a/app/assets/stylesheets/admin/index_panels.css.scss b/app/assets/stylesheets/admin/index_panels.css.scss index a910e41d14..b622212446 100644 --- a/app/assets/stylesheets/admin/index_panels.css.scss +++ b/app/assets/stylesheets/admin/index_panels.css.scss @@ -1,4 +1,4 @@ -tr.panel-toggle-row { +tbody.panel-ctrl { td.panel-toggle{ -webkit-touch-callout: none; -webkit-user-select: none; @@ -65,64 +65,60 @@ tr.panel-toggle-row { } &.expanded{ - td { - border-bottom: 2px solid #444444; + >tr:not(.panel-row) { + >td { + border-bottom: 1px solid #6788a2; - &.selected { - background-color: #ffffff; - border-left: 2px solid #444444; - border-right: 2px solid #444444; - border-top: 2px solid #444444; - border-bottom: none; - - &:hover { + &.selected { background-color: #ffffff; - } + border-left: 1px solid #6788a2; + border-right: 1px solid #6788a2; + border-top: 1px solid #6788a2; + border-bottom: none; - * { - color: #1b3c56; - } + &:hover { + background-color: #ffffff; + } - i.icon-status::before { - opacity: 1.0; - } + * { + color: #1b3c56; + } - i.icon-chevron::before { - content: "\f077"; - } - } - } - } -} - -tr.panel-row { - display: none; - - &:hover { - td { - background-color: #ffffff; - } - } - - >td { - border-color: #444444; - padding: 0; - .panel { - border-left: 1px solid #444444; - border-right: 1px solid #444444; - border-bottom: 1px solid #444444; - - .row{ - margin: 0px -4px; - - padding: 20px 0px; - - .column.alpha, .columns.alpha { - padding-left: 20px; - } - - .column.omega, .columns.omega { - padding-right: 20px; + i.icon-status::before { + opacity: 1.0; + } + + i.icon-chevron::before { + content: "\f077"; + } + } + } + } + } + + tr.panel-row { + &:hover { + td { + background-color: #ffffff; + } + } + + >td { + border-color: #6788a2; + padding: 0; + .panel { + .row{ + margin: 0px -4px; + + padding: 20px 0px; + + .column.alpha, .columns.alpha { + padding-left: 20px; + } + + .column.omega, .columns.omega { + padding-right: 20px; + } } } } diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 8d77015af7..1532ea8520 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -79,10 +79,6 @@ form.order_cycle { width: 20px; } - tr.supplier td { - border-bottom: 2px solid #C3D9FF; - } - .exchange-select-all-variants { clear: both; margin: 5px; diff --git a/app/assets/stylesheets/admin/tag_rules.css.scss b/app/assets/stylesheets/admin/tag_rules.css.scss index 028ad6ce1c..05decd6027 100644 --- a/app/assets/stylesheets/admin/tag_rules.css.scss +++ b/app/assets/stylesheets/admin/tag_rules.css.scss @@ -1,3 +1,11 @@ +tags-input { + &.limit-reached { + input, span.input { + display: none; + } + } +} + .no_tags { margin-bottom: 40px; color: #aeaeae; @@ -6,6 +14,13 @@ } .customer_tag { + .header { + cursor: move; + } +} + +.customer_tag, .default_rules { + background-color: #ffffff; border: 1px solid #cee1f4; margin-bottom: 40px; @@ -20,6 +35,9 @@ tr { td { border: none; + h5 { + display: inline-block; + } } } } @@ -33,22 +51,30 @@ font-weight: bold; } - table { - padding: 0px; - margin: 0px 0px 10px 0px; - - tr.tag_rule { - border: none; + .tag_rule { + table { padding: 0px; - margin: 0px; + margin: 0px 0px 10px 0px; - td { + &:hover { + td { + background-color: #ebf3fb; + } + } + + tr { border: none; - padding: 4px 10px 10px 10px; + padding: 0px; margin: 0px; - input { - width: auto; + td { + border: none; + padding: 2px 10px 2px 10px; + margin: 0px; + + input { + width: auto; + } } } } diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.sass b/app/assets/stylesheets/darkswarm/producer_node.css.sass index 7bac2dde6d..24eb0467c2 100644 --- a/app/assets/stylesheets/darkswarm/producer_node.css.sass +++ b/app/assets/stylesheets/darkswarm/producer_node.css.sass @@ -5,7 +5,7 @@ .active_table .active_table_node // Header row - @media all and (max-width: 640px) + @media all and (max-width: 640px) .skinny-head background-color: $clr-turquoise-light @include border-radius-mixed(0.5em, 0.5em, 0, 0) @@ -16,7 +16,7 @@ .follow-icons &, & * font-size: 1.5rem - + // Producer icons i.ofn-i_059-producer, i.ofn-i_060-producer-reversed @@ -41,11 +41,11 @@ &:hover, &:focus, &:active &.secondary color: #666 - .hub-name, .button-address + .hub-name, .button-address border-bottom: 1px solid #999 &.primary color: $clr-brick-bright - .hub-name, .button-address + .hub-name, .button-address border-bottom: 1px solid $clr-brick-bright p.word-wrap @@ -56,6 +56,10 @@ .fat-taxons background-color: $clr-turquoise-light + .fat-properties + background-color: $clr-turquoise-ultra-light + border: 1px solid $clr-turquoise-light + .producer-name color: $clr-turquoise @@ -72,7 +76,7 @@ max-height: 160px width: auto &.left - padding: 0.25rem 1rem 0.25rem 0 + padding: 0.25rem 1rem 0.25rem 0 &.right padding: 0.25rem 0.5rem 0.25rem 2rem @@ -87,10 +91,7 @@ &.closed .active_table_row.closed border: 1px solid transparent - @media all and (max-width: 640px) + @media all and (max-width: 640px) border-color: $clr-turquoise-light &:hover, &:active, &:focus border-color: $clr-turquoise - - - diff --git a/app/assets/stylesheets/darkswarm/taxons.css.sass b/app/assets/stylesheets/darkswarm/taxons.css.sass index 4f23a8bc3a..1d1e5b652f 100644 --- a/app/assets/stylesheets/darkswarm/taxons.css.sass +++ b/app/assets/stylesheets/darkswarm/taxons.css.sass @@ -1,7 +1,7 @@ @import branding @import mixins -.fat-taxons +.fat-taxons, .fat-properties display: inline-block line-height: 1 margin-right: 0.5rem @@ -29,7 +29,7 @@ svg path fill: $disabled-dark - + .product-header render-svg svg diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index fe975aa038..c1f5f6bf9a 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -3,6 +3,14 @@ module Admin before_filter :load_managed_shops, only: :index, if: :html_request? respond_to :json + respond_override update: { json: { + success: lambda { + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise)) + render_as_json @customer, tag_rule_mapping: tag_rule_mapping + }, + failure: lambda { render json: { errors: @customer.errors.full_messages }, status: :unprocessable_entity } + } } + def index respond_to do |format| format.html @@ -40,6 +48,7 @@ module Admin invoke_callbacks(:destroy, :fails) respond_with(@object) do |format| format.html { redirect_to location_after_destroy } + format.json { render json: { errors: @object.errors.full_messages }, status: :conflict } end end end diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 9d2c3cff85..58bf679dc3 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -1,3 +1,5 @@ +require 'open_food_network/tag_rule_applicator' + class BaseController < ApplicationController include Spree::Core::ControllerHelpers include Spree::Core::ControllerHelpers::RespondWith @@ -19,6 +21,9 @@ class BaseController < ApplicationController @order_cycles = OrderCycle.with_distributor(@distributor).active .order(@distributor.preferred_shopfront_order_cycle_order) + applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor, "FilterOrderCycles", current_customer.andand.tag_list) + applicator.filter!(@order_cycles) + # And default to the only order cycle if there's only the one if @order_cycles.count == 1 current_order(true).set_order_cycle! @order_cycles.first diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index fe2eda8bfd..7f627ec73a 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -40,15 +40,30 @@ class EnterprisesController < BaseController distributor = Enterprise.is_distributor.find_by_permalink(params[:id]) || Enterprise.is_distributor.find(params[:id]) order = current_order(true) + reset_distributor(order, distributor) + + reset_user_and_customer(order) if try_spree_current_user + + reset_order_cycle(order, distributor) + + order.save! + end + + def reset_distributor(order, distributor) if order.distributor && order.distributor != distributor order.empty! order.set_order_cycle! nil end - order.distributor = distributor + end + def reset_user_and_customer(order) + order.associate_user!(spree_current_user) if order.user.blank? || order.email.blank? + order.send(:associate_customer) if order.customer.nil? # Only associates existing customers + end + + def reset_order_cycle(order, distributor) order_cycle_options = OrderCycle.active.with_distributor(distributor) order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1 - order.save! end end diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 55b6bcc02b..091a501ca5 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -11,7 +11,11 @@ class ShopController < BaseController def products begin - products_json = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor, current_order_cycle).products_json + renderer = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor, current_order_cycle) + + # If we add any more filtering logic, we should probably + # move it all to a lib class like 'CachedProductsFilterer' + products_json = filter(renderer.products_json) render json: products_json @@ -33,4 +37,24 @@ class ShopController < BaseController end end + private + + def filtered_json(products_json) + if applicator.send(:rules).any? + filter(products_json) + else + products_json + end + end + + def filter(products_json) + products_hash = JSON.parse(products_json) + applicator.filter!(products_hash) + JSON.unparse(products_hash) + end + + def applicator + return @applicator unless @applicator.nil? + @applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterProducts", current_customer.andand.tag_list) + end end diff --git a/app/controllers/spree/admin/payment_methods_controller_decorator.rb b/app/controllers/spree/admin/payment_methods_controller_decorator.rb index 963b61fa81..fc15324d0b 100644 --- a/app/controllers/spree/admin/payment_methods_controller_decorator.rb +++ b/app/controllers/spree/admin/payment_methods_controller_decorator.rb @@ -57,6 +57,7 @@ module Spree else @providers = Gateway.providers.reject{ |p| p.name.include? "Bogus" }.sort{|p1, p2| p1.name <=> p2.name } end + @calculators = PaymentMethod.calculators.sort_by(&:name) end def load_hubs diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 079f93d41f..67105b6b34 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -23,6 +23,10 @@ module Admin admin_inject_json_ams_array "admin.paymentMethods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer end + def admin_inject_payment_method + admin_inject_json_ams "admin.paymentMethods", "paymentMethod", @payment_method, Api::Admin::PaymentMethodSerializer + end + def admin_inject_shipping_methods admin_inject_json_ams_array "admin.shippingMethods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer end diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb index 080db9c0e8..66d98d7862 100644 --- a/app/helpers/checkout_helper.rb +++ b/app/helpers/checkout_helper.rb @@ -94,4 +94,13 @@ module CheckoutHelper current_order.tokenized_permission.save! session[:access_token] = token end + + def payment_method_price(method, order) + price = method.compute_amount(order) + if price == 0 + t('checkout_method_free') + else + "{{ #{price} | localizeCurrency }}" + end + end end diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb index 93b7c96f95..cc2b966b01 100644 --- a/app/helpers/enterprises_helper.rb +++ b/app/helpers/enterprises_helper.rb @@ -3,14 +3,31 @@ module EnterprisesHelper @current_distributor ||= current_order(false).andand.distributor end + def current_customer + return nil unless spree_current_user && current_distributor + @current_customer ||= spree_current_user.customer_of(current_distributor) + end + def available_shipping_methods + return [] unless current_distributor.present? shipping_methods = current_distributor.shipping_methods - if current_distributor.present? - current_distributor.apply_tag_rules_to(shipping_methods, customer: current_order.customer) - end + + applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterShippingMethods", current_customer.andand.tag_list) + applicator.filter!(shipping_methods) + shipping_methods.uniq end + def available_payment_methods + return [] unless current_distributor.present? + payment_methods = current_distributor.payment_methods.available(:front_end).all + + applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterPaymentMethods", current_customer.andand.tag_list) + applicator.filter!(payment_methods) + + payment_methods + end + def managed_enterprises Enterprise.managed_by(spree_current_user) end diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index c6be5dc63e..50b9c93fca 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -23,8 +23,8 @@ module InjectionHelper end def inject_available_payment_methods - inject_json_ams "paymentMethods", current_order.available_payment_methods, - Api::PaymentMethodSerializer + inject_json_ams "paymentMethods", available_payment_methods, + Api::PaymentMethodSerializer, current_order: current_order end def inject_taxons diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 30fad48f58..102ac60417 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -33,7 +33,7 @@ class ProducerMailer < Spree::BaseMailer joins(:order => :order_cycle, :variant => :product). where('order_cycles.id = ?', order_cycle). merge(Spree::Product.in_supplier(producer)). - merge(Spree::Order.complete) + merge(Spree::Order.by_state('complete')) end def total_from_line_items(line_items) diff --git a/app/models/customer.rb b/app/models/customer.rb index 34f62a6aa6..c208c619ba 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -3,10 +3,13 @@ class Customer < ActiveRecord::Base belongs_to :enterprise belongs_to :user, class_name: Spree.user_class + has_many :orders, class_name: Spree::Order + before_destroy :check_for_orders before_validation :downcase_email + before_validation :empty_code - validates :code, uniqueness: { scope: :enterprise_id, allow_blank: true, allow_nil: true } + validates :code, uniqueness: { scope: :enterprise_id, 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 @@ -20,7 +23,17 @@ class Customer < ActiveRecord::Base email.andand.downcase! end + def empty_code + self.code = nil if code.blank? + end + def associate_user self.user = user || Spree::User.find_by_email(email) end + + def check_for_orders + return true unless orders.any? + errors[:base] << "Delete failed: customer has associated orders" + false + end end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 9f839f9d08..554529d7f2 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -344,13 +344,6 @@ 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/exchange.rb b/app/models/exchange.rb index ad290f6c26..3d0e6417e6 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -1,4 +1,6 @@ class Exchange < ActiveRecord::Base + acts_as_taggable + belongs_to :order_cycle belongs_to :sender, :class_name => 'Enterprise' belongs_to :receiver, :class_name => 'Enterprise' @@ -58,6 +60,7 @@ class Exchange < ActiveRecord::Base exchange.order_cycle = new_order_cycle exchange.enterprise_fee_ids = self.enterprise_fee_ids exchange.variant_ids = self.variant_ids + exchange.tag_ids = self.tag_ids exchange.save! exchange end diff --git a/app/models/spree/adjustment_decorator.rb b/app/models/spree/adjustment_decorator.rb index 0d1bc941f6..c8529fc577 100644 --- a/app/models/spree/adjustment_decorator.rb +++ b/app/models/spree/adjustment_decorator.rb @@ -12,6 +12,7 @@ module Spree scope :with_tax, where('spree_adjustments.included_tax > 0') scope :without_tax, where('spree_adjustments.included_tax = 0') + scope :payment_fee, where(originator_type: 'Spree::PaymentMethod') attr_accessible :included_tax diff --git a/app/models/spree/gateway/pay_pal_express_decorator.rb b/app/models/spree/gateway/pay_pal_express_decorator.rb new file mode 100644 index 0000000000..dc8239a107 --- /dev/null +++ b/app/models/spree/gateway/pay_pal_express_decorator.rb @@ -0,0 +1,7 @@ +module Spree + class Gateway::PayPalExpress < Gateway + # Something odd is happening with class inheritance here, this class (defined in spree_paypal_express gem) + # doesn't seem to pick up attr_accessible from the Gateway class, so we redefine the attrs we need here + attr_accessible :tag_list + end +end diff --git a/app/models/spree/gateway_decorator.rb b/app/models/spree/gateway_decorator.rb index 3c6bb2638d..9125ef1d2d 100644 --- a/app/models/spree/gateway_decorator.rb +++ b/app/models/spree/gateway_decorator.rb @@ -1,4 +1,5 @@ Spree::Gateway.class_eval do + acts_as_taggable # Due to class load order, when config.cache_classes is enabled (ie. staging and production # environments), this association isn't inherited from PaymentMethod. As a result, creating @@ -12,4 +13,6 @@ Spree::Gateway.class_eval do # Default to live preference :server, :string, :default => 'live' preference :test_mode, :boolean, :default => false + + attr_accessible :tag_list end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index a06a34bcd0..a4995274b3 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -1,6 +1,7 @@ require 'open_food_network/enterprise_fee_calculator' require 'open_food_network/distribution_change_validator' require 'open_food_network/feature_toggle' +require 'open_food_network/tag_rule_applicator' ActiveSupport::Notifications.subscribe('spree.order.contents_changed') do |name, start, finish, id, payload| payload[:order].reload.update_distribution_charge! @@ -178,10 +179,6 @@ 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 @@ -206,11 +203,8 @@ Spree::Order.class_eval do adjustments.eligible.where("originator_type = ? AND source_type != ?", 'EnterpriseFee', 'Spree::LineItem').sum(&:amount) end - # Show payment methods for this distributor - def available_payment_methods - @available_payment_methods ||= Spree::PaymentMethod.available(:front_end).select do |pm| - (self.distributor && (pm.distributors.include? self.distributor)) - end + def payment_fee + adjustments.payment_fee.map(&:amount).sum end # Does this order have shipments that can be shipped? @@ -225,10 +219,6 @@ Spree::Order.class_eval do end end - def available_shipping_methods(display_on = nil) - Spree::ShippingMethod.all_available(self, display_on) - end - def shipping_tax adjustments(:reload).shipping.sum &:included_tax end diff --git a/app/models/spree/payment_decorator.rb b/app/models/spree/payment_decorator.rb index 1a80f0269f..d178e1de54 100644 --- a/app/models/spree/payment_decorator.rb +++ b/app/models/spree/payment_decorator.rb @@ -1,5 +1,33 @@ module Spree Payment.class_eval do + has_one :adjustment, as: :source, dependent: :destroy + + after_save :ensure_correct_adjustment, :update_order + + def ensure_correct_adjustment + if adjustment + adjustment.originator = payment_method + adjustment.label = adjustment_label + adjustment.save + else + payment_method.create_adjustment(adjustment_label, order, self, true) + reload + end + end + + def adjustment_label + I18n.t('payment_method_fee') + end + + # This is called by the calculator of a payment method + def line_items + if order.complete? && Spree::Config[:track_inventory_levels] + order.line_items.select { |li| inventory_units.pluck(:variant_id).include?(li.variant_id) } + else + order.line_items + end + end + # Pin payments lacks void and credit methods, but it does have refund # Here we swap credit out for refund and remove void as a possible action def actions_with_pin_payment_adaptations diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index 915eb65ae8..77e22c591f 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -1,8 +1,14 @@ Spree::PaymentMethod.class_eval do + acts_as_taggable + # See gateway_decorator.rb when modifying this association has_and_belongs_to_many :distributors, join_table: 'distributors_payment_methods', :class_name => 'Enterprise', association_foreign_key: 'distributor_id' - attr_accessible :distributor_ids + attr_accessible :distributor_ids, :tag_list + + calculated_adjustments + + after_initialize :init validates :distributors, presence: { message: "^At least one hub must be selected" } @@ -31,6 +37,11 @@ Spree::PaymentMethod.class_eval do where('spree_payment_methods.environment=? OR spree_payment_methods.environment=? OR spree_payment_methods.environment IS NULL', Rails.env, '') } + def init + self.class.calculated_adjustments unless reflections.keys.include? :calculator + self.calculator ||= Spree::Calculator::FlatRate.new(preferred_amount: 0) + end + def has_distributor?(distributor) self.distributors.include?(distributor) end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index e6acd440c7..f8c177ceac 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -1,4 +1,5 @@ require 'open_food_network/permalink_generator' +require 'open_food_network/property_merge' Spree::Product.class_eval do include PermalinkGenerator @@ -128,11 +129,7 @@ Spree::Product.class_eval do ps = product_properties.all if inherits_properties - supplier.producer_properties.each do |producer_property| - unless ps.find { |product_property| product_property.property.presentation == producer_property.property.presentation } - ps << producer_property - end - end + ps = OpenFoodNetwork::PropertyMerge.merge(ps, supplier.producer_properties) end ps. diff --git a/app/models/spree/property_decorator.rb b/app/models/spree/property_decorator.rb index 5b8e53338c..f6eaefc75f 100644 --- a/app/models/spree/property_decorator.rb +++ b/app/models/spree/property_decorator.rb @@ -1,5 +1,11 @@ module Spree Property.class_eval do + scope :applied_by, ->(enterprise) { + select('DISTINCT spree_properties.*'). + joins(:product_properties). + where('spree_product_properties.product_id IN (?)', enterprise.supplied_product_ids) + } + after_save :refresh_products_cache # When a Property is destroyed, dependent-destroy will destroy all ProductProperties, diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb index a1e07cc53e..a051de98fa 100644 --- a/app/models/spree/taxon_decorator.rb +++ b/app/models/spree/taxon_decorator.rb @@ -24,10 +24,9 @@ Spree::Taxon.class_eval do joins(:products => :supplier). select('spree_taxons.*, enterprises.id AS enterprise_id'). each do |t| - - taxons[t.enterprise_id.to_i] ||= Set.new - taxons[t.enterprise_id.to_i] << t.id - end + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end taxons end @@ -43,10 +42,9 @@ Spree::Taxon.class_eval do where('o_exchanges.incoming = ?', false). select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id'). each do |t| - - taxons[t.enterprise_id.to_i] ||= Set.new - taxons[t.enterprise_id.to_i] << t.id - end + taxons[t.enterprise_id.to_i] ||= Set.new + taxons[t.enterprise_id.to_i] << t.id + end taxons end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index afd32bd294..562b0d10ca 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -38,7 +38,8 @@ Spree.user_class.class_eval do end def customer_of(enterprise) - customers.of(enterprise).first + return nil unless enterprise + customers.find_by_enterprise_id(enterprise) end def send_signup_confirmation diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb index 8d385673db..a5f2d93dee 100644 --- a/app/models/tag_rule.rb +++ b/app/models/tag_rule.rb @@ -1,30 +1,15 @@ 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 + attr_accessible :enterprise, :enterprise_id, :is_default, :priority + attr_accessible :preferred_customer_tags - scope :for, lambda { |enterprises| where(enterprise_id: enterprises) } - - 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 + scope :for, ->(enterprise) { where(enterprise_id: enterprise) } + scope :prioritised, -> { order('priority ASC') } def self.mapping_for(enterprises) self.for(enterprises).inject({}) do |mapping, rule| @@ -38,20 +23,4 @@ class TagRule < ActiveRecord::Base mapping 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/filter_order_cycles.rb b/app/models/tag_rule/filter_order_cycles.rb new file mode 100644 index 0000000000..de626ac70a --- /dev/null +++ b/app/models/tag_rule/filter_order_cycles.rb @@ -0,0 +1,22 @@ +class TagRule::FilterOrderCycles < TagRule + preference :matched_order_cycles_visibility, :string, default: "visible" + preference :exchange_tags, :string, default: "" + + attr_accessible :preferred_matched_order_cycles_visibility, :preferred_exchange_tags + + def tags_match?(order_cycle) + exchange_tags = exchange_for(order_cycle).andand.tag_list || [] + preferred_tags = preferred_exchange_tags.split(",") + ( exchange_tags & preferred_tags ).any? + end + + def reject_matched? + preferred_matched_order_cycles_visibility != "visible" + end + + private + + def exchange_for(order_cycle) + order_cycle.exchanges.outgoing.to_enterprise(enterprise).first + end +end diff --git a/app/models/tag_rule/filter_payment_methods.rb b/app/models/tag_rule/filter_payment_methods.rb new file mode 100644 index 0000000000..04a10889e0 --- /dev/null +++ b/app/models/tag_rule/filter_payment_methods.rb @@ -0,0 +1,16 @@ +class TagRule::FilterPaymentMethods < TagRule + preference :matched_payment_methods_visibility, :string, default: "visible" + preference :payment_method_tags, :string, default: "" + + attr_accessible :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags + + def tags_match?(payment_method) + payment_method_tags = payment_method.andand.tag_list || [] + preferred_tags = preferred_payment_method_tags.split(",") + ( payment_method_tags & preferred_tags ).any? + end + + def reject_matched? + preferred_matched_payment_methods_visibility != "visible" + end +end diff --git a/app/models/tag_rule/filter_products.rb b/app/models/tag_rule/filter_products.rb new file mode 100644 index 0000000000..e1d3865036 --- /dev/null +++ b/app/models/tag_rule/filter_products.rb @@ -0,0 +1,22 @@ +class TagRule + class FilterProducts < TagRule + preference :matched_variants_visibility, :string, default: "visible" + preference :variant_tags, :string, default: "" + + attr_accessible :preferred_matched_variants_visibility, :preferred_variant_tags + + def self.tagged_children_for(product) + product["variants"] + end + + def tags_match?(variant) + variant_tags = variant.andand["tag_list"] || [] + preferred_tags = preferred_variant_tags.split(",") + (variant_tags & preferred_tags).any? + end + + def reject_matched? + preferred_matched_variants_visibility != "visible" + end + end +end diff --git a/app/models/tag_rule/filter_shipping_methods.rb b/app/models/tag_rule/filter_shipping_methods.rb index 74438e560e..2091a14a63 100644 --- a/app/models/tag_rule/filter_shipping_methods.rb +++ b/app/models/tag_rule/filter_shipping_methods.rb @@ -4,19 +4,8 @@ class TagRule::FilterShippingMethods < TagRule 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 + def reject_matched? + preferred_matched_shipping_methods_visibility != "visible" end def tags_match?(shipping_method) @@ -24,9 +13,4 @@ class TagRule::FilterShippingMethods < TagRule 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/models/variant_override.rb b/app/models/variant_override.rb index b373900e0c..e96b56db56 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -1,4 +1,6 @@ class VariantOverride < ActiveRecord::Base + acts_as_taggable + belongs_to :hub, class_name: 'Enterprise' belongs_to :variant, class_name: 'Spree::Variant' diff --git a/app/models/variant_override_set.rb b/app/models/variant_override_set.rb index 730246cebe..2d39d452fd 100644 --- a/app/models/variant_override_set.rb +++ b/app/models/variant_override_set.rb @@ -1,16 +1,28 @@ class VariantOverrideSet < ModelSet def initialize(collection, attributes={}) - super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } ) + super(VariantOverride, collection, attributes, nil, proc { |attrs, tag_list| deletable?(attrs, tag_list) } ) end private - def deletable?(attrs) + def deletable?(attrs, tag_list) attrs['price'].blank? && attrs['count_on_hand'].blank? && attrs['default_stock'].blank? && attrs['resettable'].blank? && attrs['sku'].nil? && - attrs['on_demand'].nil? + attrs['on_demand'].nil? && + tag_list.empty? + end + + def collection_to_delete + # Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute) + deleted = [] + collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes, e.tag_list) } + deleted + end + + def collection_to_keep + collection.reject { |e| @delete_if.andand.call(e.attributes, e.tag_list) } end end diff --git a/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface index 3b588a98c1..aa9ead7a0f 100644 --- a/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface +++ b/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface @@ -1,6 +1,7 @@ / replace "div[data-hook='admin_payment_method_form_fields']" -%div.alpha.eleven.columns += admin_inject_payment_method +%div.alpha.eleven.columns{ "ng-app" => "admin.paymentMethods", "ng-controller" => "paymentMethodCtrl" } .row .alpha.three.columns = label_tag nil, t(:name) @@ -33,4 +34,16 @@ = radio_button :payment_method, :active, false   = label_tag nil, t(:say_no) - = render 'providers' \ No newline at end of file + + .row + .alpha.three.columns + = label(:payment_method, :tags, t(:tags)) + .omega.eight.columns + = hidden_field(:payment_method, :tag_list, "ng-value" => "paymentMethod.tag_list") + %tags-with-translation#something{ object: "paymentMethod" } + + = render 'providers' + + .row + .omega.eleven.columns + = render partial: 'spree/admin/shared/calculator_fields', :locals => { :f => f } diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index 8f70da1a0d..49a49b3250 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -3,19 +3,26 @@ 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, :tag_groups + attributes :owner, :users, :tag_groups, :default_tag_group 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| + object.tag_rules.prioritised.reject(&:is_default).each_with_object([]) do |tag_rule, tag_groups| 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? + if tag_group[:rules].empty? + tag_groups << tag_group + tag_group[:position] = tag_groups.count + end tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash end - tag_groups + end + + def default_tag_group + default_rules = object.tag_rules.select(&:is_default) + serialized_rules = ActiveModel::ArraySerializer.new(default_rules, each_serializer: Api::Admin::TagRuleSerializer) + { tags: [], rules: serialized_rules } end def find_match(tag_groups, tags) diff --git a/app/serializers/api/admin/exchange_serializer.rb b/app/serializers/api/admin/exchange_serializer.rb index 615d49f695..e1c15c5210 100644 --- a/app/serializers/api/admin/exchange_serializer.rb +++ b/app/serializers/api/admin/exchange_serializer.rb @@ -1,5 +1,6 @@ class Api::Admin::ExchangeSerializer < ActiveModel::Serializer attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions + attributes :tags, :tag_list has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer @@ -35,4 +36,12 @@ class Api::Admin::ExchangeSerializer < ActiveModel::Serializer OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle) .visible_variants_for_outgoing_exchanges_to(object.receiver) end + + def tag_list + object.tag_list.join(",") + end + + def tags + object.tag_list.map{ |t| { text: t } } + end end diff --git a/app/serializers/api/admin/payment_method_serializer.rb b/app/serializers/api/admin/payment_method_serializer.rb new file mode 100644 index 0000000000..42c11a6953 --- /dev/null +++ b/app/serializers/api/admin/payment_method_serializer.rb @@ -0,0 +1,11 @@ +class Api::Admin::PaymentMethodSerializer < ActiveModel::Serializer + attributes :id, :name, :type, :tag_list, :tags + + def tag_list + object.tag_list.join(",") + end + + def tags + object.tag_list.map{ |t| { text: t } } + end +end diff --git a/app/serializers/api/admin/shipping_method_serializer.rb b/app/serializers/api/admin/shipping_method_serializer.rb index 9fbb864d09..e160d97fdf 100644 --- a/app/serializers/api/admin/shipping_method_serializer.rb +++ b/app/serializers/api/admin/shipping_method_serializer.rb @@ -1,5 +1,9 @@ class Api::Admin::ShippingMethodSerializer < ActiveModel::Serializer - attributes :id, :name, :tags + attributes :id, :name, :tag_list, :tags + + def tag_list + object.tag_list.join(",") + end def tags object.tag_list.map{ |t| { text: t } } diff --git a/app/serializers/api/admin/tag_rule_serializer.rb b/app/serializers/api/admin/tag_rule_serializer.rb index ac333c23c6..ca24a02638 100644 --- a/app/serializers/api/admin/tag_rule_serializer.rb +++ b/app/serializers/api/admin/tag_rule_serializer.rb @@ -10,7 +10,7 @@ end module Api::Admin::TagRule class BaseSerializer < ActiveModel::Serializer - attributes :id, :enterprise_id, :type, :preferred_customer_tags + attributes :id, :enterprise_id, :type, :is_default, :preferred_customer_tags end class DiscountOrderSerializer < BaseSerializer @@ -18,10 +18,34 @@ module Api::Admin::TagRule end class FilterShippingMethodsSerializer < BaseSerializer - attributes :preferred_matched_shipping_methods_visibility, :shipping_method_tags + attributes :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags, :shipping_method_tags def shipping_method_tags object.preferred_shipping_method_tags.split(",") end end + + class FilterPaymentMethodsSerializer < BaseSerializer + attributes :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags, :payment_method_tags + + def payment_method_tags + object.preferred_payment_method_tags.split(",") + end + end + + class FilterProductsSerializer < BaseSerializer + attributes :preferred_matched_variants_visibility, :preferred_variant_tags, :variant_tags + + def variant_tags + object.preferred_variant_tags.split(",") + end + end + + class FilterOrderCyclesSerializer < BaseSerializer + attributes :preferred_matched_order_cycles_visibility, :preferred_exchange_tags, :exchange_tags + + def exchange_tags + object.preferred_exchange_tags.split(",") + end + end end diff --git a/app/serializers/api/admin/variant_override_serializer.rb b/app/serializers/api/admin/variant_override_serializer.rb index c1e6e0038c..68b86818a1 100644 --- a/app/serializers/api/admin/variant_override_serializer.rb +++ b/app/serializers/api/admin/variant_override_serializer.rb @@ -1,3 +1,12 @@ class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable + attributes :tag_list, :tags + + def tag_list + object.tag_list.join(",") + end + + def tags + object.tag_list.map { |t| { text: t } } + end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 3210c1d2af..127aee1a08 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -1,3 +1,5 @@ +require 'open_food_network/property_merge' + class Api::EnterpriseSerializer < ActiveModel::Serializer # We reference this here because otherwise the serializer complains about its absence Api::IdSerializer @@ -47,7 +49,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer attributes :taxons, :supplied_taxons has_one :address, serializer: Api::AddressSerializer - + has_many :properties, serializer: Api::PropertySerializer def taxons ids_to_objs options[:data].distributed_taxons[object.id] @@ -57,6 +59,14 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer ids_to_objs options[:data].supplied_taxons[object.id] end + def properties + # This results in 3 queries per enterprise + product_properties = Spree::Property.applied_by(object) + producer_properties = object.properties + + OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties + end + def pickup services = options[:data].shipping_method_services[object.id] services ? services[:pickup] : false diff --git a/app/serializers/api/payment_method_serializer.rb b/app/serializers/api/payment_method_serializer.rb index d62bc18154..a135f5d792 100644 --- a/app/serializers/api/payment_method_serializer.rb +++ b/app/serializers/api/payment_method_serializer.rb @@ -1,3 +1,8 @@ class Api::PaymentMethodSerializer < ActiveModel::Serializer - attributes :name, :description, :id, :method_type + attributes :name, :description, :id, :method_type, + :price + + def price + object.compute_amount(options[:current_order]) + end end diff --git a/app/serializers/api/property_serializer.rb b/app/serializers/api/property_serializer.rb index ce04480d30..7da4fce990 100644 --- a/app/serializers/api/property_serializer.rb +++ b/app/serializers/api/property_serializer.rb @@ -1,3 +1,3 @@ class Api::PropertySerializer < ActiveModel::Serializer - + attributes :id, :name, :presentation end diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index 6908eaf84f..6a38fe9b93 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -1,6 +1,7 @@ class Api::VariantSerializer < ActiveModel::Serializer - attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display, - :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name + attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display + attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name + attributes :tag_list def price object.price diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index c570632c64..98e8b3c414 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -14,21 +14,25 @@ = admin_inject_shops %div{ ng: { controller: 'customersCtrl' } } - .row{ ng: { hide: "!RequestMonitor.loading && customers.length > 0" } } - .five.columns.alpha - %h3 - =t :please_select_hub - .four.columns - %select.select2.fullwidth#shop_id{ 'ng-model' => 'CurrentShop.shop', name: 'shop_id', 'ng-options' => 'shop as shop.name for shop in shops' } - .seven.columns.omega   + .row.filters + .sixteen.columns.alpha.omega + .filter_select.five.columns.alpha + %label{ :for => 'quick_search', ng: {class: '{disabled: !shop_id}'} }=t('admin.quick_search') + %br + %input.fullwidth{ :type => "text", :id => 'quick_search', ng: { model: 'quickSearch', disabled: '!shop_id' }, :placeholder => "Search by email/code..." } + .filter_select.four.columns + %label{ :for => 'hub_id', ng: { bind: "shop_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } } + %br + %input.ofn-select2.fullwidth#shop_id{ 'ng-model' => 'shop_id', name: 'shop_id', data: 'shops', on: { selecting: 'confirmRefresh' } } + .seven.columns.omega   - .row{ 'ng-hide' => 'RequestMonitor.loading || !CurrentShop.shop.id || customers.length == 0' } - .controls.sixteen.columns.alpha.omega - .five.columns.alpha - %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } - .eight.columns   - %columns-dropdown{ action: "#{controller_name}_#{action_name}" } - .row{ 'ng-if' => 'CurrentShop.shop.id && RequestMonitor.loading' } + %hr.divider{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } + + .row.controls{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } + .thirteen.columns.alpha   + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } + + .row{ 'ng-if' => 'shop_id && RequestMonitor.loading' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 @@ -64,7 +68,9 @@ -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } %td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' } %td.code{ 'ng-show' => 'columns.code.visible' } - %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } + %input{ type: 'text', name: 'code', ng: {model: 'customer.code', change: 'checkForDuplicateCodes()'}, "obj-for-update" => "customer", "attr-for-update" => "code" } + %i.icon-warning-sign{ ng: {if: 'duplicate'} } + = t('.duplicate_code') %td.tags{ 'ng-show' => 'columns.tags.visible' } .tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"} %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } diff --git a/app/views/admin/enterprise_groups/_form_about.html.haml b/app/views/admin/enterprise_groups/_form_about.html.haml index 1e97697a4e..e7932bc272 100644 --- a/app/views/admin/enterprise_groups/_form_about.html.haml +++ b/app/views/admin/enterprise_groups/_form_about.html.haml @@ -1,6 +1,5 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='About'" } } - %legend - = t 'admin_entreprise_groups_about' +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='about'" } } + %legend {{menu.selected.label}} = f.field_container :long_description do %text-angular{'id' => 'enterprise_group_long_description', 'name' => 'enterprise_group[long_description]', 'class' => 'text-angular', 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]"} diff --git a/app/views/admin/enterprise_groups/_form_address.html.haml b/app/views/admin/enterprise_groups/_form_address.html.haml index fa7b1e7e75..2b8add5392 100644 --- a/app/views/admin/enterprise_groups/_form_address.html.haml +++ b/app/views/admin/enterprise_groups/_form_address.html.haml @@ -1,7 +1,6 @@ = f.fields_for :address do |af| - %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Contact'" } } - %legend - = t 'admin_entreprise_groups_contact' + %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='contact'" } } + %legend {{menu.selected.label}} .row .alpha.three.columns = af.label :phone diff --git a/app/views/admin/enterprise_groups/_form_images.html.haml b/app/views/admin/enterprise_groups/_form_images.html.haml index 24bfad3bf0..7add7c914a 100644 --- a/app/views/admin/enterprise_groups/_form_images.html.haml +++ b/app/views/admin/enterprise_groups/_form_images.html.haml @@ -1,6 +1,5 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Images'" } } - %legend - = t 'admin_entreprise_groups_images' +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='images'" } } + %legend {{menu.selected.label}} .row .alpha.three.columns = f.label :logo, 'ofn-with-tip' => t('admin_entreprise_groups_data_powertip_logo') diff --git a/app/views/admin/enterprise_groups/_form_primary_details.html.haml b/app/views/admin/enterprise_groups/_form_primary_details.html.haml index 56771c458c..f003963b4e 100644 --- a/app/views/admin/enterprise_groups/_form_primary_details.html.haml +++ b/app/views/admin/enterprise_groups/_form_primary_details.html.haml @@ -1,6 +1,5 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Primary Details'" } } - %legend - = t "admin_entreprise_groups_primary_details" +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='primary_details'" } } + %legend {{menu.selected.label}} = f.field_container :name do = f.label :name %br/ diff --git a/app/views/admin/enterprise_groups/_form_users.html.haml b/app/views/admin/enterprise_groups/_form_users.html.haml index 6bd6eaa4a3..22ac7921ca 100644 --- a/app/views/admin/enterprise_groups/_form_users.html.haml +++ b/app/views/admin/enterprise_groups/_form_users.html.haml @@ -1,6 +1,5 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Users'" } } - %legend - = t(:users) +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='users'" } } + %legend {{menu.selected.label}} .row .three.columns.alpha =f.label :owner_id, t(:admin_entreprise_groups_owner) diff --git a/app/views/admin/enterprise_groups/_form_web.html.haml b/app/views/admin/enterprise_groups/_form_web.html.haml index 5d982f1bbb..e0de2f7159 100644 --- a/app/views/admin/enterprise_groups/_form_web.html.haml +++ b/app/views/admin/enterprise_groups/_form_web.html.haml @@ -1,6 +1,5 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Web'" } } - %legend - = t 'admin_entreprise_groups_web' +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='web'" } } + %legend {{menu.selected.label}} .row .alpha.three.columns = f.label :website diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 75dbf98f17..d89970946d 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -29,16 +29,16 @@ %th.package{ ng: { show: 'columns.package.visible' } }=t('.package') %th.status{ ng: { show: 'columns.status.visible' } }=t('.status') %th.manage{ ng: { show: 'columns.manage.visible' } }=t('.manage') - %tbody{ :id => "e_{{enterprise.id}}", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )", controller: 'EnterpriseIndexRowCtrl' } } - %tr.enterprise.panel-toggle-row{ object: "enterprise", ng: { class: { even: "'even'", odd: "'odd'"} } } + %tbody.panel-ctrl{ :id => "e_{{enterprise.id}}", object: "enterprise", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )" } } + %tr.enterprise{ ng: { class: { even: "'even'", odd: "'odd'"} } } %td.name{ ng: { show: 'columns.name.visible' } } %span{ ng: { bind: "::enterprise.name" } } - %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: producerError}" }, name: "producer" } - %h5{ ng: { bind: "producer" } } - %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: packageError}" }, name: "package" } - %h5{ ng: { bind: "package" } } + %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: enterprise.producerError}" }, name: "producer" } + %h5{ ng: { bind: "enterprise.producer" } } + %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: enterprise.packageError}" }, name: "package" } + %h5{ ng: { bind: "enterprise.package" } } %td.status.panel-toggle.text-center{ ng: { show: 'columns.status.visible' }, name: "status" } - %i.icon-status{ ng: { class: "::status()" } } + %i.icon-status{ ng: { class: "enterprise.status" } } %td.manage{ ng: { show: 'columns.manage.visible' } } %a.button.fullwidth{ ng: { href: '{{::enterprise.edit_path}}' } } Manage diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index e363078733..ee3dfa2183 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,60 +1,60 @@ -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Primary Details'" } } - %legend Primary Details +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='primary_details'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/primary_details', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Users'" } } - %legend Users +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='users'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/users', f: f = f.fields_for :address do |af| - %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Address'" } } - %legend Address + %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='address'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/address', af: af -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Contact'" } } - %legend Contact +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='contact'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/contact', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Social'" } } - %legend Social +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='social'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/social', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Business Details'" } } - %legend Business Details +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='business_details'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/business_details', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='About'" } } - %legend About Us +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='about'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/about_us', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Images'" } } - %legend Images +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='images'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/images', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Properties'" } } - %legend Properties +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='properties'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/properties', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shipping Methods'" } } - %legend Shipping Methods +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='shipping_methods'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/shipping_methods', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Payment Methods'" } } - %legend Payment Methods +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='payment_methods'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/payment_methods', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Enterprise Fees'" } } - %legend Enterprise Fees +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='enterprise_fees'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/enterprise_fees', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Inventory Settings'" } } - %legend Inventory Settings +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='inventory_settings'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/inventory_settings', f: f -%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shop Preferences'" } } - %legend Shop Preferences +%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='shop_preferences'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/shop_preferences', f: f -%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='Tag Rules'" } } - %legend Tag Rules +%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='tag_rules'" } } + %legend {{menu.selected.label}} = render 'admin/enterprises/form/tag_rules', f: f diff --git a/app/views/admin/enterprises/_ng_form.html.haml b/app/views/admin/enterprises/_ng_form.html.haml index a19bc43314..ca38b7eca4 100644 --- a/app/views/admin/enterprises/_ng_form.html.haml +++ b/app/views/admin/enterprises/_ng_form.html.haml @@ -1,12 +1,18 @@ -# Not all inputs are ng inputs, they don't make the ng-form dirty on change. -# ng-change is only valid for inputs, not for a form. -# So we use onchange and have to get the scope to access the ng controller -= form_for [main_app, :admin, @enterprise], html: { name: "enterprise", += form_for [main_app, :admin, @enterprise], html: { name: "enterprise_form", "ng-app" => 'admin.enterprises', - "ng-submit" => "navClear()", "ng-controller" => 'enterpriseCtrl', - 'onchange' => 'angular.element(enterprise).scope().enterprise.$setDirty()', + 'onchange' => 'angular.element(enterprise_form).scope().setFormDirty()', } do |f| + + %save-bar{ dirty: "enterprise_form.$dirty", persist: "true" } + %input.red{ type: "button", value: "Update", ng: { click: "submit()", disabled: "!enterprise_form.$dirty" } } + %input{ type: "button", ng: { value: "enterprise_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_enterprises_path}')" } } + + + .row .sixteen.columns.alpha .four.columns.alpha @@ -14,8 +20,3 @@ .one.column   .eleven.columns.omega.fullwidth_inputs = render 'form', f: f - .row - .five.columns.alpha -   - .eleven.columns.alpha - = render partial: "spree/admin/shared/#{action}_resource_links" diff --git a/app/views/admin/enterprises/form/_payment_methods.html.haml b/app/views/admin/enterprises/form/_payment_methods.html.haml index 8857980bd9..57d9426d03 100644 --- a/app/views/admin/enterprises/form/_payment_methods.html.haml +++ b/app/views/admin/enterprises/form/_payment_methods.html.haml @@ -7,7 +7,7 @@ %th %tbody - @payment_methods.each do |payment_method| - %tr{ ng: { controller: 'paymentMethodCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } } + %tr{ ng: { controller: 'paymentMethodsCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } } %td= payment_method.name %td= f.check_box :payment_method_ids, { multiple: true, 'ng-model' => 'PaymentMethod.selected' }, payment_method.id, nil %td= link_to "Edit", edit_admin_payment_method_path(payment_method) @@ -30,4 +30,4 @@ .text-center %a.button{ href: "#{new_admin_payment_method_path}"} Create One Now - %i.icon-arrow-right \ No newline at end of file + %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml index 4ed9aeb5c1..e58f7c5a62 100644 --- a/app/views/admin/enterprises/form/_tag_rules.html.haml +++ b/app/views/admin/enterprises/form/_tag_rules.html.haml @@ -1,9 +1,11 @@ .row{ ng: { controller: "TagRulesCtrl" } } .eleven.columns.alpha.omega - .eleven.columns.alpha.omega + %ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", after: { sort: "updateRuleCounts()" } } .no_tags{ ng: { show: "tagGroups.length == 0" } } No tags apply to this enterprise yet - .customer_tag{ ng: { repeat: "tagGroup in tagGroups" } } + = render 'admin/enterprises/form/tag_rules/default_rules' + -# = render 'customer_tags' + .customer_tag{ id: "tg_{{tagGroup.position}}", ng: { repeat: "tagGroup in tagGroups" } } .header %table %colgroup @@ -14,20 +16,12 @@ %h5 For customers tagged: %td - %tags-input{ ng: { model: 'tagGroup.tags'}, - min: { tags: "1" }, - on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } } + %tags-with-translation{ object: "tagGroup", max: 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{ ng: { if: "::rule.type == 'TagRule::DiscountOrder'" } } - %filter-shipping-methods{ ng: { if: "::rule.type == 'TagRule::FilterShippingMethods'" } } - %td.actions - %a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" } + .tag_rule{ ng: { repeat: "rule in tagGroup.rules" } } .add_rule.text-center - %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "new-tag-rule-dialog" => true } - .add_tage + %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true } + .add_tag %input.button.red.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } } diff --git a/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml new file mode 100644 index 0000000000..09c78f9c8c --- /dev/null +++ b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml @@ -0,0 +1,15 @@ +.default_rules + .header + %table + %colgroup + %col{width: '100%'} + %tr + %td + %h5 + By Default + %i.text-big.icon-question-sign.help-modal{ template: 'admin/modals/tag_rule_help.html' } + .no_rules{ ng: { show: "defaultTagGroup.rules.length == 0" } } + No default rules apply yet + .tag_rule{ ng: { repeat: "rule in defaultTagGroup.rules" } } + .add_rule.text-center + %input.button.icon-plus{ type: 'button', value: "+ Add A New Default Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true } diff --git a/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml b/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml deleted file mode 100644 index e2218599ce..0000000000 --- a/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%td{:colspan => 4} - .exchange-select-all-variants - %label - = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants' - Select all - - .exchange-products - -# Scope product list based on permissions the current user has to view variants in this exchange - .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' } - .exchange-product-details - %label - -# MASTER_VARIANTS: No longer required - -# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', - -# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' - %img{'ng-src' => '{{ product.image_url }}'} - .name {{ product.name }} - .supplier {{ product.supplier_name }} - - .exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'} - %label - = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', - 'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' - {{ variant.label }} diff --git a/app/views/admin/order_cycles/_exchange_form.html.haml b/app/views/admin/order_cycles/_exchange_form.html.haml index ed3e43fd6b..0e3dbb4340 100644 --- a/app/views/admin/order_cycles/_exchange_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_form.html.haml @@ -1,29 +1,42 @@ -%td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }} -%td.products - = f.submit 'Products', 'ng-click' => 'toggleProducts($event, exchange)' - {{ exchangeSelectedVariants(exchange) }} / +%tr{ ng: { class: "'#{type} #{type}-{{ exchange.enterprise_id }}'" } } + %td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }} + %td.products.panel-toggle.text-center{ name: "products" } + {{ exchangeSelectedVariants(exchange) }} / + - if type == 'supplier' + {{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }} + - else + {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }} + selected - if type == 'supplier' - {{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }} - - else - {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }} - selected + %td.receival-details + = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions' + - if type == 'distributor' + %td.tags.panel-toggle.text-center{ name: "tags", ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } + {{ exchange.tags.length }} + %td.collection-details + = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' + %br/ + = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Pick-up instructions', 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' + %td.fees + %ol{ ng: { show: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } + %li{'ng-repeat' => 'enterprise_fee in exchange.enterprise_fees'} + = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', 'ng-model' => 'enterprise_fee.enterprise_id', 'ng-options' => 'enterprise.id as enterprise.name for enterprise in enterprisesWithFees()'} + + = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', 'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(enterprise_fee.enterprise_id)'} + + = link_to 'Remove', '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'} + + = f.submit 'Add fee', 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' + %td.actions + %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"} + - if type == 'supplier' - %td.receival-details - = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions' + %tr.panel-row{ object: "exchange", + panels: "{products: 'exchange_supplied_products'}", + locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant", + colspan: 4 } - if type == 'distributor' - %td.collection-details - = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' - %br/ - = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Pick-up instructions', 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' -%td.fees - %ol{ ng: { show: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } - %li{'ng-repeat' => 'enterprise_fee in exchange.enterprise_fees'} - = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', 'ng-model' => 'enterprise_fee.enterprise_id', 'ng-options' => 'enterprise.id as enterprise.name for enterprise in enterprisesWithFees()'} - - = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', 'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(enterprise_fee.enterprise_id)'} - - = link_to 'Remove', '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'} - - = f.submit 'Add fee', 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator' -%td.actions - %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"} + %tr.panel-row{ object: "exchange", + panels: "{products: 'exchange_distributed_products', tags: 'exchange_tags'}", + locals: "$index,order_cycle,exchange,supplied_products,setExchangeVariants,incomingExchangeVariantsFor,productSuppliedToOrderCycle,variantSuppliedToOrderCycle", + colspan: 5 } diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index e53fd515b0..ebe9e45905 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -17,11 +17,8 @@ %th Receival details %th Fees %th.actions - %tbody{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'} - %tr{'class' => "supplier supplier-{{ exchange.enterprise_id }}"} - = render 'exchange_form', :f => f, :type => 'supplier' - %tr.products{'ng-show' => 'exchange.showProducts'} - = render 'exchange_supplied_products_form' + %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'} + = render 'exchange_form', :f => f, :type => 'supplier' - if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator = render 'add_exchange_form', f: f, type: 'supplier' @@ -37,22 +34,18 @@ %a{href: '#', 'ng-click' => "OrderCycle.toggleAllProducts('outgoing')"} %span{'ng-show' => "OrderCycle.showProducts['outgoing']"} Collapse all %span{'ng-hide' => "OrderCycle.showProducts['outgoing']"} Expand all + %th{ ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } Tags %th Pickup / Delivery details %th Fees %th.actions - %tbody{'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'} - %tr{'class' => "distributor distributor-{{ exchange.enterprise_id }}"} - = render 'exchange_form', :f => f, :type => 'distributor' - %tr.products{'ng-show' => 'exchange.showProducts'} - = render 'exchange_distributed_products_form' + %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'} + = render 'exchange_form', :f => f, :type => 'distributor' + - if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator = render 'add_exchange_form', f: f, type: 'distributor' .actions - - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-hide' => 'loaded()'} Loading... diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index ba28526f7b..a7a0363cd3 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -14,14 +14,11 @@ %table.exchanges %tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}} %tr.products - = render 'exchange_supplied_products_form' + %td{ ng: { include: "'admin/panels/exchange_supplied_products.html'" } } %br/ = label_tag 'Fees' = render 'coordinator_fees', f: f .actions - - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-hide' => 'loaded()'} Loading... diff --git a/app/views/admin/order_cycles/new.html.haml b/app/views/admin/order_cycles/new.html.haml index a257bf3c0b..4e5a95544c 100644 --- a/app/views/admin/order_cycles/new.html.haml +++ b/app/views/admin/order_cycles/new.html.haml @@ -4,7 +4,12 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleCreateOrderCycleCtrl' : 'AdminCreateOrderCycleCtrl' = admin_inject_order_cycle_instance -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller} do |f| += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } + %input.red{ type: "button", value: "Create", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } + %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } + - if order_cycles_simple_form = render 'simple_form', f: f - else diff --git a/app/views/admin/shared/_side_menu.html.haml b/app/views/admin/shared/_side_menu.html.haml index cf1d3d42f7..267a341991 100644 --- a/app/views/admin/shared/_side_menu.html.haml +++ b/app/views/admin/shared/_side_menu.html.haml @@ -5,4 +5,4 @@ show: '!showItem || showItem(item)', class: '{ selected: item.selected }' } } %i{ class: "{{item.icon_class}}" } - %span {{ item.name }} + %span {{ item.label }} diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index 4330496b3e..711c52f6cf 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -11,6 +11,7 @@ %col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } } %col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } } %col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } } + %col.tags{ width: "30%", ng: { show: 'columns.tags.visible' } } %col.visibility{ width: "10%", ng: { show: 'columns.visibility.visible' } } %thead %tr{ ng: { controller: "ColumnsCtrl" } } @@ -22,6 +23,7 @@ %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.on_demand?') %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.variant_overrides.index.enable_reset?') %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.variant_overrides.index.inherit?') + %th.tags{ ng: { show: 'columns.tags.visible' } }=t('admin.tags') %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.variant_overrides.index.hide') %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } = render 'admin/variant_overrides/products_product' diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 119ee867df..0427333790 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -7,4 +7,5 @@ %td.on_demand{ ng: { show: 'columns.on_demand.visible' } } %td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } + %td.tags{ ng: { show: 'columns.tags.visible' } } %td.visibility{ ng: { show: 'columns.visibility.visible' } } diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index c139bc7ffb..94fc309798 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -1,4 +1,4 @@ -%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'}} +%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'} } %td.producer{ ng: { show: 'columns.producer.visible' } } %td.product{ ng: { show: 'columns.product.visible' } } %span{ ng: { bind: '::variant.display_name || ""'} } @@ -17,6 +17,9 @@ %input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'} %td.inheritance{ ng: { show: 'columns.inheritance.visible' } } %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true } + %td.tags{ ng: { show: 'columns.tags.visible' } } + .tag_watcher{ 'track-tag-list' => true } + %tags_with_translation{ object: 'variantOverrides[hub_id][variant.id]', form: 'variant_overrides_form' } %td.visibility{ ng: { show: 'columns.visibility.visible' } } %button.icon-remove.hide.fullwidth{ :type => 'button', ng: { click: "setVisibility(hub_id,variant.id,false)" } } = t('admin.variant_overrides.index.hide') diff --git a/app/views/checkout/_order.rabl b/app/views/checkout/_order.rabl deleted file mode 100644 index 778f4428d0..0000000000 --- a/app/views/checkout/_order.rabl +++ /dev/null @@ -1,44 +0,0 @@ -#NOTE: when adding new fields for user input, it may want to be cached in localStorage -# If so, make sure to add it to controller attribute caching - -object current_order -attributes :id, :email, :shipping_method_id, :user_id - -node :display_total do - current_order.display_total.money.to_f -end - -node :payment_method_id do - current_order.payments.first.andand.payment_method_id -end - -child current_order.bill_address => :bill_address do - attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode -end - -child current_order.ship_address => :ship_address do - attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode -end - -# This is actually totally decoupled data and should be injected separately into their -# own services - -node :shipping_methods do - Hash[current_distributor.shipping_methods.uniq.collect { |method| - [method.id, { - require_ship_address: method.require_ship_address, - price: method.compute_amount(current_order).to_f, - name: method.name, - description: method.description - }] - }] -end - -node :payment_methods do - Hash[current_order.available_payment_methods.collect { - |method| [method.id, { - name: method.name, - method_type: method.method_type - }] - }] -end diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml index c11d7cd45e..8d387145d5 100644 --- a/app/views/checkout/_payment.html.haml +++ b/app/views/checkout/_payment.html.haml @@ -17,7 +17,7 @@ -# The problem being how to render the partials .row .small-6.columns - - current_order.available_payment_methods.each do |method| + - available_payment_methods.each do |method| .row .small-12.columns %label @@ -26,6 +26,7 @@ name: "order.payment_method_id", "ng-model" => "order.payment_method_id" = method.name + = "(#{payment_method_price(method, @order)})" %small.error.medium.input-text{"ng-show" => "!fieldValid('order.payment_method_id')"} = "{{ fieldErrors('order.payment_method_id') }}" diff --git a/app/views/checkout/_summary.html.haml b/app/views/checkout/_summary.html.haml index 3a4f6a4ea1..a3b61c5ded 100644 --- a/app/views/checkout/_summary.html.haml +++ b/app/views/checkout/_summary.html.haml @@ -19,6 +19,11 @@ = t :checkout_shipping_price %td.shipping.text-right {{ Checkout.shippingPrice() | localizeCurrency }} + %tr + %th + = t :payment_method_fee + %td.text-right {{ Checkout.paymentPrice() | localizeCurrency }} + %tr %th = t :checkout_total_price diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 6b665b4f34..28ed0c985f 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -16,9 +16,13 @@ %label = t :producers_buy %p.trans-sentence - %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} - %render-svg{path: "{{taxon.icon}}"} - %span{"ng-bind" => "::taxon.name"} + %div + %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} + %render-svg{path: "{{taxon.icon}}"} + %span{"ng-bind" => "::taxon.name"} + %div + %span.fat-properties{"ng-repeat" => "property in producer.properties"} + %span{"ng-bind" => "property.presentation"} %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}   diff --git a/app/views/spree/admin/overview/_order_cycles.html.haml b/app/views/spree/admin/overview/_order_cycles.html.haml index 8628837a47..1a00bd5102 100644 --- a/app/views/spree/admin/overview/_order_cycles.html.haml +++ b/app/views/spree/admin/overview/_order_cycles.html.haml @@ -6,9 +6,8 @@ %a.three.columns.omega.icon-plus.button.blue{ href: "#{main_app.new_admin_order_cycle_path}" } = t "spree_admin_enterprises_create_new" - else - %a.with-tip{ title: t(:spree_admin_order_cycles_tip) } + %a{ "ofn-with-tip" => t(:spree_admin_order_cycles_tip) } = t "admin.whats_this" - %a{ "ofn-with-tip" => "Order cycles determine when and where your products are available to customers." } What's this? %div.seven.columns.alpha.list - if @order_cycle_count > 0 %div.seven.columns.alpha.list-item diff --git a/app/views/spree/admin/payment_methods/_providers.html.haml b/app/views/spree/admin/payment_methods/_providers.html.haml index 77b11fb473..657e21029e 100644 --- a/app/views/spree/admin/payment_methods/_providers.html.haml +++ b/app/views/spree/admin/payment_methods/_providers.html.haml @@ -1,10 +1,8 @@ -:javascript - angular.module('ofn.admin').value('paymentMethod', #{ { id: @payment_method.id, type: @payment_method.type }.to_json }) -#provider-settings{ ng: { app: "ofn.admin", controller: "ProvidersCtrl" } } +#provider-settings{ ng: { controller: "ProvidersCtrl" } } .row .alpha.three.columns = label :payment_method, :type, t(:provider) .omega.eight.columns = collection_select(:payment_method, :type, @providers, :to_s, :clean_name, (!@object.persisted? ? { :selected => "Spree::PaymentMethod::Check"} : {}), { class: 'select2 fullwidth', 'provider-prefs-for' => "#{@object.id}"}) - %div{"ng-include" => "include_html" } \ No newline at end of file + %div{"ng-include" => "include_html" } diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 235544cb8a..4e6cf8c704 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -8,4 +8,3 @@ = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' - = render 'spree/admin/products/bulk_edit/save_button_row' diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml index 40f135d62e..4a0878bd5c 100644 --- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -1,6 +1,3 @@ .controls.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0' } - .four.columns.alpha - %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} - .nine.columns - = render 'spree/admin/shared/status_message' + .thirteen.columns %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/spree/admin/products/bulk_edit/_products.html.haml b/app/views/spree/admin/products/bulk_edit/_products.html.haml index 81fb31573b..b8c580af41 100644 --- a/app/views/spree/admin/products/bulk_edit/_products.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products.html.haml @@ -1,9 +1,14 @@ %div.sixteen.columns.alpha{ 'ng-hide' => 'loading || filteredProducts.length == 0' } - %table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" } + %form{ name: 'bulk_product_form' } + %save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "submitProducts()", disabled: "!bulk_product_form.$dirty" } } + %input{ type: "button", value: "Close", 'ng-click' => "cancel('#{bulk_edit_admin_products_path}')" } - = render 'spree/admin/products/bulk_edit/products_head' + %table.index#listing_products.bulk{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1" } - %tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } + = render 'spree/admin/products/bulk_edit/products_head' - = render 'spree/admin/products/bulk_edit/products_product' - = render 'spree/admin/products/bulk_edit/products_variant' + %tbody{ 'ng-repeat' => 'product in filteredProducts = ( products | filter:query | producer: producerFilter | category: categoryFilter | limitTo:limit )', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } + + = render 'spree/admin/products/bulk_edit/products_product' + = render 'spree/admin/products/bulk_edit/products_variant' diff --git a/config/application.rb b/config/application.rb index 1fb6c88bbf..6ffee13160 100644 --- a/config/application.rb +++ b/config/application.rb @@ -34,6 +34,11 @@ module Openfoodnetwork Spree::Calculator::PerItem, Spree::Calculator::PriceSack, OpenFoodNetwork::Calculator::Weight] + app.config.spree.calculators.payment_methods = [Spree::Calculator::FlatPercentItemTotal, + Spree::Calculator::FlatRate, + Spree::Calculator::FlexiRate, + Spree::Calculator::PerItem, + Spree::Calculator::PriceSack] end # Register Spree payment methods diff --git a/config/environments/production.rb b/config/environments/production.rb index a55540883d..819bb98306 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -31,7 +31,7 @@ Openfoodnetwork::Application.configure do config.force_ssl = true # See everything in the log (default is :info) - # config.log_level = :debug + config.log_level = :warn # Use a different logger for distributed setups # config.logger = SyslogLogger.new diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index a6a045222d..92ba3db249 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -40,9 +40,7 @@ module Spree module Core class Environment class Calculators - include EnvironmentExtension - - attr_accessor :enterprise_fees + attr_accessor :enterprise_fees, :payment_methods end end end diff --git a/config/locales/en-US.yml b/config/locales/en-US.yml new file mode 100644 index 0000000000..e86100ba93 --- /dev/null +++ b/config/locales/en-US.yml @@ -0,0 +1,1048 @@ +# English language file +# --------------------- +# +# Originally adapted from source language file maintained by the Australian OFN team. +# Visit Transifex to translate this file into other languages: +# +# https://www.transifex.com/open-food-foundation/open-food-network/ +# +# If you translate this file in a text editor, please share your results with us by +# +# - uploading the file to Transifex or +# - opening a pull request at GitHub. +# +# +# See http://community.openfoodnetwork.org/t/localisation-ofn-in-your-language/397 + +en-US: + activerecord: + # Overridden here due to a bug in spree i18n (Issue #870) + weight_measurement_unit: "Weight (per lb)" + attributes: + spree/order: + payment_state: Payment State + shipment_state: Shipment State + devise: + failure: + invalid: | + Invalid email or password. + Were you a guest last time? Perhaps you need to create an account or reset your password. + enterprise_confirmations: + enterprise: + confirmed: Thankyou, your email address has been confirmed. + not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? + confirmation_sent: "Confirmation email sent!" + confirmation_not_sent: "Could not send a confirmation email." + home: "OFN" + title: Open Food Network + welcome_to: 'Welcome to ' + site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" + search_by_name: Search by name or city... + producers: 'US Producers' + producers_join: US producers are now welcome to join the Open Food Network. #FIXME + charges_sales_tax: Charges GST? + print_invoice: "Print Invoice" + send_invoice: "Send Invoice" + resend_confirmation: "Resend Confirmation" + view_order: "View Order" + edit_order: "Edit Order" + ship_order: "Ship Order" + cancel_order: "Cancel Order" + confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?" + confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?" + must_have_valid_business_number: "%{enterprise_name} must have a valid ABN before invoices can be sent." + invoice: "Invoice" + percentage_of_sales: "%{percentage} of sales" + percentage_of_turnover: "Percentage of turnover" + monthly_cap_excl_tax: "monthly cap (excl. GST)" + capped_at_cap: "capped at %{cap}" + per_month: "per month" + free: "free" + plus_tax: "plus GST" + total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)" + say_no: "No" + say_yes: "Yes" + + sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" + + + admin: + # General form elements + quick_search: Quick Search + clear_all: Clear All + producer: Producer + shop: Shop + product: Product + variant: Variant + + columns: Columns + actions: Actions + viewing: "Viewing: %{current_view_name}" + + whats_this: What's this? + + tag_has_rules: "Existing rules for this tag: %{num}" + has_one_rule: "has one rule" + has_n_rules: "has %{num} rules" + + 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 + sku: SKU + price: Price + on_hand: On Hand + on_demand: On Demand? + enable_reset: Enable Stock Level Reset? + inherit: Inherit? + add: Add + hide: Hide + select_a_shop: Select A Shop + review_now: Review Now + new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. + currently_empty: Your inventory is currently empty + no_matching_products: No matching products found in your inventory + no_hidden_products: No products have been hidden from this inventory + no_matching_hidden_products: No hidden products match your search criteria + no_new_products: No new products are available to add to this inventory + no_matching_new_products: No new products match your search criteria + inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. + hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. + new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + + + order_cycle: + choose_products_from: "Choose Products From:" + + 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" + invoice_column_tax: "GST" + invoice_column_price: "Price" + + logo: "Logo (640x130)" #FIXME + logo_mobile: "Mobile logo (75x26)" #FIXME + logo_mobile_svg: "Mobile logo (SVG)" #FIXME + home_hero: "Hero image" + home_show_stats: "Show statistics" + footer_logo: "Logo (220x76)" #FIXME + footer_facebook_url: "Facebook URL" + footer_twitter_url: "Twitter URL" + footer_instagram_url: "Instagram URL" + footer_linkedin_url: "LinkedIn URL" + footer_googleplus_url: "Google Plus URL" + footer_pinterest_url: "Pinterest URL" + footer_email: "Email" + footer_links_md: "Links" + footer_about_url: "About URL" + footer_tos_url: "Terms of Service URL" + + name: Name + first_name: First Name + last_name: Last Name + email: Email + phone: Phone + next: Next + address: Address + address2: Address (contd.) + city: City + state: State + postcode: Postcode + country: Country + unauthorized: Unauthorized + terms_of_service: "Terms of service" + on_demand: On demand + none: None + + label_shops: "Shops" + label_map: "Map" + label_producers: "Producers" + label_groups: "Groups" + label_about: "About" + label_shopping: "Shopping" + label_login: "Login" + label_logout: "Logout" + label_signup: "Sign up" + label_administration: "Administration" + label_admin: "Admin" + label_account: "Account" + label_more: "Show more" + label_less: "Show less" + label_notices: "Notices" + + items: "items" + cart_headline: "Your shopping cart" + total: "Total" + checkout: "Checkout now" + cart_updating: "Updating cart..." + cart_empty: "Cart empty" + cart_edit: "Edit your cart" + + card_number: Card Number + card_securitycode: "Security Code" + card_expiry_date: Expiry Date + + ofn_cart_headline: "Current cart for:" + ofn_cart_distributor: "Distributor:" + ofn_cart_oc: "Order cycle:" + ofn_cart_from: "From:" + ofn_cart_to: "To:" + ofn_cart_product: "Product:" + ofn_cart_quantitiy: "Quantity:" + ofn_cart_send: "Buy me" + + ie_warning_headline: "Your browser is out of date :-(" + ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:" + ie_warning_chrome: Download Chrome + ie_warning_firefox: Download Firefox + ie_warning_ie: Upgrade Internet Explorer + ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" + + footer_global_headline: "OFN Global" + footer_global_home: "Home" + footer_global_news: "News" + footer_global_about: "About" + footer_global_contact: "Contact" + + footer_sites_headline: "OFN Sites" + footer_sites_developer: "Developer" + footer_sites_community: "Community" + footer_sites_userguide: "User Guide" + + footer_secure: "Secure and trusted." + footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." + + footer_contact_headline: "Keep in touch" + footer_contact_email: "Email us" + + footer_nav_headline: "Navigate" + footer_join_headline: "Join us" + footer_join_producers: "Producers sign-up" + footer_join_hubs: "Hubs sign-up" + footer_join_groups: "Groups sign-up" + footer_join_partners: "Food systems partners" + + footer_legal_call: "Read our" + footer_legal_tos: "Terms and conditions" + footer_legal_visit: "Find us on" + footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + + home_shop: Shop Now + + brandstory_headline: "Food, unincorporated." + brandstory_intro: "Sometimes the best way to fix the system is to start a new one…" + brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world." + brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day." + brandstory_part3: "So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world." + brandstory_part4: "It works everywhere. It changes everything." + brandstory_part5_strong: "We call it Open Food Network." + brandstory_part6: "We all love food. Now we can love our food system too." + + system_headline: "Here's how it works." + system_step1: "1. Search" + system_step1_text: "Search our diverse, independent shops for seasonal local food. Search by neighbourhood and food category, or whether you prefer delivery or pickup." + system_step2: "2. Shop" + system_step2_text: "Transform your transactions with affordable local food from diverse producers and hubs. Know the stories behind your food and the people who make it!" + system_step3: "3. Pick-up / Delivery" + system_step3_text: "Hang on for your delivery, or visit your producer or hub for a more personal connection with your food. Food shopping as diverse as nature intended it." + + cta_headline: "Shopping that makes the world a better place." + cta_label: "I'm Ready" + + stats_headline: "We're creating a new food system." + stats_producers: "food producers" + stats_shops: "food shops" + stats_shoppers: "food shoppers" + stats_orders: "food orders" + + checkout_title: Checkout + checkout_now: Checkout now + checkout_order_ready: Order ready for + checkout_hide: Hide + checkout_expand: Expand + checkout_headline: "Ok, ready to checkout?" + checkout_as_guest: "Checkout as guest" + checkout_details: "Your details" + checkout_billing: "Billing info" + checkout_shipping: Shipping info + checkout_method_free: Free + checkout_address_same: Shipping address same as billing address? + checkout_ready_for: "Ready for:" + checkout_instructions: "Any comments or special instructions?" + checkout_payment: Payment + checkout_send: Place order now + checkout_your_order: Your order + checkout_cart_total: Cart total + checkout_shipping_price: Shipping + checkout_total_price: Total + checkout_back_to_cart: "Back to Cart" + + order_paid: PAID + order_not_paid: NOT PAID + order_total: Total order + order_payment: "Paying via:" + order_billing_address: Billing address + order_delivery_on: Delivery on + order_delivery_address: Delivery address + order_special_instructions: "Your notes:" + order_pickup_time: Ready for collection + order_pickup_instructions: Collection Instructions + order_produce: Produce + order_total_price: Total + order_includes_tax: (includes tax) + order_payment_paypal_successful: Your payment via PayPal has been processed successfully. + order_hub_info: Hub Info + + bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + bom_shared: "Shared Resource?" + bom_page_title: "Bulk Order Management" + bom_no: "Order no." + bom_date: "Order date" + bom_cycle: "Order cycle" + bom_max: "Max" + bom_hub: "Hub" + bom_variant: "Product: Unit" + bom_final_weigth_volume: "Weight/Volume" + bom_quantity: "Quantity" + bom_actions_delete: "Delete Selected" + bom_loading: "Loading orders" + bom_no_results: "No orders found." + bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." + + unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." + unsaved_changes_error: "Fields with red borders contain errors." + + products: "Products" + products_in: "in %{oc}" + products_at: "at %{distributor}" + products_elsewhere: "Products found elsewhere" + + email_welcome: "Welcome" + email_confirmed: "Thank you for confirming your email address." + email_registered: "is now part of" + email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: +%{link}" + email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration." + email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. +%{link}" + email_help: "If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!" + email_confirmation_greeting: "Hi, %{contact}!" + email_confirmation_profile_created: "A profile for %{name} has been successfully created! +To activate your Profile we need to confirm this email address." + email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." + email_confirmation_link_label: "Confirm this email address »" + email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise. +See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." + email_confirmation_userguide: "User Guide" + email_social: "Connect with Us:" + email_contact: "Email us:" + email_signoff: "Cheers," + email_signature: "%{sitename} Team" + + email_confirm_customer_greeting: "Hi %{name}," + email_confirm_customer_intro_html: "Thanks for shopping at %{distributor}!" + email_confirm_customer_number_html: "Order confirmation #%{number}" + email_confirm_customer_details_html: "Here are your order details from %{distributor}:" + email_confirm_customer_signoff: "Kind regards," + email_confirm_shop_greeting: "Hi %{name}," + email_confirm_shop_order_html: "Well done! You have a new order for %{distributor}!" + email_confirm_shop_number_html: "Order confirmation #%{number}" + email_order_summary_item: "Item" + email_order_summary_quantity: "Qty" + email_order_summary_price: "Price" + email_order_summary_subtotal: "Subtotal:" + email_order_summary_total: "Total:" + email_payment_paid: PAID + email_payment_not_paid: NOT PAID + email_payment_summary: Payment summary + email_payment_method: "Paying via:" + email_shipping_delivery_details: Delivery details + email_shipping_delivery_time: "Delivery on:" + email_shipping_delivery_address: "Delivery address:" + email_shipping_collection_details: Collection details + email_shipping_collection_time: "Ready for collection:" + email_shipping_collection_instructions: "Collection instructions:" + email_special_instructions: "Your notes:" + + email_signup_greeting: Hello! + email_signup_welcome: "Welcome to %{sitename}!" + email_signup_login: Your login + email_signup_email: Your login email is + email_signup_shop_html: "You can start shopping online now at %{link}." + email_signup_text: "Thanks for joining the network. + If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! + If you are a producer or food enterprise, we are excited to have you as a part of the network." + email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + + shopping_oc_closed: Orders are closed + shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" + shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" + shopping_oc_next_open: "The next cycle opens in %{distance_of_time}" + shopping_tabs_about: "About %{distributor}" + shopping_tabs_contact: "Contact" + shopping_contact_address: "Address" + shopping_contact_web: "Contact" + shopping_contact_social: "Follow" + shopping_groups_part_of: "is part of:" + shopping_producers_of_hub: "%{hub}'s producers:" + + enterprises_next_closing: "Next order closing" + enterprises_ready_for: "Ready for" + enterprises_choose: "Choose when you want your order:" + + hubs_buy: "Shop for:" + hubs_shopping_here: "Shopping here" + hubs_orders_closed: "Orders closed" + hubs_profile_only: "Profile only" + hubs_delivery_options: "Delivery options" + hubs_pickup: "Pickup" + hubs_delivery: "Delivery" + hubs_producers: "Our producers" + hubs_filter_by: "Filter by" + hubs_filter_type: "Type" + hubs_filter_delivery: "Delivery" + hubs_matches: "Did you mean?" + hubs_intro: Shop in your local area + hubs_distance: Closest to + hubs_distance_filter: "Show me shops near %{location}" + + products_clear_all: Clear all + products_showing: "Showing:" + products_with: with + products_search: "Search by product or producer" + products_loading: "Loading products..." + products_updating_cart: "Updating cart..." + products_cart_empty: "Cart empty" + products_edit_cart: "Edit your cart" + products_from: from + products_change: "No changes to save." + products_update_error: "Saving failed with the following error(s):" + products_update_error_msg: "Saving failed." + products_update_error_data: "Save failed due to invalid data:" + products_changes_saved: "Changes saved." + + search_no_results_html: "Sorry, no results found for %{query}. Try another search?" + + components_profiles_popover: "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere" + components_profiles_show: "Show profiles" + components_filters_nofilters: "No filters" + components_filters_clearfilters: "Clear all filters" + + groups_title: Groups + groups_headline: Groups / regions + groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you." + groups_search: "Search name or keyword" + groups_no_groups: "No groups found" + groups_about: "About Us" + + groups_producers: "Our producers" + groups_hubs: "Our hubs" + groups_contact_web: Contact + groups_contact_social: Follow + groups_contact_address: Address + groups_contact_email: Email us + groups_contact_website: Visit our website + groups_contact_facebook: Follow us on Facebook + groups_signup_title: Sign up as a group + groups_signup_headline: Groups sign up + groups_signup_intro: "We're an amazing platform for collaborative marketing, the easiest way for your members and stakeholders to reach new markets. We're non-profit, affordable, and simple." + groups_signup_email: Email us + groups_signup_motivation1: We transform food systems fairly. + groups_signup_motivation2: It's why we get out of bed every day. We're a global non-profit, based on open source code. We play fair. You can always trust us. + groups_signup_motivation3: We know you have big ideas, and we want to help. We'll share our knowledge, networks and resources. We know that isolation doesn't create change, so we'll partner with you. + groups_signup_motivation4: We meet you where you are. + groups_signup_motivation5: You might be an alliance of food hubs, producers, or distributors, and an industry body, or a local government. + groups_signup_motivation6: Whatever your role in your local food movement, we're ready to help. However you come to wonder what Open Food Network would look like or is doing in your part of the world, let's start the conversation. + groups_signup_motivation7: We make food movements make more sense. + groups_signup_motivation8: You need to activate and enable your networks, we offer a platform for conversation and action. You need real engagement. We’ll help reach all the players, all the stakeholders, all the sectors. + groups_signup_motivation9: You need resourcing. We’ll bring all our experience to bear. You need cooperation. We’ll better connect you to a global network of peers. + groups_signup_pricing: Group Account + groups_signup_studies: Case Studies + groups_signup_contact: Ready to discuss? + groups_signup_contact_text: "Get in touch to discover what OFN can do for you:" + groups_signup_detail: "Here's the detail." + + login_invalid: "Invalid email or password" + + modal_hubs: "Food Hubs" + modal_hubs_abstract: Our food hubs are the point of contact between you and the people who make your food! + modal_hubs_content1: You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logistics - so variations between hubs are to be expected. + modal_hubs_content2: You can only shop at one food hub at a time. + + modal_groups: "Groups / Regions" + modal_groups_content1: These are the organisations and relationships between hubs which make up the Open Food Network. + modal_groups_content2: Some groups are clustered by location or council, others by non-geographic similarities. + + modal_how: "How it works" + modal_how_shop: Shop the Open Food Network + modal_how_shop_explained: Search for a food hub near you to start shopping! You can expand each food hub to see what kinds of goodies are available, and click through to start shopping. (You can only shop one food hub at a time.) + modal_how_pickup: Pick-ups, delivery and shipping costs + modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected. + modal_how_more: Learn more + modal_how_more_explained: "If you want to learn more about the Open Food Network, how it works, and get involved, check out:" + + modal_producers: "Producers" + modal_producers_explained: "Our producers make all the delicious food you can shop for on the Open Food Network." + + ocs_choice_hub: "Hub:" + ocs_choice_oc: "Order Cycle:" + ocs_choice_text: "You have not yet picked where you will get your order from." + ocs_closed_headline: Orders are currently closed for this hub + ocs_closed_time: "The last cycle closed %{time} ago." + ocs_closed_contact: "Please contact your hub directly to see if they accept late orders, or wait until the next cycle opens." + ocs_closed_opens: "The next order cycle opens in %{time}" + ocs_closed_email: "Email: %{email}" + ocs_closed_phone: "Phone: %{phone}" + ocs_pickup_time: "Your order will be ready on %{pickup_time}" + ocs_change_date: "Change Collection Date" + ocs_change_date_notice: "(This will reset your cart)" + ocs_close_time: "ORDERS CLOSE" + ocs_when_headline: When do you want your order? + ocs_when_text: No products are displayed until you select a date. + ocs_when_closing: "Closing On" + ocs_when_choose: "Choose Order Cycle" + ocs_list: "List View" + + producers_about: About us + producers_buy: Shop for + producers_contact: Contact + producers_contact_phone: Call + producers_contact_social: Follow + producers_buy_at_html: "Shop for %{enterprise} products at:" + producers_filter: Filter by + producers_filter_type: Type + producers_title: Producers + producers_headline: Find local producers + producers_signup_title: Sign up as a producer + producers_signup_headline: Food producers, empowered. + producers_signup_motivation: Sell your food and tell your stories to diverse new markets. Save time and money on every overhead. We support innovation without the risk. We've levelled the playing field. + producers_signup_send: Join now + producers_signup_enterprise: Enterprise Accounts + producers_signup_studies: Stories from our producers. + producers_signup_cta_headline: Join now! + producers_signup_cta_action: Join now + producers_signup_detail: Here's the detail. + producer: Producer + + products_item: Item + products_description: Description + products_variant: Variant + products_quantity: Quantity + products_availabel: Available? + products_producer: "Producer" + products_price: "Price" + products_sku: "SKU" + products_name: "name" + products_unit: "unit" + products_on_hand: "on hand" + products_on_demand: "On demand?" + products_category: "Category" + products_tax_category: "tax category" + products_available_on: "Available On" + products_inherit: "Inherit?" + products_inherits_properties: "Inherits Properties?" + products_stock_level_reset: "Enable Stock Level Reset?" + + register_title: Register + + shops_title: Shops + shops_headline: Shopping, transformed. + shops_text: Food grows in cycles, farmers harvest in cycles, and we order food in cycles. If you find an order cycle closed, check back soon. + shops_signup_title: Sign up as a hub + shops_signup_headline: Food hubs, unlimited. + shops_signup_motivation: Whatever your model, we support you. However you change, we're with you. We're non-profit, independent, and open-sourced. We're the software partners you've dreamed of. + shops_signup_action: Join now + shops_signup_pricing: Enterprise Accounts + shops_signup_stories: Stories from our hubs. + shops_signup_help: We're ready to help. + shops_signup_help_text: You need a better return. You need new buyers and logistics partners. You need your story told across wholesale, retail, and the kitchen table. + shops_signup_detail: Here's the detail. + + orders_fees: Fees... + orders_edit_title: Shopping Cart + orders_edit_headline: Your shopping cart + orders_edit_time: Order ready for + orders_edit_continue: Continue shopping + orders_edit_checkout: Checkout + orders_form_empty_cart: "Empty cart" + orders_form_subtotal: Produce subtotal + orders_form_admin: Admin and handling + orders_form_total: Total + orders_oc_expired_headline: Orders have closed for this order cycle + orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders." + orders_oc_expired_text_others_html: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders %{link}." + orders_oc_expired_text_link: "or see the other order cycles available at this hub" + orders_oc_expired_email: "Email:" + orders_oc_expired_phone: "Phone:" + orders_show_title: Order Confirmation + orders_show_time: Order ready on + orders_show_number: Order confirmation + + products_cart_distributor_choice: "Distributor for your order:" + products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart." + products_cart_distributor_is: "Your distributor for this order is %{name}." + products_distributor_error: "Please complete your order at %{link} before shopping with another distributor." + products_oc: "Order cycle for your order:" + products_oc_change: "Your order cycle for this order will be changed to %{name} if you add this product to your cart." + products_oc_is: "Your order cycle for this order is %{name}." + products_oc_error: "Please complete your order from %{link} before shopping in a different order cycle." + products_oc_current: "your current order cycle" + products_max_quantity: Max quantity + products_distributor: Distributor + products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here. + + # keys used in javascript + password: Password + remember_me: Remember Me + are_you_sure: "Are you sure?" + orders_open: Orders open + closing: "Closing " + going_back_to_home_page: "Taking you back to the home page" + creating: Creating + updating: Updating + failed_to_create_enterprise: "Failed to create your enterprise." + failed_to_create_enterprise_unknown: "Failed to create your enterprise.\nPlease ensure all fields are completely filled out." + failed_to_update_enterprise_unknown: "Failed to update your enterprise.\nPlease ensure all fields are completely filled out." + order_not_saved_yet: "Your order hasn't been saved yet. Give us a few seconds to finish!" + filter_by: "Filter by" + hide_filters: "Hide filters" + one_filter_applied: "1 filter applied" + x_filters_applied: " filters applied" + submitting_order: "Submitting your order: please wait" + confirm_hub_change: "Are you sure? This will change your selected hub and remove any items in your shopping cart." + confirm_oc_change: "Are you sure? This will change your selected order cycle and remove any items in your shopping cart." + location_placeholder: "Type in a location..." + error_required: "can't be blank" + error_number: "must be number" + error_email: "must be email address" + item_handling_fees: "Item Handling Fees (included in item totals)" + january: "January" + february: "February" + march: "March" + april: "April" + may: "May" + june: "June" + july: "July" + august: "August" + september: "September" + october: "October" + november: "November" + december: "December" + email_not_found: "Email address not found" + email_required: "You must provide an email address" + logging_in: "Hold on a moment, we're logging you in" + signup_email: "Your email" + choose_password: "Choose a password" + confirm_password: "Confirm password" + action_signup: "Sign up now" + welcome_to_ofn: "Welcome to the Open Food Network!" + signup_or_login: "Start By Signing Up (or logging in)" + have_an_account: "Already have an account?" + action_login: "Log in now." + forgot_password: "Forgot Password?" + password_reset_sent: "An email with instructions on resetting your password has been sent!" + reset_password: "Reset password" + registration_greeting: "Greetings!" + who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" + enterprise_contact: "Primary Contact" + enterprise_contact_required: "You need to enter a primary contact." + enterprise_email_address: "Email address" + enterprise_phone: "Phone number" + back: "Back" + continue: "Continue" + limit_reached_headline: "Oh no!" + limit_reached_message: "You have reached the limit!" + limit_reached_text: "You have reached the limit for the number of enterprises you are allowed to own on the" + limit_reached_action: "Return to the homepage" + select_promo_image: "Step 3. Select Promo Image" + promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px" + promo_image_label: "Choose a promo image" + action_or: "OR" + promo_image_drag: "Drag and drop your promo here" + review_promo_image: "Step 4. Review Your Promo Banner" + review_promo_image_tip: "Tip: for best results, your promo image should fill the available space" + promo_image_placeholder: "Your logo will appear here for review once uploaded" + uploading: "Uploading..." + select_logo: "Step 1. Select Logo Image" + logo_tip: "Tip: Square images will work best, preferably at least 300×300px" + logo_label: "Choose a logo image" + logo_drag: "Drag and drop your logo here" + review_logo: "Step 2. Review Your Logo" + review_logo_tip: "Tip: for best results, your logo should fill the available space" + logo_placeholder: "Your logo will appear here for review once uploaded" + enterprise_about_headline: "Nice one!" + enterprise_about_message: "Now let's flesh out the details about" + enterprise_success: "Success! %{enterprise} added to the Open Food Network " + enterprise_registration_exit_message: "If you exit this wizard at any stage, you need to click the confirmation link in the email you have received. This will take you to your admin interface where you can continue setting up your profile." + enterprise_description: "Short Description" + enterprise_description_placeholder: "A short sentence describing your enterprise" + enterprise_long_desc: "Long Description" + enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." + enterprise_long_desc_length: "%{num} characters / up to 600 recommended" + enterprise_abn: "ABN" + enterprise_abn_placeholder: "eg. 99 123 456 789" + enterprise_acn: "ACN" + enterprise_acn_placeholder: "eg. 123 456 789" + enterprise_tax_required: "You need to make a selection." + enterprise_final_step: "Final step!" + enterprise_social_text: "How can people find %{enterprise} online?" + website: "Website" + website_placeholder: "eg. openfoodnetwork.org.au" + facebook: "Facebook" + facebook_placeholder: "eg. www.facebook.com/PageNameHere" + linkedin: "LinkedIn" + linkedin_placeholder: "eg. www.linkedin.com/YourNameHere" + twitter: "Twitter" + twitter_placeholder: "eg. @twitter_handle" + instagram: "Instagram" + instagram_placeholder: "eg. @instagram_handle" + registration_greeting: "Hi there!" + registration_intro: "You can now create a profile for your Producer or Hub" + registration_action: "Let's get started!" + registration_checklist: "You'll need" + registration_time: "5-10 minutes" + registration_enterprise_address: "Enterprise address" + registration_contact_details: "Primary contact details" + registration_logo: "Your logo image" + registration_promo_image: "Landscape image for your profile" + registration_about_us: "'About Us' text" + registration_outcome_headline: "What do I get?" + registration_outcome1_html: "Your profile helps people find and contact you on the Open Food Network." + registration_outcome2: "Use this space to tell the story of your enterprise, to help drive connections to your social and online presence. " + registration_outcome3: "It's also the first step towards trading on the Open Food Network, or opening an online store." + registration_finished_headline: "Finished!" + registration_finished_thanks: "Thanks for filling out the details for %{enterprise}." + registration_finished_login: "You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin." + registration_finished_activate: "Activate %{enterprise}." + registration_finished_activate_instruction_html: "We've sent a confirmation email to %{email} if it hasn't been activated before.
+Please follow the instructions there to make your enterprise visible on the Open Food Network." + registration_finished_action: "Open Food Network home" + registration_type_headline: "Last step to add %{enterprise}!" + registration_type_question: "Are you a producer?" + registration_type_producer: "Yes, I'm a producer" + registration_type_no_producer: "No, I'm not a producer" + registration_type_error: "Please choose one. Are you are producer?" + registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." + registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." + create_profile: "Create Profile" + registration_images_headline: "Thanks!" + registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" + registration_detail_headline: "Let's Get Started" + registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:" + registration_detail_producer: "Woot! First we need to know a little bit about your farm:" + registration_detail_name_enterprise: "Enterprise Name:" + registration_detail_name_producer: "Farm Name:" + registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm" + registration_detail_name_error: "Please choose a unique name for your enterprise" + registration_detail_address1: "Address line 1:" + registration_detail_address1_placeholder: "e.g. 123 Cranberry Drive" + registration_detail_address1_error: "Please enter an address" + registration_detail_address2: "Address line 2:" + registration_detail_suburb: "City:" + registration_detail_suburb_placeholder: "e.g. New York City" + registration_detail_suburb_error: "Please enter a city" + registration_detail_postcode: "Postcode:" + registration_detail_postcode_placeholder: "e.g. 10019" + registration_detail_postcode_error: "Postcode required" + registration_detail_state: "State:" + registration_detail_state_error: "State required" + registration_detail_country: "Country:" + registration_detail_country_error: "Please select a country" + fees: "Fees" + item_cost: "Item cost" + bulk: "Bulk" + shop_variant_quantity_min: "min" + shop_variant_quantity_max: "max" + contact: "Contact" + follow: "Follow" + shop_for_products_html: "Shop for %{enterprise} products at:" + change_shop: "Change shop to:" + shop_at: "Shop now at:" + price_breakdown: "Full price breakdown" + admin_fee: "Admin fee" + sales_fee: "Sales fee" + packing_fee: "Packing fee" + transport_fee: "Transport fee" + fundraising_fee: "Fundraising fee" + price_graph: "Price graph" + included_tax: "Included tax" + remove_tax: "Remove tax" + balance: "Balance" + transaction: "Transaction" + transaction_date: "Date" #Transaction is only in key to avoid conflict with :date + payment_state: "Payment status" + shipping_state: "Shipping status" + value: "Value" + balance_due: "Balance due" + credit: "Credit" + Paid: "Paid" + Ready: "Ready" + you_have_no_orders_yet: "You have no orders yet" + running_balance: "Running balance" + outstanding_balance: "Outstanding balance" + admin_entreprise_relationships: "Enterprise Relationships" + admin_entreprise_relationships_everything: "Everything" + admin_entreprise_relationships_permits: "permits" + admin_entreprise_relationships_seach_placeholder: "Search" + admin_entreprise_relationships_button_create: "Create" + admin_entreprise_groups: "Enterprise Groups" + admin_entreprise_groups_name: "Name" + admin_entreprise_groups_owner: "Owner" + admin_entreprise_groups_on_front_page: "On front page ?" + admin_entreprise_groups_entreprise: "Enterprises" + admin_entreprise_groups_primary_details: "Primary Details" + admin_entreprise_groups_data_powertip: "The primary user responsible for this group." + admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" + admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_entreprise_groups_about: "About" + admin_entreprise_groups_images: "Images" + admin_entreprise_groups_contact: "Contact" + admin_entreprise_groups_contact_phone_placeholder: "eg. 212 765 3210" + admin_entreprise_groups_contact_address1_placeholder: "eg. 100 Martin Luther King Drive" + admin_entreprise_groups_contact_city: "City" + admin_entreprise_groups_contact_city_placeholder: "eg. NYC, NY" + admin_entreprise_groups_contact_zipcode: "Postcode" + admin_entreprise_groups_contact_zipcode_placeholder: "eg. 10019" + admin_entreprise_groups_contact_state_id: "State" + admin_entreprise_groups_contact_country_id: "Country" + admin_entreprise_groups_web: "Web Resources" + admin_entreprise_groups_web_twitter: "eg. @the_prof" + admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com" + admin_order_cycles: "Admin Order Cycles" + open: "Open" + close: "Close" + supplier: "Supplier" + coordinator: "Coordinator" + distributor: "Distributor" + product: "Products" + enterprise_fees: "Enterprise Fees" + fee_type: "Fee Type" + tax_category: "Tax Category" + calculator: "Calculator" + calculator_values: "Calculator values" + new_order_cycles: "New Order Cycles" + select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle" + edit_order_cycle: "Edit Order Cycle" + roles: "Roles" + update: "Update" + add_producer_property: "Add producer property" + admin_settings: "Settings" + update_invoice: "Update Invoices" + finalise_invoice: "Finalise Invoices" + finalise_user_invoices: "Finalise User Invoices" + finalise_user_invoice_explained: "Use this button to finalize all invoices in the system for the previous calendar month. This task can be set up to run automatically once a month." + manually_run_task: "Manually Run Task " + update_user_invoices: "Update User Invoices" + update_user_invoice_explained: "Use this button to immediately update invoices for the month to date for each enterprise user in the system. This task can be set up to run automatically every night." + auto_finalise_invoices: "Auto-finalise invoices monthly on the 2nd at 1:30am" + auto_update_invoices: "Auto-update invoices nightly at 1:00am" + in_progress: "In Progress" + started_at: "Started at" + queued: "Queued" + scheduled_for: "Scheduled for" + customers: "Customers" + please_select_hub: "Please select a Hub" + loading_customers: "Loading Customers" + no_customers_found: "No customers found" + go: "Go" + hub: "Hub" + accounts_administration_distributor: "accounts administration distributor" + accounts_and_billing: "Accounts & Billing" + producer: "Producer" + product: "Product" + price: "Price" + on_hand: "On hand" + save_changes: "Save Changes" + spree_admin_overview_enterprises_header: "My Enterprises" + spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES" + spree_admin_enterprises_hubs_name: "Name" + spree_admin_enterprises_create_new: "CREATE NEW" + spree_admin_enterprises_shipping_methods: "Shipping Methods" + spree_admin_enterprises_fees: "Enterprise Fees" + spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" + spree_admin_enterprises_none_text: "You don't have any enterprises yet" + spree_admin_enterprises_producers_name: "Name" + spree_admin_enterprises_producers_total_products: "Total Products" + spree_admin_enterprises_producers_active_products: "Active Products" + spree_admin_enterprises_producers_order_cycles: "Products in OCs" + spree_admin_enterprises_producers_order_cycles_title: "" + spree_admin_enterprises_tabs_hubs: "HUBS" + spree_admin_enterprises_tabs_producers: "PRODUCERS" + spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES" + spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" + spree_admin_enterprises_producers_orders_cycle_text: "You don't have any active order cycles." + spree_admin_enterprises_any_active_products_text: "You don't have any active products." + spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" + spree_admin_order_cycles: "Order Cycles" + spree_admin_order_cycles_tip: "Order cycles determine when and where your products are available to customers." + dashbord: "Dashboard" + spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" + spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" + spree_admin_overview_action_required: "Action Required" + spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!" + change_package: "Change Package" + spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" + your_profil_live: "Your profile live" + on_ofn_map: "on the Open Food Network map" + see: "See" + live: "live" + manage: "Manage" + resend: "Resend" + 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" + edit_profile_details_etc: "Change your profile description, images, etc." + start_date: "Start Date" + end_date: "End Date" + order_cycle: "Order Cycle" + group_buy_unit_size: "Group Buy Unit Size" + total_qtt_ordered: "Total Quantity Ordered" + max_qtt_ordered: "Max Quantity Ordered" + current_fulfilled_units: "Current Fulfilled Units" + max_fulfilled_units: "Max Fulfilled Units" + bulk_management_warning: "WARNING: Some variants do not have a unit value" + ask: "Ask?" + no_orders_found: "No orders found." + order_no: "Order No." + weight_volume: "Weight/Volume" + remove_tax: "Remove tax" + tax_settings: "Tax Settings" + products_require_tax_category: "products require tax category" + admin_shared_address_1: "Address" + admin_shared_address_2: "Address (cont.)" + admin_share_city: "City" + admin_share_zipcode: "Postcode" + admin_share_country: "Country" + admin_share_state: "State" + hub_sidebar_hubs: "Hubs" + hub_sidebar_none_available: "None Available" + hub_sidebar_manage: "Manage" + 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 %{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_producers: "Producers: " + report_type: "Report Type: " + report_hubs: "Hubs: " + report_payment: "Payment Methods: " + report_distributor: "Distributor: " + report_payment_by: 'Payments By Type' + report_itemised_payment: 'Itemised Payment Totals' + report_payment_totals: 'Payment Totals' + report_all: 'all' + report_order_cycle: "Order Cycle: " + report_entreprises: "Enterprises: " + report_users: "Users: " + initial_invoice_number: "Initial invoice number:" + invoice_date: "Invoice date:" + due_date: "Due date:" + 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" + saving: "Saving.." + success: "success" + failure: "failure" + unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?" + one_product_unsaved: "Changes to one product remain unsaved." + products_unsaved: "Changes to %{n} products remain unsaved." + add_manager: "Add a manager" + is_already_manager: "is already a manager!" + no_change_to_save: " No change to save" + add_manager: "Add a manager" + users: "Users" + about: "About" + images: "Images" + contact: "Contact" + web: "Web" + primary_details: "Primary Details" + adrdress: "Address" + contact: "Contact" + social: "Social" + business_details: "Business Details" + properties: "Properties" + shipping_methods: "Shipping Methods" + 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" + validation_msg_product_category_cant_be_blank: "^Product Category cant be blank" + validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" + validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" + + spree: + shipment_states: + backorder: backorder + partial: partial + pending: pending + ready: ready + shipped: shipped + payment_states: + balance_due: balance due + completed: completed + checkout: checkout + credit_owed: credit owed + failed: failed + paid: paid + pending: pending + processing: processing + void: void + order_state: + address: address + adjustments: adjustments + awaiting_return: awaiting return + canceled: canceled + cart: cart + complete: complete + confirm: confirm + delivery: delivery + payment: payment + resumed: resumed + returned: returned + skrill: skrill diff --git a/config/locales/en.yml b/config/locales/en.yml index 87e47e18fb..a752944e5c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -116,6 +116,7 @@ en: valid_email_error: Please enter a valid email address add_a_new_customer_for: Add a new customer for %{shop_name} code: Code + duplicate_code: "This code is used already." products: bulk_edit: @@ -869,12 +870,9 @@ Please follow the instructions there to make your enterprise visible on the Open admin_entreprise_groups_owner: "Owner" admin_entreprise_groups_on_front_page: "On front page ?" admin_entreprise_groups_entreprise: "Enterprises" - admin_entreprise_groups_primary_details: "Primary Details" admin_entreprise_groups_data_powertip: "The primary user responsible for this group." admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" - admin_entreprise_groups_about: "About" - admin_entreprise_groups_images: "Images" admin_entreprise_groups_contact: "Contact" admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" @@ -1041,6 +1039,7 @@ Please follow the instructions there to make your enterprise visible on the Open properties: "Properties" shipping_methods: "Shipping Methods" payment_methods: "Payment Methods" + payment_method_fee: "Transaction fee" enterprise_fees: "Enterprise Fees" inventory_settings: "Inventory Settings" tag_rules: "Tag Rules" diff --git a/db/migrate/20160520065217_add_is_default_to_tag_rule.rb b/db/migrate/20160520065217_add_is_default_to_tag_rule.rb new file mode 100644 index 0000000000..209520b693 --- /dev/null +++ b/db/migrate/20160520065217_add_is_default_to_tag_rule.rb @@ -0,0 +1,5 @@ +class AddIsDefaultToTagRule < ActiveRecord::Migration + def change + add_column :tag_rules, :is_default, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20160527012603_add_priority_to_tag_rule.rb b/db/migrate/20160527012603_add_priority_to_tag_rule.rb new file mode 100644 index 0000000000..7080d7a9ac --- /dev/null +++ b/db/migrate/20160527012603_add_priority_to_tag_rule.rb @@ -0,0 +1,5 @@ +class AddPriorityToTagRule < ActiveRecord::Migration + def change + add_column :tag_rules, :priority, :integer, default: 99, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 60b72adeea..002e367dd6 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 => 20160401043927) do +ActiveRecord::Schema.define(:version => 20160527012603) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -1159,10 +1159,12 @@ ActiveRecord::Schema.define(:version => 20160401043927) do 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 + t.integer "enterprise_id", :null => false + t.string "type", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.boolean "is_default", :default => false, :null => false + t.integer "priority", :default => 99, :null => false end create_table "taggings", :force => true do |t| diff --git a/lib/open_food_network/cached_products_renderer.rb b/lib/open_food_network/cached_products_renderer.rb index 2174bf4094..eada6a7c9e 100644 --- a/lib/open_food_network/cached_products_renderer.rb +++ b/lib/open_food_network/cached_products_renderer.rb @@ -14,19 +14,11 @@ module OpenFoodNetwork end def products_json - raise NoProducts.new if @distributor.nil? || @order_cycle.nil? + raise NoProducts.new("No Products") if @distributor.nil? || @order_cycle.nil? - products_json = Rails.cache.fetch("products-json-#{@distributor.id}-#{@order_cycle.id}") do - log_warning + products_json = cached_products_json - begin - uncached_products_json - rescue ProductsRenderer::NoProducts - nil - end - end - - raise NoProducts.new if products_json.nil? + raise NoProducts.new("No Products") if products_json.nil? products_json end @@ -40,6 +32,22 @@ module OpenFoodNetwork end end + def cached_products_json + if Rails.env.production? || Rails.env.staging? + Rails.cache.fetch("products-json-#{@distributor.id}-#{@order_cycle.id}") do + log_warning + + begin + uncached_products_json + rescue ProductsRenderer::NoProducts + nil + end + end + else + uncached_products_json + end + end + def uncached_products_json ProductsRenderer.new(@distributor, @order_cycle).products_json end diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb index c18ac79550..09dc197832 100644 --- a/lib/open_food_network/column_preference_defaults.rb +++ b/lib/open_food_network/column_preference_defaults.rb @@ -19,6 +19,7 @@ module OpenFoodNetwork on_demand: { name: I18n.t("admin.on_demand?"), visible: false }, reset: { name: I18n.t("#{node}.enable_reset?"), visible: false }, inheritance: { name: I18n.t("#{node}.inherit?"), visible: false }, + tags: { name: I18n.t("admin.tags"), visible: false }, visibility: { name: I18n.t("#{node}.hide"), visible: false } } end diff --git a/lib/open_food_network/order_cycle_form_applicator.rb b/lib/open_food_network/order_cycle_form_applicator.rb index 6d2e3e3e53..1df429b4fb 100644 --- a/lib/open_food_network/order_cycle_form_applicator.rb +++ b/lib/open_food_network/order_cycle_form_applicator.rb @@ -42,13 +42,15 @@ module OpenFoodNetwork {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, pickup_time: exchange[:pickup_time], - pickup_instructions: exchange[:pickup_instructions]}) + pickup_instructions: exchange[:pickup_instructions], + tag_list: exchange[:tag_list]}) else add_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false, {variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids, pickup_time: exchange[:pickup_time], - pickup_instructions: exchange[:pickup_instructions]}) + pickup_instructions: exchange[:pickup_instructions], + tag_list: exchange[:tag_list]}) end end @@ -81,6 +83,7 @@ module OpenFoodNetwork attrs.delete :enterprise_fee_ids attrs.delete :pickup_time attrs.delete :pickup_instructions + attrs.delete :tag_list end if permission_for exchange @@ -129,71 +132,43 @@ module OpenFoodNetwork editable_variants_for_outgoing_exchanges_to(receiver).pluck(:id) end - def find_incoming_exchange(attrs) - @order_cycle.exchanges. - where(:sender_id => attrs[:enterprise_id], :receiver_id => @order_cycle.coordinator_id, :incoming => true).first - end - - def find_outgoing_exchange(attrs) - @order_cycle.exchanges. - where(:sender_id => @order_cycle.coordinator_id, :receiver_id => attrs[:enterprise_id], :incoming => false).first - end - - def persisted_variants_hash(exchange) - return {} unless exchange - - # When we have permission to edit a variant, mark it for removal here, assuming it will be included again if that is what the use wants - # When we don't have permission to edit a variant and it is already in the exchange, keep it in the exchange. - method_name = "editable_variant_ids_for_#{ exchange.incoming? ? 'incoming' : 'outgoing' }_exchange_between" - editable = send(method_name, exchange.sender, exchange.receiver) - Hash[ exchange.variants.map { |v| [v.id, editable.exclude?(v.id)] } ] + def find_exchange(sender_id, receiver_id, incoming) + @order_cycle.exchanges.find_by_sender_id_and_receiver_id_and_incoming(sender_id, receiver_id, incoming) end def incoming_exchange_variant_ids(attrs) - exchange = find_incoming_exchange(attrs) - variants = persisted_variants_hash(exchange) - - sender = exchange.andand.sender || Enterprise.find(attrs[:enterprise_id]) + sender = Enterprise.find(attrs[:enterprise_id]) receiver = @order_cycle.coordinator - permitted = editable_variant_ids_for_incoming_exchange_between(sender, receiver) + exchange = find_exchange(sender.id, receiver.id, true) - # Only change visibility for variants I have permission to edit - attrs[:variants].each do |variant_id, value| - variants[variant_id.to_i] = value if permitted.include?(variant_id.to_i) - end + requested_ids = attrs[:variants].select{ |k,v| v }.keys.map(&:to_i) # Only the ids the user has requested + existing_ids = exchange.present? ? exchange.variants.pluck(:id) : [] # The ids that already exist + editable_ids = editable_variant_ids_for_incoming_exchange_between(sender, receiver) # The ids we are allowed to add/remove - variants_to_a variants + result = existing_ids + + result |= (requested_ids & editable_ids) # add any requested & editable ids that are not yet in the exchange + result -= ((result & editable_ids) - requested_ids) # remove any editable ids that were not specifically mentioned in the request + + result end def outgoing_exchange_variant_ids(attrs) - exchange = find_outgoing_exchange(attrs) - variants = persisted_variants_hash(exchange) - sender = @order_cycle.coordinator - receiver = exchange.andand.receiver || Enterprise.find(attrs[:enterprise_id]) - permitted = editable_variant_ids_for_outgoing_exchange_between(sender, receiver) + receiver = Enterprise.find(attrs[:enterprise_id]) + exchange = find_exchange(sender.id, receiver.id, false) - # Only change visibility for variants I have permission to edit - attrs[:variants].each do |variant_id, value| - variant_id = variant_id.to_i + requested_ids = attrs[:variants].select{ |k,v| v }.keys.map(&:to_i) # Only the ids the user has requested + existing_ids = exchange.present? ? exchange.variants.pluck(:id) : [] # The ids that already exist + editable_ids = editable_variant_ids_for_outgoing_exchange_between(sender, receiver) # The ids we are allowed to add/remove - variants = update_outgoing_variants(variants, permitted, variant_id, value) - end + result = existing_ids - variants_to_a variants - end + result |= (requested_ids & editable_ids) # add any requested & editable ids that are not yet in the exchange + result -= (result - incoming_variant_ids) # remove any ids not in incoming exchanges + result -= ((result & editable_ids) - requested_ids) # remove any editable ids that were not specifically mentioned in the request - def update_outgoing_variants(variants, permitted, variant_id, value) - if !incoming_variant_ids.include? variant_id - # When a variant has been removed from incoming but remains - # in outgoing, remove it from outgoing too - variants[variant_id] = false - - elsif permitted.include? variant_id - variants[variant_id] = value - end - - variants + result end def incoming_variant_ids diff --git a/lib/open_food_network/orders_and_fulfillments_report.rb b/lib/open_food_network/orders_and_fulfillments_report.rb index 52d07771a2..089838a1b1 100644 --- a/lib/open_food_network/orders_and_fulfillments_report.rb +++ b/lib/open_food_network/orders_and_fulfillments_report.rb @@ -18,7 +18,14 @@ module OpenFoodNetwork ["Hub", "Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"] when "order_cycle_customer_totals" ["Hub", "Customer", "Email", "Phone", "Producer", "Product", "Variant", - "Amount", "Item (#{currency_symbol})", "Item + Fees (#{currency_symbol})", "Admin & Handling (#{currency_symbol})", "Ship (#{currency_symbol})", "Total (#{currency_symbol})", "Paid?", + "Amount", + "Item (#{currency_symbol})", + "Item + Fees (#{currency_symbol})", + "Admin & Handling (#{currency_symbol})", + "Ship (#{currency_symbol})", + "Pay fee (#{currency_symbol})", + "Total (#{currency_symbol})", + "Paid?", "Shipping", "Delivery?", "Ship Street", "Ship Street 2", "Ship City", "Ship Postcode", "Ship State", "Comments", "SKU", @@ -118,6 +125,7 @@ module OpenFoodNetwork proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } }, proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.admin_and_handling_total } }, proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } }, + proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.payment_fee } }, proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.total } }, proc { |line_items| line_items.all? { |li| li.order.paid? } ? "Yes" : "No" }, diff --git a/lib/open_food_network/property_merge.rb b/lib/open_food_network/property_merge.rb new file mode 100644 index 0000000000..9c21fb7b0d --- /dev/null +++ b/lib/open_food_network/property_merge.rb @@ -0,0 +1,18 @@ +module OpenFoodNetwork + class PropertyMerge + def self.merge(primary, secondary) + primary + secondary.reject do |secondary_p| + primary.any? do |primary_p| + property_of(primary_p).presentation == property_of(secondary_p).presentation + end + end + end + + + private + + def self.property_of(p) + p.respond_to?(:property) ? p.property : p + end + end +end diff --git a/lib/open_food_network/scope_product_to_hub.rb b/lib/open_food_network/scope_product_to_hub.rb index d3e018f295..98ffbd84ff 100644 --- a/lib/open_food_network/scope_product_to_hub.rb +++ b/lib/open_food_network/scope_product_to_hub.rb @@ -4,7 +4,7 @@ module OpenFoodNetwork class ScopeProductToHub def initialize(hub) @hub = hub - @variant_overrides = VariantOverride.indexed @hub + @variant_overrides = VariantOverride.indexed(@hub) end def scope(product) diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index 37a2455616..dadcfbd9a8 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -50,6 +50,10 @@ module OpenFoodNetwork def sku @variant_override.andand.sku || super end + + def tag_list + @variant_override.andand.tag_list || [] + end end end end diff --git a/lib/open_food_network/tag_rule_applicator.rb b/lib/open_food_network/tag_rule_applicator.rb new file mode 100644 index 0000000000..5225dfdd86 --- /dev/null +++ b/lib/open_food_network/tag_rule_applicator.rb @@ -0,0 +1,61 @@ +module OpenFoodNetwork + class TagRuleApplicator + attr_reader :enterprise, :rule_class, :customer_tags + + def initialize(enterprise, rule_type, customer_tags=[]) + raise "Enterprise cannot be nil" if enterprise.nil? + raise "Rule Type cannot be nil" if rule_type.nil? + + @enterprise = enterprise + @rule_class = "TagRule::#{rule_type}".constantize + @customer_tags = customer_tags || [] + end + + def filter!(subject) + return unless subject.respond_to?(:any?) && subject.any? + subject.reject! do |element| + if rule_class.respond_to?(:tagged_children_for) + children = rule_class.tagged_children_for(element) + children.reject! { |child| reject?(child) } + children.empty? + else + reject?(element) + end + end + end + + private + + def reject?(element) + customer_rules.each do |rule| + return rule.reject_matched? if rule.tags_match?(element) + end + + default_rules.each do |rule| + return rule.reject_matched? if rule.tags_match?(element) + end + + false + end + + def rules + return @rules unless @rules.nil? + @rules = rule_class.prioritised.for(enterprise) + end + + def customer_rules + return @customer_matched_rules unless @customer_matched_rules.nil? + @customer_matched_rules = rules.select{ |rule| customer_tags_match?(rule) } + end + + def default_rules + return @default_rules unless @default_rules.nil? + @default_rules = rules.select(&:is_default?) + end + + def customer_tags_match?(rule) + preferred_tags = rule.preferred_customer_tags.split(",") + (customer_tags & preferred_tags).any? + end + end +end diff --git a/lib/open_food_network/xero_invoices_report.rb b/lib/open_food_network/xero_invoices_report.rb index 331c8b204c..cab44aa55e 100644 --- a/lib/open_food_network/xero_invoices_report.rb +++ b/lib/open_food_network/xero_invoices_report.rb @@ -88,6 +88,7 @@ module OpenFoodNetwork rows += produce_summary_rows(order, invoice_number, opts) unless detail? rows += fee_summary_rows(order, invoice_number, opts) unless detail? && order.account_invoice? rows += shipping_summary_rows(order, invoice_number, opts) + rows += payment_summary_rows(order, invoice_number, opts) rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail? rows @@ -107,6 +108,10 @@ module OpenFoodNetwork [summary_row(order, 'Delivery Shipping Cost (tax inclusive)', total_shipping(order), invoice_number, tax_on_shipping_s(order), opts)] end + def payment_summary_rows(order, invoice_number, opts) + [summary_row(order, 'Transaction Fee (no tax)', total_transaction(order), invoice_number, 'GST Free Income', opts)] + end + def admin_adjustment_summary_rows(order, invoice_number, opts) [summary_row(order, 'Total untaxable admin adjustments (no tax)', total_untaxable_admin_adjustments(order), invoice_number, 'GST Free Income', opts), summary_row(order, 'Total taxable admin adjustments (tax inclusive)', total_taxable_admin_adjustments(order), invoice_number, 'GST on Income', opts)] @@ -189,6 +194,10 @@ module OpenFoodNetwork order.adjustments.shipping.sum &:amount end + def total_transaction(order) + order.adjustments.payment_fee.sum &:amount + end + def tax_on_shipping_s(order) tax_on_shipping = order.adjustments.shipping.sum(&:included_tax) > 0 tax_on_shipping ? 'GST on Income' : 'GST Free Income' diff --git a/public/404.html b/public/404.html index b650949066..4e42e63b73 100644 --- a/public/404.html +++ b/public/404.html @@ -3,24 +3,25 @@ The page you were looking for doesn't exist (404) @@ -28,10 +29,13 @@
- -

It seems the page you're looking for is in a grump. -

Return home

-

+ +

Oops! Page not found.

+ Please try again +

This might be a temporary problem. Please click the back button to return to the previous screen or go back to Home and try again.

+ Contact support +

If the problem persists or is urgent, please tell us about it. Find our contact details from the global Open Food Network Local page.

+

It really helps us if you can give as much detail as possible about what the missing page is about.

diff --git a/public/404.jpg b/public/404.jpg deleted file mode 100644 index 836b4e0188..0000000000 Binary files a/public/404.jpg and /dev/null differ diff --git a/public/500.html b/public/500.html index 5c7fa05f07..b3dd25194a 100644 --- a/public/500.html +++ b/public/500.html @@ -3,24 +3,25 @@ We're sorry, but something went wrong (500) @@ -29,17 +30,14 @@
-

We're sorry, but something went wrong. -
Try refreshing the page, or -

Return home

-
Want to let us know what went wrong? Email us at: -

- - hello [at] openfoodnetwork.org -

-

+

We're sorry, but something went wrong.

+ Please try again +

This might be a temporary problem. Please click the back button to return to the previous screen or go back to Home and try again.

+ We're on it +

If you have seen this problem before, we probably already know about it and are working on a fix. We record all the errors that come up.

+ Contact support +

If the problem persists or is urgent, please tell us about it. Find our contact details from the global Open Food Network Local page.

+

It really helps us if you can give as much detail as possible about what you were doing when this error occurred.

diff --git a/public/500.jpg b/public/500.jpg index 2cfabee423..f1c75efe6c 100644 Binary files a/public/500.jpg and b/public/500.jpg differ diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb index f64a8057e8..3c75929972 100644 --- a/spec/controllers/admin/customers_controller_spec.rb +++ b/spec/controllers/admin/customers_controller_spec.rb @@ -69,12 +69,15 @@ describe Admin::CustomersController, type: :controller do let!(:customer) { create(:customer, enterprise: enterprise) } context "where I manage the customer's enterprise" do + render_views + before do controller.stub spree_current_user: enterprise.owner end it "allows me to update the customer" do spree_put :update, format: :json, id: customer.id, customer: { email: 'new.email@gmail.com' } + expect(JSON.parse(response.body)["id"]).to eq customer.id expect(assigns(:customer)).to eq customer expect(customer.reload.email).to eq 'new.email@gmail.com' end diff --git a/spec/controllers/enterprises_controller_spec.rb b/spec/controllers/enterprises_controller_spec.rb index 0727488841..a4e66313dd 100644 --- a/spec/controllers/enterprises_controller_spec.rb +++ b/spec/controllers/enterprises_controller_spec.rb @@ -4,51 +4,92 @@ describe EnterprisesController do describe "shopping for a distributor" do let(:order) { controller.current_order(true) } - before(:each) do - @current_distributor = create(:distributor_enterprise, with_payment_and_shipping: true) - @distributor = create(:distributor_enterprise, with_payment_and_shipping: true) - @order_cycle1 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 2.days.ago, orders_close_at: 3.days.from_now ) - @order_cycle2 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 3.days.ago, orders_close_at: 4.days.from_now ) - order.set_distributor! @current_distributor + + let!(:current_distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:order_cycle1) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 2.days.ago, orders_close_at: 3.days.from_now ) } + let!(:order_cycle2) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, orders_close_at: 4.days.from_now ) } + + before do + order.set_distributor! current_distributor end it "sets the shop as the distributor on the order when shopping for the distributor" do - spree_get :shop, {id: @distributor} + spree_get :shop, {id: distributor} - controller.current_order.distributor.should == @distributor + controller.current_order.distributor.should == distributor controller.current_order.order_cycle.should be_nil end it "sorts order cycles by the distributor's preferred ordering attr" do - @distributor.update_attribute(:preferred_shopfront_order_cycle_order, 'orders_close_at') - spree_get :shop, {id: @distributor} - assigns(:order_cycles).should == [@order_cycle1, @order_cycle2].sort_by(&:orders_close_at) + distributor.update_attribute(:preferred_shopfront_order_cycle_order, 'orders_close_at') + spree_get :shop, {id: distributor} + assigns(:order_cycles).should == [order_cycle1, order_cycle2].sort_by(&:orders_close_at) - @distributor.update_attribute(:preferred_shopfront_order_cycle_order, 'orders_open_at') - spree_get :shop, {id: @distributor} - assigns(:order_cycles).should == [@order_cycle1, @order_cycle2].sort_by(&:orders_open_at) + distributor.update_attribute(:preferred_shopfront_order_cycle_order, 'orders_open_at') + spree_get :shop, {id: distributor} + assigns(:order_cycles).should == [order_cycle1, order_cycle2].sort_by(&:orders_open_at) + end + + context "using FilterOrderCycles tag rules" do + let(:user) { create(:user) } + let!(:order_cycle3) { create(:simple_order_cycle, distributors: [distributor], orders_open_at: 3.days.ago, orders_close_at: 4.days.from_now) } + let!(:oc3_exchange) { order_cycle3.exchanges.outgoing.to_enterprise(distributor).first } + let(:customer) { create(:customer, user: user, enterprise: distributor) } + + it "shows order cycles allowed by the rules" do + create(:filter_order_cycles_tag_rule, + enterprise: distributor, + preferred_customer_tags: "wholesale", + preferred_exchange_tags: "wholesale", + preferred_matched_order_cycles_visibility: 'visible') + create(:filter_order_cycles_tag_rule, + enterprise: distributor, + is_default: true, + preferred_exchange_tags: "wholesale", + preferred_matched_order_cycles_visibility: 'hidden') + + spree_get :shop, {id: distributor} + expect(assigns(:order_cycles)).to include order_cycle1, order_cycle2, order_cycle3 + + allow(controller).to receive(:spree_current_user) { user } + + spree_get :shop, {id: distributor} + expect(assigns(:order_cycles)).to include order_cycle1, order_cycle2, order_cycle3 + + oc3_exchange.update_attribute(:tag_list, "wholesale") + + spree_get :shop, {id: distributor} + expect(assigns(:order_cycles)).to include order_cycle1, order_cycle2 + expect(assigns(:order_cycles)).not_to include order_cycle3 + + customer.update_attribute(:tag_list, ["wholesale"]) + + spree_get :shop, {id: distributor} + expect(assigns(:order_cycles)).to include order_cycle1, order_cycle2, order_cycle3 + end end it "empties an order that was set for a previous distributor, when shopping at a new distributor" do line_item = create(:line_item) controller.current_order.line_items << line_item - spree_get :shop, {id: @distributor} + spree_get :shop, {id: distributor} - controller.current_order.distributor.should == @distributor + controller.current_order.distributor.should == distributor controller.current_order.order_cycle.should be_nil controller.current_order.line_items.size.should == 0 end it "should not empty an order if returning to the same distributor" do product = create(:product) - create(:product_distribution, product: product, distributor: @current_distributor) + create(:product_distribution, product: product, distributor: current_distributor) line_item = create(:line_item, variant: product.master) controller.current_order.line_items << line_item - spree_get :shop, {id: @current_distributor} + spree_get :shop, {id: current_distributor} - controller.current_order.distributor.should == @current_distributor + controller.current_order.distributor.should == current_distributor controller.current_order.order_cycle.should be_nil controller.current_order.line_items.size.should == 1 end @@ -56,10 +97,10 @@ describe EnterprisesController do describe "when an out of stock item is in the cart" do let(:variant) { create(:variant, on_demand: false, on_hand: 10) } let(:line_item) { create(:line_item, variant: variant) } - let(:order_cycle) { create(:simple_order_cycle, distributors: [@distributor], variants: [variant]) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], variants: [variant]) } before do - order.set_distribution! @current_distributor, order_cycle + order.set_distribution! current_distributor, order_cycle order.line_items << line_item Spree::Config.set allow_backorders: false @@ -68,19 +109,19 @@ describe EnterprisesController do end it "redirects to the cart" do - spree_get :shop, {id: @current_distributor} + spree_get :shop, {id: current_distributor} response.should redirect_to spree.cart_path end end it "sets order cycle if only one is available at the chosen distributor" do - @order_cycle2.destroy + order_cycle2.destroy - spree_get :shop, {id: @distributor} + spree_get :shop, {id: distributor} - controller.current_order.distributor.should == @distributor - controller.current_order.order_cycle.should == @order_cycle1 + controller.current_order.distributor.should == distributor + controller.current_order.order_cycle.should == order_cycle1 end end diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index a4ddab3ad3..c3784df428 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -8,7 +8,6 @@ describe ShopController do response.should redirect_to root_path end - describe "with a distributor in place" do before do controller.stub(:current_distributor).and_return distributor @@ -94,5 +93,137 @@ describe ShopController do end end end + + describe "determining rule relevance" do + let(:products_json) { double(:products_json) } + let(:applicator) { double(:applicator) } + + before do + allow(applicator).to receive(:rules) { tag_rules } + allow(controller).to receive(:applicator) { applicator } + allow(controller).to receive(:filter) { "some filtered json" } + end + + context "when no relevant rules exist" do + let(:tag_rules) { [] } + + it "does not attempt to apply any rules" do + controller.send(:filtered_json, products_json) + expect(expect(controller).to_not have_received(:filter)) + end + + it "returns products as JSON" do + expect(controller.send(:filtered_json, products_json)).to eq products_json + end + end + + context "when relevant rules exist" do + let(:tag_rule) { create(:filter_products_tag_rule, preferred_customer_tags: "tag1", preferred_variant_tags: "tag1", preferred_matched_variants_visibility: "hidden" ) } + let(:tag_rules) { [tag_rule] } + + it "attempts to apply any rules" do + controller.send(:filtered_json, products_json) + expect(controller).to have_received(:filter).with(products_json) + end + + it "returns filtered JSON" do + expect(controller.send(:filtered_json, products_json)).to eq "some filtered json" + end + end + end + + describe "loading available order cycles" do + let(:user) { create(:user) } + before { allow(controller).to receive(:spree_current_user) { user } } + + context "when FilterProducts tag rules are in effect" do + let(:customer) { create(:customer, user: user, enterprise: distributor) } + let!(:tag_rule) { create(:filter_products_tag_rule, + enterprise: distributor, + preferred_customer_tags: "member", + preferred_variant_tags: "members-only") } + let!(:default_tag_rule) { create(:filter_products_tag_rule, + enterprise: distributor, + is_default: true, + preferred_variant_tags: "members-only") } + let(:product1) { { "id" => 1, "name" => 'product 1', "variants" => [{ "id" => 4, "tag_list" => ["members-only"] }] } } + let(:product2) { { "id" => 2, "name" => 'product 2', "variants" => [{ "id" => 5, "tag_list" => ["members-only"] }, {"id" => 9, "tag_list" => ["something"]}] } } + let(:product3) { { "id" => 3, "name" => 'product 3', "variants" => [{ "id" => 6, "tag_list" => ["something-else"] }] } } + let(:product2_without_v5) { { "id" => 2, "name" => 'product 2', "variants" => [{"id" => 9, "tag_list" => ["something"]}] } } + let!(:products_array) { [product1, product2, product3] } + let!(:products_json) { JSON.unparse( products_array ) } + + before do + allow(controller).to receive(:current_order) { order } + end + + context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do + before { tag_rule.update_attribute(:preferred_matched_variants_visibility, 'visible') } + before { default_tag_rule.update_attribute(:preferred_matched_variants_visibility, 'hidden') } + + let(:filtered_products) { JSON.parse(controller.send(:filter, products_json)) } + + context "when the customer is nil" do + it "applies default action (hide)" do + expect(controller.current_customer).to be nil + expect(filtered_products).to include product2_without_v5, product3 + expect(filtered_products).to_not include product1, product2 + end + end + + context "when the customer's tags match" do + before { customer.update_attribute(:tag_list, 'member') } + + it "applies the action (show)" do + expect(controller.current_customer).to eq customer + expect(filtered_products).to include product1, product2, product3 + end + end + + context "when the customer's tags don't match" do + before { customer.update_attribute(:tag_list, 'something') } + + it "applies the default action (hide)" do + expect(controller.current_customer).to eq customer + expect(filtered_products).to include product2_without_v5, product3 + expect(filtered_products).to_not include product1, product2 + end + end + end + + context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do + before { tag_rule.update_attribute(:preferred_matched_variants_visibility, 'hidden') } + before { default_tag_rule.update_attribute(:preferred_matched_variants_visibility, 'visible') } + + let(:filtered_products) { JSON.parse(controller.send(:filter, products_json)) } + + context "when the customer is nil" do + it "applies default action (show)" do + expect(controller.current_customer).to be nil + expect(filtered_products).to include product1, product2, product3 + end + end + + context "when the customer's tags match" do + before { customer.update_attribute(:tag_list, 'member') } + + it "applies the action (hide)" do + expect(controller.current_customer).to eq customer + expect(filtered_products).to include product2_without_v5, product3 + expect(filtered_products).to_not include product1, product2 + end + end + + context "when the customer's tags don't match" do + before { customer.update_attribute(:tag_list, 'something') } + + it "applies the default action (show)" do + expect(controller.current_customer).to eq customer + expect(filtered_products).to include product1, product2, product3 + end + end + end + end + end end end diff --git a/spec/factories.rb b/spec/factories.rb index b4a3a1b573..06f5dbd879 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -105,10 +105,10 @@ FactoryGirl.define do end factory :exchange, :class => Exchange do - order_cycle { OrderCycle.first || FactoryGirl.create(:simple_order_cycle) } - sender { FactoryGirl.create(:enterprise) } - receiver { FactoryGirl.create(:enterprise) } incoming false + order_cycle { OrderCycle.first || FactoryGirl.create(:simple_order_cycle) } + sender { incoming ? FactoryGirl.create(:enterprise) : order_cycle.coordinator } + receiver { incoming ? order_cycle.coordinator : FactoryGirl.create(:enterprise) } end factory :variant_override, :class => VariantOverride do @@ -259,6 +259,12 @@ FactoryGirl.define do end end + factory :producer_property, class: ProducerProperty do + value 'abc123' + producer { create(:supplier_enterprise) } + property + end + factory :customer, :class => Customer do email { Faker::Internet.email } enterprise @@ -286,10 +292,22 @@ FactoryGirl.define do month { 1 + rand(12) } end + factory :filter_order_cycles_tag_rule, class: TagRule::FilterOrderCycles do + enterprise { FactoryGirl.create :distributor_enterprise } + end + factory :filter_shipping_methods_tag_rule, class: TagRule::FilterShippingMethods do enterprise { FactoryGirl.create :distributor_enterprise } end + factory :filter_products_tag_rule, class: TagRule::FilterProducts do + enterprise { FactoryGirl.create :distributor_enterprise } + end + + factory :filter_payment_methods_tag_rule, class: TagRule::FilterPaymentMethods do + enterprise { FactoryGirl.create :distributor_enterprise } + end + factory :tag_rule, class: TagRule::DiscountOrder do enterprise { FactoryGirl.create :distributor_enterprise } before(:create) do |tr| diff --git a/spec/features/admin/adjustments_spec.rb b/spec/features/admin/adjustments_spec.rb index b9a6ee3465..a051d28b2b 100644 --- a/spec/features/admin/adjustments_spec.rb +++ b/spec/features/admin/adjustments_spec.rb @@ -48,7 +48,7 @@ feature %q{ visit spree.admin_orders_path page.find('td.actions a.icon-edit').click click_link 'Adjustments' - page.find('td.actions a.icon-edit').click + page.find('tr', text: 'Shipping').find('a.icon-edit').click # Then I should see the uneditable included tax and our tax rate as the default page.should have_field :adjustment_included_tax, with: '10.00', disabled: true @@ -72,7 +72,7 @@ feature %q{ visit spree.admin_orders_path page.find('td.actions a.icon-edit').click click_link 'Adjustments' - page.find('td.actions a.icon-edit').click + page.find('tr', text: 'Shipping').find('a.icon-edit').click # Then I should see the uneditable included tax and 'Remove tax' as the default tax rate page.should have_field :adjustment_included_tax, with: '0.00', disabled: true diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index f7f32f6523..062735ccff 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -367,7 +367,10 @@ feature %q{ fill_in "variant_price", with: "10.0" end - click_button 'Save Changes', match: :first + within "#save-bar" do + click_button 'Save Changes' + end + expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -384,7 +387,10 @@ feature %q{ fill_in "product_name", with: "new name 1" - click_button 'Save Changes', match: :first + within "#save-bar" do + click_button 'Save Changes' + end + expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 1" @@ -415,24 +421,15 @@ feature %q{ fill_in "product_name", :with => "new product name" - click_button 'Save Changes', match: :first + within "#save-bar" do + click_button 'Save Changes' + end + expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new product name" end - scenario "updating when no changes have been made" do - Capybara.using_wait_time(2) do - FactoryGirl.create(:product, :name => "product 1") - login_to_admin_section - - visit '/admin/products/bulk_edit' - - click_button 'Save Changes', match: :first - expect(page.find("#status-message")).to have_content "No changes to save." - end - end - scenario "updating when a filter has been applied" do s1 = create(:supplier_enterprise) s2 = create(:supplier_enterprise) @@ -447,7 +444,10 @@ feature %q{ expect(page).to have_no_field "product_name", with: p2.name fill_in "product_name", :with => "new product1" - click_button 'Save Changes', match: :first + within "#save-bar" do + click_button 'Save Changes' + end + expect(page.find("#status-message")).to have_content "Changes saved." p1.reload expect(p1.name).to eq "new product1" diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 964608510d..61caae507f 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -5,14 +5,16 @@ feature 'Customers' do include WebHelper context "as an enterprise user" do - let(:user) { create_enterprise_user } - let(:managed_distributor) { create(:distributor_enterprise, owner: user) } + let(:user) { create_enterprise_user(enterprise_limit: 10) } + let(:managed_distributor1) { create(:distributor_enterprise, owner: user) } + let(:managed_distributor2) { create(:distributor_enterprise, owner: user) } let(:unmanaged_distributor) { create(:distributor_enterprise) } describe "using the customers index", js: true do - let!(:customer1) { create(:customer, enterprise: managed_distributor) } - let!(:customer2) { create(:customer, enterprise: managed_distributor) } + let!(:customer1) { create(:customer, enterprise: managed_distributor1, code: nil) } + let!(:customer2) { create(:customer, enterprise: managed_distributor1, code: nil) } let!(:customer3) { create(:customer, enterprise: unmanaged_distributor) } + let!(:customer4) { create(:customer, enterprise: managed_distributor2) } before do quick_login_as user @@ -21,14 +23,24 @@ feature 'Customers' do it "passes the smoke test" do # Prompts for a hub for a list of my managed enterprises - expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] + expect(page).to have_select2 "shop_id", with_options: [managed_distributor1.name,managed_distributor2.name], without_options: [unmanaged_distributor.name] - select2_select managed_distributor.name, from: "shop_id" + select2_select managed_distributor2.name, from: "shop_id" + + # Loads the right customers + expect(page).to_not have_selector "tr#c_#{customer1.id}" + expect(page).to_not have_selector "tr#c_#{customer2.id}" + expect(page).to_not have_selector "tr#c_#{customer3.id}" + expect(page).to have_selector "tr#c_#{customer4.id}" + + # Changing Shops + select2_select managed_distributor1.name, from: "shop_id" # Loads the right customers expect(page).to have_selector "tr#c_#{customer1.id}" expect(page).to have_selector "tr#c_#{customer2.id}" expect(page).to_not have_selector "tr#c_#{customer3.id}" + expect(page).to_not have_selector "tr#c_#{customer4.id}" # Searching fill_in "quick_search", with: customer2.email @@ -43,14 +55,31 @@ feature 'Customers' do first("div#columns-dropdown div.menu div.menu_item", text: "Email").click expect(page).to_not have_selector "th.email" expect(page).to_not have_content customer1.email + + # Deleting + create(:order, customer: customer1) + expect{ + within "tr#c_#{customer1.id}" do + find("a.delete-customer").click + end + expect(page).to have_selector "#info-dialog .text", text: "Delete failed: customer has associated orders" + click_button "OK" + }.to_not change{Customer.count} + + expect{ + within "tr#c_#{customer2.id}" do + find("a.delete-customer").click + end + expect(page).to_not have_selector "tr#c_#{customer2.id}" + }.to change{Customer.count}.by(-1) end it "allows updating of attributes" do - select2_select managed_distributor.name, from: "shop_id" + select2_select managed_distributor1.name, from: "shop_id" within "tr#c_#{customer1.id}" do fill_in "code", with: "new-customer-code" - expect(page).to have_css "input#code.update-pending" + expect(page).to have_css "input[name=code].update-pending" end within "tr#c_#{customer1.id}" do find(:css, "tags-input .tags input").set "awesome\n" @@ -59,12 +88,58 @@ feature 'Customers' do click_button "Save Changes" # Every says it updated - expect(page).to have_css "input#code.update-success" + expect(page).to have_css "input[name=code].update-success" expect(page).to have_css ".tag_watcher.update-success" # And it actually did expect(customer1.reload.code).to eq "new-customer-code" expect(customer1.tag_list).to eq ["awesome"] + + # Clearing attributes + within "tr#c_#{customer1.id}" do + fill_in "code", with: "" + expect(page).to have_css "input[name=code].update-pending" + end + within "tr#c_#{customer1.id}" do + find("tags-input li.tag-item a.remove-button").trigger('click') + expect(page).to have_css ".tag_watcher.update-pending" + end + click_button "Save Changes" + + # Every says it updated + expect(page).to have_css "input[name=code].update-success" + expect(page).to have_css ".tag_watcher.update-success" + + # And it actually did + expect(customer1.reload.code).to be nil + expect(customer1.tag_list).to eq [] + end + + it "prevents duplicate codes from being saved" do + select2_select managed_distributor1.name, from: "shop_id" + + within "tr#c_#{customer1.id}" do + fill_in "code", with: "new-customer-code" + expect(page).to have_css "input[name=code].update-pending" + end + within "tr#c_#{customer2.id}" do + fill_in "code", with: "new-customer-code" + expect(page).to have_content "This code is used already." + end + click_button "Save Changes" + + within "tr#c_#{customer1.id}" do + expect(page).to have_css "input[name=code].update-success" + end + + within "tr#c_#{customer2.id}" do + expect(page).to have_css "input[name=code].update-error" + end + + expect(page).to have_content "Oh no! I was unable to save your changes" + + expect(customer1.reload.code).to eq "new-customer-code" + expect(customer2.reload.code).to be nil end describe "creating a new customer" do @@ -78,7 +153,7 @@ feature 'Customers' do context "when a shop is selected" do before do - select2_select managed_distributor.name, from: "shop_id" + select2_select managed_distributor1.name, from: "shop_id" end it "creates customers when the email provided is valid" do @@ -88,21 +163,21 @@ feature 'Customers' do fill_in 'email', with: "not_an_email" click_button 'Add Customer' expect(page).to have_selector "#new-customer-dialog .error", text: "Please enter a valid email address" - }.to_not change{Customer.of(managed_distributor).count} + }.to_not change{Customer.of(managed_distributor1).count} # When an existing email is used expect{ fill_in 'email', with: customer1.email click_button 'Add Customer' expect(page).to have_selector "#new-customer-dialog .error", text: "Email is associated with an existing customer" - }.to_not change{Customer.of(managed_distributor).count} + }.to_not change{Customer.of(managed_distributor1).count} # When a new valid email is used expect{ fill_in 'email', with: "new@email.com" click_button 'Add Customer' expect(page).not_to have_selector "#new-customer-dialog" - }.to change{Customer.of(managed_distributor).count}.from(2).to(3) + }.to change{Customer.of(managed_distributor1).count}.from(2).to(3) end end end diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 3579421b84..4d09007584 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -61,6 +61,9 @@ feature %q{ end scenario "editing an existing enterprise", js: true do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 1000) + @enterprise = create(:enterprise) e2 = create(:enterprise) eg1 = create(:enterprise_group, name: 'eg1') @@ -355,6 +358,10 @@ feature %q{ within("tbody#e_#{distributor1.id}") { click_link 'Manage' } fill_in 'enterprise_name', :with => 'Eaterprises' + + # Because poltergist does not support form onchange event + # We need trigger the change manually + page.evaluate_script("angular.element(enterprise_form).scope().setFormDirty()") click_button 'Update' flash_message.should == 'Enterprise "Eaterprises" has been successfully updated!' @@ -367,6 +374,10 @@ feature %q{ within("tbody#e_#{distributor3.id}") { click_link 'Manage' } fill_in 'enterprise_name', :with => 'Eaterprises' + + # Because poltergist does not support form onchange event + # We need trigger the change manually + page.evaluate_script("angular.element(enterprise_form).scope().setFormDirty()") click_button 'Update' flash_message.should == 'Enterprise "Eaterprises" has been successfully updated!' @@ -407,8 +418,14 @@ feature %q{ # -- Update only select2_select "Certified Organic", from: 'enterprise_producer_properties_attributes_0_property_name' + fill_in 'enterprise_producer_properties_attributes_0_value', with: "NASAA 12345" + + # Because poltergist does not support form onchange event + # We need trigger the change manually + page.evaluate_script("angular.element(enterprise_form).scope().setFormDirty()") click_button 'Update' + supplier1.producer_properties(true).count.should == 1 # -- Destroy diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 874c8342b5..74b2e88453 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -59,6 +59,8 @@ feature %q{ end scenario "creating an order cycle", js: true do + page.driver.resize(1280, 2000) + # Given coordinating, supplying and distributing enterprises with some products with variants coordinator = create(:distributor_enterprise, name: 'My coordinator') supplier = create(:supplier_enterprise, name: 'My supplier') @@ -103,7 +105,7 @@ feature %q{ select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' fill_in 'order_cycle_incoming_exchange_0_receival_instructions', with: 'receival instructions' - page.find('table.exchanges tr.supplier td.products input').click + page.find('table.exchanges tr.supplier td.products').click check "order_cycle_incoming_exchange_0_variants_#{v1.id}" check "order_cycle_incoming_exchange_0_variants_#{v2.id}" @@ -124,10 +126,15 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' - page.find('table.exchanges tr.distributor td.products input').click + page.find('table.exchanges tr.distributor td.products').click check "order_cycle_outgoing_exchange_0_variants_#{v1.id}" check "order_cycle_outgoing_exchange_0_variants_#{v2.id}" + page.find('table.exchanges tr.distributor td.tags').click + within ".exchange-tags" do + find(:css, "tags-input .tags input").set "wholesale\n" + end + # And I add a distributor fee within("tr.distributor-#{distributor.id}") { click_button 'Add fee' } select 'My distributor', from: 'order_cycle_outgoing_exchange_0_enterprise_fees_0_enterprise_id' @@ -165,6 +172,7 @@ feature %q{ exchange = oc.exchanges.outgoing.first exchange.pickup_time.should == 'pickup time' exchange.pickup_instructions.should == 'pickup instructions' + exchange.tag_list.should == ['wholesale'] end @@ -196,13 +204,13 @@ feature %q{ page.should have_field 'order_cycle_incoming_exchange_1_receival_instructions', with: 'instructions 1' # And the suppliers should have products - page.all('table.exchanges tbody tr.supplier').each do |row| - row.find('td.products input').click + page.all('table.exchanges tbody tr.supplier').each_with_index do |row, i| + row.find('td.products').click - products_row = page.all('table.exchanges tr.products').select { |r| r.visible? }.first - products_row.should have_selector "input[type='checkbox'][checked='checked']" + products_panel = page.all('table.exchanges tr.panel-row .exchange-supplied-products').select { |r| r.visible? }.first + products_panel.should have_selector "input[name='order_cycle_incoming_exchange_#{i}_select_all_variants']" - row.find('td.products input').click + row.find('td.products').click end # And the suppliers should have fees @@ -223,17 +231,14 @@ feature %q{ page.should have_field 'order_cycle_outgoing_exchange_1_pickup_time', with: 'time 1' page.should have_field 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'instructions 1' - # Make the whole page visible - page.driver.resize(1280, 3600) - # And the distributors should have products - page.all('table.exchanges tbody tr.distributor').each do |row| - row.find('td.products input').click + page.all('table.exchanges tbody tr.distributor').each_with_index do |row, i| + row.find('td.products').click - products_row = page.all('table.exchanges tr.products').select { |r| r.visible? }.first - products_row.should have_selector "input[type='checkbox'][checked='checked']" + products_panel = page.all('table.exchanges tr.panel-row .exchange-distributed-products').select { |r| r.visible? }.first + products_panel.should have_selector "input[name='order_cycle_outgoing_exchange_#{i}_select_all_variants']" - row.find('td.products input').click + row.find('td.products').click end # And the distributors should have fees @@ -270,9 +275,6 @@ feature %q{ scenario "updating an order cycle", js: true do - # Make the page long enough to avoid the save bar overlaying the form - page.driver.resize(1280, 3600) - # Given an order cycle with all the settings oc = create(:order_cycle) initial_variants = oc.variants.sort_by &:id @@ -323,7 +325,7 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } + page.all("table.exchanges tr.supplier td.products").each { |e| e.click } page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) @@ -349,7 +351,12 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - page.all("table.exchanges tr.distributor td.products input").each { |e| e.click } + page.find("table.exchanges tr.distributor-#{distributor.id} td.tags").click + within ".exchange-tags" do + find(:css, "tags-input .tags input").set "wholesale\n" + end + + page.all("table.exchanges tr.distributor td.products").each { |e| e.click } uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -389,6 +396,9 @@ feature %q{ # And my distributor fees should have been configured OrderCycle.last.exchanges.outgoing.last.enterprise_fee_ids.should == [distributor_fee2.id] + # And my tags should have been save + OrderCycle.last.exchanges.outgoing.last.tag_list.should == ['wholesale'] + # And it should have some variants selected selected_initial_variants = initial_variants.take initial_variants.size - 1 OrderCycle.last.variants.map(&:id).should match_array (selected_initial_variants.map(&:id) + [v1.id, v2.id]) @@ -465,7 +475,7 @@ feature %q{ login_to_admin_section click_link 'Order Cycles' click_link oc.name - within("table.exchanges tbody tr.supplier") { page.find('td.products input').click } + within("table.exchanges tbody tr.supplier") { page.find('td.products').click } page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).trigger('click') # uncheck click_button "Update" @@ -559,6 +569,9 @@ feature %q{ end scenario "creating a new order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 2000) + click_link "Order Cycles" click_link 'New Order Cycle' @@ -591,6 +604,11 @@ feature %q{ page.should_not have_select 'order_cycle_coordinator_id', with_options: [enterprise_name] end + page.find("table.exchanges tr.distributor-#{distributor_managed.id} td.tags").click + within ".exchange-tags" do + find(:css, "tags-input .tags input").set "wholesale\n" + end + click_button 'Create' flash_message.should == "Your order cycle has been created." @@ -598,6 +616,8 @@ feature %q{ order_cycle.suppliers.should match_array [supplier_managed, supplier_permitted] order_cycle.coordinator.should == distributor_managed order_cycle.distributors.should match_array [distributor_managed, distributor_permitted] + exchange = order_cycle.exchanges.outgoing.to_enterprise(distributor_managed).first + exchange.tag_list.should == ["wholesale"] end scenario "editing an order cycle we can see (and for now, edit) all exchanges in the order cycle" do @@ -610,16 +630,22 @@ feature %q{ visit edit_admin_order_cycle_path(oc) + fill_in 'order_cycle_name', with: 'Coordinated' + # I should be able to see but not edit exchanges for supplier_unmanaged or distributor_unmanaged expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" expect(page).to have_selector "tr.supplier-#{supplier_permitted.id}" expect(page).to have_selector "tr.supplier-#{supplier_unmanaged.id}" - expect(page.all('tr.supplier').count).to be 3 + expect(page).to have_selector 'tr.supplier', count: 3 expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" expect(page).to have_selector "tr.distributor-#{distributor_unmanaged.id}" - expect(page.all('tr.distributor').count).to be 3 + expect(page).to have_selector 'tr.distributor', count: 3 + + # When I save, then those exchanges should remain + click_button 'Update' + page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -628,9 +654,6 @@ feature %q{ end scenario "editing an order cycle" do - # Make the page long enough to avoid the save bar overlaying the form - page.driver.resize(1280, 3600) - oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: distributor_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) visit edit_admin_order_cycle_path(oc) @@ -640,7 +663,6 @@ feature %q{ page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click - click_button 'Update' # Then the exchanges should be removed @@ -699,22 +721,29 @@ feature %q{ # distributor_managed and distributor_permitted (who I have given permission to) AND # and distributor_unmanaged (who distributes my products) expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" - expect(page.all('tr.supplier').count).to be 1 + expect(page).to have_selector 'tr.supplier', count: 1 expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" - expect(page.all('tr.distributor').count).to be 2 + expect(page).to have_selector 'tr.distributor', count: 2 # Open the products list for managed_supplier's incoming exchange within "tr.distributor-#{distributor_managed.id}" do - page.find("td.products input").click + page.find("td.products").click end # I should be able to see and toggle v1 - expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v1.id}", disabled: false + expect(page).to have_checked_field "order_cycle_outgoing_exchange_0_variants_#{v1.id}", disabled: false + uncheck "order_cycle_outgoing_exchange_0_variants_#{v1.id}" # I should be able to see but not toggle v2, because I don't have permission - expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true + expect(page).to have_checked_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true + + page.should_not have_selector "table.exchanges tr.distributor-#{distributor_managed.id} td.tags" + + # When I save, any exchanges that I can't manage remain + click_button 'Update' + page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -738,8 +767,14 @@ feature %q{ oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: distributor_managed, distributors: [my_distributor, distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) v1 = create(:variant, product: create(:product, supplier: supplier_managed) ) v2 = create(:variant, product: create(:product, supplier: supplier_managed) ) - ex = oc.exchanges.where(sender_id: distributor_managed, receiver_id: my_distributor, incoming: false).first - ex.update_attributes(variant_ids: [v1.id, v2.id]) + + # Incoming exchange + ex_in = oc.exchanges.where(sender_id: supplier_managed, receiver_id: distributor_managed, incoming: true).first + ex_in.update_attributes(variant_ids: [v1.id, v2.id]) + + # Outgoing exchange + ex_out = oc.exchanges.where(sender_id: distributor_managed, receiver_id: my_distributor, incoming: false).first + ex_out.update_attributes(variant_ids: [v1.id, v2.id]) # Stub editable_variants_for_incoming_exchanges method so we can test permissions serializer = Api::Admin::OrderCycleSerializer.new(oc, current_user: new_user) @@ -752,21 +787,28 @@ feature %q{ # I should see exchanges for my_distributor, and the incoming exchanges supplying the variants in it expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" - expect(page.all('tr.supplier').count).to be 1 + expect(page).to have_selector 'tr.supplier', count: 1 expect(page).to have_selector "tr.distributor-#{my_distributor.id}" - expect(page.all('tr.distributor').count).to be 1 + expect(page).to have_selector 'tr.distributor', count: 1 # Open the products list for managed_supplier's incoming exchange within "tr.supplier-#{supplier_managed.id}" do - page.find("td.products input").click + page.find("td.products").click end # I should be able to see and toggle v1 - expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v1.id}", disabled: false + expect(page).to have_checked_field "order_cycle_incoming_exchange_0_variants_#{v1.id}", disabled: false + uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" # I should be able to see but not toggle v2, because I don't have permission - expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true + expect(page).to have_checked_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true + + page.should have_selector "table.exchanges tr.distributor-#{my_distributor.id} td.tags" + + # When I save, any exchange that I can't manage remains + click_button 'Update' + page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -802,6 +844,9 @@ feature %q{ end it "creates order cycles", js: true do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 2000) + # When I go to the new order cycle page visit admin_order_cycles_path click_link 'New Order Cycle' @@ -881,9 +926,6 @@ feature %q{ end scenario "updating an order cycle" do - # Make the page long enough to avoid the save bar overlaying the form - page.driver.resize(1280, 3600) - # Given an order cycle with pickup time and instructions fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise) @@ -956,7 +998,7 @@ feature %q{ private def select_incoming_variant(supplier, exchange_no, variant) - page.find("table.exchanges tr.supplier-#{supplier.id} td.products input").click + page.find("table.exchanges tr.supplier-#{supplier.id} td.products").click check "order_cycle_incoming_exchange_#{exchange_no}_variants_#{variant.id}" end end diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb index b736155372..45703a4d07 100644 --- a/spec/features/admin/payment_method_spec.rb +++ b/spec/features/admin/payment_method_spec.rb @@ -29,47 +29,52 @@ feature %q{ payment_method = Spree::PaymentMethod.find_by_name('Cheque payment method') payment_method.distributors.should == [@distributors[0]] end + end - scenario "updating a payment method" do - pm = create(:payment_method, distributors: [@distributors[0]]) - login_to_admin_section + scenario "updating a payment method", js: true do + pm = create(:payment_method, distributors: [@distributors[0]]) + login_to_admin_section - visit spree.edit_admin_payment_method_path pm + visit spree.edit_admin_payment_method_path pm - fill_in 'payment_method_name', :with => 'New PM Name' + fill_in 'payment_method_name', :with => 'New PM Name' + find(:css, "tags-input .tags input").set "member\n" - uncheck "payment_method_distributor_ids_#{@distributors[0].id}" - 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' + uncheck "payment_method_distributor_ids_#{@distributors[0].id}" + 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' + click_button 'Update' - expect(flash_message).to 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' + expect(first('tags-input .tag-list ti-tag-item')).to have_content "member" - fill_in 'payment_method_preferred_login', with: 'otherlogin' - click_button 'Update' + 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' - expect(flash_message).to eq 'Payment Method has been successfully updated!' - expect(page).to have_field 'Password', with: '' + fill_in 'payment_method_preferred_login', with: 'otherlogin' + click_button 'Update' - 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 + expect(flash_message).to eq 'Payment Method has been successfully updated!' + expect(page).to have_field 'Password', with: '' + expect(first('tags-input .tag-list ti-tag-item')).to have_content "member" + + payment_method = Spree::PaymentMethod.find_by_name('New PM Name') + expect(payment_method.tag_list).to eq ["member"] + 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 context "as an enterprise user", js: true do @@ -102,12 +107,15 @@ feature %q{ fill_in 'payment_method_name', :with => 'Cheque payment method' check "payment_method_distributor_ids_#{distributor1.id}" + find(:css, "tags-input .tags input").set "local\n" click_button 'Create' flash_message.should == 'Payment Method has been successfully created!' + expect(first('tags-input .tag-list ti-tag-item')).to have_content "local" payment_method = Spree::PaymentMethod.find_by_name('Cheque payment method') payment_method.distributors.should == [distributor1] + payment_method.tag_list.should == ["local"] end it "shows me only payment methods I have access to" do @@ -127,7 +135,7 @@ feature %q{ pm2 visit spree.admin_payment_methods_path - page.all('td', text: 'Two').count.should == 1 + page.should have_selector 'td', text: 'Two', count: 1 end diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index 2bd39a3e38..e2c991a185 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -124,7 +124,7 @@ feature %q{ table.sort.should == [ ["Hub", "Code", "First Name", "Last Name", "Supplier", "Product", "Variant", "Quantity", "TempControlled?"] ].sort - all('table#listing_orders tbody tr').count.should == 5 # Totals row per order + page.should have_selector 'table#listing_orders tbody tr', count: 5 # Totals row per order end scenario "Pack By Supplier" do diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index 646b383c21..46816f5737 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -123,7 +123,7 @@ feature 'shipping methods' do visit spree.admin_shipping_methods_path - page.all('td', text: 'Two').count.should == 1 + page.should have_selector 'td', text: 'Two', count: 1 end pending "shows me only shipping methods for the enterprise I select" do diff --git a/spec/features/admin/tag_rules_spec.rb b/spec/features/admin/tag_rules_spec.rb index e91215a21d..f8f25ec181 100644 --- a/spec/features/admin/tag_rules_spec.rb +++ b/spec/features/admin/tag_rules_spec.rb @@ -13,6 +13,9 @@ feature 'Tag Rules', js: true do end it "allows creation of rules of each type" do + # Make the whole page visible + page.driver.resize(1280, 2000) + click_link "Tag Rules" # Creating a new tag @@ -22,13 +25,52 @@ feature 'Tag Rules', js: true do find(:css, "tags-input .tags input").set "volunteer\n" # New FilterShippingMethods Rule + expect(page).to have_content 'No rules apply to this tag yet' click_button '+ Add A New Rule' - select2_select 'Show/Hide shipping methods', from: 'rule_type_selector' + select2_select 'Show or Hide shipping methods at checkout', from: 'rule_type_selector' click_button "Add Rule" - select2_select "NOT VISIBLE", from: "enterprise_tag_rules_attributes_0_preferred_matched_shipping_methods_visibility" + within(".customer_tag #tr_0") do + find(:css, "tags-input .tags input").set "volunteers-only\n" + select2_select "NOT VISIBLE", from: "enterprise_tag_rules_attributes_0_preferred_matched_shipping_methods_visibility" + end + + # New FilterProducts Rule + click_button '+ Add A New Rule' + select2_select 'Show or Hide variants in my shop', from: 'rule_type_selector' + click_button "Add Rule" + within(".customer_tag #tr_1") do + find(:css, "tags-input .tags input").set "volunteers-only1\n" + select2_select "VISIBLE", from: "enterprise_tag_rules_attributes_1_preferred_matched_variants_visibility" + end + + # New FilterPaymentMethods Rule + click_button '+ Add A New Rule' + select2_select 'Show or Hide payment methods at checkout', from: 'rule_type_selector' + click_button "Add Rule" + within(".customer_tag #tr_2") do + find(:css, "tags-input .tags input").set "volunteers-only2\n" + select2_select "VISIBLE", from: "enterprise_tag_rules_attributes_2_preferred_matched_payment_methods_visibility" + end + + # New FilterOrderCycles Rule + click_button '+ Add A New Rule' + select2_select 'Show or Hide order cycles in my shopfront', from: 'rule_type_selector' + click_button "Add Rule" + within(".customer_tag #tr_3") do + find(:css, "tags-input .tags input").set "volunteers-only3\n" + select2_select "NOT VISIBLE", from: "enterprise_tag_rules_attributes_3_preferred_matched_order_cycles_visibility" + end + + # New DEFAULT FilterOrderCycles Rule + click_button '+ Add A New Default Rule' + select2_select 'Show or Hide order cycles in my shopfront', from: 'rule_type_selector' + click_button "Add Rule" + within(".default_rules #tr_0") do + find(:css, "tags-input .tags input").set "wholesale\n" + expect(page).to have_content "not visible" + end # 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" @@ -42,14 +84,38 @@ feature 'Tag Rules', js: true do 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_shipping_method_tags).to eq "volunteers-only" expect(tag_rule.preferred_matched_shipping_methods_visibility).to eq "hidden" + + tag_rule = TagRule::FilterProducts.last + expect(tag_rule.preferred_customer_tags).to eq "volunteer" + expect(tag_rule.preferred_variant_tags).to eq "volunteers-only1" + expect(tag_rule.preferred_matched_variants_visibility).to eq "visible" + + tag_rule = TagRule::FilterPaymentMethods.last + expect(tag_rule.preferred_customer_tags).to eq "volunteer" + expect(tag_rule.preferred_payment_method_tags).to eq "volunteers-only2" + expect(tag_rule.preferred_matched_payment_methods_visibility).to eq "visible" + + tag_rule = TagRule::FilterOrderCycles.all.reject(&:is_default).last + expect(tag_rule.preferred_customer_tags).to eq "volunteer" + expect(tag_rule.preferred_exchange_tags).to eq "volunteers-only3" + expect(tag_rule.preferred_matched_order_cycles_visibility).to eq "hidden" + + tag_rule = TagRule::FilterOrderCycles.all.select(&:is_default).last + expect(tag_rule.preferred_customer_tags).to eq "" + expect(tag_rule.preferred_exchange_tags).to eq "wholesale" + expect(tag_rule.preferred_matched_order_cycles_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" ) } + let!(:default_fsm_tag_rule) { create(:filter_shipping_methods_tag_rule, enterprise: enterprise, preferred_matched_shipping_methods_visibility: "visible", is_default: true, preferred_shipping_method_tags: "local" ) } + let!(:fp_tag_rule) { create(:filter_products_tag_rule, enterprise: enterprise, preferred_matched_variants_visibility: "visible", preferred_customer_tags: "member", preferred_variant_tags: "member" ) } + let!(:fpm_tag_rule) { create(:filter_payment_methods_tag_rule, enterprise: enterprise, preferred_matched_payment_methods_visibility: "hidden", preferred_customer_tags: "trusted", preferred_payment_method_tags: "trusted" ) } + let!(:foc_tag_rule) { create(:filter_order_cycles_tag_rule, enterprise: enterprise, preferred_matched_order_cycles_visibility: "visible", preferred_customer_tags: "wholesale", preferred_exchange_tags: "wholesale" ) } + let!(:fsm_tag_rule) { create(:filter_shipping_methods_tag_rule, enterprise: enterprise, preferred_matched_shipping_methods_visibility: "hidden", preferred_customer_tags: "local", preferred_shipping_method_tags: "local" ) } + # let!(:do_tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) } before do login_to_admin_section @@ -57,53 +123,128 @@ feature 'Tag Rules', js: true do end it "saves changes to rules of each type" do + # Make the whole page visible + page.driver.resize(1280, 2000) + 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" + # Tag groups exist + expect(page).to have_selector '.customer_tag .header', text: "For customers tagged:", count: 4 + expect(page).to have_selector '.customer_tag .header tags-input .tag-list ti-tag-item', text: "member", count: 1 + expect(page).to have_selector '.customer_tag .header tags-input .tag-list ti-tag-item', text: "local", count: 1 + expect(page).to have_selector '.customer_tag .header tags-input .tag-list ti-tag-item', text: "wholesale", count: 1 + expect(page).to have_selector '.customer_tag .header tags-input .tag-list ti-tag-item', text: "trusted", count: 1 + all(:css, ".customer_tag .header tags-input").each do |node| + node.find("li.tag-item a.remove-button").trigger('click') + node.find(".tags input").set "volunteer\n" + end - # 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 + # DEFAULT FilterShippingMethods rule + within ".default_rules #tr_0" do + within "li.tag-item", text: "local ✖" do find("a.remove-button").trigger('click') end + find(:css, "tags-input .tags input").set "volunteers-only\n" + expect(page).to have_content "not visible" + end + + # FilterProducts rule + within ".customer_tag #tr_1" do + within "li.tag-item", text: "member ✖" do find("a.remove-button").trigger('click') end + find(:css, "tags-input .tags input").set "volunteers-only1\n" + expect(page).to have_select2 "enterprise_tag_rules_attributes_1_preferred_matched_variants_visibility", selected: 'VISIBLE' + select2_select 'NOT VISIBLE', from: "enterprise_tag_rules_attributes_1_preferred_matched_variants_visibility" + end + + # FilterPaymentMethods rule + within ".customer_tag #tr_2" do + within "li.tag-item", text: "trusted ✖" do find("a.remove-button").trigger('click') end + find(:css, "tags-input .tags input").set "volunteers-only2\n" + expect(page).to have_select2 "enterprise_tag_rules_attributes_2_preferred_matched_payment_methods_visibility", selected: 'NOT VISIBLE' + select2_select 'VISIBLE', from: "enterprise_tag_rules_attributes_2_preferred_matched_payment_methods_visibility" + end + + # FilterOrderCycles rule + within ".customer_tag #tr_3" do + within "li.tag-item", text: "wholesale ✖" do find("a.remove-button").trigger('click') end + find(:css, "tags-input .tags input").set "volunteers-only3\n" + expect(page).to have_select2 "enterprise_tag_rules_attributes_3_preferred_matched_order_cycles_visibility", selected: 'VISIBLE' + select2_select 'NOT VISIBLE', from: "enterprise_tag_rules_attributes_3_preferred_matched_order_cycles_visibility" + end # 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" + within ".customer_tag #tr_4" do + within "li.tag-item", text: "local ✖" do find("a.remove-button").trigger('click') end + find(:css, "tags-input .tags input").set "volunteers-only4\n" + expect(page).to have_select2 "enterprise_tag_rules_attributes_4_preferred_matched_shipping_methods_visibility", selected: 'NOT VISIBLE' + select2_select 'VISIBLE', from: "enterprise_tag_rules_attributes_4_preferred_matched_shipping_methods_visibility" + end + + # Moving the Shipping Methods to top priority + find(".customer_tag#tg_4 .header", ).drag_to find(".customer_tag#tg_1 .header") + + # # DiscountOrder rule + # within "#tr_2" do + # expect(page).to have_field "enterprise_tag_rules_attributes_2_calculator_attributes_preferred_flat_percent", with: '0' + # fill_in "enterprise_tag_rules_attributes_2_calculator_attributes_preferred_flat_percent", with: 45 + # end 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 + # DEFAULT FilterShippingMethods rule + expect(default_fsm_tag_rule.reload.preferred_customer_tags).to eq "" + expect(default_fsm_tag_rule.preferred_shipping_method_tags).to eq "volunteers-only" + expect(default_fsm_tag_rule.preferred_matched_shipping_methods_visibility).to eq "hidden" # 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.reload.priority).to eq 1 + expect(fsm_tag_rule.preferred_customer_tags).to eq "volunteer" + expect(fsm_tag_rule.preferred_shipping_method_tags).to eq "volunteers-only4" expect(fsm_tag_rule.preferred_matched_shipping_methods_visibility).to eq "visible" + + # FilterProducts rule + expect(fp_tag_rule.reload.priority).to eq 2 + expect(fp_tag_rule.preferred_customer_tags).to eq "volunteer" + expect(fp_tag_rule.preferred_variant_tags).to eq "volunteers-only1" + expect(fp_tag_rule.preferred_matched_variants_visibility).to eq "hidden" + + # FilterPaymentMethods rule + expect(fpm_tag_rule.reload.priority).to eq 3 + expect(fpm_tag_rule.preferred_customer_tags).to eq "volunteer" + expect(fpm_tag_rule.preferred_payment_method_tags).to eq "volunteers-only2" + expect(fpm_tag_rule.preferred_matched_payment_methods_visibility).to eq "visible" + + # FilterOrderCycles rule + expect(foc_tag_rule.reload.priority).to eq 4 + expect(foc_tag_rule.preferred_customer_tags).to eq "volunteer" + expect(foc_tag_rule.preferred_exchange_tags).to eq "volunteers-only3" + expect(foc_tag_rule.preferred_matched_order_cycles_visibility).to eq "hidden" + + # DiscountOrder rule + # expect(do_tag_rule.preferred_customer_tags).to eq "member,volunteer" + # expect(do_tag_rule.calculator.preferred_flat_percent).to eq -45 end end context "deleting" do - let!(:tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) } + let!(:tag_rule) { create(:filter_products_tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) } + let!(:default_rule) { create(:filter_products_tag_rule, is_default: true, enterprise: enterprise ) } before do login_to_admin_section visit main_app.edit_admin_enterprise_path(enterprise) end - it "deletes rules from the database" do + it "deletes both default and customer rules from the database" do + # Make the whole page visible + page.driver.resize(1280, 2000) + 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) + expect do + within "#tr_1" do first("a.delete-tag-rule").click end + expect(page).to_not have_selector "#tr_1" + within "#tr_0" do first("a.delete-tag-rule").click end + expect(page).to_not have_selector "#tr_0" + end.to change{TagRule.count}.by(-2) end end end diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index 0af93d19c3..1cb559a653 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -214,7 +214,7 @@ feature %q{ end context "with overrides" do - let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true) } + let!(:vo) { create(:variant_override, variant: variant, hub: hub, price: 77.77, count_on_hand: 11111, default_stock: 1000, resettable: true, tag_list: ["tag1","tag2","tag3"]) } let!(:vo_no_auth) { create(:variant_override, variant: variant, hub: hub2, price: 1, count_on_hand: 2) } let!(:product2) { create(:simple_product, supplier: producer, variant_unit: 'weight', variant_unit_scale: 1) } let!(:variant2) { create(:variant, product: product2, unit_value: 8, price: 1.00, on_hand: 12) } @@ -256,6 +256,7 @@ feature %q{ first("div#columns-dropdown", :text => "COLUMNS").click first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click first("div#columns-dropdown div.menu div.menu_item", text: "Enable Stock Reset?").click + first("div#columns-dropdown div.menu div.menu_item", text: "Tags").click first("div#columns-dropdown", :text => "COLUMNS").click # Clearing values by 'inheriting' @@ -268,6 +269,13 @@ feature %q{ fill_in "variant-overrides-#{variant.id}-price", with: '' fill_in "variant-overrides-#{variant.id}-count_on_hand", with: '' fill_in "variant-overrides-#{variant.id}-default_stock", with: '' + within "tr#v_#{variant.id}" do + vo.tag_list.each do |tag| + within "li.tag-item", text: "#{tag} ✖" do + find("a.remove-button").trigger('click') + end + end + end page.uncheck "variant-overrides-#{variant.id}-resettable" page.should have_content "Changes to 2 overrides remain unsaved." diff --git a/spec/features/consumer/producers_spec.rb b/spec/features/consumer/producers_spec.rb index 8a26eaf026..634350af18 100644 --- a/spec/features/consumer/producers_spec.rb +++ b/spec/features/consumer/producers_spec.rb @@ -5,22 +5,58 @@ feature %q{ I want to see a list of producers So that I can shop at hubs distributing their products }, js: true do + include WebHelper include UIComponentHelper - let!(:producer) { create(:supplier_enterprise) } + + let!(:producer1) { create(:supplier_enterprise) } + let!(:producer2) { create(:supplier_enterprise) } let!(:invisible_producer) { create(:supplier_enterprise, visible: false) } - let(:taxon) { create(:taxon) } - let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) } + + let(:taxon_fruit) { create(:taxon, name: 'Fruit') } + let(:taxon_veg) { create(:taxon, name: 'Vegetables') } + + let!(:product1) { create(:simple_product, supplier: producer1, taxons: [taxon_fruit]) } + let!(:product2) { create(:simple_product, supplier: producer2, taxons: [taxon_veg]) } + let(:shop) { create(:distributor_enterprise) } - let!(:er) { create(:enterprise_relationship, parent: shop, child: producer) } + let!(:er) { create(:enterprise_relationship, parent: shop, child: producer1) } before do + product1.set_property 'Organic', 'NASAA 12345' + product2.set_property 'Biodynamic', 'ABC123' + + producer1.set_producer_property 'Local', 'Victoria' + producer2.set_producer_property 'Fair Trade', 'FT123' + visit producers_path end + + it "filters by taxon" do + toggle_filters + + toggle_filter 'Vegetables' + + page.should_not have_content producer1.name + page.should have_content producer2.name + + toggle_filter 'Vegetables' + toggle_filter 'Fruit' + + page.should have_content producer1.name + page.should_not have_content producer2.name + end + it "shows all producers with expandable details" do - page.should have_content producer.name - expand_active_table_node producer.name - page.should have_content producer.supplied_taxons.first.name.split.map(&:capitalize).join(' ') + page.should have_content producer1.name + expand_active_table_node producer1.name + + # -- Taxons + page.should have_content 'Fruit' + + # -- Properties + page.should have_content 'Organic' # Product property + page.should have_content 'Local' # Producer property end it "doesn't show invisible producers" do @@ -28,7 +64,19 @@ feature %q{ end it "links to places to buy produce" do - expand_active_table_node producer.name + expand_active_table_node producer1.name page.should have_link shop.name end + + + private + + def toggle_filters + find('a.filterbtn').click + end + + def toggle_filter(name) + page.find('span', text: name).click + end + end diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb index 9b2602c1c6..5cd34206a5 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -22,7 +22,8 @@ feature "Registration", js: true do expect(URI.parse(current_url).path).to eq registration_path # Done reading introduction - click_button_and_ensure_content "Let's get started!", "Woot! First we need to know a little bit about your enterprise:" + page.has_content? + click_and_ensure(:button, "Let's get started!", lambda { page.has_content? 'Woot!' }) # Filling in details fill_in 'enterprise_name', with: "My Awesome Enterprise" @@ -33,22 +34,20 @@ feature "Registration", js: true do fill_in 'enterprise_zipcode', with: '3070' select 'Australia', from: 'enterprise_country' select 'VIC', from: 'enterprise_state' - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'Who is responsible for managing My Awesome Enterprise?' }) + # Filling in Contact Details - expect(page).to have_content 'Who is responsible for managing My Awesome Enterprise?' fill_in 'enterprise_contact', with: 'Saskia Munroe' page.should have_field 'enterprise_email_address', with: user.email fill_in 'enterprise_phone', with: '12 3456 7890' - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'Last step to add My Awesome Enterprise!' }) # Choosing a type - expect(page).to have_content 'Last step to add My Awesome Enterprise!' - click_link 'producer-panel' - click_button 'Create Profile' + click_and_ensure(:link, 'producer-panel', lambda { page.has_content? '#producer-panel.selected' } ) + click_and_ensure(:button, "Create Profile", lambda { page.has_content? 'Nice one!' }) # Enterprise should be created - expect(page).to have_content 'Nice one!' e = Enterprise.find_by_name('My Awesome Enterprise') expect(e.address.address1).to eq "123 Abc Street" expect(e.sells).to eq "unspecified" @@ -61,10 +60,9 @@ feature "Registration", js: true do fill_in 'enterprise_abn', with: '12345' fill_in 'enterprise_acn', with: '54321' choose 'Yes' # enterprise_charges_sales_tax - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'Step 1. Select Logo Image' }) # Enterprise should be updated - expect(page).to have_content "Let's upload some pretty pictures so your profile looks great!" e.reload expect(e.description).to eq "Short description" expect(e.long_description).to eq "Long description" @@ -74,21 +72,20 @@ feature "Registration", js: true do # Images # Move from logo page - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'Step 3. Select Promo Image' }) + # Move from promo page - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'How can people find My Awesome Enterprise online?' }) # Filling in social - expect(page).to have_content 'How can people find My Awesome Enterprise online?' fill_in 'enterprise_website', with: 'www.shop.com' fill_in 'enterprise_facebook', with: 'FaCeBoOk' fill_in 'enterprise_linkedin', with: 'LiNkEdIn' fill_in 'enterprise_twitter', with: '@TwItTeR' fill_in 'enterprise_instagram', with: '@InStAgRaM' - click_button 'Continue' + click_and_ensure(:button, "Continue", lambda { page.has_content? 'Finished!' }) # Done - expect(page).to have_content "Finished!" expect(page).to have_content "We've sent a confirmation email to #{user.email} if it hasn't been activated before." e.reload expect(e.website).to eq "www.shop.com" @@ -120,12 +117,12 @@ feature "Registration", js: true do expect(page).to have_content content end - def click_button_and_ensure_content(button_text, content) + def click_and_ensure(type, text, check) # Buttons appear to be unresponsive for a while, so keep clicking them until content appears using_wait_time 0.5 do 10.times do - click_button button_text - break if page.has_content? content + send("click_#{type}", text) + break if check.call end end end diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 9e9d830a50..10147b21a4 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -106,6 +106,9 @@ feature "As a consumer I want to check out my cart", js: true do end context "using FilterShippingMethods" do + let(:user) { create(:user) } + let(:customer) { create(:customer, user: user, enterprise: distributor) } + it "shows shipping methods allowed by the rule" do # No rules in effect toggle_shipping @@ -118,18 +121,29 @@ feature "As a consumer I want to check out my cart", js: true do preferred_customer_tags: "local", preferred_shipping_method_tags: "local", preferred_matched_shipping_methods_visibility: 'visible') + create(:filter_shipping_methods_tag_rule, + enterprise: distributor, + is_default: true, + preferred_shipping_method_tags: "local", + preferred_matched_shipping_methods_visibility: 'hidden') visit checkout_path checkout_as_guest - # Rule in effect, disallows access to 'Local' + # Default 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) + quick_login_as(user) + visit checkout_path + + # Default rule in still effect, disallows access to 'Local' + page.should have_content "Frogs" + page.should have_content "Donkeys" + page.should_not have_content "Local" + + customer.update_attribute(:tag_list, "local") visit checkout_path - checkout_as_guest # #local Customer can access 'Local' shipping method page.should have_content "Frogs" @@ -286,7 +300,7 @@ feature "As a consumer I want to check out my cart", js: true do # Does not show duplicate shipping fee visit checkout_path - page.all("th", text: "Shipping").count.should == 1 + page.should have_selector "th", text: "Shipping", count: 1 end end end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 9b03259ec5..7dc9f474da 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -251,9 +251,12 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "when a product goes out of stock just before it's added to the cart" do it "stops the attempt, shows an error message and refreshes the products asynchronously" do + expect(page).to have_content "Product" + variant.update_attributes! on_hand: 0 # -- Messaging + expect(page).to have_input "variants[#{variant.id}]" fill_in "variants[#{variant.id}]", with: '1' wait_until { !cart_dirty } diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index 92945a60be..b59cb6a613 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -193,7 +193,7 @@ feature "shopping with variant overrides defined", js: true do end place_order - page.should have_content "Your order has been processed successfully" + expect(page).to have_content "Your order has been processed successfully" end def click_checkout diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 9625640fe7..8b1a709462 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -13,6 +13,7 @@ feature 'Shops', js: true do let!(:er) { create(:enterprise_relationship, parent: distributor, child: producer) } before do + producer.set_producer_property 'Organic', 'NASAA 12345' visit shops_path end @@ -36,9 +37,8 @@ feature 'Shops', js: true do it "should show closed shops after clicking the button" do create(:simple_product, distributors: [d1, d2]) visit shops_path - click_link "Show closed shops" - page.should have_selector 'hub.inactive' - page.should have_selector 'hub.inactive', text: d2.name + click_link_and_ensure("Show closed shops", -> { page.has_selector? 'hub.inactive' }) + page.should have_selector 'hub.inactive', text: d2.name end it "should link to the hub page" do @@ -46,10 +46,31 @@ feature 'Shops', js: true do expect(page).to have_current_path enterprise_shop_path(distributor) end - it "should show hub producer modals" do - expand_active_table_node distributor.name - expect(page).to have_content producer.name - open_enterprise_modal producer - modal_should_be_open_for producer + describe "hub producer modal" do + let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) } + let!(:taxon) { create(:taxon, name: 'Fruit') } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } + + it "should show hub producer modals" do + expand_active_table_node distributor.name + expect(page).to have_content producer.name + open_enterprise_modal producer + modal_should_be_open_for producer + + within ".reveal-modal" do + expect(page).to have_content 'Fruit' # Taxon + expect(page).to have_content 'Organic' # Producer property + end + end + end + + def click_link_and_ensure(link_text, check) + # Buttons appear to be unresponsive for a while, so keep clicking them until content appears + using_wait_time 0.5 do + 10.times do + click_link link_text + break if check.call + end + end end end diff --git a/spec/helpers/enterprises_helper_spec.rb b/spec/helpers/enterprises_helper_spec.rb index 1dc32d63b3..8e8fa3ed5e 100644 --- a/spec/helpers/enterprises_helper_spec.rb +++ b/spec/helpers/enterprises_helper_spec.rb @@ -1,79 +1,223 @@ require 'spec_helper' describe EnterprisesHelper do - describe "loading available shipping methods" do + let(:user) { create(:user) } + let(:distributor) { create(:distributor_enterprise) } + let(:some_other_distributor) { create(:distributor_enterprise) } - 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) } + before { allow(helper).to receive(:spree_current_user) { user } } + + describe "loading available shipping methods" do + let!(:sm1) { create(:shipping_method, require_ship_address: false, distributors: [distributor]) } + let!(:sm2) { create(:shipping_method, require_ship_address: false, distributors: [some_other_distributor]) } + + context "when the order has no current_distributor" do + before do + allow(helper).to receive(:current_distributor) { nil } + end + + it "returns an empty array" do + expect(helper.available_shipping_methods).to eq [] + end + end + + context "when no tag rules are in effect" do + before { allow(helper).to receive(:current_distributor) { distributor } } + + it "finds the shipping methods for the current distributor" do + expect(helper.available_shipping_methods).to_not include sm2 + expect(helper.available_shipping_methods).to include sm1 + end + end + + context "when FilterShippingMethods tag rules are in effect" do + let(:customer) { create(:customer, user: user, enterprise: 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: "") } + let!(:default_tag_rule) { create(:filter_shipping_methods_tag_rule, + enterprise: distributor, + is_default: true, + preferred_shipping_method_tags: "local-delivery") } + let!(:tagged_sm) { sm1 } + let!(:untagged_sm) { sm2 } before do + tagged_sm.update_attribute(:tag_list, 'local-delivery') distributor.shipping_methods = [tagged_sm, untagged_sm] - allow(helper).to receive(:current_order) { order } + allow(helper).to receive(:current_distributor) { distributor } end - context "with a preferred visiblity of 'visible" do + context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible') } + before { default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden') } context "when the customer is nil" do it "applies default action (hide)" do + expect(helper.current_customer).to be nil 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) } + before { customer.update_attribute(:tag_list, 'local') } it "applies the action (show)" do + expect(helper.current_customer).to eq customer 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) } + before { customer.update_attribute(:tag_list, 'something') } it "applies the default action (hide)" do + expect(helper.current_customer).to eq customer 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 + context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do before { tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden') } + before { default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible') } context "when the customer is nil" do it "applies default action (show)" do + expect(helper.current_customer).to be nil 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) } + before { customer.update_attribute(:tag_list, 'local') } it "applies the action (hide)" do + expect(helper.current_customer).to eq customer 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) } + before { customer.update_attribute(:tag_list, 'something') } it "applies the default action (show)" do + expect(helper.current_customer).to eq customer expect(helper.available_shipping_methods).to include tagged_sm, untagged_sm end end end end end + + describe "loading available payment methods" do + let!(:pm1) { create(:payment_method, distributors: [distributor])} + let!(:pm2) { create(:payment_method, distributors: [some_other_distributor])} + + context "when the order has no current_distributor" do + before do + allow(helper).to receive(:current_distributor) { nil } + end + + it "returns an empty array" do + expect(helper.available_payment_methods).to eq [] + end + end + + context "when no tag rules are in effect" do + before { allow(helper).to receive(:current_distributor) { distributor } } + + it "finds the payment methods for the current distributor" do + expect(helper.available_payment_methods).to_not include pm2 + expect(helper.available_payment_methods).to include pm1 + end + end + + context "when FilterPaymentMethods tag rules are in effect" do + let(:customer) { create(:customer, user: user, enterprise: distributor) } + let!(:tag_rule) { create(:filter_payment_methods_tag_rule, + enterprise: distributor, + preferred_customer_tags: "trusted", + preferred_payment_method_tags: "trusted") } + let!(:default_tag_rule) { create(:filter_payment_methods_tag_rule, + enterprise: distributor, + is_default: true, + preferred_payment_method_tags: "trusted") } + let(:tagged_pm) { pm1 } + let(:untagged_pm) { pm2 } + + before do + tagged_pm.update_attribute(:tag_list, 'trusted') + distributor.payment_methods = [tagged_pm, untagged_pm] + allow(helper).to receive(:current_distributor) { distributor } + end + + context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do + before { tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'visible') } + before { default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'hidden') } + + context "when the customer is nil" do + it "applies default action (hide)" do + expect(helper.current_customer).to be nil + expect(helper.available_payment_methods).to include untagged_pm + expect(helper.available_payment_methods).to_not include tagged_pm + end + end + + context "when the customer's tags match" do + before { customer.update_attribute(:tag_list, 'trusted') } + + it "applies the action (show)" do + expect(helper.current_customer).to eq customer + expect(helper.available_payment_methods).to include tagged_pm, untagged_pm + end + end + + context "when the customer's tags don't match" do + before { customer.update_attribute(:tag_list, 'something') } + + it "applies the default action (hide)" do + expect(helper.current_customer).to eq customer + expect(helper.available_payment_methods).to include untagged_pm + expect(helper.available_payment_methods).to_not include tagged_pm + end + end + end + + context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do + before { tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'hidden') } + before { default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'visible') } + + context "when the customer is nil" do + it "applies default action (show)" do + expect(helper.current_customer).to be nil + expect(helper.available_payment_methods).to include tagged_pm, untagged_pm + end + end + + context "when the customer's tags match" do + before { customer.update_attribute(:tag_list, 'trusted') } + + it "applies the action (hide)" do + expect(helper.current_customer).to eq customer + expect(helper.available_payment_methods).to include untagged_pm + expect(helper.available_payment_methods).to_not include tagged_pm + end + end + + context "when the customer's tags don't match" do + before { customer.update_attribute(:tag_list, 'something') } + + it "applies the default action (show)" do + expect(helper.current_customer).to eq customer + expect(helper.available_payment_methods).to include tagged_pm, untagged_pm + end + end + end + end + end end diff --git a/spec/helpers/injection_helper_spec.rb b/spec/helpers/injection_helper_spec.rb index 3b62fb2660..acec197b20 100644 --- a/spec/helpers/injection_helper_spec.rb +++ b/spec/helpers/injection_helper_spec.rb @@ -26,18 +26,20 @@ describe InjectionHelper do it "injects shipping_methods" do sm = create(:shipping_method) - helper.stub(:current_order).and_return order = create(:order) - 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} ) + current_distributor = create(:distributor_enterprise, shipping_methods: [sm]) + order = create(:order, distributor: current_distributor) + allow(helper).to receive(:current_order) { order } + allow(helper).to receive(:spree_current_user) { 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 it "injects payment methods" do pm = create(:payment_method) - helper.stub_chain(:current_order, :available_payment_methods).and_return [pm] + current_distributor = create(:distributor_enterprise, payment_methods: [pm]) + order = create(:order, distributor: current_distributor) + allow(helper).to receive(:current_order) { order } + allow(helper).to receive(:spree_current_user) { nil } helper.inject_available_payment_methods.should match pm.id.to_s helper.inject_available_payment_methods.should match pm.name end diff --git a/spec/javascripts/unit/admin/controllers/providers_controller_decorator.js.coffee b/spec/javascripts/unit/admin/controllers/providers_controller_decorator.js.coffee index 2fcd776035..4cbf017244 100644 --- a/spec/javascripts/unit/admin/controllers/providers_controller_decorator.js.coffee +++ b/spec/javascripts/unit/admin/controllers/providers_controller_decorator.js.coffee @@ -5,7 +5,7 @@ describe "ProvidersCtrl", -> describe "initialising using a payment method without a type", -> beforeEach -> - module 'ofn.admin' + module 'admin.paymentMethods' scope = {} paymentMethod = type: null @@ -18,7 +18,7 @@ describe "ProvidersCtrl", -> describe "initialising using a payment method with a type", -> beforeEach -> - module 'ofn.admin' + module 'admin.paymentMethods' scope = {} paymentMethod = type: "NOT NULL" @@ -27,4 +27,4 @@ describe "ProvidersCtrl", -> ctrl = $controller 'ProvidersCtrl', {$scope: scope, paymentMethod: paymentMethod } it "sets the include_html porperty on scope to some address", -> - expect(scope.include_html).toBe "/admin/payment_methods/show_provider_preferences?provider_type=NOT NULL;pm_id=#{paymentMethod.id};" \ No newline at end of file + expect(scope.include_html).toBe "/admin/payment_methods/show_provider_preferences?provider_type=NOT NULL;pm_id=#{paymentMethod.id};" 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 7854700dfd..5f190b9382 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,6 +1,7 @@ describe "CustomersCtrl", -> scope = null http = null + shops = null beforeEach -> module('admin.customers') @@ -8,37 +9,61 @@ describe "CustomersCtrl", -> $provide.value 'columns', [] null + shops = [ + { name: "Shop 1", id: 1 } + { name: "Shop 2", id: 2 } + { name: "Shop 3", id: 3 } + ] + + inject ($controller, $rootScope, _CustomerResource_, $httpBackend) -> scope = $rootScope http = $httpBackend - $controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: {}} + $controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: shops} jasmine.addMatchers toDeepEqual: (util, customEqualityTesters) -> compare: (actual, expected) -> { pass: angular.equals(actual, expected) } - it "has no shop pre-selected", -> - expect(scope.CurrentShop.shop).toEqual {} + it "has no shop pre-selected", inject (CurrentShop) -> + expect(CurrentShop.shop).toEqual {} describe "setting the shop on scope", -> - customer = { id: 5, email: 'someone@email.com'} + customer = { id: 5, email: 'someone@email.com', code: 'a'} customers = [customer] - beforeEach -> - http.expectGET('/admin/customers.json?enterprise_id=1').respond 200, customers + beforeEach inject (pendingChanges) -> + spyOn(pendingChanges, "removeAll") + scope.customers_form = jasmine.createSpyObj('customers_form', ['$setPristine']) + http.expectGET('/admin/customers.json?enterprise_id=3').respond 200, customers scope.$apply -> - scope.CurrentShop.shop = {id: 1} + scope.shop_id = 3 http.flush() + it "sets the CurrentShop", inject (CurrentShop) -> + expect(CurrentShop.shop).toEqual shops[2] + + it "sets the form state to pristine", -> + expect(scope.customers_form.$setPristine).toHaveBeenCalled() + + it "clears all changes", inject (pendingChanges) -> + expect(pendingChanges.removeAll).toHaveBeenCalled() + it "retrievs the list of customers", -> expect(scope.customers).toDeepEqual customers + it "finds customers by code", -> + as = scope.findByCode('a') + expect(as).toDeepEqual customers + as = scope.findByCode('b') + expect(as).toDeepEqual [] + describe "scope.add", -> it "creates a new customer", -> email = "customer@example.org" newCustomer = {id: 6, email: email} customers.unshift(newCustomer) - http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=1').respond 200, newCustomer + http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=3').respond 200, newCustomer scope.add(email) http.flush() expect(scope.customers).toDeepEqual customers @@ -60,7 +85,7 @@ describe "CustomersCtrl", -> { text: 'three' } ] beforeEach -> - http.expectGET('/admin/tag_rules/map_by_tag.json?enterprise_id=1').respond 200, tags + http.expectGET('/admin/tag_rules/map_by_tag.json?enterprise_id=3').respond 200, tags it "retrieves the tag list", -> promise = scope.findTags('') diff --git a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee index 8fca1c6ab5..1ade26f19e 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee @@ -38,7 +38,7 @@ describe "indexPanelCtrl", -> expect(scope.saving).toBe false it "emits an 'enterprise:updated' event", -> - expect(scope.$emit).toHaveBeenCalledWith("enterprise:updated") + expect(scope.$emit).toHaveBeenCalledWith("enterprise:updated", scope.enterprise) describe "when the save is unsuccessful", -> beforeEach inject ($rootScope) -> diff --git a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee index 92eba790c3..1b075a94be 100644 --- a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee @@ -1,7 +1,8 @@ describe "PanelRow directive", -> Panels = null element = null - directiveScope = null + ctrlScope = null + panelScope = null beforeEach -> module 'admin.indexUtils' @@ -13,24 +14,27 @@ describe "PanelRow directive", -> Panels = _Panels_ $templateCache.put 'admin/panel.html', '{{ template }}' # Declare the directive HTML. - element = angular.element('
') + element = angular.element('
') # Define the root scope. scope = $rootScope # Compile and digest the directive. $compile(element) scope scope.$digest() - directiveScope = element.find('span').scope() + ctrlScope = element.find('tbody').isolateScope() + panelScope = element.find('tr').isolateScope() return describe "initialisation", -> - it "registers the scope with the panels service", -> - expect(Panels.panels[12]).toEqual directiveScope + it "registers a listener on the row scope", -> + expect(ctrlScope.$$listeners["selection:changed"].length).toEqual 1 - describe "setting the selected panel", -> + describe "when a select event is triggered on the row scope", -> beforeEach -> - directiveScope.setSelected('panel1') + ctrlScope.$broadcast('selection:changed', 'panel1') it 'updates the active template on the scope', -> + panelScope.$digest() + expect(panelScope.template).toEqual "admin/panels/template.html" expect(element.find('span').html()).toEqual "admin/panels/template.html" return diff --git a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee index 7ee69cf88e..8a659df984 100644 --- a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee @@ -8,45 +8,110 @@ describe "Panels service", -> Panels = _Panels_ describe "registering panels", -> - it "adds the panel provided scope to @panelsm indexed by the provided id", -> - Panels.register(23, { some: 'scope'} ) - expect(Panels.panels[23]).toEqual { some: 'scope' } + ctrl1 = ctrl2 = null + beforeEach -> + ctrl1 = jasmine.createSpyObj('ctrl', ['select']) + ctrl2 = jasmine.createSpyObj('ctrl', ['select']) - it "ignores the input if id or scope are null", -> - Panels.register(null, { some: 'scope'} ) - Panels.register(23, null) - expect(Panels.panels).toEqual { } + it "adds the panels controller, object and selection to @panels", -> + Panels.register(ctrl1, { name: "obj1"}, "panel1") + Panels.register(ctrl2, { name: "obj2"}) + expect(Panels.panels).toEqual [ + { ctrl: ctrl1, object: { name: "obj1"}, selected: "panel1" }, + { ctrl: ctrl2, object: { name: "obj2"}, selected: null } + ] + + it "call select on the controller if a selection is provided", -> + Panels.register(ctrl1, { name: "obj1"}, "panel1") + Panels.register(ctrl2, { name: "obj2"}) + expect(ctrl1.select.calls.count()).toEqual 1 + expect(ctrl2.select.calls.count()).toEqual 0 + + it "ignores the input if object or ctrl are null", -> + Panels.register(ctrl1, null) + Panels.register(null, { name: "obj2"}) + expect(Panels.panels).toEqual [] describe "toggling a panel", -> - scopeMock = null + panelMock = ctrlMock = objMock = null beforeEach -> - scopeMock = - open: jasmine.createSpy('open') - close: jasmine.createSpy('close') - setSelected: jasmine.createSpy('setSelected') - Panels.panels = { '12': scopeMock } + ctrlMock = jasmine.createSpyObj('ctrl', ['select']) + panelMock = { ctrl: ctrlMock } + spyOn(Panels, "findPanelByObject").and.returnValue(panelMock) describe "when no panel is currently selected", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue(null) - Panels.toggle(12, 'panel_name') + panelMock.selected = null - it "calls #open on the scope", -> - expect(scopeMock.open).toHaveBeenCalledWith('panel_name') + describe "when no state is provided", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name') - describe "when #toggle is called for the currently selected panel", -> + it "selects the named panel", -> + expect(panelMock.selected).toEqual 'panel_name' + expect(ctrlMock.select).toHaveBeenCalledWith('panel_name') + + describe "when the state given is 'open'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "open") + + it "selects the named panel", -> + expect(panelMock.selected).toEqual 'panel_name' + expect(ctrlMock.select).toHaveBeenCalledWith('panel_name') + + describe "when the state given is 'closed'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "closed") + + it "does not select the named panel", -> + expect(panelMock.selected).toEqual null + expect(ctrlMock.select).not.toHaveBeenCalledWith('panel_name') + + describe "when the currently selected panel matches the named panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('panel_name') - Panels.toggle(12, 'panel_name') + panelMock.selected = 'panel_name' - it "calls #close on the scope", -> - expect(scopeMock.close).toHaveBeenCalled() + describe "when no state is provided", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name') - describe "when #toggle is called for a different panel", -> + it "de-selects the named panel", -> + expect(panelMock.selected).toEqual null + expect(ctrlMock.select).toHaveBeenCalledWith(null) + + describe "when the state given is 'open'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "open") + + it "keeps the the named panel selected, but does not call select on the controller", -> + expect(panelMock.selected).toEqual 'panel_name' + expect(ctrlMock.select).not.toHaveBeenCalledWith('panel_name') + + describe "when the state given is 'closed'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "closed") + + it "de-selects the named panel", -> + expect(panelMock.selected).toEqual null + expect(ctrlMock.select).not.toHaveBeenCalledWith('panel_name') + + describe "when the currently selected panel does not match the requested panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('some_other_panel_name') - Panels.toggle(12, 'panel_name') + panelMock.selected = 'some_other_panel' - it "calls #setSelected on the scope", -> - expect(scopeMock.setSelected).toHaveBeenCalledWith('panel_name') + describe "when no state is provided", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name') + + it "selects the named panel", -> + expect(panelMock.selected).toEqual 'panel_name' + expect(ctrlMock.select).toHaveBeenCalledWith('panel_name') + + describe "when the state given is 'open'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "open") + + it "selects the named panel", -> + expect(panelMock.selected).toEqual 'panel_name' + expect(ctrlMock.select).toHaveBeenCalledWith('panel_name') + + describe "when the state given is 'closed'", -> + beforeEach -> Panels.toggle({some: "object"}, 'panel_name', "closed") + + it "keeps the currently selected panel selected, ie. does nothing", -> + expect(panelMock.selected).toEqual "some_other_panel" + expect(ctrlMock.select).not.toHaveBeenCalledWith('panel_name') + expect(ctrlMock.select).not.toHaveBeenCalledWith('some_other_panel') diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee index 7404bbead2..70508bc519 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee @@ -8,7 +8,8 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> outgoing_exchange = {} beforeEach -> - scope = {} + scope = + $watch: jasmine.createSpy('$watch') order_cycle = coordinator_id: 123 incoming_exchanges: [incoming_exchange] diff --git a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee index 553bba5713..b3758ed20b 100644 --- a/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/dirty_variant_overrides_spec.js.coffee @@ -8,36 +8,53 @@ describe "maintaining a list of dirty variant overrides", -> beforeEach -> module "admin.variantOverrides" + module ($provide) -> + $provide.value "variantOverrides", { 2: { 1: variantOverride } } + null beforeEach inject (_DirtyVariantOverrides_) -> DirtyVariantOverrides = _DirtyVariantOverrides_ - it "adds new dirty variant overrides", -> - DirtyVariantOverrides.add variantOverride - expect(DirtyVariantOverrides.dirtyVariantOverrides).toEqual - 2: - 1: - variant_id: 1 - hub_id: 2 - price: 3 - count_on_hand: 4 + describe "adding a new dirty variant override", -> + it "adds new dirty variant overrides", -> + DirtyVariantOverrides.add(2,1,5) + expect(DirtyVariantOverrides.dirtyVariantOverrides).toEqual { 2: { 1: { id: 5, variant_id: 1, hub_id: 2 } } } - it "updates existing dirty variant overrides", -> - DirtyVariantOverrides.dirtyVariantOverrides = - 2: - 1: - variant_id: 5 - hub_id: 6 - price: 7 - count_on_hand: 8 - DirtyVariantOverrides.add variantOverride - expect(DirtyVariantOverrides.dirtyVariantOverrides).toEqual - 2: - 1: - variant_id: 1 - hub_id: 2 - price: 3 - count_on_hand: 4 + + describe "setting the value of an attribute", -> + beforeEach -> + spyOn(DirtyVariantOverrides, "add").and.callThrough() + + describe "when a record for the given VO does not exist", -> + beforeEach -> + DirtyVariantOverrides.dirtyVariantOverrides = {} + + it "sets the specified attribute on a new dirty VO", -> + DirtyVariantOverrides.set(2,1,5,'count_on_hand', 10) + expect(DirtyVariantOverrides.add).toHaveBeenCalledWith(2,1,5) + expect(DirtyVariantOverrides.dirtyVariantOverrides).toEqual + 2: + 1: + id: 5 + variant_id: 1 + hub_id: 2 + count_on_hand: 10 + + describe "when a record for the given VO exists", -> + beforeEach -> + DirtyVariantOverrides.dirtyVariantOverrides = { 2: { 1: { id: 5, variant_id: 1, hub_id: 2, price: 27 } } } + + it "sets the specified attribute on a new dirty VO", -> + DirtyVariantOverrides.set(2,1,5,'count_on_hand', 10) + expect(DirtyVariantOverrides.add).toHaveBeenCalledWith(2,1,5) + expect(DirtyVariantOverrides.dirtyVariantOverrides).toEqual + 2: + 1: + id: 5 + variant_id: 1 + hub_id: 2 + price: 27 + count_on_hand: 10 describe "with a number of variant overrides", -> beforeEach -> diff --git a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee index 73dbdabee8..26f6d1ce93 100644 --- a/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/variant_overrides_spec.js.coffee @@ -1,9 +1,9 @@ describe "VariantOverrides service", -> VariantOverrides = $httpBackend = null variantOverrides = [ - {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } - {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false} - {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false} + {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } ] beforeEach -> @@ -19,10 +19,10 @@ describe "VariantOverrides service", -> it "indexes variant overrides by hub_id -> variant_id", -> expect(VariantOverrides.variantOverrides).toEqual 10: - 100: {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } - 200: {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } + 100: {id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 200: {id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } 20: - 300: {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } + 300: {id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } it "ensures blank data available for some products", -> hubs = [{id: 10}, {id: 20}, {id: 30}] @@ -34,23 +34,23 @@ describe "VariantOverrides service", -> ] VariantOverrides.ensureDataFor hubs, products expect(VariantOverrides.variantOverrides[10]).toEqual - 100: { id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false } - 200: { id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false } - 300: { hub_id: 10, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 400: { hub_id: 10, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 500: { hub_id: 10, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 100: { id: 1, hub_id: 10, variant_id: 100, sku: "V100", price: 1, count_on_hand: 1, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 200: { id: 2, hub_id: 10, variant_id: 200, sku: "V200", price: 2, count_on_hand: 2, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 300: { hub_id: 10, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 400: { hub_id: 10, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 500: { hub_id: 10, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } expect(VariantOverrides.variantOverrides[20]).toEqual - 100: { hub_id: 20, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 200: { hub_id: 20, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 300: { id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false } - 400: { hub_id: 20, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 500: { hub_id: 20, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 100: { hub_id: 20, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 200: { hub_id: 20, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 300: { id: 3, hub_id: 20, variant_id: 300, sku: "V300", price: 3, count_on_hand: 3, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 400: { hub_id: 20, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: []} + 500: { hub_id: 20, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } expect(VariantOverrides.variantOverrides[30]).toEqual - 100: { hub_id: 30, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 200: { hub_id: 30, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 300: { hub_id: 30, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 400: { hub_id: 30, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } - 500: { hub_id: 30, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false } + 100: { hub_id: 30, variant_id: 100, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 200: { hub_id: 30, variant_id: 200, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 300: { hub_id: 30, variant_id: 300, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: []} + 400: { hub_id: 30, variant_id: 400, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } + 500: { hub_id: 30, variant_id: 500, sku: null, price: null, count_on_hand: null, on_demand: null, default_stock: null, resettable: false, tag_list : '', tags: [] } it "updates the IDs of variant overrides", -> VariantOverrides.variantOverrides[2] = {} 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 index 4a0c304ee8..7ac5cc8fbc 100644 --- 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 @@ -7,6 +7,7 @@ describe "TagRulesCtrl", -> module('admin.tagRules') enterprise = id: 45 + default_tag_group: { tags: "", rules: [{ id: 7, preferred_customer_tags: "trusted" }] } 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" }] } @@ -14,24 +15,27 @@ describe "TagRulesCtrl", -> inject ($rootScope, $controller) -> scope = $rootScope + scope.enterprise_form = jasmine.createSpyObj('enterprise_form', ['$setDirty']) 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 + expect(scope.tagGroups[0].startIndex).toEqual 1 + expect(scope.tagGroups[1].startIndex).toEqual 3 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.enterprise_form.$setDirty).toHaveBeenCalled() + 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 + expect(scope.tagGroups[0].startIndex).toEqual 1 + expect(scope.tagGroups[1].startIndex).toEqual 4 describe "deleting a tag group", -> describe "where the rule is not in the rule list for the tagGroup", -> @@ -58,8 +62,8 @@ describe "TagRulesCtrl", -> 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 + expect(scope.tagGroups[0].startIndex).toEqual 1 + expect(scope.tagGroups[1].startIndex).toEqual 2 describe "without an id", -> rule = null @@ -75,5 +79,5 @@ describe "TagRulesCtrl", -> 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 + expect(scope.tagGroups[0].startIndex).toEqual 1 + expect(scope.tagGroups[1].startIndex).toEqual 2 diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index cc0b574f47..c1c34cbe6b 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -668,6 +668,9 @@ describe "AdminProductEditCtrl", -> spyOn $scope, "displaySuccess" spyOn BulkProducts, "updateVariantLists" spyOn DirtyProducts, "clear" + + $scope.bulk_product_form = jasmine.createSpyObj('bulk_product_form', ['$setPristine']) + $scope.products = [ { id: 1 @@ -692,6 +695,7 @@ describe "AdminProductEditCtrl", -> $httpBackend.flush() $timeout.flush() expect($scope.displaySuccess).toHaveBeenCalled() + expect($scope.bulk_product_form.$setPristine).toHaveBeenCalled expect(DirtyProducts.clear).toHaveBeenCalled() expect(BulkProducts.updateVariantLists).toHaveBeenCalled() diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index bbbff7d82b..5fec8b93f5 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -9,7 +9,9 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = + order_cycle_form: jasmine.createSpyObj('order_cycle_form', ['$dirty']) + $watch: jasmine.createSpy('$watch') event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = @@ -106,11 +108,6 @@ describe 'OrderCycle controllers', -> {id: 2, name: 'Pepper Tree Place'} ]) - it 'Delegates toggleProducts to OrderCycle', -> - scope.toggleProducts(event, 'exchange') - expect(event.preventDefault).toHaveBeenCalled() - expect(OrderCycle.toggleProducts).toHaveBeenCalledWith('exchange') - it 'Delegates enterpriseFeesForEnterprise to EnterpriseFee', -> scope.enterpriseFeesForEnterprise('123') expect(EnterpriseFee.forEnterprise).toHaveBeenCalledWith(123) @@ -272,11 +269,6 @@ describe 'OrderCycle controllers', -> {id: 2, name: 'Pepper Tree Place'} ]) - it 'Delegates toggleProducts to OrderCycle', -> - scope.toggleProducts(event, 'exchange') - expect(event.preventDefault).toHaveBeenCalled() - expect(OrderCycle.toggleProducts).toHaveBeenCalledWith('exchange') - it 'Delegates enterpriseFeesForEnterprise to EnterpriseFee', -> scope.enterpriseFeesForEnterprise('123') expect(EnterpriseFee.forEnterprise).toHaveBeenCalledWith(123) @@ -534,26 +526,6 @@ describe 'OrderCycle services', -> OrderCycle.order_cycle.outgoing_exchanges = [exchange] expect(OrderCycle.exchangeDirection(exchange)).toEqual 'outgoing' - describe 'toggling products', -> - exchange = null - - beforeEach -> - exchange = {} - - it 'sets a blank value to true', -> - OrderCycle.toggleProducts(exchange) - expect(exchange.showProducts).toEqual(true) - - it 'sets a true value to false', -> - exchange.showProducts = true - OrderCycle.toggleProducts(exchange) - expect(exchange.showProducts).toEqual(false) - - it 'sets a false value to true', -> - exchange.showProducts = false - OrderCycle.toggleProducts(exchange) - expect(exchange.showProducts).toEqual(true) - describe "setting exchange variants", -> describe "when I have permissions to edit the variants", -> beforeEach -> diff --git a/spec/lib/open_food_network/cached_products_renderer_spec.rb b/spec/lib/open_food_network/cached_products_renderer_spec.rb index 03b0ab05d5..6e096fc6d1 100644 --- a/spec/lib/open_food_network/cached_products_renderer_spec.rb +++ b/spec/lib/open_food_network/cached_products_renderer_spec.rb @@ -8,70 +8,88 @@ module OpenFoodNetwork let(:order_cycle) { double(:order_cycle, id: 456) } let(:cpr) { CachedProductsRenderer.new(distributor, order_cycle) } - describe "when the distribution is not set" do - let(:cpr) { CachedProductsRenderer.new(nil, nil) } - - it "raises an exception and returns no products" do - expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts - end - end - - describe "when the products JSON is already cached" do - before do - Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", 'products' - end - - it "returns the cached JSON" do - expect(cpr.products_json).to eq 'products' - end - - it "raises an exception when there are no products" do - Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", nil - expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts - end - end - - describe "when the products JSON is not cached" do - let(:cached_json) { Rails.cache.read "products-json-#{distributor.id}-#{order_cycle.id}" } - let(:cache_present) { Rails.cache.exist? "products-json-#{distributor.id}-#{order_cycle.id}" } - - before do - Rails.cache.clear - cpr.stub(:uncached_products_json) { 'fresh products' } - end - - describe "when there are products" do - it "returns products as JSON" do - expect(cpr.products_json).to eq 'fresh products' + describe "fetching cached products JSON" do + context "when in testing / development" do + before do + allow(cpr).to receive(:uncached_products_json) { "uncached products" } end - it "caches the JSON" do - cpr.products_json - expect(cached_json).to eq 'fresh products' - end - - it "logs a warning" do - cpr.should_receive :log_warning - cpr.products_json + it "returns uncaches products JSON" do + expect(cpr.products_json).to eq 'uncached products' end end - describe "when there are no products" do - before { cpr.stub(:uncached_products_json).and_raise ProductsRenderer::NoProducts } - - it "raises an error" do - expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + context "when in production / staging" do + before do + allow(Rails.env).to receive(:production?) { true } end - it "caches the products as nil" do - expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts - expect(cache_present).to be - expect(cached_json).to be_nil + describe "when the distribution is not set" do + let(:cpr) { CachedProductsRenderer.new(nil, nil) } + + it "raises an exception and returns no products" do + expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + end end - it "logs a warning" do - cpr.should_receive :log_warning - expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + describe "when the products JSON is already cached" do + before do + Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", 'products' + end + + it "returns the cached JSON" do + expect(cpr.products_json).to eq 'products' + end + + it "raises an exception when there are no products" do + Rails.cache.write "products-json-#{distributor.id}-#{order_cycle.id}", nil + expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + end + end + + describe "when the products JSON is not cached" do + let(:cached_json) { Rails.cache.read "products-json-#{distributor.id}-#{order_cycle.id}" } + let(:cache_present) { Rails.cache.exist? "products-json-#{distributor.id}-#{order_cycle.id}" } + + before do + Rails.cache.clear + cpr.stub(:uncached_products_json) { 'fresh products' } + end + + describe "when there are products" do + it "returns products as JSON" do + expect(cpr.products_json).to eq 'fresh products' + end + + it "caches the JSON" do + cpr.products_json + expect(cached_json).to eq 'fresh products' + end + + it "logs a warning" do + cpr.should_receive :log_warning + cpr.products_json + end + end + + describe "when there are no products" do + before { cpr.stub(:uncached_products_json).and_raise ProductsRenderer::NoProducts } + + it "raises an error" do + expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + end + + it "caches the products as nil" do + expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + expect(cache_present).to be + expect(cached_json).to be_nil + end + + it "logs a warning" do + cpr.should_receive :log_warning + expect { cpr.products_json }.to raise_error CachedProductsRenderer::NoProducts + end + end end end end diff --git a/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb b/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb index 89dcb364b8..80fef2b60e 100644 --- a/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb +++ b/spec/lib/open_food_network/order_cycle_form_applicator_spec.rb @@ -29,7 +29,7 @@ module OpenFoodNetwork coordinator_id = 123 distributor_id = 456 - outgoing_exchange = {:enterprise_id => distributor_id, :incoming => false, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'} + outgoing_exchange = {:enterprise_id => distributor_id, :incoming => false, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions', tag_list: 'wholesale'} oc = double(:order_cycle, :coordinator_id => coordinator_id, :exchanges => [], :incoming_exchanges => [], :outgoing_exchanges => [outgoing_exchange]) @@ -37,7 +37,7 @@ module OpenFoodNetwork applicator.should_receive(:outgoing_exchange_variant_ids).with(outgoing_exchange).and_return([1, 3]) applicator.should_receive(:exchange_exists?).with(coordinator_id, distributor_id, false).and_return(false) - applicator.should_receive(:add_exchange).with(coordinator_id, distributor_id, false, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'}) + applicator.should_receive(:add_exchange).with(coordinator_id, distributor_id, false, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions', tag_list: 'wholesale'}) applicator.should_receive(:destroy_untouched_exchanges) applicator.go! @@ -69,7 +69,7 @@ module OpenFoodNetwork coordinator_id = 123 distributor_id = 456 - outgoing_exchange = {:enterprise_id => distributor_id, :incoming => false, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'} + outgoing_exchange = {:enterprise_id => distributor_id, :incoming => false, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions', tag_list: 'wholesale'} oc = double(:order_cycle, :coordinator_id => coordinator_id, @@ -81,7 +81,7 @@ module OpenFoodNetwork applicator.should_receive(:outgoing_exchange_variant_ids).with(outgoing_exchange).and_return([1, 3]) applicator.should_receive(:exchange_exists?).with(coordinator_id, distributor_id, false).and_return(true) - applicator.should_receive(:update_exchange).with(coordinator_id, distributor_id, false, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'}) + applicator.should_receive(:update_exchange).with(coordinator_id, distributor_id, false, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions', tag_list: 'wholesale'}) applicator.should_receive(:destroy_untouched_exchanges) applicator.go! @@ -144,128 +144,122 @@ module OpenFoodNetwork end end - describe "finding alterable exchange variants" do - let(:coordinator_mock) { double(:enterprise) } - let(:oc) { double(:oc, coordinator: coordinator_mock ) } + describe "updating the list of variants for a given outgoing exchange" do + let!(:v1) { create(:variant) } # Not Existing + Request Add + Editable + Incoming + let!(:v2) { create(:variant) } # Not Existing + Request Add + Not Editable + Incoming + let!(:v3) { create(:variant) } # Existing + Request Add + Editable + Incoming + let!(:v4) { create(:variant) } # Existing + Not mentioned + Editable + Incoming + let!(:v5) { create(:variant) } # Existing + Request Remove + Editable + Incoming + let!(:v6) { create(:variant) } # Existing + Request Remove + Not Editable + Incoming + let!(:v7) { create(:variant) } # Existing + Request Add + Not Editable + Not Incoming + let!(:v8) { create(:variant) } # Existing + Request Add + Editable + Not Incoming + let!(:v9) { create(:variant) } # Not Existing + Request Add + Editable + Not Incoming + let!(:exchange) { create(:exchange, incoming: false, variant_ids: [v3.id, v4.id, v5.id, v6.id, v7.id, v8.id]) } + let!(:oc) { exchange.order_cycle } + let!(:enterprise) { exchange.receiver } + let!(:coordinator) { oc.coordinator } let!(:applicator) { OrderCycleFormApplicator.new(oc, user) } - - describe "converting the existing variants of an exchange to a hash" do - context "when nil is passed in" do - let(:hash) { applicator.send(:persisted_variants_hash, nil) } - - it "returns an empty hash" do - expect(hash).to eq({}) - end - end - - context "when an exchange is passed in" do - let(:v1) { create(:variant) } - let(:v2) { create(:variant) } - let(:v3) { create(:variant) } - let(:exchange) { create(:exchange, variants: [v1, v2, v3]) } - let(:hash) { applicator.send(:persisted_variants_hash, exchange) } - - before do - allow(applicator).to receive(:editable_variant_ids_for_outgoing_exchange_between) { [ v1.id, v2.id ] } - end - - it "returns a hash with variant ids as keys" do - expect(hash.length).to be 3 - expect(hash.keys).to include v1.id, v2.id, v3.id - end - - it "editable variant ids are set to false" do - expect(hash[v1.id]).to be false - expect(hash[v2.id]).to be false - end - - it "and non-editable variant ids are set to true" do - expect(hash[v3.id]).to be true - end - end + let(:ids) do + applicator.send(:outgoing_exchange_variant_ids, { + :enterprise_id => enterprise.id, + :variants => { + "#{v1.id}" => true, + "#{v2.id}" => true, + "#{v3.id}" => true, + "#{v5.id}" => false, + "#{v6.id}" => false, + "#{v7.id}" => true, + "#{v8.id}" => true, + "#{v9.id}" => true + } + }) end - context "where a matching exchange does not exist" do - let(:enterprise_mock) { double(:enterprise) } - before do - applicator.stub(:find_outgoing_exchange) { nil } - applicator.stub(:incoming_variant_ids) { [1, 2, 3, 4] } - expect(applicator).to receive(:editable_variant_ids_for_outgoing_exchange_between). - with(coordinator_mock, enterprise_mock) { [1, 2, 3] } - end - - it "converts exchange variant ids hash to an array of ids" do - applicator.stub(:persisted_variants_hash) { {} } - expect(Enterprise).to receive(:find) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}) - expect(ids).to eq [1, 3] - end - - it "restricts exchange variant ids to those editable by the current user" do - applicator.stub(:persisted_variants_hash) { {4 => true} } - expect(Enterprise).to receive(:find) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true, '4' => false}}) - expect(ids).to eq [1, 3, 4] - end - - it "overrides existing variants based on submitted data, when user has permission" do - applicator.stub(:persisted_variants_hash) { {2 => true} } - expect(Enterprise).to receive(:find) { enterprise_mock} - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}) - expect(ids).to eq [1, 3] - end + before do + allow(applicator).to receive(:incoming_variant_ids) { [v1.id, v2.id, v3.id, v4.id, v5.id, v6.id] } + allow(applicator).to receive(:editable_variant_ids_for_outgoing_exchange_between) { [v1.id, v3.id, v4.id, v5.id, v8.id, v9.id]} end - context "where a matching exchange exists" do - let(:enterprise_mock) { double(:enterprise) } - let(:exchange_mock) { double(:exchange) } + it "updates the list of variants for the exchange" do + # Adds variants that are editable + expect(ids).to include v1.id - before do - applicator.stub(:find_outgoing_exchange) { exchange_mock } - applicator.stub(:incoming_variant_ids) { [1, 2, 3, 4] } - allow(applicator).to receive(:editable_variant_ids_for_outgoing_exchange_between). - with(coordinator_mock, enterprise_mock) { [1, 2, 3] } - end + # Does not add variants that are not editable + expect(ids).to_not include v2.id - it "converts exchange variant ids hash to an array of ids" do - applicator.stub(:persisted_variants_hash) { {} } - expect(exchange_mock).to receive(:receiver) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}) - expect(ids).to eq [1, 3] - end + # Keeps existing variants, when they are explicitly mentioned in the request + expect(ids).to include v3.id - it "restricts exchange variant ids to those editable by the current user" do - applicator.stub(:persisted_variants_hash) { {4 => true} } - expect(exchange_mock).to receive(:receiver) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true, '4' => false}}) - expect(ids).to eq [1, 3, 4] - end + # Removes existing variants that are editable, when they are not mentioned in the request + expect(ids).to_not include v4.id - it "overrides existing variants based on submitted data, when user has permission" do - applicator.stub(:persisted_variants_hash) { {2 => true} } - expect(exchange_mock).to receive(:receiver) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}) - expect(ids).to eq [1, 3] - end + # Removes existing variants that are editable, when the request explicitly removes them + expect(ids).to_not include v5.id - it "removes variants which the user has permission to remove and that are not included in the submitted data" do - allow(exchange_mock).to receive(:incoming?) { false } - allow(exchange_mock).to receive(:variants) { [double(:variant, id: 1), double(:variant, id: 2), double(:variant, id: 3)] } - allow(exchange_mock).to receive(:sender) { coordinator_mock } - allow(exchange_mock).to receive(:receiver) { enterprise_mock } - applicator.stub(:incoming_variant_ids) { [1, 2, 3] } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '3' => true}}) - expect(ids).to eq [1, 3] - end + # Keeps existing variants that are not editable + expect(ids).to include v6.id - it "removes variants which are not included in incoming exchanges" do - applicator.stub(:incoming_variant_ids) { [1, 2] } - applicator.stub(:persisted_variants_hash) { {3 => true} } - expect(exchange_mock).to receive(:receiver) { enterprise_mock } - ids = applicator.send(:outgoing_exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}) - expect(ids).to eq [1] - end + # Removes existing variants that are not in an incoming exchange, regardless of whether they are not editable + expect(ids).to_not include v7.id, v8.id + + # Does not add variants that are not in an incoming exchange + expect(ids).to_not include v9.id + end + end + + describe "updating the list of variants for a given incoming exchange" do + let!(:v1) { create(:variant) } # Not Existing + Request Add + Editable + let!(:v2) { create(:variant) } # Not Existing + Request Add + Not Editable + let!(:v3) { create(:variant) } # Existing + Request Add + Editable + let!(:v4) { create(:variant) } # Existing + Request Remove + Not Editable + let!(:v5) { create(:variant) } # Existing + Request Remove + Editable + let!(:v6) { create(:variant) } # Existing + Request Remove + Not Editable + let!(:v7) { create(:variant) } # Existing + Not mentioned + Editable + let!(:exchange) { create(:exchange, incoming: true, variant_ids: [v3.id, v4.id, v5.id, v6.id, v7.id]) } + let!(:oc) { exchange.order_cycle } + let!(:enterprise) { exchange.sender } + let!(:coordinator) { oc.coordinator } + let!(:applicator) { OrderCycleFormApplicator.new(oc, user) } + let(:ids) do + applicator.send(:incoming_exchange_variant_ids, { + :enterprise_id => enterprise.id, + :variants => { + "#{v1.id}" => true, + "#{v2.id}" => true, + "#{v3.id}" => true, + "#{v4.id}" => false, + "#{v5.id}" => false, + "#{v6.id}" => false + } + }) + end + + before do + allow(applicator).to receive(:editable_variant_ids_for_incoming_exchange_between) { [v1.id, v3.id, v5.id, v7.id]} + end + + it "updates the list of variants for the exchange" do + # Adds variants that are editable + expect(ids).to include v1.id + + # Does not add variants that are not editable + expect(ids).to_not include v2.id + + # Keeps existing variants, if they are editable and requested + expect(ids).to include v3.id + + # Keeps existing variants if they are non-editable, regardless of request + expect(ids).to include v4.id + + # Removes existing variants that are editable, when the request explicitly removes them + expect(ids).to_not include v5.id + + # Keeps existing variants that are not editable + expect(ids).to include v6.id + + # Removes existing variants that are editable, when they are not mentioned in the request + expect(ids).to_not include v7.id end end @@ -375,15 +369,16 @@ module OpenFoodNetwork allow(applicator).to receive(:manager_for) { false } allow(applicator).to receive(:permission_for) { true } applicator.send(:touched_exchanges=, []) - applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions'}) + applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions', tag_list: 'wholesale'}) end - it "updates the variants, enterprise fees and pickup information of the exchange" do + it "updates the variants, enterprise fees tags, and pickup information of the exchange" do exchange.reload expect(exchange.variants).to match_array [variant1, variant3] expect(exchange.enterprise_fees).to match_array [enterprise_fee2, enterprise_fee3] expect(exchange.pickup_time).to eq 'New Pickup Time' expect(exchange.pickup_instructions).to eq 'New Pickup Instructions' + expect(exchange.tag_list).to eq ['wholesale'] expect(applicator.send(:touched_exchanges)).to eq [exchange] end end @@ -394,15 +389,16 @@ module OpenFoodNetwork allow(applicator).to receive(:manager_for) { true } allow(applicator).to receive(:permission_for) { true } applicator.send(:touched_exchanges=, []) - applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions'}) + applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions', tag_list: 'wholesale'}) end - it "updates the variants, enterprise fees and pickup information of the exchange" do + it "updates the variants, enterprise fees, tags and pickup information of the exchange" do exchange.reload expect(exchange.variants).to match_array [variant1, variant3] expect(exchange.enterprise_fees).to match_array [enterprise_fee2, enterprise_fee3] expect(exchange.pickup_time).to eq 'New Pickup Time' expect(exchange.pickup_instructions).to eq 'New Pickup Instructions' + expect(exchange.tag_list).to eq ['wholesale'] expect(applicator.send(:touched_exchanges)).to eq [exchange] end end @@ -413,15 +409,16 @@ module OpenFoodNetwork allow(applicator).to receive(:manager_for) { false } allow(applicator).to receive(:permission_for) { true } applicator.send(:touched_exchanges=, []) - applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions'}) + applicator.send(:update_exchange, sender.id, receiver.id, incoming, {:variant_ids => [variant1.id, variant3.id], :enterprise_fee_ids => [enterprise_fee2.id, enterprise_fee3.id], :pickup_time => 'New Pickup Time', :pickup_instructions => 'New Pickup Instructions', tag_list: 'wholesale'}) end - it "updates the variants in the exchange, but not the fees or pickup information" do + it "updates the variants in the exchange, but not the fees, tags or pickup information" do exchange.reload expect(exchange.variants).to match_array [variant1, variant3] expect(exchange.enterprise_fees).to match_array [enterprise_fee1, enterprise_fee2] expect(exchange.pickup_time).to_not eq 'New Pickup Time' expect(exchange.pickup_instructions).to_not eq 'New Pickup Instructions' + expect(exchange.tag_list).to eq [] expect(applicator.send(:touched_exchanges)).to eq [exchange] end end diff --git a/spec/lib/open_food_network/property_merge_spec.rb b/spec/lib/open_food_network/property_merge_spec.rb new file mode 100644 index 0000000000..85f4200ab0 --- /dev/null +++ b/spec/lib/open_food_network/property_merge_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +module OpenFoodNetwork + describe PropertyMerge do + let(:p1a) { create(:property, presentation: 'One') } + let(:p1b) { create(:property, presentation: 'One') } + let(:p2) { create(:property, presentation: 'Two') } + + describe "merging Spree::Properties" do + it "merges properties" do + expect(PropertyMerge.merge([p1a], [p1b, p2])).to eq [p1a, p2] + end + end + + describe "merging ProducerProperties and Spree::ProductProperties" do + let(:pp1a) { create(:product_property, property: p1a) } + let(:pp1b) { create(:producer_property, property: p1b) } + let(:pp2) { create(:producer_property, property: p2) } + + it "merges properties" do + expect(PropertyMerge.merge([pp1a], [pp1b, pp2])).to eq [pp1a, pp2] + end + end + end +end diff --git a/spec/lib/open_food_network/tag_rule_applicator_spec.rb b/spec/lib/open_food_network/tag_rule_applicator_spec.rb new file mode 100644 index 0000000000..97551df96e --- /dev/null +++ b/spec/lib/open_food_network/tag_rule_applicator_spec.rb @@ -0,0 +1,247 @@ +require 'open_food_network/tag_rule_applicator' + +module OpenFoodNetwork + describe TagRuleApplicator do + let!(:enterprise) { create(:distributor_enterprise) } + let!(:oc_tag_rule) { create(:filter_order_cycles_tag_rule, enterprise: enterprise, priority: 6, preferred_customer_tags: "tag1", preferred_exchange_tags: "tag1", preferred_matched_order_cycles_visibility: "visible" )} + let!(:product_tag_rule1) { create(:filter_products_tag_rule, enterprise: enterprise, priority: 5, preferred_customer_tags: "tag1", preferred_variant_tags: "tag1", preferred_matched_variants_visibility: "visible" ) } + let!(:product_tag_rule2) { create(:filter_products_tag_rule, enterprise: enterprise, priority: 4, preferred_customer_tags: "tag1", preferred_variant_tags: "tag3", preferred_matched_variants_visibility: "hidden" ) } + let!(:product_tag_rule3) { create(:filter_products_tag_rule, enterprise: enterprise, priority: 3, preferred_customer_tags: "tag2", preferred_variant_tags: "tag1", preferred_matched_variants_visibility: "visible" ) } + let!(:default_product_tag_rule) { create(:filter_products_tag_rule, enterprise: enterprise, priority: 2, is_default: true, preferred_variant_tags: "tag1", preferred_matched_variants_visibility: "hidden" ) } + let!(:sm_tag_rule) { create(:filter_shipping_methods_tag_rule, enterprise: enterprise, priority: 1, preferred_customer_tags: "tag1", preferred_shipping_method_tags: "tag1", preferred_matched_shipping_methods_visibility: "visible" )} + + describe "initialisation" do + context "when enterprise is nil" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(nil, "FilterProducts", ["tag1"]) } + it { expect{applicator}.to raise_error "Enterprise cannot be nil" } + end + + context "when rule_type is nil" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, nil, ["tag1"]) } + it { expect{applicator}.to raise_error "Rule Type cannot be nil" } + end + + context "when rule_type does not match an existing rule type" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterSomething", ["tag1"]) } + it { expect{applicator}.to raise_error NameError } + end + + context "when enterprise and rule_type are present" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterProducts", customer_tags) } + + context "when the customer tags are nil" do + let!(:customer_tags) { nil } + + it "sets customer tags to an empty array" do + expect(applicator.customer_tags).to eq [] + end + + it "does not match rules without customer tags" do + rule = double(:rule, preferred_customer_tags: "") + expect(applicator.send(:customer_tags_match?, rule)).to be false + end + end + + context "when customer tags are empty" do + let!(:customer_tags) { [] } + + it "sets customer tags to an empty array" do + expect(applicator.customer_tags).to eq [] + end + + it "does not match rules without customer tags" do + rule = double(:rule, preferred_customer_tags: "") + expect(applicator.send(:customer_tags_match?, rule)).to be false + end + end + + context "when customer_tags are present" do + let!(:customer_tags) { ["tag1"] } + + let(:rules) { applicator.send(:rules)} + let(:customer_rules) { applicator.send(:customer_rules)} + let(:default_rules) { applicator.send(:default_rules)} + + it "stores enterprise, rule_class and customer_tags as instance variables" do + expect(applicator.enterprise).to eq enterprise + expect(applicator.rule_class).to eq TagRule::FilterProducts + expect(applicator.customer_tags).to eq ["tag1"] + end + + it "selects only rules of the specified type, in order of priority" do + expect(rules).to eq [default_product_tag_rule, product_tag_rule3, product_tag_rule2, product_tag_rule1] + end + + it "splits rules into those which match customer tags and those which don't, in order of priority" do + expect(customer_rules).to eq [product_tag_rule2, product_tag_rule1] + end + + it "splits out default rules" do + expect(default_rules).to eq [default_product_tag_rule] + end + end + end + end + + describe "filter!" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterProducts", []) } + + context "when the subject is nil" do + let(:subject) { double(:subject, reject!: false) } + + it "returns immediately" do + applicator.filter!(subject) + expect(subject).to_not have_received(:reject!) + end + end + + context "when subject is empty" do + let(:subject) { double(:subject, reject!: false) } + + it "returns immediately" do + applicator.filter!(subject) + expect(subject).to_not have_received(:reject!) + end + end + + context "when subject is an array" do + let(:element) { double(:element, ) } + let(:subject) { [element] } + + context "when rule_class reponds to tagged_children_for" do + let(:child1) { double(:child) } + let(:child2) { double(:child) } + let(:children) { [child1, child2] } + let(:rule_class) { double(:rule_class, tagged_children_for: children) } + + before{ allow(applicator).to receive(:rule_class) { rule_class } } + + context "when reject? returns true only for some children" do + before do + allow(applicator).to receive(:reject?).with(child1) { true } + allow(applicator).to receive(:reject?).with(child2) { false } + applicator.filter!(subject) + end + + it "rejects the specified children from the array" do + expect(children).to eq [child2] + end + + it "does not remove the element from the original subject" do + expect(subject).to eq [element] + end + end + + context "when reject? returns true for all children" do + before do + allow(applicator).to receive(:reject?).with(child1) { true } + allow(applicator).to receive(:reject?).with(child2) { true } + applicator.filter!(subject) + end + + it "removes all children from the array" do + expect(children).to eq [] + end + + it "removes the element from the original subject" do + expect(subject).to eq [] + end + end + end + + context "when rule_class doesn't respond to tagged_children_for" do + let(:rule_class) { double(:rule_class) } + + before{ allow(applicator).to receive(:rule_class) { rule_class } } + + context "when reject? returns false for the element" do + before do + allow(applicator).to receive(:reject?).with(element) { false } + applicator.filter!(subject) + end + + it "does not remove the element from the original subject" do + expect(subject).to eq [element] + end + end + + context "when reject? returns true for the element" do + before do + allow(applicator).to receive(:reject?).with(element) { true } + applicator.filter!(subject) + end + + it "removes the element from the original subject" do + expect(subject).to eq [] + end + end + end + end + end + + describe "reject?" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterProducts", ["tag1"]) } + let(:customer_rule) { double(:customer_rule, reject_matched?: "customer_rule.reject_matched?" )} + let(:default_rule) { double(:customer_rule, reject_matched?: "default_rule.reject_matched?" )} + let(:dummy) { double(:dummy) } + + before{ allow(applicator).to receive(:customer_rules) { [customer_rule] } } + before{ allow(applicator).to receive(:default_rules) { [default_rule] } } + + context "when a customer rule matches the tags of the element" do + before{ allow(customer_rule).to receive(:tags_match?).with(dummy) { true } } + + it "returns the value of customer_rule.reject_matched?" do + expect(applicator.send(:reject?, dummy)).to eq "customer_rule.reject_matched?" + end + end + + context "when no customer rules match the tags of the element" do + before{ allow(customer_rule).to receive(:tags_match?) { false } } + + context "when a default rule matches the tags of the element" do + before{ allow(default_rule).to receive(:tags_match?) { true } } + + it "returns the value of the default_rule.reject_matched?" do + expect(applicator.send(:reject?, dummy)).to eq "default_rule.reject_matched?" + end + end + + context "when a default rule matches the tags of the element" do + before{ allow(default_rule).to receive(:tags_match?) { false } } + + it "returns false" do + expect(applicator.send(:reject?, dummy)).to be false + end + end + end + end + + + describe "smoke test for products" do + let(:product1) { { id: 1, name: 'product 1', "variants" => [{ id: 4, "tag_list" => ["tag1"] }] } } + let(:product2) { { id: 2, name: 'product 2', "variants" => [{ id: 5, "tag_list" => ["tag1"] }, {id: 9, "tag_list" => ["tag2"]}] } } + let(:product3) { { id: 3, name: 'product 3', "variants" => [{ id: 6, "tag_list" => ["tag3"] }] } } + let!(:products_array) { [product1, product2, product3] } + + context "when customer tags don't match any rules" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterProducts", ["lalalala"]) } + + it "applies the default rule" do + applicator.filter!(products_array) + expect(products_array).to eq [{ id: 2, name: 'product 2', "variants" => [{id: 9, "tag_list" => ["tag2"]}] }, product3] + end + end + + context "when customer tags match one or more rules" do + let(:applicator) { OpenFoodNetwork::TagRuleApplicator.new(enterprise, "FilterProducts", ["tag1"]) } + + it "applies those rules" do + # product_tag_rule1 and product_tag_rule2 are being applied + applicator.filter!(products_array) + expect(products_array).to eq [product1, product2] + end + end + end + end +end diff --git a/spec/lib/open_food_network/xero_invoices_report_spec.rb b/spec/lib/open_food_network/xero_invoices_report_spec.rb index 45ab77be6d..af67f8bed9 100644 --- a/spec/lib/open_food_network/xero_invoices_report_spec.rb +++ b/spec/lib/open_food_network/xero_invoices_report_spec.rb @@ -28,6 +28,7 @@ module OpenFoodNetwork report.stub(:produce_summary_rows) { ['produce'] } report.stub(:fee_summary_rows) { ['fee'] } report.stub(:shipping_summary_rows) { ['shipping'] } + report.stub(:payment_summary_rows) { ['payment'] } report.stub(:admin_adjustment_summary_rows) { ['admin'] } order.stub(:account_invoice?) { false } end diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 7291714e03..c964342073 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -14,6 +14,7 @@ describe ProducerMailer do let(:p2) { create(:product, price: 23.45, supplier: s2) } let(:p3) { create(:product, price: 34.56, supplier: s1) } let(:p4) { create(:product, price: 45.67, supplier: s1) } + let(:p5) { create(:product, price: 56.78, supplier: s1) } let(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { order_cycle.exchanges.create! sender: s1, receiver: d1, incoming: true, receival_instructions: 'Outside shed.' } @@ -33,6 +34,14 @@ describe ProducerMailer do order.save order end + let!(:order_canceled) do + order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete') + order.line_items << create(:line_item, variant: p5.variants.first) + order.finalize! + order.cancel + order.save + order + end let(:mail) { ActionMailer::Base.deliveries.last } before do @@ -75,8 +84,11 @@ describe ProducerMailer do mail.body.encoded.should_not include p3.name end + it "does not include canceled orders" do + mail.body.encoded.should_not include p5.name + end + it "includes the total" do - # puts mail.text_part.body.encoded mail.body.encoded.should include 'Total: $50.00' body_as_html(mail).find("tr.total-row") .should have_selector("td", text: "$50.00") diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb index f2ac14e9b2..32e380f4d3 100644 --- a/spec/models/customer_spec.rb +++ b/spec/models/customer_spec.rb @@ -1,6 +1,23 @@ require 'spec_helper' describe Customer, type: :model do + describe 'an existing customer' do + let(:customer) { create(:customer) } + + it "saves its code" do + code = "code one" + customer.code = code + customer.save + expect(customer.code).to eq code + end + + it "can remove its code" do + customer.code = "" + customer.save + expect(customer.code).to be nil + end + end + describe 'creation callbacks' do let!(:user1) { create(:user) } let!(:user2) { create(:user) } diff --git a/spec/models/exchange_spec.rb b/spec/models/exchange_spec.rb index 4b8f0ed1a3..1a4a4f7e3f 100644 --- a/spec/models/exchange_spec.rb +++ b/spec/models/exchange_spec.rb @@ -282,9 +282,11 @@ describe Exchange do new_oc = create(:simple_order_cycle) ex1 = oc.exchanges.last + ex1.update_attribute(:tag_list, "wholesale") ex2 = ex1.clone! new_oc ex1.eql?(ex2).should be_true + expect(ex2.reload.tag_list).to eq ["wholesale"] end describe "converting to hash" do diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 4db0541d6c..114c7d5fac 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -21,21 +21,6 @@ describe Spree::Order do end end - describe "Payment methods" do - let(:order_distributor) { create(:distributor_enterprise) } - let(:some_other_distributor) { create(:distributor_enterprise) } - let(:order) { build(:order, distributor: order_distributor) } - let(:pm1) { create(:payment_method, distributors: [order_distributor])} - let(:pm2) { create(:payment_method, distributors: [some_other_distributor])} - - it "finds the correct payment methods" do - Spree::PaymentMethod.stub(:available).and_return [pm1, pm2] - order.available_payment_methods.include?(pm2).should == false - order.available_payment_methods.include?(pm1).should == true - end - - end - describe "updating the distribution charge" do let(:order) { build(:order) } @@ -107,38 +92,6 @@ describe Spree::Order do subject.update_distribution_charge! end - - 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) } - - before do - tag_rule.calculator.update_attribute(:preferred_flat_percent, -10) - end - - 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 - - context "when the rule does not apply" do - before { tag_rule.update_attribute(:preferred_customer_tags, "tagtag") } - - 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 describe "looking up whether a line item can be provided by an order cycle" do diff --git a/spec/models/spree/payment_method_spec.rb b/spec/models/spree/payment_method_spec.rb index b61fd3bffd..70808c231e 100644 --- a/spec/models/spree/payment_method_spec.rb +++ b/spec/models/spree/payment_method_spec.rb @@ -25,5 +25,18 @@ module Spree # Testing else condition Spree::Gateway::BogusSimple.clean_name.should == "BogusSimple" end + + it "computes the amount of fees" do + pickup = create(:payment_method) + order = create(:order) + expect(pickup.compute_amount(order)).to eq 0 + transaction = create(:payment_method, calculator: Calculator::FlatRate.new(preferred_amount: 10)) + expect(transaction.compute_amount(order)).to eq 10 + transaction = create(:payment_method, calculator: Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) + expect(transaction.compute_amount(order)).to eq 0 + product = create(:product) + order.add_variant(product.master) + expect(transaction.compute_amount(order)).to eq 2.0 + end end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 9c2ee6b664..10e67e5900 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -420,7 +420,7 @@ module Spree end end - context "when product has an inherit_properties value set to true" do + context "when product has an inherit_properties value set to false" do let(:supplier) { create(:supplier_enterprise) } let(:product) { create(:simple_product, supplier: supplier, inherits_properties: false) } diff --git a/spec/models/spree/property_spec.rb b/spec/models/spree/property_spec.rb index a86513b5a9..6867b9de1f 100644 --- a/spec/models/spree/property_spec.rb +++ b/spec/models/spree/property_spec.rb @@ -2,6 +2,36 @@ require 'spec_helper' module Spree describe Property do + describe "scopes" do + describe ".applied_by" do + let(:producer) { create(:supplier_enterprise) } + let(:producer_other) { create(:supplier_enterprise) } + let(:product) { create(:simple_product, supplier: producer) } + let(:product_other_producer) { create(:simple_product, supplier: producer_other) } + let(:product_other_property) { create(:simple_product, supplier: producer) } + let(:property) { product.properties.last } + let(:property_other) { product_other_producer.properties.last } + + before do + product.set_property 'Organic', 'NASAA 12345' + product_other_property.set_property 'Organic', 'NASAA 12345' + product_other_producer.set_property 'Biodynamic', 'ASDF 1234' + end + + it "returns properties applied to supplied products" do + expect(Spree::Property.applied_by(producer)).to eq [property] + end + + it "doesn't return properties not applied" do + expect(Spree::Property.applied_by(producer)).not_to include property_other + end + + it "doesn't return duplicates" do + expect(Spree::Property.applied_by(producer).to_a.count).to eq 1 + end + end + end + describe "callbacks" do let(:property) { product_property.property } let(:product) { product_property.product } diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 926e5b3c5f..8fb4b22dc5 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -3,21 +3,20 @@ require 'spec_helper' module Spree describe Taxon do let(:e) { create(:supplier_enterprise) } - let(:t0) { p1.taxons.order('id ASC').first } - let(:t1) { create(:taxon) } - let(:t2) { create(:taxon) } + let!(:t1) { create(:taxon) } + let!(:t2) { create(:taxon) } describe "callbacks" do - let(:product) { create(:simple_product, taxons: [t1]) } + let!(:p2) { create(:simple_product, taxons: [t1]) } it "refreshes the products cache on save" do - expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product) + expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2) t1.name = 'asdf' t1.save end it "refreshes the products cache on destroy" do - expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(product) + expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2) t1.destroy end end @@ -26,17 +25,17 @@ module Spree let!(:p1) { create(:simple_product, supplier: e, taxons: [t1, t2]) } it "finds taxons" do - Taxon.supplied_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + Taxon.supplied_taxons.should == {e.id => Set.new(p1.taxons.map(&:id))} end end describe "finding all distributed taxons" do let!(:oc) { create(:simple_order_cycle, distributors: [e], variants: [p1.master]) } - let(:s) { create(:supplier_enterprise) } - let(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } + let!(:s) { create(:supplier_enterprise) } + let!(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } it "finds taxons" do - Taxon.distributed_taxons.should == {e.id => Set.new([t0.id, t1.id, t2.id])} + Taxon.distributed_taxons.should == {e.id => Set.new(p1.taxons.map(&:id))} end end end diff --git a/spec/models/tag_rule/discount_order_spec.rb b/spec/models/tag_rule/discount_order_spec.rb index dab901dfbf..93723ad84a 100644 --- a/spec/models/tag_rule/discount_order_spec.rb +++ b/spec/models/tag_rule/discount_order_spec.rb @@ -3,11 +3,11 @@ require 'spec_helper' describe TagRule::DiscountOrder, type: :model do let!(:tag_rule) { create(:tag_rule) } - describe "determining relevance based on additional requirements" do + pending "determining relevance based on additional requirements" do let(:subject) { double(:subject) } before do - tag_rule.set_context(subject,{}) + tag_rule.context = {subject: subject} allow(tag_rule).to receive(:customer_tags_match?) { true } allow(subject).to receive(:class) { Spree::Order } end @@ -29,12 +29,12 @@ describe TagRule::DiscountOrder, type: :model do end end - describe "determining whether a the rule has already been applied to an order" do + pending "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) + tag_rule.context = {subject: order} end context "where adjustments originating from the rule already exist" do @@ -47,7 +47,7 @@ describe TagRule::DiscountOrder, type: :model do end end - describe "applying the rule" do + pending "applying the rule" do # Assume that all validation is done by the TagRule base class let!(:line_item) { create(:line_item, price: 100.00) } @@ -56,7 +56,7 @@ describe TagRule::DiscountOrder, type: :model do before do order.update_distribution_charge! tag_rule.calculator.update_attribute(:preferred_flat_percent, -10.00) - tag_rule.set_context(order, nil) + tag_rule.context = {subject: order} end context "in a simple scenario" do diff --git a/spec/models/tag_rule/filter_order_cycles_spec.rb b/spec/models/tag_rule/filter_order_cycles_spec.rb new file mode 100644 index 0000000000..4c08af7c7f --- /dev/null +++ b/spec/models/tag_rule/filter_order_cycles_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe TagRule::FilterOrderCycles, type: :model do + let!(:tag_rule) { create(:filter_order_cycles_tag_rule) } + + describe "determining whether tags match for a given exchange" do + context "when the exchange is nil" do + before do + allow(tag_rule).to receive(:exchange_for) { nil } + end + + it "returns false" do + expect(tag_rule.send(:tags_match?, nil)).to be false + end + end + + context "when the exchange is not nil" do + let(:exchange_object) { double(:exchange, tag_list: ["member","local","volunteer"]) } + + before do + allow(tag_rule).to receive(:exchange_for) { exchange_object } + end + + context "when the rule has no preferred exchange tags specified" do + before { allow(tag_rule).to receive(:preferred_exchange_tags) { "" } } + it { expect(tag_rule.send(:tags_match?, exchange_object)).to be false } + end + + context "when the rule has preferred exchange tags specified that match ANY of the exchange tags" do + before { allow(tag_rule).to receive(:preferred_exchange_tags) { "wholesale,some_tag,member" } } + it { expect(tag_rule.send(:tags_match?, exchange_object)).to be true } + end + + context "when the rule has preferred exchange tags specified that match NONE of the exchange tags" do + before { allow(tag_rule).to receive(:preferred_exchange_tags) { "wholesale,some_tag,some_other_tag" } } + it { expect(tag_rule.send(:tags_match?, exchange_object)).to be false } + end + end + end +end diff --git a/spec/models/tag_rule/filter_payment_methods_spec.rb b/spec/models/tag_rule/filter_payment_methods_spec.rb new file mode 100644 index 0000000000..d8d30cefaa --- /dev/null +++ b/spec/models/tag_rule/filter_payment_methods_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe TagRule::FilterPaymentMethods, type: :model do + let!(:tag_rule) { create(:filter_payment_methods_tag_rule) } + + describe "determining whether tags match for a given payment method" do + context "when the payment method is nil" do + + it "returns false" do + expect(tag_rule.send(:tags_match?, nil)).to be false + end + end + + context "when the payment method is not nil" do + let(:payment_method) { create(:payment_method, tag_list: ["member","local","volunteer"]) } + + context "when the rule has no preferred payment method tags specified" do + before { allow(tag_rule).to receive(:preferred_payment_method_tags) { "" } } + it { expect(tag_rule.send(:tags_match?, payment_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_payment_method_tags) { "wholesale,some_tag,member" } } + it { expect(tag_rule.send(:tags_match?, payment_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_payment_method_tags) { "wholesale,some_tag,some_other_tag" } } + it { expect(tag_rule.send(:tags_match?, payment_method)).to be false } + end + end + end +end diff --git a/spec/models/tag_rule/filter_products_spec.rb b/spec/models/tag_rule/filter_products_spec.rb new file mode 100644 index 0000000000..0008db456d --- /dev/null +++ b/spec/models/tag_rule/filter_products_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe TagRule::FilterProducts, type: :model do + let!(:tag_rule) { create(:filter_products_tag_rule) } + + describe "determining whether tags match for a given variant" do + context "when the variant is nil" do + + it "returns false" do + expect(tag_rule.send(:tags_match?, nil)).to be false + end + end + + context "when the variant is not nil" do + let(:variant_object) { { "tag_list" => ["member","local","volunteer"] } } + + context "when the rule has no preferred variant tags specified" do + before { allow(tag_rule).to receive(:preferred_variant_tags) { "" } } + it { expect(tag_rule.send(:tags_match?, variant_object)).to be false } + end + + context "when the rule has preferred variant tags specified that match ANY of the variant tags" do + before { allow(tag_rule).to receive(:preferred_variant_tags) { "wholesale,some_tag,member" } } + it { expect(tag_rule.send(:tags_match?, variant_object)).to be true } + end + + context "when the rule has preferred variant tags specified that match NONE of the variant tags" do + before { allow(tag_rule).to receive(:preferred_variant_tags) { "wholesale,some_tag,some_other_tag" } } + it { expect(tag_rule.send(:tags_match?, variant_object)).to be false } + 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 index 539aa3c6ca..ff90ed8428 100644 --- a/spec/models/tag_rule/filter_shipping_methods_spec.rb +++ b/spec/models/tag_rule/filter_shipping_methods_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe TagRule::DiscountOrder, type: :model do +describe TagRule::FilterShippingMethods, type: :model do let!(:tag_rule) { create(:filter_shipping_methods_tag_rule) } describe "determining whether tags match for a given shipping method" do @@ -30,54 +30,4 @@ describe TagRule::DiscountOrder, type: :model do 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 index 549c2f88aa..06dac64194 100644 --- a/spec/models/tag_rule_spec.rb +++ b/spec/models/tag_rule_spec.rb @@ -8,194 +8,4 @@ describe TagRule, type: :model 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a477ad31e1..f4c5b73fac 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,7 +35,7 @@ require 'capybara/poltergeist' Capybara.javascript_driver = :poltergeist Capybara.register_driver :poltergeist do |app| - options = {phantomjs_options: ['--load-images=no'], window_size: [1280, 800], timeout: 2.minutes} + options = {phantomjs_options: ['--load-images=no'], window_size: [1280, 3600], timeout: 2.minutes} # Extend poltergeist's timeout to allow ample time to use pry in browser thread #options.merge! {timeout: 5.minutes} # Enable the remote inspector: Use page.driver.debug to open a remote debugger in chrome diff --git a/spec/support/request/checkout_workflow.rb b/spec/support/request/checkout_workflow.rb index e08fb031da..4a0f37c0ea 100644 --- a/spec/support/request/checkout_workflow.rb +++ b/spec/support/request/checkout_workflow.rb @@ -8,7 +8,7 @@ module CheckoutWorkflow end def place_order - click_button "Place order now" + find("button", text: "Place order now").trigger "click" end def toggle_accordion(id)