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.
- It seems the page you're looking for is in a grump.
-
+ 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.