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 769cec1c182..9e956a19775 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -4,7 +4,6 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt $scope.RequestMonitor = RequestMonitor $scope.submitAll = pendingChanges.submitAll $scope.add = Customers.add - $scope.deleteCustomer = Customers.remove $scope.customerLimit = 20 $scope.columns = Columns.columns @@ -21,6 +20,10 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt $scope.shop_id = shops[0].id if shops.length == 1 + $scope.deleteCustomer = (customer) -> + if confirm(t('admin.customers.index.confirm_delete')) + Customers.remove(customer) + $scope.checkForDuplicateCodes = -> delete this.customer.code unless this.customer.code this.duplicate = $scope.isDuplicateCode(this.customer.code) diff --git a/app/assets/javascripts/admin/orders/directives/customer_search_override.js.coffee b/app/assets/javascripts/admin/orders/directives/customer_search_override.js.coffee new file mode 100644 index 00000000000..6f36322d914 --- /dev/null +++ b/app/assets/javascripts/admin/orders/directives/customer_search_override.js.coffee @@ -0,0 +1,59 @@ +angular.module("admin.orders").directive 'customerSearchOverride', -> + restrict: 'C' + link: (scope, element, attr) -> + formatCustomerResult = (customer) -> + customerTemplate + customer: customer + bill_address: customer.bill_address + ship_address: customer.ship_address + + element.select2 + placeholder: Spree.translations.choose_a_customer + ajax: + url: '/admin/search/customers.json' + datatype: 'json' + data: (term, page) -> + { + q: term + distributor_id: $('#distributor_id').val() # modified + } + results: (data, page) -> + { results: data } + dropdownCssClass: 'customer_search' + formatResult: formatCustomerResult + formatSelection: (customer) -> + _.each [ + 'bill_address' + 'ship_address' + ], (address) -> + data = customer[address] + address_parts = [ + 'firstname' + 'lastname' + 'company' + 'address1' + 'address2' + 'city' + 'zipcode' + 'phone' + ] + attribute_wrapper = '#order_' + address + '_attributes_' + if data # modified + _.each address_parts, (part) -> + $(attribute_wrapper + part).val data[part] + return + $(attribute_wrapper + 'state_id').select2 'val', data['state_id'] + $(attribute_wrapper + 'country_id').select2 'val', data['country_id'] + else + _.each address_parts, (part) -> + $(attribute_wrapper + part).val '' + return + $(attribute_wrapper + 'state_id').select2 'val', '' + $(attribute_wrapper + 'country_id').select2 'val', '' + return + $('#order_email').val customer.email + $('#user_id').val customer.user_id # modified + $('#guest_checkout_true').prop 'checked', false + $('#guest_checkout_false').prop 'checked', true + $('#guest_checkout_false').prop 'disabled', false + customer.email diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee index d28437dd218..78f77e3356a 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/accordion_controller.js.coffee @@ -1,11 +1,8 @@ -Darkswarm.controller "AccordionCtrl", ($scope, storage, $timeout, $document, CurrentHub) -> - $scope.accordion = - details: true - billing: false - shipping: false - payment: false +Darkswarm.controller "AccordionCtrl", ($scope, localStorageService, $timeout, $document, CurrentHub) -> + key = "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}" + value = if localStorageService.get(key) then {} else { details: true, billing: false, shipping: false, payment: false } + localStorageService.bind $scope, "accordion", value, key $scope.accordionSections = ["details", "billing", "shipping", "payment"] - storage.bind $scope, "accordion", {storeName: "accordion_#{$scope.order.id}#{CurrentHub.hub.id}#{$scope.order.user_id}"} $scope.show = (section)-> $scope.accordion[section] = true diff --git a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee index 11975350331..8a4f1877732 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout/checkout_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, CurrentHub) -> +Darkswarm.controller "CheckoutCtrl", ($scope, localStorageService, Checkout, CurrentUser, CurrentHub) -> $scope.Checkout = Checkout $scope.submitted = false @@ -7,11 +7,11 @@ Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, Cu prefix = "order_#{Checkout.order.id}#{CurrentUser.id or ""}#{CurrentHub.hub.id}" for field in $scope.fieldsToBind - storage.bind $scope, "Checkout.order.#{field}", - storeName: "#{prefix}_#{field}" - storage.bind $scope, "Checkout.ship_address_same_as_billing", - storeName: "#{prefix}_sameasbilling" - defaultValue: true + localStorageService.bind $scope, "Checkout.order.#{field}", Checkout.order[field], "#{prefix}_#{field}" + + localStorageService.bind $scope, "Checkout.ship_address_same_as_billing", true, "#{prefix}_sameasbilling" + localStorageService.bind $scope, "Checkout.default_bill_address", false, "#{prefix}_defaultasbilladdress" + localStorageService.bind $scope, "Checkout.default_ship_address", false, "#{prefix}_defaultasshipaddress" $scope.order = Checkout.order # Ordering is important $scope.secrets = Checkout.secrets diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index ce5a73d69ee..2b51bc309c9 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,6 +1,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", 'mm.foundation', - 'angularLocalStorage', + 'LocalStorageModule', 'infinite-scroll', 'angular-flash.service', 'templates', diff --git a/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee b/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee index b2478a6fad0..574c29cc308 100644 --- a/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/change_order_cycle.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.directive "ofnChangeOrderCycle", (OrderCycle, Cart, storage) -> +Darkswarm.directive "ofnChangeOrderCycle", (OrderCycle, Cart) -> # Compares chosen order cycle with pre-set OrderCycle. Will trigger # a confirmation if they are different, and Cart isn't empty restrict: "A" diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index 456d567bb7c..70cbe316e82 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, storage)-> +Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, localStorageService)-> # Handles syncing of current cart/order state to server new class Cart dirty: false @@ -114,4 +114,4 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo clear: -> @line_items = [] - storage.clearAll() # One day this will have to be moar GRANULAR + localStorageService.clearAll() # One day this will have to be moar GRANULAR diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index 0ab94f61a46..8f43b783a20 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -3,7 +3,6 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h errors: {} secrets: {} order: CurrentOrder.order - ship_address_same_as_billing: true submit: -> Loading.message = t 'submitting_order' @@ -19,7 +18,10 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h # Rails wants our Spree::Address data to be provided with _attributes preprocess: -> - munged_order = {} + munged_order = + default_bill_address: !!@default_bill_address + default_ship_address: !!@default_ship_address + for name, value of @order # Clone all data from the order JSON object switch name when "bill_address" diff --git a/app/assets/javascripts/shared/angular-local-storage.js b/app/assets/javascripts/shared/angular-local-storage.js index 2ac18eba9e7..dfde2f6cfb4 100644 --- a/app/assets/javascripts/shared/angular-local-storage.js +++ b/app/assets/javascripts/shared/angular-local-storage.js @@ -1,171 +1,546 @@ -/* - * Angular.js localStorage module - * https://github.com/agrublev/angularLocalStorage +/** + * An Angular module that gives you access to the browsers local storage + * @version v0.5.0 - 2016-08-29 + * @link https://github.com/grevory/angular-local-storage + * @author grevory + * @license MIT License, http://www.opensource.org/licenses/MIT */ +(function (window, angular) { +var isDefined = angular.isDefined, + isUndefined = angular.isUndefined, + isNumber = angular.isNumber, + isObject = angular.isObject, + isArray = angular.isArray, + extend = angular.extend, + toJson = angular.toJson; -(function (window, angular, undefined) { - 'use strict'; - - angular.module('angularLocalStorage', ['ngCookies']).factory('storage', ['$parse', '$cookieStore', '$window', '$log', function ($parse, $cookieStore, $window, $log) { - /** - * Global Vars - */ - var storage = (typeof $window.localStorage === 'undefined') ? undefined : $window.localStorage; - var supported = typeof storage !== 'undefined'; - - var privateMethods = { - /** - * Pass any type of a string from the localStorage to be parsed so it returns a usable version (like an Object) - * @param res - a string that will be parsed for type - * @returns {*} - whatever the real type of stored value was - */ - parseValue: function (res) { - var val; - try { - val = angular.fromJson(res); - if (typeof val === 'undefined') { - val = res; - } - if (val === 'true') { - val = true; - } - if (val === 'false') { - val = false; - } - if ($window.parseFloat(val) === val && !angular.isObject(val)) { - val = $window.parseFloat(val); - } - } catch (e) { - val = res; - } - return val; - } - }; - - var publicMethods = { - /** - * Set - let's you set a new localStorage key pair set - * @param key - a string that will be used as the accessor for the pair - * @param value - the value of the localStorage item - * @returns {*} - will return whatever it is you've stored in the local storage - */ - set: function (key, value) { - if (!supported) { - try { - $cookieStore.put(key, value); - return value; - } catch(e) { - $log.log('Local Storage not supported, make sure you have angular-cookies enabled.'); - } - } - var saver = angular.toJson(value); - storage.setItem(key, saver); - return privateMethods.parseValue(saver); - }, - - /** - * Get - let's you get the value of any pair you've stored - * @param key - the string that you set as accessor for the pair - * @returns {*} - Object,String,Float,Boolean depending on what you stored - */ - get: function (key) { - if (!supported) { - try { - return privateMethods.parseValue($.cookie(key)); - } catch (e) { - return null; - } - } - var item = storage.getItem(key); - return privateMethods.parseValue(item); - }, - - /** - * Remove - let's you nuke a value from localStorage - * @param key - the accessor value - * @returns {boolean} - if everything went as planned - */ - remove: function (key) { - if (!supported) { - try { - $cookieStore.remove(key); - return true; - } catch (e) { - return false; - } - } - storage.removeItem(key); - return true; - }, - - /** - * Bind - let's you directly bind a localStorage value to a $scope variable - * @param {Angular $scope} $scope - the current scope you want the variable available in - * @param {String} key - the name of the variable you are binding - * @param {Object} opts - (optional) custom options like default value or unique store name - * Here are the available options you can set: - * * defaultValue: the default value - * * storeName: add a custom store key value instead of using the scope variable name - * @returns {*} - returns whatever the stored value is - */ - bind: function ($scope, key, opts) { - var defaultOpts = { - defaultValue: '', - storeName: '' - }; - // Backwards compatibility with old defaultValue string - if (angular.isString(opts)) { - opts = angular.extend({},defaultOpts,{defaultValue:opts}); - } else { - // If no defined options we use defaults otherwise extend defaults - opts = (angular.isUndefined(opts)) ? defaultOpts : angular.extend(defaultOpts,opts); - } - - // Set the storeName key for the localStorage entry - // use user defined in specified - var storeName = opts.storeName || key; - - // If a value doesn't already exist store it as is - if (!publicMethods.get(storeName)) { - publicMethods.set(storeName, $parse(key)($scope) || opts.defaultValue); - } else { - // If it does exist assign it to the $scope value - $parse(key).assign($scope, publicMethods.get(storeName)); +angular + .module('LocalStorageModule', []) + .provider('localStorageService', function() { + // You should set a prefix to avoid overwriting any local storage variables from the rest of your app + // e.g. localStorageServiceProvider.setPrefix('yourAppName'); + // With provider you can use config as this: + // myApp.config(function (localStorageServiceProvider) { + // localStorageServiceProvider.prefix = 'yourAppName'; + // }); + this.prefix = 'ls'; + + // You could change web storage type localstorage or sessionStorage + this.storageType = 'localStorage'; + + // Cookie options (usually in case of fallback) + // expiry = Number of days before cookies expire // 0 = Does not expire + // path = The web path the cookie represents + // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests) + this.cookie = { + expiry: 30, + path: '/', + secure: false + }; + + // Decides wether we should default to cookies if localstorage is not supported. + this.defaultToCookie = true; + + // Send signals for each of the following actions? + this.notify = { + setItem: true, + removeItem: false + }; + + // Setter for the prefix + this.setPrefix = function(prefix) { + this.prefix = prefix; + return this; + }; + + // Setter for the storageType + this.setStorageType = function(storageType) { + this.storageType = storageType; + return this; + }; + // Setter for defaultToCookie value, default is true. + this.setDefaultToCookie = function (shouldDefault) { + this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value. + return this; + }; + // Setter for cookie config + this.setStorageCookie = function(exp, path, secure) { + this.cookie.expiry = exp; + this.cookie.path = path; + this.cookie.secure = secure; + return this; + }; + + // Setter for cookie domain + this.setStorageCookieDomain = function(domain) { + this.cookie.domain = domain; + return this; + }; + + // Setter for notification config + // itemSet & itemRemove should be booleans + this.setNotify = function(itemSet, itemRemove) { + this.notify = { + setItem: itemSet, + removeItem: itemRemove + }; + return this; + }; + + this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) { + var self = this; + var prefix = self.prefix; + var cookie = self.cookie; + var notify = self.notify; + var storageType = self.storageType; + var webStorage; + + // When Angular's $document is not available + if (!$document) { + $document = document; + } else if ($document[0]) { + $document = $document[0]; + } + + // If there is a prefix set in the config lets use that with an appended period for readability + if (prefix.substr(-1) !== '.') { + prefix = !!prefix ? prefix + '.' : ''; + } + var deriveQualifiedKey = function(key) { + return prefix + key; + }; + + // Removes prefix from the key. + var underiveQualifiedKey = function (key) { + return key.replace(new RegExp('^' + prefix, 'g'), ''); + }; + + // Check if the key is within our prefix namespace. + var isKeyPrefixOurs = function (key) { + return key.indexOf(prefix) === 0; + }; + + // Checks the browser to see if local storage is supported + var checkSupport = function () { + try { + var supported = (storageType in $window && $window[storageType] !== null); + + // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage + // is available, but trying to call .setItem throws an exception. + // + // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage + // that exceeded the quota." + var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7)); + if (supported) { + webStorage = $window[storageType]; + webStorage.setItem(key, ''); + webStorage.removeItem(key); + } + + return supported; + } catch (e) { + // Only change storageType to cookies if defaulting is enabled. + if (self.defaultToCookie) + storageType = 'cookie'; + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + }; + var browserSupportsLocalStorage = checkSupport(); + + // Directly adds a value to local storage + // If local storage is not available in the browser use cookies + // Example use: localStorageService.add('library','angular'); + var addToLocalStorage = function (key, value, type) { + setStorageType(type); + + // Let's convert undefined values to null to get the value consistent + if (isUndefined(value)) { + value = null; + } else { + value = toJson(value); + } + + // If this browser does not support local storage use cookies + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + + if (notify.setItem) { + $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'}); + } + return addToCookies(key, value); + } + + try { + if (webStorage) { + webStorage.setItem(deriveQualifiedKey(key), value); + } + if (notify.setItem) { + $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType}); + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return addToCookies(key, value); + } + return true; + }; + + // Directly get a value from local storage + // Example use: localStorageService.get('library'); // returns 'angular' + var getFromLocalStorage = function (key, type) { + setStorageType(type); + + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + + return getFromCookies(key); + } + + var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null; + // angular.toJson will convert null to 'null', so a proper conversion is needed + // FIXME not a perfect solution, since a valid 'null' string can't be stored + if (!item || item === 'null') { + return null; + } + + try { + return JSON.parse(item); + } catch (e) { + return item; + } + }; + + // Remove an item from local storage + // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular' + // + // This is var-arg removal, check the last argument to see if it is a storageType + // and set type accordingly before removing. + // + var removeFromLocalStorage = function () { + // can't pop on arguments, so we do this + var consumed = 0; + if (arguments.length >= 1 && + (arguments[arguments.length - 1] === 'localStorage' || + arguments[arguments.length - 1] === 'sessionStorage')) { + consumed = 1; + setStorageType(arguments[arguments.length - 1]); + } + + var i, key; + for (i = 0; i < arguments.length - consumed; i++) { + key = arguments[i]; + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + + if (notify.removeItem) { + $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'}); + } + removeFromCookies(key); + } + else { + try { + webStorage.removeItem(deriveQualifiedKey(key)); + if (notify.removeItem) { + $rootScope.$broadcast('LocalStorageModule.notification.removeitem', { + key: key, + storageType: self.storageType + }); + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + removeFromCookies(key); + } + } + } + }; + + // Return array of keys for local storage + // Example use: var keys = localStorageService.keys() + var getKeysForLocalStorage = function (type) { + setStorageType(type); + + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + return []; + } + + var prefixLength = prefix.length; + var keys = []; + for (var key in webStorage) { + // Only return keys that are for this app + if (key.substr(0, prefixLength) === prefix) { + try { + keys.push(key.substr(prefixLength)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description); + return []; + } + } + } + return keys; + }; + + // Remove all data for this app from local storage + // Also optionally takes a regular expression string and removes the matching key-value pairs + // Example use: localStorageService.clearAll(); + // Should be used mostly for development purposes + var clearAllFromLocalStorage = function (regularExpression, type) { + setStorageType(type); + + // Setting both regular expressions independently + // Empty strings result in catchall RegExp + var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp(); + var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp(); + + if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') { + if (!browserSupportsLocalStorage) { + $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED'); + } + return clearAllFromCookies(); + } + if (!browserSupportsLocalStorage && !self.defaultToCookie) + return false; + var prefixLength = prefix.length; + + for (var key in webStorage) { + // Only remove items that are for this app and match the regular expression + if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) { + try { + removeFromLocalStorage(key.substr(prefixLength)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return clearAllFromCookies(); + } + } + } + return true; + }; + + // Checks the browser to see if cookies are supported + var browserSupportsCookies = (function() { + try { + return $window.navigator.cookieEnabled || + ("cookie" in $document && ($document.cookie.length > 0 || + ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1)); + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + }()); + + // Directly adds a value to cookies + // Typically used as a fallback if local storage is not available in the browser + // Example use: localStorageService.cookie.add('library','angular'); + var addToCookies = function (key, value, daysToExpiry, secure) { + + if (isUndefined(value)) { + return false; + } else if(isArray(value) || isObject(value)) { + value = toJson(value); + } + + if (!browserSupportsCookies) { + $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); + return false; + } + + try { + var expiry = '', + expiryDate = new Date(), + cookieDomain = ''; + + if (value === null) { + // Mark that the cookie has expired one day ago + expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + value = ''; + } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) { + expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + } else if (cookie.expiry !== 0) { + expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000)); + expiry = "; expires=" + expiryDate.toGMTString(); + } + if (!!key) { + var cookiePath = "; path=" + cookie.path; + if (cookie.domain) { + cookieDomain = "; domain=" + cookie.domain; + } + /* Providing the secure parameter always takes precedence over config + * (allows developer to mix and match secure + non-secure) */ + if (typeof secure === 'boolean') { + if (secure === true) { + /* We've explicitly specified secure, + * add the secure attribute to the cookie (after domain) */ + cookieDomain += "; secure"; + } + // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says + } + else if (cookie.secure === true) { + // secure parameter wasn't specified, get default from config + cookieDomain += "; secure"; + } + $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain; + } + } catch (e) { + $rootScope.$broadcast('LocalStorageModule.notification.error', e.message); + return false; + } + return true; + }; + + // Directly get a value from a cookie + // Example use: localStorageService.cookie.get('library'); // returns 'angular' + var getFromCookies = function (key) { + if (!browserSupportsCookies) { + $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED'); + return false; + } + + var cookies = $document.cookie && $document.cookie.split(';') || []; + for(var i=0; i < cookies.length; i++) { + var thisCookie = cookies[i]; + while (thisCookie.charAt(0) === ' ') { + thisCookie = thisCookie.substring(1,thisCookie.length); + } + if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) { + var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length)); + try { + return JSON.parse(storedValues); + } catch(e) { + return storedValues; + } + } + } + return null; + }; + + var removeFromCookies = function (key) { + addToCookies(key,null); + }; + + var clearAllFromCookies = function () { + var thisCookie = null; + var prefixLength = prefix.length; + var cookies = $document.cookie.split(';'); + for(var i = 0; i < cookies.length; i++) { + thisCookie = cookies[i]; + + while (thisCookie.charAt(0) === ' ') { + thisCookie = thisCookie.substring(1, thisCookie.length); + } + + var key = thisCookie.substring(prefixLength, thisCookie.indexOf('=')); + removeFromCookies(key); + } + }; + + var getStorageType = function() { + return storageType; + }; + + var setStorageType = function(type) { + if (type && storageType !== type) { + storageType = type; + browserSupportsLocalStorage = checkSupport(); + } + return browserSupportsLocalStorage; + }; + + // Add a listener on scope variable to save its changes to local storage + // Return a function which when called cancels binding + var bindToScope = function(scope, key, def, lsKey, type) { + lsKey = lsKey || key; + var value = getFromLocalStorage(lsKey, type); + + if (value === null && isDefined(def)) { + value = def; + } else if (isObject(value) && isObject(def)) { + value = extend(value, def); + } + + $parse(key).assign(scope, value); + + return scope.$watch(key, function(newVal) { + addToLocalStorage(lsKey, newVal, type); + }, isObject(scope[key])); + }; + + // Add listener to local storage, for update callbacks. + if (browserSupportsLocalStorage) { + if ($window.addEventListener) { + $window.addEventListener("storage", handleStorageChangeCallback, false); + $rootScope.$on('$destroy', function() { + $window.removeEventListener("storage", handleStorageChangeCallback); + }); + } else if($window.attachEvent){ + // attachEvent and detachEvent are proprietary to IE v6-10 + $window.attachEvent("onstorage", handleStorageChangeCallback); + $rootScope.$on('$destroy', function() { + $window.detachEvent("onstorage", handleStorageChangeCallback); + }); + } + } + + // Callback handler for storage changed. + function handleStorageChangeCallback(e) { + if (!e) { e = $window.event; } + if (notify.setItem) { + if (isKeyPrefixOurs(e.key)) { + var key = underiveQualifiedKey(e.key); + // Use timeout, to avoid using $rootScope.$apply. + $timeout(function () { + $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType }); + }); + } + } } + // Return localStorageService.length + // ignore keys that not owned + var lengthOfLocalStorage = function(type) { + setStorageType(type); - // Register a listener for changes on the $scope value - // to update the localStorage value - $scope.$watch(key, function (val) { - if (angular.isDefined(val)) { - publicMethods.set(storeName, val); - } - }, true); - - return publicMethods.get(storeName); - }, - /** - * Unbind - let's you unbind a variable from localStorage while removing the value from both - * the localStorage and the local variable and sets it to null - * @param $scope - the scope the variable was initially set in - * @param key - the name of the variable you are unbinding - * @param storeName - (optional) if you used a custom storeName you will have to specify it here as well - */ - unbind: function($scope,key,storeName) { - storeName = storeName || key; - $parse(key).assign($scope, null); - $scope.$watch(key, function () { }); - publicMethods.remove(storeName); - }, - /** - * Clear All - let's you clear out ALL localStorage variables, use this carefully! - */ - clearAll: function() { - storage.clear(); - } - }; - - return publicMethods; - }]); + var count = 0; + var storage = $window[storageType]; + for(var i = 0; i < storage.length; i++) { + if(storage.key(i).indexOf(prefix) === 0 ) { + count++; + } + } + return count; + }; + return { + isSupported: browserSupportsLocalStorage, + getStorageType: getStorageType, + setStorageType: setStorageType, + set: addToLocalStorage, + add: addToLocalStorage, //DEPRECATED + get: getFromLocalStorage, + keys: getKeysForLocalStorage, + remove: removeFromLocalStorage, + clearAll: clearAllFromLocalStorage, + bind: bindToScope, + deriveKey: deriveQualifiedKey, + underiveKey: underiveQualifiedKey, + length: lengthOfLocalStorage, + defaultToCookie: this.defaultToCookie, + cookie: { + isSupported: browserSupportsCookies, + set: addToCookies, + add: addToCookies, //DEPRECATED + get: getFromCookies, + remove: removeFromCookies, + clearAll: clearAllFromCookies + } + }; + }]; + }); })(window, window.angular); diff --git a/app/assets/javascripts/templates/admin/edit_address_dialog.html.haml b/app/assets/javascripts/templates/admin/edit_address_dialog.html.haml index 351a267bd3a..ff69e95ee1e 100644 --- a/app/assets/javascripts/templates/admin/edit_address_dialog.html.haml +++ b/app/assets/javascripts/templates/admin/edit_address_dialog.html.haml @@ -11,21 +11,27 @@ %table.no-borders %tr %td{style: 'width: 30%'} - = t('spree.street_address') + = t('spree.firstname') %span.required * %td - %input{ type: 'text', name: 'address1', required: true, ng: { model: 'address.address1'} } + %input{ type: 'text', name: 'firstname', required: true, ng: { model: 'address.firstname'} } %tr %td - = t('spree.street_address_1') + = t('spree.lastname') + %span.required * %td - %input{ type: 'text', name: 'address2', ng: { model: 'address.address2'} } + %input{ type: 'text', name: 'lastname', required: true, ng: { model: 'address.lastname'} } %tr %td - = t('spree.phone') + = t('spree.street_address') %span.required * %td - %input{ type: 'text', name: 'phone', required: true, ng: { model: 'address.phone'} } + %input{ type: 'text', name: 'address1', required: true, ng: { model: 'address.address1'} } + %tr + %td + = t('spree.street_address_1') + %td + %input{ type: 'text', name: 'address2', ng: { model: 'address.address2'} } %tr %td = t('spree.city') @@ -43,19 +49,29 @@ = t('spree.country') %span.required * %td - %select{name: 'country', required: true, ng: {model: 'address.country_id', options: 'country.id as country.name for country in availableCountries'}} + %select{ name: 'country', required: true, ng: { model: 'address.country_id' } } %option{value: ''} = t('admin.customers.index.select_country') + %option{ ng: { repeat: 'country in availableCountries' }, value: '{{country.id}}' } + {{country.name}} %tr %td = t('spree.state') %span.required * %td - %select{name: 'state', required: true, ng: {model: 'address.state_id', options: 'state.id as state.name for state in states'}} + %select{ name: 'state', required: true, ng: { model: 'address.state_id' } } %option{value: ''} = t('admin.customers.index.select_state') + %option{ ng: { repeat: 'state in states' }, value: '{{state.id}}' } + {{state.name}} + %tr + %td + = t('spree.phone') + %span.required * + %td + %input{ type: 'text', name: 'phone', required: true, ng: { model: 'address.phone'} } .text-center - %input.button.red.icon-plus{ type: 'submit', value: 'Update Address'} + %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.update_address')} diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index c6d12914776..8d855a7f85f 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -38,15 +38,18 @@ def update end end if @order.state == "complete" || @order.completed? + set_default_bill_address + set_default_ship_address + flash[:success] = t(:order_processed_successfully) - respond_to do |format| - format.html do - respond_with(@order, :location => order_path(@order)) - end - format.js do - render json: {path: order_path(@order)}, status: 200 - end + respond_to do |format| + format.html do + respond_with(@order, :location => order_path(@order)) + end + format.js do + render json: {path: order_path(@order)}, status: 200 end + end else update_failed end @@ -58,6 +61,31 @@ def update private + def set_default_bill_address + if params[:order][:default_bill_address] + new_bill_address = @order.bill_address.clone.attributes + + user_bill_address_id = spree_current_user.bill_address.andand.id + spree_current_user.update_attributes(bill_address_attributes: new_bill_address.merge('id' => user_bill_address_id)) + + customer_bill_address_id = @order.customer.bill_address.andand.id + @order.customer.update_attributes(bill_address_attributes: new_bill_address.merge('id' => customer_bill_address_id)) + end + + end + + def set_default_ship_address + if params[:order][:default_ship_address] + new_ship_address = @order.ship_address.clone.attributes + + user_ship_address_id = spree_current_user.ship_address.andand.id + spree_current_user.update_attributes(ship_address_attributes: new_ship_address.merge('id' => user_ship_address_id)) + + customer_ship_address_id = @order.customer.ship_address.andand.id + @order.customer.update_attributes(ship_address_attributes: new_ship_address.merge('id' => customer_ship_address_id)) + end + end + def check_order_for_phantom_fees phantom_fees = @order.adjustments.joins('LEFT OUTER JOIN spree_line_items ON spree_line_items.id = spree_adjustments.source_id'). where("originator_type = 'EnterpriseFee' AND source_type = 'Spree::LineItem' AND spree_line_items.id IS NULL") @@ -136,9 +164,12 @@ def before_address last_used_bill_address = lua.last_used_bill_address.andand.clone last_used_ship_address = lua.last_used_ship_address.andand.clone - preferred_bill_address, preferred_ship_address = spree_current_user.bill_address, spree_current_user.ship_address if spree_current_user.respond_to?(:bill_address) && spree_current_user.respond_to?(:ship_address) - @order.bill_address ||= preferred_bill_address || last_used_bill_address || Spree::Address.default - @order.ship_address ||= preferred_ship_address || last_used_ship_address || Spree::Address.default + preferred_bill_address, preferred_ship_address = spree_current_user.bill_address, spree_current_user.ship_address if spree_current_user + + customer_preferred_bill_address, customer_preferred_ship_address = @order.customer.bill_address, @order.customer.ship_address if @order.customer + + @order.bill_address ||= customer_preferred_bill_address ||= preferred_bill_address || last_used_bill_address || Spree::Address.default + @order.ship_address ||= customer_preferred_ship_address ||= preferred_ship_address || last_used_ship_address || Spree::Address.default end def after_payment diff --git a/app/controllers/spree/admin/search_controller_decorator.rb b/app/controllers/spree/admin/search_controller_decorator.rb index 6c5942934e4..746a05c195f 100644 --- a/app/controllers/spree/admin/search_controller_decorator.rb +++ b/app/controllers/spree/admin/search_controller_decorator.rb @@ -16,10 +16,21 @@ def known_users render :users end + def customers + if spree_current_user.enterprises.pluck(:id).include? params[:distributor_id].to_i + @customers = Customer.ransack({m: 'or', email_start: params[:q], name_start: params[:q]}) + .result.where(enterprise_id: params[:distributor_id]) + else + @customers = [] + end + + render json: @customers, each_serializer: Api::Admin::CustomerSerializer + end def users_with_ams users_without_ams render json: @users, each_serializer: Api::Admin::UserSerializer end + alias_method_chain :users, :ams end diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb index 6359cb01bd8..57cbfd7b73f 100644 --- a/app/helpers/checkout_helper.rb +++ b/app/helpers/checkout_helper.rb @@ -1,4 +1,8 @@ module CheckoutHelper + def guest_checkout_allowed? + current_order.distributor.allow_guest_orders? + end + def checkout_adjustments_for(order, opts={}) adjustments = order.adjustments.eligible exclude = opts[:exclude] || {} diff --git a/app/models/customer.rb b/app/models/customer.rb index 3003acb8485..efa8b5e0968 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -16,7 +16,6 @@ class Customer < ActiveRecord::Base before_validation :downcase_email before_validation :empty_code - before_validation :set_unused_address_fields 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') } @@ -36,11 +35,6 @@ def empty_code self.code = nil if code.blank? end - def set_unused_address_fields - bill_address.firstname = bill_address.lastname = 'unused' if bill_address.present? - ship_address.firstname = ship_address.lastname = 'unused' if ship_address.present? - end - def associate_user self.user = user || Spree::User.find_by_email(email) end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 2936a47d411..c2bd99bcdbe 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -93,7 +93,7 @@ def add_enterprise_management_abilities(user) user.enterprises.include? enterprise_fee.enterprise end - can [:admin, :known_users], :search + can [:admin, :known_users, :customers], :search can [:admin, :show], :account diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index fccbc639b2e..f6128a736db 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -289,7 +289,8 @@ def associate_customer def ensure_customer unless associate_customer - self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user) + customer_name = bill_address.andand.full_name + self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user, name: customer_name, bill_address: bill_address.andand.clone, ship_address: ship_address.andand.clone) end end end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 562b0d10ca8..b16e082f87e 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -14,7 +14,10 @@ accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true - attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit + accepts_nested_attributes_for :bill_address + accepts_nested_attributes_for :ship_address + + attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit, :bill_address_attributes, :ship_address_attributes after_create :send_signup_confirmation validate :limit_owned_enterprises diff --git a/app/overrides/spree/admin/orders/customer_details/edit/replace_customer_search.html.haml.deface b/app/overrides/spree/admin/orders/customer_details/edit/replace_customer_search.html.haml.deface new file mode 100644 index 00000000000..db8cebfb0ef --- /dev/null +++ b/app/overrides/spree/admin/orders/customer_details/edit/replace_customer_search.html.haml.deface @@ -0,0 +1,8 @@ +/ replace "code[erb-loud]:contains('hidden_field_tag :customer_search')" + +- content_for :app_wrapper_attrs do + = 'ng-app=admin.orders' + += hidden_field_tag :customer_search_override, nil, :class => 'fullwidth title customer-search-override' + += hidden_field_tag :distributor_id, @order.distributor_id diff --git a/app/serializers/api/address_serializer.rb b/app/serializers/api/address_serializer.rb index 66e4267e1f2..3ad93881e01 100644 --- a/app/serializers/api/address_serializer.rb +++ b/app/serializers/api/address_serializer.rb @@ -9,4 +9,12 @@ class Api::AddressSerializer < ActiveModel::Serializer def state_name object.state.andand.abbr end + + def state_id + object.state_id.andand.to_s + end + + def country_id + object.country_id.andand.to_s + end end diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 1c9d19306d2..327ac97189f 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -8,9 +8,13 @@ def tag_list object.tag_list.join(",") end + def name + object.name.blank? ? object.bill_address.andand.full_name : object.name + end + def tags object.tag_list.map do |tag| - tag_rule_map = options[:tag_rule_mapping][tag] + tag_rule_map = options[:tag_rule_mapping].andand[tag] tag_rule_map || { text: tag, rules: nil } end end diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index 49a49b3250a..9fd20c1d783 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -4,6 +4,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer 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, :default_tag_group + attributes :require_login has_one :owner, serializer: Api::Admin::UserSerializer has_many :users, serializer: Api::Admin::UserSerializer diff --git a/app/views/admin/enterprises/form/_primary_details.html.haml b/app/views/admin/enterprises/form/_primary_details.html.haml index ccf59c8a125..52b2ffebcec 100644 --- a/app/views/admin/enterprises/form/_primary_details.html.haml +++ b/app/views/admin/enterprises/form/_primary_details.html.haml @@ -50,17 +50,6 @@ .five.columns.omega = f.radio_button :visible, false = f.label :visible, "Not Visible", :value => "false" -.row - .three.columns.alpha - %label= t '.shopfront_requires_login' - %div{'ofn-with-tip' => t('.shopfront_requires_login_tip')} - %a= t 'admin.whats_this' - .two.columns - = f.radio_button :require_login, false - = f.label :require_login, t('.shopfront_requires_login_false'), value: :false - .five.columns.omega - = f.radio_button :require_login, true - = f.label :require_login, t('.shopfront_requires_login_true'), value: :true .permalink{ ng: { controller: "permalinkCtrl" } } .row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } } .three.columns.alpha diff --git a/app/views/admin/enterprises/form/_shop_preferences.html.haml b/app/views/admin/enterprises/form/_shop_preferences.html.haml index 3085727f850..6573e4f3b47 100644 --- a/app/views/admin/enterprises/form/_shop_preferences.html.haml +++ b/app/views/admin/enterprises/form/_shop_preferences.html.haml @@ -33,3 +33,27 @@ .five.columns.omega = radio_button :enterprise, :preferred_shopfront_order_cycle_order, :orders_close_at, { 'ng-model' => 'Enterprise.preferred_shopfront_order_cycle_order' } = label :enterprise, :preferred_shopfront_order_cycle_order_orders_close_at, "Close Date" +.row + .alpha.eleven.columns + .three.columns.alpha + %label= t '.shopfront_requires_login' + %div{'ofn-with-tip' => t('.shopfront_requires_login_tip')} + %a= t 'admin.whats_this' + .three.columns + = f.radio_button :require_login, false, "ng-model" => "Enterprise.require_login", "ng-value" => "false" + = f.label :require_login, t('.shopfront_requires_login_false'), value: :false + .five.columns.omega + = f.radio_button :require_login, true, "ng-model" => "Enterprise.require_login", "ng-value" => "true" + = f.label :require_login, t('.shopfront_requires_login_true'), value: :true +.row{ng: {if: "!Enterprise.require_login"}} + .alpha.eleven.columns + .three.columns.alpha + %label= t '.allow_guest_orders' + %div{'ofn-with-tip' => t('.allow_guest_orders_tip')} + %a= t 'admin.whats_this' + .three.columns + = f.radio_button :allow_guest_orders, true + = f.label :allow_guest_orders, t('.allow_guest_orders_true'), value: :true + .five.columns.omega + = f.radio_button :allow_guest_orders, false + = f.label :allow_guest_orders, t('.allow_guest_orders_false'), value: :false diff --git a/app/views/checkout/_authentication.html.haml b/app/views/checkout/_authentication.html.haml index 1c3b06e63bf..45d977d8e36 100644 --- a/app/views/checkout/_authentication.html.haml +++ b/app/views/checkout/_authentication.html.haml @@ -4,11 +4,16 @@ %h3.pad-top = t :checkout_headline .row.pad-top - .small-5.columns.text-center - %button.primary.expand{"auth" => "login"} - = t :label_login - .small-2.columns.text-center - %p.pad-top= "-#{t :action_or}-" - .small-5.columns.text-center - %button.neutral-btn.dark.expand{"ng-click" => "enabled = true"} - = t :checkout_as_guest + -if guest_checkout_allowed? + .small-5.columns.text-center + %button.primary.expand{"auth" => "login"} + = t :label_login + .small-2.columns.text-center + %p.pad-top= "-#{t :action_or}-" + .small-5.columns.text-center + %button.neutral-btn.dark.expand{"ng-click" => "enabled = true"} + = t :checkout_as_guest + -else + .small-6.columns.small-centered + %button.primary.expand{"auth" => "login"} + = t :label_login diff --git a/app/views/checkout/_billing.html.haml b/app/views/checkout/_billing.html.haml index 7ea4d57da18..bd590d63a59 100644 --- a/app/views/checkout/_billing.html.haml +++ b/app/views/checkout/_billing.html.haml @@ -12,7 +12,13 @@ %accordion-group{"is-open" => "accordion.billing", "ng-class" => "{valid: billing.$valid, open: accordion.billing}"} = render 'checkout/accordion_heading' - + + - if spree_current_user + .small-12.columns + %label + %input{type: :checkbox, "ng-model" => "Checkout.default_bill_address"} + = t :checkout_default_bill_address + = f.fields_for :bill_address, @order.bill_address do |ba| .row .small-12.columns diff --git a/app/views/checkout/_shipping.html.haml b/app/views/checkout/_shipping.html.haml index 8bdc855b7a8..4e6e63d6669 100644 --- a/app/views/checkout/_shipping.html.haml +++ b/app/views/checkout/_shipping.html.haml @@ -33,6 +33,11 @@ %input{type: :checkbox, "ng-model" => "Checkout.ship_address_same_as_billing"} = t :checkout_address_same + - if spree_current_user + %label{"ng-if" => "Checkout.requireShipAddress()"} + %input{type: :checkbox, "ng-model" => "Checkout.default_ship_address"} + = t :checkout_default_ship_address + .small-12.columns.medium-6.columns.large-6.columns #distributor_address.panel{"ng-show" => "Checkout.shippingMethod().description"} %span{ style: "white-space: pre-wrap;" }{{ Checkout.shippingMethod().description }} diff --git a/app/views/home/sell.html.haml b/app/views/home/sell.html.haml index 64c71f01c13..5238fd4cd84 100644 --- a/app/views/home/sell.html.haml +++ b/app/views/home/sell.html.haml @@ -44,7 +44,6 @@ %p = t :sell_embed - %a{href: "hello@openfoodnetwork.org".reverse, target: '_blank', mailto: true} - = t :sell_ask_services + = link_to t(:sell_ask_services), ContentConfig.footer_email.reverse, mailto: true = render "shared/footer" diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index 4f9b3d78c17..a00a35a5e09 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -42,7 +42,7 @@ %tr %td{:align => "center"} %p - %a{:href => "#{ URI.join(spree.root_url, "Terms-of-service.pdf").to_s }", :target => "_blank"} + %a{:href => "#{ URI.join(spree.root_url, ContentConfig.footer_tos_url).to_s }", :target => "_blank"} = t :terms_of_service | %a{:href => "#{ spree.root_url }"} diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 035d74ea1c0..f76c59352d2 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,29 +1,12 @@ -# English language file -# --------------------- -# -# This is the 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_GB: +en-GB: activerecord: - # Overridden here due to a bug in spree i18n (Issue #870) attributes: spree/order: payment_state: Payment State shipment_state: Shipment State devise: failure: - invalid: | + invalid: Invalid email or password. Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: @@ -37,13 +20,12 @@ en_GB: subject: "Please confirm the email address for %{enterprise}" welcome: subject: "%{enterprise} is now on %{sitename}" - 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 seriously change the world." search_by_name: Search by name... producers: 'UK Producers' - producers_join: UK producers are now welcome to join Open Food Network UK. #FIXME + producers_join: UK producers are now welcome to join Open Food Network UK. charges_sales_tax: Charges VAT? print_invoice: "Print Invoice" send_invoice: "Send Invoice" @@ -70,12 +52,8 @@ en_GB: say_no: "No" say_yes: "Yes" then: then - sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" - - admin: - # Common properties / models date: Date email: Email name: Name @@ -92,23 +70,17 @@ en_GB: sku: SKU tags: Tags variant: Variant - - # General form elements quick_search: Quick Search clear_all: Clear All start_date: "Start Date" end_date: "End Date" - 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" @@ -118,7 +90,6 @@ en_GB: add_a_new_customer_for: Add a new customer for %{shop_name} code: Code duplicate_code: "This code is used already." - products: bulk_edit: unit: Unit @@ -128,7 +99,6 @@ en_GB: inherits_properties?: Inherits Properties? available_on: Available On av_on: "Av. On" - variant_overrides: index: title: Inventory @@ -139,7 +109,7 @@ en_GB: hide: Hide select_a_shop: Select A Shop review_now: Review Now - new_products_alert_message: '' + 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 @@ -149,7 +119,6 @@ en_GB: 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! - orders: bulk_management: tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." @@ -171,27 +140,23 @@ en_GB: max_fulfilled_units: "Max Fulfilled Units" order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." variants_without_unit_value: "WARNING: Some variants do not have a unit value" - order_cycles: edit: choose_products_from: "Choose Products From:" - enterprise: select_outgoing_oc_products_from: Select outgoing OC products from - enterprises: index: - producer?: '' - package: '' - status: '' - manage: '' + producer?: Producer? + package: Package + status: Status + manage: Manage 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" @@ -209,19 +174,16 @@ en_GB: 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: "VAT" invoice_column_price: "Price" - - logo: "Logo (640x130)" #FIXME - logo_mobile: "Mobile logo (75x26)" #FIXME - logo_mobile_svg: "Mobile logo (SVG)" #FIXME + logo: "Logo (640x130)" + logo_mobile: "Mobile logo (75x26)" + logo_mobile_svg: "Mobile logo (SVG)" home_hero: "Hero image" home_show_stats: "Show statistics" - footer_logo: "Logo (220x76)" #FIXME + footer_logo: "Logo (220x76)" footer_facebook_url: "Facebook URL" footer_twitter_url: "Twitter URL" footer_instagram_url: "Instagram URL" @@ -232,7 +194,6 @@ en_GB: 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 @@ -249,12 +210,13 @@ en_GB: terms_of_service: "Terms of service" on_demand: To Order none: None - label_shops: "Shops" label_map: "Map" label_producers: "Producers" label_groups: "Groups" label_about: "About" + label_connect: "Connect" + label_learn: "Learn" label_shopping: "Shopping" label_login: "Login" label_logout: "Logout" @@ -265,7 +227,6 @@ en_GB: label_more: "Show more" label_less: "Show less" label_notices: "Community Forum" - items: "items" cart_headline: "Your shopping cart" total: "Total" @@ -273,11 +234,9 @@ en_GB: 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:" @@ -286,45 +245,34 @@ en_GB: 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_join_body: "Create a listing, shop or group directory on the Open Food Network." + footer_join_cta: "Tell me more!" 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." @@ -333,24 +281,24 @@ en_GB: 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." + learn_body: "Explore models, stories and resources to support you to develop your fair food business or organisation. Find training, events and other opportunities to learn from peers." + learn_cta: "Get Inspired" + connect_body: "Search our full directories of producers, hubs and groups to find fair food traders near you. List your business or organisation on the OFN so buyers can find you. Join the community to get advice and solve problems together." + connect_cta: "Go Exploring" + system_headline: "Shopping - 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 @@ -372,7 +320,6 @@ en_GB: 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 @@ -388,15 +335,12 @@ en_GB: order_includes_tax: (includes tax) order_payment_paypal_successful: Your payment via PayPal has been processed successfully. order_hub_info: Hub info - 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" @@ -414,7 +358,6 @@ en_GB: 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}" @@ -439,7 +382,6 @@ en_GB: 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 @@ -447,14 +389,11 @@ en_GB: 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" - - producer_mail_greeting: "" - producer_mail_text_before: "" - producer_mail_order_text: "" - producer_mail_delivery_instructions: "" - producer_mail_text_after: "" - producer_mail_signoff: "" - + producer_mail_greeting: "Dear" + producer_mail_text_before: "We now have all the consumer orders for the next food delivery." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" + producer_mail_signoff: "Thanks and best wishes" 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" @@ -466,11 +405,9 @@ en_GB: 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" @@ -486,7 +423,6 @@ en_GB: 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 @@ -501,21 +437,17 @@ en_GB: 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 @@ -542,18 +474,14 @@ en_GB: 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.) @@ -561,29 +489,8 @@ en_GB: 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 @@ -592,6 +499,7 @@ en_GB: producers_buy_at_html: "Shop for %{enterprise} products at:" producers_filter: Filter by producers_filter_type: Type + producers_filter_property: Property producers_title: Producers producers_headline: Find local producers producers_signup_title: Sign up as a producer @@ -603,18 +511,27 @@ en_GB: 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_available: '' + products_available: Available? products_producer: "Producer" products_price: "Price" - register_title: Register - + sell_title: "Register" + sell_headline: "Get on the Open Food Network!" + sell_motivation: "Showcase your beautiful food." + sell_producers: "Producers" + sell_hubs: "Hubs" + sell_groups: "Groups" + sell_producers_detail: "Set up a profile for your business on the OFN in just minutes. At any time you can upgrade your profile to an online store and sell your products direct to customers." + sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." + sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." + sell_user_guide: "Find out more in our user guide." + sell_listing_price: "Listing on OFN is free. Opening and running a shop on OFN is free for six months and always free for small enterprises. We charge just 2% of sales for enterprises turning over more than £5000/year." + sell_embed: "We can also embed an OFN shop in your own customised website or build a customised local food network website for your region." + sell_ask_services: "Ask us about OFN services." 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. @@ -627,7 +544,6 @@ en_GB: 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 @@ -647,7 +563,6 @@ en_GB: 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}." @@ -660,13 +575,9 @@ en_GB: 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. - - shop_trial_length: "" - shop_trial_expires_in: "" - shop_trial_expired_notice: "" - - - # keys used in javascript + shop_trial_length: "Shop Trial Length (Days)" + shop_trial_expires_in: "Your shopfront trial expires in" + shop_trial_expired_notice: "OFN UK is a non-profit social enterprise making software affordable through open source collaboration. Thanks for being part of it!" password: Password remember_me: Remember Me are_you_sure: "Are you sure?" @@ -717,7 +628,6 @@ en_GB: 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." @@ -827,7 +737,6 @@ en_GB: bulk: "Bulk" shop_variant_quantity_min: "min" shop_variant_quantity_max: "max" - contact: "Contact" follow: "Follow" shop_for_products_html: "Shop for %{enterprise} products at:" change_shop: "Change shop to:" @@ -840,10 +749,9 @@ en_GB: fundraising_fee: "Fundraising fee" price_graph: "Price graph" included_tax: "Included tax" - remove_tax: "Remove tax" balance: "Balance" transaction: "Transaction" - transaction_date: "Date" #Transaction is only in key to avoid conflict with :date + transaction_date: "Date" payment_state: "Payment status" shipping_state: "Shipping status" value: "Value" @@ -885,14 +793,12 @@ en_GB: supplier: "Supplier" coordinator: "Coordinator" distributor: "Distributor" - product: "Products" - enterprise_fees: "Enterprise Fees" fee_type: "Fee Type" tax_category: "Tax Category" calculator: "Calculator" calculator_values: "Calculator values" new_order_cycles: "New Order Cycles" - select_a_coordinator_for_your_order_cycle: "" + select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" edit_order_cycle: "Edit Order Cycle" roles: "Roles" update: "Update" @@ -936,7 +842,6 @@ en_GB: spree_admin_enterprises_producers_total_products: "Total Products" spree_admin_enterprises_producers_active_products: "Active Products" spree_admin_enterprises_producers_order_cycles: "Products in OCs" - spree_admin_enterprises_producers_order_cycles_title: "" spree_admin_enterprises_tabs_hubs: "HUBS" spree_admin_enterprises_tabs_producers: "PRODUCERS" spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES" @@ -982,7 +887,7 @@ en_GB: hub_sidebar_blue: "blue" hub_sidebar_red: "red" shop_trial_in_progress: "Your shopfront trial expires in %{days}." - shop_trial_expired: "To view or change your current Open Food Network plan go to the Dashboard -> Change Package." #FIXME + shop_trial_expired: "To view or change your current Open Food Network plan go to the Dashboard -> Change Package." report_customers_distributor: "Distributor" report_customers_supplier: "Supplier" report_customers_cycle: "Order Cycle" @@ -1016,14 +921,12 @@ en_GB: unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?" one_product_unsaved: "Changes to one product remain unsaved." products_unsaved: "Changes to %{n} products remain unsaved." - add_manager: "Add a manager" is_already_manager: "is already a manager!" no_change_to_save: "No change to save" add_manager: "Add a manager" - users: "Users" + users: "Users" about: "About" images: "Images" - contact: "Contact" web: "Web" primary_details: "Primary Details" adrdress: "Address" @@ -1033,7 +936,7 @@ en_GB: properties: "Properties" shipping_methods: "Shipping Methods" payment_methods: "Payment Methods" - payment_method_fee: "" + payment_method_fee: "Transaction fee" enterprise_fees: "Enterprise Fees" inventory_settings: "Inventory Settings" tag_rules: "Tag Rules" @@ -1043,7 +946,6 @@ en_GB: validation_msg_product_category_cant_be_blank: "^Product Category cant be blank" validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" - spree: shipment_states: backorder: backorder @@ -1061,7 +963,7 @@ en_GB: pending: pending processing: processing void: void - invalid: '' + invalid: invalid order_state: address: address adjustments: adjustments diff --git a/config/locales/en.yml b/config/locales/en.yml index 1d8707a7b94..ba2b1acab6d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -128,6 +128,8 @@ en: select_country: 'Select Country' select_state: 'Select State' edit: 'Edit' + update_address: 'Update Address' + confirm_delete: 'Sure to delete?' products: bulk_edit: @@ -196,11 +198,15 @@ en: status: Status manage: Manage form: - primary_details: - shopfront_requires_login: "Shopfront requires login?" - shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront." + shop_preferences: + shopfront_requires_login: "Publicly visible shopfront?" + shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront or if it's visible to everybody." shopfront_requires_login_false: "Public" - shopfront_requires_login_true: "Require customers to login" + shopfront_requires_login_true: "Visible to registered customers only" + allow_guest_orders: "Guest orders" + allow_guest_orders_tip: "Allow checkout as guest or require a registered user." + allow_guest_orders_false: "Require login to order" + allow_guest_orders_true: "Allow guest checkout" home: hubs: @@ -376,7 +382,9 @@ en: checkout_as_guest: "Checkout as guest" checkout_details: "Your details" checkout_billing: "Billing info" + checkout_default_bill_address: "Save as default billing address" checkout_shipping: Shipping info + checkout_default_ship_address: "Save as default shipping address" checkout_method_free: Free checkout_address_same: Shipping address same as billing address? checkout_ready_for: "Ready for:" @@ -1066,6 +1074,7 @@ Please follow the instructions there to make your enterprise visible on the Open validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" spree: + zipcode: Postcode shipment_states: backorder: backorder partial: partial diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml deleted file mode 100644 index bb35552b675..00000000000 --- a/config/locales/en_GB.yml +++ /dev/null @@ -1,991 +0,0 @@ -en_GB: - activerecord: - 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." - enterprise_mailer: - confirmation_instructions: - subject: "Please confirm the email address for %{enterprise}" - welcome: - subject: "%{enterprise} is now on %{sitename}" - 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 seriously change the world." - search_by_name: Search by name, town, county or postcode... - producers: 'UK Producers' - producers_join: UK producers are now welcome to join Open Food Network UK. - charges_sales_tax: Charges VAT? - 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: "Please enter your company number." - invoice: "Invoice" - percentage_of_sales: "%{percentage} of sales" - percentage_of_turnover: "Percentage of turnover" - monthly_cap_excl_tax: "monthly cap (excl. VAT)" - capped_at_cap: "capped at %{cap}" - per_month: "per month" - free: "free" - free_trial: "free trial" - plus_tax: "plus VAT" - total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. VAT)" - min_bill_turnover_desc: "once turnover exceeds %{mbt_amount}" - business_model_configuration: "Business model configuration" - say_no: "No" - say_yes: "Yes" - then: then - sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" - admin: - date: Date - email: Email - name: Name - on_hand: In Stock - on_demand: On Demand - on_demand?: On Demand? - order_cycle: Order Cycle - phone: Phone - price: Price - producer: Producer - product: Product - quantity: Quantity - shop: Shop - sku: SKU - tags: Tags - variant: Variant - quick_search: Quick Search - clear_all: Clear All - start_date: "Start Date" - end_date: "End Date" - 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" - new_customer: "New Customer" - customer_placeholder: "customer@example.org" - valid_email_error: Please enter a valid email address - add_a_new_customer_for: Add a new customer for %{shop_name} - code: Code - duplicate_code: "This code is used already." - bill_address: "Billing Address" - ship_address: "Shipping Address" - update_address_success: 'Address updated successfully.' - update_address_error: 'Sorry! Please input all of the required fields!' - edit_bill_address: 'Edit Billing Address' - edit_ship_address: 'Edit Shipping Address' - required_fileds: 'Required fields are denoted with an asterisk' - select_country: 'Select Country' - select_state: 'Select County' - edit: 'Edit' - products: - bulk_edit: - unit: Unit - display_as: Display As - category: Category - tax_category: Tax Category - inherits_properties?: Inherits Properties? - available_on: Available On - av_on: "Av. On" - variant_overrides: - index: - 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 - enable_reset?: Enable Stock 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! - orders: - bulk_management: - tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." - shared: "Shared Resource?" - order_no: "Order No." - order_date: "Order Date" - max: "Max" - product_unit: "Product: Unit" - weight_volume: "Weight/Volume" - ask: "Ask?" - page_title: "Bulk Order Management" - actions_delete: "Delete Selected" - loading: "Loading orders" - no_results: "No orders found." - group_buy_unit_size: "Group Buy Unit Size" - total_qtt_ordered: "Total Quantity Ordered" - max_qtt_ordered: "Max Quantity Ordered" - current_fulfilled_units: "Current Fulfilled Units" - max_fulfilled_units: "Max Fulfilled Units" - order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." - variants_without_unit_value: "WARNING: Some variants do not have a unit value" - order_cycles: - edit: - choose_products_from: "Choose Products From:" - enterprise: - select_outgoing_oc_products_from: Select outgoing OC products from - enterprises: - index: - producer?: Producer? - package: Package - status: Status - manage: Manage - 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 selling through 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." - invoice_column_item: "Item" - invoice_column_qty: "Qty" - invoice_column_tax: "VAT" - invoice_column_price: "Price" - logo: "Logo (640x130)" - logo_mobile: "Mobile logo (75x26)" - logo_mobile_svg: "Mobile logo (SVG)" - home_hero: "Hero image" - home_show_stats: "Show statistics" - footer_logo: "Logo (220x76)" - 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: County - postcode: Postcode - country: Country - unauthorized: Unauthorized - terms_of_service: "Terms of service" - on_demand: To Order - none: None - label_shops: "Shops" - label_map: "Map" - label_producers: "Producers" - label_groups: "Groups" - label_about: "About" - label_connect: "Connect" - label_learn: "Learn" - 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: "Community Forum" - 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_body: "Create a listing, shop or group directory on the Open Food Network." - footer_join_cta: "Tell me more!" - 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." - learn_body: "Explore models, stories and resources to support you to develop your fair food business or organisation. Find training, events and other opportunities to learn from peers." - learn_cta: "Get Inspired" - connect_body: "Search our full directories of producers, hubs and groups to find fair food traders near you. List your business or organisation on the OFN so buyers can find you. Join the community to get advice and solve problems together." - connect_cta: "Go Exploring" - system_headline: "Shopping - 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 - 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}" - join_community: "Join the community" - 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 %{email}" - producer_mail_greeting: "Dear" - producer_mail_text_before: "We now have all the consumer orders for the next food delivery." - producer_mail_order_text: "Here is a summary of the orders for your products:" - producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" - producer_mail_signoff: "Thanks and best wishes" - 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." - 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_filter_property: Property - 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. - products_item: Item - products_description: Description - products_variant: Variant - products_quantity: Quantity - products_available: Available? - products_producer: "Producer" - products_price: "Price" - register_title: Register - sell_title: "Register" - sell_headline: "Get on the Open Food Network!" - sell_motivation: "Showcase your beautiful food." - sell_producers: "Producers" - sell_hubs: "Hubs" - sell_groups: "Groups" - sell_producers_detail: "Set up a profile for your business on the OFN in just minutes. At any time you can upgrade your profile to an online store and sell your products direct to customers." - sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." - sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." - sell_user_guide: "Find out more in our user guide." - sell_listing_price: "Listing on OFN is free. Opening and running a shop on OFN is free for six months and always free for small enterprises. We charge just 2% of sales for enterprises turning over more than £5000/year." - sell_embed: "We can also embed an OFN shop in your own customised website or build a customised local food network website for your region." - sell_ask_services: "Ask us about OFN services." - 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. - shop_trial_length: "Shop Trial Length (Days)" - shop_trial_expires_in: "Your shopfront trial expires in" - shop_trial_expired_notice: "OFN UK is a non-profit social enterprise making software affordable through open source collaboration. Thanks for being part of it!" - 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" - who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" - enterprise_contact: "Primary contact" - enterprise_contact_required: "You need to enter a primary contact." - enterprise_email_address: "Email address" - enterprise_phone: "Phone number" - back: "Back" - continue: "Continue" - limit_reached_headline: "Oh no!" - limit_reached_message: "You have reached the limit!" - limit_reached_text: "You have reached the limit for the number of enterprises you are allowed to own on the" - limit_reached_action: "Return to the homepage" - select_promo_image: "Step 3. Select promo image" - promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px" - promo_image_label: "Choose a promo image" - action_or: "OR" - promo_image_drag: "Drag and drop your promo here" - review_promo_image: "Step 4. Review your promo banner" - review_promo_image_tip: "Tip: for best results, your promo image should fill the available space" - promo_image_placeholder: "Your logo will appear here for review once uploaded" - uploading: "Uploading..." - select_logo: "Step 1. Select logo image" - logo_tip: "Tip: Square images will work best, preferably at least 300×300px" - logo_label: "Choose a logo image" - logo_drag: "Drag and drop your logo here" - review_logo: "Step 2. Review your logo" - review_logo_tip: "Tip: for best results, your logo should fill the available space" - logo_placeholder: "Your logo will appear here for review once uploaded" - enterprise_about_headline: "Nice one!" - enterprise_about_message: "Now let's flesh out the details about" - enterprise_success: "Success! %{enterprise} added to the Open Food Network " - enterprise_registration_exit_message: "If you exit this wizard at any stage, you need to click the confirmation link in the email you have received. This will take you to your admin interface where you can continue setting up your profile." - enterprise_description: "Short Description" - enterprise_description_placeholder: "A short sentence describing your enterprise" - enterprise_long_desc: "Long Description" - enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." - enterprise_long_desc_length: "%{num} characters / up to 600 recommended" - enterprise_abn: "ABN" - enterprise_abn_placeholder: "eg. 99 123 456 789" - enterprise_acn: "ACN" - enterprise_acn_placeholder: "eg. 123 456 789" - enterprise_tax_required: "You need to make a selection." - enterprise_final_step: "Final step!" - enterprise_social_text: "How can people find %{enterprise} online?" - website: "Website" - website_placeholder: "eg. openfoodnetwork.org.au" - facebook: "Facebook" - facebook_placeholder: "eg. www.facebook.com/PageNameHere" - linkedin: "LinkedIn" - linkedin_placeholder: "eg. www.linkedin.com/YourNameHere" - twitter: "Twitter" - twitter_placeholder: "eg. @twitter_handle" - instagram: "Instagram" - instagram_placeholder: "eg. @instagram_handle" - registration_greeting: "Hi there!" - registration_intro: "You can now create a profile for your Producer or Hub" - registration_action: "Let's get started!" - registration_checklist: "You'll need" - registration_time: "5-10 minutes" - registration_enterprise_address: "Enterprise address" - registration_contact_details: "Primary contact details" - registration_logo: "Your logo image" - registration_promo_image: "Landscape image for your profile" - registration_about_us: "'About Us' text" - registration_outcome_headline: "What do I get?" - registration_outcome1_html: "Your profile helps people find and contact you on the Open Food Network." - registration_outcome2: "Use this space to tell the story of your enterprise, to help drive connections to your social and online presence. " - registration_outcome3: "It's also the first step towards trading on the Open Food Network, or opening an online store." - registration_finished_headline: "Finished!" - registration_finished_thanks: "Thanks for filling out the details for %{enterprise}." - registration_finished_login: "You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin." - registration_finished_activate: "Activate %{enterprise}." - registration_finished_activate_instruction_html: "We've sent a confirmation email to %{email} if it hasn't been activated before.
Please follow the instructions there to make your enterprise visible on the Open Food Network." - registration_finished_action: "Open Food Network home" - registration_type_headline: "Last step to add %{enterprise}!" - registration_type_question: "Are you a producer?" - registration_type_producer: "Yes, I'm a producer" - registration_type_no_producer: "No, I'm not a producer" - registration_type_error: "Please choose one. Are you are producer?" - registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." - registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - create_profile: "Create profile" - registration_images_headline: "Thanks!" - registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" - registration_detail_headline: "Let's get started" - registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:" - registration_detail_producer: "Woot! First we need to know a little bit about your farm:" - registration_detail_name_enterprise: "Enterprise name:" - registration_detail_name_producer: "Farm name:" - registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm" - registration_detail_name_error: "Please choose a unique name for your enterprise" - registration_detail_address1: "Address line 1:" - registration_detail_address1_placeholder: "e.g. 123 Cranberry Drive" - registration_detail_address1_error: "Please enter an address" - registration_detail_address2: "Address line 2:" - registration_detail_suburb: "Suburb:" - registration_detail_suburb_placeholder: "e.g. Northcote" - registration_detail_suburb_error: "Please enter a suburb" - registration_detail_postcode: "Postcode:" - registration_detail_postcode_placeholder: "e.g. 3070" - registration_detail_postcode_error: "Postcode required" - registration_detail_state: "State:" - registration_detail_state_error: "State required" - registration_detail_country: "Country:" - registration_detail_country_error: "Please select a country" - fees: "Fees" - item_cost: "Item cost" - bulk: "Bulk" - shop_variant_quantity_min: "min" - shop_variant_quantity_max: "max" - follow: "Follow" - shop_for_products_html: "Shop for %{enterprise} products at:" - change_shop: "Change shop to:" - shop_at: "Shop now at:" - price_breakdown: "Full price breakdown" - admin_fee: "Admin fee" - sales_fee: "Sales fee" - packing_fee: "Packing fee" - transport_fee: "Transport fee" - fundraising_fee: "Fundraising fee" - price_graph: "Price graph" - included_tax: "Included tax" - balance: "Balance" - transaction: "Transaction" - transaction_date: "Date" - payment_state: "Payment status" - shipping_state: "Shipping status" - value: "Value" - balance_due: "Balance due" - credit: "Credit" - Paid: "Paid" - Ready: "Ready" - you_have_no_orders_yet: "You have no orders yet" - running_balance: "Running balance" - outstanding_balance: "Outstanding balance" - admin_entreprise_relationships: "Enterprise Relationships" - admin_entreprise_relationships_everything: "Everything" - admin_entreprise_relationships_permits: "permits" - admin_entreprise_relationships_seach_placeholder: "Search" - admin_entreprise_relationships_button_create: "Create" - admin_entreprise_groups: "Enterprise Groups" - admin_entreprise_groups_name: "Name" - admin_entreprise_groups_owner: "Owner" - admin_entreprise_groups_on_front_page: "On front page?" - admin_entreprise_groups_entreprise: "Enterprises" - admin_entreprise_groups_data_powertip: "The primary user responsible for this group." - admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" - admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" - admin_entreprise_groups_contact: "Contact" - admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" - admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" - admin_entreprise_groups_contact_city: "Suburb" - admin_entreprise_groups_contact_city_placeholder: "eg. Camden" - admin_entreprise_groups_contact_zipcode: "Postcode" - admin_entreprise_groups_contact_zipcode_placeholder: "eg. NW1 0AA" - admin_entreprise_groups_contact_state_id: "County" - admin_entreprise_groups_contact_country_id: "Country" - admin_entreprise_groups_web: "Web Resources" - admin_entreprise_groups_web_twitter: "eg. @openfoodnetuk" - admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.co.uk" - admin_order_cycles: "Admin Order Cycles" - open: "Open" - close: "Close" - supplier: "Supplier" - coordinator: "Coordinator" - distributor: "Distributor" - fee_type: "Fee Type" - tax_category: "Tax Category" - calculator: "Calculator" - calculator_values: "Calculator values" - flat_percent_per_item: "Flat Percent (per item)" - new_order_cycles: "New Order Cycles" - select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" - edit_order_cycle: "Edit Order Cycle" - roles: "Roles" - update: "Update" - add_producer_property: "Add producer property" - admin_settings: "Settings" - update_invoice: "Update Invoices" - finalise_invoice: "Finalise Invoices" - finalise_user_invoices: "Finalise User Invoices" - finalise_user_invoice_explained: "Use this button to finalise all invoices in the system for the previous calendar month. This task can be set up to run automatically once a month." - manually_run_task: "Manually Run Task" - update_user_invoices: "Update User Invoices" - update_user_invoice_explained: "Use this button to immediately update invoices for the month to date for each enterprise user in the system. This task can be set up to run automatically every night." - auto_finalise_invoices: "Auto-finalise invoices monthly on the 2nd at 1:30am" - auto_update_invoices: "Auto-update invoices nightly at 1:00am" - in_progress: "In Progress" - started_at: "Started at" - queued: "Queued" - scheduled_for: "Scheduled for" - customers: "Customers" - please_select_hub: "Please select a Hub" - loading_customers: "Loading Customers" - no_customers_found: "No customers found" - go: "Go" - hub: "Hub" - accounts_administration_distributor: "accounts administration distributor" - accounts_and_billing: "Accounts & Billing" - producer: "Producer" - product: "Product" - price: "Price" - on_hand: "In stock" - save_changes: "Save Changes" - spree_admin_overview_enterprises_header: "My Enterprises" - spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES" - spree_admin_enterprises_hubs_name: "Name" - spree_admin_enterprises_create_new: "CREATE NEW" - spree_admin_enterprises_shipping_methods: "Shipping Methods" - spree_admin_enterprises_fees: "Enterprise Fees" - spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" - spree_admin_enterprises_none_text: "You don't have any enterprises yet" - spree_admin_enterprises_producers_name: "Name" - spree_admin_enterprises_producers_total_products: "Total Products" - spree_admin_enterprises_producers_active_products: "Active Products" - spree_admin_enterprises_producers_order_cycles: "Products in OCs" - spree_admin_enterprises_tabs_hubs: "HUBS" - spree_admin_enterprises_tabs_producers: "PRODUCERS" - spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES" - spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" - spree_admin_enterprises_producers_orders_cycle_text: "You don't have any active order cycles." - spree_admin_enterprises_any_active_products_text: "You don't have any active products." - spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" - spree_admin_order_cycles: "Order Cycles" - spree_admin_order_cycles_tip: "Order cycles determine when and where your products are available to customers." - dashbord: "Dashboard" - spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" - spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" - spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" - change_package: "Change Package" - spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" - your_profil_live: "Your profile live" - on_ofn_map: "on the Open Food Network map" - see: "See" - live: "live" - manage: "Manage" - resend: "Resend" - add_and_manage_products: "Add & manage products" - add_and_manage_order_cycles: "Add & manage order cycles" - manage_order_cycles: "Manage order cycles" - manage_products: "Manage products" - edit_profile_details: "Edit profile details" - edit_profile_details_etc: "Change your profile description, images, etc." - order_cycle: "Order Cycle" - remove_tax: "Remove tax" - tax_settings: "Tax Settings" - products_require_tax_category: "products require tax category" - admin_shared_address_1: "Address" - admin_shared_address_2: "Address (cont.)" - admin_share_city: "City" - admin_share_zipcode: "Postcode" - admin_share_country: "Country" - admin_share_state: "County" - hub_sidebar_hubs: "Hubs" - hub_sidebar_none_available: "None Available" - hub_sidebar_manage: "Manage" - hub_sidebar_at_least: "At least one hub must be selected" - hub_sidebar_blue: "blue" - hub_sidebar_red: "red" - shop_trial_in_progress: "Your shopfront trial expires in %{days}." - shop_trial_expired: "To view or change your current Open Food Network plan go to the Dashboard -> Change Package." - report_customers_distributor: "Distributor" - report_customers_supplier: "Supplier" - report_customers_cycle: "Order Cycle" - report_customers_type: "Report Type" - report_customers_csv: "Download as csv" - report_producers: "Producers:" - report_type: "Report Type:" - report_hubs: "Hubs:" - report_payment: "Payment Methods:" - report_distributor: "Distributor:" - report_payment_by: 'Payments By Type' - report_itemised_payment: 'Itemised Payment Totals' - report_payment_totals: 'Payment Totals' - report_all: 'all' - report_order_cycle: "Order Cycle:" - report_entreprises: "Enterprises:" - report_users: "Users:" - initial_invoice_number: "Initial invoice number:" - invoice_date: "Invoice date:" - due_date: "Invoice date:" - account_code: "Account code:" - equals: "Equals" - contains: "contains" - discount: "Discount" - filter_products: " Filter Products" - delete_product_variant: "The last variant cannot be deleted!" - progress: "progress" - saving: "Saving.." - success: "success" - failure: "failure" - unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?" - one_product_unsaved: "Changes to one product remain unsaved." - products_unsaved: "Changes to %{n} products remain unsaved." - is_already_manager: "is already a manager!" - no_change_to_save: "No change to save" - add_manager: "Add a manager" - users: "Users" - about: "About" - images: "Images" - web: "Web" - primary_details: "Primary Details" - adrdress: "Address" - contact: "Contact" - social: "Social" - business_details: "Business Details" - properties: "Properties" - shipping_methods: "Shipping Methods" - payment_methods: "Payment Methods" - payment_method_fee: "Transaction fee" - enterprise_fees: "Enterprise Fees" - inventory_settings: "Inventory Settings" - tag_rules: "Tag Rules" - shop_preferences: "Shop Preferences" - validation_msg_relationship_already_established: "^That relationship is already established." - validation_msg_at_least_one_hub: "^At least one hub must be selected" - validation_msg_product_category_cant_be_blank: "^Product Category cant be blank" - validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" - validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" - spree: - shipment_states: - backorder: backorder - partial: partial - pending: pending - ready: ready - shipped: shipped - payment_states: - balance_due: balance due - completed: completed - checkout: checkout - credit_owed: credit owed - failed: failed - paid: paid - pending: pending - processing: processing - void: void - invalid: invalid - order_state: - address: address - adjustments: adjustments - awaiting_return: awaiting return - canceled: canceled - cart: cart - complete: complete - confirm: confirm - delivery: delivery - payment: payment - resumed: resumed - returned: returned - skrill: skrill diff --git a/config/routes.rb b/config/routes.rb index 5f37bbe8316..69319ae84a1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -234,6 +234,8 @@ namespace :admin do get '/search/known_users' => "search#known_users", :as => :search_known_users + get '/search/customers' => 'search#customers', :as => :search_customers + resources :products do get :product_distributions, on: :member diff --git a/db/migrate/20160921060442_add_allow_guest_orders_to_enterprise.rb b/db/migrate/20160921060442_add_allow_guest_orders_to_enterprise.rb new file mode 100644 index 00000000000..7e92c7e74c8 --- /dev/null +++ b/db/migrate/20160921060442_add_allow_guest_orders_to_enterprise.rb @@ -0,0 +1,5 @@ +class AddAllowGuestOrdersToEnterprise < ActiveRecord::Migration + def change + add_column :enterprises, :allow_guest_orders, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 48b07e3cdb8..64bfd0df690 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20160819065331) do +ActiveRecord::Schema.define(:version => 20160921060442) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -248,6 +248,7 @@ t.boolean "charges_sales_tax", :default => false, :null => false t.string "email_address" t.boolean "require_login", :default => false, :null => false + t.boolean "allow_guest_orders", :default => true, :null => false end add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id" diff --git a/lib/open_food_network/order_cycle_management_report.rb b/lib/open_food_network/order_cycle_management_report.rb index 8a9c6ba60fc..b4fb9a6f329 100644 --- a/lib/open_food_network/order_cycle_management_report.rb +++ b/lib/open_food_network/order_cycle_management_report.rb @@ -55,15 +55,15 @@ def payment_method_row(order) end def delivery_row(order) - ba = order.billing_address + sa = order.shipping_address da = order.distributor.andand.address - [ba.firstname, - ba.lastname, + [sa.firstname, + sa.lastname, order.distributor.andand.name, customer_code(order.email), - "#{ba.address1} #{ba.address2} #{ba.city}", - ba.zipcode, - ba.phone, + "#{sa.address1} #{sa.address2} #{sa.city}", + sa.zipcode, + sa.phone, order.shipping_method.andand.name, order.payments.first.andand.payment_method.andand.name, order.payments.first.amount, diff --git a/lib/open_food_network/packing_report.rb b/lib/open_food_network/packing_report.rb index 61c2a09db5e..f3f54479f52 100644 --- a/lib/open_food_network/packing_report.rb +++ b/lib/open_food_network/packing_report.rb @@ -83,8 +83,8 @@ def rules sort_by: proc { |product| product.name } }, { group_by: proc { |line_item| line_item.full_name }, sort_by: proc { |full_name| full_name } }, - { group_by: proc { |line_item| line_item.order.bill_address.lastname }, - sort_by: proc { |lastname| lastname } } ] + { group_by: proc { |line_item| line_item.order }, + sort_by: proc { |order| order.bill_address.lastname } } ] end end diff --git a/spec/controllers/spree/admin/search_controller_spec.rb b/spec/controllers/spree/admin/search_controller_spec.rb index b66ba94888d..46a52847055 100644 --- a/spec/controllers/spree/admin/search_controller_spec.rb +++ b/spec/controllers/spree/admin/search_controller_spec.rb @@ -31,5 +31,44 @@ end end end + + describe 'searching for customers' do + let!(:customer_1) { create(:customer, enterprise: enterprise, email: 'test1@email.com') } + let!(:customer_2) { create(:customer, enterprise: enterprise, name: 'test2') } + let!(:customer_3) { create(:customer, email: 'test3@email.com') } + + describe 'when search owned enterprises' do + before do + spree_get :customers, q: "test", distributor_id: enterprise.id + @results = JSON.parse(response.body) + end + + describe 'when search query matches the email or name' do + it 'returns a list of customers of the enterprise' do + expect(@results.size).to eq 2 + + expect(@results.find { |c| c['id'] == customer_1.id}).to be_true + expect(@results.find { |c| c['id'] == customer_2.id}).to be_true + end + + it 'does not return the customer of other enterprises' do + expect(@results.find { |c| c['id'] == customer_3.id}).to be_false + p customer_3 + p enterprise + end + end + end + + describe 'when search in unmanaged enterprise' do + before do + spree_get :customers, q: "test", distributor_id: customer_3.enterprise_id + @results = JSON.parse(response.body) + end + + it 'returns empty array' do + expect(@results).to eq [] + end + end + end end end diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 07352ad1516..10084828713 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -78,6 +78,8 @@ select2_select managed_distributor1.name, from: "shop_id" within "tr#c_#{customer1.id}" do + find_field('name').value.should eq 'John Doe' + fill_in "code", with: "new-customer-code" expect(page).to have_css "input[name=code].update-pending" @@ -161,6 +163,10 @@ first('#bill-address-link').click expect(page).to have_content 'Edit Billing Address' + + expect(page).to have_select('country', selected: 'Australia') + expect(page).to have_select('state', selected: 'Victoria') + fill_in 'address1', with: "New Address1" click_button 'Update Address' @@ -176,6 +182,8 @@ first('#ship-address-link').click expect(page).to have_content 'Edit Shipping Address' + fill_in 'firstname', with: "First" + fill_in 'lastname', with: "Last" fill_in 'address1', with: "New Address1" fill_in 'phone', with: "12345678" fill_in 'city', with: "Melbourne" @@ -190,6 +198,8 @@ ship_address = customer4.reload.ship_address + expect(ship_address.firstname).to eq 'First' + expect(ship_address.lastname).to eq 'Last' expect(ship_address.address1).to eq 'New Address1' expect(ship_address.phone).to eq '12345678' expect(ship_address.city).to eq 'Melbourne' diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 4d090075847..b436c0c3eed 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -85,9 +85,12 @@ page.should have_selector '.available' choose 'Own' - # Require login to view shopfront + # Require login to view shopfront or for checkout + within(".side_menu") { click_link "Shop Preferences" } expect(page).to have_checked_field "enterprise_require_login_false" - choose "Require customers to login" + expect(page).to have_checked_field "enterprise_allow_guest_orders_true" + choose "Visible to registered customers only" + expect(page).to have_no_checked_field "enterprise_require_login_false" within (".side_menu") { click_link "Users" } select2_search user.email, from: 'Owner' @@ -170,7 +173,6 @@ @enterprise.reload expect(@enterprise.owner).to eq user expect(page).to have_checked_field "enterprise_visible_true" - expect(page).to have_checked_field "enterprise_require_login_true" click_link "Business Details" page.should have_checked_field "enterprise_charges_sales_tax_true" @@ -190,6 +192,7 @@ click_link "Shop Preferences" page.should have_content 'This is my shopfront message.' page.should have_checked_field "enterprise_preferred_shopfront_order_cycle_order_orders_open_at" + expect(page).to have_checked_field "enterprise_require_login_true" end describe "producer properties" do diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 18b34661063..cf1598f3fea 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -10,10 +10,11 @@ background do @user = create(:user) @product = create(:simple_product) - @distributor = create(:distributor_enterprise, charges_sales_tax: true) + @distributor = create(:distributor_enterprise, owner: @user, charges_sales_tax: true) @order_cycle = create(:simple_order_cycle, name: 'One', distributors: [@distributor], variants: [@product.variants.first]) @order = create(:order_with_totals_and_distribution, user: @user, distributor: @distributor, order_cycle: @order_cycle, state: 'complete', payment_state: 'balance_due') + @customer = create(:customer, enterprise: @distributor, email: @user.email, user: @user, ship_address: create(:address)) # ensure order has a payment to capture @order.finalize! @@ -125,7 +126,9 @@ @order.save! # When I create a new order - login_to_admin_section + quick_login_as @user + visit spree.admin_path + visit '/admin/orders' click_link 'New Order' select2_select @distributor.name, from: 'order_distributor_id' @@ -137,14 +140,14 @@ within('h1.page-title') { page.should have_content "Customer Details" } # And I select that customer's email address and save the order - targetted_select2_search @order.user.email, from: '#customer_search', dropdown_css: '.select2-drop' + targetted_select2_search @customer.email, from: '#customer_search_override', dropdown_css: '.select2-drop' click_button 'Continue' within('h1.page-title') { page.should have_content "Shipments" } # Then their addresses should be associated with the order order = Spree::Order.last - order.ship_address.lastname.should == 'Ship' - order.bill_address.lastname.should == 'Bill' + order.ship_address.lastname.should == @customer.ship_address.lastname + order.bill_address.lastname.should == @customer.bill_address.lastname end scenario "capture multiple payments from the orders index page" do diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 6055cb47d1a..7bd540169fe 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -61,6 +61,59 @@ page.should have_content "An item in your cart has become unavailable" end end + context 'login in as user' do + let(:user) { create(:user) } + + before do + quick_login_as(user) + visit checkout_path + + toggle_shipping + choose sm1.name + toggle_payment + choose pm1.name + toggle_details + within "#details" do + fill_in "First Name", with: "Will" + fill_in "Last Name", with: "Marshall" + fill_in "Email", with: "test@test.com" + fill_in "Phone", with: "0468363090" + end + toggle_billing + check "Save as default billing address" + within "#billing" do + fill_in "City", with: "Melbourne" + fill_in "Postcode", with: "3066" + fill_in "Address", with: "123 Your Head" + select "Australia", from: "Country" + select "Victoria", from: "State" + end + + toggle_shipping + check "Shipping address same as billing address?" + check "Save as default shipping address" + end + + it "sets user's default billing address and shipping address" do + user.bill_address.should be_nil + user.ship_address.should be_nil + + order.bill_address.should be_nil + order.ship_address.should be_nil + + place_order + page.should have_content "Your order has been processed successfully" + + order.reload.bill_address.address1.should eq '123 Your Head' + order.reload.ship_address.address1.should eq '123 Your Head' + + order.customer.bill_address.address1.should eq '123 Your Head' + order.customer.ship_address.address1.should eq '123 Your Head' + + user.reload.bill_address.address1.should eq '123 Your Head' + user.reload.ship_address.address1.should eq '123 Your Head' + end + end context "on the checkout page" do before do @@ -73,6 +126,11 @@ page.should have_content distributor.name end + it 'does not show the save as defalut address checkbox' do + page.should_not have_content "Save as default billing address" + page.should_not have_content "Save as default shipping address" + end + it "shows a breakdown of the order price" do toggle_shipping choose sm2.name diff --git a/spec/helpers/checkout_helper_spec.rb b/spec/helpers/checkout_helper_spec.rb index e47a5e8c51b..880f37520b4 100644 --- a/spec/helpers/checkout_helper_spec.rb +++ b/spec/helpers/checkout_helper_spec.rb @@ -20,4 +20,14 @@ helper.display_checkout_tax_total(order).should == Spree::Money.new(123.45, currency: 'AUD') end end + + it "knows if guests can checkout" do + distributor = create(:distributor_enterprise) + order = create(:order, distributor: distributor) + helper.stub(:current_order) { order } + expect(helper.guest_checkout_allowed?).to be true + + order.distributor.allow_guest_orders = false + expect(helper.guest_checkout_allowed?).to be false + end end diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index f97ff35d365..5f3deff5503 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -73,6 +73,9 @@ describe "CustomersCtrl", -> expect(scope.customers).toDeepEqual customers describe "scope.deleteCustomer", -> + beforeEach -> + spyOn(window, 'confirm').and.returnValue(true) + it "deletes a customer", -> expect(scope.customers.length).toBe 2 customer = scope.customers[0] diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee index d395ae9cc66..f3ad508c4d9 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee @@ -6,7 +6,7 @@ describe "CheckoutCtrl", -> CurrentHubMock = hub: id: 1 - storage = null + localStorageService = null beforeEach -> module("Darkswarm") @@ -23,14 +23,16 @@ describe "CheckoutCtrl", -> id: 1 email: "public" user_id: 1 + bill_address: 'bill_address' + ship_address: 'ship address' secrets: card_number: "this is a secret" describe "with user", -> beforeEach -> - inject ($controller, $rootScope, _storage_) -> - storage = _storage_ - spyOn(storage, "bind").and.callThrough() + inject ($controller, $rootScope, _localStorageService_) -> + localStorageService = _localStorageService_ + spyOn(localStorageService, "bind").and.callThrough() scope = $rootScope.$new() CurrentUser = { id: 1 } ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: CurrentUser } @@ -57,20 +59,24 @@ describe "CheckoutCtrl", -> expect(scope.enabled).toEqual true describe "Local storage", -> - it "binds to localStorage when given a scope", -> + it "binds to localStorage when given a scope", inject ($timeout) -> prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}" - console.log prefix + field = scope.fieldsToBind[0] - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", {storeName: "#{prefix}_#{field}"}) - expect(storage.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", {storeName: "#{prefix}_sameasbilling", defaultValue: true}) + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.order.#{field}", Checkout.order[field], "#{prefix}_#{field}") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.ship_address_same_as_billing", true, "#{prefix}_sameasbilling") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.default_bill_address", false, "#{prefix}_defaultasbilladdress") + expect(localStorageService.bind).toHaveBeenCalledWith(scope, "Checkout.default_ship_address", false, "#{prefix}_defaultasshipaddress") it "it can retrieve data from localstorage", -> prefix = "order_#{scope.order.id}#{CurrentUser.id or ""}#{CurrentHubMock.hub.id}" - expect(localStorage.getItem("#{prefix}_email")).toMatch "public" + scope.$digest() + expect(localStorage.getItem("ls.#{prefix}_email")).toMatch "public" it "does not store secrets in local storage", -> Checkout.secrets = card_number: "superfuckingsecret" + scope.$digest() keys = (localStorage.key(i) for i in [0..localStorage.length]) for key in keys expect(localStorage.getItem(key)).not.toMatch Checkout.secrets.card_number @@ -85,6 +91,9 @@ describe "CheckoutCtrl", -> expect(scope.enabled).toEqual false it "does not store secrets in local storage", -> + Checkout.secrets = + card_number: "superfuckingsecret" + scope.$digest() keys = (localStorage.key(i) for i in [0..localStorage.length]) for key in keys expect(localStorage.getItem(key)).not.toMatch Checkout.secrets.card_number diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee index 43e83409adf..78fd10f054f 100644 --- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee @@ -131,6 +131,16 @@ describe 'Checkout service', -> Checkout.ship_address_same_as_billing = true expect(Checkout.preprocess().ship_address_attributes).toEqual(orderData.bill_address) + it "munges the default as billing address and shipping address", -> + expect(Checkout.preprocess().default_bill_address).toEqual(false) + expect(Checkout.preprocess().default_ship_address).toEqual(false) + + Checkout.default_bill_address = true + Checkout.default_ship_address = true + + expect(Checkout.preprocess().default_bill_address).toEqual(true) + expect(Checkout.preprocess().default_ship_address).toEqual(true) + it "creates attributes for card fields", -> source_attributes = Checkout.preprocess().payments_attributes[0].source_attributes expect(source_attributes).toBeDefined() diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb index 37134e95743..106ec3bcf7f 100644 --- a/spec/models/customer_spec.rb +++ b/spec/models/customer_spec.rb @@ -24,7 +24,9 @@ it 'updates the shipping address' do expect(customer.shipping_address).to be_nil - ship_address = {zipcode: "3127", + ship_address = {firstname: 'fname', + lastname: 'lname', + zipcode: "3127", city: "Melbourne", state_id: 1, phone: "455500146", @@ -33,8 +35,8 @@ customer.update_attributes!(ship_address_attributes: ship_address) expect(customer.ship_address.city).to eq 'Melbourne' - expect(customer.ship_address.firstname).to eq 'unused' - expect(customer.ship_address.lastname).to eq 'unused' + expect(customer.ship_address.firstname).to eq 'fname' + expect(customer.ship_address.lastname).to eq 'lname' end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 4af3d233b16..44321353fef 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -498,7 +498,7 @@ module Spree end it "should have the ability to search for users which share management of its enterprises" do - should have_ability([:admin, :known_users], for: :search) + should have_ability([:admin, :known_users, :customers], for: :search) should_not have_ability([:users], for: :search) end end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 2140163f98a..b8ea3ae0ee7 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -590,10 +590,18 @@ end context "and order#email_for_customer does not match any existing customers" do - it "creates a new customer" do + before { + order.bill_address = create(:address) + order.ship_address = create(:address) + } + it "creates a new customer with defaut name and addresses" do expect(order.customer).to be_nil expect{order.send(:ensure_customer)}.to change{Customer.count}.by 1 expect(order.customer).to be_a Customer + + expect(order.customer.name).to eq order.bill_address.full_name + expect(order.customer.bill_address.same_as?(order.bill_address)).to be true + expect(order.customer.ship_address.same_as?(order.ship_address)).to be true end end end diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 441757bf38f..0b574146774 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -4,6 +4,29 @@ describe "associations" do it { should have_many(:owned_enterprises) } + describe "addresses" do + let(:user) { create(:user, bill_address: create(:address)) } + + it 'updates billing address with new address' do + old_bill_address = user.bill_address + new_bill_address = create(:address, firstname: 'abc') + + user.update_attributes(bill_address_attributes: new_bill_address.clone.attributes.merge('id' => old_bill_address.id)) + + expect(user.bill_address.id).to eq old_bill_address.id + expect(user.bill_address.firstname).to eq new_bill_address.firstname + end + + it 'creates new shipping address' do + new_ship_address = create(:address, firstname: 'abc') + + user.update_attributes(ship_address_attributes: new_ship_address.clone.attributes) + + expect(user.ship_address.id).not_to eq new_ship_address.id + expect(user.ship_address.firstname).to eq new_ship_address.firstname + end + end + describe "enterprise ownership" do let(:u1) { create(:user, enterprise_limit: 2) } let(:u2) { create(:user, enterprise_limit: 1) } diff --git a/spec/serializers/admin/customer_serializer_spec.rb b/spec/serializers/admin/customer_serializer_spec.rb index 5694b1004f8..697b03f41ac 100644 --- a/spec/serializers/admin/customer_serializer_spec.rb +++ b/spec/serializers/admin/customer_serializer_spec.rb @@ -2,7 +2,7 @@ let(:customer) { create(:customer, tag_list: "one, two, three") } let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") } - it "serializes a customer" do + it "serializes a customer with tags" do tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: customer.enterprise_id)) serializer = Api::Admin::CustomerSerializer.new customer, tag_rule_mapping: tag_rule_mapping result = JSON.parse(serializer.to_json) @@ -16,4 +16,13 @@ expect(result['bill_address']['address1']).to eq customer.bill_address.address1 expect(result['ship_address']).to be nil end + + it 'serializes a customer without tag_rule_mapping' do + serializer = Api::Admin::CustomerSerializer.new customer + result = JSON.parse(serializer.to_json) + + result['tags'].each do |tag| + expect(tag['rules']).to be nil + end + end end diff --git a/spec/support/delayed_job_helper.rb b/spec/support/delayed_job_helper.rb index 82fd79c95a4..84d03cc6513 100644 --- a/spec/support/delayed_job_helper.rb +++ b/spec/support/delayed_job_helper.rb @@ -34,7 +34,12 @@ def clear_jobs match do |event_proc| last_job_id_before = Delayed::Job.last.andand.id || 0 - event_proc.call + begin + event_proc.call + rescue StandardError => e + @exception = e + raise e + end @jobs_created = Delayed::Job.where('id > ?', last_job_id_before) @@ -57,11 +62,11 @@ def clear_jobs end failure_message_for_should do |event_proc| - "expected #{klass} to be enqueued matching #{options.inspect} (#{@jobs_created.andand.count || '???'} others enqueued)" + @exception || "expected #{klass} to be enqueued matching #{options.inspect} (#{@jobs_created.count} others enqueued)" end failure_message_for_should_not do |event_proc| - "expected #{klass} to not be enqueued matching #{options.inspect}" + @exception || "expected #{klass} to not be enqueued matching #{options.inspect}" end end end