")
+ else
+ for product in data.products
+ if product.master.images[0] != undefined && product.master.images[0].small_url != undefined
+ product.image = product.master.images[0].small_url
+ el.append(productTemplate({ product: product }))
+ $('#sorting_explanation').show()
diff --git a/backend/app/assets/javascripts/spree/backend/underscore-min.js b/backend/app/assets/javascripts/spree/backend/underscore-min.js
new file mode 100644
index 00000000000..32ca0c1b142
--- /dev/null
+++ b/backend/app/assets/javascripts/spree/backend/underscore-min.js
@@ -0,0 +1,1227 @@
+// Underscore.js 1.4.4
+// ===================
+
+// > http://underscorejs.org
+// > (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+// > Underscore may be freely distributed under the MIT license.
+
+// Baseline setup
+// --------------
+(function() {
+
+ // Establish the root object, `window` in the browser, or `global` on the server.
+ var root = this;
+
+ // Save the previous value of the `_` variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets returned to break out of a loop iteration.
+ var breaker = {};
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ concat = ArrayProto.concat,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty;
+
+ // All **ECMAScript 5** native function implementations that we hope to use
+ // are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys,
+ nativeBind = FuncProto.bind;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) {
+ if (obj instanceof _) return obj;
+ if (!(this instanceof _)) return new _(obj);
+ this._wrapped = obj;
+ };
+
+ // Export the Underscore object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, add `_` as a global object via a string identifier,
+ // for Closure Compiler "advanced" mode.
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ exports = module.exports = _;
+ }
+ exports._ = _;
+ } else {
+ root._ = _;
+ }
+
+ // Current version.
+ _.VERSION = '1.4.4';
+
+ // Collection Functions
+ // --------------------
+
+ // The cornerstone, an `each` implementation, aka `forEach`.
+ // Handles objects with the built-in `forEach`, arrays, and raw objects.
+ // Delegates to **ECMAScript 5**'s native `forEach` if available.
+ var each = _.each = _.forEach = function(obj, iterator, context) {
+ if (obj == null) return;
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, l = obj.length; i < l; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var key in obj) {
+ if (_.has(obj, key)) {
+ if (iterator.call(context, obj[key], key, obj) === breaker) return;
+ }
+ }
+ }
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to **ECMAScript 5**'s native `map` if available.
+ _.map = _.collect = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ each(obj, function(value, index, list) {
+ results[results.length] = iterator.call(context, value, index, list);
+ });
+ return results;
+ };
+
+ var reduceError = 'Reduce of empty array with no initial value';
+
+ // **Reduce** builds up a single result from a list of values, aka `inject`,
+ // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
+ _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduce && obj.reduce === nativeReduce) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
+ }
+ each(obj, function(value, index, list) {
+ if (!initial) {
+ memo = value;
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, value, index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as `foldr`.
+ // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
+ _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
+ var initial = arguments.length > 2;
+ if (obj == null) obj = [];
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
+ if (context) iterator = _.bind(iterator, context);
+ return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
+ }
+ var length = obj.length;
+ if (length !== +length) {
+ var keys = _.keys(obj);
+ length = keys.length;
+ }
+ each(obj, function(value, index, list) {
+ index = keys ? keys[--length] : --length;
+ if (!initial) {
+ memo = obj[index];
+ initial = true;
+ } else {
+ memo = iterator.call(context, memo, obj[index], index, list);
+ }
+ });
+ if (!initial) throw new TypeError(reduceError);
+ return memo;
+ };
+
+ // Return the first value which passes a truth test. Aliased as `detect`.
+ _.find = _.detect = function(obj, iterator, context) {
+ var result;
+ any(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ return true;
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to **ECMAScript 5**'s native `filter` if available.
+ // Aliased as `select`.
+ _.filter = _.select = function(obj, iterator, context) {
+ var results = [];
+ if (obj == null) return results;
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) results[results.length] = value;
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ return _.filter(obj, function(value, index, list) {
+ return !iterator.call(context, value, index, list);
+ }, context);
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to **ECMAScript 5**'s native `every` if available.
+ // Aliased as `all`.
+ _.every = _.all = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = true;
+ if (obj == null) return result;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to **ECMAScript 5**'s native `some` if available.
+ // Aliased as `any`.
+ var any = _.some = _.any = function(obj, iterator, context) {
+ iterator || (iterator = _.identity);
+ var result = false;
+ if (obj == null) return result;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+ each(obj, function(value, index, list) {
+ if (result || (result = iterator.call(context, value, index, list))) return breaker;
+ });
+ return !!result;
+ };
+
+ // Determine if the array or object contains a given value (using `===`).
+ // Aliased as `include`.
+ _.contains = _.include = function(obj, target) {
+ if (obj == null) return false;
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+ return any(obj, function(value) {
+ return value === target;
+ });
+ };
+
+ // Invoke a method (with arguments) on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = slice.call(arguments, 2);
+ var isFunc = _.isFunction(method);
+ return _.map(obj, function(value) {
+ return (isFunc ? method : value[method]).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of `map`: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Convenience version of a common use case of `filter`: selecting only objects
+ // containing specific `key:value` pairs.
+ _.where = function(obj, attrs, first) {
+ if (_.isEmpty(attrs)) return first ? null : [];
+ return _[first ? 'find' : 'filter'](obj, function(value) {
+ for (var key in attrs) {
+ if (attrs[key] !== value[key]) return false;
+ }
+ return true;
+ });
+ };
+
+ // Convenience version of a common use case of `find`: getting the first object
+ // containing specific `key:value` pairs.
+ _.findWhere = function(obj, attrs) {
+ return _.where(obj, attrs, true);
+ };
+
+ // Return the maximum element or (element-based computation).
+ // Can't optimize arrays of integers longer than 65,535 elements.
+ // See: https://bugs.webkit.org/show_bug.cgi?id=80797
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.max.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return -Infinity;
+ var result = {computed : -Infinity, value: -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) {
+ return Math.min.apply(Math, obj);
+ }
+ if (!iterator && _.isEmpty(obj)) return Infinity;
+ var result = {computed : Infinity, value: Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Shuffle an array.
+ _.shuffle = function(obj) {
+ var rand;
+ var index = 0;
+ var shuffled = [];
+ each(obj, function(value) {
+ rand = _.random(index++);
+ shuffled[index - 1] = shuffled[rand];
+ shuffled[rand] = value;
+ });
+ return shuffled;
+ };
+
+ // An internal function to generate lookup iterators.
+ var lookupIterator = function(value) {
+ return _.isFunction(value) ? value : function(obj){ return obj[value]; };
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, value, context) {
+ var iterator = lookupIterator(value);
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ index : index,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria;
+ var b = right.criteria;
+ if (a !== b) {
+ if (a > b || a === void 0) return 1;
+ if (a < b || b === void 0) return -1;
+ }
+ return left.index < right.index ? -1 : 1;
+ }), 'value');
+ };
+
+ // An internal function used for aggregate "group by" operations.
+ var group = function(obj, value, context, behavior) {
+ var result = {};
+ var iterator = lookupIterator(value || _.identity);
+ each(obj, function(value, index) {
+ var key = iterator.call(context, value, index, obj);
+ behavior(result, key, value);
+ });
+ return result;
+ };
+
+ // Groups the object's values by a criterion. Pass either a string attribute
+ // to group by, or a function that returns the criterion.
+ _.groupBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key, value) {
+ (_.has(result, key) ? result[key] : (result[key] = [])).push(value);
+ });
+ };
+
+ // Counts instances of an object that group by a certain criterion. Pass
+ // either a string attribute to count by, or a function that returns the
+ // criterion.
+ _.countBy = function(obj, value, context) {
+ return group(obj, value, context, function(result, key) {
+ if (!_.has(result, key)) result[key] = 0;
+ result[key]++;
+ });
+ };
+
+ // Use a comparator function to figure out the smallest index at which
+ // an object should be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator, context) {
+ iterator = iterator == null ? _.identity : lookupIterator(iterator);
+ var value = iterator.call(context, obj);
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Safely convert anything iterable into a real, live array.
+ _.toArray = function(obj) {
+ if (!obj) return [];
+ if (_.isArray(obj)) return slice.call(obj);
+ if (obj.length === +obj.length) return _.map(obj, _.identity);
+ return _.values(obj);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ if (obj == null) return 0;
+ return (obj.length === +obj.length) ? obj.length : _.keys(obj).length;
+ };
+
+ // Array Functions
+ // ---------------
+
+ // Get the first element of an array. Passing **n** will return the first N
+ // values in the array. Aliased as `head` and `take`. The **guard** check
+ // allows it to work with `_.map`.
+ _.first = _.head = _.take = function(array, n, guard) {
+ if (array == null) return void 0;
+ return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the last entry of the array. Especially useful on
+ // the arguments object. Passing **n** will return all the values in
+ // the array, excluding the last N. The **guard** check allows it to work with
+ // `_.map`.
+ _.initial = function(array, n, guard) {
+ return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
+ };
+
+ // Get the last element of an array. Passing **n** will return the last N
+ // values in the array. The **guard** check allows it to work with `_.map`.
+ _.last = function(array, n, guard) {
+ if (array == null) return void 0;
+ if ((n != null) && !guard) {
+ return slice.call(array, Math.max(array.length - n, 0));
+ } else {
+ return array[array.length - 1];
+ }
+ };
+
+ // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+ // Especially useful on the arguments object. Passing an **n** will return
+ // the rest N values in the array. The **guard**
+ // check allows it to work with `_.map`.
+ _.rest = _.tail = _.drop = function(array, n, guard) {
+ return slice.call(array, (n == null) || guard ? 1 : n);
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, _.identity);
+ };
+
+ // Internal implementation of a recursive `flatten` function.
+ var flatten = function(input, shallow, output) {
+ each(input, function(value) {
+ if (_.isArray(value)) {
+ shallow ? push.apply(output, value) : flatten(value, shallow, output);
+ } else {
+ output.push(value);
+ }
+ });
+ return output;
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array, shallow) {
+ return flatten(array, shallow, []);
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ return _.difference(array, slice.call(arguments, 1));
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ // Aliased as `unique`.
+ _.uniq = _.unique = function(array, isSorted, iterator, context) {
+ if (_.isFunction(isSorted)) {
+ context = iterator;
+ iterator = isSorted;
+ isSorted = false;
+ }
+ var initial = iterator ? _.map(array, iterator, context) : array;
+ var results = [];
+ var seen = [];
+ each(initial, function(value, index) {
+ if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) {
+ seen.push(value);
+ results.push(array[index]);
+ }
+ });
+ return results;
+ };
+
+ // Produce an array that contains the union: each distinct element from all of
+ // the passed-in arrays.
+ _.union = function() {
+ return _.uniq(concat.apply(ArrayProto, arguments));
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersection = function(array) {
+ var rest = slice.call(arguments, 1);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Take the difference between one array and a number of other arrays.
+ // Only the elements present in just the first array will remain.
+ _.difference = function(array) {
+ var rest = concat.apply(ArrayProto, slice.call(arguments, 1));
+ return _.filter(array, function(value){ return !_.contains(rest, value); });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = slice.call(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) {
+ results[i] = _.pluck(args, "" + i);
+ }
+ return results;
+ };
+
+ // Converts lists into objects. Pass either a single array of `[key, value]`
+ // pairs, or two parallel arrays of the same length -- one of keys, and one of
+ // the corresponding values.
+ _.object = function(list, values) {
+ if (list == null) return {};
+ var result = {};
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (values) {
+ result[list[i]] = values[i];
+ } else {
+ result[list[i][0]] = list[i][1];
+ }
+ }
+ return result;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
+ // we need this function. Return the position of the first occurrence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to **ECMAScript 5**'s native `indexOf` if available.
+ // If the array is large and already in sort order, pass `true`
+ // for **isSorted** to use binary search.
+ _.indexOf = function(array, item, isSorted) {
+ if (array == null) return -1;
+ var i = 0, l = array.length;
+ if (isSorted) {
+ if (typeof isSorted == 'number') {
+ i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted);
+ } else {
+ i = _.sortedIndex(array, item);
+ return array[i] === item ? i : -1;
+ }
+ }
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted);
+ for (; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
+ _.lastIndexOf = function(array, item, from) {
+ if (array == null) return -1;
+ var hasIndex = from != null;
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) {
+ return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item);
+ }
+ var i = (hasIndex ? from : array.length);
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python `range()` function. See
+ // [the Python documentation](http://docs.python.org/library/functions.html#range).
+ _.range = function(start, stop, step) {
+ if (arguments.length <= 1) {
+ stop = start || 0;
+ start = 0;
+ }
+ step = arguments[2] || 1;
+
+ var len = Math.max(Math.ceil((stop - start) / step), 0);
+ var idx = 0;
+ var range = new Array(len);
+
+ while(idx < len) {
+ range[idx++] = start;
+ start += step;
+ }
+
+ return range;
+ };
+
+ // Function (ahem) Functions
+ // ------------------
+
+ // Create a function bound to a given object (assigning `this`, and arguments,
+ // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+ // available.
+ _.bind = function(func, context) {
+ if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+ var args = slice.call(arguments, 2);
+ return function() {
+ return func.apply(context, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Partially apply a function by creating a version that has had some of its
+ // arguments pre-filled, without changing its dynamic `this` context.
+ _.partial = function(func) {
+ var args = slice.call(arguments, 1);
+ return function() {
+ return func.apply(this, args.concat(slice.call(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = slice.call(arguments, 1);
+ if (funcs.length === 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Memoize an expensive function by storing its results.
+ _.memoize = function(func, hasher) {
+ var memo = {};
+ hasher || (hasher = _.identity);
+ return function() {
+ var key = hasher.apply(this, arguments);
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ };
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+ };
+
+ // Returns a function, that, when invoked, will only be triggered at most once
+ // during a given window of time.
+ _.throttle = function(func, wait) {
+ var context, args, timeout, result;
+ var previous = 0;
+ var later = function() {
+ previous = new Date;
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date;
+ var remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ };
+
+ // Returns a function, that, as long as it continues to be invoked, will not
+ // be triggered. The function will be called after it stops being called for
+ // N milliseconds. If `immediate` is passed, trigger the function on the
+ // leading edge, instead of the trailing.
+ _.debounce = function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+ if (!immediate) result = func.apply(context, args);
+ };
+ var callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) result = func.apply(context, args);
+ return result;
+ };
+ };
+
+ // Returns a function that will be executed at most one time, no matter how
+ // often you call it. Useful for lazy initialization.
+ _.once = function(func) {
+ var ran = false, memo;
+ return function() {
+ if (ran) return memo;
+ ran = true;
+ memo = func.apply(this, arguments);
+ func = null;
+ return memo;
+ };
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func];
+ push.apply(args, arguments);
+ return wrapper.apply(this, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments;
+ for (var i = funcs.length - 1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // Returns a function that will only be executed after being called N times.
+ _.after = function(times, func) {
+ if (times <= 0) return func();
+ return function() {
+ if (--times < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ };
+
+ // Object Functions
+ // ----------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to **ECMAScript 5**'s native `Object.keys`
+ _.keys = nativeKeys || function(obj) {
+ if (obj !== Object(obj)) throw new TypeError('Invalid object');
+ var keys = [];
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ var values = [];
+ for (var key in obj) if (_.has(obj, key)) values.push(obj[key]);
+ return values;
+ };
+
+ // Convert an object into a list of `[key, value]` pairs.
+ _.pairs = function(obj) {
+ var pairs = [];
+ for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]);
+ return pairs;
+ };
+
+ // Invert the keys and values of an object. The values must be serializable.
+ _.invert = function(obj) {
+ var result = {};
+ for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key;
+ return result;
+ };
+
+ // Return a sorted list of the function names available on the object.
+ // Aliased as `methods`
+ _.functions = _.methods = function(obj) {
+ var names = [];
+ for (var key in obj) {
+ if (_.isFunction(obj[key])) names.push(key);
+ }
+ return names.sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Return a copy of the object only containing the whitelisted properties.
+ _.pick = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ each(keys, function(key) {
+ if (key in obj) copy[key] = obj[key];
+ });
+ return copy;
+ };
+
+ // Return a copy of the object without the blacklisted properties.
+ _.omit = function(obj) {
+ var copy = {};
+ var keys = concat.apply(ArrayProto, slice.call(arguments, 1));
+ for (var key in obj) {
+ if (!_.contains(keys, key)) copy[key] = obj[key];
+ }
+ return copy;
+ };
+
+ // Fill in a given object with default properties.
+ _.defaults = function(obj) {
+ each(slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] == null) obj[prop] = source[prop];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (!_.isObject(obj)) return obj;
+ return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in
+ // order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Internal recursive comparison function for `isEqual`.
+ var eq = function(a, b, aStack, bStack) {
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
+ if (a === b) return a !== 0 || 1 / a == 1 / b;
+ // A strict comparison is necessary because `null == undefined`.
+ if (a == null || b == null) return a === b;
+ // Unwrap any wrapped objects.
+ if (a instanceof _) a = a._wrapped;
+ if (b instanceof _) b = b._wrapped;
+ // Compare `[[Class]]` names.
+ var className = toString.call(a);
+ if (className != toString.call(b)) return false;
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') return false;
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) return bStack[length] == b;
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0, result = true;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+ }
+ }
+ } else {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ _.isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (_.has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (_.has(b, key) && !(size--)) break;
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+ return result;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ return eq(a, b, [], []);
+ };
+
+ // Is a given array, string, or object empty?
+ // An "empty" object has no enumerable own-properties.
+ _.isEmpty = function(obj) {
+ if (obj == null) return true;
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (_.has(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType === 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return toString.call(obj) == '[object Array]';
+ };
+
+ // Is a given variable an object?
+ _.isObject = function(obj) {
+ return obj === Object(obj);
+ };
+
+ // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+ each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+ _['is' + name] = function(obj) {
+ return toString.call(obj) == '[object ' + name + ']';
+ };
+ });
+
+ // Define a fallback version of the method in browsers (ahem, IE), where
+ // there isn't any inspectable "Arguments" type.
+ if (!_.isArguments(arguments)) {
+ _.isArguments = function(obj) {
+ return !!(obj && _.has(obj, 'callee'));
+ };
+ }
+
+ // Optimize `isFunction` if appropriate.
+ if (typeof (/./) !== 'function') {
+ _.isFunction = function(obj) {
+ return typeof obj === 'function';
+ };
+ }
+
+ // Is a given object a finite number?
+ _.isFinite = function(obj) {
+ return isFinite(obj) && !isNaN(parseFloat(obj));
+ };
+
+ // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && obj != +obj;
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ // Shortcut function for checking if an object has a given property directly
+ // on itself (in other words, not on a prototype).
+ _.has = function(obj, key) {
+ return hasOwnProperty.call(obj, key);
+ };
+
+ // Utility Functions
+ // -----------------
+
+ // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function **n** times.
+ _.times = function(n, iterator, context) {
+ var accum = Array(n);
+ for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i);
+ return accum;
+ };
+
+ // Return a random integer between min and max (inclusive).
+ _.random = function(min, max) {
+ if (max == null) {
+ max = min;
+ min = 0;
+ }
+ return min + Math.floor(Math.random() * (max - min + 1));
+ };
+
+ // List of HTML entities for escaping.
+ var entityMap = {
+ escape: {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ '/': '/'
+ }
+ };
+ entityMap.unescape = _.invert(entityMap.escape);
+
+ // Regexes containing the keys and values listed immediately above.
+ var entityRegexes = {
+ escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'),
+ unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g')
+ };
+
+ // Functions for escaping and unescaping strings to/from HTML interpolation.
+ _.each(['escape', 'unescape'], function(method) {
+ _[method] = function(string) {
+ if (string == null) return '';
+ return ('' + string).replace(entityRegexes[method], function(match) {
+ return entityMap[method][match];
+ });
+ };
+ });
+
+ // If the value of the named property is a function then invoke it;
+ // otherwise, return it.
+ _.result = function(object, property) {
+ if (object == null) return null;
+ var value = object[property];
+ return _.isFunction(value) ? value.call(object) : value;
+ };
+
+ // Add your own custom functions to the Underscore object.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ var func = _[name] = obj[name];
+ _.prototype[name] = function() {
+ var args = [this._wrapped];
+ push.apply(args, arguments);
+ return result.call(this, func.apply(_, args));
+ };
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = ++idCounter + '';
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ evaluate : /<%([\s\S]+?)%>/g,
+ interpolate : /<%=([\s\S]+?)%>/g,
+ escape : /<%-([\s\S]+?)%>/g
+ };
+
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /(.)^/;
+
+ // Certain characters need to be escaped so that they can be put into a
+ // string literal.
+ var escapes = {
+ "'": "'",
+ '\\': '\\',
+ '\r': 'r',
+ '\n': 'n',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+ // JavaScript micro-templating, similar to John Resig's implementation.
+ // Underscore templating handles arbitrary delimiters, preserves whitespace,
+ // and correctly escapes quotes within interpolated code.
+ _.template = function(text, data, settings) {
+ var render;
+ settings = _.defaults({}, settings, _.templateSettings);
+
+ // Combine delimiters into one regular expression via alternation.
+ var matcher = new RegExp([
+ (settings.escape || noMatch).source,
+ (settings.interpolate || noMatch).source,
+ (settings.evaluate || noMatch).source
+ ].join('|') + '|$', 'g');
+
+ // Compile the template source, escaping string literals appropriately.
+ var index = 0;
+ var source = "__p+='";
+ text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+ source += text.slice(index, offset)
+ .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+ if (escape) {
+ source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+ }
+ if (interpolate) {
+ source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+ }
+ if (evaluate) {
+ source += "';\n" + evaluate + "\n__p+='";
+ }
+ index = offset + match.length;
+ return match;
+ });
+ source += "';\n";
+
+ // If a variable is not specified, place data values in local scope.
+ if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+ source = "var __t,__p='',__j=Array.prototype.join," +
+ "print=function(){__p+=__j.call(arguments,'');};\n" +
+ source + "return __p;\n";
+
+ try {
+ render = new Function(settings.variable || 'obj', '_', source);
+ } catch (e) {
+ e.source = source;
+ throw e;
+ }
+
+ if (data) return render(data, _);
+ var template = function(data) {
+ return render.call(this, data, _);
+ };
+
+ // Provide the compiled function source as a convenience for precompilation.
+ template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+ return template;
+ };
+
+ // Add a "chain" function, which will delegate to the wrapper.
+ _.chain = function(obj) {
+ return _(obj).chain();
+ };
+
+ // OOP
+ // ---------------
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj) {
+ return this._chain ? _(obj).chain() : obj;
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ var obj = this._wrapped;
+ method.apply(obj, arguments);
+ if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0];
+ return result.call(this, obj);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ _.prototype[name] = function() {
+ return result.call(this, method.apply(this._wrapped, arguments));
+ };
+ });
+
+ _.extend(_.prototype, {
+
+ // Start chaining a wrapped Underscore object.
+ chain: function() {
+ this._chain = true;
+ return this;
+ },
+
+ // Extracts the result from a wrapped and chained object.
+ value: function() {
+ return this._wrapped;
+ }
+
+ });
+
+}).call(this);
diff --git a/backend/app/assets/javascripts/spree/backend/user_picker.js b/backend/app/assets/javascripts/spree/backend/user_picker.js
new file mode 100644
index 00000000000..2fdc768e0bf
--- /dev/null
+++ b/backend/app/assets/javascripts/spree/backend/user_picker.js
@@ -0,0 +1,40 @@
+$.fn.userAutocomplete = function () {
+ 'use strict';
+
+ this.select2({
+ minimumInputLength: 1,
+ multiple: true,
+ initSelection: function (element, callback) {
+ $.get(Spree.routes.user_search, {
+ ids: element.val()
+ }, function (data) {
+ callback(data);
+ });
+ },
+ ajax: {
+ url: Spree.routes.user_search,
+ datatype: 'json',
+ data: function (term) {
+ return {
+ q: term,
+ token: Spree.api_key
+ };
+ },
+ results: function (data) {
+ return {
+ results: data
+ };
+ }
+ },
+ formatResult: function (user) {
+ return user.email;
+ },
+ formatSelection: function (user) {
+ return user.email;
+ }
+ });
+};
+
+$(document).ready(function () {
+ $('.user_picker').userAutocomplete();
+});
diff --git a/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee.erb b/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee.erb
new file mode 100644
index 00000000000..700d3ad552a
--- /dev/null
+++ b/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee.erb
@@ -0,0 +1,37 @@
+# variant autocompletion
+$(document).ready ->
+ if $("#variant_autocomplete_template").length > 0
+ window.variantTemplate = Handlebars.compile($("#variant_autocomplete_template").text())
+ window.variantStockTemplate = Handlebars.compile($("#variant_autocomplete_stock_template").text())
+ window.variantLineItemTemplate = Handlebars.compile($("#variant_line_items_autocomplete_stock_template").text())
+ return
+
+formatVariantResult = (variant) ->
+ variant.image = variant.images[0].mini_url if variant["images"][0] isnt `undefined` and variant["images"][0].mini_url isnt `undefined`
+ variantTemplate variant: variant
+
+$.fn.variantAutocomplete = ->
+ @select2
+ placeholder: Spree.translations.variant_placeholder
+ minimumInputLength: 3
+ initSelection: (element, callback) ->
+ $.get Spree.routes.variants_search + "/" + element.val(), {}, (data) ->
+ callback data
+ ajax:
+ url: Spree.url(Spree.routes.variants_api)
+ datatype: "json"
+ data: (term, page) ->
+ q:
+ product_name_or_sku_cont: term
+ token: Spree.api_key
+
+ results: (data, page) ->
+ window.variants = data["variants"]
+ results: data["variants"]
+
+ formatResult: formatVariantResult
+ formatSelection: (variant) ->
+ if !!variant.options_text
+ variant.name + " (#{variant.options_text})"
+ else
+ variant.name
diff --git a/backend/app/assets/javascripts/spree/backend/variant_management.js.coffee b/backend/app/assets/javascripts/spree/backend/variant_management.js.coffee
new file mode 100644
index 00000000000..46990003908
--- /dev/null
+++ b/backend/app/assets/javascripts/spree/backend/variant_management.js.coffee
@@ -0,0 +1,10 @@
+jQuery ->
+ $('.track_inventory_checkbox').on 'click', ->
+ $(@).siblings('.variant_track_inventory').val($(@).is(':checked'))
+ $(@).parent('form').submit()
+ $('.toggle_variant_track_inventory').on 'submit', ->
+ $.ajax
+ type: @method
+ url: @action
+ data: $(@).serialize()
+ false
diff --git a/core/app/assets/javascripts/admin/zone.js.coffee b/backend/app/assets/javascripts/spree/backend/zone.js.coffee
similarity index 90%
rename from core/app/assets/javascripts/admin/zone.js.coffee
rename to backend/app/assets/javascripts/spree/backend/zone.js.coffee
index 2c70c78828a..885521190d7 100644
--- a/core/app/assets/javascripts/admin/zone.js.coffee
+++ b/backend/app/assets/javascripts/spree/backend/zone.js.coffee
@@ -1,14 +1,18 @@
$ ->
- if ($ '#country_based').is(':checked')
- show_country()
- else
- show_state()
($ '#country_based').click ->
show_country()
($ '#state_based').click ->
show_state()
+ if ($ '#country_based').is(':checked')
+ show_country()
+ else if ($ '#state_based').is(':checked')
+ show_state()
+ else
+ show_state()
+ ($ '#state_based').click()
+
show_country = ->
($ '#state_members :input').each ->
diff --git a/backend/app/assets/javascripts/spree/frontend/backend.js b/backend/app/assets/javascripts/spree/frontend/backend.js
new file mode 100644
index 00000000000..e44d478def9
--- /dev/null
+++ b/backend/app/assets/javascripts/spree/frontend/backend.js
@@ -0,0 +1 @@
+// Placeholder for backend dummy application
diff --git a/backend/app/assets/stylesheets/spree/backend.css b/backend/app/assets/stylesheets/spree/backend.css
new file mode 100644
index 00000000000..eeb84ea1520
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend.css
@@ -0,0 +1,17 @@
+/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+
+ *= require jquery.alerts/jquery.alerts
+ *= require jquery.alerts/jquery.alerts.spree
+ *= require responsive-tables
+ *= require normalize
+ *= require skeleton
+ *= require jquery-ui/datepicker
+ *= require jquery-ui/autocomplete
+ *= require jquery.powertip
+ *= require select2
+
+ *= require spree/backend/spree_admin
+*/
diff --git a/core/app/assets/stylesheets/admin/components/_actions.scss b/backend/app/assets/stylesheets/spree/backend/components/_actions.scss
similarity index 91%
rename from core/app/assets/stylesheets/admin/components/_actions.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_actions.scss
index 811ef0af236..1aeac5e630f 100644
--- a/core/app/assets/stylesheets/admin/components/_actions.scss
+++ b/backend/app/assets/stylesheets/spree/backend/components/_actions.scss
@@ -8,7 +8,7 @@ table tbody tr {
}
}
- &.action-remove td, &.action-void td {
+ &.action-remove td, &.action-void td {
text-decoration: line-through;
&.actions {
@@ -28,4 +28,4 @@ table tbody tr {
td.actions {
background-color: transparent !important;
}
-}
\ No newline at end of file
+}
diff --git a/core/app/assets/stylesheets/admin/components/_date-picker.scss b/backend/app/assets/stylesheets/spree/backend/components/_date-picker.scss
similarity index 95%
rename from core/app/assets/stylesheets/admin/components/_date-picker.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_date-picker.scss
index 90ba9b2579c..09149b69389 100644
--- a/core/app/assets/stylesheets/admin/components/_date-picker.scss
+++ b/backend/app/assets/stylesheets/spree/backend/components/_date-picker.scss
@@ -1,6 +1,6 @@
.date-range-filter {
.range-divider {
- padding: 0 6px 0 5px;
+ padding: 0;
}
input.datepicker {
width: 96px !important;
@@ -9,6 +9,7 @@
#ui-datepicker-div {
@include border-radius($border-radius);
+
border-color: $color-3;
padding: 0;
margin-top: 10px;
@@ -23,6 +24,7 @@
margin-top: -10px;
left: 25px;
z-index: 1;
+ display: block;
}
.ui-datepicker-header {
@@ -47,13 +49,15 @@
}
.ui-icon {
+ @extend [class^="icon-"]:before;
+ @extend .fa;
+
background-image: none;
text-indent: 0;
color: $color-1;
width: 10px;
margin-left: -5px;
- @extend [class^="icon-"]:before;
-
+
&:hover {
color: very-light($color-2, 25);
}
@@ -63,14 +67,15 @@
left: 0;
.ui-icon {
- @extend .icon-arrow-left;
+ @extend .fa-arrow-left;
}
}
+
.ui-datepicker-next {
right: 0;
.ui-icon {
- @extend .icon-arrow-right;
+ @extend .fa-arrow-right;
}
&:hover {
diff --git a/core/app/assets/stylesheets/admin/components/_messages.scss b/backend/app/assets/stylesheets/spree/backend/components/_messages.scss
similarity index 81%
rename from core/app/assets/stylesheets/admin/components/_messages.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_messages.scss
index 8300434d62c..15397ea513c 100644
--- a/core/app/assets/stylesheets/admin/components/_messages.scss
+++ b/backend/app/assets/stylesheets/spree/backend/components/_messages.scss
@@ -25,7 +25,7 @@
}
}
-.flash {
+.flash, .alert {
position: fixed;
top: 0;
left: 0;
@@ -40,4 +40,15 @@
&.notice { background-color: rgba($color-notice, 0.8) }
&.success { background-color: rgba($color-success, 0.8) }
&.error { background-color: rgba($color-error, 0.8) }
+}
+
+.alert {
+ position: relative;
+ font-weight: normal !important;
+
+ a {
+ text-decoration: underline;
+ }
+
+ &.error a { color: very-light($color-error, 10) }
}
\ No newline at end of file
diff --git a/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss b/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss
new file mode 100644
index 00000000000..41b7488c9ac
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/components/_navigation.scss
@@ -0,0 +1,169 @@
+// Navigation
+//---------------------------------------------------
+.inline-menu {
+ margin: 0;
+ -webkit-margin-before: 0;
+ -webkit-padding-start: 0;
+}
+
+nav.menu {
+ ul {
+ list-style: none;
+
+ li {
+ a {
+ padding: 10px 0;
+ display: block;
+ position: relative;
+ text-align: left;
+ border: 1px solid transparent;
+ text-transform: uppercase;
+ font-weight: 600;
+ font-size: 90%;
+ }
+
+ &.active a {
+ color: $color-2;
+ border-left-width: 0;
+ border-bottom-color: $color-2;
+ }
+
+ &:hover a {
+ color: $color-2;
+ }
+ }
+ }
+}
+
+[data-hook="admin_login_navigation_bar"] {
+ ul {
+ text-align: right;
+
+ li {
+ padding: 5px 0 5px 10px;
+ text-align: right;
+ font-size: 90%;
+ color: $color-link;
+ margin-top: 8px;
+
+ &[data-hook="user-logged-in-as"] {
+ width: 50%;
+ color: $color-body-text;
+ }
+
+ &.fa:before {
+ padding-right: 4px;
+ }
+
+ &:hover {
+ i {
+ color: $color-2;
+ }
+ }
+ }
+ }
+}
+
+#admin-menu {
+ background-color: $color-3;
+
+ ul{
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ }
+
+ li {
+ display: table-cell;
+
+ a {
+ display: block;
+ padding: 25px 15px;
+ color: $color-1 !important;
+ text-transform: uppercase;
+ position: relative;
+ text-align: center;
+
+ @media(max-width: 1009px) {
+ top: -3px;
+ margin-bottom: -5px;
+ }
+
+ i {
+ display: inline;
+ }
+
+ &:hover {
+ background-color: $color-2;
+
+ &:after {
+ content: '';
+ position: absolute;
+ border-left: 10px solid transparent;
+ border-right: 10px solid transparent;
+ border-top: 5px solid $color-2;
+ bottom: 0px;
+ margin-bottom: -5px;
+ left: 50%;
+ margin-left: -10px;
+ z-index: 1;
+ }
+ }
+
+ span.text {
+ font-weight: 600;
+ }
+ }
+
+ .dropdown {
+ width: 300px;
+ background-color: $color-3;
+ width: 200px;
+ z-index: 100000;
+
+ > li {
+ width: 200px !important;
+
+ a:after {
+ display: none;
+ }
+ }
+ }
+
+ &.selected a {
+ @extend a:hover;
+ }
+ }
+}
+
+#sub-menu {
+ background-color: $color-2;
+ padding-bottom: 0;
+
+ li {
+ a {
+ display: block;
+ padding: 12px 20px;
+ color: $color-1;
+ text-align: center;
+ text-transform: uppercase;
+ position: relative;
+ font-size: 85%;
+ }
+
+ &.selected a, a:hover {
+ &:after {
+ content: '';
+ position: absolute;
+ border-left: 10px solid transparent;
+ border-right: 10px solid transparent;
+ border-top: 5px solid $color-2;
+ bottom: 0px;
+ margin-bottom: -5px;
+ left: 50%;
+ margin-left: -10px;
+ z-index: 1;
+ }
+ }
+ }
+}
diff --git a/core/app/assets/stylesheets/admin/components/_pagination.scss b/backend/app/assets/stylesheets/spree/backend/components/_pagination.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/components/_pagination.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_pagination.scss
diff --git a/core/app/assets/stylesheets/admin/components/_product_autocomplete.scss b/backend/app/assets/stylesheets/spree/backend/components/_product_autocomplete.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/components/_product_autocomplete.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_product_autocomplete.scss
diff --git a/core/app/assets/stylesheets/admin/components/_progress.scss b/backend/app/assets/stylesheets/spree/backend/components/_progress.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/components/_progress.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_progress.scss
diff --git a/backend/app/assets/stylesheets/spree/backend/components/_sidebar.scss b/backend/app/assets/stylesheets/spree/backend/components/_sidebar.scss
new file mode 100644
index 00000000000..960de96ab01
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/components/_sidebar.scss
@@ -0,0 +1,26 @@
+// Sidebar
+//---------------------------------------------------
+#sidebar {
+ overflow: visible;
+ border-top: 1px solid $color-border;
+ margin-top: 17px;
+
+ .sidebar-title {
+ color: $color-2;
+ text-transform: uppercase;
+ text-align: center;
+ font-size: 14px;
+ font-weight: 600;
+
+ > span {
+ display: inline;
+ background: #fff;
+ padding: 5px 10px;
+ position: relative;
+ top: -14px;
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ }
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/components/_states.scss b/backend/app/assets/stylesheets/spree/backend/components/_states.scss
new file mode 100644
index 00000000000..97ad366abfb
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/components/_states.scss
@@ -0,0 +1,34 @@
+.state {
+ text-transform: uppercase;
+ font-size: 80%;
+ font-weight: 600;
+
+ &:before {
+ content: '';
+ position: relative;
+ display: inline-block;
+ margin-right: 3px;
+ border-radius: $body-font-size/2;
+ width: $body-font-size - 4px;
+ height: $body-font-size - 4px;
+ }
+
+ @each $state in $states {
+ &.#{$state}:before {
+ background-color: get-value($states, $states-bg-colors, $state);
+
+ // &, a {
+ // color: get-value($states, $states-text-colors, $state);
+ // }
+ }
+ }
+}
+
+table tbody tr {
+ &[class*="state"] td:first-child {
+ border-left-width: 3px;
+ }
+ &.state-complete td:first-child { border-left-color: $color-success }
+ &.state-cart td:first-child { border-left-color: very-light($color-notice, 6) }
+ &.state-canceled td:first-child { border-left-color: $color-error }
+}
diff --git a/core/app/assets/stylesheets/admin/components/_table-filter.scss b/backend/app/assets/stylesheets/spree/backend/components/_table-filter.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/components/_table-filter.scss
rename to backend/app/assets/stylesheets/spree/backend/components/_table-filter.scss
diff --git a/core/app/assets/stylesheets/admin/globals/_functions.scss b/backend/app/assets/stylesheets/spree/backend/globals/_functions.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/globals/_functions.scss
rename to backend/app/assets/stylesheets/spree/backend/globals/_functions.scss
diff --git a/core/app/assets/stylesheets/admin/globals/_mixins.scss b/backend/app/assets/stylesheets/spree/backend/globals/_mixins.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/globals/_mixins.scss
rename to backend/app/assets/stylesheets/spree/backend/globals/_mixins.scss
diff --git a/backend/app/assets/stylesheets/spree/backend/globals/_variables.scss b/backend/app/assets/stylesheets/spree/backend/globals/_variables.scss
new file mode 100644
index 00000000000..414c35a65a2
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/globals/_variables.scss
@@ -0,0 +1,171 @@
+// -------------------------------------------------------------
+// Variables used in all other files
+//--------------------------------------------------------------
+
+// Fonts
+//--------------------------------------------------------------
+$base-font-family: "Open Sans", "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif !default;
+
+// Colors
+//--------------------------------------------------------------
+
+// Basic color palette for admin
+$color-1: #FFFFFF !default; // White
+$color-2: #9FC820 !default; // Green
+$color-3: #5498DA !default; // Light Blue
+$color-4: #6788A2 !default; // Dark Blue
+$color-5: #C60F13 !default; // Red
+$color-6: #FF9300 !default; // Yellow
+
+// Body base colors
+$color-body-bg: $color-1 !default;
+$color-body-text: $color-4 !default;
+$color-headers: $color-4 !default;
+$color-link: $color-3 !default;
+$color-link-hover: $color-2 !default;
+$color-link-active: $color-2 !default;
+$color-link-focus: $color-2 !default;
+$color-link-visited: $color-3 !default;
+$color-border: very-light($color-3, 12) !default;
+
+// Basic flash colors
+$color-success: $color-2 !default;
+$color-notice: $color-6 !default;
+$color-error: $color-5 !default;
+
+// Table colors
+$color-tbl-odd: $color-1 !default;
+$color-tbl-even: very-light($color-3, 4) !default;
+$color-tbl-thead: very-light($color-3, 4) !default;
+
+// Button colors
+$color-btn-bg: $color-3 !default;
+$color-btn-text: $color-1 !default;
+$color-btn-hover-bg: $color-2 !default;
+$color-btn-hover-text: $color-1 !default;
+
+// Actions colors
+$color-action-edit-bg: very-light($color-success, 5 ) !default;
+$color-action-edit-brd: very-light($color-success, 20 ) !default;
+$color-action-clone-bg: very-light($color-notice, 5 ) !default;
+$color-action-clone-brd: very-light($color-notice, 15 ) !default;
+$color-action-remove-bg: very-light($color-error, 5 ) !default;
+$color-action-remove-brd: very-light($color-error, 10 ) !default;
+$color-action-void-bg: very-light($color-error, 10 ) !default;
+$color-action-void-brd: very-light($color-error, 20 ) !default;
+$color-action-cancel-bg: very-light($color-notice, 10 ) !default;
+$color-action-cancel-brd: very-light($color-notice, 20 ) !default;
+$color-action-capture-bg: very-light($color-success, 5 ) !default;
+$color-action-capture-brd: very-light($color-success, 20 ) !default;
+$color-action-save-bg: very-light($color-success, 5 ) !default;
+$color-action-save-brd: very-light($color-success, 20 ) !default;
+$color-action-mail-bg: very-light($color-success, 5 ) !default;
+$color-action-mail-brd: very-light($color-success, 20 ) !default;
+
+// Select2 select field colors
+$color-sel-bg: $color-3 !default;
+$color-sel-text: $color-1 !default;
+$color-sel-hover-bg: $color-2 !default;
+$color-sel-hover-text: $color-1 !default;
+
+// Text inputs colors
+$color-txt-brd: $color-border !default;
+$color-txt-text: $color-3 !default;
+$color-txt-hover-brd: $color-2 !default;
+
+// States label colors
+$color-ste-complete-bg: $color-success !default;
+$color-ste-complete-text: $color-1 !default;
+$color-ste-completed-bg: $color-success !default;
+$color-ste-completed-text: $color-1 !default;
+$color-ste-sold-bg: $color-success !default;
+$color-ste-sold-text: $color-1 !default;
+$color-ste-pending-bg: $color-notice !default;
+$color-ste-pending-text: $color-1 !default;
+$color-ste-awaiting_return-bg: $color-notice !default;
+$color-ste-awaiting_return-text: $color-1 !default;
+$color-ste-returned-bg: $color-notice !default;
+$color-ste-returned-text: $color-1 !default;
+$color-ste-credit_owed-bg: $color-notice !default;
+$color-ste-credit_owed-text: $color-1 !default;
+$color-ste-paid-bg: $color-success !default;
+$color-ste-paid-text: $color-1 !default;
+$color-ste-shipped-bg: $color-success !default;
+$color-ste-shipped-text: $color-1 !default;
+$color-ste-balance_due-bg: $color-notice !default;
+$color-ste-balance_due-text: $color-1 !default;
+$color-ste-backorder-bg: $color-notice !default;
+$color-ste-backorder-text: $color-1 !default;
+$color-ste-none-bg: $color-error !default;
+$color-ste-none-text: $color-1 !default;
+$color-ste-ready-bg: $color-success !default;
+$color-ste-ready-text: $color-1 !default;
+$color-ste-void-bg: $color-error !default;
+$color-ste-void-text: $color-1 !default;
+$color-ste-canceled-bg: $color-error !default;
+$color-ste-canceled-text: $color-1 !default;
+$color-ste-failed-bg: $color-error !default;
+$color-ste-failed-text: $color-1 !default;
+$color-ste-address-bg: $color-error !default;
+$color-ste-address-text: $color-1 !default;
+$color-ste-checkout-bg: $color-notice !default;
+$color-ste-checkout-text: $color-1 !default;
+$color-ste-cart-bg: $color-notice !default;
+$color-ste-cart-text: $color-1 !default;
+$color-ste-payment-bg: $color-error !default;
+$color-ste-payment-text: $color-1 !default;
+$color-ste-delivery-bg: $color-success !default;
+$color-ste-delivery-text: $color-1 !default;
+$color-ste-confirm-bg: $color-error !default;
+$color-ste-confirm-text: $color-1 !default;
+$color-ste-active-bg: $color-success !default;
+$color-ste-active-text: $color-1 !default;
+$color-ste-inactive-bg: $color-notice !default;
+$color-ste-inactive-text: $color-1 !default;
+$color-ste-considered_risky-bg: $color-error !default;
+$color-ste-considered_risky-text:$color-1 !default;
+$color-ste-considered_safe-bg: $color-success !default;
+$color-ste-considered_safe-text: $color-1 !default;
+$color-ste-success-bg: $color-success !default;
+$color-ste-success-text: $color-1 !default;
+$color-ste-notice-bg: $color-notice !default;
+$color-ste-notice-text: $color-1 !default;
+$color-ste-error-bg: $color-error !default;
+$color-ste-error-text: $color-1 !default;
+
+// Available states
+$states: completed, complete, sold, pending, awaiting_return, returned, credit_owed, paid, shipped, balance_due, backorder, checkout, cart, address,
+delivery, payment, confirm, canceled, failed, ready, void, active, inactive, considered_risky, considered_safe, success, notice, error !default;
+
+$states-bg-colors: $color-ste-completed-bg, $color-ste-complete-bg, $color-ste-sold-bg, $color-ste-pending-bg, $color-ste-awaiting_return-bg,
+$color-ste-returned-bg, $color-ste-credit_owed-bg, $color-ste-paid-bg, $color-ste-shipped-bg, $color-ste-balance_due-bg, $color-ste-backorder-bg,
+$color-ste-checkout-bg, $color-ste-cart-bg, $color-ste-address-bg, $color-ste-delivery-bg, $color-ste-payment-bg, $color-ste-confirm-bg,
+$color-ste-canceled-bg, $color-ste-failed-bg, $color-ste-ready-bg, $color-ste-void-bg, $color-ste-active-bg, $color-ste-inactive-bg, $color-ste-considered_risky-bg,
+$color-ste-considered_safe-bg, $color-ste-success-bg, $color-ste-notice-bg, $color-ste-error-bg !default;
+
+$states-text-colors: $color-ste-completed-text, $color-ste-complete-text, $color-ste-sold-text, $color-ste-pending-text, $color-ste-awaiting_return-text,
+$color-ste-returned-text, $color-ste-credit_owed-text, $color-ste-paid-text, $color-ste-shipped-text, $color-ste-balance_due-text, $color-ste-backorder-text,
+$color-ste-checkout-text, $color-ste-cart-text, $color-ste-address-text, $color-ste-delivery-text, $color-ste-payment-text, $color-ste-confirm-text,
+$color-ste-canceled-text, $color-ste-failed-text, $color-ste-ready-text, $color-ste-void-text, $color-ste-active-text, $color-ste-inactive-text, $color-ste-considered_risky-text,
+$color-ste-considered_safe-text, $color-ste-success-text, $color-ste-notice-text, $color-ste-error-text !default;
+
+// Available actions
+$actions: edit, clone, remove, void, capture, save, cancel, mail !default;
+$actions-bg-colors: $color-action-edit-bg, $color-action-clone-bg, $color-action-remove-bg, $color-action-void-bg, $color-action-capture-bg, $color-action-save-bg, $color-action-cancel-bg, $color-action-mail-bg !default;
+$actions-brd-colors: $color-action-edit-brd, $color-action-clone-brd, $color-action-remove-brd, $color-action-void-brd, $color-action-capture-brd, $color-action-save-brd, $color-action-cancel-brd, $color-action-mail-brd !default;
+
+// Sizes
+//--------------------------------------------------------------
+$body-font-size: 13px !default;
+
+$h6-size: $body-font-size + 2 !default;
+$h5-size: $h6-size + 2 !default;
+$h4-size: $h5-size + 2 !default;
+$h3-size: $h4-size + 2 !default;
+$h2-size: $h3-size + 2 !default;
+$h1-size: $h2-size + 2 !default;
+
+$border-radius: 3px !default;
+
+$font-weight-bold: 600 !default;
+$font-weight-normal: 400 !default;
diff --git a/backend/app/assets/stylesheets/spree/backend/globals/_variables_override.scss b/backend/app/assets/stylesheets/spree/backend/globals/_variables_override.scss
new file mode 100644
index 00000000000..0a49b49d2f5
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/globals/_variables_override.scss
@@ -0,0 +1,7 @@
+/*---------------------------------------------------------
+ Empty file to override variables in user applications.
+
+ To set your own colors, sizes or fonts just override this
+ file in your application and set variables according to
+ globals/_variables.scss file.
+ --------------------------------------------------------- */
diff --git a/core/app/assets/stylesheets/admin/hacks/_ie.scss b/backend/app/assets/stylesheets/spree/backend/hacks/_ie.scss
similarity index 92%
rename from core/app/assets/stylesheets/admin/hacks/_ie.scss
rename to backend/app/assets/stylesheets/spree/backend/hacks/_ie.scss
index 15fc1f2601b..ed71a30318f 100644
--- a/core/app/assets/stylesheets/admin/hacks/_ie.scss
+++ b/backend/app/assets/stylesheets/spree/backend/hacks/_ie.scss
@@ -2,10 +2,10 @@
html.ie {
// Properly align icons in circle
- table td.actions .no-text.icon-edit {
+ table td.actions .no-text.fa-edit {
padding-top: 3px !important;
}
- table td.actions .no-text.icon-envelope-alt {
+ table td.actions .no-text.fa-envelope-alt {
padding-top: 4px !important;
}
@@ -18,7 +18,6 @@ html.ie {
}
.select2-search {
&:before {
- position: relative;
z-index: 10000;
content: '\f002' !important;
}
@@ -70,4 +69,4 @@ html.ie8 {
}
}
-}
\ No newline at end of file
+}
diff --git a/core/app/assets/stylesheets/admin/hacks/_mozilla.scss b/backend/app/assets/stylesheets/spree/backend/hacks/_mozilla.scss
similarity index 88%
rename from core/app/assets/stylesheets/admin/hacks/_mozilla.scss
rename to backend/app/assets/stylesheets/spree/backend/hacks/_mozilla.scss
index a3e37cfacc5..c9523c63701 100644
--- a/core/app/assets/stylesheets/admin/hacks/_mozilla.scss
+++ b/backend/app/assets/stylesheets/spree/backend/hacks/_mozilla.scss
@@ -6,7 +6,7 @@ html.firefox {
}
// Properly align icons in circle
- table td.actions .no-text.icon-edit {
+ table td.actions .no-text.fa-edit {
padding-left: 1px;
}
@@ -22,6 +22,7 @@ html.firefox {
// Fix select2 search input left padding to not overlap search icon
.select2-search input.select2-input {
+ padding-bottom: 12px !important;
padding-left: 25px !important;
}
@@ -29,4 +30,4 @@ html.firefox {
input#image_attachment {
width: 80%;
}
-}
\ No newline at end of file
+}
diff --git a/core/app/assets/stylesheets/admin/hacks/_opera.scss b/backend/app/assets/stylesheets/spree/backend/hacks/_opera.scss
similarity index 83%
rename from core/app/assets/stylesheets/admin/hacks/_opera.scss
rename to backend/app/assets/stylesheets/spree/backend/hacks/_opera.scss
index af8c878ec2d..fbd601f6a1a 100644
--- a/core/app/assets/stylesheets/admin/hacks/_opera.scss
+++ b/backend/app/assets/stylesheets/spree/backend/hacks/_opera.scss
@@ -6,7 +6,7 @@ html.opera {
}
// Properly align icons in circle
- table td.actions .no-text.icon-edit {
+ table td.actions .no-text.fa-edit {
padding-left: 1px;
}
diff --git a/core/app/assets/stylesheets/admin/plugins/_jstree.scss b/backend/app/assets/stylesheets/spree/backend/plugins/_jstree.scss
similarity index 86%
rename from core/app/assets/stylesheets/admin/plugins/_jstree.scss
rename to backend/app/assets/stylesheets/spree/backend/plugins/_jstree.scss
index cc2e70238df..e8ad28a03cd 100644
--- a/core/app/assets/stylesheets/admin/plugins/_jstree.scss
+++ b/backend/app/assets/stylesheets/spree/backend/plugins/_jstree.scss
@@ -2,16 +2,17 @@
> ul, .jstree-icon {
background-image: none;
}
-
+
.jstree-icon {
@extend [class^="icon-"]:before;
+ @extend .fa;
}
- .jstree-open > .jstree-icon {
- @extend .icon-caret-down;
+ .jstree-open > .jstree-icon {
+ @extend .fa-caret-down;
}
- .jstree-closed > .jstree-icon {
- @extend .icon-caret-right;
+ .jstree-closed > .jstree-icon {
+ @extend .fa-caret-right;
}
li {
@@ -26,36 +27,38 @@
width: 90%;
height: auto;
line-height: inherit;
- text-transform: uppercase;
padding: 5px 0 5px 10px;
margin-bottom: 10px;
.jstree-icon {
padding-left: 0px;
- @extend .icon-move;
+ @extend .fa;
+ @extend .fa-arrows;
}
}
}
}
-#vakata-dragged.jstree-apple .jstree-invalid,
+#vakata-dragged.jstree-apple .jstree-invalid,
#vakata-dragged.jstree-apple .jstree-ok,
#jstree-marker {
background-image: none !important;
background-color: transparent !important;
- @extend [class^="icon-"]:before;
+ @extend [class^="icon-"]:before;
}
#vakata-dragged.jstree-apple .jstree-invalid {
- @extend .icon-remove;
+ @extend .fa;
+ @extend .fa-bars;
color: $color-5;
}
#vakata-dragged.jstree-apple .jstree-ok {
- @extend .icon-ok;
+ @extend .fa;
+ @extend .fa-check;
color: $color-2;
}
#jstree-marker {
- @extend .icon-caret-right;
+ @extend .fa-caret-right;
color: $color-body-text !important;
width: 4px !important;
}
@@ -72,7 +75,7 @@
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
box-shadow: none !important;
-
+
}
#vakata-contextmenu {
@@ -110,7 +113,7 @@
-webkit-box-shadow: none !important;
line-height: inherit !important;
padding: 5px 10px !important;
- margin: 0 !important;
+ margin: 0 !important;
}
}
@@ -129,4 +132,4 @@
li.vakata-separator {
display: none;
}
-}
\ No newline at end of file
+}
diff --git a/core/app/assets/stylesheets/admin/plugins/_powertip.scss b/backend/app/assets/stylesheets/spree/backend/plugins/_powertip.scss
similarity index 86%
rename from core/app/assets/stylesheets/admin/plugins/_powertip.scss
rename to backend/app/assets/stylesheets/spree/backend/plugins/_powertip.scss
index 06839de79d3..b53f47a7e4b 100644
--- a/core/app/assets/stylesheets/admin/plugins/_powertip.scss
+++ b/backend/app/assets/stylesheets/spree/backend/plugins/_powertip.scss
@@ -17,7 +17,7 @@
&.s:before, &.se:before, &.sw:before {
border-bottom-width: 5px;
border-bottom-color: $color-3;
- top: -5px;
+ top: -5px;
}
&.w:before {
border-left-width: 5px;
@@ -32,33 +32,33 @@
&.nw:before, &.sw:before {
border-left-width: 5px;
border-right-color: $color-3;
- right: -5px;
- }
+ right: -5px;
+ }
- &.clone, &.yellow {
+ &.clone, &.yellow, &.cancel {
background-color: $color-notice;
&.n:before, &.ne:before, &.nw:before {
border-top-color: $color-notice;
}
&.e:before, &.nw:before, &.sw:before {
- border-right-color: $color-notice;
+ border-right-color: $color-notice;
}
&.s:before, &.se:before, &.sw:before {
border-bottom-color: $color-notice;
}
&.w:before {
- border-left-color: $color-notice;
+ border-left-color: $color-notice;
}
}
- &.edit, &.green, &.capture {
+ &.edit, &.green, &.capture, &.save, &.add {
background-color: $color-success;
&.n:before, &.ne:before, &.nw:before {
border-top-color: $color-success;
}
&.e:before, &.nw:before, &.sw:before {
- border-right-color: $color-success;
+ border-right-color: $color-success;
}
&.s:before, &.se:before, &.sw:before {
border-bottom-color: $color-success;
@@ -67,7 +67,7 @@
border-left-color: $color-success;
}
}
- &.remove, &.red, &.void {
+ &.remove, &.red, &.void {
background-color: $color-error;
&.n:before, &.ne:before, &.nw:before {
@@ -83,4 +83,4 @@
border-left-color: $color-error;
}
}
-}
\ No newline at end of file
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/plugins/_select2.scss b/backend/app/assets/stylesheets/spree/backend/plugins/_select2.scss
new file mode 100644
index 00000000000..087cee4b2ed
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/plugins/_select2.scss
@@ -0,0 +1,198 @@
+.select2-container {
+ &:hover .select2-choice, &.select2-container-active .select2-choice {
+ background-color: $color-sel-hover-bg !important;
+ border-color: $color-sel-hover-bg !important;
+ }
+ .select2-choice {
+ background-image: none !important;
+ background-color: $color-sel-bg;
+ border: none !important;
+ box-shadow: none !important;
+ @include border-radius($border-radius);
+ color: $color-1 !important;
+ font-size: 90%;
+ height: 31px;
+ line-height: inherit !important;
+ padding: 5px 15px 7px;
+
+ span {
+ display: block;
+ padding: 2px;
+ }
+
+ .select2-search-choice-close {
+ @extend .fa;
+ @extend .fa-times;
+ margin-top: 2px;
+ font-size: 100% !important;
+ background-image: none !important;
+ }
+ }
+
+ &.select2-container-active {
+ .select2-choice {
+ box-shadow: none !important;
+ }
+
+ &.select2-dropdown-open .select2-choice div b {
+ @extend .fa-caret-up
+ }
+ }
+}
+
+.select2-drop {
+ box-shadow: none !important;
+ z-index: 1000000;
+ max-width: auto !important;
+ border-top: 1px solid;
+ border-color: $color-sel-hover-bg;
+
+ &.select2-drop-above {
+ border-color: $color-sel-hover-bg;
+ }
+}
+
+.select2-search {
+ @extend .fa;
+ @extend .fa-search;
+
+ font-size: 100%;
+ color: darken($color-border, 15);
+ padding: 0 9px 0 0;
+
+ &:before {
+ @extend [class^="icon-"]:before;
+
+ position: absolute;
+ top: 16px;
+ left: 13px;
+ }
+
+ input {
+ @extend input[type="text"];
+ margin-top: 5px;
+ margin-left: 4px;
+ padding-left: 25px;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ font-family: $base-font-family;
+ font-size: 90%;
+ box-shadow: none;
+ background-image: none;
+ }
+}
+
+.select2-container .select2-choice .select2-arrow {
+ background-image: none;
+ background: transparent;
+ border: 0;
+
+ b {
+ padding-top: 7px;
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: none !important;
+ font-family: FontAwesome;
+ font-weight: 200 !important;
+
+ &:before {
+ content: "\f0d7";
+ }
+ }
+}
+
+.select2-results {
+ padding-left: 0 !important;
+
+ li {
+ font-size: 85% !important;
+
+
+ &:nth-child(odd) {
+ background: #efefef;
+ }
+
+ &.select2-highlighted {
+ .select2-result-label {
+ &, h6 {
+ color: $color-1 !important;
+ }
+ }
+ }
+
+ .select2-result-label {
+ color: $color-body-text;
+ min-height: 22px;
+ clear: both;
+ overflow: auto;
+ }
+
+ &.select2-no-results, &.select2-searching {
+ padding: 5px;
+ background-color: transparent;
+ color: $color-body-text;
+ text-align: center;
+ font-weight: $font-weight-bold;
+ text-transform: uppercase;
+ }
+ }
+
+ .select2-highlighted {
+ background-color: $color-sel-bg !important;
+ }
+}
+
+.select2-container-multi {
+ &.select2-container-active, &.select2-dropdown-open {
+ .select2-choices {
+ border-color: $color-sel-hover-bg !important;
+ box-shadow: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ }
+ .select2-choices {
+ @extend input[type="text"];
+ padding: 6px 3px 3px 3px;
+ box-shadow: none;
+ background-image: none !important;
+
+ .select2-search-choice {
+ @include border-radius($border-radius);
+ margin: 0 0 3px 3px;
+ background-image: none;
+ background-color: $color-sel-bg;
+ border: none;
+ box-shadow: none;
+ color: $color-1 !important;
+ font-size: 85%;
+
+ &:hover {
+ background-color: $color-sel-hover-bg;
+ }
+
+ .select2-search-choice-close {
+ background-image: none !important;
+ font-size: 85% !important;
+ @extend .fa;
+ @extend .fa-times;
+ margin-left: 2px;
+ color: $color-1;
+ &:before {
+ font-size: 11px;
+ }
+ }
+ }
+ }
+}
+
+label .select2-container {
+ margin-top: -6px;
+ .select2-choice {
+ span {
+ text-transform: none;
+ font-weight: normal;
+ }
+ }
+}
diff --git a/core/app/assets/stylesheets/admin/plugins/_token-input.scss b/backend/app/assets/stylesheets/spree/backend/plugins/_token-input.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/plugins/_token-input.scss
rename to backend/app/assets/stylesheets/spree/backend/plugins/_token-input.scss
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_adjustments_table.scss b/backend/app/assets/stylesheets/spree/backend/sections/_adjustments_table.scss
new file mode 100644
index 00000000000..9948dd0cc15
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_adjustments_table.scss
@@ -0,0 +1,8 @@
+[data-hook="adjustment_buttons"] {
+ tr {
+ div {
+ width: 50%;
+ float: left;
+ }
+ }
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_alerts.scss b/backend/app/assets/stylesheets/spree/backend/sections/_alerts.scss
new file mode 100644
index 00000000000..91259a2a288
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_alerts.scss
@@ -0,0 +1,27 @@
+.alert {
+ padding: 10px;
+ color: white;
+ font-weight: bold;
+ font-size: 125%;
+
+ &.security {
+ background: #cc0000;
+ border-bottom: 3px solid darken(#cc0000, 10);
+ a {
+ color: lighten($color-3, 20);
+ }
+ }
+
+ &.release, &.news {
+ background: $color-2;
+ border-bottom: 3px solid darken($color-2, 10);
+ a {
+ color: darken($color-3, 10);
+ }
+ }
+
+ .dismiss {
+ float: right;
+ color: white !important;
+ }
+}
\ No newline at end of file
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_bulk_transfer.scss b/backend/app/assets/stylesheets/spree/backend/sections/_bulk_transfer.scss
new file mode 100644
index 00000000000..654001399d1
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_bulk_transfer.scss
@@ -0,0 +1,8 @@
+#add-variant-to-transfer {
+ .field {
+ .bulk_add_variant {
+ margin-top: 19px;
+ width: 100%;
+ }
+ }
+}
diff --git a/core/app/assets/stylesheets/admin/sections/_edit_checkouts.scss b/backend/app/assets/stylesheets/spree/backend/sections/_edit_checkouts.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/sections/_edit_checkouts.scss
rename to backend/app/assets/stylesheets/spree/backend/sections/_edit_checkouts.scss
diff --git a/core/app/assets/stylesheets/admin/sections/_image_settings.scss b/backend/app/assets/stylesheets/spree/backend/sections/_image_settings.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/sections/_image_settings.scss
rename to backend/app/assets/stylesheets/spree/backend/sections/_image_settings.scss
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_log_entries.scss b/backend/app/assets/stylesheets/spree/backend/sections/_log_entries.scss
new file mode 100644
index 00000000000..979802e8e6a
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_log_entries.scss
@@ -0,0 +1,17 @@
+.log_entry {
+ &.success {
+ background: lighten($color-2, 15);
+
+ td h4 {
+ color: darken($color-body-text, 25);
+ }
+ }
+
+ &.fail {
+ background: lighten($color-5, 25);
+
+ td h4 {
+ color: lighten($color-body-text, 50);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_orders.scss b/backend/app/assets/stylesheets/spree/backend/sections/_orders.scss
new file mode 100644
index 00000000000..549c8e73fd2
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_orders.scss
@@ -0,0 +1,64 @@
+// Customize orders filter
+[data-hook="admin_orders_index_search"] {
+ select[data-placeholder="Status"] {
+ width: 100%;
+ }
+
+ .select2-container {
+ width: 100% !important;
+ }
+}
+
+// Order-total
+[data-hook="order_details_total"]{
+ text-align: center;
+
+ .order-total {
+ font-size: 35px;
+ font-weight: 600;
+ color: $color-success;
+ }
+}
+
+[data-hook="admin_order_form_fields"] {
+ legend.stock-location {
+ color: $color-body-text;
+
+ .shipment-number {
+ color: $color-success;
+ }
+ .stock-location-name {
+ color: $color-success;
+ }
+ }
+}
+
+// Customize orduct add fieldset
+#add-line-item {
+ fieldset {
+ padding: 10px 0;
+
+ .field {
+ margin-bottom: 0;
+
+ input[type="text"], input[type="number"] {
+ width: 100%;
+ }
+ }
+ .actions {
+ .button {
+ margin-top: 28px;
+ }
+ }
+ }
+}
+
+[data-hook="adjustments_new_coupon_code"] {
+ margin-bottom: 18px;
+}
+
+#risk_analysis legend {
+ background-color: #c00;
+ color: #FFF;
+ font-size: 2em;
+}
\ No newline at end of file
diff --git a/core/app/assets/stylesheets/admin/sections/_overview.scss b/backend/app/assets/stylesheets/spree/backend/sections/_overview.scss
similarity index 100%
rename from core/app/assets/stylesheets/admin/sections/_overview.scss
rename to backend/app/assets/stylesheets/spree/backend/sections/_overview.scss
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_products.scss b/backend/app/assets/stylesheets/spree/backend/sections/_products.scss
new file mode 100644
index 00000000000..3e54fd54096
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_products.scss
@@ -0,0 +1,123 @@
+[data-hook="admin_products_sidebar"] {
+ // .field.checkbox:first-child {
+ // margin-top: 36px;
+ // }
+ .actions {
+ padding: 0 !important;
+ }
+}
+
+[data-hook="admin_product_form_fields"] {
+ label {
+ display: inline-block;
+ }
+ input, select, textarea, .select2-container {
+ width: 100%;
+ }
+ input[type="checkbox"] {
+ width: auto;
+ vertical-align: bottom;
+ }
+}
+
+[data-hook="admin_product_form_multiple_variants"] {
+ .info-actions {
+ text-align: right;
+ }
+}
+
+.outstanding-balance {
+ margin-bottom: 15px;
+ text-transform: uppercase;
+
+ strong {
+ color: $color-2;
+ }
+}
+
+#listing_product_stock {
+ > tbody {
+ > tr {
+ &.even {
+ td {
+ background-color: $color-tbl-even;
+ }
+ }
+ &.odd:hover > td {
+ background-color: $color-tbl-even;
+ }
+ > td {
+ background-color: $color-tbl-odd;
+
+ > table {
+ > tbody {
+ > tr {
+ > td {
+ border-color: lighten($color-border, 4);
+ }
+ }
+ > tr.even {
+ td {
+ background-color: lighten($color-tbl-even, 2);
+ }
+ }
+ > tr.odd {
+ td {
+ background-color: $color-tbl-odd;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ td {
+ vertical-align: top;
+
+ &.stock_location_info {
+ padding: 0;
+
+ table {
+ margin-bottom: 0;
+ border-collapse: collapse;
+ min-height: 102px;
+
+ thead {
+ th {
+ border-top: none;
+ padding: 5px 10px;
+ text-transform: none;
+
+ &:first-child {
+ border-left: none;
+ }
+ &:last-child {
+ border-right: none;
+ }
+ }
+ }
+ tbody {
+ tr {
+ td {
+ padding: 5px 10px;
+ vertical-align: middle;
+
+ &:first-child {
+ border-left: none;
+ }
+ &:last-child {
+ border-right: none;
+ }
+ }
+
+ &:last-child {
+ td {
+ border-bottom: none;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/app/assets/stylesheets/admin/sections/_promotions.scss b/backend/app/assets/stylesheets/spree/backend/sections/_promotions.scss
similarity index 86%
rename from core/app/assets/stylesheets/admin/sections/_promotions.scss
rename to backend/app/assets/stylesheets/spree/backend/sections/_promotions.scss
index 12de22124ff..607257f934c 100644
--- a/core/app/assets/stylesheets/admin/sections/_promotions.scss
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_promotions.scss
@@ -34,6 +34,25 @@
margin-bottom: 0;
}
+ .tier {
+ position: relative;
+ padding-bottom: 10px;
+
+ .remove {
+ position: absolute;
+ left: -15px;
+ top: 10px;
+
+ &:hover {
+ color: #c60f13;
+ }
+ }
+ }
+
+ .right-align {
+ text-align: right;
+ }
+
.field {
padding-bottom: 10px;
@@ -97,4 +116,4 @@
border: none;
padding-bottom: 0;
}
-}
\ No newline at end of file
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_return_authorizations.scss b/backend/app/assets/stylesheets/spree/backend/sections/_return_authorizations.scss
new file mode 100644
index 00000000000..9debd607c3d
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_return_authorizations.scss
@@ -0,0 +1,24 @@
+@import "spree/backend/globals/variables";
+
+.return-items-table {
+ .refund-amount-input {
+ width: 80px;
+ }
+ .fa-thumbs-up {
+ margin-bottom: 10px;
+ }
+ .select2-container {
+ text-align: left;
+ max-width: 150px;
+ }
+}
+.expedited-exchanges-warning {
+ display: none;
+ color: black;
+ padding: 15px;
+ margin: 10px 0;
+ font-weight: bold;
+ background-color: $color-6;
+ border-radius: 4px;
+ opacity: 0.6;
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_tax_zones.scss b/backend/app/assets/stylesheets/spree/backend/sections/_tax_zones.scss
new file mode 100644
index 00000000000..0c42a1c7a14
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_tax_zones.scss
@@ -0,0 +1,15 @@
+#ul-nested-country {
+ > li {
+ margin-bottom: 10px;
+ }
+
+ .select2-container, select {
+ width: 90%;
+ }
+
+ a.remove {
+ display: inline-block;
+ margin-top: 6px;
+ }
+
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_taxons.scss b/backend/app/assets/stylesheets/spree/backend/sections/_taxons.scss
new file mode 100644
index 00000000000..354725f07f0
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_taxons.scss
@@ -0,0 +1,21 @@
+#sorting_explanation {
+ font-size: 133%;
+ font-style: italic;
+}
+
+.small_product {
+ text-align: center;
+
+ img {
+ border: 1px solid #d9d9db;
+ padding: 5px;
+ }
+
+ margin: 10px;
+}
+
+#taxon_products {
+ > li {
+ height: 170px;
+ }
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_users.scss b/backend/app/assets/stylesheets/spree/backend/sections/_users.scss
new file mode 100644
index 00000000000..8e5be5caf14
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/sections/_users.scss
@@ -0,0 +1,5 @@
+#listing_orders, #listing_items {
+ td.order-state {
+ height: 60px;
+ }
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/shared/_forms.scss b/backend/app/assets/stylesheets/spree/backend/shared/_forms.scss
new file mode 100644
index 00000000000..19fb811e867
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/shared/_forms.scss
@@ -0,0 +1,307 @@
+input[type="text"],
+input[type="password"],
+input[type="email"],
+input[type="date"],
+input[type="datetime"],
+input[type="time"],
+input[type="url"],
+input[type="number"],
+input[type="tel"],
+textarea, fieldset {
+ @include border-radius($border-radius);
+ padding: 7px 10px;
+ border: 1px solid $color-txt-brd;
+ color: $color-txt-text;
+ font-size: 90%;
+
+ &:focus {
+ outline: none;
+ border-color: $color-txt-hover-brd;
+ }
+
+ &[disabled] {
+ opacity: 0.7;
+ }
+}
+
+textarea {
+ line-height: 19px;
+}
+
+.fullwidth {
+ width: 100%;
+}
+
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate;
+
+ .form-control,
+ .input-group-addon {
+ display: table-cell;
+
+ &:first-child {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ &:last-child {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+
+ .form-control {
+ width: 100%;
+ }
+
+ .input-group-addon {
+ padding: 6px 10px;
+ border: 1px solid #cee1f4;
+ background: #eff5fc;
+ border-radius: 3px;
+
+ &:first-child {
+ border-right: none;
+ }
+
+ &:last-child {
+ border-left: none;
+ }
+ }
+}
+
+label {
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 85%;
+ display: inline;
+ margin-bottom: 5px;
+ color: $color-4;
+
+ &.inline {
+ display: inline-block !important;
+ }
+
+ &.block {
+ display: block !important;
+ }
+}
+
+.label-block label { display: block }
+
+input[type="submit"],
+input[type="button"],
+button, .button {
+ @include border-radius($border-radius);
+ display: inline-block;
+ padding: 8px 15px;
+ border: none;
+ background-color: $color-btn-bg;
+ color: $color-btn-text;
+ text-transform: uppercase;
+ font-weight: 600 !important;
+ white-space: nowrap;
+
+ &:before {
+ font-weight: normal !important;
+ }
+
+ &:visited, &:active, &:focus { color: $color-btn-text }
+
+ &:hover {
+ background-color: $color-btn-hover-bg;
+ color: $color-btn-hover-text;
+ }
+
+ &:active:focus {
+ box-shadow: 0 0 8px 0 darken($color-btn-hover-bg, 5) inset;
+ }
+
+ &.fullwidth {
+ width: 100%;
+ text-align: center;
+ }
+}
+
+span.info {
+ font-style: italic;
+ font-size: 85%;
+ color: lighten($color-body-text, 15);
+ display: block;
+ line-height: 20px;
+ margin: 5px 0;
+}
+
+.field {
+ padding: 10px 0;
+
+ &.checkbox {
+ min-height: 73px;
+
+ input[type="checkbox"] {
+ display: inline-block;
+ width: auto;
+ }
+
+ label {
+ cursor: pointer;
+ display: block;
+ }
+ }
+
+ ul {
+ border-top: 1px solid $color-border;
+ list-style: none;
+ padding-top: 5px;
+
+ li {
+ display: inline-block;
+ padding-right: 10px;
+
+
+ label {
+ font-weight: normal;
+ text-transform: none;
+ }
+ &.white-space-nowrap {
+ white-space: nowrap;
+ }
+ }
+ }
+
+ &.withError {
+ .field_with_errors {
+ label {
+ color: very-light($color-error, 30);
+ }
+
+ input {
+ border-color: very-light($color-error, 15);
+ }
+ }
+ .formError {
+ color: very-light($color-error, 30);
+ font-style: italic;
+ font-size: 85%;
+ }
+ }
+}
+
+fieldset {
+ box-shadow: none;
+ box-sizing: border-box;
+ border-color: $color-border;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ margin-left: 0;
+ margin-right: 0;
+ position: relative;
+ margin-bottom: 35px;
+ padding: 10px 0 15px 0;
+ background-color: transparent;
+ border-left: none;
+ border-right: none;
+ border-radius: 0;
+
+ &.no-border-bottom {
+ border-bottom: none;
+ margin-bottom: 0;
+ }
+
+ &.no-border-top {
+ border-top: none;
+ padding-top: 0;
+ }
+
+ legend {
+ background-color: $color-1;
+ color: $color-2;
+ font-size: 14px;
+ font-weight: 600;
+ text-transform: uppercase;
+ text-align: center;
+ padding: 8px 15px;
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ i {
+ color: $color-link;
+ }
+ }
+
+ label {
+ color: lighten($color-body-text, 8);
+ }
+
+ .filter-actions {
+ margin-bottom: -32px;
+ margin-top: 15px;
+ text-align: center;
+
+ form {
+ display: inline-block;
+ }
+
+ button, .button, input[type="submit"], input[type="button"], span.or {
+ @include border-radius($border-radius);
+
+ -webkit-box-shadow: 0 0 0 15px $color-1;
+ -moz-box-shadow: 0 0 0 15px $color-1;
+ -ms-box-shadow: 0 0 0 15px $color-1;
+ -o-box-shadow: 0 0 0 15px $color-1;
+ box-shadow: 0 0 0 15px $color-1;
+
+ &:hover {
+ border-color: $color-1;
+ }
+ }
+
+ span.or {
+ background-color: $color-1;
+ border-width: 5px;
+ margin-left: 5px;
+ margin-right: 5px;
+ position: relative;
+
+ -webkit-box-shadow: 0 0 0 5px $color-1;
+ -moz-box-shadow: 0 0 0 5px $color-1;
+ -ms-box-shadow: 0 0 0 5px $color-1;
+ -o-box-shadow: 0 0 0 5px $color-1;
+ box-shadow: 0 0 0 5px $color-1;
+ }
+ }
+
+ &.labels-inline {
+ .field {
+ margin-bottom: 0;
+ display: table;
+ width: 100%;
+
+ label, input {
+ display: table-cell !important;
+ }
+ input {
+ width: 100%;
+ }
+
+ &.checkbox {
+ input {
+ width: auto !important
+ }
+ }
+ }
+ .actions {
+ padding: 0;
+ text-align: right;
+ }
+ }
+}
+
+.form-actions {
+ margin-top: 18px;
+}
+.form-buttons {
+ text-align: center;
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/shared/_icons.scss b/backend/app/assets/stylesheets/spree/backend/shared/_icons.scss
new file mode 100644
index 00000000000..a8fdeed5735
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/shared/_icons.scss
@@ -0,0 +1,53 @@
+// Some fixes for fontwesome stylesheets
+[class^="icon-"], [class*=" icon-"] {
+ &:before {
+ padding-right: 5px;
+ }
+
+ &.button, &.icon_link {
+ width: auto;
+
+ &:before {
+ padding-right: 5px;
+ padding-top: 3px;
+ }
+ }
+}
+
+.fa-email:before { @extend .fa-envelope:before }
+.fa-resume:before { @extend .fa-refresh:before }
+.fa-approve:before { @extend .fa-check:before }
+
+.fa-remove:before,
+.fa-cancel:before,
+.fa-void:before { @extend .fa-times:before }
+
+.fa-trash:before { @extend .fa-trash-o:before }
+
+.fa-capture:before { @extend .fa-check:before }
+.fa-credit:before { @extend .fa-check:before }
+.fa-approve:before { @extend .fa-check:before }
+.fa-icon-cogs:before { @extend .fa-cogs:before }
+.fa-ok:before,
+.fa-icon-ok:before { @extend .fa-check:before }
+
+button, a {
+ &.fa:before {
+ padding-right: 5px;
+ }
+}
+
+// Admin navigation fixes
+.icon-user,
+.icon-signout,
+.icon-external-link {
+ @extend .fa;
+}
+.icon-user { @extend .fa-user; }
+.icon-signout { @extend .fa-sign-out; }
+.icon-external-link { @extend .fa-external-link; }
+
+// Avoid ugly default browser font (usually a serif) when an element has an icon AND text
+.fa {
+ font-family: "FontAwesome", $base-font-family !important;
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/shared/_layout.scss b/backend/app/assets/stylesheets/spree/backend/shared/_layout.scss
new file mode 100644
index 00000000000..f54c796b672
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/shared/_layout.scss
@@ -0,0 +1,99 @@
+// Basics
+//---------------------------------------------------
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+// Helpers
+.block-table {
+ display: table;
+ width: 100%;
+
+ .table-cell {
+ display: table-cell;
+ vertical-align: middle;
+ padding: 0 10px;
+
+ &:first-child {
+ padding-left: 0;
+ }
+ &:last-child {
+ padding-right: 0;
+ }
+ }
+}
+
+.hidden {
+ display: none;
+}
+
+// For block grids
+.frameless {
+ margin-left: -10px;
+ margin-right: -10px;
+}
+
+.container .column,
+.container .columns {
+ // Float container right instead of left.
+ .right {
+ float: right;
+ }
+}
+
+// Header
+//---------------------------------------------------
+#header {
+ background-color: $color-1;
+ padding: 5px 0;
+}
+
+#logo { height: 40px }
+
+[data-hook="admin-title"] { font-size: 14px }
+
+.page-title {
+ i {
+ color: $color-2;
+ }
+}
+
+// Content
+//---------------------------------------------------
+#content {
+ background-color: $color-1;
+ position: relative;
+ z-index: 0;
+ padding: 0;
+ margin-top: 15px;
+}
+
+#content-header {
+ padding: 15px 0;
+ background-color: very-light($color-3, 4);
+ border-bottom: 1px solid $color-border;
+
+ .page-title {
+ font-size: 20px;
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+ .page-actions {
+ text-align: right;
+ line-height: 38px;
+ form {
+ display: inline-block;
+ }
+ }
+}
+
+// Footer
+//---------------------------------------------------
+#footer {
+ margin-top: 15px;
+ border-top: 1px solid $color-border;
+ padding: 10px 0;
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/shared/_tables.scss b/backend/app/assets/stylesheets/spree/backend/shared/_tables.scss
new file mode 100644
index 00000000000..5b0a8875109
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/shared/_tables.scss
@@ -0,0 +1,214 @@
+table {
+ width: 100%;
+ margin-bottom: 15px;
+ border-collapse: separate;
+
+ th, td {
+ padding: 7px 5px;
+ border-right: 1px solid $color-border;
+ border-bottom: 1px solid $color-border;
+ vertical-align: middle;
+ text-overflow: ellipsis;
+
+ img {
+ border: 1px solid transparent;
+ }
+
+ &:first-child {
+ border-left: 1px solid $color-border;
+ }
+
+ a {
+ border-bottom: 1px dotted lighten($color-link, 10);
+
+ &:hover {
+ border-color: lighten($color-link-hover, 10);
+ }
+ }
+
+ .handle {
+ display: block !important;
+ text-align: center;
+ padding-right: 0;
+ }
+
+ &.actions {
+ background-color: transparent;
+ border: none !important;
+ text-align: center;
+
+ span.text {
+ font-size: $body-font-size;
+ }
+
+ [class*='fa-'].no-text {
+ font-size: 120%;
+ background-color: very-light($color-3);
+ border: 1px solid $color-border;
+ border-radius: 15px;
+ width: 29px;
+ height: 29px;
+ display: inline-block;
+ padding-top: 6px;
+
+ &:before {
+ text-align: center !important;
+ width: 27px;
+ display: inline-block;
+ }
+
+ &:hover {
+ border-color: transparent;
+ }
+ }
+
+ button[class*='fa-'] {
+ color: $color-link;
+ padding: 0 !important;
+ }
+
+ .fa-envelope-alt, .fa-eye-open {
+ color: $color-link;
+ padding-left: 0px;
+
+ &:hover {
+ background-color: $color-3;
+ color: $color-1;
+ }
+ }
+ .fa-trash:hover, .fa-void:hover {
+ background-color: $color-error;
+ color: $color-1;
+ }
+ .fa-cancel:hover {
+ background-color: $color-notice;
+ color: $color-1;
+ }
+ .fa-edit:hover, .fa-capture:hover, .fa-ok:hover, .fa-plus:hover, .fa-save:hover {
+ background-color: $color-success;
+ color: $color-1;
+ }
+ .fa-copy:hover {
+ background-color: $color-notice;
+ color: $color-1;
+ }
+ .fa-thumbs-up:hover {
+ background-color: $color-success;
+ color: $color-1;
+ }
+ .fa-thumbs-down:hover {
+ background-color: $color-error;
+ color: $color-1;
+ }
+ }
+
+ input[type="number"],
+ input[type="text"] {
+ width: 100%;
+ }
+
+ &.no-border {
+ border-right: none;
+ }
+
+ .handle {
+ @extend [class^="icon-"]:before;
+ @extend .fa;
+ @extend .fa-bars;
+ cursor: move;
+ }
+
+ }
+
+ &.no-borders {
+ td, th {
+ border: none !important;
+ }
+
+ }
+
+ thead {
+ th {
+ padding: 10px;
+ border-top: 1px solid $color-border;
+ border-bottom: none;
+ background-color: $color-tbl-thead;
+ text-transform: uppercase;
+ font-size: 85%;
+ font-weight: $font-weight-bold;
+ }
+ }
+
+ tbody {
+ tr {
+ &:first-child th,
+ &:first-child td {
+ border-top: 1px solid $color-border;
+ }
+ &.even td {
+ background-color: $color-tbl-even;
+
+ img {
+ border: 1px solid very-light($color-3, 6);
+ }
+ }
+
+ &:hover td {
+ background-color: very-light($color-3, 5);
+
+ img {
+ border: 1px solid $color-border;
+ }
+ }
+
+ &.deleted td {
+ background-color: very-light($color-error, 6);
+ border-color: very-light($color-error, 15);
+ }
+
+ &.ui-sortable-placeholder td {
+ border: 1px solid $color-2 !important;
+ visibility: visible !important;
+
+ &.actions {
+ background-color: transparent;
+ border-right: none !important;
+ border-top: none !important;
+ border-bottom: none !important;
+ border-left: 1px solid $color-2 !important;
+ }
+ }
+
+ &.ui-sortable-helper {
+ width: 100%;
+
+ td {
+ background-color: lighten($color-3, 33);
+ border-bottom: 1px solid $color-border;
+
+ &.actions {
+ display: none;
+ }
+ }
+ }
+ }
+
+ &.no-border-top tr:first-child td {
+ border-top: none;
+ }
+
+ &.grand-total {
+ td {
+ border-color: $color-2 !important;
+ text-transform: uppercase;
+ font-size: 110%;
+ font-weight: 600;
+ background-color: lighten($color-2, 50);
+ }
+ .total {
+ background-color: $color-2;
+ color: $color-1;
+ }
+ }
+ }
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/shared/_typography.scss b/backend/app/assets/stylesheets/spree/backend/shared/_typography.scss
new file mode 100644
index 00000000000..24074e59db2
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/shared/_typography.scss
@@ -0,0 +1,138 @@
+// Base
+//--------------------------------------------------------------
+body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; font-size: 13px; }
+
+body {
+ font-family: $base-font-family;
+ font-size: $body-font-size;
+ font-weight: 400;
+ color: $color-body-text;
+ text-rendering: optimizeLegibility;
+}
+
+hr {
+ border-top: 1px solid $color-border;
+ border-bottom: 1px solid white;
+ border-left: none;
+}
+
+strong, b {
+ font-weight: 600;
+}
+
+// links
+//--------------------------------------------------------------
+a {
+ color: $color-link;
+ text-decoration: none;
+ line-height: inherit;
+
+ &, &:hover, &:active, &:visited, &:focus {
+ outline: none;
+ }
+
+ &:visited {
+ color: $color-link-visited;
+ }
+ &:focus {
+ color: $color-link-focus;
+ }
+ &:active {
+ color: $color-link-active;
+ }
+ &:hover {
+ color: $color-link-hover;
+ }
+}
+
+// Headings
+//--------------------------------------------------------------
+
+h1,h2,h3,h4,h5,h6 {
+ font-weight: 600;
+ color: $color-headers;
+ line-height: 1.1;
+}
+
+h1 { font-size: $h1-size; line-height: $h1-size + 6 }
+h2 { font-size: $h2-size; line-height: $h1-size + 4 }
+h3 { font-size: $h3-size; line-height: $h1-size + 2 }
+h4 { font-size: $h4-size; line-height: $h1-size }
+h5 { font-size: $h5-size; line-height: $h1-size }
+h6 { font-size: $h6-size; line-height: $h1-size }
+
+
+// Lists
+//--------------------------------------------------------------
+ul {
+ &.inline-menu {
+ li {
+ display: inline-block;
+ }
+ }
+ &.fields {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+}
+
+ul.text_list {
+ border-top: none;
+ margin: 0;
+ list-style: disc inside none;
+}
+
+dl {
+ width: 100%;
+ overflow: hidden;
+ margin: 5px 0;
+ color: lighten($color-body-text, 15);
+
+ dt, dd {
+ float: left;
+ line-height: 16px;
+ padding: 5px;
+ text-align: justify;
+ }
+
+ dt {
+ width: 40%;
+ font-weight: 600;
+ padding-left: 0;
+ text-transform: uppercase;
+ font-size: 85%;
+ }
+
+ dd {
+ width: 60%;
+ padding-right: 0;
+ }
+
+ dd:after {
+ content: '';
+ clear: both;
+ }
+
+}
+
+// Helpers
+.align-center { text-align: center }
+.align-right { text-align: right }
+.align-left { text-align: left }
+.align-justify { text-align: justify }
+
+.uppercase { text-transform: uppercase }
+
+.green { color: $color-2 }
+.blue { color: $color-3 }
+.red { color: $color-5 }
+.yellow { color: $color-6 }
+
+.no-objects-found {
+ text-align: center;
+ font-size: 120%;
+ text-transform: uppercase;
+ padding: 40px 0px;
+ color: lighten($color-body-text, 15);
+}
diff --git a/backend/app/assets/stylesheets/spree/backend/spree_admin.scss b/backend/app/assets/stylesheets/spree/backend/spree_admin.scss
new file mode 100644
index 00000000000..9d90aaf5719
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/backend/spree_admin.scss
@@ -0,0 +1,45 @@
+@import 'spree/backend/globals/functions';
+@import 'spree/backend/globals/variables_override';
+@import 'spree/backend/globals/variables';
+@import 'spree/backend/globals/mixins';
+
+@import 'spree/backend/shared/typography';
+@import 'spree/backend/shared/tables';
+@import 'spree/backend/shared/icons';
+@import 'spree/backend/shared/forms';
+@import 'spree/backend/shared/layout';
+
+@import 'spree/backend/components/states';
+@import 'spree/backend/components/actions';
+@import 'spree/backend/components/date-picker';
+@import 'spree/backend/components/messages';
+@import 'spree/backend/components/pagination';
+@import 'spree/backend/components/progress';
+@import 'spree/backend/components/table-filter';
+@import 'spree/backend/components/navigation';
+@import 'spree/backend/components/sidebar';
+@import 'spree/backend/components/product_autocomplete';
+
+@import 'font-awesome';
+@import 'spree/backend/plugins/powertip';
+@import 'spree/backend/plugins/select2';
+@import 'spree/backend/plugins/token-input';
+@import 'spree/backend/plugins/jstree';
+
+@import 'spree/backend/sections/adjustments_table';
+@import 'spree/backend/sections/alerts';
+@import 'spree/backend/sections/orders';
+@import 'spree/backend/sections/overview';
+@import 'spree/backend/sections/products';
+@import 'spree/backend/sections/promotions';
+@import 'spree/backend/sections/edit_checkouts';
+@import 'spree/backend/sections/bulk_transfer';
+@import 'spree/backend/sections/return_authorizations';
+@import 'spree/backend/sections/tax_zones';
+@import 'spree/backend/sections/log_entries';
+@import 'spree/backend/sections/taxons';
+@import 'spree/backend/sections/users';
+
+@import 'spree/backend/hacks/mozilla';
+@import 'spree/backend/hacks/opera';
+@import 'spree/backend/hacks/ie';
diff --git a/backend/app/assets/stylesheets/spree/frontend/backend.css b/backend/app/assets/stylesheets/spree/frontend/backend.css
new file mode 100644
index 00000000000..63eac109f71
--- /dev/null
+++ b/backend/app/assets/stylesheets/spree/frontend/backend.css
@@ -0,0 +1 @@
+/* Placeholder for backend dummy app */
diff --git a/backend/app/controllers/spree/admin/adjustments_controller.rb b/backend/app/controllers/spree/admin/adjustments_controller.rb
new file mode 100644
index 00000000000..9e061c18471
--- /dev/null
+++ b/backend/app/controllers/spree/admin/adjustments_controller.rb
@@ -0,0 +1,38 @@
+module Spree
+ module Admin
+ class AdjustmentsController < ResourceController
+
+ belongs_to 'spree/order', find_by: :number
+
+ create.after :update_totals
+ destroy.after :update_totals
+ update.after :update_totals
+
+ skip_before_action :load_resource, only: [:toggle_state, :edit, :update, :destroy]
+
+ before_action :find_adjustment, only: [:destroy, :edit, :update]
+
+ def index
+ @adjustments = @order.all_adjustments.order("created_at ASC")
+ end
+
+ private
+
+ def find_adjustment
+ # Need to assign to @object here to keep ResourceController happy
+ @adjustment = @object = parent.all_adjustments.find(params[:id])
+ end
+
+ def update_totals
+ @order.reload.update!
+ end
+
+ # Override method used to create a new instance to correctly
+ # associate adjustment with order
+ def build_resource
+ parent.adjustments.build(order: parent)
+ end
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/base_controller.rb b/backend/app/controllers/spree/admin/base_controller.rb
new file mode 100644
index 00000000000..1455a106651
--- /dev/null
+++ b/backend/app/controllers/spree/admin/base_controller.rb
@@ -0,0 +1,68 @@
+module Spree
+ module Admin
+ class BaseController < Spree::BaseController
+ ssl_required
+
+ helper 'spree/admin/navigation'
+ helper 'spree/admin/tables'
+ layout '/spree/layouts/admin'
+
+ before_action :authorize_admin
+
+ protected
+
+ def action
+ params[:action].to_sym
+ end
+
+ def authorize_admin
+ if respond_to?(:model_class, true) && model_class
+ record = model_class
+ else
+ record = controller_name.to_sym
+ end
+ authorize! :admin, record
+ authorize! action, record
+ end
+
+ # Need to generate an API key for a user due to some backend actions
+ # requiring authentication to the Spree API
+ def generate_admin_api_key
+ if (user = try_spree_current_user) && user.spree_api_key.blank?
+ user.generate_spree_api_key!
+ end
+ end
+
+ def flash_message_for(object, event_sym)
+ resource_desc = object.class.model_name.human
+ resource_desc += " \"#{object.name}\"" if object.respond_to?(:name) && object.name.present?
+ Spree.t(event_sym, resource: resource_desc)
+ end
+
+ def render_js_for_destroy
+ render partial: '/spree/admin/shared/destroy'
+ end
+
+ # Index request for JSON needs to pass a CSRF token in order to prevent JSON Hijacking
+ def check_json_authenticity
+ return unless request.format.js? || request.format.json?
+ return unless protect_against_forgery?
+ auth_token = params[request_forgery_protection_token]
+ unless auth_token && form_authenticity_token == URI.unescape(auth_token)
+ raise(ActionController::InvalidAuthenticityToken)
+ end
+ end
+
+ def config_locale
+ Spree::Backend::Config[:locale]
+ end
+
+ def can_not_transition_without_customer_info
+ unless @order.billing_address.present?
+ flash[:notice] = Spree.t(:fill_in_customer_info)
+ redirect_to edit_admin_order_customer_url(@order)
+ end
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/countries_controller.rb b/backend/app/controllers/spree/admin/countries_controller.rb
similarity index 100%
rename from core/app/controllers/spree/admin/countries_controller.rb
rename to backend/app/controllers/spree/admin/countries_controller.rb
diff --git a/backend/app/controllers/spree/admin/customer_returns_controller.rb b/backend/app/controllers/spree/admin/customer_returns_controller.rb
new file mode 100644
index 00000000000..5ac6af5ab82
--- /dev/null
+++ b/backend/app/controllers/spree/admin/customer_returns_controller.rb
@@ -0,0 +1,67 @@
+module Spree
+ module Admin
+ class CustomerReturnsController < ResourceController
+ belongs_to 'spree/order', find_by: :number
+
+ before_action :parent # ensure order gets loaded to support our pseudo parent-child relationship
+ before_action :load_form_data, only: [:new, :edit]
+
+ create.before :build_return_items_from_params
+ create.fails :load_form_data
+
+ def edit
+ @pending_return_items = @customer_return.return_items.select(&:pending?)
+ @accepted_return_items = @customer_return.return_items.select(&:accepted?)
+ @rejected_return_items = @customer_return.return_items.select(&:rejected?)
+ @manual_intervention_return_items = @customer_return.return_items.select(&:manual_intervention_required?)
+ @pending_reimbursements = @customer_return.reimbursements.select(&:pending?)
+
+ super
+ end
+
+ private
+
+ def location_after_save
+ url_for([:edit, :admin, @order, @customer_return])
+ end
+
+ def build_resource
+ Spree::CustomerReturn.new
+ end
+
+ def find_resource
+ Spree::CustomerReturn.accessible_by(current_ability, :read).find(params[:id])
+ end
+
+ def collection
+ parent # trigger loading the order
+ @collection ||= Spree::ReturnItem
+ .accessible_by(current_ability, :read)
+ .where(inventory_unit_id: @order.inventory_units.pluck(:id))
+ .map(&:customer_return).uniq.compact
+ @customer_returns = @collection
+ end
+
+ def load_form_data
+ return_items = @order.inventory_units.map(&:current_or_new_return_item).reject(&:customer_return_id)
+ @rma_return_items = return_items.select(&:return_authorization_id)
+ end
+
+ def permitted_resource_params
+ @permitted_resource_params ||= params.require('customer_return').permit(permitted_customer_return_attributes)
+ end
+
+ def build_return_items_from_params
+ return_items_params = permitted_resource_params.delete(:return_items_attributes).values
+
+ @customer_return.return_items = return_items_params.map do |item_params|
+ next unless item_params.delete('returned') == '1'
+ return_item = item_params[:id] ? Spree::ReturnItem.find(item_params[:id]) : Spree::ReturnItem.new
+ return_item.attributes = item_params
+ return_item
+ end.compact
+ end
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/general_settings_controller.rb b/backend/app/controllers/spree/admin/general_settings_controller.rb
new file mode 100644
index 00000000000..12bc0d42950
--- /dev/null
+++ b/backend/app/controllers/spree/admin/general_settings_controller.rb
@@ -0,0 +1,45 @@
+module Spree
+ module Admin
+ class GeneralSettingsController < Spree::Admin::BaseController
+ include Spree::Backend::Callbacks
+
+ before_action :set_store
+
+ def edit
+ @preferences_security = [:allow_ssl_in_production, :allow_ssl_in_staging, :allow_ssl_in_development_and_test]
+ @preferences_currency = [:display_currency, :hide_cents]
+ end
+
+ def update
+ params.each do |name, value|
+ next unless Spree::Config.has_preference? name
+ Spree::Config[name] = value
+ end
+
+ current_store.update_attributes store_params
+
+ flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:general_settings))
+ redirect_to edit_admin_general_settings_path
+ end
+
+ def clear_cache
+ Rails.cache.clear
+ invoke_callbacks(:clear_cache, :after)
+ head :no_content
+ end
+
+ private
+ def store_params
+ params.require(:store).permit(permitted_params)
+ end
+
+ def permitted_params
+ Spree::PermittedAttributes.store_attributes
+ end
+
+ def set_store
+ @store = current_store
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/images_controller.rb b/backend/app/controllers/spree/admin/images_controller.rb
new file mode 100644
index 00000000000..a9401bd0c22
--- /dev/null
+++ b/backend/app/controllers/spree/admin/images_controller.rb
@@ -0,0 +1,34 @@
+module Spree
+ module Admin
+ class ImagesController < ResourceController
+ before_action :load_data
+
+ create.before :set_viewable
+ update.before :set_viewable
+
+ private
+
+ def location_after_destroy
+ admin_product_images_url(@product)
+ end
+
+ def location_after_save
+ admin_product_images_url(@product)
+ end
+
+ def load_data
+ @product = Product.friendly.find(params[:product_id])
+ @variants = @product.variants.collect do |variant|
+ [variant.sku_and_options_text, variant.id]
+ end
+ @variants.insert(0, [Spree.t(:all), @product.master.id])
+ end
+
+ def set_viewable
+ @image.viewable_type = 'Spree::Variant'
+ @image.viewable_id = params[:image][:viewable_id]
+ end
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/log_entries_controller.rb b/backend/app/controllers/spree/admin/log_entries_controller.rb
new file mode 100644
index 00000000000..6e064efe4c3
--- /dev/null
+++ b/backend/app/controllers/spree/admin/log_entries_controller.rb
@@ -0,0 +1,19 @@
+module Spree
+ module Admin
+ class LogEntriesController < Spree::Admin::BaseController
+ before_action :find_order_and_payment
+
+ def index
+ @log_entries = @payment.log_entries
+ end
+
+
+ private
+
+ def find_order_and_payment
+ @order = Spree::Order.where(:number => params[:order_id]).first!
+ @payment = @order.payments.find(params[:payment_id])
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/option_types_controller.rb b/backend/app/controllers/spree/admin/option_types_controller.rb
similarity index 95%
rename from core/app/controllers/spree/admin/option_types_controller.rb
rename to backend/app/controllers/spree/admin/option_types_controller.rb
index 367ea03dd59..cb7bc4db330 100644
--- a/core/app/controllers/spree/admin/option_types_controller.rb
+++ b/backend/app/controllers/spree/admin/option_types_controller.rb
@@ -1,7 +1,7 @@
module Spree
module Admin
class OptionTypesController < ResourceController
- before_filter :setup_new_option_value, :only => [:edit]
+ before_action :setup_new_option_value, only: :edit
def update_values_positions
params[:positions].each do |id, index|
diff --git a/backend/app/controllers/spree/admin/option_values_controller.rb b/backend/app/controllers/spree/admin/option_values_controller.rb
new file mode 100644
index 00000000000..06ee314edb7
--- /dev/null
+++ b/backend/app/controllers/spree/admin/option_values_controller.rb
@@ -0,0 +1,11 @@
+module Spree
+ module Admin
+ class OptionValuesController < Spree::Admin::BaseController
+ def destroy
+ option_value = Spree::OptionValue.find(params[:id])
+ option_value.destroy
+ render :text => nil
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/orders/customer_details_controller.rb b/backend/app/controllers/spree/admin/orders/customer_details_controller.rb
new file mode 100644
index 00000000000..401430b094a
--- /dev/null
+++ b/backend/app/controllers/spree/admin/orders/customer_details_controller.rb
@@ -0,0 +1,57 @@
+module Spree
+ module Admin
+ module Orders
+ class CustomerDetailsController < Spree::Admin::BaseController
+ before_action :load_order
+
+ def show
+ edit
+ render :action => :edit
+ end
+
+ def edit
+ country_id = Address.default.country.id
+ @order.build_bill_address(:country_id => country_id) if @order.bill_address.nil?
+ @order.build_ship_address(:country_id => country_id) if @order.ship_address.nil?
+
+ @order.bill_address.country_id = country_id if @order.bill_address.country.nil?
+ @order.ship_address.country_id = country_id if @order.ship_address.country.nil?
+ end
+
+ def update
+ if @order.update_attributes(order_params)
+ if params[:guest_checkout] == "false"
+ @order.associate_user!(Spree.user_class.find(params[:user_id]), @order.email.blank?)
+ end
+ @order.next
+ @order.refresh_shipment_rates
+ flash[:success] = Spree.t('customer_details_updated')
+ redirect_to edit_admin_order_url(@order)
+ else
+ render :action => :edit
+ end
+
+ end
+
+ private
+ def order_params
+ params.require(:order).permit(
+ :email,
+ :use_billing,
+ :bill_address_attributes => permitted_address_attributes,
+ :ship_address_attributes => permitted_address_attributes
+ )
+ end
+
+ def load_order
+ @order = Order.includes(:adjustments).find_by_number!(params[:order_id])
+ end
+
+ def model_class
+ Spree::Order
+ end
+
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/orders_controller.rb b/backend/app/controllers/spree/admin/orders_controller.rb
new file mode 100644
index 00000000000..dea0a0cd0fb
--- /dev/null
+++ b/backend/app/controllers/spree/admin/orders_controller.rb
@@ -0,0 +1,149 @@
+module Spree
+ module Admin
+ class OrdersController < Spree::Admin::BaseController
+ before_action :initialize_order_events
+ before_action :load_order, only: [:edit, :update, :cancel, :resume, :approve, :resend, :open_adjustments, :close_adjustments, :cart]
+
+ respond_to :html
+
+ def index
+ params[:q] ||= {}
+ params[:q][:completed_at_not_null] ||= '1' if Spree::Config[:show_only_complete_orders_by_default]
+ @show_only_completed = params[:q][:completed_at_not_null] == '1'
+ params[:q][:s] ||= @show_only_completed ? 'completed_at desc' : 'created_at desc'
+ params[:q][:completed_at_not_null] = '' unless @show_only_completed
+
+ # As date params are deleted if @show_only_completed, store
+ # the original date so we can restore them into the params
+ # after the search
+ created_at_gt = params[:q][:created_at_gt]
+ created_at_lt = params[:q][:created_at_lt]
+
+ params[:q].delete(:inventory_units_shipment_id_null) if params[:q][:inventory_units_shipment_id_null] == "0"
+
+ if params[:q][:created_at_gt].present?
+ params[:q][:created_at_gt] = Time.zone.parse(params[:q][:created_at_gt]).beginning_of_day rescue ""
+ end
+
+ if params[:q][:created_at_lt].present?
+ params[:q][:created_at_lt] = Time.zone.parse(params[:q][:created_at_lt]).end_of_day rescue ""
+ end
+
+ if @show_only_completed
+ params[:q][:completed_at_gt] = params[:q].delete(:created_at_gt)
+ params[:q][:completed_at_lt] = params[:q].delete(:created_at_lt)
+ end
+
+ @search = Order.accessible_by(current_ability, :index).ransack(params[:q])
+
+ # lazyoading other models here (via includes) may result in an invalid query
+ # e.g. SELECT DISTINCT DISTINCT "spree_orders".id, "spree_orders"."created_at" AS alias_0 FROM "spree_orders"
+ # see https://github.com/spree/spree/pull/3919
+ @orders = @search.result(distinct: true).
+ page(params[:page]).
+ per(params[:per_page] || Spree::Config[:orders_per_page])
+
+ # Restore dates
+ params[:q][:created_at_gt] = created_at_gt
+ params[:q][:created_at_lt] = created_at_lt
+ end
+
+ def new
+ @order = Order.create(order_params)
+ redirect_to cart_admin_order_url(@order)
+ end
+
+ def edit
+ can_not_transition_without_customer_info
+
+ unless @order.completed?
+ @order.refresh_shipment_rates
+ end
+ end
+
+ def cart
+ unless @order.completed?
+ @order.refresh_shipment_rates
+ end
+ if @order.shipped_shipments.count > 0
+ redirect_to edit_admin_order_url(@order)
+ end
+ end
+
+ def update
+ if @order.update_attributes(params[:order]) && @order.line_items.present?
+ @order.update!
+ unless @order.completed?
+ # Jump to next step if order is not completed.
+ redirect_to admin_order_customer_path(@order) and return
+ end
+ else
+ @order.errors.add(:line_items, Spree.t('errors.messages.blank')) if @order.line_items.empty?
+ end
+
+ render :action => :edit
+ end
+
+ def cancel
+ @order.canceled_by(try_spree_current_user)
+ flash[:success] = Spree.t(:order_canceled)
+ redirect_to :back
+ end
+
+ def resume
+ @order.resume!
+ flash[:success] = Spree.t(:order_resumed)
+ redirect_to :back
+ end
+
+ def approve
+ @order.approved_by(try_spree_current_user)
+ flash[:success] = Spree.t(:order_approved)
+ redirect_to :back
+ end
+
+ def resend
+ OrderMailer.confirm_email(@order.id, true).deliver
+ flash[:success] = Spree.t(:order_email_resent)
+
+ redirect_to :back
+ end
+
+ def open_adjustments
+ adjustments = @order.all_adjustments.where(state: 'closed')
+ adjustments.update_all(state: 'open')
+ flash[:success] = Spree.t(:all_adjustments_opened)
+
+ respond_with(@order) { |format| format.html { redirect_to :back } }
+ end
+
+ def close_adjustments
+ adjustments = @order.all_adjustments.where(state: 'open')
+ adjustments.update_all(state: 'closed')
+ flash[:success] = Spree.t(:all_adjustments_closed)
+
+ respond_with(@order) { |format| format.html { redirect_to :back } }
+ end
+
+ private
+ def order_params
+ params[:created_by_id] = try_spree_current_user.try(:id)
+ params.permit(:created_by_id)
+ end
+
+ def load_order
+ @order = Order.includes(:adjustments).find_by_number!(params[:id])
+ authorize! action, @order
+ end
+
+ # Used for extensions which need to provide their own custom event links on the order details view.
+ def initialize_order_events
+ @order_events = %w{approve cancel resume}
+ end
+
+ def model_class
+ Spree::Order
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/payment_methods_controller.rb b/backend/app/controllers/spree/admin/payment_methods_controller.rb
new file mode 100644
index 00000000000..9097921d707
--- /dev/null
+++ b/backend/app/controllers/spree/admin/payment_methods_controller.rb
@@ -0,0 +1,72 @@
+module Spree
+ module Admin
+ class PaymentMethodsController < ResourceController
+ skip_before_action :load_resource, only: :create
+ before_action :load_data
+ before_action :validate_payment_method_provider, only: :create
+
+ respond_to :html
+
+ def create
+ @payment_method = params[:payment_method].delete(:type).constantize.new(payment_method_params)
+ @object = @payment_method
+ invoke_callbacks(:create, :before)
+ if @payment_method.save
+ invoke_callbacks(:create, :after)
+ flash[:success] = Spree.t(:successfully_created, :resource => Spree.t(:payment_method))
+ redirect_to edit_admin_payment_method_path(@payment_method)
+ else
+ invoke_callbacks(:create, :fails)
+ respond_with(@payment_method)
+ end
+ end
+
+ def update
+ invoke_callbacks(:update, :before)
+ payment_method_type = params[:payment_method].delete(:type)
+ if @payment_method['type'].to_s != payment_method_type
+ @payment_method.update_columns(
+ type: payment_method_type,
+ updated_at: Time.now,
+ )
+ @payment_method = PaymentMethod.find(params[:id])
+ end
+
+ update_params = params[ActiveModel::Naming.param_key(@payment_method)] || {}
+ attributes = payment_method_params.merge(update_params)
+ attributes.each do |k,v|
+ if k.include?("password") && attributes[k].blank?
+ attributes.delete(k)
+ end
+ end
+
+ if @payment_method.update_attributes(attributes)
+ invoke_callbacks(:update, :after)
+ flash[:success] = Spree.t(:successfully_updated, :resource => Spree.t(:payment_method))
+ redirect_to edit_admin_payment_method_path(@payment_method)
+ else
+ invoke_callbacks(:update, :fails)
+ respond_with(@payment_method)
+ end
+ end
+
+ private
+
+ def load_data
+ @providers = Gateway.providers.sort{|p1, p2| p1.name <=> p2.name }
+ end
+
+ def validate_payment_method_provider
+ valid_payment_methods = Rails.application.config.spree.payment_methods.map(&:to_s)
+ if !valid_payment_methods.include?(params[:payment_method][:type])
+ flash[:error] = Spree.t(:invalid_payment_provider)
+ redirect_to new_admin_payment_method_path
+ end
+ end
+
+ def payment_method_params
+ params.require(:payment_method).permit!
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/payments_controller.rb b/backend/app/controllers/spree/admin/payments_controller.rb
new file mode 100644
index 00000000000..9b35c0b3406
--- /dev/null
+++ b/backend/app/controllers/spree/admin/payments_controller.rb
@@ -0,0 +1,103 @@
+module Spree
+ module Admin
+ class PaymentsController < Spree::Admin::BaseController
+ include Spree::Backend::Callbacks
+
+ before_action :load_order, only: [:create, :new, :index, :fire]
+ before_action :load_payment, except: [:create, :new, :index]
+ before_action :load_data
+ before_action :can_not_transition_without_customer_info
+
+ respond_to :html
+
+ def index
+ @payments = @order.payments.includes(:refunds => :reason)
+ @refunds = @payments.flat_map(&:refunds)
+ redirect_to new_admin_order_payment_url(@order) if @payments.empty?
+ end
+
+ def new
+ @payment = @order.payments.build
+ end
+
+ def create
+ invoke_callbacks(:create, :before)
+ @payment ||= @order.payments.build(object_params)
+ if @payment.payment_method.source_required? && params[:card].present? and params[:card] != 'new'
+ @payment.source = @payment.payment_method.payment_source_class.find_by_id(params[:card])
+ end
+
+ begin
+ if @payment.save
+ invoke_callbacks(:create, :after)
+ # Transition order as far as it will go.
+ while @order.next; end
+ # If "@order.next" didn't trigger payment processing already (e.g. if the order was
+ # already complete) then trigger it manually now
+ @payment.process! if @order.completed? && @payment.checkout?
+ flash[:success] = flash_message_for(@payment, :successfully_created)
+ redirect_to admin_order_payments_path(@order)
+ else
+ invoke_callbacks(:create, :fails)
+ flash[:error] = Spree.t(:payment_could_not_be_created)
+ render :new
+ end
+ rescue Spree::Core::GatewayError => e
+ invoke_callbacks(:create, :fails)
+ flash[:error] = "#{e.message}"
+ redirect_to new_admin_order_payment_path(@order)
+ end
+ end
+
+ def fire
+ return unless event = params[:e] and @payment.payment_source
+
+ # Because we have a transition method also called void, we do this to avoid conflicts.
+ event = "void_transaction" if event == "void"
+ if @payment.send("#{event}!")
+ flash[:success] = Spree.t(:payment_updated)
+ else
+ flash[:error] = Spree.t(:cannot_perform_operation)
+ end
+ rescue Spree::Core::GatewayError => ge
+ flash[:error] = "#{ge.message}"
+ ensure
+ redirect_to admin_order_payments_path(@order)
+ end
+
+ private
+
+ def object_params
+ if params[:payment] and params[:payment_source] and source_params = params.delete(:payment_source)[params[:payment][:payment_method_id]]
+ params[:payment][:source_attributes] = source_params
+ end
+
+ params.require(:payment).permit(permitted_payment_attributes)
+ end
+
+ def load_data
+ @amount = params[:amount] || load_order.total
+ @payment_methods = PaymentMethod.available(:back_end)
+ if @payment and @payment.payment_method
+ @payment_method = @payment.payment_method
+ else
+ @payment_method = @payment_methods.first
+ end
+ end
+
+ def load_order
+ @order = Order.find_by_number!(params[:order_id])
+ authorize! action, @order
+ @order
+ end
+
+ def load_payment
+ @payment = Payment.find(params[:id])
+ end
+
+ def model_class
+ Spree::Payment
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/product_properties_controller.rb b/backend/app/controllers/spree/admin/product_properties_controller.rb
new file mode 100644
index 00000000000..4f5480f0c1e
--- /dev/null
+++ b/backend/app/controllers/spree/admin/product_properties_controller.rb
@@ -0,0 +1,18 @@
+module Spree
+ module Admin
+ class ProductPropertiesController < ResourceController
+ belongs_to 'spree/product', :find_by => :slug
+ before_action :find_properties
+ before_action :setup_property, only: :index
+
+ private
+ def find_properties
+ @properties = Spree::Property.pluck(:name)
+ end
+
+ def setup_property
+ @product.product_properties.build
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/products_controller.rb b/backend/app/controllers/spree/admin/products_controller.rb
new file mode 100644
index 00000000000..566cb76a78c
--- /dev/null
+++ b/backend/app/controllers/spree/admin/products_controller.rb
@@ -0,0 +1,147 @@
+module Spree
+ module Admin
+ class ProductsController < ResourceController
+ helper 'spree/products'
+
+ before_action :load_data, except: :index
+ create.before :create_before
+ update.before :update_before
+ helper_method :clone_object_url
+
+ def show
+ session[:return_to] ||= request.referer
+ redirect_to action: :edit
+ end
+
+ def index
+ session[:return_to] = request.url
+ respond_with(@collection)
+ end
+
+ def update
+ if params[:product][:taxon_ids].present?
+ params[:product][:taxon_ids] = params[:product][:taxon_ids].split(',')
+ end
+ if params[:product][:option_type_ids].present?
+ params[:product][:option_type_ids] = params[:product][:option_type_ids].split(',')
+ end
+ invoke_callbacks(:update, :before)
+ if @object.update_attributes(permitted_resource_params)
+ invoke_callbacks(:update, :after)
+ flash[:success] = flash_message_for(@object, :successfully_updated)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_save }
+ format.js { render layout: false }
+ end
+ else
+ # Stops people submitting blank slugs, causing errors when they try to
+ # update the product again
+ @product.slug = @product.slug_was if @product.slug.blank?
+ invoke_callbacks(:update, :fails)
+ respond_with(@object)
+ end
+ end
+
+ def destroy
+ @product = Product.friendly.find(params[:id])
+ @product.destroy
+
+ flash[:success] = Spree.t('notice_messages.product_deleted')
+
+ respond_with(@product) do |format|
+ format.html { redirect_to collection_url }
+ format.js { render_js_for_destroy }
+ end
+ end
+
+ def clone
+ @new = @product.duplicate
+
+ if @new.save
+ flash[:success] = Spree.t('notice_messages.product_cloned')
+ else
+ flash[:error] = Spree.t('notice_messages.product_not_cloned')
+ end
+
+ redirect_to edit_admin_product_url(@new)
+ end
+
+ def stock
+ @variants = @product.variants.includes(*variant_stock_includes)
+ @variants = [@product.master] if @variants.empty?
+ @stock_locations = StockLocation.accessible_by(current_ability, :read)
+ if @stock_locations.empty?
+ flash[:error] = Spree.t(:stock_management_requires_a_stock_location)
+ redirect_to admin_stock_locations_path
+ end
+ end
+
+ protected
+
+ def find_resource
+ Product.with_deleted.friendly.find(params[:id])
+ end
+
+ def location_after_save
+ spree.edit_admin_product_url(@product)
+ end
+
+ def load_data
+ @taxons = Taxon.order(:name)
+ @option_types = OptionType.order(:name)
+ @tax_categories = TaxCategory.order(:name)
+ @shipping_categories = ShippingCategory.order(:name)
+ end
+
+ def collection
+ return @collection if @collection.present?
+ params[:q] ||= {}
+ params[:q][:deleted_at_null] ||= "1"
+
+ params[:q][:s] ||= "name asc"
+ @collection = super
+ # Don't delete params[:q][:deleted_at_null] here because it is used in view to check the
+ # checkbox for 'q[deleted_at_null]'. This also messed with pagination when deleted_at_null is checked.
+ if params[:q][:deleted_at_null] == '0'
+ @collection = @collection.with_deleted
+ end
+ # @search needs to be defined as this is passed to search_form_for
+ # Temporarily remove params[:q][:deleted_at_null] from params[:q] to ransack products.
+ # This is to include all products and not just deleted products.
+ @search = @collection.ransack(params[:q].reject { |k, _v| k.to_s == 'deleted_at_null' })
+ @collection = @search.result.
+ distinct_by_product_ids(params[:q][:s]).
+ includes(product_includes).
+ page(params[:page]).
+ per(params[:per_page] || Spree::Config[:admin_products_per_page])
+ @collection
+ end
+
+ def create_before
+ return if params[:product][:prototype_id].blank?
+ @prototype = Spree::Prototype.find(params[:product][:prototype_id])
+ end
+
+ def update_before
+ # note: we only reset the product properties if we're receiving a post
+ # from the form on that tab
+ return unless params[:clear_product_properties]
+ params[:product] ||= {}
+ end
+
+ def product_includes
+ [{ variants: [:images], master: [:images, :default_price] }]
+ end
+
+ def clone_object_url(resource)
+ clone_admin_product_url resource
+ end
+
+ private
+
+ def variant_stock_includes
+ [:images, stock_items: :stock_location, option_values: :option_type]
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/promotion_actions_controller.rb b/backend/app/controllers/spree/admin/promotion_actions_controller.rb
new file mode 100644
index 00000000000..ec52c0a2e72
--- /dev/null
+++ b/backend/app/controllers/spree/admin/promotion_actions_controller.rb
@@ -0,0 +1,45 @@
+class Spree::Admin::PromotionActionsController < Spree::Admin::BaseController
+ before_action :load_promotion, only: [:create, :destroy]
+ before_action :validate_promotion_action_type, only: :create
+
+ def create
+ @calculators = Spree::Promotion::Actions::CreateAdjustment.calculators
+ @promotion_action = params[:action_type].constantize.new(params[:promotion_action])
+ @promotion_action.promotion = @promotion
+ if @promotion_action.save
+ flash[:success] = Spree.t(:successfully_created, :resource => Spree.t(:promotion_action))
+ end
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+
+ def destroy
+ @promotion_action = @promotion.promotion_actions.find(params[:id])
+ if @promotion_action.destroy
+ flash[:success] = Spree.t(:successfully_removed, :resource => Spree.t(:promotion_action))
+ end
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+
+ private
+
+ def load_promotion
+ @promotion = Spree::Promotion.find(params[:promotion_id])
+ end
+
+ def validate_promotion_action_type
+ valid_promotion_action_types = Rails.application.config.spree.promotions.actions.map(&:to_s)
+ if !valid_promotion_action_types.include?(params[:action_type])
+ flash[:error] = Spree.t(:invalid_promotion_action)
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/promotion_categories_controller.rb b/backend/app/controllers/spree/admin/promotion_categories_controller.rb
new file mode 100644
index 00000000000..024be2974de
--- /dev/null
+++ b/backend/app/controllers/spree/admin/promotion_categories_controller.rb
@@ -0,0 +1,6 @@
+module Spree
+ module Admin
+ class PromotionCategoriesController < ResourceController
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/promotion_rules_controller.rb b/backend/app/controllers/spree/admin/promotion_rules_controller.rb
new file mode 100644
index 00000000000..f390c0da811
--- /dev/null
+++ b/backend/app/controllers/spree/admin/promotion_rules_controller.rb
@@ -0,0 +1,50 @@
+class Spree::Admin::PromotionRulesController < Spree::Admin::BaseController
+ helper 'spree/promotion_rules'
+
+ before_action :load_promotion, only: [:create, :destroy]
+ before_action :validate_promotion_rule_type, only: :create
+
+ def create
+ # Remove type key from this hash so that we don't attempt
+ # to set it when creating a new record, as this is raises
+ # an error in ActiveRecord 3.2.
+ promotion_rule_type = params[:promotion_rule].delete(:type)
+ @promotion_rule = promotion_rule_type.constantize.new(params[:promotion_rule])
+ @promotion_rule.promotion = @promotion
+ if @promotion_rule.save
+ flash[:success] = Spree.t(:successfully_created, :resource => Spree.t(:promotion_rule))
+ end
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+
+ def destroy
+ @promotion_rule = @promotion.promotion_rules.find(params[:id])
+ if @promotion_rule.destroy
+ flash[:success] = Spree.t(:successfully_removed, :resource => Spree.t(:promotion_rule))
+ end
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+
+ private
+
+ def load_promotion
+ @promotion = Spree::Promotion.find(params[:promotion_id])
+ end
+
+ def validate_promotion_rule_type
+ valid_promotion_rule_types = Rails.application.config.spree.promotions.rules.map(&:to_s)
+ if !valid_promotion_rule_types.include?(params[:promotion_rule][:type])
+ flash[:error] = Spree.t(:invalid_promotion_rule)
+ respond_to do |format|
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
+ format.js { render :layout => false }
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/promotions_controller.rb b/backend/app/controllers/spree/admin/promotions_controller.rb
new file mode 100644
index 00000000000..bd4a2c0dbec
--- /dev/null
+++ b/backend/app/controllers/spree/admin/promotions_controller.rb
@@ -0,0 +1,38 @@
+module Spree
+ module Admin
+ class PromotionsController < ResourceController
+ before_action :load_data
+
+ helper 'spree/promotion_rules'
+
+ protected
+ def location_after_save
+ spree.edit_admin_promotion_url(@promotion)
+ end
+
+ def load_data
+ @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments
+ @promotion_categories = Spree::PromotionCategory.order(:name)
+ end
+
+ def collection
+ return @collection if defined?(@collection)
+ params[:q] ||= HashWithIndifferentAccess.new
+ params[:q][:s] ||= 'id desc'
+
+ @collection = super
+ @search = @collection.ransack(params[:q])
+ @collection = @search.result(distinct: true).
+ includes(promotion_includes).
+ page(params[:page]).
+ per(params[:per_page] || Spree::Config[:promotions_per_page])
+
+ @collection
+ end
+
+ def promotion_includes
+ [:promotion_actions]
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/properties_controller.rb b/backend/app/controllers/spree/admin/properties_controller.rb
new file mode 100644
index 00000000000..fdf01614eb1
--- /dev/null
+++ b/backend/app/controllers/spree/admin/properties_controller.rb
@@ -0,0 +1,25 @@
+module Spree
+ module Admin
+ class PropertiesController < ResourceController
+ def index
+ respond_with(@collection)
+ end
+
+ private
+
+ def collection
+ return @collection if @collection.present?
+ # params[:q] can be blank upon pagination
+ params[:q] = {} if params[:q].blank?
+
+ @collection = super
+ @search = @collection.ransack(params[:q])
+ @collection = @search.result.
+ page(params[:page]).
+ per(Spree::Config[:properties_per_page])
+
+ @collection
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/prototypes_controller.rb b/backend/app/controllers/spree/admin/prototypes_controller.rb
similarity index 94%
rename from core/app/controllers/spree/admin/prototypes_controller.rb
rename to backend/app/controllers/spree/admin/prototypes_controller.rb
index b799e5ce5a6..fb657df760c 100644
--- a/core/app/controllers/spree/admin/prototypes_controller.rb
+++ b/backend/app/controllers/spree/admin/prototypes_controller.rb
@@ -20,8 +20,6 @@ def available
def select
@prototype ||= Prototype.find(params[:id])
@prototype_properties = @prototype.properties
-
- respond_with(@prototypes)
end
end
diff --git a/backend/app/controllers/spree/admin/refund_reasons_controller.rb b/backend/app/controllers/spree/admin/refund_reasons_controller.rb
new file mode 100644
index 00000000000..e08a6a69ce3
--- /dev/null
+++ b/backend/app/controllers/spree/admin/refund_reasons_controller.rb
@@ -0,0 +1,6 @@
+module Spree
+ module Admin
+ class RefundReasonsController < ResourceController
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/refunds_controller.rb b/backend/app/controllers/spree/admin/refunds_controller.rb
new file mode 100644
index 00000000000..eb29286887a
--- /dev/null
+++ b/backend/app/controllers/spree/admin/refunds_controller.rb
@@ -0,0 +1,38 @@
+module Spree
+ module Admin
+ class RefundsController < ResourceController
+ belongs_to 'spree/payment'
+ before_action :load_order
+
+ helper_method :refund_reasons
+
+ rescue_from Spree::Core::GatewayError, with: :spree_core_gateway_error, only: :create
+
+ private
+
+ def location_after_save
+ admin_order_payments_path(@payment.order)
+ end
+
+ def load_order
+ # the spree/admin/shared/order_tabs partial expects the @order instance variable to be set
+ @order = @payment.order if @payment
+ end
+
+ def refund_reasons
+ @refund_reasons ||= RefundReason.active.all
+ end
+
+ def build_resource
+ super.tap do |refund|
+ refund.amount = refund.payment.credit_allowed
+ end
+ end
+
+ def spree_core_gateway_error(error)
+ flash[:error] = error.message
+ render :new
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/reimbursement_types_controller.rb b/backend/app/controllers/spree/admin/reimbursement_types_controller.rb
new file mode 100644
index 00000000000..59f25080d8a
--- /dev/null
+++ b/backend/app/controllers/spree/admin/reimbursement_types_controller.rb
@@ -0,0 +1,6 @@
+module Spree
+ module Admin
+ class ReimbursementTypesController < ResourceController
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/reimbursements_controller.rb b/backend/app/controllers/spree/admin/reimbursements_controller.rb
new file mode 100644
index 00000000000..cfc145b53ab
--- /dev/null
+++ b/backend/app/controllers/spree/admin/reimbursements_controller.rb
@@ -0,0 +1,45 @@
+module Spree
+ module Admin
+ class ReimbursementsController < ResourceController
+ belongs_to 'spree/order', find_by: :number
+
+ before_action :load_simulated_refunds, only: :edit
+
+ rescue_from Spree::Core::GatewayError, with: :spree_core_gateway_error, only: :perform
+
+ def perform
+ @reimbursement.perform!
+ redirect_to location_after_save
+ end
+
+ private
+
+ def build_resource
+ if params[:build_from_customer_return_id].present?
+ customer_return = CustomerReturn.find(params[:build_from_customer_return_id])
+
+ Reimbursement.build_from_customer_return(customer_return)
+ else
+ super
+ end
+ end
+
+ def location_after_save
+ if @reimbursement.reimbursed?
+ admin_order_reimbursement_path(parent, @reimbursement)
+ else
+ edit_admin_order_reimbursement_path(parent, @reimbursement)
+ end
+ end
+
+ def load_simulated_refunds
+ @reimbursement_objects = @reimbursement.simulate
+ end
+
+ def spree_core_gateway_error(error)
+ flash[:error] = error.message
+ redirect_to edit_admin_order_reimbursement_path(parent, @reimbursement)
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/reports_controller.rb b/backend/app/controllers/spree/admin/reports_controller.rb
new file mode 100644
index 00000000000..1fc312f5c1c
--- /dev/null
+++ b/backend/app/controllers/spree/admin/reports_controller.rb
@@ -0,0 +1,65 @@
+module Spree
+ module Admin
+ class ReportsController < Spree::Admin::BaseController
+ respond_to :html
+
+ class << self
+ def available_reports
+ @@available_reports
+ end
+
+ def add_available_report!(report_key, report_description_key = nil)
+ if report_description_key.nil?
+ report_description_key = "#{report_key}_description"
+ end
+ @@available_reports[report_key] = {name: Spree.t(report_key), description: Spree.t(report_description_key)}
+ end
+ end
+
+ def initialize
+ super
+ ReportsController.add_available_report!(:sales_total)
+ end
+
+ def index
+ @reports = ReportsController.available_reports
+ end
+
+ def sales_total
+ params[:q] = {} unless params[:q]
+
+ if params[:q][:completed_at_gt].blank?
+ params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
+ else
+ params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
+ end
+
+ if params[:q] && !params[:q][:completed_at_lt].blank?
+ params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
+ end
+
+ params[:q][:s] ||= "completed_at desc"
+
+ @search = Order.complete.ransack(params[:q])
+ @orders = @search.result
+
+ @totals = {}
+ @orders.each do |order|
+ @totals[order.currency] = { :item_total => ::Money.new(0, order.currency), :adjustment_total => ::Money.new(0, order.currency), :sales_total => ::Money.new(0, order.currency) } unless @totals[order.currency]
+ @totals[order.currency][:item_total] += order.display_item_total.money
+ @totals[order.currency][:adjustment_total] += order.display_adjustment_total.money
+ @totals[order.currency][:sales_total] += order.display_total.money
+ end
+ end
+
+ private
+
+ def model_class
+ Spree::Admin::ReportsController
+ end
+
+ @@available_reports = {}
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/resource_controller.rb b/backend/app/controllers/spree/admin/resource_controller.rb
new file mode 100644
index 00000000000..cff55367234
--- /dev/null
+++ b/backend/app/controllers/spree/admin/resource_controller.rb
@@ -0,0 +1,258 @@
+class Spree::Admin::ResourceController < Spree::Admin::BaseController
+ include Spree::Backend::Callbacks
+
+ helper_method :new_object_url, :edit_object_url, :object_url, :collection_url
+ before_action :load_resource, except: :update_positions
+ rescue_from ActiveRecord::RecordNotFound, :with => :resource_not_found
+
+ respond_to :html
+
+ def new
+ invoke_callbacks(:new_action, :before)
+ respond_with(@object) do |format|
+ format.html { render :layout => !request.xhr? }
+ if request.xhr?
+ format.js { render :layout => false }
+ end
+ end
+ end
+
+ def edit
+ respond_with(@object) do |format|
+ format.html { render :layout => !request.xhr? }
+ if request.xhr?
+ format.js { render :layout => false }
+ end
+ end
+ end
+
+ def update
+ invoke_callbacks(:update, :before)
+ if @object.update_attributes(permitted_resource_params)
+ invoke_callbacks(:update, :after)
+ flash[:success] = flash_message_for(@object, :successfully_updated)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_save }
+ format.js { render :layout => false }
+ end
+ else
+ invoke_callbacks(:update, :fails)
+ respond_with(@object) do |format|
+ format.html do
+ flash.now[:error] = @object.errors.full_messages.join(", ")
+ render action: 'edit'
+ end
+ format.js { render layout: false }
+ end
+ end
+ end
+
+ def create
+ invoke_callbacks(:create, :before)
+ @object.attributes = permitted_resource_params
+ if @object.save
+ invoke_callbacks(:create, :after)
+ flash[:success] = flash_message_for(@object, :successfully_created)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_save }
+ format.js { render :layout => false }
+ end
+ else
+ invoke_callbacks(:create, :fails)
+ respond_with(@object) do |format|
+ format.html do
+ flash.now[:error] = @object.errors.full_messages.join(", ")
+ render action: 'new'
+ end
+ format.js { render layout: false }
+ end
+ end
+ end
+
+ def update_positions
+ ActiveRecord::Base.transaction do
+ params[:positions].each do |id, index|
+ model_class.find(id).set_list_position(index)
+ end
+ end
+
+ respond_to do |format|
+ format.js { render text: 'Ok' }
+ end
+ end
+
+ def destroy
+ invoke_callbacks(:destroy, :before)
+ if @object.destroy
+ invoke_callbacks(:destroy, :after)
+ flash[:success] = flash_message_for(@object, :successfully_removed)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_destroy }
+ format.js { render :partial => "spree/admin/shared/destroy" }
+ end
+ else
+ invoke_callbacks(:destroy, :fails)
+ respond_with(@object) do |format|
+ format.html { redirect_to location_after_destroy }
+ end
+ end
+ end
+
+ protected
+
+ class << self
+ attr_accessor :parent_data
+
+ def belongs_to(model_name, options = {})
+ @parent_data ||= {}
+ @parent_data[:model_name] = model_name
+ @parent_data[:model_class] = model_name.to_s.classify.constantize
+ @parent_data[:find_by] = options[:find_by] || :id
+ end
+ end
+
+ def resource_not_found
+ flash[:error] = flash_message_for(model_class.new, :not_found)
+ redirect_to collection_url
+ end
+
+ def model_class
+ "Spree::#{controller_name.classify}".constantize
+ end
+
+ def model_name
+ parent_data[:model_name].gsub('spree/', '')
+ end
+
+ def object_name
+ controller_name.singularize
+ end
+
+ def load_resource
+ if member_action?
+ @object ||= load_resource_instance
+
+ # call authorize! a third time (called twice already in Admin::BaseController)
+ # this time we pass the actual instance so fine-grained abilities can control
+ # access to individual records, not just entire models.
+ authorize! action, @object
+
+ instance_variable_set("@#{object_name}", @object)
+ else
+ @collection ||= collection
+
+ # note: we don't call authorize here as the collection method should use
+ # CanCan's accessible_by method to restrict the actual records returned
+
+ instance_variable_set("@#{controller_name}", @collection)
+ end
+ end
+
+ def load_resource_instance
+ if new_actions.include?(action)
+ build_resource
+ elsif params[:id]
+ find_resource
+ end
+ end
+
+ def parent_data
+ self.class.parent_data
+ end
+
+ def parent
+ if parent_data.present?
+ @parent ||= parent_data[:model_class].send("find_by_#{parent_data[:find_by]}", params["#{model_name}_id"])
+ instance_variable_set("@#{model_name}", @parent)
+ else
+ nil
+ end
+ end
+
+ def find_resource
+ if parent_data.present?
+ parent.send(controller_name).find(params[:id])
+ else
+ model_class.find(params[:id])
+ end
+ end
+
+ def build_resource
+ if parent_data.present?
+ parent.send(controller_name).build
+ else
+ model_class.new
+ end
+ end
+
+ def collection
+ return parent.send(controller_name) if parent_data.present?
+ if model_class.respond_to?(:accessible_by) && !current_ability.has_block?(params[:action], model_class)
+ model_class.accessible_by(current_ability, action)
+ else
+ model_class.where(nil)
+ end
+ end
+
+ def location_after_destroy
+ collection_url
+ end
+
+ def location_after_save
+ collection_url
+ end
+
+ # URL helpers
+
+ def new_object_url(options = {})
+ if parent_data.present?
+ spree.new_polymorphic_url([:admin, parent, model_class], options)
+ else
+ spree.new_polymorphic_url([:admin, model_class], options)
+ end
+ end
+
+ def edit_object_url(object, options = {})
+ if parent_data.present?
+ spree.send "edit_admin_#{model_name}_#{object_name}_url", parent, object, options
+ else
+ spree.send "edit_admin_#{object_name}_url", object, options
+ end
+ end
+
+ def object_url(object = nil, options = {})
+ target = object ? object : @object
+ if parent_data.present?
+ spree.send "admin_#{model_name}_#{object_name}_url", parent, target, options
+ else
+ spree.send "admin_#{object_name}_url", target, options
+ end
+ end
+
+ def collection_url(options = {})
+ if parent_data.present?
+ spree.polymorphic_url([:admin, parent, model_class], options)
+ else
+ spree.polymorphic_url([:admin, model_class], options)
+ end
+ end
+
+ # Allow all attributes to be updatable.
+ #
+ # Other controllers can, should, override it to set custom logic
+ def permitted_resource_params
+ params[object_name].present? ? params.require(object_name).permit! : ActionController::Parameters.new
+ end
+
+ def collection_actions
+ [:index]
+ end
+
+ def member_action?
+ !collection_actions.include? action
+ end
+
+ def new_actions
+ [:new, :create]
+ end
+end
diff --git a/backend/app/controllers/spree/admin/return_authorization_reasons_controller.rb b/backend/app/controllers/spree/admin/return_authorization_reasons_controller.rb
new file mode 100644
index 00000000000..fd7a7b135d6
--- /dev/null
+++ b/backend/app/controllers/spree/admin/return_authorization_reasons_controller.rb
@@ -0,0 +1,6 @@
+module Spree
+ module Admin
+ class ReturnAuthorizationReasonsController < ResourceController
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/return_authorizations_controller.rb b/backend/app/controllers/spree/admin/return_authorizations_controller.rb
new file mode 100644
index 00000000000..09c0bd4fc96
--- /dev/null
+++ b/backend/app/controllers/spree/admin/return_authorizations_controller.rb
@@ -0,0 +1,51 @@
+module Spree
+ module Admin
+ class ReturnAuthorizationsController < ResourceController
+ belongs_to 'spree/order', :find_by => :number
+
+ before_action :load_form_data, only: [:new, :edit]
+ create.fails :load_form_data
+ update.fails :load_form_data
+
+ def fire
+ @return_authorization.send("#{params[:e]}!")
+ flash[:success] = Spree.t(:return_authorization_updated)
+ redirect_to :back
+ end
+
+ private
+
+ def load_form_data
+ load_return_items
+ load_reimbursement_types
+ load_return_authorization_reasons
+ end
+
+ # To satisfy how nested attributes works we want to create placeholder ReturnItems for
+ # any InventoryUnits that have not already been added to the ReturnAuthorization.
+ def load_return_items
+ all_inventory_units = @return_authorization.order.inventory_units
+ associated_inventory_units = @return_authorization.return_items.map(&:inventory_unit)
+ unassociated_inventory_units = all_inventory_units - associated_inventory_units
+
+ new_return_items = unassociated_inventory_units.map do |new_unit|
+ Spree::ReturnItem.new(inventory_unit: new_unit).tap(&:set_default_pre_tax_amount)
+ end
+
+ @form_return_items = (@return_authorization.return_items + new_return_items).sort_by(&:inventory_unit_id)
+ end
+
+ def load_reimbursement_types
+ @reimbursement_types = Spree::ReimbursementType.accessible_by(current_ability, :read).active
+ end
+
+ def load_return_authorization_reasons
+ @reasons = Spree::ReturnAuthorizationReason.active
+ # Only allow an inactive reason if it's already associated to the RMA
+ if @return_authorization.reason && !@return_authorization.reason.active?
+ @reasons << @return_authorization.reason
+ end
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/return_items_controller.rb b/backend/app/controllers/spree/admin/return_items_controller.rb
new file mode 100644
index 00000000000..9e1f3b941c3
--- /dev/null
+++ b/backend/app/controllers/spree/admin/return_items_controller.rb
@@ -0,0 +1,9 @@
+module Spree
+ module Admin
+ class ReturnItemsController < ResourceController
+ def location_after_save
+ url_for([:edit, :admin, @return_item.customer_return.order, @return_item.customer_return])
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/root_controller.rb b/backend/app/controllers/spree/admin/root_controller.rb
new file mode 100644
index 00000000000..2390765b13d
--- /dev/null
+++ b/backend/app/controllers/spree/admin/root_controller.rb
@@ -0,0 +1,17 @@
+module Spree
+ module Admin
+ class RootController < Spree::Admin::BaseController
+
+ skip_before_filter :authorize_admin
+
+ def index
+ redirect_to admin_root_redirect_path
+ end
+
+ protected
+ def admin_root_redirect_path
+ spree.admin_orders_path
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/search_controller.rb b/backend/app/controllers/spree/admin/search_controller.rb
similarity index 93%
rename from core/app/controllers/spree/admin/search_controller.rb
rename to backend/app/controllers/spree/admin/search_controller.rb
index 49abde0ef13..fd946d62526 100644
--- a/core/app/controllers/spree/admin/search_controller.rb
+++ b/backend/app/controllers/spree/admin/search_controller.rb
@@ -2,7 +2,7 @@ module Spree
module Admin
class SearchController < Spree::Admin::BaseController
# http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
- before_filter :check_json_authenticity, :only => :index
+ before_action :check_json_authenticity, only: :index
respond_to :json
# TODO: Clean this up by moving searching out to user_class_extensions
diff --git a/core/app/controllers/spree/admin/shipping_categories_controller.rb b/backend/app/controllers/spree/admin/shipping_categories_controller.rb
similarity index 100%
rename from core/app/controllers/spree/admin/shipping_categories_controller.rb
rename to backend/app/controllers/spree/admin/shipping_categories_controller.rb
diff --git a/backend/app/controllers/spree/admin/shipping_methods_controller.rb b/backend/app/controllers/spree/admin/shipping_methods_controller.rb
new file mode 100644
index 00000000000..7e07b6233dc
--- /dev/null
+++ b/backend/app/controllers/spree/admin/shipping_methods_controller.rb
@@ -0,0 +1,46 @@
+module Spree
+ module Admin
+ class ShippingMethodsController < ResourceController
+ before_action :load_data, except: :index
+ before_action :set_shipping_category, only: [:create, :update]
+ before_action :set_zones, only: [:create, :update]
+
+ def destroy
+ @object.destroy
+
+ flash[:success] = flash_message_for(@object, :successfully_removed)
+
+ respond_with(@object) do |format|
+ format.html { redirect_to collection_url }
+ format.js { render_js_for_destroy }
+ end
+ end
+
+ private
+
+ def set_shipping_category
+ return true if params["shipping_method"][:shipping_categories] == ""
+ @shipping_method.shipping_categories = Spree::ShippingCategory.where(:id => params["shipping_method"][:shipping_categories])
+ @shipping_method.save
+ params[:shipping_method].delete(:shipping_categories)
+ end
+
+ def set_zones
+ return true if params["shipping_method"][:zones] == ""
+ @shipping_method.zones = Spree::Zone.where(:id => params["shipping_method"][:zones])
+ @shipping_method.save
+ params[:shipping_method].delete(:zones)
+ end
+
+ def location_after_save
+ edit_admin_shipping_method_path(@shipping_method)
+ end
+
+ def load_data
+ @available_zones = Zone.order(:name)
+ @tax_categories = Spree::TaxCategory.order(:name)
+ @calculators = ShippingMethod.calculators.sort_by(&:name)
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/states_controller.rb b/backend/app/controllers/spree/admin/states_controller.rb
new file mode 100644
index 00000000000..cf82bf52718
--- /dev/null
+++ b/backend/app/controllers/spree/admin/states_controller.rb
@@ -0,0 +1,29 @@
+module Spree
+ module Admin
+ class StatesController < ResourceController
+ belongs_to 'spree/country'
+ before_action :load_data
+
+ def index
+ respond_with(@collection) do |format|
+ format.html
+ format.js { render :partial => 'state_list' }
+ end
+ end
+
+ protected
+
+ def location_after_save
+ admin_country_states_url(@country)
+ end
+
+ def collection
+ super.order(:name)
+ end
+
+ def load_data
+ @countries = Country.order(:name)
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/stock_items_controller.rb b/backend/app/controllers/spree/admin/stock_items_controller.rb
new file mode 100644
index 00000000000..3d008f2481c
--- /dev/null
+++ b/backend/app/controllers/spree/admin/stock_items_controller.rb
@@ -0,0 +1,51 @@
+module Spree
+ module Admin
+ class StockItemsController < Spree::Admin::BaseController
+ before_action :determine_backorderable, only: :update
+
+ def update
+ stock_item.save
+ respond_to do |format|
+ format.js { head :ok }
+ end
+ end
+
+ def create
+ variant = Variant.find(params[:variant_id])
+ stock_location = StockLocation.find(params[:stock_location_id])
+ stock_movement = stock_location.stock_movements.build(stock_movement_params)
+ stock_movement.stock_item = stock_location.set_up_stock_item(variant)
+
+ if stock_movement.save
+ flash[:success] = flash_message_for(stock_movement, :successfully_created)
+ else
+ flash[:error] = Spree.t(:could_not_create_stock_movement)
+ end
+
+ redirect_to :back
+ end
+
+ def destroy
+ stock_item.destroy
+
+ respond_with(@stock_item) do |format|
+ format.html { redirect_to :back }
+ format.js
+ end
+ end
+
+ private
+ def stock_movement_params
+ params.require(:stock_movement).permit(permitted_stock_movement_attributes)
+ end
+
+ def stock_item
+ @stock_item ||= StockItem.find(params[:id])
+ end
+
+ def determine_backorderable
+ stock_item.backorderable = params[:stock_item].present? && params[:stock_item][:backorderable].present?
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/stock_locations_controller.rb b/backend/app/controllers/spree/admin/stock_locations_controller.rb
new file mode 100644
index 00000000000..f5fdc0ec662
--- /dev/null
+++ b/backend/app/controllers/spree/admin/stock_locations_controller.rb
@@ -0,0 +1,25 @@
+module Spree
+ module Admin
+ class StockLocationsController < ResourceController
+
+ before_action :set_country, only: :new
+
+ private
+
+ def set_country
+ begin
+ if Spree::Config[:default_country_id].present?
+ @stock_location.country = Spree::Country.find(Spree::Config[:default_country_id])
+ else
+ @stock_location.country = Spree::Country.find_by!(iso: 'US')
+ end
+
+ rescue ActiveRecord::RecordNotFound
+ flash[:error] = Spree.t(:stock_locations_need_a_default_country)
+ redirect_to admin_stock_locations_path and return
+ end
+ end
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/stock_movements_controller.rb b/backend/app/controllers/spree/admin/stock_movements_controller.rb
new file mode 100644
index 00000000000..897a440785f
--- /dev/null
+++ b/backend/app/controllers/spree/admin/stock_movements_controller.rb
@@ -0,0 +1,39 @@
+module Spree
+ module Admin
+ class StockMovementsController < Spree::Admin::BaseController
+ respond_to :html
+ helper_method :stock_location
+
+ def index
+ @stock_movements = stock_location.stock_movements.recent.
+ includes(:stock_item => { :variant => :product }).
+ page(params[:page])
+ end
+
+ def new
+ @stock_movement = stock_location.stock_movements.build
+ end
+
+ def create
+ @stock_movement = stock_location.stock_movements.build(stock_movement_params)
+ @stock_movement.save
+ flash[:success] = flash_message_for(@stock_movement, :successfully_created)
+ redirect_to admin_stock_location_stock_movements_path(stock_location)
+ end
+
+ def edit
+ @stock_movement = StockMovement.find(params[:id])
+ end
+
+ private
+
+ def stock_location
+ @stock_location ||= StockLocation.find(params[:stock_location_id])
+ end
+
+ def stock_movement_params
+ params.require(:stock_movement).permit(:quantity, :stock_item_id, :action)
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/stock_transfers_controller.rb b/backend/app/controllers/spree/admin/stock_transfers_controller.rb
new file mode 100644
index 00000000000..8fb7499c582
--- /dev/null
+++ b/backend/app/controllers/spree/admin/stock_transfers_controller.rb
@@ -0,0 +1,53 @@
+module Spree
+ module Admin
+ class StockTransfersController < Admin::BaseController
+ before_action :load_stock_locations, only: :index
+
+ def index
+ @q = StockTransfer.ransack(params[:q])
+
+ @stock_transfers = @q.result
+ .includes(:stock_movements => { :stock_item => :stock_location })
+ .order('created_at DESC')
+ .page(params[:page])
+ end
+
+ def show
+ @stock_transfer = StockTransfer.find_by_param(params[:id])
+ end
+
+ def new
+
+ end
+
+ def create
+ variants = Hash.new(0)
+ params[:variant].each_with_index do |variant_id, i|
+ variants[variant_id] += params[:quantity][i].to_i
+ end
+
+ stock_transfer = StockTransfer.create(:reference => params[:reference])
+ stock_transfer.transfer(source_location,
+ destination_location,
+ variants)
+
+ flash[:success] = Spree.t(:stock_successfully_transferred)
+ redirect_to admin_stock_transfer_path(stock_transfer)
+ end
+
+ private
+ def load_stock_locations
+ @stock_locations = Spree::StockLocation.active.order_default
+ end
+
+ def source_location
+ @source_location ||= params.has_key?(:transfer_receive_stock) ? nil :
+ StockLocation.find(params[:transfer_source_location_id])
+ end
+
+ def destination_location
+ @destination_location ||= StockLocation.find(params[:transfer_destination_location_id])
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/tax_categories_controller.rb b/backend/app/controllers/spree/admin/tax_categories_controller.rb
similarity index 94%
rename from core/app/controllers/spree/admin/tax_categories_controller.rb
rename to backend/app/controllers/spree/admin/tax_categories_controller.rb
index 187ed6b25de..74e5e83c0b9 100644
--- a/core/app/controllers/spree/admin/tax_categories_controller.rb
+++ b/backend/app/controllers/spree/admin/tax_categories_controller.rb
@@ -2,7 +2,7 @@ module Spree
module Admin
class TaxCategoriesController < ResourceController
def destroy
- if @object.mark_deleted!
+ if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
diff --git a/backend/app/controllers/spree/admin/tax_rates_controller.rb b/backend/app/controllers/spree/admin/tax_rates_controller.rb
new file mode 100644
index 00000000000..15db5a1493b
--- /dev/null
+++ b/backend/app/controllers/spree/admin/tax_rates_controller.rb
@@ -0,0 +1,15 @@
+module Spree
+ module Admin
+ class TaxRatesController < ResourceController
+ before_action :load_data
+
+ private
+
+ def load_data
+ @available_zones = Zone.order(:name)
+ @available_categories = TaxCategory.order(:name)
+ @calculators = TaxRate.calculators.sort_by(&:name)
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/taxonomies_controller.rb b/backend/app/controllers/spree/admin/taxonomies_controller.rb
old mode 100755
new mode 100644
similarity index 93%
rename from core/app/controllers/spree/admin/taxonomies_controller.rb
rename to backend/app/controllers/spree/admin/taxonomies_controller.rb
index 0824538517c..3010db2a52a
--- a/core/app/controllers/spree/admin/taxonomies_controller.rb
+++ b/backend/app/controllers/spree/admin/taxonomies_controller.rb
@@ -5,8 +5,6 @@ class TaxonomiesController < ResourceController
def get_children
@taxons = Taxon.find(params[:parent_id]).children
-
- respond_with(@taxons)
end
private
diff --git a/backend/app/controllers/spree/admin/taxons_controller.rb b/backend/app/controllers/spree/admin/taxons_controller.rb
new file mode 100644
index 00000000000..f73dbea356c
--- /dev/null
+++ b/backend/app/controllers/spree/admin/taxons_controller.rb
@@ -0,0 +1,107 @@
+module Spree
+ module Admin
+ class TaxonsController < Spree::Admin::BaseController
+
+ respond_to :html, :json, :js
+
+ def index
+
+ end
+
+ def search
+ if params[:ids]
+ @taxons = Spree::Taxon.where(:id => params[:ids].split(','))
+ else
+ @taxons = Spree::Taxon.limit(20).ransack(:name_cont => params[:q]).result
+ end
+ end
+
+ def create
+ @taxonomy = Taxonomy.find(params[:taxonomy_id])
+ @taxon = @taxonomy.taxons.build(params[:taxon])
+ if @taxon.save
+ respond_with(@taxon) do |format|
+ format.json {render :json => @taxon.to_json }
+ end
+ else
+ flash[:error] = Spree.t('errors.messages.could_not_create_taxon')
+ respond_with(@taxon) do |format|
+ format.html { redirect_to @taxonomy ? edit_admin_taxonomy_url(@taxonomy) : admin_taxonomies_url }
+ end
+ end
+ end
+
+ def edit
+ @taxonomy = Taxonomy.find(params[:taxonomy_id])
+ @taxon = @taxonomy.taxons.find(params[:id])
+ @permalink_part = @taxon.permalink.split("/").last
+ end
+
+ def update
+ @taxonomy = Taxonomy.find(params[:taxonomy_id])
+ @taxon = @taxonomy.taxons.find(params[:id])
+ parent_id = params[:taxon][:parent_id]
+ new_position = params[:taxon][:position]
+
+ if parent_id
+ @taxon.parent = Taxon.find(parent_id.to_i)
+ end
+
+ if new_position
+ @taxon.child_index = new_position.to_i
+ end
+
+ @taxon.save!
+
+ # regenerate permalink
+ if parent_id
+ @taxon.reload
+ @taxon.set_permalink
+ @taxon.save!
+ @update_children = true
+ end
+
+ if params.key? "permalink_part"
+ parent_permalink = @taxon.permalink.split("/")[0...-1].join("/")
+ parent_permalink += "/" unless parent_permalink.blank?
+ params[:taxon][:permalink] = parent_permalink + params[:permalink_part]
+ end
+ #check if we need to rename child taxons if parent name or permalink changes
+ @update_children = true if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink
+
+ if @taxon.update_attributes(taxon_params)
+ flash[:success] = flash_message_for(@taxon, :successfully_updated)
+ end
+
+ #rename child taxons
+ if @update_children
+ @taxon.descendants.each do |taxon|
+ taxon.reload
+ taxon.set_permalink
+ taxon.save!
+ end
+ end
+
+ respond_with(@taxon) do |format|
+ format.html {redirect_to edit_admin_taxonomy_url(@taxonomy) }
+ format.json {render :json => @taxon.to_json }
+ end
+ end
+
+ def destroy
+ @taxon = Taxon.find(params[:id])
+ @taxon.destroy
+ respond_with(@taxon) { |format| format.json { render :json => '' } }
+ end
+
+ private
+ def taxon_params
+ params.require(:taxon).permit(permitted_params)
+ end
+
+ def permitted_params
+ Spree::PermittedAttributes.taxon_attributes
+ end
+ end
+ end
+end
diff --git a/core/app/controllers/spree/admin/trackers_controller.rb b/backend/app/controllers/spree/admin/trackers_controller.rb
similarity index 100%
rename from core/app/controllers/spree/admin/trackers_controller.rb
rename to backend/app/controllers/spree/admin/trackers_controller.rb
diff --git a/backend/app/controllers/spree/admin/users_controller.rb b/backend/app/controllers/spree/admin/users_controller.rb
new file mode 100644
index 00000000000..7ee8833bf4e
--- /dev/null
+++ b/backend/app/controllers/spree/admin/users_controller.rb
@@ -0,0 +1,159 @@
+module Spree
+ module Admin
+ class UsersController < ResourceController
+ rescue_from Spree::Core::DestroyWithOrdersError, :with => :user_destroy_with_orders_error
+
+ after_action :sign_in_if_change_own_password, only: :update
+
+ # http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
+ before_action :check_json_authenticity, only: :index
+ before_action :load_roles
+
+ def index
+ respond_with(@collection) do |format|
+ format.html
+ format.json { render :json => json_data }
+ end
+ end
+
+ def show
+ redirect_to edit_admin_user_path(@user)
+ end
+
+ def create
+ if params[:user]
+ roles = params[:user].delete("spree_role_ids")
+ end
+
+ @user = Spree.user_class.new(user_params)
+ if @user.save
+
+ if roles
+ @user.spree_roles = roles.reject(&:blank?).collect{|r| Spree::Role.find(r)}
+ end
+
+ flash.now[:success] = Spree.t(:created_successfully)
+ render :edit
+ else
+ render :new
+ end
+ end
+
+ def update
+ if params[:user]
+ roles = params[:user].delete("spree_role_ids")
+ end
+
+ if @user.update_attributes(user_params)
+ if roles
+ @user.spree_roles = roles.reject(&:blank?).collect{|r| Spree::Role.find(r)}
+ end
+ flash.now[:success] = Spree.t(:account_updated)
+ end
+
+ render :edit
+ end
+
+ def addresses
+ if request.put?
+ if @user.update_attributes(user_params)
+ flash.now[:success] = Spree.t(:account_updated)
+ end
+
+ render :addresses
+ end
+ end
+
+ def orders
+ params[:q] ||= {}
+ @search = Spree::Order.reverse_chronological.ransack(params[:q].merge(user_id_eq: @user.id))
+ @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
+ end
+
+ def items
+ params[:q] ||= {}
+ @search = Spree::Order.includes(
+ line_items: {
+ variant: [:product, { option_values: :option_type }]
+ }).ransack(params[:q].merge(user_id_eq: @user.id))
+ @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
+ end
+
+ def generate_api_key
+ if @user.generate_spree_api_key!
+ flash[:success] = Spree.t('api.key_generated')
+ end
+ redirect_to edit_admin_user_path(@user)
+ end
+
+ def clear_api_key
+ if @user.clear_spree_api_key!
+ flash[:success] = Spree.t('api.key_cleared')
+ end
+ redirect_to edit_admin_user_path(@user)
+ end
+
+ def model_class
+ Spree.user_class
+ end
+
+ protected
+
+ def collection
+ return @collection if @collection.present?
+ if request.xhr? && params[:q].present?
+ @collection = Spree.user_class.includes(:bill_address, :ship_address)
+ .where("spree_users.email #{LIKE} :search
+ OR (spree_addresses.firstname #{LIKE} :search AND spree_addresses.id = spree_users.bill_address_id)
+ OR (spree_addresses.lastname #{LIKE} :search AND spree_addresses.id = spree_users.bill_address_id)
+ OR (spree_addresses.firstname #{LIKE} :search AND spree_addresses.id = spree_users.ship_address_id)
+ OR (spree_addresses.lastname #{LIKE} :search AND spree_addresses.id = spree_users.ship_address_id)",
+ { :search => "#{params[:q].strip}%" })
+ .limit(params[:limit] || 100)
+ else
+ @search = Spree.user_class.ransack(params[:q])
+ @collection = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page])
+ end
+ end
+
+ private
+ def user_params
+ params.require(:user).permit(PermittedAttributes.user_attributes |
+ [:spree_role_ids,
+ ship_address_attributes: PermittedAttributes.address_attributes,
+ bill_address_attributes: PermittedAttributes.address_attributes])
+ end
+
+ # handling raise from Spree::Admin::ResourceController#destroy
+ def user_destroy_with_orders_error
+ invoke_callbacks(:destroy, :fails)
+ render :status => :forbidden, :text => Spree.t(:error_user_destroy_with_orders)
+ end
+
+ # Allow different formats of json data to suit different ajax calls
+ def json_data
+ json_format = params[:json_format] or 'default'
+ case json_format
+ when 'basic'
+ collection.map { |u| { 'id' => u.id, 'name' => u.email } }.to_json
+ else
+ address_fields = [:firstname, :lastname, :address1, :address2, :city, :zipcode, :phone, :state_name, :state_id, :country_id]
+ includes = { :only => address_fields , :include => { :state => { :only => :name }, :country => { :only => :name } } }
+
+ collection.to_json(:only => [:id, :email], :include =>
+ { :bill_address => includes, :ship_address => includes })
+ end
+ end
+
+ def sign_in_if_change_own_password
+ if try_spree_current_user == @user && @user.password.present?
+ sign_in(@user, :event => :authentication, :bypass => true)
+ end
+ end
+
+ def load_roles
+ @roles = Spree::Role.all
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/variants_controller.rb b/backend/app/controllers/spree/admin/variants_controller.rb
new file mode 100644
index 00000000000..6d620189e77
--- /dev/null
+++ b/backend/app/controllers/spree/admin/variants_controller.rb
@@ -0,0 +1,49 @@
+module Spree
+ module Admin
+ class VariantsController < ResourceController
+ belongs_to 'spree/product', :find_by => :slug
+ new_action.before :new_before
+ before_action :load_data, only: [:new, :create, :edit, :update]
+
+ # override the destroy method to set deleted_at value
+ # instead of actually deleting the product.
+ def destroy
+ @variant = Variant.find(params[:id])
+ if @variant.destroy
+ flash[:success] = Spree.t('notice_messages.variant_deleted')
+ else
+ flash[:success] = Spree.t('notice_messages.variant_not_deleted')
+ end
+
+ respond_with(@variant) do |format|
+ format.html { redirect_to admin_product_variants_url(params[:product_id]) }
+ format.js { render_js_for_destroy }
+ end
+ end
+
+ protected
+ def new_before
+ @object.attributes = @object.product.master.attributes.except('id', 'created_at', 'deleted_at',
+ 'sku', 'is_master')
+ # Shallow Clone of the default price to populate the price field.
+ @object.default_price = @object.product.master.default_price.clone
+ end
+
+ def collection
+ @deleted = (params.key?(:deleted) && params[:deleted] == "on") ? "checked" : ""
+
+ if @deleted.blank?
+ @collection ||= super
+ else
+ @collection ||= Variant.only_deleted.where(:product_id => parent.id)
+ end
+ @collection
+ end
+
+ private
+ def load_data
+ @tax_categories = TaxCategory.order(:name)
+ end
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/variants_including_master_controller.rb b/backend/app/controllers/spree/admin/variants_including_master_controller.rb
new file mode 100644
index 00000000000..e5047572aa0
--- /dev/null
+++ b/backend/app/controllers/spree/admin/variants_including_master_controller.rb
@@ -0,0 +1,15 @@
+module Spree
+ module Admin
+ class VariantsIncludingMasterController < VariantsController
+
+ def model_class
+ Spree::Variant
+ end
+
+ def object_name
+ "variant"
+ end
+
+ end
+ end
+end
diff --git a/backend/app/controllers/spree/admin/zones_controller.rb b/backend/app/controllers/spree/admin/zones_controller.rb
new file mode 100644
index 00000000000..b566e975fc1
--- /dev/null
+++ b/backend/app/controllers/spree/admin/zones_controller.rb
@@ -0,0 +1,26 @@
+module Spree
+ module Admin
+ class ZonesController < ResourceController
+ before_action :load_data, except: :index
+
+ def new
+ @zone.zone_members.build
+ end
+
+ protected
+
+ def collection
+ params[:q] ||= {}
+ params[:q][:s] ||= "ascend_by_name"
+ @search = super.ransack(params[:q])
+ @zones = @search.result.page(params[:page]).per(params[:per_page])
+ end
+
+ def load_data
+ @countries = Country.order(:name)
+ @states = State.order(:name)
+ @zones = Zone.order(:name)
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/spree/admin/adjustments_helper.rb b/backend/app/helpers/spree/admin/adjustments_helper.rb
new file mode 100644
index 00000000000..40ced24d7fa
--- /dev/null
+++ b/backend/app/helpers/spree/admin/adjustments_helper.rb
@@ -0,0 +1,42 @@
+module Spree
+ module Admin
+ module AdjustmentsHelper
+ def adjustment_state(adjustment)
+ state = adjustment.state.to_sym
+ icon = { closed: 'lock', open: 'unlock' }
+ content_tag(:span, '', class: "fa fa-#{ icon[state] }")
+ end
+
+ def display_adjustable(adjustable)
+ case adjustable
+ when Spree::LineItem
+ display_line_item(adjustable)
+ when Spree::Shipment
+ display_shipment(adjustable)
+ when Spree::Order
+ display_order(adjustable)
+ end
+
+ end
+
+ private
+
+ def display_line_item(line_item)
+ variant = line_item.variant
+ parts = []
+ parts << variant.product.name
+ parts << "(#{variant.options_text})" if variant.options_text.present?
+ parts << line_item.display_total
+ parts.join(" ").html_safe
+ end
+
+ def display_shipment(shipment)
+ "#{Spree.t(:shipment)} ##{shipment.number} #{shipment.display_cost}".html_safe
+ end
+
+ def display_order(order)
+ Spree.t(:order)
+ end
+ end
+ end
+end
diff --git a/core/app/helpers/spree/admin/base_helper.rb b/backend/app/helpers/spree/admin/base_helper.rb
similarity index 82%
rename from core/app/helpers/spree/admin/base_helper.rb
rename to backend/app/helpers/spree/admin/base_helper.rb
index feb613d1a36..0c5f896f8f5 100644
--- a/core/app/helpers/spree/admin/base_helper.rb
+++ b/backend/app/helpers/spree/admin/base_helper.rb
@@ -22,6 +22,14 @@ def error_message_on(object, method, options = {})
end
end
+ def datepicker_field_value(date)
+ unless date.blank?
+ l(date, :format => Spree.t('date_picker.format', default: '%Y/%m/%d'))
+ else
+ nil
+ end
+ end
+
# This method demonstrates the use of the :child_index option to render a
# form partial for, for instance, client side addition of new nested
# records.
@@ -54,7 +62,7 @@ def generate_template(form_builder, method, options = {})
def remove_nested(fields)
out = ''
out << fields.hidden_field(:_destroy) unless fields.object.new_record?
- out << (link_to icon('delete'), "#", :class => 'remove')
+ out << (link_to icon('remove'), "#", :class => 'remove')
out.html_safe
end
@@ -63,7 +71,7 @@ def preference_field_tag(name, value, options)
when :integer
text_field_tag(name, value, preference_field_options(options))
when :boolean
- hidden_field_tag(name, 0) +
+ hidden_field_tag(name, 0, id: "#{name}_hidden") +
check_box_tag(name, 1, value, preference_field_options(options))
when :string
text_field_tag(name, value, preference_field_options(options))
@@ -126,23 +134,16 @@ def preference_fields(object, form)
return unless object.respond_to?(:preferences)
object.preferences.keys.map{ |key|
- form.label("preferred_#{key}", t(key) + ": ") +
+ form.label("preferred_#{key}", Spree.t(key) + ": ") +
preference_field_for(form, "preferred_#{key}", :type => object.preference_type(key))
}.join(" ").html_safe
end
- def product_picker_field(name, value)
- products = Product.with_ids(value.split(','))
- product_names = products.inject({}){|memo,item| memo[item.id] = item.name; memo}
- product_rules = products.collect{ |p| { :id => p.id, :name => p.name } }
- %().html_safe
- end
-
def link_to_add_fields(name, target, options = {})
name = '' if options[:no_text]
- css_classes = options[:class] ? options[:class] + " add_fields" : "add_fields"
- link_to_with_icon('icon-plus', name, 'javascript:', :data => { :target => target }, :class => css_classes)
+ css_classes = options[:class] ? options[:class] + " spree_add_fields" : "spree_add_fields"
+ link_to_with_icon('plus', name, 'javascript:', :data => { :target => target }, :class => css_classes)
end
# renders hidden field and link to remove record using nested_attributes
@@ -150,13 +151,20 @@ def link_to_remove_fields(name, f, options = {})
name = '' if options[:no_text]
options[:class] = '' unless options[:class]
options[:class] += 'no-text with-tip' if options[:no_text]
- link_to_with_icon('icon-trash', name, '#', :class => "remove_fields #{options[:class]}", :data => {:action => 'remove'}, :title => t(:remove)) + f.hidden_field(:_destroy)
+ url = f.object.persisted? ? [:admin, f.object] : '#'
+ link_to_with_icon('trash', name, url, :class => "spree_remove_fields #{options[:class]}", :data => {:action => 'remove'}, :title => Spree.t(:remove)) + f.hidden_field(:_destroy)
end
def spree_dom_id(record)
dom_id(record, 'spree')
end
+ def rails_environments
+ @@rails_environments ||= Dir.glob("#{Rails.root}/config/environments/*.rb")
+ .map { |f| File.basename(f, ".rb") }
+ .sort
+ end
+
private
def attribute_name_for(field_name)
field_name.gsub(' ', '_').downcase
diff --git a/backend/app/helpers/spree/admin/customer_returns_helper.rb b/backend/app/helpers/spree/admin/customer_returns_helper.rb
new file mode 100644
index 00000000000..8880f046e84
--- /dev/null
+++ b/backend/app/helpers/spree/admin/customer_returns_helper.rb
@@ -0,0 +1,9 @@
+module Spree
+ module Admin
+ module CustomerReturnsHelper
+ def reimbursement_types
+ @reimbursement_types ||= Spree::ReimbursementType.accessible_by(current_ability, :read).active
+ end
+ end
+ end
+end
diff --git a/core/app/helpers/spree/admin/general_settings_helper.rb b/backend/app/helpers/spree/admin/general_settings_helper.rb
similarity index 100%
rename from core/app/helpers/spree/admin/general_settings_helper.rb
rename to backend/app/helpers/spree/admin/general_settings_helper.rb
diff --git a/backend/app/helpers/spree/admin/images_helper.rb b/backend/app/helpers/spree/admin/images_helper.rb
new file mode 100644
index 00000000000..36e031b6e9b
--- /dev/null
+++ b/backend/app/helpers/spree/admin/images_helper.rb
@@ -0,0 +1,18 @@
+module Spree
+ module Admin
+ module ImagesHelper
+ def options_text_for(image)
+ if image.viewable.is_a?(Spree::Variant)
+ if image.viewable.is_master?
+ Spree.t(:all)
+ else
+ image.viewable.sku_and_options_text
+ end
+ else
+ Spree.t(:all)
+ end
+ end
+ end
+ end
+end
+
diff --git a/backend/app/helpers/spree/admin/inventory_settings_helper.rb b/backend/app/helpers/spree/admin/inventory_settings_helper.rb
new file mode 100644
index 00000000000..a603f407c9e
--- /dev/null
+++ b/backend/app/helpers/spree/admin/inventory_settings_helper.rb
@@ -0,0 +1,9 @@
+module Spree
+ module Admin
+ module InventorySettingsHelper
+ def show_not(true_or_false)
+ true_or_false ? '' : Spree.t(:not)
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/spree/admin/navigation_helper.rb b/backend/app/helpers/spree/admin/navigation_helper.rb
new file mode 100644
index 00000000000..2cfc89dacd9
--- /dev/null
+++ b/backend/app/helpers/spree/admin/navigation_helper.rb
@@ -0,0 +1,158 @@
+module Spree
+ module Admin
+ module NavigationHelper
+ # Make an admin tab that coveres one or more resources supplied by symbols
+ # Option hash may follow. Valid options are
+ # * :label to override link text, otherwise based on the first resource name (translated)
+ # * :route to override automatically determining the default route
+ # * :match_path as an alternative way to control when the tab is active, /products would match /admin/products, /admin/products/5/variants etc.
+ def tab(*args)
+ options = {:label => args.first.to_s}
+
+ # Return if resource is found and user is not allowed to :admin
+ return '' if klass = klass_for(options[:label]) and cannot?(:admin, klass)
+
+ if args.last.is_a?(Hash)
+ options = options.merge(args.pop)
+ end
+ options[:route] ||= "admin_#{args.first}"
+
+ destination_url = options[:url] || spree.send("#{options[:route]}_path")
+ titleized_label = Spree.t(options[:label], :default => options[:label], :scope => [:admin, :tab]).titleize
+
+ css_classes = []
+
+ if options[:icon]
+ link = link_to_with_icon(options[:icon], titleized_label, destination_url)
+ css_classes << 'tab-with-icon'
+ else
+ link = link_to(titleized_label, destination_url)
+ end
+
+ selected = if options[:match_path].is_a? Regexp
+ request.fullpath =~ options[:match_path]
+ elsif options[:match_path]
+ request.fullpath.starts_with?("#{admin_path}#{options[:match_path]}")
+ else
+ args.include?(controller.controller_name.to_sym)
+ end
+ css_classes << 'selected' if selected
+
+ if options[:css_class]
+ css_classes << options[:css_class]
+ end
+ content_tag('li', link, :class => css_classes.join(' '))
+ end
+
+ # finds class for a given symbol / string
+ #
+ # Example :
+ # :products returns Spree::Product
+ # :my_products returns MyProduct if MyProduct is defined
+ # :my_products returns My::Product if My::Product is defined
+ # if cannot constantize it returns nil
+ # This will allow us to use cancan abilities on tab
+ def klass_for(name)
+ model_name = name.to_s
+
+ ["Spree::#{model_name.classify}", model_name.classify, model_name.gsub('_', '/').classify].find do |t|
+ t.safe_constantize
+ end.try(:safe_constantize)
+ end
+
+ def link_to_clone(resource, options={})
+ options[:data] = {:action => 'clone'}
+ link_to_with_icon('copy', Spree.t(:clone), clone_object_url(resource), options)
+ end
+
+ def link_to_new(resource)
+ options[:data] = {:action => 'new'}
+ link_to_with_icon('plus', Spree.t(:new), edit_object_url(resource))
+ end
+
+ def link_to_edit(resource, options={})
+ url = options[:url] || edit_object_url(resource)
+ options[:data] = {:action => 'edit'}
+ link_to_with_icon('edit', Spree.t(:edit), url, options)
+ end
+
+ def link_to_edit_url(url, options={})
+ options[:data] = {:action => 'edit'}
+ link_to_with_icon('edit', Spree.t(:edit), url, options)
+ end
+
+ def link_to_delete(resource, options={})
+ url = options[:url] || object_url(resource)
+ name = options[:name] || Spree.t(:delete)
+ options[:class] = "delete-resource"
+ options[:data] = { :confirm => Spree.t(:are_you_sure), :action => 'remove' }
+ link_to_with_icon 'trash', name, url, options
+ end
+
+ def link_to_with_icon(icon_name, text, url, options = {})
+ options[:class] = (options[:class].to_s + " fa fa-#{icon_name} icon_link with-tip").strip
+ options[:class] += ' no-text' if options[:no_text]
+ options[:title] = text if options[:no_text]
+ text = options[:no_text] ? '' : raw("#{text}")
+ options.delete(:no_text)
+ link_to(text, url, options)
+ end
+
+ def icon(icon_name)
+ icon_name ? content_tag(:i, '', :class => icon_name) : ''
+ end
+
+ def button(text, icon_name = nil, button_type = 'submit', options={})
+ button_tag(text, options.merge(:type => button_type, :class => "fa fa-#{icon_name} button"))
+ end
+
+ def button_link_to(text, url, html_options = {})
+ if (html_options[:method] &&
+ html_options[:method].to_s.downcase != 'get' &&
+ !html_options[:remote])
+ form_tag(url, :method => html_options.delete(:method)) do
+ button(text, html_options.delete(:icon), nil, html_options)
+ end
+ else
+ if html_options['data-update'].nil? && html_options[:remote]
+ object_name, action = url.split('/')[-2..-1]
+ html_options['data-update'] = [action, object_name.singularize].join('_')
+ end
+
+ html_options.delete('data-update') unless html_options['data-update']
+
+ html_options[:class] = 'button'
+
+ if html_options[:icon]
+ html_options[:class] += " fa fa-#{html_options[:icon]}"
+ end
+ link_to(text_for_button_link(text, html_options), url, html_options)
+ end
+ end
+
+ def text_for_button_link(text, html_options)
+ s = ''
+ s << text
+ raw(s)
+ end
+
+ def configurations_menu_item(link_text, url, description = '')
+ %(
+
#{link_to(link_text, url)}
+
#{description}
+
+ ).html_safe
+ end
+
+ def configurations_sidebar_menu_item(link_text, url, options = {})
+ is_active = url.ends_with?(controller.controller_name) ||
+ url.ends_with?("#{controller.controller_name}/edit") ||
+ url.ends_with?("#{controller.controller_name.singularize}/edit")
+ options.merge!(:class => is_active ? 'active' : nil)
+ content_tag(:li, options) do
+ link_to(link_text, url)
+ end
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/spree/admin/orders_helper.rb b/backend/app/helpers/spree/admin/orders_helper.rb
new file mode 100644
index 00000000000..6570aa2d09e
--- /dev/null
+++ b/backend/app/helpers/spree/admin/orders_helper.rb
@@ -0,0 +1,65 @@
+module Spree
+ module Admin
+ module OrdersHelper
+ # Renders all the extension partials that may have been specified in the extensions
+ def event_links
+ links = []
+ @order_events.sort.each do |event|
+ if @order.send("can_#{event}?")
+ links << button_link_to(Spree.t(event), [event, :admin, @order],
+ :method => :put,
+ :icon => "#{event}",
+ :data => { :confirm => Spree.t(:order_sure_want_to, :event => Spree.t(event)) })
+ end
+ end
+ links.join(' ').html_safe
+ end
+
+ def line_item_shipment_price(line_item, quantity)
+ Spree::Money.new(line_item.price * quantity, { currency: line_item.currency })
+ end
+
+ def avs_response_code
+ {
+ "A" => "Street address matches, but 5-digit and 9-digit postal code do not match.",
+ "B" => "Street address matches, but postal code not verified.",
+ "C" => "Street address and postal code do not match.",
+ "D" => "Street address and postal code match. ",
+ "E" => "AVS data is invalid or AVS is not allowed for this card type.",
+ "F" => "Card member's name does not match, but billing postal code matches.",
+ "G" => "Non-U.S. issuing bank does not support AVS.",
+ "H" => "Card member's name does not match. Street address and postal code match.",
+ "I" => "Address not verified.",
+ "J" => "Card member's name, billing address, and postal code match.",
+ "K" => "Card member's name matches but billing address and billing postal code do not match.",
+ "L" => "Card member's name and billing postal code match, but billing address does not match.",
+ "M" => "Street address and postal code match. ",
+ "N" => "Street address and postal code do not match.",
+ "O" => "Card member's name and billing address match, but billing postal code does not match.",
+ "P" => "Postal code matches, but street address not verified.",
+ "Q" => "Card member's name, billing address, and postal code match.",
+ "R" => "System unavailable.",
+ "S" => "Bank does not support AVS.",
+ "T" => "Card member's name does not match, but street address matches.",
+ "U" => "Address information unavailable. Returned if the U.S. bank does not support non-U.S. AVS or if the AVS in a U.S. bank is not functioning properly.",
+ "V" => "Card member's name, billing address, and billing postal code match.",
+ "W" => "Street address does not match, but 9-digit postal code matches.",
+ "X" => "Street address and 9-digit postal code match.",
+ "Y" => "Street address and 5-digit postal code match.",
+ "Z" => "Street address does not match, but 5-digit postal code matches."
+ }
+ end
+
+ def cvv_response_code
+ {
+ "M" => "CVV2 Match",
+ "N" => "CVV2 No Match",
+ "P" => "Not Processed",
+ "S" => "Issuer indicates that CVV2 data should be present on the card, but the merchant has indicated data is not present on the card",
+ "U" => "Issuer has not certified for CVV2 or Issuer has not provided Visa with the CVV2 encryption keys",
+ "" => "Transaction failed because wrong CVV2 number was entered or no CVV2 number was entered"
+ }
+ end
+ end
+ end
+end
diff --git a/core/app/helpers/spree/admin/payments_helper.rb b/backend/app/helpers/spree/admin/payments_helper.rb
similarity index 100%
rename from core/app/helpers/spree/admin/payments_helper.rb
rename to backend/app/helpers/spree/admin/payments_helper.rb
diff --git a/core/app/helpers/spree/admin/products_helper.rb b/backend/app/helpers/spree/admin/products_helper.rb
similarity index 91%
rename from core/app/helpers/spree/admin/products_helper.rb
rename to backend/app/helpers/spree/admin/products_helper.rb
index bb5ab8aa044..30b07def3b9 100644
--- a/core/app/helpers/spree/admin/products_helper.rb
+++ b/backend/app/helpers/spree/admin/products_helper.rb
@@ -7,7 +7,7 @@ def taxon_options_for(product)
content_tag(:option,
:value => taxon.id,
:selected => ('selected' if selected)) do
- (taxon.ancestors.map(&:name) + [taxon.name]).join(" -> ")
+ (taxon.ancestors.pluck(:name) + [taxon.name]).join(" -> ")
end
end.join("").html_safe
end
diff --git a/backend/app/helpers/spree/admin/reimbursement_type_helper.rb b/backend/app/helpers/spree/admin/reimbursement_type_helper.rb
new file mode 100644
index 00000000000..43ddab201ff
--- /dev/null
+++ b/backend/app/helpers/spree/admin/reimbursement_type_helper.rb
@@ -0,0 +1,9 @@
+module Spree
+ module Admin
+ module ReimbursementTypeHelper
+ def reimbursement_type_name(reimbursement_type)
+ reimbursement_type.present? ? reimbursement_type.name.humanize : ''
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/spree/admin/reimbursements_helper.rb b/backend/app/helpers/spree/admin/reimbursements_helper.rb
new file mode 100644
index 00000000000..99890c933ef
--- /dev/null
+++ b/backend/app/helpers/spree/admin/reimbursements_helper.rb
@@ -0,0 +1,14 @@
+module Spree
+ module Admin
+ module ReimbursementsHelper
+ def reimbursement_status_color(reimbursement)
+ case reimbursement.reimbursement_status
+ when 'reimbursed' then 'success'
+ when 'pending' then 'notice'
+ when 'errored' then 'error'
+ else raise "unknown reimbursement status: #{reimbursement.reimbursement_status}"
+ end
+ end
+ end
+ end
+end
diff --git a/backend/app/helpers/spree/admin/stock_locations_helper.rb b/backend/app/helpers/spree/admin/stock_locations_helper.rb
new file mode 100644
index 00000000000..2fe8daf4194
--- /dev/null
+++ b/backend/app/helpers/spree/admin/stock_locations_helper.rb
@@ -0,0 +1,15 @@
+module Spree
+ module Admin
+ module StockLocationsHelper
+ def display_name(stock_location)
+ name_parts = [stock_location.admin_name, stock_location.name]
+ name_parts.delete_if(&:blank?)
+ name_parts.join(' / ')
+ end
+
+ def state(stock_location)
+ stock_location.active? ? 'active' : 'inactive'
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/backend/app/helpers/spree/admin/stock_movements_helper.rb b/backend/app/helpers/spree/admin/stock_movements_helper.rb
new file mode 100644
index 00000000000..f4b96e4242b
--- /dev/null
+++ b/backend/app/helpers/spree/admin/stock_movements_helper.rb
@@ -0,0 +1,24 @@
+module Spree
+ module Admin
+ module StockMovementsHelper
+ def pretty_originator(stock_movement)
+ if stock_movement.originator.respond_to?(:number)
+ if stock_movement.originator.respond_to?(:order)
+ link_to stock_movement.originator.number, [:edit, :admin, stock_movement.originator.order]
+ else
+ stock_movement.originator.number
+ end
+ else
+ ""
+ end
+ end
+
+ def display_variant(stock_movement)
+ variant = stock_movement.stock_item.variant
+ output = variant.name
+ output += " (#{variant.options_text})" unless variant.options_text.blank?
+ output.html_safe
+ end
+ end
+ end
+end
diff --git a/core/app/helpers/spree/admin/tables_helper.rb b/backend/app/helpers/spree/admin/tables_helper.rb
similarity index 100%
rename from core/app/helpers/spree/admin/tables_helper.rb
rename to backend/app/helpers/spree/admin/tables_helper.rb
diff --git a/core/app/helpers/spree/admin/taxons_helper.rb b/backend/app/helpers/spree/admin/taxons_helper.rb
similarity index 100%
rename from core/app/helpers/spree/admin/taxons_helper.rb
rename to backend/app/helpers/spree/admin/taxons_helper.rb
diff --git a/backend/app/helpers/spree/promotion_rules_helper.rb b/backend/app/helpers/spree/promotion_rules_helper.rb
new file mode 100644
index 00000000000..62352e2d4be
--- /dev/null
+++ b/backend/app/helpers/spree/promotion_rules_helper.rb
@@ -0,0 +1,13 @@
+module Spree
+ module PromotionRulesHelper
+
+ def options_for_promotion_rule_types(promotion)
+ existing = promotion.rules.map { |rule| rule.class.name }
+ rule_names = Rails.application.config.spree.promotions.rules.map(&:name).reject{ |r| existing.include? r }
+ options = rule_names.map { |name| [ Spree.t("promotion_rule_types.#{name.demodulize.underscore}.name"), name] }
+ options_for_select(options)
+ end
+
+ end
+end
+
diff --git a/backend/app/models/spree/backend_configuration.rb b/backend/app/models/spree/backend_configuration.rb
new file mode 100644
index 00000000000..98feb9ed759
--- /dev/null
+++ b/backend/app/models/spree/backend_configuration.rb
@@ -0,0 +1,21 @@
+module Spree
+ class BackendConfiguration < Preferences::Configuration
+ preference :locale, :string, default: Rails.application.config.i18n.default_locale
+
+ ORDER_TABS ||= [:orders, :payments, :creditcard_payments,
+ :shipments, :credit_cards, :return_authorizations,
+ :customer_returns, :adjustments, :customer_details]
+ PRODUCT_TABS ||= [:products, :option_types, :properties, :prototypes,
+ :variants, :product_properties, :taxonomies,
+ :taxons]
+ REPORT_TABS ||= [:reports]
+ CONFIGURATION_TABS ||= [:configurations, :general_settings, :tax_categories,
+ :tax_rates, :zones, :countries, :states,
+ :payment_methods, :shipping_methods,
+ :shipping_categories, :stock_transfers,
+ :stock_locations, :trackers, :refund_reasons,
+ :reimbursement_types, :return_authorization_reasons]
+ PROMOTION_TABS ||= [:promotions, :promotion_categories]
+ USER_TABS ||= [:users]
+ end
+end
diff --git a/backend/app/views/spree/admin/adjustments/_adjustment.html.erb b/backend/app/views/spree/admin/adjustments/_adjustment.html.erb
new file mode 100644
index 00000000000..3d5c73653a8
--- /dev/null
+++ b/backend/app/views/spree/admin/adjustments/_adjustment.html.erb
@@ -0,0 +1,16 @@
+<%
+ @edit_url = edit_admin_order_adjustment_path(@order, adjustment)
+ @delete_url = admin_order_adjustment_path(@order, adjustment)
+%>
+
\ No newline at end of file
diff --git a/backend/app/views/spree/admin/adjustments/_adjustments_table.html.erb b/backend/app/views/spree/admin/adjustments/_adjustments_table.html.erb
new file mode 100644
index 00000000000..3cd766fe328
--- /dev/null
+++ b/backend/app/views/spree/admin/adjustments/_adjustments_table.html.erb
@@ -0,0 +1,25 @@
+
\ No newline at end of file
diff --git a/backend/app/views/spree/admin/option_types/_option_value_fields.html.erb b/backend/app/views/spree/admin/option_types/_option_value_fields.html.erb
new file mode 100644
index 00000000000..2ebfaaf0a2e
--- /dev/null
+++ b/backend/app/views/spree/admin/option_types/_option_value_fields.html.erb
@@ -0,0 +1,11 @@
+
+<% end %>
\ No newline at end of file
diff --git a/backend/app/views/spree/admin/orders/_line_items_edit_form.html.erb b/backend/app/views/spree/admin/orders/_line_items_edit_form.html.erb
new file mode 100644
index 00000000000..2e53a78e04c
--- /dev/null
+++ b/backend/app/views/spree/admin/orders/_line_items_edit_form.html.erb
@@ -0,0 +1,40 @@
+
+<% end %>
diff --git a/core/app/views/spree/admin/payments/_bill_address_form.html.erb b/backend/app/views/spree/admin/payments/_bill_address_form.html.erb
similarity index 79%
rename from core/app/views/spree/admin/payments/_bill_address_form.html.erb
rename to backend/app/views/spree/admin/payments/_bill_address_form.html.erb
index 6ae4433c660..aa6ce294f30 100644
--- a/core/app/views/spree/admin/payments/_bill_address_form.html.erb
+++ b/backend/app/views/spree/admin/payments/_bill_address_form.html.erb
@@ -2,7 +2,7 @@
<% order_form.fields_for :checkout do |checkout_form| %>
<% checkout_form.fields_for :bill_address do |ba_form| %>
- <%= render :partial => 'spree/admin/shared/address_form', :locals => { :f => ba_form, :name => t(:billing_address) } %>
+ <%= render :partial => 'spree/admin/shared/address_form', :locals => { :f => ba_form, :name => Spree.t(:billing_address) } %>
<% end %>
<% end %>
diff --git a/backend/app/views/spree/admin/payments/_capture_events.html.erb b/backend/app/views/spree/admin/payments/_capture_events.html.erb
new file mode 100644
index 00000000000..e4768f9938f
--- /dev/null
+++ b/backend/app/views/spree/admin/payments/_capture_events.html.erb
@@ -0,0 +1,19 @@
+<% if @payment.capture_events.exists? %>
+
<%= Spree.t(:capture_events) %>
+
+
+
+
<%= "#{Spree.t('date')}/#{Spree.t('time')}" %>
+
<%= Spree.t(:amount) %>
+
+
+
+ <% @payment.capture_events.each do |capture_event| %>
+
+
<%= pretty_time(capture_event.created_at) %>
+
<%= capture_event.display_amount %>
+
+ <% end %>
+
+
+<% end %>
\ No newline at end of file
diff --git a/backend/app/views/spree/admin/payments/_form.html.erb b/backend/app/views/spree/admin/payments/_form.html.erb
new file mode 100644
index 00000000000..07c2ceed890
--- /dev/null
+++ b/backend/app/views/spree/admin/payments/_form.html.erb
@@ -0,0 +1,36 @@
+
+
+<%= render 'spree/admin/payments/capture_events' %>
\ No newline at end of file
diff --git a/core/app/views/spree/admin/payments/source_forms/_check.html.erb b/backend/app/views/spree/admin/payments/source_forms/_check.html.erb
similarity index 100%
rename from core/app/views/spree/admin/payments/source_forms/_check.html.erb
rename to backend/app/views/spree/admin/payments/source_forms/_check.html.erb
diff --git a/backend/app/views/spree/admin/payments/source_forms/_gateway.html.erb b/backend/app/views/spree/admin/payments/source_forms/_gateway.html.erb
new file mode 100644
index 00000000000..d6ec4d73984
--- /dev/null
+++ b/backend/app/views/spree/admin/payments/source_forms/_gateway.html.erb
@@ -0,0 +1,60 @@
+
+
+ <% if previous_cards.any? %>
+ <% previous_cards.each do |card| %>
+
+ <% end %>
+
+ <% end %>
+
\ No newline at end of file
diff --git a/core/app/views/spree/shared/unauthorized.html.erb b/backend/app/views/spree/admin/promotions/rules/_one_use_per_user.html.erb
similarity index 100%
rename from core/app/views/spree/shared/unauthorized.html.erb
rename to backend/app/views/spree/admin/promotions/rules/_one_use_per_user.html.erb
diff --git a/backend/app/views/spree/admin/promotions/rules/_product.html.erb b/backend/app/views/spree/admin/promotions/rules/_product.html.erb
new file mode 100644
index 00000000000..9f4e0eb294a
--- /dev/null
+++ b/backend/app/views/spree/admin/promotions/rules/_product.html.erb
@@ -0,0 +1,9 @@
+
diff --git a/core/app/views/spree/admin/payment_methods/_form.html.erb b/core/app/views/spree/admin/payment_methods/_form.html.erb
deleted file mode 100644
index aa35d8c891e..00000000000
--- a/core/app/views/spree/admin/payment_methods/_form.html.erb
+++ /dev/null
@@ -1,53 +0,0 @@
-<% # Usage of old-style form helpers in this file is INTENTIONAL
- # For reasons, see commit 3e981c7. %>
-
-
- <% order.adjustments.eligible.each do |adjustment| %>
- <% next if (adjustment.originator_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
-
-
<%= adjustment.label %>:
-
<%= adjustment.display_amount %>
-
- <% end %>
-
-
-
<%= t(:order_total) %>:
-
<%= @order.display_total %>
-
- <% if order.price_adjustment_totals.present? %>
-
- <% @order.price_adjustment_totals.keys.each do |key| %>
-
+ <% end %>
+ <% end %>
+ <% @order.adjustments.eligible.each do |adjustment| %>
+ <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
+
+ Please remember to configure all of the emails that Spree has provided to your needs.
+ Spree comes shipped with Ink
+ prepackaged, but you can use your own version. Ink is not placed in the asset pipeline.
+
+
+
+ Also take note that Gmail does not support <style> tags.
+ Therefore, you will need a gem that will be able to remove your <style>
+ tags and place them inline. Gmail only supports inline styles. We use
+ Premailer for Rails by default.
+
+
+
+
+
+
diff --git a/core/app/views/spree/test_mailer/test_email.text.erb b/core/app/views/spree/test_mailer/test_email.text.erb
index 9944b6faef4..3491d6fbf3f 100644
--- a/core/app/views/spree/test_mailer/test_email.text.erb
+++ b/core/app/views/spree/test_mailer/test_email.text.erb
@@ -1,4 +1,4 @@
-<%= t('test_mailer.test_email.greeting') %>
+<%= Spree.t('test_mailer.test_email.greeting') %>
================
-<%= t('test_mailer.test_email.message') %>
+<%= Spree.t('test_mailer.test_email.message') %>
diff --git a/core/config/initializers/check_for_orphaned_preferences.rb b/core/config/initializers/check_for_orphaned_preferences.rb
deleted file mode 100644
index 5a3204bd249..00000000000
--- a/core/config/initializers/check_for_orphaned_preferences.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-begin
- ActiveRecord::Base.connection.execute("SELECT owner_id, owner_type, name, value FROM spree_preferences WHERE 'key' IS NULL").each do |pref|
- warn "[WARNING] Orphaned preference `#{pref[2]}` with value `#{pref[3]}` for #{pref[1]} with id of: #{pref[0]}, you should reset the preference value manually."
- end
-rescue
-end
diff --git a/core/config/initializers/friendly_id.rb b/core/config/initializers/friendly_id.rb
new file mode 100644
index 00000000000..69ecb376b19
--- /dev/null
+++ b/core/config/initializers/friendly_id.rb
@@ -0,0 +1,88 @@
+# FriendlyId Global Configuration
+#
+# Use this to set up shared configuration options for your entire application.
+# Any of the configuration options shown here can also be applied to single
+# models by passing arguments to the `friendly_id` class method or defining
+# methods in your model.
+#
+# To learn more, check out the guide:
+#
+# http://norman.github.io/friendly_id/file.Guide.html
+
+FriendlyId.defaults do |config|
+ # ## Reserved Words
+ #
+ # Some words could conflict with Rails's routes when used as slugs, or are
+ # undesirable to allow as slugs. Edit this list as needed for your app.
+ config.use :reserved
+
+ config.reserved_words = %w(new edit index session login logout users admin
+ stylesheets assets javascripts images)
+
+ # ## Friendly Finders
+ #
+ # Uncomment this to use friendly finders in all models. By default, if
+ # you wish to find a record by its friendly id, you must do:
+ #
+ # MyModel.friendly.find('foo')
+ #
+ # If you uncomment this, you can do:
+ #
+ # MyModel.find('foo')
+ #
+ # This is significantly more convenient but may not be appropriate for
+ # all applications, so you must explicity opt-in to this behavior. You can
+ # always also configure it on a per-model basis if you prefer.
+ #
+ # Something else to consider is that using the :finders addon boosts
+ # performance because it will avoid Rails-internal code that makes runtime
+ # calls to `Module.extend`.
+ #
+ # config.use :finders
+ #
+ # ## Slugs
+ #
+ # Most applications will use the :slugged module everywhere. If you wish
+ # to do so, uncomment the following line.
+ #
+ # config.use :slugged
+ #
+ # By default, FriendlyId's :slugged addon expects the slug column to be named
+ # 'slug', but you can change it if you wish.
+ #
+ # config.slug_column = 'slug'
+ #
+ # When FriendlyId can not generate a unique ID from your base method, it appends
+ # a UUID, separated by a single dash. You can configure the character used as the
+ # separator. If you're upgrading from FriendlyId 4, you may wish to replace this
+ # with two dashes.
+ #
+ # config.sequence_separator = '-'
+ #
+ # ## Tips and Tricks
+ #
+ # ### Controlling when slugs are generated
+ #
+ # As of FriendlyId 5.0, new slugs are generated only when the slug field is
+ # nil, but if you're using a column as your base method can change this
+ # behavior by overriding the `should_generate_new_friendly_id` method that
+ # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
+ # more like 4.0.
+ #
+ # config.use Module.new {
+ # def should_generate_new_friendly_id?
+ # slug.blank? || _changed?
+ # end
+ # }
+ #
+ # FriendlyId uses Rails's `parameterize` method to generate slugs, but for
+ # languages that don't use the Roman alphabet, that's not usually suffient. Here
+ # we use the Babosa library to transliterate Russian Cyrillic slugs to ASCII. If
+ # you use this, don't forget to add "babosa" to your Gemfile.
+ #
+ # config.use Module.new {
+ # def normalize_friendly_id(text)
+ # text.to_slug.normalize! :transliterations => [:russian, :latin]
+ # end
+ # }
+end
diff --git a/core/config/initializers/premailer_assets.rb b/core/config/initializers/premailer_assets.rb
new file mode 100644
index 00000000000..ea48309b687
--- /dev/null
+++ b/core/config/initializers/premailer_assets.rb
@@ -0,0 +1 @@
+Rails.application.config.assets.precompile += %w( ink.css )
diff --git a/core/config/initializers/spree.rb b/core/config/initializers/spree.rb
deleted file mode 100644
index e1d4845a06f..00000000000
--- a/core/config/initializers/spree.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'mail'
-
-# Spree Configuration
-SESSION_KEY = '_spree_session_id'
-
-LIKE = ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE'
diff --git a/core/config/initializers/user_class_extensions.rb b/core/config/initializers/user_class_extensions.rb
index a3c002f1253..24fd75fe7bb 100644
--- a/core/config/initializers/user_class_extensions.rb
+++ b/core/config/initializers/user_class_extensions.rb
@@ -1,24 +1,39 @@
Spree::Core::Engine.config.to_prepare do
if Spree.user_class
Spree.user_class.class_eval do
- include Spree::Core::UserBanners
+
+ include Spree::UserApiAuthentication
+ include Spree::UserReporting
+
has_and_belongs_to_many :spree_roles,
- :join_table => 'spree_roles_users',
- :foreign_key => "user_id",
- :class_name => "Spree::Role"
+ join_table: 'spree_roles_users',
+ foreign_key: "user_id",
+ class_name: "Spree::Role"
+
+ has_many :spree_orders, foreign_key: "user_id", class_name: "Spree::Order"
+
+ belongs_to :ship_address, class_name: 'Spree::Address'
+ belongs_to :bill_address, class_name: 'Spree::Address'
- has_many :spree_orders, :foreign_key => "user_id", :class_name => "Spree::Order"
+ def self.ransackable_associations(auth_object=nil)
+ %w[bill_address ship_address]
+ end
- belongs_to :ship_address, :class_name => 'Spree::Address'
- belongs_to :bill_address, :class_name => 'Spree::Address'
+ def self.ransackable_attributes(auth_object=nil)
+ %w[id email]
+ end
# has_spree_role? simply needs to return true or false whether a user has a role or not.
def has_spree_role?(role_in_question)
- spree_roles.where(:name => role_in_question.to_s).any?
+ spree_roles.where(name: role_in_question.to_s).any?
end
def last_incomplete_spree_order
- spree_orders.incomplete.order('created_at DESC').last
+ spree_orders.incomplete.order('created_at DESC').first
+ end
+
+ def analytics_id
+ id
end
end
end
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index c17eabbe539..da6238bee1d 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -1,97 +1,101 @@
----
en:
- no: "No"
- yes: "Yes"
- a_copy_of_all_mail_will_be_sent_to_the_following_addresses: A copy of all mail be sent to the following addresses
- abbreviation: Abbreviation
- access_denied: "Access Denied"
- account: Account
- account_updated: "Account updated!"
- action: Action
- actions:
- create: Create
- destroy: Destroy
- list: List
- listing: Listing
- new: New
- update: Update
- cancel: Cancel
- active: "Active"
- activate: "Activate"
activerecord:
attributes:
spree/address:
address1: Address
- address2: "Address (contd.)"
+ address2: Address (contd.)
city: City
- country: "Country"
- firstname: "First Name"
- lastname: "Last Name"
+ country: Country
+ firstname: First Name
+ lastname: Last Name
phone: Phone
- state: "State"
- zipcode: "Zip Code"
+ state: State
+ zipcode: Zip Code
+ spree/calculator/tiered_flat_rate:
+ preferred_base_amount: Base Amount
+ preferred_tiers: Tiers
+ spree/calculator/tiered_percent:
+ preferred_base_percent: Base Percent
+ preferred_tiers: Tiers
spree/country:
iso: ISO
iso3: ISO3
- iso_name: "ISO Name"
+ iso_name: ISO Name
name: Name
- numcode: "ISO Code"
+ numcode: ISO Code
spree/credit_card:
+ base: ''
cc_type: Type
month: Month
number: Number
- verification_value: "Verification Value"
+ verification_value: Verification Value
year: Year
+ name: Name
spree/inventory_unit:
state: State
spree/line_item:
price: Price
quantity: Quantity
+ spree/option_type:
+ name: Name
+ presentation: Presentation
spree/order:
- checkout_complete: "Checkout Complete"
- completed_at: "Completed At"
- ip_address: "IP Address"
- item_total: "Item Total"
- number: Number
- special_instructions: "Special Instructions"
- state: State
- total: Total
+ checkout_complete: Checkout Complete
+ completed_at: Completed At
+ coupon_code: Coupon Code
created_at: Order Date
+ email: Customer E-Mail
+ ip_address: IP Address
+ item_total: Item Total
+ number: Number
payment_state: Payment State
shipment_state: Shipment State
- email: Customer E-Mail
+ special_instructions: Special Instructions
+ state: State
+ total: Total
+ considered_risky: Risky
spree/order/bill_address:
- address1: "Billing address street"
- city: "Billing address city"
- firstname: "Billing address first name"
- lastname: "Billing address last name"
- phone: "Billing address phone"
- state: "Billing address state"
- zipcode: "Billing address zipcode"
+ address1: Billing address street
+ city: Billing address city
+ firstname: Billing address first name
+ lastname: Billing address last name
+ phone: Billing address phone
+ state: Billing address state
+ zipcode: Billing address zipcode
spree/order/ship_address:
- address1: "Shipping address street"
- city: "Shipping address city"
- firstname: "Shipping address first name"
- lastname: "Shipping address last name"
- phone: "Shipping address phone"
- state: "Shipping address state"
- zipcode: "Shipping address zipcode"
- spree/option_type:
- name: Name
- presentation: Presentation
+ address1: Shipping address street
+ city: Shipping address city
+ firstname: Shipping address first name
+ lastname: Shipping address last name
+ phone: Shipping address phone
+ state: Shipping address state
+ zipcode: Shipping address zipcode
+ spree/payment:
+ amount: Amount
spree/payment_method:
name: Name
spree/product:
- available_on: "Available On"
- cost_currency: "Cost Currency"
- cost_price: "Cost Price"
+ available_on: Available On
+ cost_currency: Cost Currency
+ cost_price: Cost Price
+ description: Description
+ master_price: Master Price
+ name: Name
+ on_hand: On Hand
+ shipping_category: Shipping Category
+ tax_category: Tax Category
+ spree/promotion:
+ advertise: Advertise
+ code: Code
description: Description
- master_price: "Master Price"
+ event_name: Event Name
+ expires_at: Expires At
+ name: Name
+ path: Path
+ starts_at: Starts At
+ usage_limit: Usage Limit
+ spree/promotion_category:
name: Name
- on_hand: "On Hand"
- on_demand: "On Demand"
- shipping_category: "Shipping Category"
- tax_category: "Tax Category"
spree/property:
name: Name
presentation: Presentation
@@ -104,6 +108,13 @@ en:
spree/state:
abbr: Abbreviation
name: Name
+ spree/store:
+ url: Site URL
+ meta_description: Meta Description
+ meta_keywords: Meta Keywords
+ seo_title: Seo Title
+ name: Site Name
+ mail_from_address: Mail From Address
spree/tax_category:
description: Description
name: Name
@@ -119,11 +130,11 @@ en:
name: Name
spree/user:
email: Email
- password: "Password"
- password_confirmation: "Password Confirmation"
+ password: Password
+ password_confirmation: Password Confirmation
spree/variant:
- cost_currency: "Cost Currency"
- cost_price: "Cost Price"
+ cost_currency: Cost Currency
+ cost_price: Cost Price
depth: Depth
height: Height
price: Price
@@ -137,45 +148,66 @@ en:
spree/address:
one: Address
other: Addresses
- spree/cheque_payment:
- one: Cheque Payment
- other: Cheque Payments
spree/country:
one: Country
other: Countries
spree/credit_card:
- one: "Credit Card"
- other: "Credit Cards"
- spree/creditcard_payment:
- one: "Credit Card Payment"
- other: "Credit Card Payments"
- spree/creditcard_txn:
- one: "Credit Card Transaction"
- other: "Credit Card Transactions"
+ one: Credit Card
+ other: Credit Cards
+ spree/customer_return:
+ one: Customer Return
+ other: Customer Returns
spree/inventory_unit:
- one: "Inventory Unit"
- other: "Inventory Units"
+ one: Inventory Unit
+ other: Inventory Units
spree/line_item:
- one: "Line Item"
- other: "Line Items"
+ one: Line Item
+ other: Line Items
+ spree/option_type:
+ one: Option Type
+ other: Option Types
+ spree/option_value:
+ one: Option Value
+ other: Option Values
spree/order:
one: Order
other: Orders
spree/payment:
one: Payment
other: Payments
+ spree/payment_method:
+ one: Payment Method
+ other: Payment Methods
spree/product:
one: Product
other: Products
+ spree/promotion:
+ one: Promotion
+ other: Promotions
+ spree/promotion_category:
+ one: Promotion Category
+ other: Promotion Categories
spree/property:
one: Property
other: Properties
spree/prototype:
one: Prototype
other: Prototypes
+ spree/refund_reason:
+ one: Refund Reason
+ other: Refund Reasons
+ spree/reimbursement:
+ one: Reimbursement
+ other: Reimbursements
+ spree/reimbursement_type:
+ one: Reimbursement Type
+ other: Reimbursement Types
spree/return_authorization:
one: Return Authorization
other: Return Authorizations
+ spree/return_authorization_reason:
+ one: Return Authorization Reason
+ other: Return Authorization Reasons
spree/role:
one: Roles
other: Roles
@@ -183,23 +215,38 @@ en:
one: Shipment
other: Shipments
spree/shipping_category:
- one: "Shipping Category"
- other: "Shipping Categories"
+ one: Shipping Category
+ other: Shipping Categories
+ spree/shipping_method:
+ one: Shipping Method
+ other: Shipping Methods
spree/state:
one: State
other: States
+ spree/stock_movement:
+ one: Stock Movement
+ other: Stock Movements
+ spree/stock_location:
+ one: Stock Location
+ other: Stock Locations
+ spree/stock_transfer:
+ one: Stock Transfer
+ other: Stock Transfers
spree/tax_category:
- one: "Tax Category"
- other: "Tax Categories"
+ one: Tax Category
+ other: Tax Categories
spree/tax_rate:
- one: "Tax Rate"
- other: "Tax Rates"
+ one: Tax Rate
+ other: Tax Rates
spree/taxon:
one: Taxon
other: Taxons
spree/taxonomy:
one: Taxonomy
other: Taxonomies
+ spree/tracker:
+ one: Tracker
+ other: Trackers
spree/user:
one: User
other: Users
@@ -209,920 +256,1132 @@ en:
spree/zone:
one: Zone
other: Zones
- add: Add
- add_category: "Add Category"
- add_country: "Add Country"
- add_new_header: "Add New Header"
- add_new_style: "Add New Style"
- add_option_type: "Add Option Type"
- add_option_types: "Add Option Types"
- add_option_value: "Add Option Value"
- add_product: "Add Product"
- add_product_properties: "Add Product Properties"
- add_scope: "Add a scope"
- add_state: "Add State"
- add_to_cart: "Add To Cart"
- add_zone: "Add Zone"
- additional_item: Additional Item Cost
- address: Address
- address_information: "Address Information"
- adjustment: Adjustment
- adjustment_total: Adjustment Total
- adjustments: Adjustments
- administration: Administration
- admin:
- mail_methods:
- send_testmail: 'Send Testmail'
- testmail:
- delivery_error: 'Testmail delivery error'
- delivery_success: 'Testmail sent successfully'
- error: 'Testmail error: %{e}'
- all: "All"
- all_departments: All departments
- allow_backorders: "Allow Backorders"
- allow_ssl_in_development_and_test: Allow SSL to be used when in development and test modes
- allow_ssl_in_staging: Allow SSL to be used in staging mode
- allow_ssl_in_production: Allow SSL to be used in production mode
- allowed_ssl_in_production_mode: "SSL will %{not} be used in production"
- already_registered: Already Registered?
- alt_text: Alternative Text
- alternative_phone: Alternative Phone
- amount: Amount
- analytics_trackers: Analytics Trackers
- and: and
- apply: "Apply"
- are_you_sure: "Are you sure?"
- are_you_sure_category: "Are you sure you want to delete this category?"
- are_you_sure_delete: "Are you sure you want to delete this record?"
- are_you_sure_delete_image: "Are you sure you want to delete this image?"
- are_you_sure_option_type: "Are you sure you want to delete this option type?"
- are_you_sure_you_want_to_capture: "Are you sure you want to capture?"
- assign_taxon: "Assign Taxon"
- assign_taxons: "Assign Taxons"
- attachment_default_style: "Attachments Style"
- attachment_default_url: "Attachments URL"
- attachment_path: "Attachments Path"
- attachment_styles: "Paperclip Styles"
- authorization_failure: "Authorization Failure"
- authorized: Authorized
- availability: "Availability"
- available_on: "Available On"
- available_taxons: "Available Taxons"
- awaiting_return: Awaiting Return
- back: Back
- back_end: Back End
- back_to_adjustments_list: "Back To Adjustments List"
- back_to_images_list: "Back To Images List"
- back_to_mail_methods_list: "Back To Mail Methods List"
- back_to_option_types_list: "Back To Option Types List"
- back_to_payment_methods_list: "Back To Payment Methods List"
- back_to_payments_list: "Back To Payments List"
- back_to_products_list: "Back To Products List"
- back_to_properties_list: "Back To Products List"
- back_to_prototypes_list: "Back To Prototypes List"
- back_to_reports_list: "Back To Reports List"
- back_to_shipping_categories: "Back To Shipping Categories"
- back_to_shipping_methods_list: "Back To Shipping Methods List"
- back_to_states_list: "Back To States List"
- back_to_store: "Go Back To Store"
- back_to_tax_categories_list: "Back To Tax Categories List"
- back_to_taxonomies_list: "Back To Taxonomies List"
- back_to_trackers_list: "Back To Trackers List"
- back_to_zones_list: "Back To Zones List"
- backordered: Backordered
- backordering_is_allowed: "Backordering %{not} allowed"
- balance_due: "Balance Due"
- bill_address: "Bill Address"
- billing: Billing
- billing_address: "Billing Address"
- both: Both
- calculator: Calculator
- calculator_settings_warning: "If you are changing the calculator type, you must save first before you can edit the calculator settings"
- cancel: cancel
- cancel_my_account: Cancel my account
- cancel_my_account_description: "Unhappy?"
- canceled: Canceled
- cannot_create_payment_without_payment_methods: You cannot create a payment for an order without any payment methods defined.
- cannot_create_returns: Cannot create returns as this order has no shipped units.
- cannot_perform_operation: "Cannot perform requested operation"
- capture: Capture
- card_code: "Card Code"
- card_details: "Card details"
- card_number: "Card Number"
- card_type_is: Card type is
- cart: Cart
- categories: Categories
- category: Category
- change: Change
- change_language: "Change Language"
- change_my_password: "Change my password"
- charge_total: Charge Total
- charged: Charged
- charges: Charges
- checkout: Checkout
- cheque: Cheque
- city: City
- clone: Clone
- code: Code
- combine: Combine
- complete: complete
- complete_list: "Complete List"
- configuration: Configuration
- configuration_options: "Configuration Options"
- configurations: Configurations
- configure_s3: "Configure S3"
- configured: Configured
- confirm: Confirm
- confirm_delete: "Confirm Deletion"
- confirm_password: "Password Confirmation"
- continue: Continue
- continue_shopping: "Continue shopping"
- copy_all_mails_to: Copy All Mails To
- cost_currency: "Cost Currency"
- cost_price: "Cost Price"
- count_of_reduced_by: "count of '%{name}' reduced by %{count}"
- country: Country
- country_based: "Country Based"
- create: Create
- create_a_new_account: "Create a new account"
- create_user_account: Create User Account
- created_successfully: "Created Successfully"
- credit: Credit
- credit_card: "Credit Card"
- credit_card_capture_complete: "Credit Card Was Captured"
- credit_card_payment: "Credit Card Payment"
- credit_owed: "Credit Owed"
- credit_total: Credit Total
- credit_card: Credit Card
- credit_cards: Credit Cards
- credits: Credits
- current: Current
- currency: Currency
- currency_symbol_position: "Put currency symbol before or after dollar amount?"
- currency_settings: "Currency Settings"
- customer: Customer
- customer_details: "Customer Details"
- customer_details_updated: "The customer's details have been updated."
- customer_search: "Customer Search"
- cut: Cut
- date_created: Date created
- date_completed: Date Completed
- date_range: "Date Range"
- debit: Debit
- default: Default
- default_meta_description: Default Meta Description
- default_meta_keywords: Default Meta Keywords
- default_seo_title: Default Seo Title
- default_tax: Default Tax
- default_tax_zone: Default Tax Zone
- defined_paperclip_styles: Defined Paperclip Styles
- delete: Delete
- delivery: Delivery
- depth: Depth
- description: Description
- destroy: Destroy
- didnt_receive_confirmation_instructions: "Didn't receive confirmation instructions?"
- didnt_receive_unlock_instructions: "Didn't receive unlock instructions?"
- discount_amount: "Discount Amount"
- display: Display
- display_currency: "Display currency"
- dismiss_banner: "No. Thanks! I'm not interested, do not display this message again"
- dollar_amounts_displayed_as: "Dollar amounts displayed as %{example}"
- edit: Edit
- editing_billing_integration: Editing Billing Integration
- editing_category: "Editing Category"
- editing_mail_method: Editing Mail Method
- editing_option_type: "Editing Option Type"
- editing_option_types: "Editing Option Types"
- editing_payment_method: Editing Payment Method
- editing_product: "Editing Product"
- editing_product_group: "Editing Product Group"
- editing_property: "Editing Property"
- editing_prototype: "Editing Prototype"
- editing_shipping_category: "Editing Shipping Category"
- editing_shipping_method: "Editing Shipping Method"
- editing_state: "Editing State"
- editing_tax_category: "Editing Tax Category"
- editing_tax_rate: "Editing Tax Rate"
- editing_tracker: Editing Tracker
- editing_user: "Editing User"
- editing_zone: "Editing Zone"
- email: Email
- email_address: "Email Address"
- email_server_settings_description: "Set email server settings."
- empty: "Empty"
- empty_cart: "Empty Cart"
- enable_login_via_login_password: "Use standard email/password"
- enable_login_via_openid: "Use OpenID instead"
- enable_mail_delivery: Enable Mail Delivery
- ending_in: "Ending in"
- enter_exactly_as_shown_on_card: Please enter exactly as shown on the card
- enter_at_least_five_letters: Enter at least five letters of customer name
- enter_password_to_confirm: "(we need your current password to confirm your changes)"
- enter_token: Enter Token
- environment: "Environment"
- error: error
+ errors:
+ models:
+ spree/calculator/tiered_flat_rate:
+ attributes:
+ base:
+ keys_should_be_positive_number: "Tier keys should all be numbers larger than 0"
+ preferred_tiers:
+ should_be_hash: "should be a hash"
+ spree/calculator/tiered_percent:
+ attributes:
+ base:
+ keys_should_be_positive_number: "Tier keys should all be numbers larger than 0"
+ values_should_be_percent: "Tier values should all be percentages between 0% and 100%"
+ preferred_tiers:
+ should_be_hash: "should be a hash"
+ spree/classification:
+ attributes:
+ taxon_id:
+ already_linked: "is already linked to this product"
+ spree/credit_card:
+ attributes:
+ base:
+ card_expired: "Card has expired"
+ expiry_invalid: "Card expiration is invalid"
+ spree/line_item:
+ attributes:
+ currency:
+ must_match_order_currency: "Must match order currency"
+ spree/refund:
+ attributes:
+ amount:
+ greater_than_allowed: is greater than the allowed amount.
+ spree/reimbursement:
+ attributes:
+ base:
+ return_items_order_id_does_not_match: One or more of the return items specified do not belong to the same order as the reimbursement.
+ spree/return_item:
+ attributes:
+ reimbursement:
+ cannot_be_associated_unless_accepted: cannot be associated to a return item that is not accepted.
+ inventory_unit:
+ other_completed_return_item_exists: "%{inventory_unit_id} has already been taken by return item %{return_item_id}"
+ spree/store:
+ attributes:
+ base:
+ cannot_destroy_default_store: Cannot destroy the default Store.
+
+
+ devise:
+ confirmations:
+ confirmed: Your account was successfully confirmed. You are now signed in.
+ send_instructions: You will receive an email with instructions about how to confirm your account in a few minutes.
+ failure:
+ inactive: Your account was not activated yet.
+ invalid: Invalid email or password.
+ invalid_token: Invalid authentication token.
+ locked: Your account is locked.
+ timeout: Your session expired, please sign in again to continue.
+ unauthenticated: You need to sign in or sign up before continuing.
+ unconfirmed: You have to confirm your account before continuing.
+ mailer:
+ confirmation_instructions:
+ subject: Confirmation instructions
+ reset_password_instructions:
+ subject: Reset password instructions
+ unlock_instructions:
+ subject: Unlock Instructions
+ oauth_callbacks:
+ failure: Could not authorize you from %{kind} because "%{reason}".
+ success: Successfully authorized from %{kind} account.
+ unlocks:
+ send_instructions: You will receive an email with instructions about how to unlock your account in a few minutes.
+ unlocked: Your account was successfully unlocked. You are now signed in.
+ user_passwords:
+ user:
+ cannot_be_blank: Your password cannot be blank.
+ send_instructions: You will receive an email with instructions about how to reset your password in a few minutes.
+ updated: Your password was changed successfully. You are now signed in.
+ user_registrations:
+ destroyed: Bye! Your account was successfully cancelled. We hope to see you again soon.
+ inactive_signed_up: You have signed up successfully. However, we could not sign you in because your account is %{reason}.
+ signed_up: Welcome! You have signed up successfully.
+ updated: You updated your account successfully.
+ user_sessions:
+ signed_in: Signed in successfully.
+ signed_out: Signed out successfully.
errors:
messages:
- could_not_create_taxon: "Could not create taxon"
- no_shipping_methods_available: "No shipping methods available for selected location, please change your address and try again."
- no_payment_methods_available: "No payment methods are configured for this environment"
- errors_prohibited_this_record_from_being_saved:
- one: "1 error prohibited this record from being saved"
- other: "%{count} errors prohibited this record from being saved"
- error_user_destroy_with_orders: "Users with completed orders may not be deleted"
- event: Event
- events:
- spree:
- cart:
- add: 'Add to cart'
- order:
- contents_changed: "Order contents changed"
+ already_confirmed: was already confirmed
+ not_found: not found
+ not_locked: was not locked
+ not_saved:
+ one: ! '1 error prohibited this %{resource} from being saved:'
+ other: ! '%{count} errors prohibited this %{resource} from being saved:'
+ spree:
+ abbreviation: Abbreviation
+ accept: Accept
+ acceptance_status: Acceptance status
+ acceptance_errors: Acceptance errors
+ accepted: Accepted
+ account: Account
+ account_updated: Account updated
+ action: Action
+ actions:
+ cancel: Cancel
+ continue: Continue
+ create: Create
+ destroy: Destroy
+ edit: Edit
+ list: List
+ listing: Listing
+ new: New
+ refund: Refund
+ save: Save
+ update: Update
+ activate: Activate
+ active: Active
+ add: Add
+ add_action_of_type: Add action of type
+ add_country: Add Country
+ add_coupon_code: Add Coupon Code
+ add_new_header: Add New Header
+ add_new_style: Add New Style
+ add_one: Add One
+ add_option_value: Add Option Value
+ add_product: Add Product
+ add_product_properties: Add Product Properties
+ add_rule_of_type: Add rule of type
+ add_state: Add State
+ add_stock: Add Stock
+ add_stock_management: Add Stock Management
+ add_to_cart: Add To Cart
+ add_variant: Add Variant
+ additional_item: Additional Item
+ address1: Address
+ address2: Address (contd.)
+ adjustable: Adjustable
+ adjustment: Adjustment
+ adjustment_amount: Amount
+ adjustment_successfully_closed: Adjustment has been successfully closed!
+ adjustment_successfully_opened: Adjustment has been successfully opened!
+ adjustment_total: Adjustment Total
+ adjustments: Adjustments
+ admin:
+ tab:
+ configuration: Configuration
+ option_types: Option Types
+ orders: Orders
+ overview: Overview
+ products: Products
+ promotions: Promotions
+ properties: Properties
+ prototypes: Prototypes
+ reports: Reports
+ taxonomies: Taxonomies
+ taxons: Taxons
+ users: Users
user:
- signup: 'User signup'
- page_view: "Static page viewed"
- existing_customer: "Existing Customer"
- expiration: "Expiration"
- expiration_month: "Expiration Month"
- expiration_year: "Expiration Year"
- extension: Extension
- extensions: Extensions
- filename: Filename
- final_confirmation: "Final Confirmation"
- finalize: Finalize
- finalized_payments: Finalized Payments
- first_item: First Item Cost
- first_name: "First Name"
- first_name_begins_with: "First Name Begins With"
- flat_percent: "Flat Percent"
- flat_rate_amount: Amount
- flat_rate_per_item: "Flat Rate (per item)"
- flat_rate_per_order: "Flat Rate (per order)"
- flexible_rate: "Flexible Rate"
- forgot_password: "Forgot Password?"
- from_state: From State
- front_end: Front End
- full_name: "Full Name"
- gateway: Gateway
- gateway_configuration: "Gateway configuration"
- gateway_config_unavailable: "Gateway unavailable for environment"
- gateway_error: "Gateway Error"
- gateway_setting_description: "Select a payment gateway and configure its settings."
- gateway_settings_warning: "If you are changing the gateway type, you must save first before you can edit the gateway settings"
- general: "General"
- general_settings: "General Settings"
- edit_general_settings: "Edit General Settings"
- general_settings_description: "Configure general Spree settings."
- google_analytics: "Google Analytics"
- google_analytics_active: "Active"
- google_analytics_create: "Create New Google Analytics Account"
- google_analytics_id: "Analytics ID"
- google_analytics_new: "New Google Analytics Account"
- google_analytics_setting_description: "Manage Google Analytics ID."
- guest_checkout: Guest Checkout
- guest_user_account: Checkout as a Guest
- has_no_shipped_units: has no shipped units
- height: Height
- hello_user: "Hello User"
- hide_cents: "Hide cents"
- history: History
- home: "Home"
- icon: "Icon"
- icons_by: "Icons by"
- image: Image
- images: Images
- images_for: "Images for"
- image_settings: "Image Settings"
- image_settings_description: "Image Settings Description"
- image_settings_updated: "Image Settings successfully updated."
- image_settings_warning: "You will need to regenerate thumbnails if you update the paperclip styles. Use rake paperclip:refresh:thumbnails to do this."
- in_progress: "In Progress"
- include_in_shipment: Include in Shipment
- included_in_other_shipment: Included in another Shipment
- included_in_price: Included in Price
- included_in_this_shipment: Included in this Shipment
- included_price_validation: "cannot be selected unless you have set a Default Tax Zone"
- instructions_to_reset_password: "Fill out the form below and instructions to reset your password will be emailed to you:"
- insufficient_stock: "Insufficient stock available, only %{on_hand} remaining"
- integration_settings_warning: "If you are changing the billing integration, you must save first before you can edit the integration settings"
- intercept_email_address: Intercept Email Address
- intercept_email_instructions: "Override email recipient and replace with this address."
- invalid_search: "Invalid search criteria."
- inventory: Inventory
- inventory_adjustment: "Inventory Adjustment"
- inventory_setting_description: "Inventory Configuration, Backordering, Zero-Stock Display."
- inventory_settings: "Inventory Settings"
- is_not_available_to_shipment_address: is not available to shipment address
- issue_number: Issue Number
- item: Item
- item_description: "Item Description"
- item_total: "Item Total"
- last_name: "Last Name"
- last_name_begins_with: "Last Name Begins With"
- learn_more: Learn More
- leave_blank_to_not_change: "(leave blank if you don't want to change it)"
- list: List
- listing_categories: "Listing Categories"
- listing_option_types: "Listing Option Types"
- listing_orders: "Listing Orders"
- listing_product_groups: "Listing Product Groups"
- listing_products: "Listing Products"
- listing_reports: "Listing Reports"
- listing_tax_categories: "Listing Tax Categories"
- listing_users: "Listing Users"
- live: "Live"
- loading: Loading
- locale_changed: "Locale Changed"
- logged_in_as: "Logged in as"
- logged_in_succesfully: "Logged in successfully"
- logged_out: "You have been logged out."
- login: Login
- login_as_existing: "Login as Existing Customer"
- login_failed: "Login authentication failed."
- login_name: Login
- logout: Logout
- look_for_similar_items: Look for similar items
- maestro_or_solo_cards: Maestro/Solo cards
- mail_delivery_enabled: "Mail delivery is enabled"
- mail_delivery_not_enabled: "Mail delivery is not enabled"
- mail_methods: Mail Methods
- mail_server_preferences: Mail Server Preferences
- make_refund: Make refund
- mark_shipped: "Mark Shipped"
- master_price: "Master Price"
- match_choices:
- none: "None"
- one: "One"
- all: "All"
- match_rule: "Products That Must Match:"
- max_items: Max Items
- meta_description: "Meta Description"
- meta_keywords: "Meta Keywords"
- metadata: "Metadata"
- missing_required_information: "Missing Required Information"
- minimal_amount: "Minimal Amount"
- month: "Month"
- more: More
- my_account: "My Account"
- my_orders: "My Orders"
- name: Name
- name_or_sku: "Name or SKU (enter at least first 4 characters of product name)"
- new: New
- new_adjustment: "New Adjustment"
- new_billing_integration: New Billing Integration
- new_category: "New category"
- new_customer: "New Customer"
- new_group: New Group
- new_image: "New Image"
- new_mail_method: New Mail Method
- new_option_type: "New Option Type"
- new_option_value: "New Option Value"
- new_order: "New Order"
- new_order_completed: "New Order Completed"
- new_payment: "New Payment"
- new_payment_method: New Payment Method
- new_product: "New Product"
- new_product_group: New Product Group
- new_property: "New Property"
- new_prototype: "New Prototype"
- new_return_authorization: New Return Authorization
- new_shipment: "New Shipment"
- new_shipping_category: "New Shipping Category"
- new_shipping_method: "New Shipping Method"
- new_state: "New State"
- new_tax_category: "New Tax Category"
- new_tax_rate: "New Tax Rate"
- new_taxon: "New Taxon"
- new_taxonomy: "New Taxonomy"
- new_tracker: New Tracker
- new_user: "New User"
- new_variant: "New Variant"
- new_zone: "New Zone"
- next: Next
- no_items_in_cart: ""
- no_match_found: "No Match Found"
- no_products_found: "No products found"
- no_results: "No results"
- no_user_found: "No user was found with that email address"
- none: None
- none_available: "None Available"
- normal_amount: "Normal Amount"
- not: not
- not_available: "N/A"
- not_found: "%{resource} is not found"
- not_shown: "Not Shown"
- note: Note
- notice_messages:
- option_type_removed: "Succesfully removed option type."
- product_cloned: "Product has been cloned"
- product_deleted: "Product has been deleted"
- product_not_cloned: "Product could not be cloned"
- product_not_deleted: "Product could not be deleted"
- variant_deleted: "Variant has been deleted"
- variant_not_deleted: "Variant could not be deleted"
- on_hand: "On Hand"
- one_default_category_with_default_tax_rate: "You should configure exactly one default category with your countries default tax rate"
- operation: Operation
- option_type: "Option Type"
- option_types: "Option Types"
- option_value: "Option Value"
- option_values: "Option Values"
- options: Options
- or: or
- or_over_price: "%{price} or over"
- order: Order
- order_adjustments: "Order adjustments"
- order_confirmation_note: ""
- order_date: "Order Date"
- order_details: "Order Details"
- order_email_resent: "Order Email Resent"
- order_mailer:
- confirm_email:
- subject: "Order Confirmation"
- dear_customer: "Dear Customer,"
- instructions: "Please review and retain the following order information for your records."
- order_summary: "Order Summary"
- subtotal: "Subtotal:"
- total: "Order Total:"
- thanks: "Thank you for your business."
- cancel_email:
- subject: "Cancellation of Order"
- dear_customer: "Dear Customer,"
- instructions: "Your order has been CANCELED. Please retain this cancellation information for your records."
- order_summary_canceled: "Order Summary [CANCELED]"
- subtotal: "Subtotal:"
- total: "Order Total:"
- order_not_in_system: That order number is not valid on this site.
- order_number: Order
- order_operation_authorize: Authorize
- order_processed_but_following_items_are_out_of_stock: "Your order has been processed, but following items are out of stock:"
- order_processed_successfully: "Your order has been processed successfully"
- order_state:
- # keys correspond to Checkout state names:
- address: address
- adjustments: adjustments
- awaiting_return: awaiting return
- canceled: canceled
- cart: cart
+ account: Account
+ addresses: Addresses
+ items: Items
+ items_purchased: Items Purchased
+ order_history: Order History
+ order_num: "Order #"
+ orders: Orders
+ user_information: User Information
+ administration: Administration
+ agree_to_privacy_policy: Agree to Privacy Policy
+ agree_to_terms_of_service: Agree to Terms of Service
+ all: All
+ all_adjustments_closed: All adjustments successfully closed!
+ all_adjustments_opened: All adjustments successfully opened!
+ all_departments: All departments
+ all_items_have_been_returned: All items have been returned
+ allow_ssl_in_development_and_test: Allow SSL to be used when in development and test modes
+ allow_ssl_in_production: Allow SSL to be used in production mode
+ allow_ssl_in_staging: Allow SSL to be used in staging mode
+ already_signed_up_for_analytics: You have already signed up for Spree Analytics
+ alt_text: Alternative Text
+ alternative_phone: Alternative Phone
+ amount: Amount
+ analytics_desc_header_1: Spree Analytics
+ analytics_desc_header_2: Live analytics integrated into your Spree dashboard
+ analytics_desc_list_1: Get live sales information as it happens
+ analytics_desc_list_2: Requires only a free Spree account to activate
+ analytics_desc_list_3: Absolutely no code to install
+ analytics_desc_list_4: It's completely free!
+ analytics_trackers: Analytics Trackers
+ and: and
+ approve: approve
+ approver: Approver
+ approved_at: Approved at
+ are_you_sure: Are you sure?
+ are_you_sure_delete: Are you sure you want to delete this record?
+ associated_adjustment_closed: The associated adjustment is closed, and will not be recalculated. Do you want to open it?
+ authorization_failure: Authorization Failure
+ authorized: Authorized
+ auto_capture: Auto Capture
+ available_on: Available On
+ average_order_value: Average Order Value
+ avs_response: AVS Response
+ back: Back
+ back_end: Backend
+ backordered: Backordered
+ back_to_adjustments_list: Back To Adjustments List
+ back_to_customer_return: Back To Customer Return
+ back_to_customer_return_list: Back To Customer Return List
+ back_to_images_list: Back To Images List
+ back_to_option_types_list: Back To Option Types List
+ back_to_orders_list: Back To Orders List
+ back_to_payment: Back To Payment
+ back_to_payment_methods_list: Back To Payment Methods List
+ back_to_payments_list: Back To Payments List
+ back_to_products_list: Back To Products List
+ back_to_promotions_list: Back To Promotions List
+ back_to_promotion_categories_list: Back To Promotions Categories List
+ back_to_properties_list: Back To Properties List
+ back_to_prototypes_list: Back To Prototypes List
+ back_to_reports_list: Back To Reports List
+ back_to_refund_reason_list: Back To Refund Reason List
+ back_to_reimbursement_type_list: Back To Reimbursement Type List
+ back_to_return_authorizations_list: Back To Return Authorizations List
+ back_to_rma_reason_list: Back To RMA Reason List
+ back_to_shipping_categories: Back To Shipping Categories
+ back_to_shipping_categories_list: Back To Shipping Categories List
+ back_to_shipping_methods_list: Back To Shipping Methods List
+ back_to_states_list: Back To States List
+ back_to_stock_locations_list: Back to Stock Locations List
+ back_to_stock_movements_list: Back to Stock Movements List
+ back_to_stock_transfers_list: Back to Stock Transfers List
+ back_to_store: Go Back To Store
+ back_to_tax_categories_list: Back To Tax Categories List
+ back_to_taxonomies_list: Back To Taxonomies List
+ back_to_trackers_list: Back To Trackers List
+ back_to_users_list: Back To Users List
+ back_to_zones_list: Back To Zones List
+ backorderable: Backorderable
+ backorderable_default: Backorderable default
+ backorders_allowed: backorders allowed
+ balance_due: Balance Due
+ base_amount: Base Amount
+ base_percent: Base Percent
+ bill_address: Bill Address
+ billing: Billing
+ billing_address: Billing Address
+ both: Both
+ calculated_reimbursements: Calculated Reimbursements
+ calculator: Calculator
+ calculator_settings_warning: If you are changing the calculator type, you must save first before you can edit the calculator settings
+ cancel: cancel
+ canceler: Canceler
+ canceled_at: Canceled at
+ cannot_create_payment_without_payment_methods: You cannot create a payment for an order without any payment methods defined.
+ cannot_create_customer_returns: Cannot create customer returns as this order has no shipped units.
+ cannot_create_returns: Cannot create returns as this order has no shipped units.
+ cannot_perform_operation: Cannot perform requested operation
+ cannot_set_shipping_method_without_address: Cannot set shipping method until customer details are provided.
+ capture: Capture
+ capture_events: Capture events
+ card_code: Card Code
+ card_number: Card Number
+ card_type: Brand
+ card_type_is: Card type is
+ cart: Cart
+ cart_subtotal:
+ one: 'Subtotal (1 item)'
+ other: 'Subtotal (%{count} items)'
+ categories: Categories
+ category: Category
+ charged: Charged
+ checkout: Checkout
+ choose_a_customer: Choose a customer
+ choose_a_taxon_to_sort_products_for: "Choose a taxon to sort products for"
+ choose_currency: Choose Currency
+ choose_dashboard_locale: Choose Dashboard Locale
+ choose_location: Choose location
+ city: City
+ clear_cache: Clear Cache
+ clear_cache_ok: Cache was flushed
+ clear_cache_warning: Clearing cache will temporarily reduce the performance of your store.
+ click_and_drag_on_the_products_to_sort_them: Click and drag on the products to sort them.
+ clone: Clone
+ close: Close
+ close_all_adjustments: Close All Adjustments
+ code: Code
+ company: Company
complete: complete
- confirm: confirm
- delivery: delivery
- payment: payment
- resumed: resumed
- returned: returned
- skrill: skrill
- order_summary: Order Summary
- order_sure_want_to: "Are you sure you want to %{event} this order?"
- order_total: "Order Total"
- order_total_message: "The total amount charged to your card will be"
- order_updated: "Order Updated"
- orders: Orders
- other_payment_options: Other Payment Options
- out_of_stock: "Out of Stock"
- over_paid: "Over Paid"
- overview: Overview
- page_only_viewable_when_logged_in: You attempted to visit a page which can only be viewed when you are logged in
- page_only_viewable_when_logged_out: You attempted to visit a page which can only be viewed when you are logged out
- pagination:
- previous_page: "« previous page"
- next_page: "next page »"
- truncate: "…"
- paid: Paid
- parent_category: "Parent Category"
- password: Password
- password_reset_instructions: "Password Reset Instructions"
- password_reset_instructions_are_mailed: "Instructions to reset your password have been emailed to you. Please check your email."
- password_reset_token_not_found: "We're sorry, but we could not locate your account. If you are having issues try copying and pasting the URL from your email into your browser or restarting the reset password process."
- password_updated: "Password successfully updated"
- paste: Paste
- path: Path
- pay: pay
- payment: Payment
- payment_actions: "Actions"
- payment_gateway: "Payment Gateway"
- payment_information: "Payment Information"
- payment_method: Payment Method
- payment_methods: Payment Methods
- payment_methods_setting_description: Configure methods customers can use to pay.
- payment_processing_failed: "Payment could not be processed, please check the details you entered"
- payment_processor_choose_banner_text: "If you need help choosing a payment processor, please visit"
- payment_processor_choose_link: "our payments page"
- payment_state: Payment State
- payment_states:
- balance_due: balance due
- completed: completed
- checkout: checkout
- credit_owed: credit owed
- failed: failed
- paid: paid
- pending: pending
- processing: processing
- void: void
- payment_updated: Payment Updated
- payments: Payments
- pending_payments: Pending Payments
- permalink: Permalink
- phone: Phone
- place_order: Place Order
- please_create_user: "Please create a user account"
- please_define_payment_methods: "Please define some payment methods first."
- powered_by: "Powered by"
- populate_get_error: "Something went wrong. Please try adding the item again."
- presentation: Presentation
- preview: Preview
- previous: Previous
- price: Price
- price_sack: Price Sack
- price_range: Price Range
- problem_authorizing_card: "Problem authorizing credit card"
- problem_capturing_card: "Problem capturing credit card"
- problems_processing_order: "We had problems processing your order"
- proceed_as_guest: "No Thanks, Proceed as Guest"
- process: Process
- product: Product
- product_details: "Product Details"
- product_group: Product Group
- product_group_invalid: Product Group has invalid scopes
- product_groups: Product Groups
- product_has_no_description: This product has no description
- product_properties: "Product Properties"
- product_scopes:
- groups:
- price:
- description: "Scopes for selecting products based on Price"
- name: Price
- search:
- description: "Scopes for selecting products based on name, keywords and description of product"
- name: "Text search"
- taxon:
- description: "Scopes for selecting products based on Taxons"
- name: Taxon
- values:
- description: "Scopes for selecting products based on option and property values"
- name: Values
- scopes:
- ascend_by_name:
- name: Ascend by product name
- ascend_by_updated_at:
- name: Ascend by actualization date
- descend_by_name:
- name: Descend by product name
- descend_by_updated_at:
- name: Descend by actualization date
- in_name:
- args:
- words: Words
- description: "(separated by space or comma)"
- name: "Product name have following"
- sentence: product name contain %s
- in_name_or_description:
- args:
- words: Words
- description: "(separated by space or comma)"
- name: "Product name or description have following"
- sentence: name or description contain %s
- in_name_or_keywords:
- args:
- words: Words
- description: "(separated by space or comma)"
- name: "Product name or meta keywords have following"
- sentence: name or keywords contain %s
- in_taxons:
- args:
- "taxon_names": "Taxon names"
- description: "Taxon names have to be separated by comma or space(eg. adidas,shoes)"
- name: "In taxons and all their descendants"
- sentence: in %s and all their descendants
- master_price_gte:
- args:
- amount: Amount
- description: ""
- name: "Master price greater or equal to"
- sentence: price greater or equal to %.2f
- master_price_lte:
- args:
- amount: Amount
- description: ""
- name: "Master price lesser or equal to"
- sentence: price less or equal to %.2f
- price_between:
- args:
- high: High
- low: Low
- description: ""
- name: "Price between"
- sentence: price between %.2f and %.2f
- taxons_name_eq:
- args:
- taxon_name: "Taxon name"
- description: "In specific taxon - without descendants"
- name: "In Taxon(without descendants)"
- sentence: in %s
- with:
- args:
- value: Value
- description: "Selects all products that have at least one variant that have specified value as either option or property (eg. red)"
- name: With value
- sentence: with value %s
- with_ids:
- args:
- ids: IDs
- description: "Select specific products"
- name: Products with IDs
- sentence: with IDs %s
- with_option:
- args:
- option: Option
- description: "Selects all products that have specified option(eg. color)"
- name: "With option"
- sentence: with option %s
- with_option_value:
- args:
- option: Option
- value: Value
- description: "Selects all products that have at least one variant with specified option and value(eg. color:red)"
- name: "With option and value"
- sentence: with option %s and value %s
- with_property:
- args:
- property: Property
- description: "Selects all products that have specified property(eg. weight)"
- name: "With property"
- sentence: with property %s
- with_property_value:
- args:
- property: Property
- value: Value
- description: "Selects all products that have at least one variant with specified property and value(eg. weight:10kg)"
- name: "With property value"
- sentence: with property %s and value %s
- products: Products
- products_with_zero_inventory_display: "Products with a zero inventory will %{not} be displayed"
- properties: Properties
- property: Property
- prototype: Prototype
- prototypes: Prototypes
- provider: "Provider"
- provider_settings_warning: "If you are changing the provider type, you must save first before you can edit the provider settings"
- qty: Qty
- quantity_shipped: Quantity Shipped
- quantity_returned: Quantity Returned
- range: "Range"
- rate: Rate
- reason: Reason
- recalculate_order_total: "Recalculate order total"
- receive: receive
- received: Received
- refund: Refund
- register: Register as a New User
- register_or_guest: Checkout as Guest or Register
- registration: Registration
- remember_me: "Remember me"
- remove: Remove
- rename: Rename
- reports: Reports
- required_for_solo_and_maestro: Required for Solo and Maestro cards.
- resend: Resend
- resend_confirmation_instructions: "Resend confirmation instructions"
- resend_unlock_instructions: "Resend unlock instructions"
- reset_password: "Reset my password"
- resource_controller:
- member_object_not_found: "Member object not found."
- successfully_created: "Successfully created!"
- successfully_removed: "Successfully removed!"
- successfully_updated: "Successfully updated!"
- response_code: "Response Code"
- resume: "resume"
- resumed: Resumed
- return: return
- return_authorization: Return Authorization
- return_authorization_updated: Return authorization updated
- return_authorizations: Return Authorizations
- return_quantity: Return Quantity
- returned: Returned
- review: Review
- rma_credit: RMA Credit
- rma_number: RMA Number
- rma_value: RMA Value
- roles: Roles
- s3_access_key: "Access Key"
- s3_bucket: "Bucket"
- s3_headers: "S3 Headers"
- s3_secret: "Secret Key"
- s3_protocol: "S3 Protocol"
- s3_used_for_product_images: "S3 is being used for product images"
- s3_not_used_for_product_images: "S3 is not being used for product images"
- sales_tax: "Sales Tax"
- sales_total: "Sales Total"
- sales_total_description: "Sales Total For All Orders"
- save_and_continue: Save and Continue
- save_preferences: Save Preferences
- scope: Scope
- scopes: Scopes
- search: Search
- search_results: "Search results for '%{keywords}'"
- searching: Searching
- secure_connection_type: Secure Connection Type
- secure_credit_card: Secure Credit Card
- security_settings: "Security Settings"
- select: Select
- select_from_prototype: "Select From Prototype"
- select_preferred_shipping_option: "Select preferred shipping option"
- send_copy_of_all_mails_to: Send Copy of All Mails To
- send_copy_of_orders_mails_to: Send Copy of Order Mails To
- send_mails_as: Send Mails As
- send_me_reset_password_instructions: "Send me reset password instructions"
- send_order_mails_as: Send Order Mails As
- server: Server
- server_error: "The server returned an error"
- settings: Settings
- ship: ship
- ship_address: "Ship Address"
- shipment: Shipment
- shipment_details: Shipment Details
- shipment_inc_vat: "Shipment including VAT"
- shipment_mailer:
- shipped_email:
- subject: "Shipment Notification"
- dear_customer: "Dear Customer,"
- instructions: "Your order has been shipped"
- shipment_summary: "Shipment Summary"
- track_information: "Tracking Information: %{tracking}"
- thanks: "Thank you for your business."
- shipment_number: "Shipment #"
- shipment_state: Shipment State
- shipment_states:
- backorder: backorder
- partial: partial
- pending: pending
- ready: ready
- shipped: shipped
- shipment_updated: Shipment Updated
- shipments: "Shipments"
- shipped: Shipped
- shipping: Shipping
- shipping_address: "Shipping Address"
- shipping_categories: "Shipping Categories"
- shipping_categories_description: "Manage shipping categories to identify which products can be shipped via which method."
- shipping_category: Shipping Category
- shipping_category_choose: "Shipping Category"
- shipping_cost: Cost
- shipping_error: "Shipping Error"
- shipping_instructions: "Shipping Instructions"
- shipping_method: "Shipping Method"
- shipping_methods: "Shipping Methods"
- shipping_methods_description: "Manage shipping methods."
- shipping_total: "Shipping Total"
- shop_by_taxonomy: "Shop by %{taxonomy}"
- shopping_cart: "Shopping Cart"
- short_description: "Short description"
- show: Show
- show_active: "Show Active"
- show_deleted: "Show Deleted"
- show_incomplete_orders: "Show Incomplete Orders"
- show_only_complete_orders: "Only show complete orders"
- show_out_of_stock_products: "Show out-of-stock products"
- show_only_unfulfilled_orders: "Show only unfulfilled orders"
- showing_first_n: "Showing first %{n}"
- sign_up: "Sign up"
- site_name: "Site Name"
- site_url: "Site URL"
- sku: SKU
- smtp: SMTP
- smtp_authentication_type: SMTP Authentication Type
- smtp_domain: SMTP Domain
- smtp_mail_host: SMTP Mail Host
- smtp_password: SMTP Password
- smtp_port: SMTP Port
- smtp_send_all_emails_as_from_following_address: "Send all mails as from the following address."
- smtp_send_copy_to_this_addresses: "Sends a copy of all outgoing mails to this address. For multiple addresses, separate with commas."
- smtp_username: SMTP Username
- sold: Sold
- sort_ordering: "Sort ordering"
- special_instructions: "Special Instructions"
- spree:
+ configuration: Configuration
+ configurations: Configurations
+ confirm: Confirm
+ confirm_delete: Confirm Deletion
+ confirm_password: Password Confirmation
+ continue: Continue
+ continue_shopping: Continue shopping
+ cost_currency: Cost Currency
+ cost_price: Cost Price
+ could_not_connect_to_jirafe: Could not connect to Jirafe to sync data. This will be automatically retried later.
+ could_not_create_customer_return: Could not create customer return
+ could_not_create_stock_movement: There was a problem saving this stock movement. Please try again.
+ count_on_hand: Count On Hand
+ countries: Countries
+ country: Country
+ country_based: Country Based
+ country_name: Name
+ country_names:
+ CA: Canada
+ FRA: France
+ ITA: Italy
+ US: United States of America
+ coupon: Coupon
+ coupon_code: Coupon code
+ coupon_code_already_applied: The coupon code has already been applied to this order
+ coupon_code_applied: The coupon code was successfully applied to your order.
+ coupon_code_better_exists: The previously applied coupon code results in a better deal
+ coupon_code_expired: The coupon code is expired
+ coupon_code_max_usage: Coupon code usage limit exceeded
+ coupon_code_not_eligible: This coupon code is not eligible for this order
+ coupon_code_not_found: The coupon code you entered doesn't exist. Please try again.
+ coupon_code_unknown_error: This coupon code could not be applied to the cart at this time.
+ customer_return: Customer Return
+ customer_returns: Customer Returns
+ create: Create
+ create_a_new_account: Create a new account
+ create_new_order: Create new order
+ create_reimbursement: Create reimbursement
+ created_at: Created At
+ credit: Credit
+ credits: Credits
+ credit_card: Credit Card
+ credit_cards: Credit Cards
+ credit_owed: Credit Owed
+ currency: Currency
+ currency_decimal_mark: Currency decimal mark
+ currency_settings: Currency Settings
+ currency_symbol_position: Put currency symbol before or after dollar amount?
+ currency_thousands_separator: Currency thousands separator
+ current: Current
+ current_promotion_usage: ! 'Current Usage: %{count}'
+ customer: Customer
+ customer_details: Customer Details
+ customer_details_updated: Customer Details Updated
+ customer_search: Customer Search
+ cut: Cut
+ cvv_response: CVV Response
+ dash:
+ jirafe:
+ app_id: App ID
+ app_token: App Token
+ currently_unavailable: Jirafe is currently unavailable. Spree will automatically connect to Jirafe once it is available.
+ explanation: The fields below may already be populated if you chose to register with Jirafe from the admin dashboard.
+ header: Jirafe Analytics Settings
+ site_id: Site ID
+ token: Token
+ jirafe_settings_updated: Jirafe Settings have been updated.
date: Date
+ date_completed: Date Completed
date_picker:
+ first_day: 0
format: ! '%Y/%m/%d'
- js_format: 'yy/mm/dd'
+ js_format: yy/mm/dd
+ date_range: Date Range
+ default: Default
+ default_refund_amount: Default Refund Amount
+ default_tax: Default Tax
+ default_tax_zone: Default Tax Zone
+ delete: Delete
+ deleted_variants_present: Some line items in this order have products that are no longer available.
+ delivery: Delivery
+ depth: Depth
+ description: Description
+ destination: Destination
+ destroy: Destroy
+ discount_amount: Discount Amount
+ dismiss_banner: No. Thanks! I'm not interested, do not display this message again
+ display: Display
+ display_currency: Display currency
+ edit: Edit
+ editing_country: Editing Country
+ editing_option_type: Editing Option Type
+ editing_payment_method: Editing Payment Method
+ editing_product: Editing Product
+ editing_promotion: Editing Promotion
+ editing_promotion_category: Editing Promotion Category
+ editing_property: Editing Property
+ editing_prototype: Editing Prototype
+ edit_refund_reason: Edit Refund Reason
+ editing_refund_reason: Editing Refund Reason
+ editing_reimbursement: Editing Reimbursement
+ editing_reimbursement_type: Editing Reimbursement Type
+ editing_rma_reason: Editing RMA Reason
+ editing_shipping_category: Editing Shipping Category
+ editing_shipping_method: Editing Shipping Method
+ editing_state: Editing State
+ editing_stock_location: Editing Stock Location
+ editing_stock_movement: Editing Stock Movement
+ editing_tax_category: Editing Tax Category
+ editing_tax_rate: Editing Tax Rate
+ editing_tracker: Editing Tracker
+ editing_user: Editing User
+ editing_zone: Editing Zone
+ eligibility_errors:
+ messages:
+ has_excluded_product: Your cart contains a product that prevents this coupon code from being applied.
+ item_total_less_than: This coupon code can't be applied to orders less than %{amount}.
+ item_total_less_than_or_equal: This coupon code can't be applied to orders less than or equal to %{amount}.
+ item_total_more_than: This coupon code can't be applied to orders higher than %{amount}.
+ item_total_more_than_or_equal: This coupon code can't be applied to orders higher than or equal to %{amount}.
+ limit_once_per_user: This coupon code can only be used once per user.
+ missing_product: This coupon code can't be applied because you don't have all of the necessary products in your cart.
+ missing_taxon: You need to add a product from all applicable categories before applying this coupon code.
+ no_applicable_products: You need to add an applicable product before applying this coupon code.
+ no_matching_taxons: You need to add a product from an applicable category before applying this coupon code.
+ no_user_or_email_specified: You need to login or provide your email before applying this coupon code.
+ no_user_specified: You need to login before applying this coupon code.
+ not_first_order: This coupon code can only be applied to your first order.
+ email: Email
+ empty: Empty
+ empty_cart: Empty Cart
+ enable_mail_delivery: Enable Mail Delivery
+ end: End
+ ending_in: Ending in
+ environment: Environment
+ error: error
+ errors:
+ messages:
+ could_not_create_taxon: Could not create taxon
+ no_payment_methods_available: No payment methods are configured for this environment
+ no_shipping_methods_available: No shipping methods available for selected location, please change your address and try again.
+ errors_prohibited_this_record_from_being_saved:
+ one: 1 error prohibited this record from being saved
+ other: ! '%{count} errors prohibited this record from being saved'
+ event: Event
+ events:
+ spree:
+ cart:
+ add: Add to cart
+ checkout:
+ coupon_code_added: Coupon code added
+ content:
+ visited: Visit static content page
+ order:
+ contents_changed: Order contents changed
+ page_view: Static page viewed
+ user:
+ signup: User signup
+ exceptions:
+ count_on_hand_setter: Cannot set count_on_hand manually, as it is set automatically by the recalculate_count_on_hand callback. Please use `update_column(:count_on_hand, value)` instead.
+ exchange_for: Exchange for
+ expedited_exchanges_warning: "Any specified exchanges will ship to the customer immediately upon saving. The customer will be charged the full amount of the item if they do not return the original item within %{days_window} days."
+ excl: excl.
+ expiration: Expiration
+ extension: Extension
+ existing_shipments: Existing shipments
+ failed_payment_attempts: Failed Payment Attempts
+ filename: Filename
+ fill_in_customer_info: Please fill in customer info
+ filter_results: Filter Results
+ finalize: Finalize
+ find_a_taxon: Find a Taxon
+ finalized: Finalized
+ first_item: First Item
+ first_name: First Name
+ first_name_begins_with: First Name Begins With
+ flat_percent: Flat Percent
+ flat_rate_per_order: Flat Rate
+ flexible_rate: Flexible Rate
+ forgot_password: Forgot Password?
+ free_shipping: Free Shipping
+ free_shipping_amount: "-"
+ front_end: Front End
+ gateway: Gateway
+ gateway_config_unavailable: Gateway unavailable for environment
+ gateway_error: Gateway Error
+ general: General
+ general_settings: General Settings
+ google_analytics: Google Analytics
+ google_analytics_id: Analytics ID
+ guest_checkout: Guest Checkout
+ guest_user_account: Checkout as a Guest
+ has_no_shipped_units: has no shipped units
+ height: Height
+ hide_cents: Hide cents
+ home: Home
+ i18n:
+ available_locales: Available Locales
+ fields: Fields
+ language: Language
+ localization_settings: Localization Settings
+ only_incomplete: Only incomplete
+ only_complete: Only complete
+ select_locale: Select locale
+ show_only: Show only
+ supported_locales: Supported Locales
+ this_file_language: English (US)
+ translations: Translations
+ icon: Icon
+ identifier: Identifier
+ image: Image
+ images: Images
+ implement_eligible_for_return: "Must implement #eligible_for_return? for your EligibilityValidator."
+ implement_requires_manual_intervention: "Must implement #requires_manual_intervention? for your EligibilityValidator."
+ inactive: Inactive
+ incl: incl.
+ included_in_price: Included in Price
+ included_price_validation: cannot be selected unless you have set a Default Tax Zone
+ incomplete: Incomplete
+ info_product_has_multiple_skus: "This product has %{count} variants:"
+ info_number_of_skus_not_shown:
+ one: "and one other"
+ other: "and %{count} others"
+ instructions_to_reset_password: Please enter your email on the form below
+ insufficient_stock: Insufficient stock available, only %{on_hand} remaining
+ insufficient_stock_lines_present: Some line items in this order have insufficient quantity.
+ intercept_email_address: Intercept Email Address
+ intercept_email_instructions: Override email recipient and replace with this address.
+ internal_name: Internal Name
+ invalid_credit_card: Invalid credit card.
+ invalid_payment_provider: Invalid payment provider.
+ invalid_promotion_action: Invalid promotion action.
+ invalid_promotion_rule: Invalid promotion rule.
+ inventory: Inventory
+ inventory_adjustment: Inventory Adjustment
+ inventory_error_flash_for_insufficient_quantity: An item in your cart has become unavailable.
+ inventory_state: Inventory State
+ is_not_available_to_shipment_address: is not available to shipment address
+ iso_name: Iso Name
+ item: Item
+ item_description: Item Description
+ item_total: Item Total
+ item_total_rule:
+ operators:
+ gt: greater than
+ gte: greater than or equal to
+ lt: less than
+ lte: less than or equal to
+ items_cannot_be_shipped: We are unable to calculate shipping rates for the selected items.
+ items_in_rmas: Items in Return Authorizations
+ items_to_be_reimbursed: Items to be reimbursed
+ items_reimbursed: Items reimbursed
+ jirafe: Jirafe
+ landing_page_rule:
+ path: Path
+ last_name: Last Name
+ last_name_begins_with: Last Name Begins With
+ learn_more: Learn More
+ lifetime_stats: Lifetime Stats
+ line_item_adjustments: "Line item adjustments"
+ list: List
+ listing_countries: Listing Countries
+ listing_orders: Listing Orders
+ listing_products: Listing Products
+ listing_reports: Listing Reports
+ listing_tax_categories: Listing Tax Categories
+ listing_users: Listing Users
+ loading: Loading
+ locale_changed: Locale Changed
+ location: Location
+ lock: Lock
+ log_entries: "Log Entries"
+ logs: "Logs"
+ logged_in_as: Logged in as
+ logged_in_succesfully: Logged in successfully
+ logged_out: You have been logged out.
+ login: Login
+ login_as_existing: Login as Existing Customer
+ login_failed: Login authentication failed.
+ login_name: Login
+ logout: Logout
+ look_for_similar_items: Look for similar items
+ make_refund: Make refund
+ make_sure_the_above_reimbursement_amount_is_correct: Make sure the above reimbursement amount is correct
+ manage_promotion_categories: Manage Promotion Categories
+ manual_intervention_required: Manual intervention required
+ manage_variants: Manage Variants
+ master_price: Master Price
+ match_choices:
+ all: All
+ none: None
+ max_items: Max Items
+ member_since: Member Since
+ memo: Memo
+ meta_description: Meta Description
+ meta_keywords: Meta Keywords
+ meta_title: Meta Title
+ metadata: Metadata
+ minimal_amount: Minimal Amount
+ missing_return_authorization: ! 'Missing Return Authorization for %{item_name}.'
+ month: Month
+ more: More
+ move_stock_between_locations: Move Stock Between Locations
+ my_account: My Account
+ my_orders: My Orders
+ name: Name
+ name_on_card: Name on card
+ name_or_sku: Name or SKU (enter at least first 4 characters of product name)
+ new: New
+ new_adjustment: New Adjustment
+ new_customer: New Customer
+ new_customer_return: New Customer Return
+ new_country: New Country
+ new_image: New Image
+ new_option_type: New Option Type
+ new_order: New Order
+ new_order_completed: New Order Completed
+ new_payment: New Payment
+ new_payment_method: New Payment Method
+ new_product: New Product
+ new_promotion: New Promotion
+ new_promotion_category: New Promotion Category
+ new_property: New Property
+ new_prototype: New Prototype
+ new_refund: New Refund
+ new_refund_reason: New Refund Reason
+ new_rma_reason: New RMA Reason
+ new_return_authorization: New Return Authorization
+ new_shipping_category: New Shipping Category
+ new_shipping_method: New Shipping Method
+ new_shipment_at_location: New shipment at location
+ new_state: New State
+ new_stock_location: New Stock Location
+ new_stock_movement: New Stock Movement
+ new_stock_transfer: New Stock Transfer
+ new_tax_category: New Tax Category
+ new_tax_rate: New Tax Rate
+ new_taxon: New Taxon
+ new_taxonomy: New Taxonomy
+ new_tracker: New Tracker
+ new_user: New User
+ new_variant: New Variant
+ new_zone: New Zone
+ next: Next
+ no_actions_added: No actions added
+ no_images_found: No images found
+ no_orders_found: No orders found
+ no_payment_methods_found: No payment methods found
+ no_payment_found: No payment found
+ no_pending_payments: No pending payments
+ no_products_found: No products found
+ no_promotions_found: No promotions found
+ no_results: No results
+ no_rules_added: No rules added
+ no_resource_found: ! 'No %{resource} found'
+ no_shipping_methods_found: No shipping methods found
+ no_shipping_method_selected: No shipping method selected.
+ no_trackers_found: No Trackers Found
+ no_stock_locations_found: No stock locations found
+ no_tracking_present: No tracking details provided.
+ none: None
+ none_selected: None Selected
+ normal_amount: Normal Amount
+ not: not
+ not_available: N/A
+ not_enough_stock: There is not enough inventory at the source location to complete this transfer.
+ not_found: ! '%{resource} is not found'
+ note: Note
+ notice_messages:
+ product_cloned: Product has been cloned
+ product_deleted: Product has been deleted
+ product_not_cloned: Product could not be cloned
+ product_not_deleted: Product could not be deleted
+ variant_deleted: Variant has been deleted
+ variant_not_deleted: Variant could not be deleted
+ num_orders: "# Orders"
+ on_hand: On Hand
+ open: Open
+ open_all_adjustments: Open All Adjustments
+ option_type: Option Type
+ option_type_placeholder: Choose an option type
+ option_types: Option Types
+ option_value: Option Value
+ option_values: Option Values
+ optional: Optional
+ options: Options
+ or: or
+ or_over_price: ! '%{price} or over'
+ order: Order
+ order_adjustments: Order adjustments
+ order_already_updated: The order has already been updated.
+ order_approved: Order approved
+ order_canceled: Order canceled
+ order_details: Order Details
+ order_email_resent: Order Email Resent
+ order_information: Order Information
+ order_mailer:
+ cancel_email:
+ dear_customer: Dear Customer,
+ instructions: Your order has been CANCELED. Please retain this cancellation information for your records.
+ order_summary_canceled: Order Summary [CANCELED]
+ subject: Cancellation of Order
+ subtotal: ! 'Subtotal:'
+ total: ! 'Order Total:'
+ confirm_email:
+ dear_customer: Dear Customer,
+ instructions: Please review and retain the following order information for your records.
+ order_summary: Order Summary
+ subject: Order Confirmation
+ subtotal: ! 'Subtotal:'
+ thanks: Thank you for your business.
+ total: ! 'Order Total:'
+ order_not_found: We couldn't find your order. Please try that action again.
+ order_number: Order %{number}
+ order_populator:
+ out_of_stock: ! '%{item} is out of stock.'
+ selected_quantity_not_available: ! 'selected of %{item} is not available.'
+ please_enter_reasonable_quantity: Please enter a reasonable quantity.
+ order_processed_successfully: Your order has been processed successfully
+ order_resumed: Order resumed
+ order_state:
+ address: address
+ awaiting_return: awaiting return
+ canceled: canceled
+ cart: cart
+ considered_risky: considered risky
+ complete: complete
+ confirm: confirm
+ delivery: delivery
+ payment: payment
+ resumed: resumed
+ returned: returned
+ order_summary: Order Summary
+ order_sure_want_to: Are you sure you want to %{event} this order?
+ order_total: Order Total
+ order_updated: Order Updated
+ orders: Orders
+ out_of_stock: Out of Stock
+ overview: Overview
+ package_from: package from
+ pagination:
+ next_page: next page »
+ previous_page: ! '« previous page'
+ truncate: ! '…'
+ password: Password
+ paste: Paste
+ path: Path
+ pay: pay
+ payment: Payment
+ payment_could_not_be_created: Payment could not be created.
+ payment_identifier: Payment Identifier
+ payment_information: Payment Information
+ payment_method: Payment Method
+ payment_methods: Payment Methods
+ payment_method_not_supported: That payment method is unsupported. Please choose another one.
+ payment_processing_failed: Payment could not be processed, please check the details you entered
+ payment_processor_choose_banner_text: If you need help choosing a payment processor, please visit
+ payment_processor_choose_link: our payments page
+ payment_state: Payment State
+ payment_states:
+ balance_due: balance due
+ checkout: checkout
+ completed: completed
+ credit_owed: credit owed
+ failed: failed
+ paid: paid
+ pending: pending
+ processing: processing
+ void: void
+ payment_updated: Payment Updated
+ payments: Payments
+ percent: Percent
+ percent_per_item: Percent Per Item
+ permalink: Permalink
+ pending: Pending
+ phone: Phone
+ place_order: Place Order
+ please_define_payment_methods: Please define some payment methods first.
+ populate_get_error: Something went wrong. Please try adding the item again.
+ powered_by: Powered by
+ pre_tax_refund_amount: Pre-Tax Refund Amount
+ pre_tax_amount: Pre-Tax Amount
+ pre_tax_total: Pre-Tax Total
+ preferred_reimbursement_type: Preferred Reimbursement Type
+ presentation: Presentation
+ previous: Previous
+ price: Price
+ price_range: Price Range
+ price_sack: Price Sack
+ process: Process
+ product: Product
+ product_details: Product Details
+ product_has_no_description: This product has no description
+ product_not_available_in_this_currency: This product is not available in the selected currency.
+ product_properties: Product Properties
+ product_rule:
+ choose_products: Choose products
+ label: Order must contain %{select} of these products
+ match_all: all
+ match_any: at least one
+ match_none: none
+ product_source:
+ group: From product group
+ manual: Manually choose
+ products: Products
+ promotion: Promotion
+ promotionable: Promotable
+ promotion_action: Promotion Action
+ promotion_action_types:
+ create_adjustment:
+ description: Creates a promotion credit adjustment on the order
+ name: Create whole-order adjustment
+ create_item_adjustments:
+ description: Creates a promotion credit adjustment on a line item
+ name: Create per-line-item adjustment
+ create_line_items:
+ description: Populates the cart with the specified quantity of variant
+ name: Create line items
+ free_shipping:
+ description: Makes all shipments for the order free
+ name: Free shipping
+ promotion_actions: Actions
+ promotion_form:
+ match_policies:
+ all: Match all of these rules
+ any: Match any of these rules
+ promotion_rule: Promotion Rule
+ promotion_rule_types:
+ first_order:
+ description: Must be the customer's first order
+ name: First order
+ item_total:
+ description: Order total meets these criteria
+ name: Item total
+ landing_page:
+ description: Customer must have visited the specified page
+ name: Landing Page
+ one_use_per_user:
+ description: Only One Use Per User
+ name: One Use Per User
+ product:
+ description: Order includes specified product(s)
+ name: Product(s)
+ user:
+ description: Available only to the specified users
+ name: User
+ user_logged_in:
+ description: Available only to logged in users
+ name: User Logged In
+ taxon:
+ description: Order includes products with specified taxon(s)
+ name: Taxon(s)
+ promotions: Promotions
+ promotion_uses: Promotion uses
+ propagate_all_variants: Propagate all variants
+ properties: Properties
+ property: Property
+ prototype: Prototype
+ prototypes: Prototypes
+ provider: Provider
+ provider_settings_warning: If you are changing the provider type, you must save first before you can edit the provider settings
+ qty: Qty
+ quantity: Quantity
+ quantity_returned: Quantity Returned
+ quantity_shipped: Quantity Shipped
+ rate: Rate
+ reason: Reason
+ receive: receive
+ receive_stock: Receive Stock
+ received: Received
+ reception_status: Reception Status
+ reference: Reference
+ refund: Refund
+ refund_amount_must_be_greater_than_zero: Refund amount must be greater than zero
+ refund_reasons: Refund Reasons
+ refunded_amount: Refunded Amount
+ refunds: Refunds
+ refund_amount_must_be_greater_than_zero: Refund amount must be greater than zero
+ register: Register
+ registration: Registration
+ reimburse: Reimburse
+ reimbursed: Reimbursed
+ reimbursement: Reimbursement
+ reimbursement_perform_failed: "Reimbursement could not be performed. Error: %{error}"
+ reimbursement_status: Reimbursement status
+ reimbursement_type: Reimbursement type
+ reimbursement_type_override: Reimbursement Type Override
+ reimbursement_types: Reimbursement Types
+ reimbursements: Reimbursements
+ reject: Reject
+ rejected: Rejected
+ remember_me: Remember me
+ remove: Remove
+ rename: Rename
+ reports: Reports
+ resend: Resend
+ reset_password: Reset my password
+ response_code: Response Code
+ resume: resume
+ resumed: Resumed
+ return: return
+ return_authorization: Return Authorization
+ return_authorization_reasons: Return Authorization Reasons
+ return_authorization_updated: Return authorization updated
+ return_authorizations: Return Authorizations
+ return_item_inventory_unit_ineligible: Return item's inventory unit must be shipped
+ return_item_inventory_unit_reimbursed: Return item's inventory unit is already reimbursed
+ return_item_order_not_completed: Return item's order must be completed
+ return_item_rma_ineligible: Return item requires an RMA
+ return_item_time_period_ineligible: Return item is outside the eligible time period
+ return_items: Return Items
+ return_items_cannot_be_associated_with_multiple_orders: Return items cannot be associated with multiple orders.
+ reimbursement_mailer:
+ reimbursement_email:
+ days_to_send: ! 'You have %{days} days to send back any items awaiting exchange.'
+ dear_customer: Dear Customer,
+ exchange_summary: Exchange Summary
+ for: for
+ instructions: Your reimbursement has been processed.
+ refund_summary: Refund Summary
+ subject: Reimbursement Notification
+ total_refunded: ! 'Total refunded: %{total}'
+ return_number: Return Number
+ return_quantity: Return Quantity
+ returned: Returned
+ review: Review
+ risk: Risk
+ risk_analysis: Risk Analysis
+ risky: Risky
+ rma_credit: RMA Credit
+ rma_number: RMA Number
+ rma_value: RMA Value
+ roles: Roles
+ rules: Rules
+ sales_total: Sales Total
+ sales_total_description: Sales Total For All Orders
+ sales_totals: Sales Totals
+ save_and_continue: Save and Continue
+ save_my_address: Save my address
+ say_no: 'No'
+ say_yes: 'Yes'
+ scope: Scope
+ search: Search
+ search_results: Search results for '%{keywords}'
+ searching: Searching
+ secure_connection_type: Secure Connection Type
+ security_settings: Security Settings
+ select: Select
+ select_from_prototype: Select From Prototype
+ select_a_return_authorization_reason: Select a reason for the return authorization
+ select_a_stock_location: Select a stock location
+ select_stock: Select stock
+ send_copy_of_all_mails_to: Send Copy of All Mails To
+ send_mails_as: Send Mails As
+ server: Server
+ server_error: The server returned an error
+ settings: Settings
+ ship: ship
+ ship_address: Ship Address
+ ship_total: Ship Total
+ shipment: Shipment
+ shipment_adjustments: "Shipment adjustments"
+ shipment_details: "From %{stock_location} via %{shipping_method}"
+ shipment_mailer:
+ shipped_email:
+ dear_customer: Dear Customer,
+ instructions: Your order has been shipped
+ shipment_summary: Shipment Summary
+ subject: Shipment Notification
+ thanks: Thank you for your business.
+ track_information: ! 'Tracking Information: %{tracking}'
+ track_link: ! 'Tracking Link: %{url}'
+ shipment_state: Shipment State
+ shipment_states:
+ backorder: backorder
+ canceled: canceled
+ partial: partial
+ pending: pending
+ ready: ready
+ shipped: shipped
+ shipment_transfer_success: 'Variants successfully transferred'
+ shipment_transfer_error: 'There was an error transferring variants'
+ shipments: Shipments
+ shipped: Shipped
+ shipping: Shipping
+ shipping_address: Shipping Address
+ shipping_categories: Shipping Categories
+ shipping_category: Shipping Category
+ shipping_flat_rate_per_item: Flat rate per package item
+ shipping_flat_rate_per_order: Flat rate
+ shipping_flexible_rate: Flexible Rate per package item
+ shipping_instructions: Shipping Instructions
+ shipping_method: Shipping Method
+ shipping_methods: Shipping Methods
+ shipping_price_sack: Price sack
+ shipping_total: Shipping total
+ shop_by_taxonomy: Shop by %{taxonomy}
+ shopping_cart: Shopping Cart
+ show: Show
+ show_active: Show Active
+ show_deleted: Show Deleted
+ show_only_complete_orders: Only show complete orders
+ show_only_considered_risky: Only show risky orders
+ show_rate_in_label: Show rate in label
+ sku: SKU
+ skus: SKUs
+ slug: Slug
+ source: Source
+ special_instructions: Special Instructions
+ split: Split
+ spree_gateway_error_flash_for_checkout: There was a problem with your payment information. Please check your information and try again.
+ ssl:
+ change_protocol: "Please switch to using HTTP (rather than HTTPS) and retry this request."
+ start: Start
+ state: State
+ state_based: State Based
+ states: States
+ states_required: States Required
+ status: Status
+ stock_location: Stock Location
+ stock_location_info: Stock location info
+ stock_locations: Stock Locations
+ stock_locations_need_a_default_country: You must create a default country before creating a stock location.
+ stock_management: Stock Management
+ stock_management_requires_a_stock_location: Please create a stock location in order to manage stock.
+ stock_movements: Stock Movements
+ stock_movements_for_stock_location: Stock Movements for %{stock_location_name}
+ stock_successfully_transferred: Stock was successfully transferred between locations.
+ stock_transfer: Stock Transfer
+ stock_transfers: Stock Transfers
+ stop: Stop
+ store: Store
+ street_address: Street Address
+ street_address_2: Street Address (cont'd)
+ subtotal: Subtotal
+ subtract: Subtract
+ success: Success
+ successfully_created: ! '%{resource} has been successfully created!'
+ successfully_refunded: ! '%{resource} has been successfully refunded!'
+ successfully_removed: ! '%{resource} has been successfully removed!'
+ successfully_signed_up_for_analytics: Successfully signed up for Spree Analytics
+ successfully_updated: ! '%{resource} has been successfully updated!'
+ tax: Tax
+ tax_included: "Tax (incl.)"
+ tax_categories: Tax Categories
+ tax_category: Tax Category
+ tax_code: Tax Code
+ tax_rate_amount_explanation: Tax rates are a decimal amount to aid in calculations, (i.e. if the tax rate is 5% then enter 0.05)
+ tax_rates: Tax Rates
+ taxon: Taxon
+ taxon_edit: Edit Taxon
+ taxon_placeholder: Add a Taxon
+ taxon_rule:
+ choose_taxons: Choose taxons
+ label: Order must contain %{select} of these taxons
+ match_all: all
+ match_any: at least one
+ taxonomies: Taxonomies
+ taxonomy: Taxonomy
+ taxonomy_edit: Edit taxonomy
+ taxonomy_tree_error: The requested change has not been accepted and the tree has been returned to its previous state, please try again.
+ taxonomy_tree_instruction: ! '* Right click a child in the tree to access the menu for adding, deleting or sorting a child.'
+ taxons: Taxons
+ test: Test
+ test_mailer:
+ test_email:
+ greeting: Congratulations!
+ message: If you have received this email, then your email settings are correct.
+ subject: Test Mail
+ test_mode: Test Mode
+ thank_you_for_your_order: Thank you for your business. Please print out a copy of this confirmation page for your records.
+ there_are_no_items_for_this_order: There are no items for this order. Please add an item to the order to continue.
+ there_were_problems_with_the_following_fields: There were problems with the following fields
+ this_order_has_already_received_a_refund: This order has already received a refund
+ thumbnail: Thumbnail
+ tiers: Tiers
+ tiered_flat_rate: Tiered Flat Rate
+ tiered_percent: Tiered Percent
time: Time
- spree_gateway_error_flash_for_checkout: "There was a problem with your payment information. Please check your information and try again."
- spree_inventory_error_flash_for_insufficient_quantity: "An item in your cart has become unavailable."
- ssl_will_be_used_in_development_and_test_modes: "SSL will be used in development and test mode if necessary."
- ssl_will_be_used_in_production_mode: "SSL will be used in production mode"
- ssl_will_be_used_in_staging_mode: "SSL will be used in staging mode"
- ssl_will_not_be_used_in_development_and_test_modes: "SSL will not be used in development and test mode if necessary."
- ssl_will_not_be_used_in_production_mode: "SSL will not be used in production mode"
- ssl_will_not_be_used_in_staging_mode: "SSL will not be used in staging mode"
- spree_alert_checking: "Check for Spree security and release alerts"
- spree_alert_not_checking: "Not checking for Spree security and release alerts"
- start: Start
- start_date: Valid from
- state: State
- state_based: "State Based"
- state_setting_description: "Administer the list of states/provinces associated with each country."
- states: States
- status: Status
- stop: Stop
- store: Store
- street_address: "Street Address"
- street_address_2: "Street Address (cont'd)"
- subtotal: Subtotal
- subtract: Subtract
- successfully_created: "%{resource} has been successfully created!"
- successfully_removed: "%{resource} has been successfully removed!"
- successfully_updated: "%{resource} has been successfully updated!"
- system: System
- tax: Tax
- tax_categories: "Tax Categories"
- tax_categories_setting_description: "Set up tax categories to identify which products should be taxable."
- tax_category: "Tax Category"
- tax_rates: "Tax Rates"
- tax_rates_description: Tax rates setup and configuration.
- tax_settings: "Tax Settings"
- tax_settings_description: Basic tax settings.
- tax_total: "Tax Total"
- tax_type: "Tax Type"
- taxon: Taxon
- taxon_edit: Edit Taxon
- taxonomy: Taxonomy
- taxonomies: Taxonomies
- taxonomies_setting_description: "Create and manage taxonomies."
- taxonomy_edit: "Edit taxonomy"
- taxonomy_tree_error: "The requested change has not been accepted and the tree has been returned to its previous state, please try again."
- taxonomy_tree_instruction: "* Right click a child in the tree to access the menu for adding, deleting or sorting a child."
- taxons: Taxons
- test: "Test"
- test_mailer:
- test_email:
- greeting: 'Congratulations!'
- message: 'If you have received this email, then your email settings are correct.'
- subject: 'Testmail'
- test_mode: Test Mode
- thank_you_for_your_order: "Thank you for your business. Please print out a copy of this confirmation page for your records."
- there_were_problems_with_the_following_fields: "There were problems with the following fields"
- this_file_language: "English (US)"
- thumbnail: "Thumbnail"
- to_add_variants_you_must_first_define: "To add variants, you must first define"
- to_state: "To State"
- total: Total
- tracking: Tracking
- transaction: Transaction
- transactions: Transactions
- tree: Tree
- try_again: "Try Again"
- type: Type
- type_to_search: Type to search
- unable_ship_method: "Unable to generate shipping methods due to a server error."
- unable_to_authorize_credit_card: "Unable to Authorize Credit Card"
- unable_to_capture_credit_card: "Unable to Capture Credit Card"
- unable_to_connect_to_gateway: "Unable to connect to gateway."
- unable_to_save_order: "Unable to Save Order"
- under_price: "Under %{price}"
- under_paid: "Under Paid"
- unrecognized_card_type: Unrecognized card type
- update: Update
- update_password: "Update my password and log me in"
- updated_successfully: "Updated Successfully"
- updating: Updating
- usage_limit: Usage Limit
- use_as_shipping_address: Use as Shipping Address
- use_billing_address: Use Billing Address
- use_different_shipping_address: "Use Different Shipping Address"
- use_new_cc: "Use a new card"
- use_s3: "Use Amazon S3 For Images"
- user: User
- user_account: User Account
- user_created_successfully: "User created successfully"
- users: Users
- validate_on_profile_create: Validate on profile create
- validation:
- cannot_be_greater_than_available_stock: "cannot be greater than available stock."
- cannot_be_less_than_shipped_units: "cannot be less than the number of shipped units."
- cannot_destory_line_item_as_inventory_units_have_shipped: "Cannot destory line item as some inventory units have shipped."
- is_too_large: "is too large -- stock on hand cannot cover requested quantity!"
- must_be_int: "must be an integer"
- must_be_non_negative: "must be a non-negative value"
- value: Value
- variant: Variant
- variants: Variants
- vat: "VAT"
- version: Version
- view_shipping_options: "View shipping options"
- void: Void
- website: Website
- weight: Weight
- welcome_to_sample_store: "Welcome to the sample store"
- what_is_a_cvv: "What is a (CVV) Credit Card Code?"
- what_is_this: "What's This?"
- whats_this: "What's this"
- width: Width
- year: "Year"
- you_have_been_logged_out: "You have been logged out."
- you_have_no_orders_yet: "You have no orders yet."
- your_cart_is_empty: "Your cart is empty"
- zip: Zip
- zone: Zone
- zone_based: "Zone Based"
- zone_setting_description: "Collections of countries, states or other zones to be used in various calculations."
- zones: Zones
+ to_add_variants_you_must_first_define: To add variants, you must first define
+ total: Total
+ total_per_item: Total per item
+ total_pre_tax_refund: Total Pre-Tax Refund
+ total_price: Total price
+ total_sales: Total Sales
+ track_inventory: Track Inventory
+ tracking: Tracking
+ tracking_number: Tracking Number
+ tracking_url: Tracking URL
+ tracking_url_placeholder: e.g. http://quickship.com/package?num=:tracking
+ transaction_id: Transaction ID
+ transfer_from_location: Transfer From
+ transfer_stock: Transfer Stock
+ transfer_to_location: Transfer To
+ tree: Tree
+ type: Type
+ type_to_search: Type to search
+ unable_to_connect_to_gateway: Unable to connect to gateway.
+ unable_to_create_reimbursements: Unable to create reimbursements because there are items pending manual intervention.
+ under_price: Under %{price}
+ unlock: Unlock
+ unrecognized_card_type: Unrecognized card type
+ unshippable_items: Unshippable Items
+ update: Update
+ updating: Updating
+ usage_limit: Usage Limit
+ use_app_default: Use App Default
+ use_billing_address: Use Billing Address
+ use_existing_cc: Use an existing card on file
+ use_new_cc: Use a new card
+ use_new_cc_or_payment_method: Use a new card / payment method
+ use_s3: Use Amazon S3 For Images
+ user: User
+ user_rule:
+ choose_users: Choose users
+ users: Users
+ validation:
+ unpaid_amount_not_zero: "Amount was not fully reimbursed. Still due: %{amount}"
+ cannot_be_less_than_shipped_units: cannot be less than the number of shipped units.
+ cannot_destroy_line_item_as_inventory_units_have_shipped: Cannot destroy line item as some inventory units have shipped.
+ exceeds_available_stock: exceeds available stock. Please ensure line items have a valid quantity.
+ is_too_large: is too large -- stock on hand cannot cover requested quantity!
+ must_be_int: must be an integer
+ must_be_non_negative: must be a non-negative value
+ value: Value
+ variant: Variant
+ variant_placeholder: Choose a variant
+ variants: Variants
+ version: Version
+ void: Void
+ weight: Weight
+ what_is_a_cvv: What is a (CVV) Credit Card Code?
+ what_is_this: What's This?
+ width: Width
+ year: Year
+ you_have_no_orders_yet: You have no orders yet
+ your_cart_is_empty: Your cart is empty
+ your_order_is_empty_add_product: Your order is empty, please search for and add a product above
+ zip: Zip
+ zipcode: Zip Code
+ zone: Zone
+ zones: Zones
diff --git a/core/config/routes.rb b/core/config/routes.rb
old mode 100755
new mode 100644
index 29b0629c056..d673dd5c6af
--- a/core/config/routes.rb
+++ b/core/config/routes.rb
@@ -1,191 +1 @@
-Spree::Core::Engine.routes.draw do
-
- root :to => 'home#index'
-
- resources :products
-
- match '/locale/set', :to => 'locale#set'
-
- resources :tax_categories
-
- resources :states, :only => :index
- resources :countries, :only => :index
-
- # non-restful checkout stuff
- put '/checkout/update/:state', :to => 'checkout#update', :as => :update_checkout
- get '/checkout/:state', :to => 'checkout#edit', :as => :checkout_state
- get '/checkout', :to => 'checkout#edit' , :as => :checkout
-
- populate_redirect = redirect do |params, request|
- request.flash[:error] = I18n.t(:populate_get_error)
- request.referer || '/cart'
- end
-
- get '/orders/populate', :via => :get, :to => populate_redirect
-
- resources :orders do
- post :populate, :on => :collection
-
- resources :line_items
-
- resources :shipments do
- member do
- get :shipping_method
- end
- end
-
- end
- get '/cart', :to => 'orders#edit', :as => :cart
- put '/cart', :to => 'orders#update', :as => :update_cart
- put '/cart/empty', :to => 'orders#empty', :as => :empty_cart
-
- resources :shipments do
- member do
- get :shipping_method
- put :shipping_method
- end
- end
-
- # route globbing for pretty nested taxon and product paths
- match '/t/*id', :to => 'taxons#show', :as => :nested_taxons
-
- namespace :admin do
- get '/search/users', :to => "search#users", :as => :search_users
-
- resources :adjustments
- resources :zones
- resources :banners do
- member do
- post :dismiss
- end
- end
- resources :countries do
- resources :states
- end
- resources :states
- resources :tax_categories
- resources :products do
- resources :product_properties
- resources :images do
- collection do
- post :update_positions
- end
- end
- member do
- get :clone
- end
- resources :variants do
- collection do
- post :update_positions
- end
- end
- end
-
- get '/variants/search', :to => "variants#search", :as => :search_variants
-
- resources :option_types do
- collection do
- post :update_positions
- post :update_values_positions
- end
- end
-
- resources :properties do
- collection do
- get :filtered
- end
- end
-
- resources :prototypes do
- member do
- get :select
- end
-
- collection do
- get :available
- end
- end
-
- resource :inventory_settings
- resource :image_settings
- resources :google_analytics
-
- resources :orders do
- member do
- put :fire
- get :fire
- post :resend
- end
-
- resource :customer, :controller => "orders/customer_details"
-
- resources :adjustments
- resources :line_items
- resources :shipments do
- member do
- put :fire
- end
- end
- resources :return_authorizations do
- member do
- put :fire
- end
- end
- resources :payments do
- member do
- put :fire
- end
- end
- end
-
- resource :general_settings do
- collection do
- post :dismiss_alert
- end
- end
-
- resources :taxonomies do
- collection do
- post :update_positions
- end
- member do
- get :get_children
- end
-
- resources :taxons
- end
-
- resources :taxons, :only => [] do
- collection do
- get :search
- end
- end
-
- resources :reports, :only => [:index, :show] do
- collection do
- get :sales_total
- post :sales_total
- end
- end
-
- resources :shipping_methods
- resources :shipping_categories
- resources :tax_rates
- resource :tax_settings
- resources :calculators
-
- resources :trackers
- resources :payment_methods
- resources :mail_methods do
- member do
- post :testmail
- end
- end
- end
-
- match '/admin', :to => 'admin/orders#index', :as => :admin
-
- match '/unauthorized', :to => 'home#unauthorized', :as => :unauthorized
- match '/content/cvv', :to => 'content#cvv', :as => :cvv
- match '/content/*path', :to => 'content#show', :via => :get, :as => :content
-end
+Spree::Core::Engine.draw_routes
\ No newline at end of file
diff --git a/core/db/default/spree/countries.rb b/core/db/default/spree/countries.rb
new file mode 100644
index 00000000000..e1b86de29a2
--- /dev/null
+++ b/core/db/default/spree/countries.rb
@@ -0,0 +1,19 @@
+require 'carmen'
+
+countries = []
+Carmen::Country.all.each do |country|
+ countries << {
+ name: country.name,
+ iso3: country.alpha_3_code,
+ iso: country.alpha_2_code,
+ iso_name: country.name.upcase,
+ numcode: country.numeric_code,
+ states_required: country.subregions?
+ }
+end
+
+ActiveRecord::Base.transaction do
+ Spree::Country.create!(countries)
+end
+
+Spree::Config[:default_country_id] = Spree::Country.find_by(name: "United States").id
diff --git a/core/db/default/spree/countries.yml b/core/db/default/spree/countries.yml
deleted file mode 100644
index 875cca816e0..00000000000
--- a/core/db/default/spree/countries.yml
+++ /dev/null
@@ -1,1589 +0,0 @@
----
-countries_039:
- name: Chad
- iso3: TCD
- iso: TD
- iso_name: CHAD
- id: "39"
- numcode: "148"
-countries_065:
- name: Faroe Islands
- iso3: FRO
- iso: FO
- iso_name: FAROE ISLANDS
- id: "65"
- numcode: "234"
-countries_092:
- name: India
- iso3: IND
- iso: IN
- iso_name: INDIA
- id: "92"
- numcode: "356"
-countries_146:
- name: Nicaragua
- iso3: NIC
- iso: NI
- iso_name: NICARAGUA
- id: "146"
- numcode: "558"
-countries_172:
- name: Saint Lucia
- iso3: LCA
- iso: LC
- iso_name: SAINT LUCIA
- id: "172"
- numcode: "662"
-countries_066:
- name: Fiji
- iso3: FJI
- iso: FJ
- iso_name: FIJI
- id: "66"
- numcode: "242"
-countries_093:
- name: Indonesia
- iso3: IDN
- iso: ID
- iso_name: INDONESIA
- id: "93"
- numcode: "360"
-countries_147:
- name: Niger
- iso3: NER
- iso: NE
- iso_name: NIGER
- id: "147"
- numcode: "562"
-countries_173:
- name: Saint Pierre and Miquelon
- iso3: SPM
- iso: PM
- iso_name: SAINT PIERRE AND MIQUELON
- id: "173"
- numcode: "666"
-countries_067:
- name: Finland
- iso3: FIN
- iso: FI
- iso_name: FINLAND
- id: "67"
- numcode: "246"
-countries_148:
- name: Nigeria
- iso3: NGA
- iso: NG
- iso_name: NIGERIA
- id: "148"
- numcode: "566"
-countries_174:
- name: Saint Vincent and the Grenadines
- iso3: VCT
- iso: VC
- iso_name: SAINT VINCENT AND THE GRENADINES
- id: "174"
- numcode: "670"
-countries_068:
- name: France
- iso3: FRA
- iso: FR
- iso_name: FRANCE
- id: "68"
- numcode: "250"
-countries_094:
- name: Iran, Islamic Republic of
- iso3: IRN
- iso: IR
- iso_name: IRAN, ISLAMIC REPUBLIC OF
- id: "94"
- numcode: "364"
-countries_149:
- name: Niue
- iso3: NIU
- iso: NU
- iso_name: NIUE
- id: "149"
- numcode: "570"
-countries_175:
- name: Samoa
- iso3: WSM
- iso: WS
- iso_name: SAMOA
- id: "175"
- numcode: "882"
-countries_069:
- name: French Guiana
- iso3: GUF
- iso: GF
- iso_name: FRENCH GUIANA
- id: "69"
- numcode: "254"
-countries_095:
- name: Iraq
- iso3: IRQ
- iso: IQ
- iso_name: IRAQ
- id: "95"
- numcode: "368"
-countries_176:
- name: San Marino
- iso3: SMR
- iso: SM
- iso_name: SAN MARINO
- id: "176"
- numcode: "674"
-countries_096:
- name: Ireland
- iso3: IRL
- iso: IE
- iso_name: IRELAND
- id: "96"
- numcode: "372"
-countries_177:
- name: Sao Tome and Principe
- iso3: STP
- iso: ST
- iso_name: SAO TOME AND PRINCIPE
- id: "177"
- numcode: "678"
-countries_097:
- name: Israel
- iso3: ISR
- iso: IL
- iso_name: ISRAEL
- id: "97"
- numcode: "376"
-countries_178:
- name: Saudi Arabia
- iso3: SAU
- iso: SA
- iso_name: SAUDI ARABIA
- id: "178"
- numcode: "682"
-countries_098:
- name: Italy
- iso3: ITA
- iso: IT
- iso_name: ITALY
- id: "98"
- numcode: "380"
-countries_179:
- name: Senegal
- iso3: SEN
- iso: SN
- iso_name: SENEGAL
- id: "179"
- numcode: "686"
-countries_099:
- name: Jamaica
- iso3: JAM
- iso: JM
- iso_name: JAMAICA
- id: "99"
- numcode: "388"
-countries_100:
- name: Japan
- iso3: JPN
- iso: JP
- iso_name: JAPAN
- id: "100"
- numcode: "392"
-countries_101:
- name: Jordan
- iso3: JOR
- iso: JO
- iso_name: JORDAN
- id: "101"
- numcode: "400"
-countries_020:
- name: Belgium
- iso3: BEL
- iso: BE
- iso_name: BELGIUM
- id: "20"
- numcode: "56"
-countries_021:
- name: Belize
- iso3: BLZ
- iso: BZ
- iso_name: BELIZE
- id: "21"
- numcode: "84"
-countries_102:
- name: Kazakhstan
- iso3: KAZ
- iso: KZ
- iso_name: KAZAKHSTAN
- id: "102"
- numcode: "398"
-countries_210:
- name: Uganda
- iso3: UGA
- iso: UG
- iso_name: UGANDA
- id: "210"
- numcode: "800"
-countries_022:
- name: Benin
- iso3: BEN
- iso: BJ
- iso_name: BENIN
- id: "22"
- numcode: "204"
-countries_103:
- name: Kenya
- iso3: KEN
- iso: KE
- iso_name: KENYA
- id: "103"
- numcode: "404"
-countries_211:
- name: Ukraine
- iso3: UKR
- iso: UA
- iso_name: UKRAINE
- id: "211"
- numcode: "804"
-countries_023:
- name: Bermuda
- iso3: BMU
- iso: BM
- iso_name: BERMUDA
- id: "23"
- numcode: "60"
-countries_104:
- name: Kiribati
- iso3: KIR
- iso: KI
- iso_name: KIRIBATI
- id: "104"
- numcode: "296"
-countries_130:
- name: Mexico
- iso3: MEX
- iso: MX
- iso_name: MEXICO
- id: "130"
- numcode: "484"
-countries_212:
- name: United Arab Emirates
- iso3: ARE
- iso: AE
- iso_name: UNITED ARAB EMIRATES
- id: "212"
- numcode: "784"
-countries_024:
- name: Bhutan
- iso3: BTN
- iso: BT
- iso_name: BHUTAN
- id: "24"
- numcode: "64"
-countries_050:
- name: Cuba
- iso3: CUB
- iso: CU
- iso_name: CUBA
- id: "50"
- numcode: "192"
-countries_105:
- name: North Korea
- iso3: PRK
- iso: KP
- iso_name: KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF
- id: "105"
- numcode: "408"
-countries_131:
- name: Micronesia, Federated States of
- iso3: FSM
- iso: FM
- iso_name: MICRONESIA, FEDERATED STATES OF
- id: "131"
- numcode: "583"
-countries_213:
- name: United Kingdom
- iso3: GBR
- iso: GB
- iso_name: UNITED KINGDOM
- id: "213"
- numcode: "826"
-countries_025:
- name: Bolivia
- iso3: BOL
- iso: BO
- iso_name: BOLIVIA
- id: "25"
- numcode: "68"
-countries_051:
- name: Cyprus
- iso3: CYP
- iso: CY
- iso_name: CYPRUS
- id: "51"
- numcode: "196"
-countries_106:
- name: South Korea
- iso3: KOR
- iso: KR
- iso_name: KOREA, REPUBLIC OF
- id: "106"
- numcode: "410"
-countries_132:
- name: Moldova, Republic of
- iso3: MDA
- iso: MD
- iso_name: MOLDOVA, REPUBLIC OF
- id: "132"
- numcode: "498"
-countries_214:
- name: United States
- iso3: USA
- iso: US
- iso_name: UNITED STATES
- id: "214"
- numcode: "840"
-countries_026:
- name: Bosnia and Herzegovina
- iso3: BIH
- iso: BA
- iso_name: BOSNIA AND HERZEGOVINA
- id: "26"
- numcode: "70"
-countries_052:
- name: Czech Republic
- iso3: CZE
- iso: CZ
- iso_name: CZECH REPUBLIC
- id: "52"
- numcode: "203"
-countries_107:
- name: Kuwait
- iso3: KWT
- iso: KW
- iso_name: KUWAIT
- id: "107"
- numcode: "414"
-countries_133:
- name: Monaco
- iso3: MCO
- iso: MC
- iso_name: MONACO
- id: "133"
- numcode: "492"
-countries_215:
- name: Uruguay
- iso3: URY
- iso: UY
- iso_name: URUGUAY
- id: "215"
- numcode: "858"
-countries_027:
- name: Botswana
- iso3: BWA
- iso: BW
- iso_name: BOTSWANA
- id: "27"
- numcode: "72"
-countries_053:
- name: Denmark
- iso3: DNK
- iso: DK
- iso_name: DENMARK
- id: "53"
- numcode: "208"
-countries_080:
- name: Guadeloupe
- iso3: GLP
- iso: GP
- iso_name: GUADELOUPE
- id: "80"
- numcode: "312"
-countries_108:
- name: Kyrgyzstan
- iso3: KGZ
- iso: KG
- iso_name: KYRGYZSTAN
- id: "108"
- numcode: "417"
-countries_134:
- name: Mongolia
- iso3: MNG
- iso: MN
- iso_name: MONGOLIA
- id: "134"
- numcode: "496"
-countries_160:
- name: Philippines
- iso3: PHL
- iso: PH
- iso_name: PHILIPPINES
- id: "160"
- numcode: "608"
-countries_028:
- name: Brazil
- iso3: BRA
- iso: BR
- iso_name: BRAZIL
- id: "28"
- numcode: "76"
-countries_054:
- name: Djibouti
- iso3: DJI
- iso: DJ
- iso_name: DJIBOUTI
- id: "54"
- numcode: "262"
-countries_081:
- name: Guam
- iso3: GUM
- iso: GU
- iso_name: GUAM
- id: "81"
- numcode: "316"
-countries_109:
- name: Lao People's Democratic Republic
- iso3: LAO
- iso: LA
- iso_name: LAO PEOPLE'S DEMOCRATIC REPUBLIC
- id: "109"
- numcode: "418"
-countries_135:
- name: Montserrat
- iso3: MSR
- iso: MS
- iso_name: MONTSERRAT
- id: "135"
- numcode: "500"
-countries_161:
- name: Pitcairn
- iso3: PCN
- iso: PN
- iso_name: PITCAIRN
- id: "161"
- numcode: "612"
-countries_216:
- name: Uzbekistan
- iso3: UZB
- iso: UZ
- iso_name: UZBEKISTAN
- id: "216"
- numcode: "860"
-countries_029:
- name: Brunei Darussalam
- iso3: BRN
- iso: BN
- iso_name: BRUNEI DARUSSALAM
- id: "29"
- numcode: "96"
-countries_055:
- name: Dominica
- iso3: DMA
- iso: DM
- iso_name: DOMINICA
- id: "55"
- numcode: "212"
-countries_082:
- name: Guatemala
- iso3: GTM
- iso: GT
- iso_name: GUATEMALA
- id: "82"
- numcode: "320"
-countries_136:
- name: Morocco
- iso3: MAR
- iso: MA
- iso_name: MOROCCO
- id: "136"
- numcode: "504"
-countries_162:
- name: Poland
- iso3: POL
- iso: PL
- iso_name: POLAND
- id: "162"
- numcode: "616"
-countries_217:
- name: Vanuatu
- iso3: VUT
- iso: VU
- iso_name: VANUATU
- id: "217"
- numcode: "548"
-countries_056:
- name: Dominican Republic
- iso3: DOM
- iso: DO
- iso_name: DOMINICAN REPUBLIC
- id: "56"
- numcode: "214"
-countries_137:
- name: Mozambique
- iso3: MOZ
- iso: MZ
- iso_name: MOZAMBIQUE
- id: "137"
- numcode: "508"
-countries_163:
- name: Portugal
- iso3: PRT
- iso: PT
- iso_name: PORTUGAL
- id: "163"
- numcode: "620"
-countries_190:
- name: Sudan
- iso3: SDN
- iso: SD
- iso_name: SUDAN
- id: "190"
- numcode: "736"
-countries_218:
- name: Venezuela
- iso3: VEN
- iso: VE
- iso_name: VENEZUELA
- id: "218"
- numcode: "862"
-countries_057:
- name: Ecuador
- iso3: ECU
- iso: EC
- iso_name: ECUADOR
- id: "57"
- numcode: "218"
-countries_083:
- name: Guinea
- iso3: GIN
- iso: GN
- iso_name: GUINEA
- id: "83"
- numcode: "324"
-countries_138:
- name: Myanmar
- iso3: MMR
- iso: MM
- iso_name: MYANMAR
- id: "138"
- numcode: "104"
-countries_164:
- name: Puerto Rico
- iso3: PRI
- iso: PR
- iso_name: PUERTO RICO
- id: "164"
- numcode: "630"
-countries_191:
- name: Suriname
- iso3: SUR
- iso: SR
- iso_name: SURINAME
- id: "191"
- numcode: "740"
-countries_219:
- name: Viet Nam
- iso3: VNM
- iso: VN
- iso_name: VIET NAM
- id: "219"
- numcode: "704"
-countries_058:
- name: Egypt
- iso3: EGY
- iso: EG
- iso_name: EGYPT
- id: "58"
- numcode: "818"
-countries_084:
- name: Guinea-Bissau
- iso3: GNB
- iso: GW
- iso_name: GUINEA-BISSAU
- id: "84"
- numcode: "624"
-countries_139:
- name: Namibia
- iso3: NAM
- iso: NA
- iso_name: NAMIBIA
- id: "139"
- numcode: "516"
-countries_165:
- name: Qatar
- iso3: QAT
- iso: QA
- iso_name: QATAR
- id: "165"
- numcode: "634"
-countries_192:
- name: Svalbard and Jan Mayen
- iso3: SJM
- iso: SJ
- iso_name: SVALBARD AND JAN MAYEN
- id: "192"
- numcode: "744"
-countries_059:
- name: El Salvador
- iso3: SLV
- iso: SV
- iso_name: EL SALVADOR
- id: "59"
- numcode: "222"
-countries_085:
- name: Guyana
- iso3: GUY
- iso: GY
- iso_name: GUYANA
- id: "85"
- numcode: "328"
-countries_166:
- name: Reunion
- iso3: REU
- iso: RE
- iso_name: REUNION
- id: "166"
- numcode: "638"
-countries_086:
- name: Haiti
- iso3: HTI
- iso: HT
- iso_name: HAITI
- id: "86"
- numcode: "332"
-countries_167:
- name: Romania
- iso3: ROM
- iso: RO
- iso_name: ROMANIA
- id: "167"
- numcode: "642"
-countries_193:
- name: Swaziland
- iso3: SWZ
- iso: SZ
- iso_name: SWAZILAND
- id: "193"
- numcode: "748"
-countries_087:
- name: Holy See (Vatican City State)
- iso3: VAT
- iso: VA
- iso_name: HOLY SEE (VATICAN CITY STATE)
- id: "87"
- numcode: "336"
-countries_168:
- name: Russian Federation
- iso3: RUS
- iso: RU
- iso_name: RUSSIAN FEDERATION
- id: "168"
- numcode: "643"
-countries_194:
- name: Sweden
- iso3: SWE
- iso: SE
- iso_name: SWEDEN
- id: "194"
- numcode: "752"
-countries_088:
- name: Honduras
- iso3: HND
- iso: HN
- iso_name: HONDURAS
- id: "88"
- numcode: "340"
-countries_169:
- name: Rwanda
- iso3: RWA
- iso: RW
- iso_name: RWANDA
- id: "169"
- numcode: "646"
-countries_195:
- name: Switzerland
- iso3: CHE
- iso: CH
- iso_name: SWITZERLAND
- id: "195"
- numcode: "756"
-countries_089:
- name: Hong Kong
- iso3: HKG
- iso: HK
- iso_name: HONG KONG
- id: "89"
- numcode: "344"
-countries_196:
- name: Syrian Arab Republic
- iso3: SYR
- iso: SY
- iso_name: SYRIAN ARAB REPUBLIC
- id: "196"
- numcode: "760"
-countries_197:
- name: Taiwan
- iso3: TWN
- iso: TW
- iso_name: TAIWAN, PROVINCE OF CHINA
- id: "197"
- numcode: "158"
-countries_198:
- name: Tajikistan
- iso3: TJK
- iso: TJ
- iso_name: TAJIKISTAN
- id: "198"
- numcode: "762"
-countries_199:
- name: Tanzania, United Republic of
- iso3: TZA
- iso: TZ
- iso_name: TANZANIA, UNITED REPUBLIC OF
- id: "199"
- numcode: "834"
-countries_010:
- name: Armenia
- iso3: ARM
- iso: AM
- iso_name: ARMENIA
- id: "10"
- numcode: "51"
-countries_011:
- name: Aruba
- iso3: ABW
- iso: AW
- iso_name: ARUBA
- id: "11"
- numcode: "533"
-countries_012:
- name: Australia
- iso3: AUS
- iso: AU
- iso_name: AUSTRALIA
- id: "12"
- numcode: "36"
-countries_200:
- name: Thailand
- iso3: THA
- iso: TH
- iso_name: THAILAND
- id: "200"
- numcode: "764"
-countries_013:
- name: Austria
- iso3: AUT
- iso: AT
- iso_name: AUSTRIA
- id: "13"
- numcode: "40"
-countries_120:
- name: Madagascar
- iso3: MDG
- iso: MG
- iso_name: MADAGASCAR
- id: "120"
- numcode: "450"
-countries_201:
- name: Togo
- iso3: TGO
- iso: TG
- iso_name: TOGO
- id: "201"
- numcode: "768"
-countries_014:
- name: Azerbaijan
- iso3: AZE
- iso: AZ
- iso_name: AZERBAIJAN
- id: "14"
- numcode: "31"
-countries_040:
- name: Chile
- iso3: CHL
- iso: CL
- iso_name: CHILE
- id: "40"
- numcode: "152"
-countries_121:
- name: Malawi
- iso3: MWI
- iso: MW
- iso_name: MALAWI
- id: "121"
- numcode: "454"
-countries_202:
- name: Tokelau
- iso3: TKL
- iso: TK
- iso_name: TOKELAU
- id: "202"
- numcode: "772"
-countries_015:
- name: Bahamas
- iso3: BHS
- iso: BS
- iso_name: BAHAMAS
- id: "15"
- numcode: "44"
-countries_041:
- name: China
- iso3: CHN
- iso: CN
- iso_name: CHINA
- id: "41"
- numcode: "156"
-countries_122:
- name: Malaysia
- iso3: MYS
- iso: MY
- iso_name: MALAYSIA
- id: "122"
- numcode: "458"
-countries_203:
- name: Tonga
- iso3: TON
- iso: TO
- iso_name: TONGA
- id: "203"
- numcode: "776"
-countries_016:
- name: Bahrain
- iso3: BHR
- iso: BH
- iso_name: BAHRAIN
- id: "16"
- numcode: "48"
-countries_042:
- name: Colombia
- iso3: COL
- iso: CO
- iso_name: COLOMBIA
- id: "42"
- numcode: "170"
-countries_123:
- name: Maldives
- iso3: MDV
- iso: MV
- iso_name: MALDIVES
- id: "123"
- numcode: "462"
-countries_204:
- name: Trinidad and Tobago
- iso3: TTO
- iso: TT
- iso_name: TRINIDAD AND TOBAGO
- id: "204"
- numcode: "780"
-countries_017:
- name: Bangladesh
- iso3: BGD
- iso: BD
- iso_name: BANGLADESH
- id: "17"
- numcode: "50"
-countries_043:
- name: Comoros
- iso3: COM
- iso: KM
- iso_name: COMOROS
- id: "43"
- numcode: "174"
-countries_070:
- name: French Polynesia
- iso3: PYF
- iso: PF
- iso_name: FRENCH POLYNESIA
- id: "70"
- numcode: "258"
-countries_124:
- name: Mali
- iso3: MLI
- iso: ML
- iso_name: MALI
- id: "124"
- numcode: "466"
-countries_150:
- name: Norfolk Island
- iso3: NFK
- iso: NF
- iso_name: NORFOLK ISLAND
- id: "150"
- numcode: "574"
-countries_205:
- name: Tunisia
- iso3: TUN
- iso: TN
- iso_name: TUNISIA
- id: "205"
- numcode: "788"
-countries_018:
- name: Barbados
- iso3: BRB
- iso: BB
- iso_name: BARBADOS
- id: "18"
- numcode: "52"
-countries_044:
- name: Congo
- iso3: COG
- iso: CG
- iso_name: CONGO
- id: "44"
- numcode: "178"
-countries_071:
- name: Gabon
- iso3: GAB
- iso: GA
- iso_name: GABON
- id: "71"
- numcode: "266"
-countries_125:
- name: Malta
- iso3: MLT
- iso: MT
- iso_name: MALTA
- id: "125"
- numcode: "470"
-countries_151:
- name: Northern Mariana Islands
- iso3: MNP
- iso: MP
- iso_name: NORTHERN MARIANA ISLANDS
- id: "151"
- numcode: "580"
-countries_206:
- name: Turkey
- iso3: TUR
- iso: TR
- iso_name: TURKEY
- id: "206"
- numcode: "792"
-countries_045:
- name: Congo, the Democratic Republic of the
- iso3: COD
- iso: CD
- iso_name: CONGO, THE DEMOCRATIC REPUBLIC OF THE
- id: "45"
- numcode: "180"
-countries_126:
- name: Marshall Islands
- iso3: MHL
- iso: MH
- iso_name: MARSHALL ISLANDS
- id: "126"
- numcode: "584"
-countries_152:
- name: Norway
- iso3: NOR
- iso: "NO"
- iso_name: NORWAY
- id: "152"
- numcode: "578"
-countries_207:
- name: Turkmenistan
- iso3: TKM
- iso: TM
- iso_name: TURKMENISTAN
- id: "207"
- numcode: "795"
-countries_019:
- name: Belarus
- iso3: BLR
- iso: BY
- iso_name: BELARUS
- id: "19"
- numcode: "112"
-countries_046:
- name: Cook Islands
- iso3: COK
- iso: CK
- iso_name: COOK ISLANDS
- id: "46"
- numcode: "184"
-countries_072:
- name: Gambia
- iso3: GMB
- iso: GM
- iso_name: GAMBIA
- id: "72"
- numcode: "270"
-countries_127:
- name: Martinique
- iso3: MTQ
- iso: MQ
- iso_name: MARTINIQUE
- id: "127"
- numcode: "474"
-countries_153:
- name: Oman
- iso3: OMN
- iso: OM
- iso_name: OMAN
- id: "153"
- numcode: "512"
-countries_180:
- name: Seychelles
- iso3: SYC
- iso: SC
- iso_name: SEYCHELLES
- id: "180"
- numcode: "690"
-countries_208:
- name: Turks and Caicos Islands
- iso3: TCA
- iso: TC
- iso_name: TURKS AND CAICOS ISLANDS
- id: "208"
- numcode: "796"
-countries_073:
- name: Georgia
- iso3: GEO
- iso: GE
- iso_name: GEORGIA
- id: "73"
- numcode: "268"
-countries_128:
- name: Mauritania
- iso3: MRT
- iso: MR
- iso_name: MAURITANIA
- id: "128"
- numcode: "478"
-countries_154:
- name: Pakistan
- iso3: PAK
- iso: PK
- iso_name: PAKISTAN
- id: "154"
- numcode: "586"
-countries_181:
- name: Sierra Leone
- iso3: SLE
- iso: SL
- iso_name: SIERRA LEONE
- id: "181"
- numcode: "694"
-countries_209:
- name: Tuvalu
- iso3: TUV
- iso: TV
- iso_name: TUVALU
- id: "209"
- numcode: "798"
-countries_047:
- name: Costa Rica
- iso3: CRI
- iso: CR
- iso_name: COSTA RICA
- id: "47"
- numcode: "188"
-countries_074:
- name: Germany
- iso3: DEU
- iso: DE
- iso_name: GERMANY
- id: "74"
- numcode: "276"
-countries_129:
- name: Mauritius
- iso3: MUS
- iso: MU
- iso_name: MAURITIUS
- id: "129"
- numcode: "480"
-countries_155:
- name: Palau
- iso3: PLW
- iso: PW
- iso_name: PALAU
- id: "155"
- numcode: "585"
-countries_048:
- name: Cote D'Ivoire
- iso3: CIV
- iso: CI
- iso_name: COTE D'IVOIRE
- id: "48"
- numcode: "384"
-countries_156:
- name: Panama
- iso3: PAN
- iso: PA
- iso_name: PANAMA
- id: "156"
- numcode: "591"
-countries_182:
- name: Singapore
- iso3: SGP
- iso: SG
- iso_name: SINGAPORE
- id: "182"
- numcode: "702"
-countries_049:
- name: Croatia
- iso3: HRV
- iso: HR
- iso_name: CROATIA
- id: "49"
- numcode: "191"
-countries_075:
- name: Ghana
- iso3: GHA
- iso: GH
- iso_name: GHANA
- id: "75"
- numcode: "288"
-countries_157:
- name: Papua New Guinea
- iso3: PNG
- iso: PG
- iso_name: PAPUA NEW GUINEA
- id: "157"
- numcode: "598"
-countries_183:
- name: Slovakia
- iso3: SVK
- iso: SK
- iso_name: SLOVAKIA
- id: "183"
- numcode: "703"
-countries_076:
- name: Gibraltar
- iso3: GIB
- iso: GI
- iso_name: GIBRALTAR
- id: "76"
- numcode: "292"
-countries_158:
- name: Paraguay
- iso3: PRY
- iso: PY
- iso_name: PARAGUAY
- id: "158"
- numcode: "600"
-countries_184:
- name: Slovenia
- iso3: SVN
- iso: SI
- iso_name: SLOVENIA
- id: "184"
- numcode: "705"
-countries_077:
- name: Greece
- iso3: GRC
- iso: GR
- iso_name: GREECE
- id: "77"
- numcode: "300"
-countries_159:
- name: Peru
- iso3: PER
- iso: PE
- iso_name: PERU
- id: "159"
- numcode: "604"
-countries_185:
- name: Solomon Islands
- iso3: SLB
- iso: SB
- iso_name: SOLOMON ISLANDS
- id: "185"
- numcode: "90"
-countries_078:
- name: Greenland
- iso3: GRL
- iso: GL
- iso_name: GREENLAND
- id: "78"
- numcode: "304"
-countries_186:
- name: Somalia
- iso3: SOM
- iso: SO
- iso_name: SOMALIA
- id: "186"
- numcode: "706"
-countries_079:
- name: Grenada
- iso3: GRD
- iso: GD
- iso_name: GRENADA
- id: "79"
- numcode: "308"
-countries_187:
- name: South Africa
- iso3: ZAF
- iso: ZA
- iso_name: SOUTH AFRICA
- id: "187"
- numcode: "710"
-countries_188:
- name: Spain
- iso3: ESP
- iso: ES
- iso_name: SPAIN
- id: "188"
- numcode: "724"
-countries_189:
- name: Sri Lanka
- iso3: LKA
- iso: LK
- iso_name: SRI LANKA
- id: "189"
- numcode: "144"
-countries_001:
- name: Afghanistan
- iso3: AFG
- iso: AF
- iso_name: AFGHANISTAN
- id: "1"
- numcode: "4"
-countries_002:
- name: Albania
- iso3: ALB
- iso: AL
- iso_name: ALBANIA
- id: "2"
- numcode: "8"
-countries_003:
- name: Algeria
- iso3: DZA
- iso: DZ
- iso_name: ALGERIA
- id: "3"
- numcode: "12"
-countries_110:
- name: Latvia
- iso3: LVA
- iso: LV
- iso_name: LATVIA
- id: "110"
- numcode: "428"
-countries_004:
- name: American Samoa
- iso3: ASM
- iso: AS
- iso_name: AMERICAN SAMOA
- id: "4"
- numcode: "16"
-countries_030:
- name: Bulgaria
- iso3: BGR
- iso: BG
- iso_name: BULGARIA
- id: "30"
- numcode: "100"
-countries_111:
- name: Lebanon
- iso3: LBN
- iso: LB
- iso_name: LEBANON
- id: "111"
- numcode: "422"
-countries_005:
- name: Andorra
- iso3: AND
- iso: AD
- iso_name: ANDORRA
- id: "5"
- numcode: "20"
-countries_031:
- name: Burkina Faso
- iso3: BFA
- iso: BF
- iso_name: BURKINA FASO
- id: "31"
- numcode: "854"
-countries_112:
- name: Lesotho
- iso3: LSO
- iso: LS
- iso_name: LESOTHO
- id: "112"
- numcode: "426"
-countries_006:
- name: Angola
- iso3: AGO
- iso: AO
- iso_name: ANGOLA
- id: "6"
- numcode: "24"
-countries_032:
- name: Burundi
- iso3: BDI
- iso: BI
- iso_name: BURUNDI
- id: "32"
- numcode: "108"
-countries_113:
- name: Liberia
- iso3: LBR
- iso: LR
- iso_name: LIBERIA
- id: "113"
- numcode: "430"
-countries_220:
- name: Virgin Islands, British
- iso3: VGB
- iso: VG
- iso_name: VIRGIN ISLANDS, BRITISH
- id: "220"
- numcode: "92"
-countries_007:
- name: Anguilla
- iso3: AIA
- iso: AI
- iso_name: ANGUILLA
- id: "7"
- numcode: "660"
-countries_033:
- name: Cambodia
- iso3: KHM
- iso: KH
- iso_name: CAMBODIA
- id: "33"
- numcode: "116"
-countries_060:
- name: Equatorial Guinea
- iso3: GNQ
- iso: GQ
- iso_name: EQUATORIAL GUINEA
- id: "60"
- numcode: "226"
-countries_114:
- name: Libyan Arab Jamahiriya
- iso3: LBY
- iso: LY
- iso_name: LIBYAN ARAB JAMAHIRIYA
- id: "114"
- numcode: "434"
-countries_140:
- name: Nauru
- iso3: NRU
- iso: NR
- iso_name: NAURU
- id: "140"
- numcode: "520"
-countries_221:
- name: Virgin Islands, U.S.
- iso3: VIR
- iso: VI
- iso_name: VIRGIN ISLANDS, U.S.
- id: "221"
- numcode: "850"
-countries_008:
- name: Antigua and Barbuda
- iso3: ATG
- iso: AG
- iso_name: ANTIGUA AND BARBUDA
- id: "8"
- numcode: "28"
-countries_034:
- name: Cameroon
- iso3: CMR
- iso: CM
- iso_name: CAMEROON
- id: "34"
- numcode: "120"
-countries_115:
- name: Liechtenstein
- iso3: LIE
- iso: LI
- iso_name: LIECHTENSTEIN
- id: "115"
- numcode: "438"
-countries_141:
- name: Nepal
- iso3: NPL
- iso: NP
- iso_name: NEPAL
- id: "141"
- numcode: "524"
-countries_222:
- name: Wallis and Futuna
- iso3: WLF
- iso: WF
- iso_name: WALLIS AND FUTUNA
- id: "222"
- numcode: "876"
-countries_223:
- name: Western Sahara
- iso3: ESH
- iso: EH
- iso_name: WESTERN SAHARA
- id: "223"
- numcode: "732"
-countries_009:
- name: Argentina
- iso3: ARG
- iso: AR
- iso_name: ARGENTINA
- id: "9"
- numcode: "32"
-countries_035:
- name: Canada
- iso3: CAN
- iso: CA
- iso_name: CANADA
- id: "35"
- numcode: "124"
-countries_061:
- name: Eritrea
- iso3: ERI
- iso: ER
- iso_name: ERITREA
- id: "61"
- numcode: "232"
-countries_116:
- name: Lithuania
- iso3: LTU
- iso: LT
- iso_name: LITHUANIA
- id: "116"
- numcode: "440"
-countries_142:
- name: Netherlands
- iso3: NLD
- iso: NL
- iso_name: NETHERLANDS
- id: "142"
- numcode: "528"
-countries_224:
- name: Yemen
- iso3: YEM
- iso: YE
- iso_name: YEMEN
- id: "224"
- numcode: "887"
-countries_036:
- name: Cape Verde
- iso3: CPV
- iso: CV
- iso_name: CAPE VERDE
- id: "36"
- numcode: "132"
-countries_062:
- name: Estonia
- iso3: EST
- iso: EE
- iso_name: ESTONIA
- id: "62"
- numcode: "233"
-countries_117:
- name: Luxembourg
- iso3: LUX
- iso: LU
- iso_name: LUXEMBOURG
- id: "117"
- numcode: "442"
-countries_143:
- name: Netherlands Antilles
- iso3: ANT
- iso: AN
- iso_name: NETHERLANDS ANTILLES
- id: "143"
- numcode: "530"
-countries_170:
- name: Saint Helena
- iso3: SHN
- iso: SH
- iso_name: SAINT HELENA
- id: "170"
- numcode: "654"
-countries_225:
- name: Zambia
- iso3: ZMB
- iso: ZM
- iso_name: ZAMBIA
- id: "225"
- numcode: "894"
-countries_037:
- name: Cayman Islands
- iso3: CYM
- iso: KY
- iso_name: CAYMAN ISLANDS
- id: "37"
- numcode: "136"
-countries_063:
- name: Ethiopia
- iso3: ETH
- iso: ET
- iso_name: ETHIOPIA
- id: "63"
- numcode: "231"
-countries_090:
- name: Hungary
- iso3: HUN
- iso: HU
- iso_name: HUNGARY
- id: "90"
- numcode: "348"
-countries_118:
- name: Macao
- iso3: MAC
- iso: MO
- iso_name: MACAO
- id: "118"
- numcode: "446"
-countries_144:
- name: New Caledonia
- iso3: NCL
- iso: NC
- iso_name: NEW CALEDONIA
- id: "144"
- numcode: "540"
-countries_226:
- name: Zimbabwe
- iso3: ZWE
- iso: ZW
- iso_name: ZIMBABWE
- id: "226"
- numcode: "716"
-countries_038:
- name: Central African Republic
- iso3: CAF
- iso: CF
- iso_name: CENTRAL AFRICAN REPUBLIC
- id: "38"
- numcode: "140"
-countries_064:
- name: Falkland Islands (Malvinas)
- iso3: FLK
- iso: FK
- iso_name: FALKLAND ISLANDS (MALVINAS)
- id: "64"
- numcode: "238"
-countries_091:
- name: Iceland
- iso3: ISL
- iso: IS
- iso_name: ICELAND
- id: "91"
- numcode: "352"
-countries_119:
- name: Macedonia
- iso3: MKD
- iso: MK
- iso_name: MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF
- id: "119"
- numcode: "807"
-countries_145:
- name: New Zealand
- iso3: NZL
- iso: NZ
- iso_name: NEW ZEALAND
- id: "145"
- numcode: "554"
-countries_171:
- name: Saint Kitts and Nevis
- iso3: KNA
- iso: KN
- iso_name: SAINT KITTS AND NEVIS
- id: "171"
- numcode: "659"
-countries_998:
- name: Serbia
- iso3: SRB
- iso: RS
- id: "998"
- numcode: "999"
\ No newline at end of file
diff --git a/core/db/default/spree/roles.rb b/core/db/default/spree/roles.rb
new file mode 100644
index 00000000000..0b6f77ef4ca
--- /dev/null
+++ b/core/db/default/spree/roles.rb
@@ -0,0 +1,2 @@
+Spree::Role.where(:name => "admin").first_or_create
+Spree::Role.where(:name => "user").first_or_create
diff --git a/core/db/default/spree/roles.yml b/core/db/default/spree/roles.yml
deleted file mode 100644
index b0b3ffc5487..00000000000
--- a/core/db/default/spree/roles.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-admin_role:
- name: admin
-
-user_role:
- name: user
diff --git a/core/db/default/spree/states.rb b/core/db/default/spree/states.rb
new file mode 100644
index 00000000000..27f26a9aef6
--- /dev/null
+++ b/core/db/default/spree/states.rb
@@ -0,0 +1,16 @@
+ActiveRecord::Base.transaction do
+ Spree::Country.all.each do |country|
+ carmen_country = Carmen::Country.named(country.name)
+ @states ||= []
+ if carmen_country.subregions?
+ carmen_country.subregions.each do |subregion|
+ @states << {
+ name: subregion.name,
+ abbr: subregion.code,
+ country: country
+ }
+ end
+ end
+ end
+ Spree::State.create!(@states)
+end
diff --git a/core/db/default/spree/states.yml b/core/db/default/spree/states.yml
deleted file mode 100644
index 02b838d8bcb..00000000000
--- a/core/db/default/spree/states.yml
+++ /dev/null
@@ -1,256 +0,0 @@
----
-states_043:
- name: Michigan
- country_id: "214"
- id: "931624400"
- abbr: MI
-states_032:
- name: South Dakota
- country_id: "214"
- id: "615306087"
- abbr: SD
-states_021:
- name: Washington
- country_id: "214"
- id: "414569975"
- abbr: WA
-states_010:
- name: Wisconsin
- country_id: "214"
- id: "103680699"
- abbr: WI
-states_044:
- name: Arizona
- country_id: "214"
- id: "948208802"
- abbr: AZ
-states_033:
- name: Illinois
- country_id: "214"
- id: "625629523"
- abbr: IL
-states_022:
- name: New Hampshire
- country_id: "214"
- id: "426832442"
- abbr: NH
-states_011:
- name: North Carolina
- country_id: "214"
- id: "177087202"
- abbr: NC
-states_045:
- name: Kansas
- country_id: "214"
- id: "969722173"
- abbr: KS
-states_034:
- name: Missouri
- country_id: "214"
- id: "653576146"
- abbr: MO
-states_023:
- name: Arkansas
- country_id: "214"
- id: "471470972"
- abbr: AR
-states_012:
- name: Nevada
- country_id: "214"
- id: "179539703"
- abbr: NV
-states_001:
- name: District of Columbia
- country_id: "214"
- id: "6764998"
- abbr: DC
-states_046:
- name: Idaho
- country_id: "214"
- id: "982433740"
- abbr: ID
-states_035:
- name: Nebraska
- country_id: "214"
- id: "673350891"
- abbr: NE
-states_024:
- name: Pennsylvania
- country_id: "214"
- id: "471711976"
- abbr: PA
-states_013:
- name: Hawaii
- country_id: "214"
- id: "199950338"
- abbr: HI
-states_002:
- name: Utah
- country_id: "214"
- id: "17199670"
- abbr: UT
-states_047:
- name: Vermont
- country_id: "214"
- id: "989115415"
- abbr: VT
-states_036:
- name: Delaware
- country_id: "214"
- id: "721598219"
- abbr: DE
-states_025:
- name: Rhode Island
- country_id: "214"
- id: "474001862"
- abbr: RI
-states_014:
- name: Oklahoma
- country_id: "214"
- id: "248548169"
- abbr: OK
-states_003:
- name: Louisiana
- country_id: "214"
- id: "37199952"
- abbr: LA
-states_048:
- name: Montana
- country_id: "214"
- id: "999156632"
- abbr: MT
-states_037:
- name: Tennessee
- country_id: "214"
- id: "726305632"
- abbr: TN
-states_026:
- name: Maryland
- country_id: "214"
- id: "480368357"
- abbr: MD
-states_015:
- name: Florida
- country_id: "214"
- id: "267271847"
- abbr: FL
-states_004:
- name: Virginia
- country_id: "214"
- id: "41111624"
- abbr: VA
-states_049:
- name: Minnesota
- country_id: "214"
- id: "1032288924"
- abbr: MN
-states_038:
- name: New Jersey
- country_id: "214"
- id: "750950030"
- abbr: NJ
-states_027:
- name: Ohio
- country_id: "214"
- id: "485193526"
- abbr: OH
-states_016:
- name: California
- country_id: "214"
- id: "276110813"
- abbr: CA
-states_005:
- name: North Dakota
- country_id: "214"
- id: "51943165"
- abbr: ND
-states_050:
- name: Maine
- country_id: "214"
- id: "1055056709"
- abbr: ME
-states_039:
- name: Indiana
- country_id: "214"
- id: "769938586"
- abbr: IN
-states_028:
- name: Texas
- country_id: "214"
- id: "525212995"
- abbr: TX
-states_017:
- name: Oregon
- country_id: "214"
- id: "298914262"
- abbr: OR
-states_006:
- name: Wyoming
- country_id: "214"
- id: "66390489"
- abbr: WY
-states_051:
- name: Alabama
- country_id: "214"
- id: "1061493585"
- abbr: AL
-states_040:
- name: Iowa
- country_id: "214"
- id: "825306985"
- abbr: IA
-states_029:
- name: Mississippi
- country_id: "214"
- id: "532363768"
- abbr: MS
-states_018:
- name: Kentucky
- country_id: "214"
- id: "308473843"
- abbr: KY
-states_007:
- name: New Mexico
- country_id: "214"
- id: "69729944"
- abbr: NM
-states_041:
- name: Georgia
- country_id: "214"
- id: "876916760"
- abbr: GA
-states_030:
- name: Colorado
- country_id: "214"
- id: "536031023"
- abbr: CO
-states_019:
- name: Massachusetts
- country_id: "214"
- id: "385551075"
- abbr: MA
-states_008:
- name: Connecticut
- country_id: "214"
- id: "69870734"
- abbr: CT
-states_042:
- name: New York
- country_id: "214"
- id: "889445952"
- abbr: NY
-states_031:
- name: South Carolina
- country_id: "214"
- id: "597434151"
- abbr: SC
-states_020:
- name: Alaska
- country_id: "214"
- id: "403740659"
- abbr: AK
-states_009:
- name: West Virginia
- country_id: "214"
- id: "91367981"
- abbr: WV
diff --git a/core/db/default/spree/stores.rb b/core/db/default/spree/stores.rb
new file mode 100644
index 00000000000..8500e212999
--- /dev/null
+++ b/core/db/default/spree/stores.rb
@@ -0,0 +1,9 @@
+# Possibly already created by a migration.
+unless Spree::Store.where(code: 'spree').exists?
+ Spree::Store.new do |s|
+ s.code = 'spree'
+ s.name = 'Spree Demo Site'
+ s.url = 'demo.spreecommerce.com'
+ s.mail_from_address = 'spree@example.com'
+ end.save!
+end
\ No newline at end of file
diff --git a/core/db/default/spree/zone_members.yml b/core/db/default/spree/zone_members.yml
deleted file mode 100644
index a184460be39..00000000000
--- a/core/db/default/spree/zone_members.yml
+++ /dev/null
@@ -1,169 +0,0 @@
----
-zone_members_019:
- zoneable_id: "162"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_008:
- zoneable_id: "67"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_020:
- zoneable_id: "163"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_021:
- zoneable_id: "167"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_010:
- zoneable_id: "74"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_009:
- zoneable_id: "68"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_022:
- zoneable_id: "183"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_011:
- zoneable_id: "90"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_023:
- zoneable_id: "184"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_012:
- zoneable_id: "96"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_001:
- zoneable_id: "13"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_024:
- zoneable_id: "188"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_013:
- zoneable_id: "98"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_002:
- zoneable_id: "20"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_025:
- zoneable_id: "194"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_014:
- zoneable_id: "110"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_003:
- zoneable_id: "30"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_026:
- zoneable_id: "213"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_015:
- zoneable_id: "116"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_004:
- zoneable_id: "51"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_016:
- zoneable_id: "117"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_005:
- zoneable_id: "52"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_028:
- zoneable_id: "214"
- created_at: 2009-06-04 17:22:41
- updated_at: 2009-06-04 17:22:41
- zone_id: "2"
- zoneable_type: Spree::Country
-zone_members_017:
- zoneable_id: "125"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_006:
- zoneable_id: "53"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_029:
- zoneable_id: "35"
- created_at: 2009-06-04 17:22:41
- updated_at: 2009-06-04 17:22:41
- zone_id: "2"
- zoneable_type: Spree::Country
-zone_members_018:
- zoneable_id: "142"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
-zone_members_007:
- zoneable_id: "62"
- created_at: 2009-06-04 13:22:26
- updated_at: 2009-06-04 13:22:26
- zone_id: "1"
- zoneable_type: Spree::Country
diff --git a/core/db/default/spree/zones.rb b/core/db/default/spree/zones.rb
new file mode 100644
index 00000000000..52f0c2efacd
--- /dev/null
+++ b/core/db/default/spree/zones.rb
@@ -0,0 +1,17 @@
+eu_vat = Spree::Zone.create!(name: "EU_VAT", description: "Countries that make up the EU VAT zone.")
+north_america = Spree::Zone.create!(name: "North America", description: "USA + Canada")
+
+["Poland", "Finland", "Portugal", "Romania", "Germany", "France",
+ "Slovakia", "Hungary", "Slovenia", "Ireland", "Austria", "Spain",
+ "Italy", "Belgium", "Sweden", "Latvia", "Bulgaria", "United Kingdom",
+ "Lithuania", "Cyprus", "Luxembourg", "Malta", "Denmark", "Netherlands",
+ "Estonia"].
+each do |name|
+ eu_vat.zone_members.create!(zoneable: Spree::Country.find_by!(name: name))
+end
+
+["United States", "Canada"].each do |name|
+ north_america.zone_members.create!(zoneable: Spree::Country.find_by!(name: name))
+end
+
+
diff --git a/core/db/default/spree/zones.yml b/core/db/default/spree/zones.yml
deleted file mode 100644
index ed771d96351..00000000000
--- a/core/db/default/spree/zones.yml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-zones_001:
- name: EU_VAT
- created_at: 2009-06-04 17:22:26
- updated_at: 2009-06-04 17:22:26
- id: "1"
- description: Countries that make up the EU VAT zone.
-zones_002:
- name: North America
- created_at: 2009-06-04 17:22:41
- updated_at: 2009-06-04 17:22:41
- id: "2"
- description: USA + Canada
diff --git a/promo/db/migrate/20120831092359_spree_promo_one_two.rb b/core/db/migrate/20120831092359_spree_promo_one_two.rb
similarity index 100%
rename from promo/db/migrate/20120831092359_spree_promo_one_two.rb
rename to core/db/migrate/20120831092359_spree_promo_one_two.rb
diff --git a/core/db/migrate/20121031162139_split_prices_from_variants.rb b/core/db/migrate/20121031162139_split_prices_from_variants.rb
index 538c2c683a8..27aa83fa8e5 100644
--- a/core/db/migrate/20121031162139_split_prices_from_variants.rb
+++ b/core/db/migrate/20121031162139_split_prices_from_variants.rb
@@ -9,7 +9,7 @@ def up
Spree::Variant.all.each do |variant|
Spree::Price.create!(
:variant_id => variant.id,
- :amount => variant.price,
+ :amount => variant[:price],
:currency => Spree::Config[:currency]
)
end
@@ -18,13 +18,14 @@ def up
end
def down
- add_column :spree_variants, :price, :decimal, :after => :sku, :scale => 8, :precision => 2
+ prices = ActiveRecord::Base.connection.execute("select variant_id, amount from spree_prices")
+ add_column :spree_variants, :price, :decimal, :after => :sku, :scale => 2, :precision => 8
- Spree::Variant.all.each do |variant|
- variant.price = variant.default_price.amount
- variant.save!
+ prices.each do |price|
+ ActiveRecord::Base.connection.execute("update spree_variants set price = #{price['amount']} where id = #{price['variant_id']}")
end
-
+
+ change_column :spree_variants, :price, :decimal, :after => :sku, :scale => 2, :precision => 8, :null => false
drop_table :spree_prices
end
end
diff --git a/core/db/migrate/20121126040517_add_last_ip_to_spree_orders.rb b/core/db/migrate/20121126040517_add_last_ip_to_spree_orders.rb
new file mode 100644
index 00000000000..662f231069d
--- /dev/null
+++ b/core/db/migrate/20121126040517_add_last_ip_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddLastIpToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :last_ip_address, :string
+ end
+end
diff --git a/core/db/migrate/20121213162028_add_state_to_spree_adjustments.rb b/core/db/migrate/20121213162028_add_state_to_spree_adjustments.rb
new file mode 100644
index 00000000000..f06a77c7414
--- /dev/null
+++ b/core/db/migrate/20121213162028_add_state_to_spree_adjustments.rb
@@ -0,0 +1,6 @@
+class AddStateToSpreeAdjustments < ActiveRecord::Migration
+ def change
+ add_column :spree_adjustments, :state, :string
+ remove_column :spree_adjustments, :locked
+ end
+end
diff --git a/core/db/migrate/20130114053446_add_display_on_to_spree_payment_methods.rb b/core/db/migrate/20130114053446_add_display_on_to_spree_payment_methods.rb
new file mode 100644
index 00000000000..f89d725fbf8
--- /dev/null
+++ b/core/db/migrate/20130114053446_add_display_on_to_spree_payment_methods.rb
@@ -0,0 +1,9 @@
+class AddDisplayOnToSpreePaymentMethods < ActiveRecord::Migration
+ def self.up
+ add_column :spree_payment_methods, :display_on, :string
+ end
+
+ def self.down
+ remove_column :spree_payment_methods, :display_on
+ end
+end
diff --git a/core/db/migrate/20130120201805_add_position_to_product_properties.spree.rb b/core/db/migrate/20130120201805_add_position_to_product_properties.spree.rb
new file mode 100644
index 00000000000..05bc1270190
--- /dev/null
+++ b/core/db/migrate/20130120201805_add_position_to_product_properties.spree.rb
@@ -0,0 +1,6 @@
+class AddPositionToProductProperties < ActiveRecord::Migration
+ def change
+ add_column :spree_product_properties, :position, :integer, :default => 0
+ end
+end
+
diff --git a/core/db/migrate/20130203232234_add_identifier_to_spree_payments.rb b/core/db/migrate/20130203232234_add_identifier_to_spree_payments.rb
new file mode 100644
index 00000000000..224043ed0fa
--- /dev/null
+++ b/core/db/migrate/20130203232234_add_identifier_to_spree_payments.rb
@@ -0,0 +1,5 @@
+class AddIdentifierToSpreePayments < ActiveRecord::Migration
+ def change
+ add_column :spree_payments, :identifier, :string
+ end
+end
diff --git a/core/db/migrate/20130207155350_add_order_id_index_to_payments.rb b/core/db/migrate/20130207155350_add_order_id_index_to_payments.rb
new file mode 100644
index 00000000000..a77f572b024
--- /dev/null
+++ b/core/db/migrate/20130207155350_add_order_id_index_to_payments.rb
@@ -0,0 +1,9 @@
+class AddOrderIdIndexToPayments < ActiveRecord::Migration
+ def self.up
+ add_index :spree_payments, :order_id
+ end
+
+ def self.down
+ remove_index :spree_payments, :order_id
+ end
+end
diff --git a/core/db/migrate/20130208032954_add_primary_to_spree_products_taxons.rb b/core/db/migrate/20130208032954_add_primary_to_spree_products_taxons.rb
new file mode 100644
index 00000000000..016e1d1553c
--- /dev/null
+++ b/core/db/migrate/20130208032954_add_primary_to_spree_products_taxons.rb
@@ -0,0 +1,5 @@
+class AddPrimaryToSpreeProductsTaxons < ActiveRecord::Migration
+ def change
+ add_column :spree_products_taxons, :id, :primary_key
+ end
+end
diff --git a/core/db/migrate/20130211190146_create_spree_stock_items.rb b/core/db/migrate/20130211190146_create_spree_stock_items.rb
new file mode 100644
index 00000000000..c0f4c5e7a5e
--- /dev/null
+++ b/core/db/migrate/20130211190146_create_spree_stock_items.rb
@@ -0,0 +1,14 @@
+class CreateSpreeStockItems < ActiveRecord::Migration
+ def change
+ create_table :spree_stock_items do |t|
+ t.belongs_to :stock_location
+ t.belongs_to :variant
+ t.integer :count_on_hand, null: false, default: 0
+ t.integer :lock_version
+
+ t.timestamps
+ end
+ add_index :spree_stock_items, :stock_location_id
+ add_index :spree_stock_items, [:stock_location_id, :variant_id], :name => 'stock_item_by_loc_and_var_id'
+ end
+end
diff --git a/core/db/migrate/20130211191120_create_spree_stock_locations.rb b/core/db/migrate/20130211191120_create_spree_stock_locations.rb
new file mode 100644
index 00000000000..345e5350751
--- /dev/null
+++ b/core/db/migrate/20130211191120_create_spree_stock_locations.rb
@@ -0,0 +1,11 @@
+class CreateSpreeStockLocations < ActiveRecord::Migration
+ def change
+ create_table :spree_stock_locations do |t|
+ t.string :name
+ t.belongs_to :address
+
+ t.timestamps
+ end
+ add_index :spree_stock_locations, :address_id
+ end
+end
diff --git a/core/db/migrate/20130213191427_create_default_stock.rb b/core/db/migrate/20130213191427_create_default_stock.rb
new file mode 100644
index 00000000000..8369edcca62
--- /dev/null
+++ b/core/db/migrate/20130213191427_create_default_stock.rb
@@ -0,0 +1,34 @@
+class CreateDefaultStock < ActiveRecord::Migration
+ def up
+ unless column_exists? :spree_stock_locations, :default
+ add_column :spree_stock_locations, :default, :boolean, null: false, default: false
+ end
+
+ Spree::StockLocation.skip_callback(:create, :after, :create_stock_items)
+ Spree::StockLocation.skip_callback(:save, :after, :ensure_one_default)
+ Spree::StockItem.skip_callback(:save, :after, :process_backorders)
+ location = Spree::StockLocation.new(name: 'default')
+ location.save(validate: false)
+
+ Spree::Variant.find_each do |variant|
+ stock_item = Spree::StockItem.unscoped.build(stock_location: location, variant: variant)
+ stock_item.send(:count_on_hand=, variant.count_on_hand)
+ # Avoid running default_scope defined by acts_as_paranoid, related to #3805,
+ # validations would run a query with a delete_at column that might not be present yet
+ stock_item.save! validate: false
+ end
+
+ remove_column :spree_variants, :count_on_hand
+ end
+
+ def down
+ add_column :spree_variants, :count_on_hand, :integer
+
+ Spree::StockItem.find_each do |stock_item|
+ stock_item.variant.update_column :count_on_hand, stock_item.count_on_hand
+ end
+
+ Spree::StockLocation.delete_all
+ Spree::StockItem.delete_all
+ end
+end
diff --git a/core/db/migrate/20130222032153_add_order_id_index_to_shipments.rb b/core/db/migrate/20130222032153_add_order_id_index_to_shipments.rb
new file mode 100644
index 00000000000..d7788d3313e
--- /dev/null
+++ b/core/db/migrate/20130222032153_add_order_id_index_to_shipments.rb
@@ -0,0 +1,5 @@
+class AddOrderIdIndexToShipments < ActiveRecord::Migration
+ def change
+ add_index :spree_shipments, :order_id
+ end
+end
diff --git a/core/db/migrate/20130226032817_change_meta_description_on_spree_products_to_text.rb b/core/db/migrate/20130226032817_change_meta_description_on_spree_products_to_text.rb
new file mode 100644
index 00000000000..a362deb32ff
--- /dev/null
+++ b/core/db/migrate/20130226032817_change_meta_description_on_spree_products_to_text.rb
@@ -0,0 +1,5 @@
+class ChangeMetaDescriptionOnSpreeProductsToText < ActiveRecord::Migration
+ def change
+ change_column :spree_products, :meta_description, :text, :limit => nil
+ end
+end
diff --git a/core/db/migrate/20130226191231_add_stock_location_id_to_spree_shipments.rb b/core/db/migrate/20130226191231_add_stock_location_id_to_spree_shipments.rb
new file mode 100644
index 00000000000..e744775ed7a
--- /dev/null
+++ b/core/db/migrate/20130226191231_add_stock_location_id_to_spree_shipments.rb
@@ -0,0 +1,5 @@
+class AddStockLocationIdToSpreeShipments < ActiveRecord::Migration
+ def change
+ add_column :spree_shipments, :stock_location_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130227143905_add_pending_to_inventory_unit.rb b/core/db/migrate/20130227143905_add_pending_to_inventory_unit.rb
new file mode 100644
index 00000000000..ea17f507a4a
--- /dev/null
+++ b/core/db/migrate/20130227143905_add_pending_to_inventory_unit.rb
@@ -0,0 +1,6 @@
+class AddPendingToInventoryUnit < ActiveRecord::Migration
+ def change
+ add_column :spree_inventory_units, :pending, :boolean, :default => true
+ Spree::InventoryUnit.update_all(:pending => false)
+ end
+end
diff --git a/core/db/migrate/20130228164411_remove_on_demand_from_product_and_variant.rb b/core/db/migrate/20130228164411_remove_on_demand_from_product_and_variant.rb
new file mode 100644
index 00000000000..a3d1300b22e
--- /dev/null
+++ b/core/db/migrate/20130228164411_remove_on_demand_from_product_and_variant.rb
@@ -0,0 +1,6 @@
+class RemoveOnDemandFromProductAndVariant < ActiveRecord::Migration
+ def change
+ remove_column :spree_products, :on_demand
+ remove_column :spree_variants, :on_demand
+ end
+end
diff --git a/core/db/migrate/20130228210442_create_shipping_method_zone.rb b/core/db/migrate/20130228210442_create_shipping_method_zone.rb
new file mode 100644
index 00000000000..7f20bd4718b
--- /dev/null
+++ b/core/db/migrate/20130228210442_create_shipping_method_zone.rb
@@ -0,0 +1,22 @@
+class CreateShippingMethodZone < ActiveRecord::Migration
+ def up
+ create_table :shipping_methods_zones, :id => false do |t|
+ t.integer :shipping_method_id
+ t.integer :zone_id
+ end
+ # This association has been corrected in a latter migration
+ # but when this database migration runs, the table is still incorrectly named
+ # 'shipping_methods_zones' instead of 'spre_shipping_methods_zones'
+ Spree::ShippingMethod.has_and_belongs_to_many :zones, :join_table => 'shipping_methods_zones',
+ :class_name => 'Spree::Zone',
+ :foreign_key => 'shipping_method_id'
+ Spree::ShippingMethod.all.each{|sm| sm.zones << Spree::Zone.find(sm.zone_id)}
+
+ remove_column :spree_shipping_methods, :zone_id
+ end
+
+ def down
+ drop_table :shipping_methods_zones
+ add_column :spree_shipping_methods, :zone_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130301162745_remove_shipping_category_id_from_shipping_method.rb b/core/db/migrate/20130301162745_remove_shipping_category_id_from_shipping_method.rb
new file mode 100644
index 00000000000..3b0e8195944
--- /dev/null
+++ b/core/db/migrate/20130301162745_remove_shipping_category_id_from_shipping_method.rb
@@ -0,0 +1,5 @@
+class RemoveShippingCategoryIdFromShippingMethod < ActiveRecord::Migration
+ def change
+ remove_column :spree_shipping_methods, :shipping_category_id
+ end
+end
diff --git a/core/db/migrate/20130301162924_create_shipping_method_categories.rb b/core/db/migrate/20130301162924_create_shipping_method_categories.rb
new file mode 100644
index 00000000000..72b70b8ceea
--- /dev/null
+++ b/core/db/migrate/20130301162924_create_shipping_method_categories.rb
@@ -0,0 +1,13 @@
+class CreateShippingMethodCategories < ActiveRecord::Migration
+ def change
+ create_table :spree_shipping_method_categories do |t|
+ t.integer :shipping_method_id, :null => false
+ t.integer :shipping_category_id, :null => false
+
+ t.timestamps
+ end
+
+ add_index :spree_shipping_method_categories, :shipping_method_id
+ add_index :spree_shipping_method_categories, :shipping_category_id
+ end
+end
diff --git a/core/db/migrate/20130301205200_add_tracking_url_to_spree_shipping_methods.rb b/core/db/migrate/20130301205200_add_tracking_url_to_spree_shipping_methods.rb
new file mode 100644
index 00000000000..1206e383656
--- /dev/null
+++ b/core/db/migrate/20130301205200_add_tracking_url_to_spree_shipping_methods.rb
@@ -0,0 +1,5 @@
+class AddTrackingUrlToSpreeShippingMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_methods, :tracking_url, :string
+ end
+end
diff --git a/core/db/migrate/20130304162240_create_spree_shipping_rates.rb b/core/db/migrate/20130304162240_create_spree_shipping_rates.rb
new file mode 100644
index 00000000000..34aa0d7ac6d
--- /dev/null
+++ b/core/db/migrate/20130304162240_create_spree_shipping_rates.rb
@@ -0,0 +1,24 @@
+class CreateSpreeShippingRates < ActiveRecord::Migration
+ def up
+ create_table :spree_shipping_rates do |t|
+ t.belongs_to :shipment
+ t.belongs_to :shipping_method
+ t.boolean :selected, :default => false
+ t.decimal :cost, :precision => 8, :scale => 2
+ t.timestamps
+ end
+ add_index(:spree_shipping_rates, [:shipment_id, :shipping_method_id],
+ :name => 'spree_shipping_rates_join_index',
+ :unique => true)
+
+ # Spree::Shipment.all.each do |shipment|
+ # shipping_method = Spree::ShippingMethod.find(shipment.shipment_method_id)
+ # shipment.add_shipping_method(shipping_method, true)
+ # end
+ end
+
+ def down
+ # add_column :spree_shipments, :shipping_method_id, :integer
+ drop_table :spree_shipping_rates
+ end
+end
diff --git a/core/db/migrate/20130304192936_remove_category_match_attributes_from_shipping_method.rb b/core/db/migrate/20130304192936_remove_category_match_attributes_from_shipping_method.rb
new file mode 100644
index 00000000000..3950ddc726a
--- /dev/null
+++ b/core/db/migrate/20130304192936_remove_category_match_attributes_from_shipping_method.rb
@@ -0,0 +1,7 @@
+class RemoveCategoryMatchAttributesFromShippingMethod < ActiveRecord::Migration
+ def change
+ remove_column :spree_shipping_methods, :match_none
+ remove_column :spree_shipping_methods, :match_one
+ remove_column :spree_shipping_methods, :match_all
+ end
+end
diff --git a/core/db/migrate/20130305143310_create_stock_movements.rb b/core/db/migrate/20130305143310_create_stock_movements.rb
new file mode 100644
index 00000000000..32df4cdf28f
--- /dev/null
+++ b/core/db/migrate/20130305143310_create_stock_movements.rb
@@ -0,0 +1,12 @@
+class CreateStockMovements < ActiveRecord::Migration
+ def change
+ create_table :spree_stock_movements do |t|
+ t.belongs_to :stock_item
+ t.integer :quantity
+ t.string :action
+
+ t.timestamps
+ end
+ add_index :spree_stock_movements, :stock_item_id
+ end
+end
diff --git a/core/db/migrate/20130306181701_add_address_fields_to_stock_location.rb b/core/db/migrate/20130306181701_add_address_fields_to_stock_location.rb
new file mode 100644
index 00000000000..68974817a46
--- /dev/null
+++ b/core/db/migrate/20130306181701_add_address_fields_to_stock_location.rb
@@ -0,0 +1,22 @@
+class AddAddressFieldsToStockLocation < ActiveRecord::Migration
+ def change
+ remove_column :spree_stock_locations, :address_id
+
+ add_column :spree_stock_locations, :address1, :string
+ add_column :spree_stock_locations, :address2, :string
+ add_column :spree_stock_locations, :city, :string
+ add_column :spree_stock_locations, :state_id, :integer
+ add_column :spree_stock_locations, :state_name, :string
+ add_column :spree_stock_locations, :country_id, :integer
+ add_column :spree_stock_locations, :zipcode, :string
+ add_column :spree_stock_locations, :phone, :string
+
+
+ usa = Spree::Country.where(:iso => 'US').first
+ # In case USA isn't found.
+ # See #3115
+ country = usa || Spree::Country.first
+ Spree::Country.reset_column_information
+ Spree::StockLocation.update_all(:country_id => country)
+ end
+end
diff --git a/core/db/migrate/20130306191917_add_active_field_to_stock_locations.rb b/core/db/migrate/20130306191917_add_active_field_to_stock_locations.rb
new file mode 100644
index 00000000000..a5dbd91113b
--- /dev/null
+++ b/core/db/migrate/20130306191917_add_active_field_to_stock_locations.rb
@@ -0,0 +1,5 @@
+class AddActiveFieldToStockLocations < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_locations, :active, :boolean, :default => true
+ end
+end
diff --git a/core/db/migrate/20130306195650_add_backorderable_to_stock_item.rb b/core/db/migrate/20130306195650_add_backorderable_to_stock_item.rb
new file mode 100644
index 00000000000..5465ec80254
--- /dev/null
+++ b/core/db/migrate/20130306195650_add_backorderable_to_stock_item.rb
@@ -0,0 +1,5 @@
+class AddBackorderableToStockItem < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_items, :backorderable, :boolean, :default => true
+ end
+end
diff --git a/core/db/migrate/20130307161754_add_default_quantity_to_stock_movement.rb b/core/db/migrate/20130307161754_add_default_quantity_to_stock_movement.rb
new file mode 100644
index 00000000000..0c6c73ad00a
--- /dev/null
+++ b/core/db/migrate/20130307161754_add_default_quantity_to_stock_movement.rb
@@ -0,0 +1,5 @@
+class AddDefaultQuantityToStockMovement < ActiveRecord::Migration
+ def change
+ change_column :spree_stock_movements, :quantity, :integer, :default => 0
+ end
+end
diff --git a/core/db/migrate/20130318151756_add_source_and_destination_to_stock_movements.rb b/core/db/migrate/20130318151756_add_source_and_destination_to_stock_movements.rb
new file mode 100644
index 00000000000..49bc67b550c
--- /dev/null
+++ b/core/db/migrate/20130318151756_add_source_and_destination_to_stock_movements.rb
@@ -0,0 +1,8 @@
+class AddSourceAndDestinationToStockMovements < ActiveRecord::Migration
+ def change
+ change_table :spree_stock_movements do |t|
+ t.references :source, polymorphic: true
+ t.references :destination, polymorphic: true
+ end
+ end
+end
diff --git a/core/db/migrate/20130319062004_change_orders_total_precision.rb b/core/db/migrate/20130319062004_change_orders_total_precision.rb
new file mode 100644
index 00000000000..b98b7b1d1b5
--- /dev/null
+++ b/core/db/migrate/20130319062004_change_orders_total_precision.rb
@@ -0,0 +1,8 @@
+class ChangeOrdersTotalPrecision < ActiveRecord::Migration
+ def change
+ change_column :spree_orders, :item_total, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+ change_column :spree_orders, :total, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+ change_column :spree_orders, :adjustment_total, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+ change_column :spree_orders, :payment_total, :decimal, :precision => 10, :scale => 2, :default => 0.0
+ end
+end
diff --git a/core/db/migrate/20130319063911_change_spree_payments_amount_precision.rb b/core/db/migrate/20130319063911_change_spree_payments_amount_precision.rb
new file mode 100644
index 00000000000..a643c59d0fb
--- /dev/null
+++ b/core/db/migrate/20130319063911_change_spree_payments_amount_precision.rb
@@ -0,0 +1,7 @@
+class ChangeSpreePaymentsAmountPrecision < ActiveRecord::Migration
+ def change
+
+ change_column :spree_payments, :amount, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+
+ end
+end
diff --git a/core/db/migrate/20130319064308_change_spree_return_authorization_amount_precision.rb b/core/db/migrate/20130319064308_change_spree_return_authorization_amount_precision.rb
new file mode 100644
index 00000000000..450640b4feb
--- /dev/null
+++ b/core/db/migrate/20130319064308_change_spree_return_authorization_amount_precision.rb
@@ -0,0 +1,7 @@
+class ChangeSpreeReturnAuthorizationAmountPrecision < ActiveRecord::Migration
+ def change
+
+ change_column :spree_return_authorizations, :amount, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+
+ end
+end
diff --git a/core/db/migrate/20130319082943_change_adjustments_amount_precision.rb b/core/db/migrate/20130319082943_change_adjustments_amount_precision.rb
new file mode 100644
index 00000000000..534a9720715
--- /dev/null
+++ b/core/db/migrate/20130319082943_change_adjustments_amount_precision.rb
@@ -0,0 +1,7 @@
+class ChangeAdjustmentsAmountPrecision < ActiveRecord::Migration
+ def change
+
+ change_column :spree_adjustments, :amount, :decimal, :precision => 10, :scale => 2
+
+ end
+end
diff --git a/core/db/migrate/20130319183250_add_originator_to_stock_movement.rb b/core/db/migrate/20130319183250_add_originator_to_stock_movement.rb
new file mode 100644
index 00000000000..c5b6798dc5d
--- /dev/null
+++ b/core/db/migrate/20130319183250_add_originator_to_stock_movement.rb
@@ -0,0 +1,7 @@
+class AddOriginatorToStockMovement < ActiveRecord::Migration
+ def change
+ change_table :spree_stock_movements do |t|
+ t.references :originator, polymorphic: true
+ end
+ end
+end
diff --git a/core/db/migrate/20130319190507_drop_source_and_destination_from_stock_movement.rb b/core/db/migrate/20130319190507_drop_source_and_destination_from_stock_movement.rb
new file mode 100644
index 00000000000..3b316e32e59
--- /dev/null
+++ b/core/db/migrate/20130319190507_drop_source_and_destination_from_stock_movement.rb
@@ -0,0 +1,15 @@
+class DropSourceAndDestinationFromStockMovement < ActiveRecord::Migration
+ def up
+ change_table :spree_stock_movements do |t|
+ t.remove_references :source, :polymorphic => true
+ t.remove_references :destination, :polymorphic => true
+ end
+ end
+
+ def down
+ change_table :spree_stock_movements do |t|
+ t.references :source, polymorphic: true
+ t.references :destination, polymorphic: true
+ end
+ end
+end
diff --git a/core/db/migrate/20130325163316_migrate_inventory_unit_sold_to_on_hand.rb b/core/db/migrate/20130325163316_migrate_inventory_unit_sold_to_on_hand.rb
new file mode 100644
index 00000000000..6325ca0134b
--- /dev/null
+++ b/core/db/migrate/20130325163316_migrate_inventory_unit_sold_to_on_hand.rb
@@ -0,0 +1,9 @@
+class MigrateInventoryUnitSoldToOnHand < ActiveRecord::Migration
+ def up
+ Spree::InventoryUnit.where(:state => 'sold').update_all(:state => 'on_hand')
+ end
+
+ def down
+ Spree::InventoryUnit.where(:state => 'on_hand').update_all(:state => 'sold')
+ end
+end
diff --git a/core/db/migrate/20130326175857_add_stock_location_to_rma.rb b/core/db/migrate/20130326175857_add_stock_location_to_rma.rb
new file mode 100644
index 00000000000..700b354e506
--- /dev/null
+++ b/core/db/migrate/20130326175857_add_stock_location_to_rma.rb
@@ -0,0 +1,5 @@
+class AddStockLocationToRma < ActiveRecord::Migration
+ def change
+ add_column :spree_return_authorizations, :stock_location_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130328130308_update_shipment_state_for_canceled_orders.rb b/core/db/migrate/20130328130308_update_shipment_state_for_canceled_orders.rb
new file mode 100644
index 00000000000..282fc619542
--- /dev/null
+++ b/core/db/migrate/20130328130308_update_shipment_state_for_canceled_orders.rb
@@ -0,0 +1,15 @@
+class UpdateShipmentStateForCanceledOrders < ActiveRecord::Migration
+ def up
+ shipments = Spree::Shipment.joins(:order).
+ where("spree_orders.state = 'canceled'")
+ case Spree::Shipment.connection.adapter_name
+ when "SQLite3"
+ shipments.update_all("state = 'cancelled'")
+ when "MySQL" || "PostgreSQL"
+ shipments.update_all("spree_shipments.state = 'cancelled'")
+ end
+ end
+
+ def down
+ end
+end
diff --git a/core/db/migrate/20130328195253_add_seo_metas_to_taxons.rb b/core/db/migrate/20130328195253_add_seo_metas_to_taxons.rb
new file mode 100644
index 00000000000..fa27170d4a7
--- /dev/null
+++ b/core/db/migrate/20130328195253_add_seo_metas_to_taxons.rb
@@ -0,0 +1,9 @@
+class AddSeoMetasToTaxons < ActiveRecord::Migration
+ def change
+ change_table :spree_taxons do |t|
+ t.string :meta_title
+ t.string :meta_description
+ t.string :meta_keywords
+ end
+ end
+end
diff --git a/core/db/migrate/20130329134939_remove_stock_item_and_variant_lock.rb b/core/db/migrate/20130329134939_remove_stock_item_and_variant_lock.rb
new file mode 100644
index 00000000000..932e9f7c4d9
--- /dev/null
+++ b/core/db/migrate/20130329134939_remove_stock_item_and_variant_lock.rb
@@ -0,0 +1,14 @@
+class RemoveStockItemAndVariantLock < ActiveRecord::Migration
+ def up
+ # we are moving to pessimistic locking on stock_items
+ remove_column :spree_stock_items, :lock_version
+
+ # variants no longer manage their count_on_hand so we are removing their lock
+ remove_column :spree_variants, :lock_version
+ end
+
+ def down
+ add_column :spree_stock_items, :lock_version, :integer
+ add_column :spree_variants, :lock_version, :integer
+ end
+end
diff --git a/core/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb b/core/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb
new file mode 100644
index 00000000000..79fe404aa79
--- /dev/null
+++ b/core/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb
@@ -0,0 +1,5 @@
+class AddNameToSpreeCreditCards < ActiveRecord::Migration
+ def change
+ add_column :spree_credit_cards, :name, :string
+ end
+end
diff --git a/core/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb b/core/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb
new file mode 100644
index 00000000000..18f6596f5de
--- /dev/null
+++ b/core/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb
@@ -0,0 +1,13 @@
+class UpdateNameFieldsOnSpreeCreditCards < ActiveRecord::Migration
+ def up
+ if ActiveRecord::Base.connection.adapter_name.downcase.include? "mysql"
+ execute "UPDATE spree_credit_cards SET name = CONCAT(first_name, ' ', last_name)"
+ else
+ execute "UPDATE spree_credit_cards SET name = first_name || ' ' || last_name"
+ end
+ end
+
+ def down
+ execute "UPDATE spree_credit_cards SET name = NULL"
+ end
+end
diff --git a/core/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb b/core/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb
new file mode 100644
index 00000000000..2b9dadd0be9
--- /dev/null
+++ b/core/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb
@@ -0,0 +1,5 @@
+class AddIndexToSourceColumnsOnAdjustments < ActiveRecord::Migration
+ def change
+ add_index :spree_adjustments, [:source_type, :source_id]
+ end
+end
diff --git a/core/db/migrate/20130417120035_update_adjustment_states.rb b/core/db/migrate/20130417120035_update_adjustment_states.rb
new file mode 100644
index 00000000000..2dae59d352c
--- /dev/null
+++ b/core/db/migrate/20130417120035_update_adjustment_states.rb
@@ -0,0 +1,16 @@
+class UpdateAdjustmentStates < ActiveRecord::Migration
+ def up
+ Spree::Order.complete.find_each do |order|
+ order.adjustments.update_all(:state => 'closed')
+ end
+
+ Spree::Shipment.shipped.includes(:adjustment).find_each do |shipment|
+ shipment.adjustment.update_column(:state, 'finalized') if shipment.adjustment
+ end
+
+ Spree::Adjustment.where(:state => nil).update_all(:state => 'open')
+ end
+
+ def down
+ end
+end
diff --git a/core/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb b/core/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb
new file mode 100644
index 00000000000..8e1c187a58f
--- /dev/null
+++ b/core/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb
@@ -0,0 +1,15 @@
+class AddShippingRatesToShipments < ActiveRecord::Migration
+ def up
+ Spree::Shipment.find_each do |shipment|
+ shipment.shipping_rates.create(:shipping_method_id => shipment.shipping_method_id,
+ :cost => shipment.cost,
+ :selected => true)
+ end
+
+ remove_column :spree_shipments, :shipping_method_id
+ end
+
+ def down
+ add_column :spree_shipments, :shipping_method_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130418125341_create_spree_stock_transfers.rb b/core/db/migrate/20130418125341_create_spree_stock_transfers.rb
new file mode 100644
index 00000000000..9fd80891012
--- /dev/null
+++ b/core/db/migrate/20130418125341_create_spree_stock_transfers.rb
@@ -0,0 +1,14 @@
+class CreateSpreeStockTransfers < ActiveRecord::Migration
+ def change
+ create_table :spree_stock_transfers do |t|
+ t.string :type
+ t.string :reference_number
+ t.integer :source_location_id
+ t.integer :destination_location_id
+ t.timestamps
+ end
+
+ add_index :spree_stock_transfers, :source_location_id
+ add_index :spree_stock_transfers, :destination_location_id
+ end
+end
diff --git a/core/db/migrate/20130423110707_drop_products_count_on_hand.rb b/core/db/migrate/20130423110707_drop_products_count_on_hand.rb
new file mode 100644
index 00000000000..8580948c733
--- /dev/null
+++ b/core/db/migrate/20130423110707_drop_products_count_on_hand.rb
@@ -0,0 +1,5 @@
+class DropProductsCountOnHand < ActiveRecord::Migration
+ def up
+ remove_column :spree_products, :count_on_hand
+ end
+end
diff --git a/core/db/migrate/20130423223847_set_default_shipping_rate_cost.rb b/core/db/migrate/20130423223847_set_default_shipping_rate_cost.rb
new file mode 100644
index 00000000000..8950dee1ae8
--- /dev/null
+++ b/core/db/migrate/20130423223847_set_default_shipping_rate_cost.rb
@@ -0,0 +1,5 @@
+class SetDefaultShippingRateCost < ActiveRecord::Migration
+ def change
+ change_column :spree_shipping_rates, :cost, :decimal, default: 0, precision: 8, scale: 2
+ end
+end
diff --git a/core/db/migrate/20130509115210_add_number_to_stock_transfer.rb b/core/db/migrate/20130509115210_add_number_to_stock_transfer.rb
new file mode 100644
index 00000000000..83f66474070
--- /dev/null
+++ b/core/db/migrate/20130509115210_add_number_to_stock_transfer.rb
@@ -0,0 +1,23 @@
+class AddNumberToStockTransfer < ActiveRecord::Migration
+ def up
+ remove_index :spree_stock_transfers, :source_location_id
+ remove_index :spree_stock_transfers, :destination_location_id
+
+ rename_column :spree_stock_transfers, :reference_number, :reference
+ add_column :spree_stock_transfers, :number, :string
+
+ Spree::StockTransfer.find_each do |transfer|
+ transfer.send(:generate_stock_transfer_number)
+ transfer.save!
+ end
+
+ add_index :spree_stock_transfers, :number
+ add_index :spree_stock_transfers, :source_location_id
+ add_index :spree_stock_transfers, :destination_location_id
+ end
+
+ def down
+ rename_column :spree_stock_transfers, :reference, :reference_number
+ remove_column :spree_stock_transfers, :number, :string
+ end
+end
diff --git a/core/db/migrate/20130514151929_add_sku_index_to_spree_variants.rb b/core/db/migrate/20130514151929_add_sku_index_to_spree_variants.rb
new file mode 100644
index 00000000000..b028839e11c
--- /dev/null
+++ b/core/db/migrate/20130514151929_add_sku_index_to_spree_variants.rb
@@ -0,0 +1,5 @@
+class AddSkuIndexToSpreeVariants < ActiveRecord::Migration
+ def change
+ add_index :spree_variants, :sku
+ end
+end
diff --git a/core/db/migrate/20130515180736_add_backorderable_default_to_spree_stock_location.rb b/core/db/migrate/20130515180736_add_backorderable_default_to_spree_stock_location.rb
new file mode 100644
index 00000000000..37e3ee4d218
--- /dev/null
+++ b/core/db/migrate/20130515180736_add_backorderable_default_to_spree_stock_location.rb
@@ -0,0 +1,5 @@
+class AddBackorderableDefaultToSpreeStockLocation < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_locations, :backorderable_default, :boolean, default: true
+ end
+end
diff --git a/core/db/migrate/20130516151222_add_propage_all_variants_to_spree_stock_location.rb b/core/db/migrate/20130516151222_add_propage_all_variants_to_spree_stock_location.rb
new file mode 100644
index 00000000000..7ac6ecec18b
--- /dev/null
+++ b/core/db/migrate/20130516151222_add_propage_all_variants_to_spree_stock_location.rb
@@ -0,0 +1,5 @@
+class AddPropageAllVariantsToSpreeStockLocation < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_locations, :propagate_all_variants, :boolean, default: true
+ end
+end
diff --git a/core/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb b/core/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb
new file mode 100644
index 00000000000..e78abca4536
--- /dev/null
+++ b/core/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb
@@ -0,0 +1,10 @@
+class RenameShippingMethodsZonesToSpreeShippingMethodsZones < ActiveRecord::Migration
+ def change
+ rename_table :shipping_methods_zones, :spree_shipping_methods_zones
+ # If Spree::ShippingMethod zones association was patched in
+ # CreateShippingMethodZone migrations, it needs to be patched back
+ Spree::ShippingMethod.has_and_belongs_to_many :zones, :join_table => 'spree_shipping_methods_zones',
+ :class_name => 'Spree::Zone',
+ :foreign_key => 'shipping_method_id'
+ end
+end
diff --git a/core/db/migrate/20130611185927_add_user_id_index_to_spree_orders.rb b/core/db/migrate/20130611185927_add_user_id_index_to_spree_orders.rb
new file mode 100644
index 00000000000..cb8ef67b0e5
--- /dev/null
+++ b/core/db/migrate/20130611185927_add_user_id_index_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddUserIdIndexToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_index :spree_orders, :user_id
+ end
+end
diff --git a/core/db/migrate/20130618041418_add_updated_at_to_spree_countries.rb b/core/db/migrate/20130618041418_add_updated_at_to_spree_countries.rb
new file mode 100644
index 00000000000..fe41413df26
--- /dev/null
+++ b/core/db/migrate/20130618041418_add_updated_at_to_spree_countries.rb
@@ -0,0 +1,9 @@
+class AddUpdatedAtToSpreeCountries < ActiveRecord::Migration
+ def up
+ add_column :spree_countries, :updated_at, :datetime
+ end
+
+ def down
+ remove_column :spree_countries, :updated_at
+ end
+end
diff --git a/core/db/migrate/20130619012236_add_updated_at_to_spree_states.rb b/core/db/migrate/20130619012236_add_updated_at_to_spree_states.rb
new file mode 100644
index 00000000000..1d5d971ee46
--- /dev/null
+++ b/core/db/migrate/20130619012236_add_updated_at_to_spree_states.rb
@@ -0,0 +1,9 @@
+class AddUpdatedAtToSpreeStates < ActiveRecord::Migration
+ def up
+ add_column :spree_states, :updated_at, :datetime
+ end
+
+ def down
+ remove_column :spree_states, :updated_at
+ end
+end
diff --git a/core/db/migrate/20130626232741_add_cvv_result_code_and_cvv_result_message_to_spree_payments.rb b/core/db/migrate/20130626232741_add_cvv_result_code_and_cvv_result_message_to_spree_payments.rb
new file mode 100644
index 00000000000..b8c2d79a680
--- /dev/null
+++ b/core/db/migrate/20130626232741_add_cvv_result_code_and_cvv_result_message_to_spree_payments.rb
@@ -0,0 +1,6 @@
+class AddCvvResultCodeAndCvvResultMessageToSpreePayments < ActiveRecord::Migration
+ def change
+ add_column :spree_payments, :cvv_response_code, :string
+ add_column :spree_payments, :cvv_response_message, :string
+ end
+end
diff --git a/core/db/migrate/20130628021056_add_unique_index_to_permalink_on_spree_products.rb b/core/db/migrate/20130628021056_add_unique_index_to_permalink_on_spree_products.rb
new file mode 100644
index 00000000000..9e2a54b7426
--- /dev/null
+++ b/core/db/migrate/20130628021056_add_unique_index_to_permalink_on_spree_products.rb
@@ -0,0 +1,5 @@
+class AddUniqueIndexToPermalinkOnSpreeProducts < ActiveRecord::Migration
+ def change
+ add_index "spree_products", ["permalink"], :name => "permalink_idx_unique", :unique => true
+ end
+end
diff --git a/core/db/migrate/20130628022817_add_unique_index_to_orders_shipments_and_stock_transfers.rb b/core/db/migrate/20130628022817_add_unique_index_to_orders_shipments_and_stock_transfers.rb
new file mode 100644
index 00000000000..2dbc6b2c13e
--- /dev/null
+++ b/core/db/migrate/20130628022817_add_unique_index_to_orders_shipments_and_stock_transfers.rb
@@ -0,0 +1,7 @@
+class AddUniqueIndexToOrdersShipmentsAndStockTransfers < ActiveRecord::Migration
+ def add
+ add_index "spree_orders", ["number"], :name => "number_idx_unique", :unique => true
+ add_index "spree_shipments", ["number"], :name => "number_idx_unique", :unique => true
+ add_index "spree_stock_transfers", ["number"], :name => "number_idx_unique", :unique => true
+ end
+end
diff --git a/core/db/migrate/20130708052307_add_deleted_at_to_spree_tax_rates.rb b/core/db/migrate/20130708052307_add_deleted_at_to_spree_tax_rates.rb
new file mode 100644
index 00000000000..607148512fe
--- /dev/null
+++ b/core/db/migrate/20130708052307_add_deleted_at_to_spree_tax_rates.rb
@@ -0,0 +1,5 @@
+class AddDeletedAtToSpreeTaxRates < ActiveRecord::Migration
+ def change
+ add_column :spree_tax_rates, :deleted_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20130711200933_remove_lock_version_from_inventory_units.rb b/core/db/migrate/20130711200933_remove_lock_version_from_inventory_units.rb
new file mode 100644
index 00000000000..a4cb43a1850
--- /dev/null
+++ b/core/db/migrate/20130711200933_remove_lock_version_from_inventory_units.rb
@@ -0,0 +1,6 @@
+class RemoveLockVersionFromInventoryUnits < ActiveRecord::Migration
+ def change
+ # we are moving to pessimistic locking on stock_items
+ remove_column :spree_inventory_units, :lock_version
+ end
+end
diff --git a/core/db/migrate/20130718042445_add_cost_price_to_line_item.rb b/core/db/migrate/20130718042445_add_cost_price_to_line_item.rb
new file mode 100644
index 00000000000..f67fa1a0a37
--- /dev/null
+++ b/core/db/migrate/20130718042445_add_cost_price_to_line_item.rb
@@ -0,0 +1,5 @@
+class AddCostPriceToLineItem < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :cost_price, :decimal, :precision => 8, :scale => 2
+ end
+end
diff --git a/core/db/migrate/20130718233855_set_backorderable_to_default_to_false.rb b/core/db/migrate/20130718233855_set_backorderable_to_default_to_false.rb
new file mode 100644
index 00000000000..c05cd19426a
--- /dev/null
+++ b/core/db/migrate/20130718233855_set_backorderable_to_default_to_false.rb
@@ -0,0 +1,6 @@
+class SetBackorderableToDefaultToFalse < ActiveRecord::Migration
+ def change
+ change_column :spree_stock_items, :backorderable, :boolean, :default => false
+ change_column :spree_stock_locations, :backorderable_default, :boolean, :default => false
+ end
+end
diff --git a/core/db/migrate/20130725031716_add_created_by_id_to_spree_orders.rb b/core/db/migrate/20130725031716_add_created_by_id_to_spree_orders.rb
new file mode 100644
index 00000000000..e3277cf3493
--- /dev/null
+++ b/core/db/migrate/20130725031716_add_created_by_id_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddCreatedByIdToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :created_by_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130729214043_index_completed_at_on_spree_orders.rb b/core/db/migrate/20130729214043_index_completed_at_on_spree_orders.rb
new file mode 100644
index 00000000000..0ecfcf35d46
--- /dev/null
+++ b/core/db/migrate/20130729214043_index_completed_at_on_spree_orders.rb
@@ -0,0 +1,5 @@
+class IndexCompletedAtOnSpreeOrders < ActiveRecord::Migration
+ def change
+ add_index :spree_orders, :completed_at
+ end
+end
diff --git a/core/db/migrate/20130802014537_add_tax_category_id_to_spree_line_items.rb b/core/db/migrate/20130802014537_add_tax_category_id_to_spree_line_items.rb
new file mode 100644
index 00000000000..953ceeb9076
--- /dev/null
+++ b/core/db/migrate/20130802014537_add_tax_category_id_to_spree_line_items.rb
@@ -0,0 +1,5 @@
+class AddTaxCategoryIdToSpreeLineItems < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :tax_category_id, :integer
+ end
+end
diff --git a/core/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb b/core/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb
new file mode 100644
index 00000000000..641b19def2b
--- /dev/null
+++ b/core/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb
@@ -0,0 +1,10 @@
+class MigrateTaxCategoriesToLineItems < ActiveRecord::Migration
+ def change
+ Spree::LineItem.find_each do |line_item|
+ next if line_item.variant.nil?
+ next if line_item.variant.product.nil?
+ next if line_item.product.nil?
+ line_item.update_column(:tax_category_id, line_item.product.tax_category_id)
+ end
+ end
+end
diff --git a/core/db/migrate/20130806022521_drop_spree_mail_methods.rb b/core/db/migrate/20130806022521_drop_spree_mail_methods.rb
new file mode 100644
index 00000000000..7e1e9769b64
--- /dev/null
+++ b/core/db/migrate/20130806022521_drop_spree_mail_methods.rb
@@ -0,0 +1,12 @@
+class DropSpreeMailMethods < ActiveRecord::Migration
+ def up
+ drop_table :spree_mail_methods
+ end
+
+ def down
+ create_table(:spree_mail_methods) do |t|
+ t.string :environment
+ t.boolean :active
+ end
+ end
+end
diff --git a/core/db/migrate/20130806145853_set_default_stock_location_on_shipments.rb b/core/db/migrate/20130806145853_set_default_stock_location_on_shipments.rb
new file mode 100644
index 00000000000..7c149446813
--- /dev/null
+++ b/core/db/migrate/20130806145853_set_default_stock_location_on_shipments.rb
@@ -0,0 +1,8 @@
+class SetDefaultStockLocationOnShipments < ActiveRecord::Migration
+ def change
+ if Spree::Shipment.where('stock_location_id IS NULL').count > 0
+ location = Spree::StockLocation.find_by(name: 'default') || Spree::StockLocation.first
+ Spree::Shipment.where('stock_location_id IS NULL').update_all(stock_location_id: location.id)
+ end
+ end
+end
diff --git a/core/db/migrate/20130807024301_upgrade_adjustments.rb b/core/db/migrate/20130807024301_upgrade_adjustments.rb
new file mode 100644
index 00000000000..6a4a68e1bde
--- /dev/null
+++ b/core/db/migrate/20130807024301_upgrade_adjustments.rb
@@ -0,0 +1,40 @@
+class UpgradeAdjustments < ActiveRecord::Migration
+ def up
+ # Temporarily make originator association available
+ Spree::Adjustment.class_eval do
+ belongs_to :originator, polymorphic: true
+ end
+ # Shipping adjustments are now tracked as fields on the object
+ Spree::Adjustment.where(:source_type => "Spree::Shipment").find_each do |adjustment|
+ # Account for possible invalid data
+ next if adjustment.source.nil?
+ adjustment.source.update_column(:cost, adjustment.amount)
+ adjustment.destroy!
+ end
+
+ # Tax adjustments have their sources altered
+ Spree::Adjustment.where(:originator_type => "Spree::TaxRate").find_each do |adjustment|
+ adjustment.source_id = adjustment.originator_id
+ adjustment.source_type = "Spree::TaxRate"
+ adjustment.save!
+ end
+
+ # Promotion adjustments have their source altered also
+ Spree::Adjustment.where(:originator_type => "Spree::PromotionAction").find_each do |adjustment|
+ next if adjustment.originator.nil?
+ adjustment.source = adjustment.originator
+ begin
+ if adjustment.source.calculator_type == "Spree::Calculator::FreeShipping"
+ # Previously this was a Spree::Promotion::Actions::CreateAdjustment
+ # And it had a calculator to work out FreeShipping
+ # In Spree 2.2, the "calculator" is now the action itself.
+ adjustment.source.becomes(Spree::Promotion::Actions::FreeShipping)
+ end
+ rescue
+ # Fail silently. This is primarily in instances where the calculator no longer exists
+ end
+
+ adjustment.save!
+ end
+ end
+end
diff --git a/core/db/migrate/20130807024302_rename_adjustment_fields.rb b/core/db/migrate/20130807024302_rename_adjustment_fields.rb
new file mode 100644
index 00000000000..d4eaeb2a725
--- /dev/null
+++ b/core/db/migrate/20130807024302_rename_adjustment_fields.rb
@@ -0,0 +1,14 @@
+class RenameAdjustmentFields < ActiveRecord::Migration
+ def up
+ remove_column :spree_adjustments, :originator_id
+ remove_column :spree_adjustments, :originator_type
+
+ add_column :spree_adjustments, :order_id, :integer unless column_exists?(:spree_adjustments, :order_id)
+
+ # This enables the Spree::Order#all_adjustments association to work correctly
+ Spree::Adjustment.reset_column_information
+ Spree::Adjustment.where(adjustable_type: "Spree::Order").find_each do |adjustment|
+ adjustment.update_column(:order_id, adjustment.adjustable_id)
+ end
+ end
+end
diff --git a/core/db/migrate/20130809164245_add_admin_name_column_to_spree_shipping_methods.rb b/core/db/migrate/20130809164245_add_admin_name_column_to_spree_shipping_methods.rb
new file mode 100644
index 00000000000..35a51ed4c34
--- /dev/null
+++ b/core/db/migrate/20130809164245_add_admin_name_column_to_spree_shipping_methods.rb
@@ -0,0 +1,5 @@
+class AddAdminNameColumnToSpreeShippingMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_methods, :admin_name, :string
+ end
+end
diff --git a/core/db/migrate/20130809164330_add_admin_name_column_to_spree_stock_locations.rb b/core/db/migrate/20130809164330_add_admin_name_column_to_spree_stock_locations.rb
new file mode 100644
index 00000000000..01657867cb7
--- /dev/null
+++ b/core/db/migrate/20130809164330_add_admin_name_column_to_spree_stock_locations.rb
@@ -0,0 +1,5 @@
+class AddAdminNameColumnToSpreeStockLocations < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_locations, :admin_name, :string
+ end
+end
diff --git a/core/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb b/core/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb
new file mode 100644
index 00000000000..9cafc576367
--- /dev/null
+++ b/core/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddShipmentTotalToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :shipment_total, :decimal, :precision => 10, :scale => 2, :default => 0.0, :null => false
+ end
+end
diff --git a/core/db/migrate/20130813140619_expand_order_number_size.rb b/core/db/migrate/20130813140619_expand_order_number_size.rb
new file mode 100644
index 00000000000..6963289718e
--- /dev/null
+++ b/core/db/migrate/20130813140619_expand_order_number_size.rb
@@ -0,0 +1,9 @@
+class ExpandOrderNumberSize < ActiveRecord::Migration
+ def up
+ change_column :spree_orders, :number, :string, :limit => 32
+ end
+
+ def down
+ change_column :spree_orders, :number, :string, :limit => 15
+ end
+end
diff --git a/core/db/migrate/20130813232134_rename_activators_to_promotions.rb b/core/db/migrate/20130813232134_rename_activators_to_promotions.rb
new file mode 100644
index 00000000000..90d62029bfa
--- /dev/null
+++ b/core/db/migrate/20130813232134_rename_activators_to_promotions.rb
@@ -0,0 +1,5 @@
+class RenameActivatorsToPromotions < ActiveRecord::Migration
+ def change
+ rename_table :spree_activators, :spree_promotions
+ end
+end
diff --git a/core/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb b/core/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb
new file mode 100644
index 00000000000..2c653ec38f9
--- /dev/null
+++ b/core/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb
@@ -0,0 +1,5 @@
+class AddAdjustmentTotalToLineItems < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :adjustment_total, :decimal, :precision => 10, :scale => 2, :default => 0.0
+ end
+end
diff --git a/core/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb b/core/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb
new file mode 100644
index 00000000000..7c9bc0f07ee
--- /dev/null
+++ b/core/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb
@@ -0,0 +1,5 @@
+class AddAdjustmentTotalToShipments < ActiveRecord::Migration
+ def change
+ add_column :spree_shipments, :adjustment_total, :decimal, :precision => 10, :scale => 2, :default => 0.0
+ end
+end
diff --git a/core/db/migrate/20130826062534_add_depth_to_spree_taxons.rb b/core/db/migrate/20130826062534_add_depth_to_spree_taxons.rb
new file mode 100644
index 00000000000..84d33081a42
--- /dev/null
+++ b/core/db/migrate/20130826062534_add_depth_to_spree_taxons.rb
@@ -0,0 +1,16 @@
+class AddDepthToSpreeTaxons < ActiveRecord::Migration
+ def up
+ if !Spree::Taxon.column_names.include?('depth')
+ add_column :spree_taxons, :depth, :integer
+
+ say_with_time 'Update depth on all taxons' do
+ Spree::Taxon.reset_column_information
+ Spree::Taxon.all.each { |t| t.save }
+ end
+ end
+ end
+
+ def down
+ remove_column :spree_taxons, :depth
+ end
+end
diff --git a/core/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb b/core/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb
new file mode 100644
index 00000000000..69a7a5f65fe
--- /dev/null
+++ b/core/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb
@@ -0,0 +1,8 @@
+class AddTaxTotalToLineItemsShipmentsAndOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :tax_total, :decimal, precision: 10, scale: 2, default: 0.0
+ add_column :spree_shipments, :tax_total, :decimal, precision: 10, scale: 2, default: 0.0
+ # This column may already be here from a 2.1.x migration
+ add_column :spree_orders, :tax_total, :decimal, precision: 10, scale: 2, default: 0.0 unless Spree::Order.column_names.include?("tax_total")
+ end
+end
diff --git a/core/db/migrate/20130830001033_add_shipping_category_to_shipping_methods_and_products.rb b/core/db/migrate/20130830001033_add_shipping_category_to_shipping_methods_and_products.rb
new file mode 100644
index 00000000000..6dab377defe
--- /dev/null
+++ b/core/db/migrate/20130830001033_add_shipping_category_to_shipping_methods_and_products.rb
@@ -0,0 +1,15 @@
+class AddShippingCategoryToShippingMethodsAndProducts < ActiveRecord::Migration
+ def up
+ default_category = Spree::ShippingCategory.first
+ default_category ||= Spree::ShippingCategory.create!(:name => "Default")
+
+ Spree::ShippingMethod.all.each do |method|
+ method.shipping_categories << default_category if method.shipping_categories.blank?
+ end
+
+ Spree::Product.where(shipping_category_id: nil).update_all(shipping_category_id: default_category.id)
+ end
+
+ def down
+ end
+end
diff --git a/core/db/migrate/20130830001159_migrate_old_shipping_calculators.rb b/core/db/migrate/20130830001159_migrate_old_shipping_calculators.rb
new file mode 100644
index 00000000000..ea53b0816c2
--- /dev/null
+++ b/core/db/migrate/20130830001159_migrate_old_shipping_calculators.rb
@@ -0,0 +1,19 @@
+class MigrateOldShippingCalculators < ActiveRecord::Migration
+ def up
+ Spree::ShippingMethod.all.each do |shipping_method|
+ old_calculator = shipping_method.calculator
+ next if old_calculator.class < Spree::ShippingCalculator # We don't want to mess with new shipping calculators
+ new_calculator = eval(old_calculator.class.name.sub("::Calculator::", "::Calculator::Shipping::")).new
+ new_calculator.preferences.keys.each do |pref|
+ # Preferences can't be read/set by name, you have to prefix preferred_
+ pref_method = "preferred_#{pref}"
+ new_calculator.send("#{pref_method}=", old_calculator.send(pref_method))
+ end
+ new_calculator.calculable = old_calculator.calculable
+ new_calculator.save
+ end
+ end
+
+ def down
+ end
+end
diff --git a/core/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb b/core/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb
new file mode 100644
index 00000000000..308a61b0169
--- /dev/null
+++ b/core/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb
@@ -0,0 +1,5 @@
+class AddCodeToSpreePromotionRules < ActiveRecord::Migration
+ def change
+ add_column :spree_promotion_rules, :code, :string
+ end
+end
diff --git a/core/db/migrate/20130909115621_change_states_required_for_countries.rb b/core/db/migrate/20130909115621_change_states_required_for_countries.rb
new file mode 100644
index 00000000000..b887e49bf54
--- /dev/null
+++ b/core/db/migrate/20130909115621_change_states_required_for_countries.rb
@@ -0,0 +1,9 @@
+class ChangeStatesRequiredForCountries < ActiveRecord::Migration
+ def up
+ change_column_default :spree_countries, :states_required, false
+ end
+
+ def down
+ change_column_default :spree_countries, :states_required, true
+ end
+end
diff --git a/core/db/migrate/20130915032339_add_deleted_at_to_spree_stock_items.rb b/core/db/migrate/20130915032339_add_deleted_at_to_spree_stock_items.rb
new file mode 100644
index 00000000000..a1eceebee6f
--- /dev/null
+++ b/core/db/migrate/20130915032339_add_deleted_at_to_spree_stock_items.rb
@@ -0,0 +1,5 @@
+class AddDeletedAtToSpreeStockItems < ActiveRecord::Migration
+ def change
+ add_column :spree_stock_items, :deleted_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20130917024658_remove_promotions_event_name_field.rb b/core/db/migrate/20130917024658_remove_promotions_event_name_field.rb
new file mode 100644
index 00000000000..d0f64031e70
--- /dev/null
+++ b/core/db/migrate/20130917024658_remove_promotions_event_name_field.rb
@@ -0,0 +1,5 @@
+class RemovePromotionsEventNameField < ActiveRecord::Migration
+ def change
+ remove_column :spree_promotions, :event_name
+ end
+end
diff --git a/core/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb b/core/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb
new file mode 100644
index 00000000000..74e749462b2
--- /dev/null
+++ b/core/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb
@@ -0,0 +1,7 @@
+class AddPromoTotalToLineItemsAndShipmentsAndOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :promo_total, :decimal, precision: 10, scale: 2, default: 0.0
+ add_column :spree_shipments, :promo_total, :decimal, precision: 10, scale: 2, default: 0.0
+ add_column :spree_orders, :promo_total, :decimal, precision: 10, scale: 2, default: 0.0
+ end
+end
diff --git a/core/db/migrate/20131001013410_remove_unused_credit_card_fields.rb b/core/db/migrate/20131001013410_remove_unused_credit_card_fields.rb
new file mode 100644
index 00000000000..5dac86c5153
--- /dev/null
+++ b/core/db/migrate/20131001013410_remove_unused_credit_card_fields.rb
@@ -0,0 +1,16 @@
+class RemoveUnusedCreditCardFields < ActiveRecord::Migration
+ def up
+ remove_column :spree_credit_cards, :start_month if column_exists?(:spree_credit_cards, :start_month)
+ remove_column :spree_credit_cards, :start_year if column_exists?(:spree_credit_cards, :start_year)
+ remove_column :spree_credit_cards, :issue_number if column_exists?(:spree_credit_cards, :issue_number)
+ end
+ def down
+ add_column :spree_credit_cards, :start_month, :string
+ add_column :spree_credit_cards, :start_year, :string
+ add_column :spree_credit_cards, :issue_number, :string
+ end
+
+ def column_exists?(table, column)
+ ActiveRecord::Base.connection.column_exists?(table, column)
+ end
+end
diff --git a/core/db/migrate/20131026154747_add_track_inventory_to_variant.rb b/core/db/migrate/20131026154747_add_track_inventory_to_variant.rb
new file mode 100644
index 00000000000..c25bd4abd66
--- /dev/null
+++ b/core/db/migrate/20131026154747_add_track_inventory_to_variant.rb
@@ -0,0 +1,5 @@
+class AddTrackInventoryToVariant < ActiveRecord::Migration
+ def change
+ add_column :spree_variants, :track_inventory, :boolean, :default => true
+ end
+end
diff --git a/core/db/migrate/20131107132123_add_tax_category_to_variants.rb b/core/db/migrate/20131107132123_add_tax_category_to_variants.rb
new file mode 100644
index 00000000000..83262e65137
--- /dev/null
+++ b/core/db/migrate/20131107132123_add_tax_category_to_variants.rb
@@ -0,0 +1,6 @@
+class AddTaxCategoryToVariants < ActiveRecord::Migration
+ def change
+ add_column :spree_variants, :tax_category_id, :integer
+ add_index :spree_variants, :tax_category_id
+ end
+end
diff --git a/core/db/migrate/20131113035136_add_channel_to_spree_orders.rb b/core/db/migrate/20131113035136_add_channel_to_spree_orders.rb
new file mode 100644
index 00000000000..53d932a0ce7
--- /dev/null
+++ b/core/db/migrate/20131113035136_add_channel_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddChannelToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :channel, :string, default: "spree"
+ end
+end
diff --git a/core/db/migrate/20131118043959_add_included_to_adjustments.rb b/core/db/migrate/20131118043959_add_included_to_adjustments.rb
new file mode 100644
index 00000000000..8fc88d6826f
--- /dev/null
+++ b/core/db/migrate/20131118043959_add_included_to_adjustments.rb
@@ -0,0 +1,5 @@
+class AddIncludedToAdjustments < ActiveRecord::Migration
+ def change
+ add_column :spree_adjustments, :included, :boolean, :default => false unless Spree::Adjustment.column_names.include?("included")
+ end
+end
diff --git a/core/db/migrate/20131118050234_rename_tax_total_fields.rb b/core/db/migrate/20131118050234_rename_tax_total_fields.rb
new file mode 100644
index 00000000000..57f3c475ca7
--- /dev/null
+++ b/core/db/migrate/20131118050234_rename_tax_total_fields.rb
@@ -0,0 +1,11 @@
+class RenameTaxTotalFields < ActiveRecord::Migration
+ def change
+ rename_column :spree_line_items, :tax_total, :additional_tax_total
+ rename_column :spree_shipments, :tax_total, :additional_tax_total
+ rename_column :spree_orders, :tax_total, :additional_tax_total
+
+ add_column :spree_line_items, :included_tax_total, :decimal, precision: 10, scale: 2, null: false, default: 0.0
+ add_column :spree_shipments, :included_tax_total, :decimal, precision: 10, scale: 2, null: false, default: 0.0
+ add_column :spree_orders, :included_tax_total, :decimal, precision: 10, scale: 2, null: false, default: 0.0
+ end
+end
diff --git a/core/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb b/core/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb
new file mode 100644
index 00000000000..478ee9318b7
--- /dev/null
+++ b/core/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb
@@ -0,0 +1,21 @@
+class AddLineItemIdToSpreeInventoryUnits < ActiveRecord::Migration
+ def change
+ # Stores running the product-assembly extension already have a line_item_id column
+ unless column_exists? Spree::InventoryUnit.table_name, :line_item_id
+ add_column :spree_inventory_units, :line_item_id, :integer
+ add_index :spree_inventory_units, :line_item_id
+
+ shipments = Spree::Shipment.includes(:inventory_units, :order)
+
+ shipments.find_each do |shipment|
+ shipment.inventory_units.group_by(&:variant_id).each do |variant_id, units|
+
+ line_item = shipment.order.line_items.find_by(variant_id: variant_id)
+ next unless line_item
+
+ Spree::InventoryUnit.where(id: units.map(&:id)).update_all(line_item_id: line_item.id)
+ end
+ end
+ end
+ end
+end
diff --git a/core/db/migrate/20131120234456_add_updated_at_to_variants.rb b/core/db/migrate/20131120234456_add_updated_at_to_variants.rb
new file mode 100644
index 00000000000..a60077897b0
--- /dev/null
+++ b/core/db/migrate/20131120234456_add_updated_at_to_variants.rb
@@ -0,0 +1,5 @@
+class AddUpdatedAtToVariants < ActiveRecord::Migration
+ def change
+ add_column :spree_variants, :updated_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20131127001002_add_position_to_classifications.rb b/core/db/migrate/20131127001002_add_position_to_classifications.rb
new file mode 100644
index 00000000000..b4b5deb6d10
--- /dev/null
+++ b/core/db/migrate/20131127001002_add_position_to_classifications.rb
@@ -0,0 +1,5 @@
+class AddPositionToClassifications < ActiveRecord::Migration
+ def change
+ add_column :spree_products_taxons, :position, :integer
+ end
+end
diff --git a/core/db/migrate/20131211112807_create_spree_orders_promotions.rb b/core/db/migrate/20131211112807_create_spree_orders_promotions.rb
new file mode 100644
index 00000000000..2deb04d0aff
--- /dev/null
+++ b/core/db/migrate/20131211112807_create_spree_orders_promotions.rb
@@ -0,0 +1,8 @@
+class CreateSpreeOrdersPromotions < ActiveRecord::Migration
+ def change
+ create_table :spree_orders_promotions, :id => false do |t|
+ t.references :order
+ t.references :promotion
+ end
+ end
+end
diff --git a/core/db/migrate/20131211192741_unique_shipping_method_categories.rb b/core/db/migrate/20131211192741_unique_shipping_method_categories.rb
new file mode 100644
index 00000000000..b6dd115aa78
--- /dev/null
+++ b/core/db/migrate/20131211192741_unique_shipping_method_categories.rb
@@ -0,0 +1,24 @@
+class UniqueShippingMethodCategories < ActiveRecord::Migration
+ def change
+ klass = Spree::ShippingMethodCategory
+ columns = %w[shipping_category_id shipping_method_id]
+
+ say "Find duplicate #{klass} records"
+ duplicates = klass.
+ select((columns + %w[COUNT(*)]).join(',')).
+ group(columns.join(',')).
+ having('COUNT(*) > 1').
+ map { |row| row.attributes.slice(*columns) }
+
+ say "Delete all but the oldest duplicate #{klass} record"
+ duplicates.each do |conditions|
+ klass.where(conditions).order(:created_at).drop(1).each(&:destroy)
+ end
+
+ say "Add unique index to #{klass.table_name} for #{columns.inspect}"
+ add_index klass.table_name, columns, unique: true, name: 'unique_spree_shipping_method_categories'
+
+ say "Remove redundant simple index on #{klass.table_name}"
+ remove_index klass.table_name, name: 'index_spree_shipping_method_categories_on_shipping_category_id'
+ end
+end
diff --git a/core/db/migrate/20131218054603_add_item_count_to_spree_orders.rb b/core/db/migrate/20131218054603_add_item_count_to_spree_orders.rb
new file mode 100644
index 00000000000..92115af1965
--- /dev/null
+++ b/core/db/migrate/20131218054603_add_item_count_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddItemCountToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :item_count, :integer, :default => 0
+ end
+end
diff --git a/core/db/migrate/20140106065820_remove_value_type_from_spree_preferences.rb b/core/db/migrate/20140106065820_remove_value_type_from_spree_preferences.rb
new file mode 100644
index 00000000000..895f8af985b
--- /dev/null
+++ b/core/db/migrate/20140106065820_remove_value_type_from_spree_preferences.rb
@@ -0,0 +1,8 @@
+class RemoveValueTypeFromSpreePreferences < ActiveRecord::Migration
+ def up
+ remove_column :spree_preferences, :value_type
+ end
+ def down
+ raise ActiveRecord::IrreversableMigration
+ end
+end
diff --git a/core/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb b/core/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb
new file mode 100644
index 00000000000..9bbb8273b1c
--- /dev/null
+++ b/core/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb
@@ -0,0 +1,5 @@
+class RenamePermalinkToSlugForProducts < ActiveRecord::Migration
+ def change
+ rename_column :spree_products, :permalink, :slug
+ end
+end
diff --git a/core/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb b/core/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb
new file mode 100644
index 00000000000..656fb6b44eb
--- /dev/null
+++ b/core/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb
@@ -0,0 +1,5 @@
+class AddIndexToVariantIdAndCurrencyOnPrices < ActiveRecord::Migration
+ def change
+ add_index :spree_prices, [:variant_id, :currency]
+ end
+end
diff --git a/core/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb b/core/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb
new file mode 100644
index 00000000000..7fe31b0b002
--- /dev/null
+++ b/core/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb
@@ -0,0 +1,6 @@
+class RenameActivatorIdInRulesAndActionsToPromotionId < ActiveRecord::Migration
+ def change
+ rename_column :spree_promotion_rules, :activator_id, :promotion_id
+ rename_column :spree_promotion_actions, :activator_id, :promotion_id
+ end
+end
diff --git a/core/db/migrate/20140129024326_add_deleted_at_to_spree_prices.rb b/core/db/migrate/20140129024326_add_deleted_at_to_spree_prices.rb
new file mode 100644
index 00000000000..949b7c11d92
--- /dev/null
+++ b/core/db/migrate/20140129024326_add_deleted_at_to_spree_prices.rb
@@ -0,0 +1,5 @@
+class AddDeletedAtToSpreePrices < ActiveRecord::Migration
+ def change
+ add_column :spree_prices, :deleted_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb b/core/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb
new file mode 100644
index 00000000000..b22f2c3e7a0
--- /dev/null
+++ b/core/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb
@@ -0,0 +1,6 @@
+class AddApproverIdAndApprovedAtToOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :approver_id, :integer
+ add_column :spree_orders, :approved_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb b/core/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb
new file mode 100644
index 00000000000..243d95b57c6
--- /dev/null
+++ b/core/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddConfirmationDeliveredToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :confirmation_delivered, :boolean, default: false
+ end
+end
diff --git a/core/db/migrate/20140204192230_add_auto_capture_to_payment_methods.rb b/core/db/migrate/20140204192230_add_auto_capture_to_payment_methods.rb
new file mode 100644
index 00000000000..bb5b912860d
--- /dev/null
+++ b/core/db/migrate/20140204192230_add_auto_capture_to_payment_methods.rb
@@ -0,0 +1,5 @@
+class AddAutoCaptureToPaymentMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_payment_methods, :auto_capture, :boolean
+ end
+end
diff --git a/core/db/migrate/20140205120320_create_spree_payment_capture_events.rb b/core/db/migrate/20140205120320_create_spree_payment_capture_events.rb
new file mode 100644
index 00000000000..587de419851
--- /dev/null
+++ b/core/db/migrate/20140205120320_create_spree_payment_capture_events.rb
@@ -0,0 +1,12 @@
+class CreateSpreePaymentCaptureEvents < ActiveRecord::Migration
+ def change
+ create_table :spree_payment_capture_events do |t|
+ t.decimal :amount, precision: 10, scale: 2, default: 0.0
+ t.integer :payment_id
+
+ t.timestamps
+ end
+
+ add_index :spree_payment_capture_events, :payment_id
+ end
+end
diff --git a/core/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb b/core/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb
new file mode 100644
index 00000000000..9964dcf4e4d
--- /dev/null
+++ b/core/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb
@@ -0,0 +1,5 @@
+class AddUncapturedAmountToPayments < ActiveRecord::Migration
+ def change
+ add_column :spree_payments, :uncaptured_amount, :decimal, precision: 10, scale: 2, default: 0.0
+ end
+end
diff --git a/core/db/migrate/20140205181631_default_variant_weight_to_zero.rb b/core/db/migrate/20140205181631_default_variant_weight_to_zero.rb
new file mode 100644
index 00000000000..b5710e74abb
--- /dev/null
+++ b/core/db/migrate/20140205181631_default_variant_weight_to_zero.rb
@@ -0,0 +1,11 @@
+class DefaultVariantWeightToZero < ActiveRecord::Migration
+ def up
+ Spree::Variant.unscoped.where(weight: nil).update_all("weight = 0.0")
+
+ change_column :spree_variants, :weight, :decimal, precision: 8, scale: 2, default: 0.0
+ end
+
+ def down
+ change_column :spree_variants, :weight, :decimal, precision: 8, scale: 2
+ end
+end
diff --git a/core/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb b/core/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb
new file mode 100644
index 00000000000..b330c066e86
--- /dev/null
+++ b/core/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb
@@ -0,0 +1,5 @@
+class AddTaxCategoryIdToShippingMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_methods, :tax_category_id, :integer
+ end
+end
diff --git a/core/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb b/core/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb
new file mode 100644
index 00000000000..8b584d032c0
--- /dev/null
+++ b/core/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb
@@ -0,0 +1,5 @@
+class AddTaxRateIdToShippingRates < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_rates, :tax_rate_id, :integer
+ end
+end
diff --git a/core/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb b/core/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb
new file mode 100644
index 00000000000..c2de36b5f21
--- /dev/null
+++ b/core/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb
@@ -0,0 +1,6 @@
+class AddPreTaxAmountToLineItemsAndShipments < ActiveRecord::Migration
+ def change
+ add_column :spree_line_items, :pre_tax_amount, :decimal, precision: 8, scale: 2
+ add_column :spree_shipments, :pre_tax_amount, :decimal, precision: 8, scale: 2
+ end
+end
diff --git a/core/db/migrate/20140213184916_add_more_indexes.rb b/core/db/migrate/20140213184916_add_more_indexes.rb
new file mode 100644
index 00000000000..e23d0d57d49
--- /dev/null
+++ b/core/db/migrate/20140213184916_add_more_indexes.rb
@@ -0,0 +1,13 @@
+class AddMoreIndexes < ActiveRecord::Migration
+ def change
+ add_index :spree_payment_methods, [:id, :type]
+ add_index :spree_calculators, [:id, :type]
+ add_index :spree_calculators, [:calculable_id, :calculable_type]
+ add_index :spree_payments, :payment_method_id
+ add_index :spree_promotion_actions, [:id, :type]
+ add_index :spree_promotion_actions, :promotion_id
+ add_index :spree_promotions, [:id, :type]
+ add_index :spree_option_values, :option_type_id
+ add_index :spree_shipments, :stock_location_id
+ end
+end
diff --git a/core/db/migrate/20140219060952_add_considered_risky_to_orders.rb b/core/db/migrate/20140219060952_add_considered_risky_to_orders.rb
new file mode 100644
index 00000000000..7b4c4161097
--- /dev/null
+++ b/core/db/migrate/20140219060952_add_considered_risky_to_orders.rb
@@ -0,0 +1,5 @@
+class AddConsideredRiskyToOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :considered_risky, :boolean, :default => false
+ end
+end
diff --git a/core/db/migrate/20140227112348_add_preference_store_to_everything.rb b/core/db/migrate/20140227112348_add_preference_store_to_everything.rb
new file mode 100644
index 00000000000..5381e32693c
--- /dev/null
+++ b/core/db/migrate/20140227112348_add_preference_store_to_everything.rb
@@ -0,0 +1,8 @@
+class AddPreferenceStoreToEverything < ActiveRecord::Migration
+ def change
+ add_column :spree_calculators, :preferences, :text
+ add_column :spree_gateways, :preferences, :text
+ add_column :spree_payment_methods, :preferences, :text
+ add_column :spree_promotion_rules, :preferences, :text
+ end
+end
diff --git a/core/db/migrate/20140307235515_add_user_id_to_spree_credit_cards.rb b/core/db/migrate/20140307235515_add_user_id_to_spree_credit_cards.rb
new file mode 100644
index 00000000000..837343be7df
--- /dev/null
+++ b/core/db/migrate/20140307235515_add_user_id_to_spree_credit_cards.rb
@@ -0,0 +1,13 @@
+class AddUserIdToSpreeCreditCards < ActiveRecord::Migration
+ def change
+ unless Spree::CreditCard.column_names.include? "user_id"
+ add_column :spree_credit_cards, :user_id, :integer
+ add_index :spree_credit_cards, :user_id
+ end
+
+ unless Spree::CreditCard.column_names.include? "payment_method_id"
+ add_column :spree_credit_cards, :payment_method_id, :integer
+ add_index :spree_credit_cards, :payment_method_id
+ end
+ end
+end
diff --git a/core/db/migrate/20140309023735_migrate_old_preferences.rb b/core/db/migrate/20140309023735_migrate_old_preferences.rb
new file mode 100644
index 00000000000..fe3028c8f2f
--- /dev/null
+++ b/core/db/migrate/20140309023735_migrate_old_preferences.rb
@@ -0,0 +1,23 @@
+class MigrateOldPreferences < ActiveRecord::Migration
+ def up
+ migrate_preferences(Spree::Calculator)
+ migrate_preferences(Spree::PaymentMethod)
+ migrate_preferences(Spree::PromotionRule)
+ end
+
+ def down
+ end
+
+ private
+ def migrate_preferences klass
+ klass.reset_column_information
+ klass.find_each do |record|
+ store = Spree::Preferences::ScopedStore.new(record.class.name.underscore, record.id)
+ record.defined_preferences.each do |key|
+ value = store.fetch(key){}
+ record.preferences[key] = value unless value.nil?
+ end
+ record.save!
+ end
+ end
+end
diff --git a/core/db/migrate/20140309024355_create_spree_stores.rb b/core/db/migrate/20140309024355_create_spree_stores.rb
new file mode 100644
index 00000000000..cf1eb37b240
--- /dev/null
+++ b/core/db/migrate/20140309024355_create_spree_stores.rb
@@ -0,0 +1,25 @@
+class CreateSpreeStores < ActiveRecord::Migration
+ def change
+ if table_exists?(:spree_stores)
+ rename_column :spree_stores, :domains, :url
+ rename_column :spree_stores, :email, :mail_from_address
+ add_column :spree_stores, :meta_description, :text
+ add_column :spree_stores, :meta_keywords, :text
+ add_column :spree_stores, :seo_title, :string
+ else
+ create_table :spree_stores do |t|
+ t.string :name
+ t.string :url
+ t.text :meta_description
+ t.text :meta_keywords
+ t.string :seo_title
+ t.string :mail_from_address
+ t.string :default_currency
+ t.string :code
+ t.boolean :default, default: false, null: false
+
+ t.timestamps
+ end
+ end
+ end
+end
diff --git a/core/db/migrate/20140309033438_create_store_from_preferences.rb b/core/db/migrate/20140309033438_create_store_from_preferences.rb
new file mode 100644
index 00000000000..6267cc4b73d
--- /dev/null
+++ b/core/db/migrate/20140309033438_create_store_from_preferences.rb
@@ -0,0 +1,37 @@
+class CreateStoreFromPreferences < ActiveRecord::Migration
+ def change
+ # workaround for spree_i18n and Store translations
+ Spree::Store.class_eval do
+ def self.translated?(name)
+ false
+ end
+ end
+
+ preference_store = Spree::Preferences::Store.instance
+ if store = Spree::Store.where(default: true).first
+ store.meta_description = preference_store.get('spree/app_configuration/default_meta_description') {}
+ store.meta_keywords = preference_store.get('spree/app_configuration/default_meta_keywords') {}
+ store.seo_title = preference_store.get('spree/app_configuration/default_seo_title') {}
+ store.save!
+ else
+ # we set defaults for the things we now require
+ Spree::Store.new do |s|
+ s.name = preference_store.get 'spree/app_configuration/site_name' do
+ 'Spree Demo Site'
+ end
+ s.url = preference_store.get 'spree/app_configuration/site_url' do
+ 'demo.spreecommerce.com'
+ end
+ s.mail_from_address = preference_store.get 'spree/app_configuration/mails_from' do
+ 'spree@example.com'
+ end
+
+ s.meta_description = preference_store.get('spree/app_configuration/default_meta_description') {}
+ s.meta_keywords = preference_store.get('spree/app_configuration/default_meta_keywords') {}
+ s.seo_title = preference_store.get('spree/app_configuration/default_seo_title') {}
+ s.default_currency = preference_store.get('spree/app_configuration/currency') {}
+ s.code = 'spree'
+ end.save!
+ end
+ end
+end
diff --git a/core/db/migrate/20140315053743_add_timestamps_to_spree_assets.rb b/core/db/migrate/20140315053743_add_timestamps_to_spree_assets.rb
new file mode 100644
index 00000000000..d636eac65d6
--- /dev/null
+++ b/core/db/migrate/20140315053743_add_timestamps_to_spree_assets.rb
@@ -0,0 +1,6 @@
+class AddTimestampsToSpreeAssets < ActiveRecord::Migration
+ def change
+ add_column :spree_assets, :created_at, :datetime
+ add_column :spree_assets, :updated_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb b/core/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb
new file mode 100644
index 00000000000..e8abb860ecb
--- /dev/null
+++ b/core/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb
@@ -0,0 +1,8 @@
+class CreateSpreeTaxonsPromotionRules < ActiveRecord::Migration
+ def change
+ create_table :spree_taxons_promotion_rules do |t|
+ t.references :taxon, index: true
+ t.references :promotion_rule, index: true
+ end
+ end
+end
diff --git a/core/db/migrate/20140331100557_add_additional_store_fields.rb b/core/db/migrate/20140331100557_add_additional_store_fields.rb
new file mode 100644
index 00000000000..cd9841101ef
--- /dev/null
+++ b/core/db/migrate/20140331100557_add_additional_store_fields.rb
@@ -0,0 +1,8 @@
+class AddAdditionalStoreFields < ActiveRecord::Migration
+ def change
+ add_column :spree_stores, :code, :string unless column_exists?(:spree_stores, :code)
+ add_column :spree_stores, :default, :boolean, default: false, null: false unless column_exists?(:spree_stores, :default)
+ add_index :spree_stores, :code
+ add_index :spree_stores, :default
+ end
+end
diff --git a/core/db/migrate/20140410141842_add_many_missing_indexes.rb b/core/db/migrate/20140410141842_add_many_missing_indexes.rb
new file mode 100644
index 00000000000..b55a7dd1981
--- /dev/null
+++ b/core/db/migrate/20140410141842_add_many_missing_indexes.rb
@@ -0,0 +1,18 @@
+class AddManyMissingIndexes < ActiveRecord::Migration
+ def change
+ add_index :spree_adjustments, [:adjustable_id, :adjustable_type]
+ add_index :spree_adjustments, :eligible
+ add_index :spree_adjustments, :order_id
+ add_index :spree_promotions, :code
+ add_index :spree_promotions, :expires_at
+ add_index :spree_states, :country_id
+ add_index :spree_stock_items, :deleted_at
+ add_index :spree_option_types, :position
+ add_index :spree_option_values, :position
+ add_index :spree_product_option_types, :option_type_id
+ add_index :spree_product_option_types, :product_id
+ add_index :spree_products_taxons, :position
+ add_index :spree_promotions, :starts_at
+ add_index :spree_stores, :url
+ end
+end
diff --git a/core/db/migrate/20140410150358_correct_some_polymorphic_index_and_add_more_missing.rb b/core/db/migrate/20140410150358_correct_some_polymorphic_index_and_add_more_missing.rb
new file mode 100644
index 00000000000..06082b0c6a7
--- /dev/null
+++ b/core/db/migrate/20140410150358_correct_some_polymorphic_index_and_add_more_missing.rb
@@ -0,0 +1,66 @@
+class CorrectSomePolymorphicIndexAndAddMoreMissing < ActiveRecord::Migration
+ def change
+ add_index :spree_addresses, :country_id
+ add_index :spree_addresses, :state_id
+ remove_index :spree_adjustments, [:source_type, :source_id]
+ add_index :spree_adjustments, [:source_id, :source_type]
+ add_index :spree_credit_cards, :address_id
+ add_index :spree_gateways, :active
+ add_index :spree_gateways, :test_mode
+ add_index :spree_inventory_units, :return_authorization_id
+ add_index :spree_line_items, :tax_category_id
+ add_index :spree_log_entries, [:source_id, :source_type]
+ add_index :spree_orders, :approver_id
+ add_index :spree_orders, :bill_address_id
+ add_index :spree_orders, :confirmation_delivered
+ add_index :spree_orders, :considered_risky
+ add_index :spree_orders, :created_by_id
+ add_index :spree_orders, :ship_address_id
+ add_index :spree_orders, :shipping_method_id
+ add_index :spree_orders_promotions, [:order_id, :promotion_id]
+ add_index :spree_payments, [:source_id, :source_type]
+ add_index :spree_prices, :deleted_at
+ add_index :spree_product_option_types, :position
+ add_index :spree_product_properties, :position
+ add_index :spree_product_properties, :property_id
+ add_index :spree_products, :shipping_category_id
+ add_index :spree_products, :tax_category_id
+ add_index :spree_promotion_action_line_items, :promotion_action_id
+ add_index :spree_promotion_action_line_items, :variant_id
+ add_index :spree_promotion_rules, :promotion_id
+ add_index :spree_promotions, :advertise
+ add_index :spree_return_authorizations, :number
+ add_index :spree_return_authorizations, :order_id
+ add_index :spree_return_authorizations, :stock_location_id
+ add_index :spree_shipments, :address_id
+ add_index :spree_shipping_methods, :deleted_at
+ add_index :spree_shipping_methods, :tax_category_id
+ add_index :spree_shipping_rates, :selected
+ add_index :spree_shipping_rates, :tax_rate_id
+ add_index :spree_state_changes, [:stateful_id, :stateful_type]
+ add_index :spree_state_changes, :user_id
+ add_index :spree_stock_items, :backorderable
+ add_index :spree_stock_locations, :active
+ add_index :spree_stock_locations, :backorderable_default
+ add_index :spree_stock_locations, :country_id
+ add_index :spree_stock_locations, :propagate_all_variants
+ add_index :spree_stock_locations, :state_id
+ add_index :spree_tax_categories, :deleted_at
+ add_index :spree_tax_categories, :is_default
+ add_index :spree_tax_rates, :deleted_at
+ add_index :spree_tax_rates, :included_in_price
+ add_index :spree_tax_rates, :show_rate_in_label
+ add_index :spree_tax_rates, :tax_category_id
+ add_index :spree_tax_rates, :zone_id
+ add_index :spree_taxonomies, :position
+ add_index :spree_taxons, :position
+ add_index :spree_trackers, :active
+ add_index :spree_variants, :deleted_at
+ add_index :spree_variants, :is_master
+ add_index :spree_variants, :position
+ add_index :spree_variants, :track_inventory
+ add_index :spree_zone_members, :zone_id
+ add_index :spree_zone_members, [:zoneable_id, :zoneable_type]
+ add_index :spree_zones, :default_tax
+ end
+end
diff --git a/core/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb b/core/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb
new file mode 100644
index 00000000000..e7cf4a53a7d
--- /dev/null
+++ b/core/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb
@@ -0,0 +1,5 @@
+class AddUserIdCreatedByIdIndexToOrder < ActiveRecord::Migration
+ def change
+ add_index :spree_orders, [:user_id, :created_by_id]
+ end
+end
diff --git a/core/db/migrate/20140508151342_change_spree_price_amount_precision.rb b/core/db/migrate/20140508151342_change_spree_price_amount_precision.rb
new file mode 100644
index 00000000000..f68e0961bb3
--- /dev/null
+++ b/core/db/migrate/20140508151342_change_spree_price_amount_precision.rb
@@ -0,0 +1,8 @@
+class ChangeSpreePriceAmountPrecision < ActiveRecord::Migration
+ def change
+ change_column :spree_prices, :amount, :decimal, :precision => 10, :scale => 2
+ change_column :spree_line_items, :price, :decimal, :precision => 10, :scale => 2
+ change_column :spree_line_items, :cost_price, :decimal, :precision => 10, :scale => 2
+ change_column :spree_variants, :cost_price, :decimal, :precision => 10, :scale => 2
+ end
+end
diff --git a/core/db/migrate/20140518174634_add_token_to_spree_orders.rb b/core/db/migrate/20140518174634_add_token_to_spree_orders.rb
new file mode 100644
index 00000000000..7af7e4a0088
--- /dev/null
+++ b/core/db/migrate/20140518174634_add_token_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddTokenToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :guest_token, :string
+ end
+end
diff --git a/core/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb b/core/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb
new file mode 100644
index 00000000000..0e304d04c1b
--- /dev/null
+++ b/core/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb
@@ -0,0 +1,29 @@
+class MoveOrderTokenFromTokenizedPermission < ActiveRecord::Migration
+ class Spree::TokenizedPermission < Spree::Base
+ belongs_to :permissable, polymorphic: true
+ end
+
+ def up
+ case Spree::Order.connection.adapter_name
+ when 'SQLite'
+ Spree::Order.has_one :tokenized_permission, :as => :permissable
+ Spree::Order.includes(:tokenized_permission).each do |o|
+ o.update_column :guest_token, o.tokenized_permission.token
+ end
+ when 'Mysql2', 'MySQL'
+ execute "UPDATE spree_orders, spree_tokenized_permissions
+ SET spree_orders.guest_token = spree_tokenized_permissions.token
+ WHERE spree_tokenized_permissions.permissable_id = spree_orders.id
+ AND spree_tokenized_permissions.permissable_type = 'Spree::Order'"
+ else
+ execute "UPDATE spree_orders
+ SET guest_token = spree_tokenized_permissions.token
+ FROM spree_tokenized_permissions
+ WHERE spree_tokenized_permissions.permissable_id = spree_orders.id
+ AND spree_tokenized_permissions.permissable_type = 'Spree::Order'"
+ end
+ end
+
+ def down
+ end
+end
diff --git a/core/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb b/core/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb
new file mode 100644
index 00000000000..451b5fb41b5
--- /dev/null
+++ b/core/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb
@@ -0,0 +1,10 @@
+class SetShipmentTotalForUsersUpgrading < ActiveRecord::Migration
+ def up
+ # NOTE You might not need this at all unless you're upgrading from Spree 2.1.x
+ # or below. For those upgrading this should populate the Order#shipment_total
+ # for legacy orders
+ Spree::Order.complete.where('shipment_total = ?', 0).includes(:shipments).find_each do |order|
+ order.update_column(:shipment_total, order.shipments.sum(:cost))
+ end
+ end
+end
diff --git a/core/db/migrate/20140604135309_drop_credit_card_first_name_and_last_name.rb b/core/db/migrate/20140604135309_drop_credit_card_first_name_and_last_name.rb
new file mode 100644
index 00000000000..c77f648da53
--- /dev/null
+++ b/core/db/migrate/20140604135309_drop_credit_card_first_name_and_last_name.rb
@@ -0,0 +1,6 @@
+class DropCreditCardFirstNameAndLastName < ActiveRecord::Migration
+ def change
+ remove_column :spree_credit_cards, :first_name, :string
+ remove_column :spree_credit_cards, :last_name, :string
+ end
+end
diff --git a/core/db/migrate/20140609201656_add_deleted_at_to_spree_promotion_actions.rb b/core/db/migrate/20140609201656_add_deleted_at_to_spree_promotion_actions.rb
new file mode 100644
index 00000000000..c8e9a353659
--- /dev/null
+++ b/core/db/migrate/20140609201656_add_deleted_at_to_spree_promotion_actions.rb
@@ -0,0 +1,6 @@
+class AddDeletedAtToSpreePromotionActions < ActiveRecord::Migration
+ def change
+ add_column :spree_promotion_actions, :deleted_at, :datetime
+ add_index :spree_promotion_actions, :deleted_at
+ end
+end
diff --git a/core/db/migrate/20140616202624_remove_uncaptured_amount_from_spree_payments.rb b/core/db/migrate/20140616202624_remove_uncaptured_amount_from_spree_payments.rb
new file mode 100644
index 00000000000..bd73332338e
--- /dev/null
+++ b/core/db/migrate/20140616202624_remove_uncaptured_amount_from_spree_payments.rb
@@ -0,0 +1,5 @@
+class RemoveUncapturedAmountFromSpreePayments < ActiveRecord::Migration
+ def change
+ remove_column :spree_payments, :uncaptured_amount
+ end
+end
diff --git a/core/db/migrate/20140625214618_create_spree_refunds.rb b/core/db/migrate/20140625214618_create_spree_refunds.rb
new file mode 100644
index 00000000000..deec7b4bb6a
--- /dev/null
+++ b/core/db/migrate/20140625214618_create_spree_refunds.rb
@@ -0,0 +1,12 @@
+class CreateSpreeRefunds < ActiveRecord::Migration
+ def change
+ create_table :spree_refunds do |t|
+ t.integer :payment_id
+ t.integer :return_authorization_id
+ t.decimal :amount, precision: 10, scale: 2, default: 0.0, null: false
+ t.string :transaction_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/core/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb b/core/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb
new file mode 100644
index 00000000000..6a4981d996f
--- /dev/null
+++ b/core/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb
@@ -0,0 +1,12 @@
+class CreateSpreeReturnAuthorizationInventoryUnit < ActiveRecord::Migration
+ def change
+ create_table :spree_return_authorization_inventory_units do |t|
+ t.integer :return_authorization_id
+ t.integer :inventory_unit_id
+ t.integer :exchange_variant_id
+ t.datetime :received_at
+
+ t.timestamps
+ end
+ end
+end
diff --git a/core/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb b/core/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb
new file mode 100644
index 00000000000..2c23ab106bc
--- /dev/null
+++ b/core/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb
@@ -0,0 +1,5 @@
+class RenameReturnAuthorizationInventoryUnitToReturnItems < ActiveRecord::Migration
+ def change
+ rename_table :spree_return_authorization_inventory_units, :spree_return_items
+ end
+end
diff --git a/core/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb b/core/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb
new file mode 100644
index 00000000000..9ec679db28d
--- /dev/null
+++ b/core/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb
@@ -0,0 +1,10 @@
+class BackfillLineItemPreTaxAmount < ActiveRecord::Migration
+ def change
+ # set pre_tax_amount to discounted_amount - included_tax_total
+ execute(<<-SQL)
+ UPDATE spree_line_items
+ SET pre_tax_amount = ((price * quantity) + promo_total) - included_tax_total
+ WHERE pre_tax_amount IS NULL;
+ SQL
+ end
+end
diff --git a/core/db/migrate/20140710041921_recreate_spree_return_authorizations.rb b/core/db/migrate/20140710041921_recreate_spree_return_authorizations.rb
new file mode 100644
index 00000000000..ec2b98971c1
--- /dev/null
+++ b/core/db/migrate/20140710041921_recreate_spree_return_authorizations.rb
@@ -0,0 +1,55 @@
+class RecreateSpreeReturnAuthorizations < ActiveRecord::Migration
+ def up
+ # If the app has any legacy return authorizations then rename the table & columns and leave them there
+ # for the spree_legacy_return_authorizations extension to pick up with.
+ # Otherwise just drop the tables/columns as they are no longer used in stock spree. The spree_legacy_return_authorizations
+ # extension will recreate these tables for dev environments & etc as needed.
+ if Spree::ReturnAuthorization.exists?
+ rename_table :spree_return_authorizations, :spree_legacy_return_authorizations
+ rename_column :spree_inventory_units, :return_authorization_id, :legacy_return_authorization_id
+ else
+ drop_table :spree_return_authorizations
+ remove_column :spree_inventory_units, :return_authorization_id
+ end
+
+ Spree::Adjustment.where(source_type: 'Spree::ReturnAuthorization').update_all(source_type: 'Spree::LegacyReturnAuthorization')
+
+ # For now just recreate the table as it was. Future changes to the schema (including dropping "amount") will be coming in a
+ # separate commit.
+ create_table :spree_return_authorizations do |t|
+ t.string "number"
+ t.string "state"
+ t.decimal "amount", precision: 10, scale: 2, default: 0.0, null: false
+ t.integer "order_id"
+ t.text "reason"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "stock_location_id"
+ end
+
+ end
+
+ def down
+ drop_table :spree_return_authorizations
+
+ Spree::Adjustment.where(source_type: 'Spree::LegacyReturnAuthorization').update_all(source_type: 'Spree::ReturnAuthorization')
+
+ if table_exists?(:spree_legacy_return_authorizations)
+ rename_table :spree_legacy_return_authorizations, :spree_return_authorizations
+ rename_column :spree_inventory_units, :legacy_return_authorization_id, :return_authorization_id
+ else
+ create_table :spree_return_authorizations do |t|
+ t.string "number"
+ t.string "state"
+ t.decimal "amount", precision: 10, scale: 2, default: 0.0, null: false
+ t.integer "order_id"
+ t.text "reason"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "stock_location_id"
+ end
+ add_column :spree_inventory_units, :return_authorization_id, :integer, after: :shipment_id
+ add_index :spree_inventory_units, :return_authorization_id
+ end
+ end
+end
diff --git a/core/db/migrate/20140710181204_add_amount_fields_to_return_items.rb b/core/db/migrate/20140710181204_add_amount_fields_to_return_items.rb
new file mode 100644
index 00000000000..6a12c8810f6
--- /dev/null
+++ b/core/db/migrate/20140710181204_add_amount_fields_to_return_items.rb
@@ -0,0 +1,7 @@
+class AddAmountFieldsToReturnItems < ActiveRecord::Migration
+ def change
+ add_column :spree_return_items, :pre_tax_amount, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ add_column :spree_return_items, :included_tax_total, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ add_column :spree_return_items, :additional_tax_total, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ end
+end
diff --git a/core/db/migrate/20140710190048_drop_return_authorization_amount.rb b/core/db/migrate/20140710190048_drop_return_authorization_amount.rb
new file mode 100644
index 00000000000..42764c9486e
--- /dev/null
+++ b/core/db/migrate/20140710190048_drop_return_authorization_amount.rb
@@ -0,0 +1,5 @@
+class DropReturnAuthorizationAmount < ActiveRecord::Migration
+ def change
+ remove_column :spree_return_authorizations, :amount
+ end
+end
diff --git a/core/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb b/core/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb
new file mode 100644
index 00000000000..a6da1d62259
--- /dev/null
+++ b/core/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb
@@ -0,0 +1,28 @@
+class CreateSpreeReturnAuthorizationReasons < ActiveRecord::Migration
+ def change
+ create_table :spree_return_authorization_reasons do |t|
+ t.string :name
+ t.boolean :active, default: true
+ t.boolean :mutable, default: true
+
+ t.timestamps
+ end
+
+ reversible do |direction|
+ direction.up do
+ Spree::ReturnAuthorizationReason.create!(name: 'Better price available')
+ Spree::ReturnAuthorizationReason.create!(name: 'Missed estimated delivery date')
+ Spree::ReturnAuthorizationReason.create!(name: 'Missing parts or accessories')
+ Spree::ReturnAuthorizationReason.create!(name: 'Damaged/Defective')
+ Spree::ReturnAuthorizationReason.create!(name: 'Different from what was ordered')
+ Spree::ReturnAuthorizationReason.create!(name: 'Different from description')
+ Spree::ReturnAuthorizationReason.create!(name: 'No longer needed/wanted')
+ Spree::ReturnAuthorizationReason.create!(name: 'Accidental order')
+ Spree::ReturnAuthorizationReason.create!(name: 'Unauthorized purchase')
+ end
+ end
+
+ add_column :spree_return_authorizations, :return_authorization_reason_id, :integer
+ add_index :spree_return_authorizations, :return_authorization_reason_id, name: 'index_return_authorizations_on_return_authorization_reason_id'
+ end
+end
diff --git a/core/db/migrate/20140713140527_create_spree_refund_reasons.rb b/core/db/migrate/20140713140527_create_spree_refund_reasons.rb
new file mode 100644
index 00000000000..1d62143d381
--- /dev/null
+++ b/core/db/migrate/20140713140527_create_spree_refund_reasons.rb
@@ -0,0 +1,14 @@
+class CreateSpreeRefundReasons < ActiveRecord::Migration
+ def change
+ create_table :spree_refund_reasons do |t|
+ t.string :name
+ t.boolean :active, default: true
+ t.boolean :mutable, default: true
+
+ t.timestamps
+ end
+
+ add_column :spree_refunds, :refund_reason_id, :integer
+ add_index :spree_refunds, :refund_reason_id, name: 'index_refunds_on_refund_reason_id'
+ end
+end
diff --git a/core/db/migrate/20140713142214_rename_return_authorization_reason.rb b/core/db/migrate/20140713142214_rename_return_authorization_reason.rb
new file mode 100644
index 00000000000..06dc3a066e9
--- /dev/null
+++ b/core/db/migrate/20140713142214_rename_return_authorization_reason.rb
@@ -0,0 +1,5 @@
+class RenameReturnAuthorizationReason < ActiveRecord::Migration
+ def change
+ rename_column :spree_return_authorizations, :reason, :memo
+ end
+end
diff --git a/core/db/migrate/20140715182625_create_spree_promotion_categories.rb b/core/db/migrate/20140715182625_create_spree_promotion_categories.rb
new file mode 100644
index 00000000000..8441394480e
--- /dev/null
+++ b/core/db/migrate/20140715182625_create_spree_promotion_categories.rb
@@ -0,0 +1,11 @@
+class CreateSpreePromotionCategories < ActiveRecord::Migration
+ def change
+ create_table :spree_promotion_categories do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ add_column :spree_promotions, :promotion_category_id, :integer
+ add_index :spree_promotions, :promotion_category_id
+ end
+end
diff --git a/core/db/migrate/20140716204111_drop_received_at_on_return_items.rb b/core/db/migrate/20140716204111_drop_received_at_on_return_items.rb
new file mode 100644
index 00000000000..3388173aea5
--- /dev/null
+++ b/core/db/migrate/20140716204111_drop_received_at_on_return_items.rb
@@ -0,0 +1,9 @@
+class DropReceivedAtOnReturnItems < ActiveRecord::Migration
+ def up
+ remove_column :spree_return_items, :received_at
+ end
+
+ def down
+ add_column :spree_return_items, :received_at, :datetime
+ end
+end
diff --git a/core/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb b/core/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb
new file mode 100644
index 00000000000..6e2a1a17e2c
--- /dev/null
+++ b/core/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb
@@ -0,0 +1,6 @@
+class AddReceptionAndAcceptanceStatusToReturnItems < ActiveRecord::Migration
+ def change
+ add_column :spree_return_items, :reception_status, :string
+ add_column :spree_return_items, :acceptance_status, :string
+ end
+end
diff --git a/core/db/migrate/20140717155155_create_default_refund_reason.rb b/core/db/migrate/20140717155155_create_default_refund_reason.rb
new file mode 100644
index 00000000000..76aecf9d6d8
--- /dev/null
+++ b/core/db/migrate/20140717155155_create_default_refund_reason.rb
@@ -0,0 +1,9 @@
+class CreateDefaultRefundReason < ActiveRecord::Migration
+ def up
+ Spree::RefundReason.create!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false)
+ end
+
+ def down
+ Spree::RefundReason.find_by(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false).destroy
+ end
+end
diff --git a/core/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb b/core/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb
new file mode 100644
index 00000000000..71f5ca13cd8
--- /dev/null
+++ b/core/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb
@@ -0,0 +1,7 @@
+class AddDefaultToSpreeStockLocations < ActiveRecord::Migration
+ def change
+ unless column_exists? :spree_stock_locations, :default
+ add_column :spree_stock_locations, :default, :boolean, null: false, default: false
+ end
+ end
+end
diff --git a/core/db/migrate/20140718133010_create_spree_customer_returns.rb b/core/db/migrate/20140718133010_create_spree_customer_returns.rb
new file mode 100644
index 00000000000..6caf95ba9c1
--- /dev/null
+++ b/core/db/migrate/20140718133010_create_spree_customer_returns.rb
@@ -0,0 +1,9 @@
+class CreateSpreeCustomerReturns < ActiveRecord::Migration
+ def change
+ create_table :spree_customer_returns do |t|
+ t.string :number
+ t.integer :stock_location_id
+ t.timestamps
+ end
+ end
+end
diff --git a/core/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb b/core/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb
new file mode 100644
index 00000000000..094276cc9df
--- /dev/null
+++ b/core/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb
@@ -0,0 +1,6 @@
+class AddCustomerReturnIdToReturnItem < ActiveRecord::Migration
+ def change
+ add_column :spree_return_items, :customer_return_id, :integer
+ add_index :spree_return_items, :customer_return_id, name: 'index_return_items_on_customer_return_id'
+ end
+end
diff --git a/core/db/migrate/20140718195325_create_friendly_id_slugs.rb b/core/db/migrate/20140718195325_create_friendly_id_slugs.rb
new file mode 100644
index 00000000000..770f626446b
--- /dev/null
+++ b/core/db/migrate/20140718195325_create_friendly_id_slugs.rb
@@ -0,0 +1,15 @@
+class CreateFriendlyIdSlugs < ActiveRecord::Migration
+ def change
+ create_table :friendly_id_slugs do |t|
+ t.string :slug, :null => false
+ t.integer :sluggable_id, :null => false
+ t.string :sluggable_type, :limit => 50
+ t.string :scope
+ t.datetime :created_at
+ end
+ add_index :friendly_id_slugs, :sluggable_id
+ add_index :friendly_id_slugs, [:slug, :sluggable_type]
+ add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
+ add_index :friendly_id_slugs, :sluggable_type
+ end
+end
diff --git a/core/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb b/core/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb
new file mode 100644
index 00000000000..1373514492f
--- /dev/null
+++ b/core/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb
@@ -0,0 +1,5 @@
+class RenameSpreeRefundReturnAuthorizationId < ActiveRecord::Migration
+ def change
+ rename_column :spree_refunds, :return_authorization_id, :customer_return_id
+ end
+end
diff --git a/core/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb b/core/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb
new file mode 100644
index 00000000000..dc48900b86a
--- /dev/null
+++ b/core/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb
@@ -0,0 +1,13 @@
+class IncreaseReturnItemPreTaxAmountPrecision < ActiveRecord::Migration
+ def up
+ change_column :spree_return_items, :pre_tax_amount, :decimal, precision: 12, scale: 4, default: 0.0, null: false
+ change_column :spree_return_items, :included_tax_total, :decimal, precision: 12, scale: 4, default: 0.0, null: false
+ change_column :spree_return_items, :additional_tax_total, :decimal, precision: 12, scale: 4, default: 0.0, null: false
+ end
+
+ def down
+ change_column :spree_return_items, :pre_tax_amount, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ change_column :spree_return_items, :included_tax_total, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ change_column :spree_return_items, :additional_tax_total, :decimal, precision: 10, scale: 2, default: 0.0, null: false
+ end
+end
diff --git a/core/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb b/core/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb
new file mode 100644
index 00000000000..c7df6b4da7a
--- /dev/null
+++ b/core/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb
@@ -0,0 +1,15 @@
+class CopyProductSlugsToSlugHistory < ActiveRecord::Migration
+ def change
+
+ # do what sql does best: copy all slugs into history table in a single query
+ # rather than load potentially millions of products into memory
+ Spree::Product.connection.execute <<-SQL
+INSERT INTO #{FriendlyId::Slug.table_name} (slug, sluggable_id, sluggable_type, created_at)
+ SELECT slug, id, '#{Spree::Product.to_s}', #{ActiveRecord::Base.send(:sanitize_sql_array, ['?', Time.current])}
+ FROM #{Spree::Product.table_name}
+ WHERE slug IS NOT NULL
+ ORDER BY id
+SQL
+
+ end
+end
diff --git a/core/db/migrate/20140725131539_create_spree_reimbursements.rb b/core/db/migrate/20140725131539_create_spree_reimbursements.rb
new file mode 100644
index 00000000000..e77d24e061b
--- /dev/null
+++ b/core/db/migrate/20140725131539_create_spree_reimbursements.rb
@@ -0,0 +1,21 @@
+class CreateSpreeReimbursements < ActiveRecord::Migration
+ def change
+ create_table :spree_reimbursements do |t|
+ t.string :number
+ t.string :reimbursement_status
+ t.integer :customer_return_id
+ t.integer :order_id
+ t.decimal :total, precision: 10, scale: 2
+
+ t.timestamps
+ end
+
+ add_index :spree_reimbursements, :customer_return_id
+ add_index :spree_reimbursements, :order_id
+
+ remove_column :spree_refunds, :customer_return_id, :integer
+ add_column :spree_refunds, :reimbursement_id, :integer
+
+ add_column :spree_return_items, :reimbursement_id, :integer
+ end
+end
diff --git a/core/db/migrate/20140728225422_add_promotionable_to_spree_products.rb b/core/db/migrate/20140728225422_add_promotionable_to_spree_products.rb
new file mode 100644
index 00000000000..5c5606bf6ca
--- /dev/null
+++ b/core/db/migrate/20140728225422_add_promotionable_to_spree_products.rb
@@ -0,0 +1,5 @@
+class AddPromotionableToSpreeProducts < ActiveRecord::Migration
+ def change
+ add_column :spree_products, :promotionable, :boolean, default: true
+ end
+end
diff --git a/core/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb b/core/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb
new file mode 100644
index 00000000000..e0c0f9035dd
--- /dev/null
+++ b/core/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb
@@ -0,0 +1,7 @@
+class AddExchangeInventoryUnitForeignKeys < ActiveRecord::Migration
+ def change
+ add_column :spree_return_items, :exchange_inventory_unit_id, :integer
+
+ add_index :spree_return_items, :exchange_inventory_unit_id
+ end
+end
diff --git a/core/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb b/core/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb
new file mode 100644
index 00000000000..31fcb47c2a9
--- /dev/null
+++ b/core/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb
@@ -0,0 +1,5 @@
+class AddAcceptanceStatusErrorsToReturnItem < ActiveRecord::Migration
+ def change
+ add_column :spree_return_items, :acceptance_status_errors, :text
+ end
+end
diff --git a/core/db/migrate/20140731150017_create_spree_reimbursement_types.rb b/core/db/migrate/20140731150017_create_spree_reimbursement_types.rb
new file mode 100644
index 00000000000..f1cd2095c8f
--- /dev/null
+++ b/core/db/migrate/20140731150017_create_spree_reimbursement_types.rb
@@ -0,0 +1,20 @@
+class CreateSpreeReimbursementTypes < ActiveRecord::Migration
+ def change
+ create_table :spree_reimbursement_types do |t|
+ t.string :name
+ t.boolean :active, default: true
+ t.boolean :mutable, default: true
+
+ t.timestamps
+ end
+
+ reversible do |direction|
+ direction.up do
+ Spree::ReimbursementType.create!(name: Spree::ReimbursementType::ORIGINAL)
+ end
+ end
+
+ add_column :spree_return_items, :preferred_reimbursement_type_id, :integer
+ add_column :spree_return_items, :override_reimbursement_type_id, :integer
+ end
+end
diff --git a/core/db/migrate/20140804185157_add_default_to_shipment_cost.rb b/core/db/migrate/20140804185157_add_default_to_shipment_cost.rb
new file mode 100644
index 00000000000..8106f43a832
--- /dev/null
+++ b/core/db/migrate/20140804185157_add_default_to_shipment_cost.rb
@@ -0,0 +1,10 @@
+class AddDefaultToShipmentCost < ActiveRecord::Migration
+ def up
+ change_column :spree_shipments, :cost, :decimal, precision: 10, scale: 2, default: 0.0
+ Spree::Shipment.where(cost: nil).update_all(cost: 0)
+ end
+
+ def down
+ change_column :spree_shipments, :cost, :decimal, precision: 10, scale: 2
+ end
+end
diff --git a/core/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb b/core/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb
new file mode 100644
index 00000000000..3815970f3d7
--- /dev/null
+++ b/core/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb
@@ -0,0 +1,5 @@
+class AddDefaultToSpreeCreditCards < ActiveRecord::Migration
+ def change
+ add_column :spree_credit_cards, :default, :boolean, null: false, default: false
+ end
+end
diff --git a/core/db/migrate/20140805171219_make_existing_credit_cards_default.rb b/core/db/migrate/20140805171219_make_existing_credit_cards_default.rb
new file mode 100644
index 00000000000..5d2e3735bd1
--- /dev/null
+++ b/core/db/migrate/20140805171219_make_existing_credit_cards_default.rb
@@ -0,0 +1,10 @@
+class MakeExistingCreditCardsDefault < ActiveRecord::Migration
+ def up
+ # set the newest credit card for every user to be the default; SQL technique from
+ # http://stackoverflow.com/questions/121387/fetch-the-row-which-has-the-max-value-for-a-column
+ Spree::CreditCard.where.not(user_id: nil).joins("LEFT OUTER JOIN spree_credit_cards cc2 ON cc2.user_id = spree_credit_cards.user_id AND spree_credit_cards.created_at < cc2.created_at").where("cc2.user_id IS NULL").update_all(default: true)
+ end
+ def down
+ # do nothing
+ end
+end
diff --git a/core/db/migrate/20140806144901_add_type_to_reimbursement_type.rb b/core/db/migrate/20140806144901_add_type_to_reimbursement_type.rb
new file mode 100644
index 00000000000..2e47e16901f
--- /dev/null
+++ b/core/db/migrate/20140806144901_add_type_to_reimbursement_type.rb
@@ -0,0 +1,9 @@
+class AddTypeToReimbursementType < ActiveRecord::Migration
+ def change
+ add_column :spree_reimbursement_types, :type, :string
+ add_index :spree_reimbursement_types, :type
+
+ Spree::ReimbursementType.reset_column_information
+ Spree::ReimbursementType.find_by(name: Spree::ReimbursementType::ORIGINAL).update_attributes!(type: 'Spree::ReimbursementType::OriginalPayment')
+ end
+end
diff --git a/core/db/migrate/20140808184039_create_spree_reimbursement_credits.rb b/core/db/migrate/20140808184039_create_spree_reimbursement_credits.rb
new file mode 100644
index 00000000000..559a6b58c35
--- /dev/null
+++ b/core/db/migrate/20140808184039_create_spree_reimbursement_credits.rb
@@ -0,0 +1,10 @@
+class CreateSpreeReimbursementCredits < ActiveRecord::Migration
+ def change
+ create_table :spree_reimbursement_credits do |t|
+ t.decimal :amount, precision: 10, scale: 2, default: 0.0, null: false
+ t.integer :reimbursement_id
+ t.integer :creditable_id
+ t.string :creditable_type
+ end
+ end
+end
diff --git a/core/db/migrate/20140827170513_add_meta_title_to_spree_products.rb b/core/db/migrate/20140827170513_add_meta_title_to_spree_products.rb
new file mode 100644
index 00000000000..4a0b513fbe2
--- /dev/null
+++ b/core/db/migrate/20140827170513_add_meta_title_to_spree_products.rb
@@ -0,0 +1,7 @@
+class AddMetaTitleToSpreeProducts < ActiveRecord::Migration
+ def change
+ change_table :spree_products do |t|
+ t.string :meta_title
+ end
+ end
+end
diff --git a/core/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb b/core/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb
new file mode 100644
index 00000000000..2f6f7088786
--- /dev/null
+++ b/core/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb
@@ -0,0 +1,5 @@
+class AddCodeToSpreeTaxCategories < ActiveRecord::Migration
+ def change
+ add_column :spree_tax_categories, :tax_code, :string
+ end
+end
diff --git a/core/db/migrate/20140927193717_default_pre_tax_amount_should_be_zero.rb b/core/db/migrate/20140927193717_default_pre_tax_amount_should_be_zero.rb
new file mode 100644
index 00000000000..5e0050af46c
--- /dev/null
+++ b/core/db/migrate/20140927193717_default_pre_tax_amount_should_be_zero.rb
@@ -0,0 +1,6 @@
+class DefaultPreTaxAmountShouldBeZero < ActiveRecord::Migration
+ def change
+ change_column :spree_line_items, :pre_tax_amount, :decimal, precision: 8, scale: 2, default: 0
+ change_column :spree_shipments, :pre_tax_amount, :decimal, precision: 8, scale: 2, default: 0
+ end
+end
diff --git a/core/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb b/core/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb
new file mode 100644
index 00000000000..9ea9f37c564
--- /dev/null
+++ b/core/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb
@@ -0,0 +1,5 @@
+class AddCodeToSpreeShippingMethods < ActiveRecord::Migration
+ def change
+ add_column :spree_shipping_methods, :code, :string
+ end
+end
diff --git a/core/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb b/core/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb
new file mode 100644
index 00000000000..ad974a9f7d1
--- /dev/null
+++ b/core/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb
@@ -0,0 +1,6 @@
+class AddCancelAuditFieldsToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :canceled_at, :datetime
+ add_column :spree_orders, :canceler_id, :integer
+ end
+end
diff --git a/core/db/migrate/20141009204607_add_store_id_to_orders.rb b/core/db/migrate/20141009204607_add_store_id_to_orders.rb
new file mode 100644
index 00000000000..548c6e1b4df
--- /dev/null
+++ b/core/db/migrate/20141009204607_add_store_id_to_orders.rb
@@ -0,0 +1,8 @@
+class AddStoreIdToOrders < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :store_id, :integer
+ if Spree::Store.default.persisted?
+ Spree::Order.where(store_id: nil).update_all(store_id: Spree::Store.default.id)
+ end
+ end
+end
diff --git a/core/db/migrate/20141012083513_create_spree_taxons_prototypes.rb b/core/db/migrate/20141012083513_create_spree_taxons_prototypes.rb
new file mode 100644
index 00000000000..46f988539ef
--- /dev/null
+++ b/core/db/migrate/20141012083513_create_spree_taxons_prototypes.rb
@@ -0,0 +1,8 @@
+class CreateSpreeTaxonsPrototypes < ActiveRecord::Migration
+ def change
+ create_table :spree_taxons_prototypes do |t|
+ t.belongs_to :taxon, index: true
+ t.belongs_to :prototype, index: true
+ end
+ end
+end
diff --git a/core/db/migrate/20141021194502_add_state_lock_version_to_order.rb b/core/db/migrate/20141021194502_add_state_lock_version_to_order.rb
new file mode 100644
index 00000000000..92582c50011
--- /dev/null
+++ b/core/db/migrate/20141021194502_add_state_lock_version_to_order.rb
@@ -0,0 +1,5 @@
+class AddStateLockVersionToOrder < ActiveRecord::Migration
+ def change
+ add_column :spree_orders, :state_lock_version, :integer, default: 0, null: false
+ end
+end
diff --git a/core/db/migrate/20141023005240_add_counter_cache_from_spree_variants_to_spree_stock_items.rb b/core/db/migrate/20141023005240_add_counter_cache_from_spree_variants_to_spree_stock_items.rb
new file mode 100644
index 00000000000..fe8fece0f4d
--- /dev/null
+++ b/core/db/migrate/20141023005240_add_counter_cache_from_spree_variants_to_spree_stock_items.rb
@@ -0,0 +1,13 @@
+class AddCounterCacheFromSpreeVariantsToSpreeStockItems < ActiveRecord::Migration
+ def up
+ add_column :spree_variants, :stock_items_count, :integer, default: 0, null: false
+
+ Spree::Variant.find_each do |variant|
+ Spree::Variant.reset_counters(variant.id, :stock_items)
+ end
+ end
+
+ def down
+ remove_column :spree_variants, :stock_items_count
+ end
+end
diff --git a/core/db/migrate/20141101231208_fix_adjustment_order_presence.rb b/core/db/migrate/20141101231208_fix_adjustment_order_presence.rb
new file mode 100644
index 00000000000..fbca7355cd4
--- /dev/null
+++ b/core/db/migrate/20141101231208_fix_adjustment_order_presence.rb
@@ -0,0 +1,13 @@
+class FixAdjustmentOrderPresence < ActiveRecord::Migration
+ def change
+ say 'Fixing adjustments without direct order reference'
+ Spree::Adjustment.where(order: nil).find_each do |adjustment|
+ adjustable = adjustment.adjustable
+ if adjustable.is_a? Spree::Order
+ adjustment.update_attributes!(order_id: adjustable.id)
+ else
+ adjustment.update_attributes!(adjustable: adjustable.order)
+ end
+ end
+ end
+end
diff --git a/core/db/migrate/20141105213646_update_classifications_positions.rb b/core/db/migrate/20141105213646_update_classifications_positions.rb
new file mode 100644
index 00000000000..c1d64794eb3
--- /dev/null
+++ b/core/db/migrate/20141105213646_update_classifications_positions.rb
@@ -0,0 +1,9 @@
+class UpdateClassificationsPositions < ActiveRecord::Migration
+ def up
+ Spree::Taxon.all.each do |taxon|
+ taxon.classifications.each_with_index do |c12n, i|
+ c12n.set_list_position(i + 1)
+ end
+ end
+ end
+end
diff --git a/core/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb b/core/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb
new file mode 100644
index 00000000000..90ea9edb009
--- /dev/null
+++ b/core/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb
@@ -0,0 +1,5 @@
+class AddGuestTokenIndexToSpreeOrders < ActiveRecord::Migration
+ def change
+ add_index :spree_orders, :guest_token
+ end
+end
diff --git a/core/db/migrate/20150515211137_fix_adjustment_order_id.rb b/core/db/migrate/20150515211137_fix_adjustment_order_id.rb
new file mode 100644
index 00000000000..4988887dde3
--- /dev/null
+++ b/core/db/migrate/20150515211137_fix_adjustment_order_id.rb
@@ -0,0 +1,70 @@
+class FixAdjustmentOrderId < ActiveRecord::Migration
+ def change
+ say 'Populate order_id from adjustable_id where appropriate'
+ execute(<<-SQL.squish)
+ UPDATE
+ spree_adjustments
+ SET
+ order_id = adjustable_id
+ WHERE
+ adjustable_type = 'Spree::Order'
+ ;
+ SQL
+
+ # Submitter of change does not care about MySQL, as it is not officially supported.
+ # Still spree officials decided to provide a working code path for MySQL users, hence
+ # submitter made a AR code path he could validate on PostgreSQL.
+ #
+ # Whoever runs a big enough MySQL installation where the AR solution hurts:
+ # Will have to write a better MySQL specific equivalent.
+ if Spree::Order.connection.adapter_name.eql?('MySQL')
+ Spree::Adjustment.where(adjustable_type: 'Spree::LineItem').find_each do |adjustment|
+ adjustment.update_columns(order_id: Spree::LineItem.find(adjustment.adjustable_id).order_id)
+ end
+ else
+ execute(<<-SQL.squish)
+ UPDATE
+ spree_adjustments
+ SET
+ order_id =
+ (SELECT order_id FROM spree_line_items WHERE spree_line_items.id = spree_adjustments.adjustable_id)
+ WHERE
+ adjustable_type = 'Spree::LineItem'
+ SQL
+ end
+
+ say 'Fix schema for spree_adjustments order_id column'
+ change_table :spree_adjustments do |t|
+ t.change :order_id, :integer, null: false
+ end
+
+ # Improved schema for postgresql, uncomment if you like it:
+ #
+ # # Negated Logical implication.
+ # #
+ # # When adjustable_type is 'Spree::Order' (p) the adjustable_id must be order_id (q).
+ # #
+ # # When adjustable_type is NOT 'Spree::Order' the adjustable id allowed to be any value (including of order_id in
+ # # case foreign keys match). XOR does not work here.
+ # #
+ # # Postgresql does not have an operator for logical implication. So we need to build the following truth table
+ # # via AND with OR:
+ # #
+ # # p q | CHECK = !(p -> q)
+ # # -----------
+ # # t t | t
+ # # t f | f
+ # # f t | t
+ # # f f | t
+ # #
+ # # According to de-morgans law the logical implication q -> p is equivalent to !p || q
+ # #
+ # execute(<<-SQL.squish)
+ # ALTER TABLE ONLY spree_adjustments
+ # ADD CONSTRAINT fk_spree_adjustments FOREIGN KEY (order_id)
+ # REFERENCES spree_orders(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
+ # ADD CONSTRAINT check_spree_adjustments_order_id CHECK
+ # (adjustable_type <> 'Spree::Order' OR order_id = adjustable_id);
+ # SQL
+ end
+end
diff --git a/core/db/migrate/20150522181728_add_deleted_at_to_friendly_id_slugs.rb b/core/db/migrate/20150522181728_add_deleted_at_to_friendly_id_slugs.rb
new file mode 100644
index 00000000000..14add83a485
--- /dev/null
+++ b/core/db/migrate/20150522181728_add_deleted_at_to_friendly_id_slugs.rb
@@ -0,0 +1,6 @@
+class AddDeletedAtToFriendlyIdSlugs < ActiveRecord::Migration
+ def change
+ add_column :friendly_id_slugs, :deleted_at, :datetime
+ add_index :friendly_id_slugs, :deleted_at
+ end
+end
diff --git a/core/db/migrate/20150707204155_enable_acts_as_paranoid_on_calculators.rb b/core/db/migrate/20150707204155_enable_acts_as_paranoid_on_calculators.rb
new file mode 100644
index 00000000000..6e7a7db367c
--- /dev/null
+++ b/core/db/migrate/20150707204155_enable_acts_as_paranoid_on_calculators.rb
@@ -0,0 +1,6 @@
+class EnableActsAsParanoidOnCalculators < ActiveRecord::Migration
+ def change
+ add_column :spree_calculators, :deleted_at, :datetime
+ add_index :spree_calculators, :deleted_at
+ end
+end
diff --git a/core/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt b/core/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt
index eb8d5c6b2ec..03b6c095877 100644
--- a/core/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt
+++ b/core/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt
@@ -1,15 +1,20 @@
module Spree
- module AuthenticationHelpers
+ module CurrentUserHelpers
def self.included(receiver)
- receiver.send :helper_method, :spree_login_path
- receiver.send :helper_method, :spree_signup_path
- receiver.send :helper_method, :spree_logout_path
receiver.send :helper_method, :spree_current_user
end
def spree_current_user
current_user
end
+ end
+
+ module AuthenticationHelpers
+ def self.included(receiver)
+ receiver.send :helper_method, :spree_login_path
+ receiver.send :helper_method, :spree_signup_path
+ receiver.send :helper_method, :spree_logout_path
+ end
def spree_login_path
main_app.login_path
@@ -26,3 +31,6 @@ module Spree
end
ApplicationController.send :include, Spree::AuthenticationHelpers
+ApplicationController.send :include, Spree::CurrentUserHelpers
+
+Spree::Api::BaseController.send :include, Spree::CurrentUserHelpers
diff --git a/core/lib/generators/spree/dummy/dummy_generator.rb b/core/lib/generators/spree/dummy/dummy_generator.rb
index 178821c2bec..58a3c6f00b6 100644
--- a/core/lib/generators/spree/dummy/dummy_generator.rb
+++ b/core/lib/generators/spree/dummy/dummy_generator.rb
@@ -1,4 +1,6 @@
require "rails/generators/rails/app/app_generator"
+require 'active_support/core_ext/hash'
+require 'spree/core/version'
module Spree
class DummyGenerator < Rails::Generators::Base
@@ -22,7 +24,9 @@ def clean_up
]
def generate_test_dummy
- opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
+ # calling slice on a Thor::CoreExtensions::HashWithIndifferentAccess
+ # object has been known to return nil
+ opts = {}.merge(options).slice(*PASSTHROUGH_OPTIONS)
opts[:database] = 'sqlite3' if opts[:database].blank?
opts[:force] = true
opts[:skip_bundle] = true
@@ -41,8 +45,20 @@ def test_dummy_config
template "rails/boot.rb", "#{dummy_path}/config/boot.rb", :force => true
template "rails/application.rb", "#{dummy_path}/config/application.rb", :force => true
template "rails/routes.rb", "#{dummy_path}/config/routes.rb", :force => true
+ template "rails/test.rb", "#{dummy_path}/config/environments/test.rb", :force => true
template "rails/script/rails", "#{dummy_path}/spec/dummy/script/rails", :force => true
template "initializers/custom_user.rb", "#{dummy_path}/config/initializers/custom_user.rb", :force => true
+ template "initializers/devise.rb", "#{dummy_path}/config/initializers/devise.rb", :force => true
+ end
+
+ def test_dummy_inject_extension_requirements
+ if DummyGeneratorHelper.inject_extension_requirements
+ inside dummy_path do
+ inject_require_for('spree_frontend')
+ inject_require_for('spree_backend')
+ inject_require_for('spree_api')
+ end
+ end
end
def test_dummy_clean
@@ -60,12 +76,24 @@ def test_dummy_clean
remove_file "vendor"
remove_file "spec"
end
+
end
attr :lib_name
attr :database
protected
+
+ def inject_require_for(requirement)
+ inject_into_file 'config/application.rb', %Q[
+begin
+ require '#{requirement}'
+rescue LoadError
+ # #{requirement} is not available.
+end
+ ], :before => /require '#{@lib_name}'/, :verbose => true
+ end
+
def dummy_path
ENV['DUMMY_PATH'] || 'spec/dummy'
end
@@ -95,13 +123,18 @@ def remove_directory_if_exists(path)
end
def gemfile_path
- version_file = File.expand_path("../../Versionfile", Dir.pwd)
- if File.exist?(version_file)
- '../../../../Gemfile'
- else
+ core_gems = ["spree/core", "spree/api", "spree/backend", "spree/frontend"]
+
+ if core_gems.include?(lib_name)
'../../../../../Gemfile'
+ else
+ '../../../../Gemfile'
end
end
-
end
end
+
+module Spree::DummyGeneratorHelper
+ mattr_accessor :inject_extension_requirements
+ self.inject_extension_requirements = false
+end
diff --git a/core/lib/generators/spree/dummy/templates/initializers/devise.rb b/core/lib/generators/spree/dummy/templates/initializers/devise.rb
new file mode 100644
index 00000000000..7dff9547df5
--- /dev/null
+++ b/core/lib/generators/spree/dummy/templates/initializers/devise.rb
@@ -0,0 +1,3 @@
+if Object.const_defined?("Devise")
+ Devise.secret_key = "<%= SecureRandom.hex(50) %>"
+end
\ No newline at end of file
diff --git a/core/lib/generators/spree/dummy/templates/rails/database.yml b/core/lib/generators/spree/dummy/templates/rails/database.yml
index edcb56b11a6..d24023adc51 100644
--- a/core/lib/generators/spree/dummy/templates/rails/database.yml
+++ b/core/lib/generators/spree/dummy/templates/rails/database.yml
@@ -1,54 +1,65 @@
+<% if agent_number = ENV['TC_AGENT_NUMBER']
+database_prefix = agent_number + '_'
+end %>
<% case ENV['DB']
when 'sqlite' %>
development:
adapter: sqlite3
- database: "db/spree_development.sqlite3"
+ database: db/spree_development.sqlite3
test:
adapter: sqlite3
- database: "db/spree_test.sqlite3"
+ database: db/spree_test.sqlite3
+ timeout: 10000
production:
adapter: sqlite3
- database: "db/spree_production.sqlite3"
+ database: db/spree_production.sqlite3
<% when 'mysql' %>
development:
adapter: mysql2
- database: spree_development
- username:
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_development
encoding: utf8
test:
adapter: mysql2
- database: spree_test
- username:
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_test
encoding: utf8
production:
adapter: mysql2
- database: spree_production
- username:
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_production
encoding: utf8
<% when 'postgres' %>
+<% db_host = ENV['DB_HOST'] -%>
development:
adapter: postgresql
- database: spree_development
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_development
username: postgres
min_messages: warning
+<% unless db_host.blank? %>
+ host: <%= db_host %>
+<% end %>
test:
adapter: postgresql
- database: spree_test
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_test
username: postgres
min_messages: warning
+<% unless db_host.blank? %>
+ host: <%= db_host %>
+<% end %>
production:
adapter: postgresql
- database: spree_production
+ database: <%= database_prefix %><%= options[:lib_name] %>_spree_production
username: postgres
min_messages: warning
+<% unless db_host.blank? %>
+ host: <%= db_host %>
+<% end %>
<% else %>
development:
adapter: sqlite3
- database: "db/spree_development.sqlite3"
+ database: db/spree_development.sqlite3
test:
adapter: sqlite3
- database: "db/spree_test.sqlite3"
+ database: db/spree_test.sqlite3
production:
adapter: sqlite3
- database: "db/spree_production.sqlite3"
+ database: db/spree_production.sqlite3
<% end %>
diff --git a/core/lib/generators/spree/dummy/templates/rails/routes.rb b/core/lib/generators/spree/dummy/templates/rails/routes.rb
index 6aa5d34d63d..1daf9a4121a 100644
--- a/core/lib/generators/spree/dummy/templates/rails/routes.rb
+++ b/core/lib/generators/spree/dummy/templates/rails/routes.rb
@@ -1,3 +1,2 @@
Rails.application.routes.draw do
- <%= 'mount Spree::Core::Engine => "/"' if defined?(Spree::Core) %>
end
diff --git a/core/lib/generators/spree/dummy/templates/rails/test.rb b/core/lib/generators/spree/dummy/templates/rails/test.rb
new file mode 100644
index 00000000000..9f175964881
--- /dev/null
+++ b/core/lib/generators/spree/dummy/templates/rails/test.rb
@@ -0,0 +1,34 @@
+Dummy::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ config.eager_load = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+ ActionMailer::Base.default :from => "spree@example.com"
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
diff --git a/core/lib/generators/spree/install/install_generator.rb b/core/lib/generators/spree/install/install_generator.rb
index 0727f77aabc..d7a07932c0b 100644
--- a/core/lib/generators/spree/install/install_generator.rb
+++ b/core/lib/generators/spree/install/install_generator.rb
@@ -13,6 +13,7 @@ class InstallGenerator < Rails::Generators::Base
class_option :admin_email, :type => :string
class_option :admin_password, :type => :string
class_option :lib_name, :type => :string, :default => 'spree'
+ class_option :enforce_available_locales, :type => :boolean, :default => nil
def self.source_paths
paths = self.superclass.source_paths
@@ -45,36 +46,36 @@ def config_spree_yml
end
end
- def remove_unneeded_files
- remove_file "public/index.html"
- end
-
def additional_tweaks
return unless File.exists? 'public/robots.txt'
append_file "public/robots.txt", <<-ROBOTS
User-agent: *
-Disallow: /checkouts
+Disallow: /checkout
+Disallow: /cart
Disallow: /orders
-Disallow: /countries
-Disallow: /line_items
-Disallow: /password_resets
-Disallow: /states
-Disallow: /user_sessions
-Disallow: /users
+Disallow: /user
+Disallow: /account
+Disallow: /api
+Disallow: /password
ROBOTS
end
def setup_assets
@lib_name = 'spree'
%w{javascripts stylesheets images}.each do |path|
- empty_directory "app/assets/#{path}/store"
- empty_directory "app/assets/#{path}/admin"
+ empty_directory "vendor/assets/#{path}/spree/frontend" if defined? Spree::Frontend || Rails.env.test?
+ empty_directory "vendor/assets/#{path}/spree/backend" if defined? Spree::Backend || Rails.env.test?
+ end
+
+ if defined? Spree::Frontend || Rails.env.test?
+ template "vendor/assets/javascripts/spree/frontend/all.js"
+ template "vendor/assets/stylesheets/spree/frontend/all.css"
end
- template "app/assets/javascripts/store/all.js"
- template "app/assets/javascripts/admin/all.js"
- template "app/assets/stylesheets/store/all.css"
- template "app/assets/stylesheets/admin/all.css"
+ if defined? Spree::Backend || Rails.env.test?
+ template "vendor/assets/javascripts/spree/backend/all.js"
+ template "vendor/assets/stylesheets/spree/backend/all.css"
+ end
end
def create_overrides_directory
@@ -97,7 +98,12 @@ def configure_application
end
APP
- append_file "config/environment.rb", "\nActiveRecord::Base.include_root_in_json = true\n"
+ if !options[:enforce_available_locales].nil?
+ application <<-APP
+ # Prevent this deprecation message: https://github.com/svenfuchs/i18n/commit/3b6e56e
+ I18n.enforce_available_locales = #{options[:enforce_available_locales]}
+ APP
+ end
end
def include_seed_data
@@ -162,7 +168,7 @@ def load_sample_data
end
def notify_about_routes
- insert_into_file File.join('config', 'routes.rb'), :after => "Application.routes.draw do\n" do
+ insert_into_file File.join('config', 'routes.rb'), :after => "Rails.application.routes.draw do\n" do
%Q{
# This line mounts Spree's routes at the root of your application.
# This means, any requests to URLs such as /products, will go to Spree::ProductsController.
@@ -189,6 +195,5 @@ def complete
puts "Enjoy!"
end
end
-
end
end
diff --git a/core/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js b/core/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js
deleted file mode 100644
index 590b2a0fba0..00000000000
--- a/core/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-//= require jquery
-//= require jquery_ujs
-<% if options[:lib_name] == 'spree' %>
-//= require admin/spree_core
-//= require admin/spree_promo
-<% else %>
-//= require admin/<%= options[:lib_name].gsub("/", "_") %>
-<% end %>
-//= require_tree .
diff --git a/core/lib/generators/spree/install/templates/app/assets/javascripts/store/all.js b/core/lib/generators/spree/install/templates/app/assets/javascripts/store/all.js
deleted file mode 100644
index 3512a2a08f0..00000000000
--- a/core/lib/generators/spree/install/templates/app/assets/javascripts/store/all.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-//= require jquery
-//= require jquery_ujs
-<% if options[:lib_name] == 'spree' %>
-//= require store/spree_core
-//= require store/spree_promo
-<% else %>
-//= require store/<%= options[:lib_name].gsub("/", "_") %>
-<% end %>
-//= require_tree .
diff --git a/core/lib/generators/spree/install/templates/app/assets/stylesheets/admin/all.css b/core/lib/generators/spree/install/templates/app/assets/stylesheets/admin/all.css
deleted file mode 100644
index 86e4e5d5e10..00000000000
--- a/core/lib/generators/spree/install/templates/app/assets/stylesheets/admin/all.css
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * This is a manifest file that'll automatically include all the stylesheets available in this directory
- * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
- * the top of the compiled file, but it's generally better to create a new file per style scope.
- *
-<% if options[:lib_name] == 'spree' %>
- *= require admin/spree_core
- *= require admin/spree_promo
-<% else %>
- *= require admin/<%= options[:lib_name].gsub("/", "_") %>
-<% end %>
- *= require_self
- *= require_tree .
-*/
diff --git a/core/lib/generators/spree/install/templates/app/assets/stylesheets/store/all.css b/core/lib/generators/spree/install/templates/app/assets/stylesheets/store/all.css
deleted file mode 100644
index a361ef2e865..00000000000
--- a/core/lib/generators/spree/install/templates/app/assets/stylesheets/store/all.css
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * This is a manifest file that'll automatically include all the stylesheets available in this directory
- * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
- * the top of the compiled file, but it's generally better to create a new file per style scope.
- *
-<% if options[:lib_name] == 'spree' %>
- *= require store/spree_core
- *= require store/spree_promo
-<% else %>
- *= require store/<%= options[:lib_name].gsub("/", "_") %>
-<% end %>
- *= require_self
- *= require_tree .
-*/
diff --git a/core/lib/generators/spree/install/templates/config/initializers/spree.rb b/core/lib/generators/spree/install/templates/config/initializers/spree.rb
index f9a3c557ec4..9f06c10123c 100644
--- a/core/lib/generators/spree/install/templates/config/initializers/spree.rb
+++ b/core/lib/generators/spree/install/templates/config/initializers/spree.rb
@@ -7,8 +7,8 @@
# config.setting_name = 'new value'
Spree.config do |config|
# Example:
- # Uncomment to override the default site name.
- # config.site_name = "Spree Demo Site"
+ # Uncomment to stop tracking inventory levels in the application
+ # config.track_inventory_levels = false
end
Spree.user_class = <%= (options[:user_class].blank? ? "Spree::LegacyUser" : options[:user_class]).inspect %>
diff --git a/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/backend/all.js b/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/backend/all.js
new file mode 100644
index 00000000000..d76d69041bd
--- /dev/null
+++ b/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/backend/all.js
@@ -0,0 +1,13 @@
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+//= require jquery
+//= require jquery_ujs
+//= require spree/backend
+<% unless options[:lib_name] == 'spree' || options[:lib_name] == 'spree/backend' %>
+//= require spree/backend/<%= options[:lib_name].gsub("/", "_") %>
+<% end %>
+//= require_tree .
diff --git a/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/frontend/all.js b/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/frontend/all.js
new file mode 100644
index 00000000000..e493c1abe33
--- /dev/null
+++ b/core/lib/generators/spree/install/templates/vendor/assets/javascripts/spree/frontend/all.js
@@ -0,0 +1,13 @@
+// This is a manifest file that'll be compiled into including all the files listed below.
+// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
+// be included in the compiled file accessible from http://example.com/assets/application.js
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+//= require jquery
+//= require jquery_ujs
+//= require spree/frontend
+<% unless options[:lib_name] == 'spree' || options[:lib_name] == 'spree/frontend' %>
+//= require spree/frontend/<%= options[:lib_name].gsub("/", "_") %>
+<% end %>
+//= require_tree .
diff --git a/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/backend/all.css b/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/backend/all.css
new file mode 100644
index 00000000000..ef4f3000d6e
--- /dev/null
+++ b/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/backend/all.css
@@ -0,0 +1,12 @@
+/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *
+ *= require spree/backend
+<% unless options[:lib_name] == 'spree' || options[:lib_name] == 'spree/backend' %>
+ *= require spree/backend/<%= options[:lib_name].gsub("/", "_") %>
+<% end %>
+ *= require_self
+ *= require_tree .
+*/
diff --git a/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/frontend/all.css b/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/frontend/all.css
new file mode 100644
index 00000000000..d4b7c30e5d1
--- /dev/null
+++ b/core/lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/frontend/all.css
@@ -0,0 +1,12 @@
+/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *
+ *= require spree/frontend
+<% unless options[:lib_name] == 'spree' || options[:lib_name] == 'spree/frontend' %>
+ *= require spree/frontend/<%= options[:lib_name].gsub("/", "_") %>
+<% end %>
+ *= require_self
+ *= require_tree .
+*/
diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb
index d8330926dff..a8dfe47f5bb 100644
--- a/core/lib/spree/core.rb
+++ b/core/lib/spree/core.rb
@@ -1,46 +1,18 @@
-#++
-# Copyright (c) 2007-2012, Spree Commerce, Inc. and other contributors
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the Spree Commerce, Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from this
-# software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#--
require 'rails/all'
-require 'rails/generators'
-require 'state_machine'
-require 'paperclip'
-require 'kaminari'
-require 'awesome_nested_set'
-require 'acts_as_list'
require 'active_merchant'
-require 'ransack'
-require 'jquery-rails'
-require 'deface'
+require 'acts_as_list'
+require 'awesome_nested_set'
require 'cancan'
-require 'select2-rails'
-require 'spree/money'
-require 'rabl'
+require 'friendly_id'
+require 'font-awesome-rails'
+require 'kaminari'
+require 'mail'
+require 'monetize'
+require 'paperclip'
+require 'paranoia'
+require 'premailer/rails'
+require 'ransack'
+require 'state_machine'
module Spree
@@ -48,21 +20,18 @@ module Spree
def self.user_class
if @@user_class.is_a?(Class)
- raise "Spree.user_class MUST be a String object, not a Class object."
- elsif @@user_class.is_a?(String)
- @@user_class.constantize
+ raise "Spree.user_class MUST be a String or Symbol object, not a Class object."
+ elsif @@user_class.is_a?(String) || @@user_class.is_a?(Symbol)
+ @@user_class.to_s.constantize
end
end
- module Core
- end
-
# Used to configure Spree.
#
# Example:
#
# Spree.config do |config|
- # config.site_name = "An awesome Spree site"
+ # config.track_inventory_levels = false
# end
#
# This method is defined within the core gem on purpose.
@@ -70,41 +39,47 @@ module Core
def self.config(&block)
yield(Spree::Config)
end
-end
-
-require 'spree/core/ext/active_record'
-require 'spree/core/delegate_belongs_to'
+ module Core
+ autoload :ProductFilters, "spree/core/product_filters"
-require 'spree/core/responder'
-require 'spree/core/ssl_requirement'
-require 'spree/core/store_helpers'
-require 'spree/core/calculated_adjustments'
-require 'spree/core/mail_settings'
-require 'spree/core/mail_interceptor'
-require 'spree/core/middleware/redirect_legacy_product_url'
-require 'spree/core/middleware/seo_assist'
-require 'spree/core/permalinks'
-require 'spree/core/token_resource'
-require 'spree/core/s3_support'
+ class GatewayError < RuntimeError; end
+ class DestroyWithOrdersError < StandardError; end
+ end
+end
require 'spree/core/version'
+require 'spree/core/environment_extension'
+require 'spree/core/environment/calculators'
+require 'spree/core/environment'
+require 'spree/promo/environment'
+require 'spree/migrations'
require 'spree/core/engine'
-require 'generators/spree/dummy/dummy_generator'
-ActiveRecord::Base.class_eval do
- include Spree::Core::CalculatedAdjustments
- include CollectiveIdea::Acts::NestedSet
-end
+require 'spree/i18n'
+require 'spree/localized_number'
+require 'spree/money'
+require 'spree/permitted_attributes'
-if defined?(ActionView)
- require 'awesome_nested_set/helper'
- ActionView::Base.class_eval do
- include CollectiveIdea::Acts::NestedSet::Helper
- end
-end
+require 'spree/core/delegate_belongs_to'
+require 'spree/core/importer'
+require 'spree/core/permalinks'
+require 'spree/core/product_duplicator'
+require 'spree/core/controller_helpers/auth'
+require 'spree/core/controller_helpers/common'
+require 'spree/core/controller_helpers/order'
+require 'spree/core/controller_helpers/respond_with'
+require 'spree/core/controller_helpers/search'
+require 'spree/core/controller_helpers/ssl'
+require 'spree/core/controller_helpers/store'
+require 'spree/core/controller_helpers/strong_parameters'
-ActiveSupport.on_load(:action_view) do
- include Spree::Core::StoreHelpers
+# Hack waiting on https://github.com/pluginaweek/state_machine/pull/275
+module StateMachine
+ module Integrations
+ module ActiveModel
+ public :around_validation
+ end
+ end
end
diff --git a/core/lib/spree/core/calculated_adjustments.rb b/core/lib/spree/core/calculated_adjustments.rb
deleted file mode 100644
index 5d8830cfa32..00000000000
--- a/core/lib/spree/core/calculated_adjustments.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module Spree
- module Core
- module CalculatedAdjustments
- module ClassMethods
- def calculated_adjustments
- has_one :calculator, :as => :calculable, :dependent => :destroy
- accepts_nested_attributes_for :calculator
- attr_accessible :calculator_type, :calculator_attributes
- validates :calculator, :presence => true
-
- def self.calculators
- Rails.application.config.spree.calculators.send(self.to_s.tableize.gsub('/', '_').sub('spree_', ''))
- end
- end
- end
-
- def calculator_type
- calculator.class.to_s if calculator
- end
-
- def calculator_type=(calculator_type)
- clazz = calculator_type.constantize if calculator_type
- self.calculator = clazz.new if clazz and not self.calculator.is_a? clazz
- end
-
- # Creates a new adjustment for the target object (which is any class that has_many :adjustments) and
- # sets amount based on the calculator as applied to the calculable argument (Order, LineItems[], Shipment, etc.)
- # By default the adjustment will not be considered mandatory
- def create_adjustment(label, target, calculable, mandatory=false)
- amount = compute_amount(calculable)
- return if amount == 0 && !mandatory
- target.adjustments.create({ :amount => amount,
- :source => calculable,
- :originator => self,
- :label => label,
- :mandatory => mandatory}, :without_protection => true)
- end
-
- # Updates the amount of the adjustment using our Calculator and calling the +compute+ method with the +calculable+
- # referenced passed to the method.
- def update_adjustment(adjustment, calculable)
- adjustment.update_attribute_without_callbacks(:amount, compute_amount(calculable))
- end
-
- # Calculate the amount to be used when creating an adjustment
- def compute_amount(calculable)
- self.calculator.compute(calculable)
- end
-
- def self.included(receiver)
- receiver.extend ClassMethods
- end
- end
- end
-end
diff --git a/core/lib/spree/core/controller_helpers/auth.rb b/core/lib/spree/core/controller_helpers/auth.rb
index fae6d80d154..05c42cc4b9e 100644
--- a/core/lib/spree/core/controller_helpers/auth.rb
+++ b/core/lib/spree/core/controller_helpers/auth.rb
@@ -2,15 +2,14 @@ module Spree
module Core
module ControllerHelpers
module Auth
- def self.included(base)
- base.class_eval do
- include SslRequirement
+ extend ActiveSupport::Concern
- helper_method :try_spree_current_user
+ included do
+ before_filter :set_guest_token
+ helper_method :try_spree_current_user
- rescue_from CanCan::AccessDenied do |exception|
- return unauthorized
- end
+ rescue_from CanCan::AccessDenied do |exception|
+ redirect_unauthorized_access
end
end
@@ -19,17 +18,14 @@ def current_ability
@current_ability ||= Spree::Ability.new(try_spree_current_user)
end
- # Redirect as appropriate when an access request fails. The default action is to redirect to the login screen.
- # Override this method in your controllers if you want to have special behavior in case the user is not authorized
- # to access the requested action. For example, a popup window might simply close itself.
- def unauthorized
- if try_spree_current_user
- flash[:error] = t(:authorization_failure)
- redirect_to '/unauthorized'
- else
- store_location
- url = respond_to?(:spree_login_path) ? spree_login_path : root_path
- redirect_to url
+ def redirect_back_or_default(default)
+ redirect_to(session["spree_user_return_to"] || default)
+ session["spree_user_return_to"] = nil
+ end
+
+ def set_guest_token
+ unless cookies.signed[:guest_token].present?
+ cookies.permanent.signed[:guest_token] = SecureRandom.urlsafe_base64(nil, false)
end
end
@@ -45,20 +41,42 @@ def store_location
disallowed_urls.map!{ |url| url[/\/\w+$/] }
unless disallowed_urls.include?(request.fullpath)
- session['user_return_to'] = request.fullpath.gsub('//', '/')
+ session['spree_user_return_to'] = request.fullpath.gsub('//', '/')
end
end
# proxy method to *possible* spree_current_user method
# Authentication extensions (such as spree_auth_devise) are meant to provide spree_current_user
def try_spree_current_user
- respond_to?(:spree_current_user) ? spree_current_user : nil
+ # This one will be defined by apps looking to hook into Spree
+ # As per authentication_helpers.rb
+ if respond_to?(:spree_current_user)
+ spree_current_user
+ # This one will be defined by Devise
+ elsif respond_to?(:current_spree_user)
+ current_spree_user
+ else
+ nil
+ end
end
- def redirect_back_or_default(default)
- redirect_to(session["user_return_to"] || default)
- session["user_return_to"] = nil
+ # Redirect as appropriate when an access request fails. The default action is to redirect to the login screen.
+ # Override this method in your controllers if you want to have special behavior in case the user is not authorized
+ # to access the requested action. For example, a popup window might simply close itself.
+ def redirect_unauthorized_access
+ if try_spree_current_user
+ flash[:error] = Spree.t(:authorization_failure)
+ redirect_to '/unauthorized'
+ else
+ store_location
+ if respond_to?(:spree_login_path)
+ redirect_to spree_login_path
+ else
+ redirect_to spree.respond_to?(:root_path) ? spree.root_path : root_path
+ end
+ end
end
+
end
end
end
diff --git a/core/lib/spree/core/controller_helpers/common.rb b/core/lib/spree/core/controller_helpers/common.rb
index f3917c0d088..a2382e39a29 100644
--- a/core/lib/spree/core/controller_helpers/common.rb
+++ b/core/lib/spree/core/controller_helpers/common.rb
@@ -2,87 +2,71 @@ module Spree
module Core
module ControllerHelpers
module Common
- def self.included(base)
- base.class_eval do
- helper_method :title
- helper_method :title=
- helper_method :accurate_title
- helper_method :current_order
- helper_method :current_currency
+ extend ActiveSupport::Concern
+ included do
+ helper_method :title
+ helper_method :title=
+ helper_method :accurate_title
- layout :get_layout
+ layout :get_layout
- before_filter :set_user_language
- end
- end
+ before_filter :set_user_language
- protected
+ protected
- # Convenience method for firing instrumentation events with the default payload hash
- def fire_event(name, extra_payload = {})
- ActiveSupport::Notifications.instrument(name, default_notification_payload.merge(extra_payload))
- end
-
- # Creates the hash that is sent as the payload for all notifications. Specific notifications will
- # add additional keys as appropriate. Override this method if you need additional data when
- # responding to a notification
- def default_notification_payload
- {:user => try_spree_current_user, :order => current_order}
- end
+ # can be used in views as well as controllers.
+ # e.g. <% self.title = 'This is a custom title for this view' %>
+ attr_writer :title
- # can be used in views as well as controllers.
- # e.g. <% title = 'This is a custom title for this view' %>
- attr_writer :title
-
- def title
- title_string = @title.present? ? @title : accurate_title
- if title_string.present?
- if Spree::Config[:always_put_site_name_in_title]
- [default_title, title_string].join(' - ')
+ def title
+ title_string = @title.present? ? @title : accurate_title
+ if title_string.present?
+ if Spree::Config[:always_put_site_name_in_title]
+ [title_string, default_title].join(' - ')
+ else
+ title_string
+ end
else
- title_string
+ default_title
end
- else
- default_title
end
- end
- def default_title
- Spree::Config[:site_name]
- end
+ def default_title
+ current_store.name
+ end
- # this is a hook for subclasses to provide title
- def accurate_title
- Spree::Config[:default_seo_title]
- end
+ # this is a hook for subclasses to provide title
+ def accurate_title
+ current_store.seo_title
+ end
- def current_currency
- Spree::Config[:currency]
- end
+ def render_404(exception = nil)
+ respond_to do |type|
+ type.html { render :status => :not_found, :file => "#{::Rails.root}/public/404", :formats => [:html], :layout => nil}
+ type.all { render :status => :not_found, :nothing => true }
+ end
+ end
- def render_404(exception = nil)
- respond_to do |type|
- type.html { render :status => :not_found, :file => "#{::Rails.root}/public/404", :formats => [:html], :layout => nil}
- type.all { render :status => :not_found, :nothing => true }
+ private
+
+ def set_user_language
+ locale = session[:locale]
+ locale ||= config_locale if respond_to?(:config_locale, true)
+ locale ||= Rails.application.config.i18n.default_locale
+ locale ||= I18n.default_locale unless I18n.available_locales.map(&:to_s).include?(locale)
+ I18n.locale = locale
end
- end
- private
- def set_user_language
- locale = session[:locale]
- locale ||= Rails.application.config.i18n.default_locale
- locale ||= I18n.default_locale unless I18n.available_locales.include?(locale.try(:to_sym))
- I18n.locale = locale.to_sym
- end
+ # Returns which layout to render.
+ #
+ # You can set the layout you want to render inside your Spree configuration with the +:layout+ option.
+ #
+ # Default layout is: +app/views/spree/layouts/spree_application+
+ #
+ def get_layout
+ layout ||= Spree::Config[:layout]
+ end
- # Returns which layout to render.
- #
- # You can set the layout you want to render inside your Spree configuration with the +:layout+ option.
- #
- # Default layout is: +app/views/spree/layouts/spree_application+
- #
- def get_layout
- layout ||= Spree::Config[:layout]
end
end
end
diff --git a/core/lib/spree/core/controller_helpers/order.rb b/core/lib/spree/core/controller_helpers/order.rb
index a5b56315379..e4458418fa4 100644
--- a/core/lib/spree/core/controller_helpers/order.rb
+++ b/core/lib/spree/core/controller_helpers/order.rb
@@ -2,69 +2,104 @@ module Spree
module Core
module ControllerHelpers
module Order
- def self.included(base)
- base.class_eval do
- helper_method :current_order
- before_filter :set_current_order
- end
- end
+ extend ActiveSupport::Concern
- # This should be overridden by an auth-related extension which would then have the
- # opportunity to associate the new order with the # current user before saving.
- def before_save_new_order
+ included do
+ before_filter :set_current_order
+
+ helper_method :current_currency
+ helper_method :current_order
+ helper_method :simple_current_order
end
- # This should be overridden by an auth-related extension which would then have the
- # opporutnity to store tokens, etc. in the session # after saving.
- def after_save_new_order
+ # Used in the link_to_cart helper.
+ def simple_current_order
+
+ return @simple_current_order if @simple_current_order
+
+ @simple_current_order = find_order_by_token_or_user
+
+ if @simple_current_order
+ @simple_current_order.last_ip_address = ip_address
+ return @simple_current_order
+ else
+ @simple_current_order = Spree::Order.new
+ end
end
- # The current incomplete order from the session for use in cart and during checkout
- def current_order(create_order_if_necessary = false)
+ # The current incomplete order from the guest_token for use in cart and during checkout
+ def current_order(options = {})
+ options[:create_order_if_necessary] ||= false
+
return @current_order if @current_order
- if session[:order_id]
- current_order = Spree::Order.find_by_id_and_currency(session[:order_id], current_currency, :include => :adjustments)
- @current_order = current_order unless current_order.try(:completed?)
- end
- if create_order_if_necessary and (@current_order.nil? or @current_order.completed?)
- @current_order = Spree::Order.new(currency: current_currency)
- before_save_new_order
+
+ @current_order = find_order_by_token_or_user(options, true)
+
+ if options[:create_order_if_necessary] && (@current_order.nil? || @current_order.completed?)
+ @current_order = Spree::Order.new(current_order_params)
+ @current_order.user ||= try_spree_current_user
+ # See issue #3346 for reasons why this line is here
+ @current_order.created_by ||= try_spree_current_user
@current_order.save!
- after_save_new_order
end
- session[:order_id] = @current_order ? @current_order.id : nil
- @current_order
+
+ if @current_order
+ @current_order.last_ip_address = ip_address
+ return @current_order
+ end
end
def associate_user
@order ||= current_order
if try_spree_current_user && @order
- if @order.user.blank? || @order.email.blank?
- @order.associate_user!(try_spree_current_user)
- end
+ @order.associate_user!(try_spree_current_user) if @order.user.blank? || @order.email.blank?
end
+ end
- # This will trigger any "first order" promotions to be triggered
- # Assuming of course that this session variable was set correctly in
- # the authentication provider's registrations controller
- if session[:spree_user_signup]
- fire_event('spree.user.signup', :user => try_spree_current_user, :order => current_order(true))
+ def set_current_order
+ if try_spree_current_user && current_order
+ try_spree_current_user.orders.incomplete.where('id != ?', current_order.id).each do |order|
+ current_order.merge!(order, try_spree_current_user)
+ end
end
+ end
- session[:guest_token] = nil
- session[:spree_user_signup] = nil
+ def current_currency
+ Spree::Config[:currency]
end
- def set_current_order
- if user = try_spree_current_user
- last_incomplete_order = user.last_incomplete_spree_order
- if session[:order_id].nil? && last_incomplete_order
- session[:order_id] = last_incomplete_order.id
- elsif current_order && last_incomplete_order && current_order != last_incomplete_order
- current_order.merge!(last_incomplete_order)
- end
+ def ip_address
+ request.remote_ip
+ end
+
+ private
+
+ def last_incomplete_order
+ @last_incomplete_order ||= try_spree_current_user.last_incomplete_spree_order
+ end
+
+ def current_order_params
+ { currency: current_currency, guest_token: cookies.signed[:guest_token], store_id: current_store.id, user_id: try_spree_current_user.try(:id) }
+ end
+
+ def find_order_by_token_or_user(options={}, with_adjustments = false)
+ options[:lock] ||= false
+
+ # Find any incomplete orders for the guest_token
+ if with_adjustments
+ order = Spree::Order.incomplete.includes(:adjustments).lock(options[:lock]).find_by(current_order_params)
+ else
+ order = Spree::Order.incomplete.lock(options[:lock]).find_by(current_order_params)
end
+
+ # Find any incomplete orders for the current user
+ if order.nil? && try_spree_current_user
+ order = last_incomplete_order
+ end
+
+ order
end
+
end
end
end
diff --git a/core/lib/spree/core/controller_helpers/respond_with.rb b/core/lib/spree/core/controller_helpers/respond_with.rb
index e85777cb74f..a4e6a11ae21 100644
--- a/core/lib/spree/core/controller_helpers/respond_with.rb
+++ b/core/lib/spree/core/controller_helpers/respond_with.rb
@@ -1,3 +1,5 @@
+require 'spree/responder'
+
module ActionController
class Base
def respond_with(*resources, &block)
@@ -17,7 +19,8 @@ def respond_with(*resources, &block)
# The action name is needed for processing
options.merge!(:action_name => action_name.to_sym)
# If responder is not specified then pass in Spree::Responder
- (options.delete(:responder) || Spree::Responder).call(self, resources, options)
+ responder = options.delete(:responder) || self.responder
+ responder.call(self, resources, options)
end
end
end
@@ -33,6 +36,8 @@ module RespondWith
included do
cattr_accessor :spree_responders
self.spree_responders = {}
+
+ self.responder = Spree::Responder
end
module ClassMethods
diff --git a/core/lib/spree/core/controller_helpers/search.rb b/core/lib/spree/core/controller_helpers/search.rb
new file mode 100644
index 00000000000..b1a2f8f6a7a
--- /dev/null
+++ b/core/lib/spree/core/controller_helpers/search.rb
@@ -0,0 +1,14 @@
+module Spree
+ module Core
+ module ControllerHelpers
+ module Search
+ def build_searcher params
+ Spree::Config.searcher_class.new(params).tap do |searcher|
+ searcher.current_user = try_spree_current_user
+ searcher.current_currency = current_currency
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/controller_helpers/ssl.rb b/core/lib/spree/core/controller_helpers/ssl.rb
new file mode 100644
index 00000000000..852be6c197b
--- /dev/null
+++ b/core/lib/spree/core/controller_helpers/ssl.rb
@@ -0,0 +1,60 @@
+module Spree
+ module Core
+ module ControllerHelpers
+ module SSL
+ extend ActiveSupport::Concern
+
+ included do
+ before_filter :force_non_ssl_redirect, :if => Proc.new { Spree::Config[:redirect_https_to_http] }
+ class_attribute :ssl_allowed_actions
+
+ def self.ssl_allowed(*actions)
+ self.ssl_allowed_actions ||= []
+ self.ssl_allowed_actions.concat actions
+ end
+
+ def self.ssl_required(*actions)
+ ssl_allowed *actions
+ if actions.empty? or Rails.application.config.force_ssl
+ force_ssl :if => :ssl_supported?
+ else
+ force_ssl :if => :ssl_supported?, :only => actions
+ end
+ end
+
+ def ssl_supported?
+ return Spree::Config[:allow_ssl_in_production] if Rails.env.production?
+ return Spree::Config[:allow_ssl_in_staging] if Rails.env.staging?
+ return Spree::Config[:allow_ssl_in_development_and_test] if (Rails.env.development? or Rails.env.test?)
+ end
+
+ private
+ def ssl_allowed?
+ (!ssl_allowed_actions.nil? && (ssl_allowed_actions.empty? || ssl_allowed_actions.include?(action_name.to_sym)))
+ end
+
+ # Redirect the existing request to use the HTTP protocol.
+ #
+ # ==== Parameters
+ # * host - Redirect to a different host name
+ def force_non_ssl_redirect(host = nil)
+ if request.ssl? && !ssl_allowed?
+ if request.get?
+ redirect_options = {
+ :protocol => 'http://',
+ :host => host || request.host,
+ :path => request.fullpath,
+ }
+ flash.keep if respond_to?(:flash)
+ insecure_url = ActionDispatch::Http::URL.url_for(redirect_options)
+ redirect_to insecure_url, :status => :moved_permanently
+ else
+ render :text => Spree.t(:change_protocol, :scope => :ssl), :status => :upgrade_required
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/controller_helpers/store.rb b/core/lib/spree/core/controller_helpers/store.rb
new file mode 100644
index 00000000000..375bfee2a9e
--- /dev/null
+++ b/core/lib/spree/core/controller_helpers/store.rb
@@ -0,0 +1,19 @@
+module Spree
+ module Core
+ module ControllerHelpers
+ module Store
+ extend ActiveSupport::Concern
+
+ included do
+
+ def current_store
+ @current_store ||= Spree::Store.current(request.env['SERVER_NAME'])
+ end
+ helper_method :current_store
+
+ end
+
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/controller_helpers/strong_parameters.rb b/core/lib/spree/core/controller_helpers/strong_parameters.rb
new file mode 100644
index 00000000000..78e75798556
--- /dev/null
+++ b/core/lib/spree/core/controller_helpers/strong_parameters.rb
@@ -0,0 +1,42 @@
+module Spree
+ module Core
+ module ControllerHelpers
+ module StrongParameters
+ def permitted_attributes
+ Spree::PermittedAttributes
+ end
+
+ delegate *Spree::PermittedAttributes::ATTRIBUTES,
+ to: :permitted_attributes,
+ prefix: :permitted
+
+ def permitted_payment_attributes
+ permitted_attributes.payment_attributes + [
+ source_attributes: permitted_source_attributes
+ ]
+ end
+
+ def permitted_checkout_attributes
+ permitted_attributes.checkout_attributes + [
+ bill_address_attributes: permitted_address_attributes,
+ ship_address_attributes: permitted_address_attributes,
+ payments_attributes: permitted_payment_attributes,
+ shipments_attributes: permitted_shipment_attributes
+ ]
+ end
+
+ def permitted_order_attributes
+ permitted_checkout_attributes + [
+ line_items_attributes: permitted_line_item_attributes
+ ]
+ end
+
+ def permitted_product_attributes
+ permitted_attributes.product_attributes + [
+ product_properties_attributes: permitted_product_properties_attributes
+ ]
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/custom_fixtures.rb b/core/lib/spree/core/custom_fixtures.rb
deleted file mode 100644
index e80c804d02a..00000000000
--- a/core/lib/spree/core/custom_fixtures.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'active_record/fixtures'
-
-module Spree
- module Core
- class Fixtures < ActiveRecord::Fixtures
- # Replace this method to prevent the table being emptied on each call. Needed
- # when both core & auth have user fixtures, see below for code commented out.
- #
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
-
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
-
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
- }
-
- unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
-
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
-
- fixtures_map[path] = ActiveRecord::Fixtures.new(
- connection,
- table_name,
- class_names[table_name.to_sym] || table_name.classify,
- ::File.join(fixtures_directory, path))
- end
-
- all_loaded_fixtures.update(fixtures_map)
-
- connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
-
- # REMOVED BY SPREE
- # table_rows.keys.each do |table|
- # conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
- # end
-
- table_rows.each do |table_name,rows|
- rows.each do |row|
- conn.insert_fixture(row, table_name)
- end
- end
- end
-
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
- end
- end
- end
-
- cache_fixtures(connection, fixtures_map)
- end
- end
- cached_fixtures(connection, table_names)
- end
- end
- end
-end
diff --git a/core/lib/spree/core/delegate_belongs_to.rb b/core/lib/spree/core/delegate_belongs_to.rb
index 759e336acc3..e168fc006b5 100644
--- a/core/lib/spree/core/delegate_belongs_to.rb
+++ b/core/lib/spree/core/delegate_belongs_to.rb
@@ -4,8 +4,8 @@
#
# Todo - integrate with ActiveRecord::Dirty to make sure changes to delegate object are noticed
# Should do
-# class User < ActiveRecord::Base; delegate_belongs_to :contact, :firstname; end
-# class Contact < ActiveRecord::Base; end
+# class User < Spree::Base; delegate_belongs_to :contact, :firstname; end
+# class Contact < Spree::Base; end
# u = User.first
# u.changed? # => false
# u.firstname = 'Bobby'
@@ -38,14 +38,11 @@ def delegate_belongs_to(association, *attrs)
attrs.concat get_association_column_names(association) if attrs.delete :defaults
attrs.each do |attr|
class_def attr do |*args|
- if args.empty?
- send(:delegator_for, association).send(attr)
- else
- send(:delegator_for, association).send(attr, *args)
- end
+ send(:delegator_for, association, attr, *args)
end
+
class_def "#{attr}=" do |val|
- send(:delegator_for, association).send("#{attr}=", val)
+ send(:delegator_for_setter, association, attr, val)
end
end
end
@@ -71,19 +68,28 @@ def initialize_association(type, association, opts={})
end
private
-
def class_def(name, method=nil, &blk)
class_eval { method.nil? ? define_method(name, &blk) : define_method(name, method) }
end
+ end
+ def delegator_for(association, attr, *args)
+ return if self.class.column_names.include?(attr.to_s)
+ send("#{association}=", self.class.reflect_on_association(association).klass.new) if send(association).nil?
+ if args.empty?
+ send(association).send(attr)
+ else
+ send(association).send(attr, *args)
+ end
end
- def delegator_for(association)
+ def delegator_for_setter(association, attr, val)
+ return if self.class.column_names.include?(attr.to_s)
send("#{association}=", self.class.reflect_on_association(association).klass.new) if send(association).nil?
- send(association)
+ send(association).send("#{attr}=", val)
end
protected :delegator_for
-
+ protected :delegator_for_setter
end
-ActiveRecord::Base.send :include, DelegateBelongsTo
\ No newline at end of file
+ActiveRecord::Base.send :include, DelegateBelongsTo
diff --git a/core/lib/spree/core/engine.rb b/core/lib/spree/core/engine.rb
index 32eb2b59fe5..3fbbaf18093 100644
--- a/core/lib/spree/core/engine.rb
+++ b/core/lib/spree/core/engine.rb
@@ -4,63 +4,34 @@ class Engine < ::Rails::Engine
isolate_namespace Spree
engine_name 'spree'
- config.middleware.use "Spree::Core::Middleware::SeoAssist"
- config.middleware.use "Spree::Core::Middleware::RedirectLegacyProductUrl"
-
- config.autoload_paths += %W(#{config.root}/lib)
-
- def self.activate
- end
-
- config.to_prepare &method(:activate).to_proc
-
- Rabl.configure do |config|
- config.include_json_root = false
- config.include_child_root = false
- end
-
- config.after_initialize do
- ActiveSupport::Notifications.subscribe(/^spree\./) do |*args|
- event_name, start_time, end_time, id, payload = args
- Activator.active.event_name_starts_with(event_name).each do |activator|
- payload[:event_name] = event_name
- activator.activate(payload)
- end
- end
+ rake_tasks do
+ load File.join(root, "lib", "tasks", "exchanges.rake")
end
- # We need to reload the routes here due to how Spree sets them up.
- # The different facets of Spree (auth, promo, etc.) append/prepend routes to Core
- # *after* Core has been loaded.
- #
- # So we wait until after initialization is complete to do one final reload.
- # This then makes the appended/prepended routes available to the application.
- config.after_initialize do
- Rails.application.routes_reloader.reload!
- end
-
-
initializer "spree.environment", :before => :load_config_initializers do |app|
app.config.spree = Spree::Core::Environment.new
Spree::Config = app.config.spree.preferences #legacy access
end
- initializer "spree.load_preferences", :before => "spree.environment" do
- ::ActiveRecord::Base.send :include, Spree::Preferences::Preferable
- end
-
initializer "spree.register.calculators" do |app|
app.config.spree.calculators.shipping_methods = [
- Spree::Calculator::FlatPercentItemTotal,
- Spree::Calculator::FlatRate,
- Spree::Calculator::FlexiRate,
- Spree::Calculator::PerItem,
- Spree::Calculator::PriceSack]
+ Spree::Calculator::Shipping::FlatPercentItemTotal,
+ Spree::Calculator::Shipping::FlatRate,
+ Spree::Calculator::Shipping::FlexiRate,
+ Spree::Calculator::Shipping::PerItem,
+ Spree::Calculator::Shipping::PriceSack]
app.config.spree.calculators.tax_rates = [
Spree::Calculator::DefaultTax]
end
+ initializer "spree.register.stock_splitters" do |app|
+ app.config.spree.stock_splitters = [
+ Spree::Stock::Splitter::ShippingCategory,
+ Spree::Stock::Splitter::Backordered
+ ]
+ end
+
initializer "spree.register.payment_methods" do |app|
app.config.spree.payment_methods = [
Spree::Gateway::Bogus,
@@ -68,30 +39,77 @@ def self.activate
Spree::PaymentMethod::Check ]
end
+ # We need to define promotions rules here so extensions and existing apps
+ # can add their custom classes on their initializer files
+ initializer 'spree.promo.environment' do |app|
+ app.config.spree.add_class('promotions')
+ app.config.spree.promotions = Spree::Promo::Environment.new
+ app.config.spree.promotions.rules = []
+ end
+
+ initializer 'spree.promo.register.promotion.calculators' do |app|
+ app.config.spree.calculators.add_class('promotion_actions_create_adjustments')
+ app.config.spree.calculators.promotion_actions_create_adjustments = [
+ Spree::Calculator::FlatPercentItemTotal,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate,
+ Spree::Calculator::TieredPercent,
+ Spree::Calculator::TieredFlatRate
+ ]
+
+ app.config.spree.calculators.add_class('promotion_actions_create_item_adjustments')
+ app.config.spree.calculators.promotion_actions_create_item_adjustments = [
+ Spree::Calculator::PercentOnLineItem,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate
+ ]
+ end
+
+ # Promotion rules need to be evaluated on after initialize otherwise
+ # Spree.user_class would be nil and users might experience errors related
+ # to malformed model associations (Spree.user_class is only defined on
+ # the app initializer)
+ config.after_initialize do
+ Rails.application.config.spree.promotions.rules.concat [
+ Spree::Promotion::Rules::ItemTotal,
+ Spree::Promotion::Rules::Product,
+ Spree::Promotion::Rules::User,
+ Spree::Promotion::Rules::FirstOrder,
+ Spree::Promotion::Rules::UserLoggedIn,
+ Spree::Promotion::Rules::OneUsePerUser,
+ Spree::Promotion::Rules::Taxon,
+ ]
+ end
+
+ initializer 'spree.promo.register.promotions.actions' do |app|
+ app.config.spree.promotions.actions = [
+ Promotion::Actions::CreateAdjustment,
+ Promotion::Actions::CreateItemAdjustments,
+ Promotion::Actions::CreateLineItems,
+ Promotion::Actions::FreeShipping]
+ end
+
# filter sensitive information during logging
initializer "spree.params.filter" do |app|
- app.config.filter_parameters += [:password, :password_confirmation, :number]
+ app.config.filter_parameters += [
+ :password,
+ :password_confirmation,
+ :number,
+ :verification_value]
end
- # sets the manifests / assets to be precompiled, even when initialize_on_precompile is false
- initializer "spree.assets.precompile", :group => :all do |app|
- app.config.assets.precompile += %w[
- store/all.*
- admin/all.*
- admin/orders/edit_form.js
- admin/address_states.js
- jqPlot/excanvas.min.js
- admin/images/new.js
- jquery.jstree/themes/apple/*
- ]
+ initializer "spree.core.checking_migrations" do |app|
+ Migrations.new(config, engine_name).check
end
- initializer "spree.mail.settings" do |app|
- if Spree::MailMethod.table_exists?
- Spree::Core::MailSettings.init
- Mail.register_interceptor(Spree::Core::MailInterceptor)
+ config.to_prepare do
+ # Load application's model / class decorators
+ Dir.glob(File.join(File.dirname(__FILE__), '../../../app/**/*_decorator*.rb')) do |c|
+ Rails.configuration.cache_classes ? require(c) : load(c)
end
end
end
end
end
+
+require 'spree/core/routes'
diff --git a/core/lib/spree/core/environment.rb b/core/lib/spree/core/environment.rb
index 443b110c0a6..7ff2f0330b7 100644
--- a/core/lib/spree/core/environment.rb
+++ b/core/lib/spree/core/environment.rb
@@ -3,7 +3,8 @@ module Core
class Environment
include EnvironmentExtension
- attr_accessor :calculators, :payment_methods, :preferences
+ attr_accessor :calculators, :payment_methods, :preferences,
+ :stock_splitters
def initialize
@calculators = Calculators.new
diff --git a/core/lib/spree/core/ext/active_record.rb b/core/lib/spree/core/ext/active_record.rb
deleted file mode 100644
index 233a6232ccd..00000000000
--- a/core/lib/spree/core/ext/active_record.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveRecord::Persistence
-
- # Update attributes of a record in the database without callbacks, validations etc.
- def update_attributes_without_callbacks(attributes)
- self.assign_attributes(attributes, :without_protection => true)
- self.class.update_all(attributes, { :id => id })
- end
-
- # Update a single attribute in the database
- def update_attribute_without_callbacks(name, value)
- send("#{name}=", value)
- update_attributes_without_callbacks(name => value)
- end
-
-end
diff --git a/core/lib/spree/core/gateway_error.rb b/core/lib/spree/core/gateway_error.rb
deleted file mode 100644
index 5a6ddda30b7..00000000000
--- a/core/lib/spree/core/gateway_error.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Spree
- module Core
- class GatewayError < RuntimeError; end
- end
-end
diff --git a/core/lib/spree/core/importer.rb b/core/lib/spree/core/importer.rb
new file mode 100644
index 00000000000..e71b9e0d5ba
--- /dev/null
+++ b/core/lib/spree/core/importer.rb
@@ -0,0 +1,9 @@
+module Spree
+ module Core
+ module Importer
+ end
+ end
+end
+
+require 'spree/core/importer/order'
+require 'spree/core/importer/product'
diff --git a/core/lib/spree/core/importer/order.rb b/core/lib/spree/core/importer/order.rb
new file mode 100644
index 00000000000..8260c23fd58
--- /dev/null
+++ b/core/lib/spree/core/importer/order.rb
@@ -0,0 +1,236 @@
+module Spree
+ module Core
+ module Importer
+ class Order
+
+ def self.import(user, params)
+ begin
+ ensure_country_id_from_params params[:ship_address_attributes]
+ ensure_state_id_from_params params[:ship_address_attributes]
+ ensure_country_id_from_params params[:bill_address_attributes]
+ ensure_state_id_from_params params[:bill_address_attributes]
+
+ create_params = params.slice :currency
+ order = Spree::Order.create! create_params
+ order.associate_user!(user)
+
+ shipments_attrs = params.delete(:shipments_attributes)
+
+ create_shipments_from_params(shipments_attrs, order)
+ create_line_items_from_params(params.delete(:line_items_attributes),order)
+ create_shipments_from_params(params.delete(:shipments_attributes), order)
+ create_adjustments_from_params(params.delete(:adjustments_attributes), order)
+ create_payments_from_params(params.delete(:payments_attributes), order)
+
+ if completed_at = params.delete(:completed_at)
+ order.completed_at = completed_at
+ order.state = 'complete'
+ end
+
+ params.delete(:user_id) unless user.try(:has_spree_role?, "admin") && params.key?(:user_id)
+
+ order.update_attributes!(params)
+
+ order.create_proposed_shipments unless shipments_attrs.present?
+
+ # Really ensure that the order totals & states are correct
+ order.updater.update
+ if shipments_attrs.present?
+ order.shipments.each_with_index do |shipment, index|
+ shipment.update_columns(cost: shipments_attrs[index][:cost].to_f) if shipments_attrs[index][:cost].present?
+ end
+ end
+ order.reload
+ rescue Exception => e
+ order.destroy if order && order.persisted?
+ raise e.message
+ end
+ end
+
+ def self.create_shipments_from_params(shipments_hash, order)
+ return [] unless shipments_hash
+
+ line_items = order.line_items
+ shipments_hash.each do |s|
+ begin
+ shipment = order.shipments.build
+ shipment.tracking = s[:tracking]
+ shipment.stock_location = Spree::StockLocation.find_by_admin_name(s[:stock_location]) || Spree::StockLocation.find_by_name!(s[:stock_location])
+
+ inventory_units = s[:inventory_units] || []
+ inventory_units.each do |iu|
+ ensure_variant_id_from_params(iu)
+
+ unit = shipment.inventory_units.build
+ unit.order = order
+
+ # Spree expects a Inventory Unit to always reference a line
+ # item and variant otherwise users might get exceptions when
+ # trying to view these units. Note the Importer might not be
+ # able to find the line item if line_item.variant_id |= iu.variant_id
+ unit.variant_id = iu[:variant_id]
+ unit.line_item_id = line_items.select do |l|
+ l.variant_id.to_i == iu[:variant_id].to_i
+ end.first.try(:id)
+ end
+
+ # Mark shipped if it should be.
+ if s[:shipped_at].present?
+ shipment.shipped_at = s[:shipped_at]
+ shipment.state = 'shipped'
+ shipment.inventory_units.each do |unit|
+ unit.state = 'shipped'
+ end
+ end
+
+ shipment.save!
+
+ shipping_method = Spree::ShippingMethod.find_by_name(s[:shipping_method]) || Spree::ShippingMethod.find_by_admin_name!(s[:shipping_method])
+ rate = shipment.shipping_rates.create!(:shipping_method => shipping_method,
+ :cost => s[:cost])
+ shipment.selected_shipping_rate_id = rate.id
+ shipment.update_amounts
+
+ rescue Exception => e
+ raise "Order import shipments: #{e.message} #{s}"
+ end
+ end
+ end
+
+ def self.create_line_items_from_params(line_items_hash, order)
+ return {} unless line_items_hash
+ line_items_hash.each_key do |k|
+ begin
+ extra_params = line_items_hash[k].except(:variant_id, :quantity, :sku)
+ line_item = ensure_variant_id_from_params(line_items_hash[k])
+ line_item = order.contents.add(Spree::Variant.find(line_item[:variant_id]), line_item[:quantity])
+ # Raise any errors with saving to prevent import succeeding with line items failing silently.
+ if extra_params.present?
+ line_item.update_attributes!(extra_params)
+ else
+ line_item.save!
+ end
+ rescue Exception => e
+ raise "Order import line items: #{e.message} #{line_item}"
+ end
+ end
+ end
+
+ def self.create_adjustments_from_params(adjustments, order)
+ return [] unless adjustments
+ adjustments.each do |a|
+ begin
+ adjustment = order.adjustments.build(
+ order: order,
+ amount: a[:amount].to_f,
+ label: a[:label]
+ )
+ adjustment.save!
+ adjustment.close!
+ rescue Exception => e
+ raise "Order import adjustments: #{e.message} #{a}"
+ end
+ end
+ end
+
+ def self.create_payments_from_params(payments_hash, order)
+ return [] unless payments_hash
+ payments_hash.each do |p|
+ begin
+ payment = order.payments.build order: order
+ payment.amount = p[:amount].to_f
+ # Order API should be using state as that's the normal payment field.
+ # spree_wombat serializes payment state as status so imported orders should fall back to status field.
+ payment.state = p[:state] || p[:status] || 'completed'
+ payment.payment_method = Spree::PaymentMethod.find_by_name!(p[:payment_method])
+ payment.source = create_source_payment_from_params(p[:source], payment) if p[:source]
+ payment.save!
+ rescue Exception => e
+ raise "Order import payments: #{e.message} #{p}"
+ end
+ end
+ end
+
+ def self.create_source_payment_from_params(source_hash, payment)
+ begin
+ Spree::CreditCard.create(
+ month: source_hash[:month],
+ year: source_hash[:year],
+ cc_type: source_hash[:cc_type],
+ last_digits: source_hash[:last_digits],
+ name: source_hash[:name],
+ payment_method: payment.payment_method,
+ gateway_customer_profile_id: source_hash[:gateway_customer_profile_id],
+ gateway_payment_profile_id: source_hash[:gateway_payment_profile_id],
+ imported: true
+ )
+ rescue Exception => e
+ raise "Order import source payments: #{e.message} #{source_hash}"
+ end
+ end
+
+ def self.ensure_variant_id_from_params(hash)
+ begin
+ sku = hash.delete(:sku)
+ unless hash[:variant_id].present?
+ hash[:variant_id] = Spree::Variant.active.find_by_sku!(sku).id
+ end
+ hash
+ rescue ActiveRecord::RecordNotFound => e
+ raise "Ensure order import variant: Variant w/SKU #{sku} not found."
+ rescue Exception => e
+ raise "Ensure order import variant: #{e.message} #{hash}"
+ end
+ end
+
+ def self.ensure_country_id_from_params(address)
+ return if address.nil? or address[:country_id].present? or address[:country].nil?
+
+ begin
+ search = {}
+ if name = address[:country]['name']
+ search[:name] = name
+ elsif iso_name = address[:country]['iso_name']
+ search[:iso_name] = iso_name.upcase
+ elsif iso = address[:country]['iso']
+ search[:iso] = iso.upcase
+ elsif iso3 = address[:country]['iso3']
+ search[:iso3] = iso3.upcase
+ end
+
+ address.delete(:country)
+ address[:country_id] = Spree::Country.where(search).first!.id
+
+ rescue Exception => e
+ raise "Ensure order import address country: #{e.message} #{search}"
+ end
+ end
+
+ def self.ensure_state_id_from_params(address)
+ return if address.nil? or address[:state_id].present? or address[:state].nil?
+
+ begin
+ search = {}
+ if name = address[:state]['name']
+ search[:name] = name
+ elsif abbr = address[:state]['abbr']
+ search[:abbr] = abbr.upcase
+ end
+
+ address.delete(:state)
+ search[:country_id] = address[:country_id]
+
+ if state = Spree::State.where(search).first
+ address[:state_id] = state.id
+ else
+ address[:state_name] = search[:name] || search[:abbr]
+ end
+ rescue Exception => e
+ raise "Ensure order import address state: #{e.message} #{search}"
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/importer/product.rb b/core/lib/spree/core/importer/product.rb
new file mode 100644
index 00000000000..b7875a0e9f0
--- /dev/null
+++ b/core/lib/spree/core/importer/product.rb
@@ -0,0 +1,62 @@
+module Spree
+ module Core
+ module Importer
+ class Product
+ attr_reader :product, :product_attrs, :variants_attrs, :options_attrs
+
+ def initialize(product, product_params, options = {})
+ @product = product || Spree::Product.new(product_params)
+
+ @product_attrs = product_params
+ @variants_attrs = options[:variants_attrs] || []
+ @options_attrs = options[:options_attrs] || []
+ end
+
+ def create
+ if product.save
+ variants_attrs.each do |variant_attribute|
+ # make sure the product is assigned before the options=
+ product.variants.create({ product: product }.merge(variant_attribute))
+ end
+
+ set_up_options
+ end
+
+ product
+ end
+
+ def update
+ if product.update_attributes(product_attrs)
+ variants_attrs.each do |variant_attribute|
+ # update the variant if the id is present in the payload
+ if variant_attribute['id'].present?
+ product.variants.find(variant_attribute['id'].to_i).update_attributes(variant_attribute)
+ else
+ # make sure the product is assigned before the options=
+ product.variants.create({ product: product }.merge(variant_attribute))
+ end
+ end
+
+ set_up_options
+ end
+
+ product
+ end
+
+ private
+ def set_up_options
+ options_attrs.each do |name|
+ option_type = Spree::OptionType.where(name: name).first_or_initialize do |option_type|
+ option_type.presentation = name
+ option_type.save!
+ end
+
+ unless product.option_types.include?(option_type)
+ product.option_types << option_type
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/mail_interceptor.rb b/core/lib/spree/core/mail_interceptor.rb
deleted file mode 100644
index 3a45b7ef5d2..00000000000
--- a/core/lib/spree/core/mail_interceptor.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# Allows us to intercept any outbound mail message and make last minute changes (such as specifying a "from" address or
-# sending to a test email account.)
-#
-# See http://railscasts.com/episodes/206-action-mailer-in-rails-3 for more details.
-module Spree
- module Core
- class MailInterceptor
-
- def self.delivering_email(message)
- return unless mail_method = Spree::MailMethod.current
- message.from ||= mail_method.preferred_mails_from
-
- if mail_method.preferred_intercept_email.present?
- message.subject = "[#{message.to}] #{message.subject}"
- message.to = mail_method.preferred_intercept_email
- end
-
- if mail_method.preferred_mail_bcc.present?
- message.bcc ||= mail_method.preferred_mail_bcc
- end
- end
-
- end
- end
-end
diff --git a/core/lib/spree/core/mail_settings.rb b/core/lib/spree/core/mail_settings.rb
deleted file mode 100644
index 6badb9e4770..00000000000
--- a/core/lib/spree/core/mail_settings.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Spree
- module Core
- module MailSettings
-
- # Override the Rails application mail settings based on preference.
- # This makes it possible to configure the mail settings
- # through an admin interface instead of requiring changes to the Rails envrionment file.
- def self.init
- ActionMailer::Base.default_url_options[:host] = Spree::Config[:site_url]
- return unless mail_method = Spree::MailMethod.current
- if mail_method.prefers_enable_mail_delivery?
- mail_server_settings = {
- :address => mail_method.preferred_mail_host,
- :domain => mail_method.preferred_mail_domain,
- :port => mail_method.preferred_mail_port,
- :authentication => mail_method.preferred_mail_auth_type
- }
-
- if mail_method.preferred_mail_auth_type != 'none'
- mail_server_settings[:user_name] = mail_method.preferred_smtp_username
- mail_server_settings[:password] = mail_method.preferred_smtp_password
- end
-
- tls = mail_method.preferred_secure_connection_type == 'TLS'
- mail_server_settings[:enable_starttls_auto] = tls
-
- ActionMailer::Base.smtp_settings = mail_server_settings
- ActionMailer::Base.perform_deliveries = true
- else
- #logger.warn "NOTICE: Mail not enabled"
- ActionMailer::Base.perform_deliveries = false
- end
- end
-
- end
- end
-end
diff --git a/core/lib/spree/core/middleware/redirect_legacy_product_url.rb b/core/lib/spree/core/middleware/redirect_legacy_product_url.rb
deleted file mode 100644
index 9e888cf318b..00000000000
--- a/core/lib/spree/core/middleware/redirect_legacy_product_url.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module Spree
- module Core
- module Middleware
- class RedirectLegacyProductUrl
- def initialize(app)
- @app = app
- end
-
- def call(env)
- if env["PATH_INFO"] =~ %r{/t/.+/p/(.+)}
- return [301, {'Location'=> "/products/#{$1}" }, []]
- end
- @app.call(env)
- end
- end
- end
- end
-end
diff --git a/core/lib/spree/core/permalinks.rb b/core/lib/spree/core/permalinks.rb
index 094223cfcce..e875a94f978 100644
--- a/core/lib/spree/core/permalinks.rb
+++ b/core/lib/spree/core/permalinks.rb
@@ -14,11 +14,7 @@ def make_permalink(options={})
options[:field] ||= :permalink
self.permalink_options = options
- validates permalink_options[:field], :uniqueness => true
-
- if self.table_exists? && self.column_names.include?(permalink_options[:field].to_s)
- before_validation(:on => :create) { save_permalink }
- end
+ before_validation(:on => :create) { save_permalink }
end
def find_by_param(value, *args)
@@ -33,27 +29,43 @@ def permalink_field
permalink_options[:field]
end
+ def permalink_prefix
+ permalink_options[:prefix] || ""
+ end
+
+ def permalink_length
+ permalink_options[:length] || 9
+ end
+
def permalink_order
order = permalink_options[:order]
"#{order} ASC," if order
end
end
+ def generate_permalink
+ "#{self.class.permalink_prefix}#{Array.new(self.class.permalink_length){rand(9)}.join}"
+ end
+
def save_permalink(permalink_value=self.to_param)
- field = self.class.permalink_field
- # Do other links exist with this permalink?
- other = self.class.where("#{field} LIKE ?", "#{permalink_value}%")
- if other.any?
- # Find the existing permalink with the highest number, and increment that number.
- # (If none of the existing permalinks have a number, this will evaluate to 1.)
- number = other.map { |o| o.send(field)[/-(\d+)$/, 1].to_i }.max + 1
- permalink_value += "-#{number.to_s}"
- end
- write_attribute(field, permalink_value)
+ self.with_lock do
+ permalink_value ||= generate_permalink
+
+ field = self.class.permalink_field
+ # Do other links exist with this permalink?
+ other = self.class.where("#{self.class.table_name}.#{field} LIKE ?", "#{permalink_value}%")
+ if other.any?
+ # Find the existing permalink with the highest number, and increment that number.
+ # (If none of the existing permalinks have a number, this will evaluate to 1.)
+ number = other.map { |o| o.send(field)[/-(\d+)$/, 1].to_i }.max + 1
+ permalink_value += "-#{number.to_s}"
+ end
+ write_attribute(field, permalink_value)
+ end
end
end
end
end
ActiveRecord::Base.send :include, Spree::Core::Permalinks
-ActiveRecord::Relation.send :include, Spree::Core::Permalinks
+ActiveRecord::Relation.send :include, Spree::Core::Permalinks
\ No newline at end of file
diff --git a/core/lib/spree/core/product_duplicator.rb b/core/lib/spree/core/product_duplicator.rb
new file mode 100644
index 00000000000..8615683cab2
--- /dev/null
+++ b/core/lib/spree/core/product_duplicator.rb
@@ -0,0 +1,74 @@
+module Spree
+ class ProductDuplicator
+ attr_accessor :product
+
+ @@clone_images_default = true
+ mattr_accessor :clone_images_default
+
+ def initialize(product, include_images = @@clone_images_default)
+ @product = product
+ @include_images = include_images
+ end
+
+ def duplicate
+ new_product = duplicate_product
+
+ # don't dup the actual variants, just the characterising types
+ new_product.option_types = product.option_types if product.has_variants?
+
+ # allow site to do some customization
+ new_product.send(:duplicate_extra, product) if new_product.respond_to?(:duplicate_extra)
+ new_product.save!
+ new_product
+ end
+
+ protected
+
+ def duplicate_product
+ product.dup.tap do |new_product|
+ new_product.name = "COPY OF #{product.name}"
+ new_product.taxons = product.taxons
+ new_product.created_at = nil
+ new_product.deleted_at = nil
+ new_product.updated_at = nil
+ new_product.product_properties = reset_properties
+ new_product.master = duplicate_master
+ new_product.variants = product.variants.map { |variant| duplicate_variant variant }
+ end
+ end
+
+ def duplicate_master
+ master = product.master
+ master.dup.tap do |new_master|
+ new_master.sku = "COPY OF #{master.sku}"
+ new_master.deleted_at = nil
+ new_master.images = master.images.map { |image| duplicate_image image } if @include_images
+ new_master.price = master.price
+ new_master.currency = master.currency
+ end
+ end
+
+ def duplicate_variant(variant)
+ new_variant = variant.dup
+ new_variant.sku = "COPY OF #{new_variant.sku}"
+ new_variant.deleted_at = nil
+ new_variant.option_values = variant.option_values.map { |option_value| option_value}
+ new_variant
+ end
+
+ def duplicate_image(image)
+ new_image = image.dup
+ new_image.assign_attributes(:attachment => image.attachment.clone)
+ new_image
+ end
+
+ def reset_properties
+ product.product_properties.map do |prop|
+ prop.dup.tap do |new_prop|
+ new_prop.created_at = nil
+ new_prop.updated_at = nil
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/product_filters.rb b/core/lib/spree/core/product_filters.rb
new file mode 100644
index 00000000000..81e11ccaa9b
--- /dev/null
+++ b/core/lib/spree/core/product_filters.rb
@@ -0,0 +1,194 @@
+module Spree
+ module Core
+ # THIS FILE SHOULD BE OVER-RIDDEN IN YOUR SITE EXTENSION!
+ # the exact code probably won't be useful, though you're welcome to modify and reuse
+ # the current contents are mainly for testing and documentation
+
+ # To override this file...
+ # 1) Make a copy of it in your sites local /lib/spree folder
+ # 2) Add it to the config load path, or require it in an initializer, e.g...
+ #
+ # # config/initializers/spree.rb
+ # require 'spree/product_filters'
+ #
+
+ # set up some basic filters for use with products
+ #
+ # Each filter has two parts
+ # * a parametrized named scope which expects a list of labels
+ # * an object which describes/defines the filter
+ #
+ # The filter description has three components
+ # * a name, for displaying on pages
+ # * a named scope which will 'execute' the filter
+ # * a mapping of presentation labels to the relevant condition (in the context of the named scope)
+ # * an optional list of labels and values (for use with object selection - see taxons examples below)
+ #
+ # The named scopes here have a suffix '_any', following Ransack's convention for a
+ # scope which returns results which match any of the inputs. This is purely a convention,
+ # but might be a useful reminder.
+ #
+ # When creating a form, the name of the checkbox group for a filter F should be
+ # the name of F's scope with [] appended, eg "price_range_any[]", and for
+ # each label you should have a checkbox with the label as its value. On submission,
+ # Rails will send the action a hash containing (among other things) an array named
+ # after the scope whose values are the active labels.
+ #
+ # Ransack will then convert this array to a call to the named scope with the array
+ # contents, and the named scope will build a query with the disjunction of the conditions
+ # relating to the labels, all relative to the scope's context.
+ #
+ # The details of how/when filters are used is a detail for specific models (eg products
+ # or taxons), eg see the taxon model/controller.
+
+ # See specific filters below for concrete examples.
+ module ProductFilters
+ # Example: filtering by price
+ # The named scope just maps incoming labels onto their conditions, and builds the conjunction
+ # 'price' is in the base scope's context (ie, "select foo from products where ...") so
+ # we can access the field right away
+ # The filter identifies which scope to use, then sets the conditions for each price range
+ #
+ # If user checks off three different price ranges then the argument passed to
+ # below scope would be something like ["$10 - $15", "$15 - $18", "$18 - $20"]
+ #
+ Spree::Product.add_search_scope :price_range_any do |*opts|
+ conds = opts.map {|o| Spree::Core::ProductFilters.price_filter[:conds][o]}.reject { |c| c.nil? }
+ scope = conds.shift
+ conds.each do |new_scope|
+ scope = scope.or(new_scope)
+ end
+ Spree::Product.joins(master: :default_price).where(scope)
+ end
+
+ def ProductFilters.format_price(amount)
+ Spree::Money.new(amount)
+ end
+
+ def ProductFilters.price_filter
+ v = Spree::Price.arel_table
+ conds = [ [ Spree.t(:under_price, price: format_price(10)) , v[:amount].lteq(10)],
+ [ "#{format_price(10)} - #{format_price(15)}" , v[:amount].in(10..15)],
+ [ "#{format_price(15)} - #{format_price(18)}" , v[:amount].in(15..18)],
+ [ "#{format_price(18)} - #{format_price(20)}" , v[:amount].in(18..20)],
+ [ Spree.t(:or_over_price, price: format_price(20)) , v[:amount].gteq(20)]]
+ {
+ name: Spree.t(:price_range),
+ scope: :price_range_any,
+ conds: Hash[*conds.flatten],
+ labels: conds.map { |k,v| [k, k] }
+ }
+ end
+
+
+ # Example: filtering by possible brands
+ #
+ # First, we define the scope. Two interesting points here: (a) we run our conditions
+ # in the scope where the info for the 'brand' property has been loaded; and (b)
+ # because we may want to filter by other properties too, we give this part of the
+ # query a unique name (which must be used in the associated conditions too).
+ #
+ # Secondly, the filter. Instead of a static list of values, we pull out all existing
+ # brands from the db, and then build conditions which test for string equality on
+ # the (uniquely named) field "p_brand.value". There's also a test for brand info
+ # being blank: note that this relies on with_property doing a left outer join
+ # rather than an inner join.
+ Spree::Product.add_search_scope :brand_any do |*opts|
+ conds = opts.map {|o| ProductFilters.brand_filter[:conds][o]}.reject { |c| c.nil? }
+ scope = conds.shift
+ conds.each do |new_scope|
+ scope = scope.or(new_scope)
+ end
+ Spree::Product.with_property('brand').where(scope)
+ end
+
+ def ProductFilters.brand_filter
+ brand_property = Spree::Property.find_by(name: 'brand')
+ brands = brand_property ? Spree::ProductProperty.where(property_id: brand_property.id).pluck(:value).uniq.map(&:to_s) : []
+ pp = Spree::ProductProperty.arel_table
+ conds = Hash[*brands.map { |b| [b, pp[:value].eq(b)] }.flatten]
+ {
+ name: 'Brands',
+ scope: :brand_any,
+ conds: conds,
+ labels: (brands.sort).map { |k| [k, k] }
+ }
+ end
+
+ # Example: a parameterized filter
+ # The filter above may show brands which aren't applicable to the current taxon,
+ # so this one only shows the brands that are relevant to a particular taxon and
+ # its descendants.
+ #
+ # We don't have to give a new scope since the conditions here are a subset of the
+ # more general filter, so decoding will still work - as long as the filters on a
+ # page all have unique names (ie, you can't use the two brand filters together
+ # if they use the same scope). To be safe, the code uses a copy of the scope.
+ #
+ # HOWEVER: what happens if we want a more precise scope? we can't pass
+ # parametrized scope names to Ransack, only atomic names, so couldn't ask
+ # for taxon T's customized filter to be used. BUT: we can arrange for the form
+ # to pass back a hash instead of an array, where the key acts as the (taxon)
+ # parameter and value is its label array, and then get a modified named scope
+ # to get its conditions from a particular filter.
+ #
+ # The brand-finding code can be simplified if a few more named scopes were added to
+ # the product properties model.
+ Spree::Product.add_search_scope :selective_brand_any do |*opts|
+ Spree::Product.brand_any(*opts)
+ end
+
+ def ProductFilters.selective_brand_filter(taxon = nil)
+ taxon ||= Spree::Taxonomy.first.root
+ brand_property = Spree::Property.find_by(name: 'brand')
+ scope = Spree::ProductProperty.where(property: brand_property).
+ joins(product: :taxons).
+ where("#{Spree::Taxon.table_name}.id" => [taxon] + taxon.descendants)
+ brands = scope.pluck(:value).uniq
+ {
+ name: 'Applicable Brands',
+ scope: :selective_brand_any,
+ labels: brands.sort.map { |k| [k, k] }
+ }
+ end
+
+ # Provide filtering on the immediate children of a taxon
+ #
+ # This doesn't fit the pattern of the examples above, so there's a few changes.
+ # Firstly, it uses an existing scope which was not built for filtering - and so
+ # has no need of a conditions mapping, and secondly, it has a mapping of name
+ # to the argument type expected by the other scope.
+ #
+ # This technique is useful for filtering on objects (by passing ids) or with a
+ # scope that can be used directly (eg. testing only ever on a single property).
+ #
+ # This scope selects products in any of the active taxons or their children.
+ #
+ def ProductFilters.taxons_below(taxon)
+ return Spree::Core::ProductFilters.all_taxons if taxon.nil?
+ {
+ name: 'Taxons under ' + taxon.name,
+ scope: :taxons_id_in_tree_any,
+ labels: taxon.children.sort_by(&:position).map { |t| [t.name, t.id] },
+ conds: nil
+ }
+ end
+
+ # Filtering by the list of all taxons
+ #
+ # Similar idea as above, but we don't want the descendants' products, hence
+ # it uses one of the auto-generated scopes from Ransack.
+ #
+ # idea: expand the format to allow nesting of labels?
+ def ProductFilters.all_taxons
+ taxons = Spree::Taxonomy.all.map { |t| [t.root] + t.root.descendants }.flatten
+ {
+ name: 'All taxons',
+ scope: :taxons_id_equals_any,
+ labels: taxons.sort_by(&:name).map { |t| [t.name, t.id] },
+ conds: nil # not needed
+ }
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/routes.rb b/core/lib/spree/core/routes.rb
new file mode 100644
index 00000000000..a1f568090bb
--- /dev/null
+++ b/core/lib/spree/core/routes.rb
@@ -0,0 +1,46 @@
+module Spree
+ module Core
+ class Engine < ::Rails::Engine
+ def self.add_routes(&block)
+ @spree_routes ||= []
+
+ # Anything that causes the application's routes to be reloaded,
+ # will cause this method to be called more than once
+ # i.e. https://github.com/plataformatec/devise/blob/31971e69e6a1bcf6c7f01eaaa44f227c4af5d4d2/lib/devise/rails.rb#L14
+ # In the case of Devise, this *only* happens in the production env
+ # This coupled with Rails 4's insistence that routes are not drawn twice,
+ # poses quite a serious problem.
+ #
+ # This is mainly why this whole file exists in the first place.
+ #
+ # Thus we need to make sure that the routes aren't drawn twice.
+ unless @spree_routes.include?(block)
+ @spree_routes << block
+ end
+ end
+
+ def self.append_routes(&block)
+ @append_routes ||= []
+ # See comment in add_routes.
+ unless @append_routes.include?(block)
+ @append_routes << block
+ end
+ end
+
+ def self.draw_routes(&block)
+ @spree_routes ||= []
+ @append_routes ||= []
+ eval_block(block) if block_given?
+ @spree_routes.each { |r| eval_block(&r) }
+ @append_routes.each { |r| eval_block(&r) }
+ # # Clear out routes so that they aren't drawn twice.
+ @spree_routes = []
+ @append_routes = []
+ end
+
+ def eval_block(&block)
+ Spree::Core::Engine.routes.eval_block(block)
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/core/s3_support.rb b/core/lib/spree/core/s3_support.rb
deleted file mode 100644
index 60f6f93fd7b..00000000000
--- a/core/lib/spree/core/s3_support.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Spree
- module Core
- # This module exists to reduce duplication in S3 settings between
- # the Image and Taxon models in Spree
- module S3Support
- extend ActiveSupport::Concern
-
- included do
- def self.supports_s3(field)
- # Load user defined paperclip settings
- config = Spree::Config
- if config[:use_s3]
- s3_creds = { :access_key_id => config[:s3_access_key], :secret_access_key => config[:s3_secret], :bucket => config[:s3_bucket] }
- self.attachment_definitions[field][:storage] = :s3
- self.attachment_definitions[field][:s3_credentials] = s3_creds
- self.attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.decode(config[:s3_headers])
- self.attachment_definitions[field][:bucket] = config[:s3_bucket]
- self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol] unless config[:s3_protocol].blank?
- self.attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias] unless config[:s3_host_alias].blank?
- end
- end
- end
- end
- end
-end
diff --git a/core/lib/spree/core/scopes.rb b/core/lib/spree/core/scopes.rb
deleted file mode 100644
index 32e9799edb2..00000000000
--- a/core/lib/spree/core/scopes.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module Spree
- module Core
- # This module contains all custom scopes created for selecting products.
- #
- # All usable scopes *should* be included in SCOPES constant, it represents
- # all scopes that are selectable from user interface, extensions can extend
- # and modify it, but should provide corresponding translations.
- #
- # Format of constant is following:
- #
- # {
- # :namespace/grouping => {
- # :name_of_the_scope => [:list, :of, :arguments]
- # }
- # }
- #
- # This values are used in translation file, to describe them in the interface.
- # So for each scope you define here you have to provide following entry in translation file
- # product_scopes:
- # name_of_the_group:
- # name: Translated name of the group
- # description: Longer description of what this scope group does, inluding
- # any possible help user may need
- # scopes:
- # name_of_the_scope:
- # name: Short name of the scope
- # description: What does this scope does exactly
- # arguments:
- # arg1: Description of argument
- # arg2: Description of second Argument
- #
- module Scopes
- module_function
-
- def generate_translation(all_scopes)
- result = {"groups" => {}, "scopes" => {}}
- all_scopes.dup.each_pair do |group_name, scopes|
- result["groups"][group_name.to_s] = {
- 'name' => group_name.to_s.humanize,
- 'description' => "Scopes for selecting products based on #{group_name.to_s}",
- }
-
- scopes.each_pair do |scope_name, targs|
- hashed_args = {}
- targs.each{|v| hashed_args[v.to_s] = v.to_s.humanize}
-
- result['scopes'][scope_name.to_s] = {
- 'name' => scope_name.to_s.humanize,
- 'description' => "",
- 'args' => hashed_args.dup
- }
- end
- end
- result
- end
-
- def generate_translations
- require 'ya2yaml'
- {
- 'product_scopes' => generate_translation(Spree::ProductScope.all_scopes)
- }.ya2yaml
- end
- end
- end
-end
diff --git a/core/lib/spree/core/search/base.rb b/core/lib/spree/core/search/base.rb
index 1781ad02794..f90dc352b8c 100644
--- a/core/lib/spree/core/search/base.rb
+++ b/core/lib/spree/core/search/base.rb
@@ -13,10 +13,13 @@ def initialize(params)
end
def retrieve_products
- @products_scope = get_base_scope
+ @products = get_base_scope
curr_page = page || 1
- @products = @products_scope.includes([:master => :prices]).where("spree_prices.amount IS NOT NULL").where("spree_prices.currency" => current_currency).page(curr_page).per(per_page)
+ unless Spree::Config.show_products_without_price
+ @products = @products.where("spree_prices.amount IS NOT NULL").where("spree_prices.currency" => current_currency)
+ end
+ @products = @products.page(curr_page).per(per_page)
end
def method_missing(name)
@@ -32,18 +35,39 @@ def get_base_scope
base_scope = Spree::Product.active
base_scope = base_scope.in_taxon(taxon) unless taxon.blank?
base_scope = get_products_conditions_for(base_scope, keywords)
- base_scope = base_scope.on_hand unless Spree::Config[:show_zero_stock_products]
base_scope = add_search_scopes(base_scope)
+ base_scope = add_eagerload_scopes(base_scope)
base_scope
end
+ def add_eagerload_scopes scope
+ # TL;DR Switch from `preload` to `includes` as soon as Rails starts honoring
+ # `order` clauses on `has_many` associations when a `where` constraint
+ # affecting a joined table is present (see
+ # https://github.com/rails/rails/issues/6769).
+ #
+ # Ideally this would use `includes` instead of `preload` calls, leaving it
+ # up to Rails whether associated objects should be fetched in one big join
+ # or multiple independent queries. However as of Rails 4.1.8 any `order`
+ # defined on `has_many` associations are ignored when Rails builds a join
+ # query.
+ #
+ # Would we use `includes` in this particular case, Rails would do
+ # separate queries most of the time but opt for a join as soon as any
+ # `where` constraints affecting joined tables are added to the search;
+ # which is the case as soon as a taxon is added to the base scope.
+ scope = scope.preload(master: :prices)
+ scope = scope.preload(master: :images) if include_images
+ scope
+ end
+
def add_search_scopes(base_scope)
search.each do |name, scope_attribute|
scope_name = name.to_sym
if base_scope.respond_to?(:search_scopes) && base_scope.search_scopes.include?(scope_name.to_sym)
base_scope = base_scope.send(scope_name, *scope_attribute)
else
- base_scope = base_scope.merge(Spree::Product.search({scope_name => scope_attribute}).result)
+ base_scope = base_scope.merge(Spree::Product.ransack({scope_name => scope_attribute}).result)
end
end if search
base_scope
@@ -61,6 +85,7 @@ def prepare(params)
@properties[:taxon] = params[:taxon].blank? ? nil : Spree::Taxon.find(params[:taxon])
@properties[:keywords] = params[:keywords]
@properties[:search] = params[:search]
+ @properties[:include_images] = params[:include_images]
per_page = params[:per_page].to_i
@properties[:per_page] = per_page > 0 ? per_page : Spree::Config[:products_per_page]
diff --git a/core/lib/spree/core/ssl_requirement.rb b/core/lib/spree/core/ssl_requirement.rb
deleted file mode 100644
index eecc0bc9faf..00000000000
--- a/core/lib/spree/core/ssl_requirement.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# ++
-# Copyright (c) 2007-2012, Spree Commerce, Inc. and other contributors
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-# * Neither the name of the Spree Commerce, Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from this
-# software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# --
-
-# ++
-# Copyright (c) 2005 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-# --
-
-# Modified version of the ssl_requirement plugin by DHH
-module SslRequirement
- extend ActiveSupport::Concern
-
- included do
- before_filter(:ensure_proper_protocol)
- end
-
- module ClassMethods
- # Specifies that the named actions requires an SSL connection to be performed (which is enforced by ensure_proper_protocol).
- def ssl_required(*actions)
- class_attribute(:ssl_required_actions)
- self.ssl_required_actions = actions
- end
-
- def ssl_allowed(*actions)
- class_attribute(:ssl_allowed_actions)
- self.ssl_allowed_actions = actions
- end
- end
-
- protected
- # Returns true if the current action is supposed to run as SSL
- def ssl_required?
- if self.class.respond_to?(:ssl_required_actions)
- actions = self.class.ssl_required_actions
- actions.empty? || actions.include?(action_name.to_sym)
- else
- return false
- end
- end
-
- def ssl_allowed?
- if self.class.respond_to?(:ssl_allowed_actions)
- actions = self.class.ssl_allowed_actions
- actions.empty? || actions.include?(action_name.to_sym)
- else
- return false
- end
- end
-
- private
-
- def ssl_supported?
- return Spree::Config[:allow_ssl_in_production] if Rails.env.production?
- return Spree::Config[:allow_ssl_in_staging] if Rails.env.staging?
- return Spree::Config[:allow_ssl_in_development_and_test] if (Rails.env.development? or Rails.env.test?)
- end
-
- def ensure_proper_protocol
- return true if ssl_allowed?
- if ssl_required? && !request.ssl? && ssl_supported?
- redirect_to "https://" + request.host + request.fullpath
- flash.keep
- elsif request.ssl? && !ssl_required?
- redirect_to "http://" + request.host + request.fullpath
- flash.keep
- end
-
- end
-end
diff --git a/core/lib/spree/core/store_helpers.rb b/core/lib/spree/core/store_helpers.rb
deleted file mode 100644
index 20168bc22fb..00000000000
--- a/core/lib/spree/core/store_helpers.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# Methods added to this helper will be available to all templates in the application.
-module Spree
- module Core
- module StoreHelpers
-
- # helper to determine if its appropriate to show the store menu
- def store_menu?
- %w{thank_you}.exclude? params[:action]
- end
-
- end
- end
-end
diff --git a/core/lib/spree/core/testing_support/authorization_helpers.rb b/core/lib/spree/core/testing_support/authorization_helpers.rb
deleted file mode 100644
index cb40b9c3bc0..00000000000
--- a/core/lib/spree/core/testing_support/authorization_helpers.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module AuthorizationHelpers
- module Controller
- def stub_authorization!
- before do
- controller.should_receive(:authorize!).twice.and_return(true)
- end
- end
- end
-
- module Request
- class SuperAbility
- include CanCan::Ability
-
- def initialize(user)
- # allow anyone to perform index on Order
- can :manage, :all
- end
- end
-
- def stub_authorization!
- before(:all) { Spree::Ability.register_ability(AuthorizationHelpers::Request::SuperAbility) }
- after(:all) { Spree::Ability.remove_ability(AuthorizationHelpers::Request::SuperAbility) }
- end
- end
-end
-
-RSpec.configure do |config|
- config.extend AuthorizationHelpers::Controller, :type => :controller
- config.extend AuthorizationHelpers::Request, :type => :request
-end
\ No newline at end of file
diff --git a/core/lib/spree/core/testing_support/common_rake.rb b/core/lib/spree/core/testing_support/common_rake.rb
deleted file mode 100644
index 54400382eb9..00000000000
--- a/core/lib/spree/core/testing_support/common_rake.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-unless defined?(Spree::InstallGenerator)
- require 'generators/spree/install/install_generator'
-end
-
-desc "Generates a dummy app for testing"
-namespace :common do
- task :test_app, :user_class do |t, args|
- args.with_defaults(:user_class => "Spree::LegacyUser")
- require "#{ENV['LIB_NAME']}"
-
- Spree::DummyGenerator.start ["--lib_name=#{ENV['LIB_NAME']}", "--database=#{ENV['DB_NAME']}", "--quiet"]
- Spree::InstallGenerator.start ["--lib_name=#{ENV['LIB_NAME']}", "--auto-accept", "--migrate=false", "--seed=false", "--sample=false", "--quiet", "--user_class=#{args[:user_class]}"]
-
- puts "Setting up dummy database..."
- cmd = "bundle exec rake db:drop db:create db:migrate db:test:prepare"
-
- if RUBY_PLATFORM =~ /mswin/ #windows
- cmd += " >nul"
- else
- cmd += " >/dev/null"
- end
-
- system(cmd)
- end
-end
diff --git a/core/lib/spree/core/testing_support/controller_requests.rb b/core/lib/spree/core/testing_support/controller_requests.rb
deleted file mode 100644
index fdc298d35ba..00000000000
--- a/core/lib/spree/core/testing_support/controller_requests.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# Use this module to easily test Spree actions within Spree components
-# or inside your application to test routes for the mounted Spree engine.
-#
-# Inside your spec_helper.rb, include this module inside the RSpec.configure
-# block by doing this:
-#
-# require 'spree/core/testing_support/controller_requests'
-# RSpec.configure do |c|
-# c.include Spree::Core::TestingSupport::ControllerRequests, :type => :controller
-# end
-#
-# Then, in your controller tests, you can access spree routes like this:
-#
-# require 'spec_helper'
-#
-# describe Spree::ProductsController do
-# it "can see all the products" do
-# spree_get :index
-# end
-# end
-#
-# Use spree_get, spree_post, spree_put or spree_delete to make requests
-# to the Spree engine, and use regular get, post, put or delete to make
-# requests to your application.
-#
-module Spree
- module Core
- module TestingSupport
- module ControllerRequests
- def spree_get(action, parameters = nil, session = nil, flash = nil)
- process_spree_action(action, parameters, session, flash, "GET")
- end
-
- # Executes a request simulating POST HTTP method and set/volley the response
- def spree_post(action, parameters = nil, session = nil, flash = nil)
- process_spree_action(action, parameters, session, flash, "POST")
- end
-
- # Executes a request simulating PUT HTTP method and set/volley the response
- def spree_put(action, parameters = nil, session = nil, flash = nil)
- process_spree_action(action, parameters, session, flash, "PUT")
- end
-
- # Executes a request simulating DELETE HTTP method and set/volley the response
- def spree_delete(action, parameters = nil, session = nil, flash = nil)
- process_spree_action(action, parameters, session, flash, "DELETE")
- end
-
- def spree_xhr_get(action, parameters = nil, session = nil, flash = nil)
- parameters ||= {}
- parameters.reverse_merge!(:format => :json)
- parameters.merge!(:use_route => :spree)
- xml_http_request(:get, action, parameters, session, flash)
- end
-
- private
-
- def process_spree_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
- parameters ||= {}
- process(action, parameters.merge!(:use_route => :spree), session, flash, method)
- end
- end
- end
- end
-end
-
-
diff --git a/core/lib/spree/core/testing_support/factories.rb b/core/lib/spree/core/testing_support/factories.rb
deleted file mode 100644
index 43b2368c2e2..00000000000
--- a/core/lib/spree/core/testing_support/factories.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-Spree::Zone.class_eval do
- def self.global
- find_by_name("GlobalZone") || create(:global_zone)
- end
-end
-
-require 'factory_girl'
-
-Dir["#{File.dirname(__FILE__)}/factories/**"].each do |f|
- require File.expand_path(f)
-end
diff --git a/core/lib/spree/core/testing_support/factories/activator_factory.rb b/core/lib/spree/core/testing_support/factories/activator_factory.rb
deleted file mode 100644
index 934474484e1..00000000000
--- a/core/lib/spree/core/testing_support/factories/activator_factory.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-FactoryGirl.define do
- factory :activator, :class => Spree::Activator do
- name 'Activator name'
- event_name 'spree.order.contents_changed'
- starts_at 2.weeks.ago
- expires_at 2.weeks.from_now
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/address_factory.rb b/core/lib/spree/core/testing_support/factories/address_factory.rb
deleted file mode 100644
index 66797492ab5..00000000000
--- a/core/lib/spree/core/testing_support/factories/address_factory.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-FactoryGirl.define do
- factory :address, :class => Spree::Address do
- firstname 'John'
- lastname 'Doe'
- company 'Company'
- address1 '10 Lovely Street'
- address2 'Northwest'
- city 'Herndon'
- zipcode '20170'
- phone '123-456-7890'
- alternative_phone '123-456-7899'
-
- state { |address| address.association(:state) }
- country do |address|
- if address.state
- address.state.country
- else
- address.association(:country)
- end
- end
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/adjustment_factory.rb b/core/lib/spree/core/testing_support/factories/adjustment_factory.rb
deleted file mode 100644
index 433048dee14..00000000000
--- a/core/lib/spree/core/testing_support/factories/adjustment_factory.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-FactoryGirl.define do
- factory :adjustment, :class => Spree::Adjustment do
- adjustable { FactoryGirl.create(:order) }
- amount '100.0'
- label 'Shipping'
- source { FactoryGirl.create(:shipment) }
- eligible true
- end
- factory :line_item_adjustment, :class => Spree::Adjustment do
- adjustable { FactoryGirl.create(:line_item) }
- amount '10.0'
- label 'VAT 5%'
- source { FactoryGirl.create(:tax_rate) }
- eligible true
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/calculator_factory.rb b/core/lib/spree/core/testing_support/factories/calculator_factory.rb
deleted file mode 100644
index 9b49a993734..00000000000
--- a/core/lib/spree/core/testing_support/factories/calculator_factory.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-FactoryGirl.define do
- factory :calculator, :class => Spree::Calculator::FlatRate do
- after_create { |c| c.set_preference(:amount, 10.0) }
- end
-
- factory :no_amount_calculator, :class => Spree::Calculator::FlatRate do
- after_create { |c| c.set_preference(:amount, 0) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/configuration_factory.rb b/core/lib/spree/core/testing_support/factories/configuration_factory.rb
deleted file mode 100644
index d0d25fa05ad..00000000000
--- a/core/lib/spree/core/testing_support/factories/configuration_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :configuration, :class => Spree::Configuration do
- name 'Default Configuration'
- type 'app_configuration'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/country_factory.rb b/core/lib/spree/core/testing_support/factories/country_factory.rb
deleted file mode 100644
index 54a912de2f0..00000000000
--- a/core/lib/spree/core/testing_support/factories/country_factory.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-FactoryGirl.define do
- factory :country, :class => Spree::Country do
- iso_name 'UNITED STATES'
- name 'United States of Foo'
- iso 'US'
- iso3 'USA'
- numcode 840
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/credit_card_factory.rb b/core/lib/spree/core/testing_support/factories/credit_card_factory.rb
deleted file mode 100644
index 7af2667f24f..00000000000
--- a/core/lib/spree/core/testing_support/factories/credit_card_factory.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# allows credit card info to be saved to the database which is needed for factories to work properly
-class TestCard < Spree::CreditCard
- def remove_readonly_attributes(attributes) attributes; end
-end
-
-FactoryGirl.define do
- factory :credit_card, :class => TestCard do
- verification_value 123
- month 12
- year 2013
- number '4111111111111111'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/inventory_unit_factory.rb b/core/lib/spree/core/testing_support/factories/inventory_unit_factory.rb
deleted file mode 100644
index f04b701b71e..00000000000
--- a/core/lib/spree/core/testing_support/factories/inventory_unit_factory.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-FactoryGirl.define do
- factory :inventory_unit, :class => Spree::InventoryUnit do
- variant { FactoryGirl.create(:variant) }
- order { FactoryGirl.create(:order) }
- state 'sold'
- shipment { FactoryGirl.create(:shipment, :state => 'pending') }
- #return_authorization { FactoryGirl.create(:return_authorization) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/line_item_factory.rb b/core/lib/spree/core/testing_support/factories/line_item_factory.rb
deleted file mode 100644
index 7d81a35692f..00000000000
--- a/core/lib/spree/core/testing_support/factories/line_item_factory.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-FactoryGirl.define do
- factory :line_item, :class => Spree::LineItem do
- quantity 1
- price { BigDecimal.new('10.00') }
-
- # associations:
- association(:order, :factory => :order)
- association(:variant, :factory => :variant)
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/mail_method_factory.rb b/core/lib/spree/core/testing_support/factories/mail_method_factory.rb
deleted file mode 100644
index c7c2c12e19e..00000000000
--- a/core/lib/spree/core/testing_support/factories/mail_method_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :mail_method, :class => Spree::MailMethod do
- environment { Rails.env }
- active true
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/options_factory.rb b/core/lib/spree/core/testing_support/factories/options_factory.rb
deleted file mode 100644
index e32383a7afe..00000000000
--- a/core/lib/spree/core/testing_support/factories/options_factory.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-FactoryGirl.define do
- factory :option_value, :class => Spree::OptionValue do
- name 'Size'
- presentation 'S'
- option_type
- end
-
- factory :option_type, :class => Spree::OptionType do
- name 'foo-size'
- presentation 'Size'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/order_factory.rb b/core/lib/spree/core/testing_support/factories/order_factory.rb
deleted file mode 100644
index b1b24bf66fc..00000000000
--- a/core/lib/spree/core/testing_support/factories/order_factory.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-FactoryGirl.define do
- factory :order, :class => Spree::Order do
- # associations:
- association(:user, :factory => :user)
- association(:bill_address, :factory => :address)
- completed_at nil
- bill_address_id nil
- ship_address_id nil
- email 'foo@example.com'
- end
-
- factory :order_with_totals, :parent => :order do
- after_create { |order| FactoryGirl.create(:line_item, :order => order) }
- end
-
- factory :order_with_inventory_unit_shipped, :parent => :order do
- after_create do |order|
- FactoryGirl.create(:line_item, :order => order)
- FactoryGirl.create(:inventory_unit, :order => order, :state => 'shipped')
- end
- end
-
- factory :completed_order_with_totals, :parent => :order_with_totals do
- bill_address { FactoryGirl.create(:address) }
- ship_address { FactoryGirl.create(:address) }
- after_create do |order|
- FactoryGirl.create(:inventory_unit, :order => order, :state => 'shipped')
- end
- state 'complete'
- completed_at Time.now
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/payment_factory.rb b/core/lib/spree/core/testing_support/factories/payment_factory.rb
deleted file mode 100644
index 02ee59fef5f..00000000000
--- a/core/lib/spree/core/testing_support/factories/payment_factory.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-FactoryGirl.define do
- factory :payment, :class => Spree::Payment do
- amount 45.75
- payment_method { FactoryGirl.create(:bogus_payment_method) }
- source { FactoryGirl.build(:credit_card) }
- order { FactoryGirl.create(:order) }
- state 'pending'
- response_code '12345'
-
- # limit the payment amount to order's remaining balance, to avoid over-pay exceptions
- after_create do |pmt|
- #pmt.update_attribute(:amount, [pmt.amount, pmt.order.outstanding_balance].min)
- end
- end
-
- # factory :creditcard_txn do
- # payment
- # amount 45.75
- # response_code 12345
- # txn_type CreditcardTxn::TxnType::AUTHORIZE
- #
- # # match the payment amount to the payment's value
- # after_create do |txn|
- # # txn.update_attribute(:amount, [txn.amount, txn.payment.payment].min)
- # txn.update_attribute(:amount, txn.payment.amount)
- # end
- # end
-
- factory :check_payment, :class => Spree::Payment do
- amount 45.75
- payment_method { FactoryGirl.create(:payment_method) }
- order { FactoryGirl.create(:order) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/payment_method_factory.rb b/core/lib/spree/core/testing_support/factories/payment_method_factory.rb
deleted file mode 100644
index 8d9c839f68f..00000000000
--- a/core/lib/spree/core/testing_support/factories/payment_method_factory.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-FactoryGirl.define do
- factory :payment_method, :class => Spree::PaymentMethod::Check do
- name 'Check'
- environment 'test'
- end
-
- factory :bogus_payment_method, :class => Spree::Gateway::Bogus do
- name 'Credit Card'
- environment 'test'
- end
-
- # authorize.net was moved to spree_gateway. Leaving this factory
- # in place with bogus in case anyone is using it
- factory :authorize_net_payment_method, :class => Spree::Gateway::BogusSimple do
- name 'Credit Card'
- environment 'test'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/price_factory.rb b/core/lib/spree/core/testing_support/factories/price_factory.rb
deleted file mode 100644
index d59f18bf418..00000000000
--- a/core/lib/spree/core/testing_support/factories/price_factory.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-FactoryGirl.define do
- factory :price, :class => Spree::Price do
- variant :variant
- amount 19.99
- currency 'USD'
- end
-end
-
diff --git a/core/lib/spree/core/testing_support/factories/product_factory.rb b/core/lib/spree/core/testing_support/factories/product_factory.rb
deleted file mode 100644
index 8f13ecbe3b3..00000000000
--- a/core/lib/spree/core/testing_support/factories/product_factory.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-FactoryGirl.define do
- factory :simple_product, :class => Spree::Product do
- sequence(:name) { |n| "Product ##{n} - #{Kernel.rand(9999)}" }
- description { Faker::Lorem.paragraphs(1 + Kernel.rand(5)).join("\n") }
- price 19.99
- cost_price 17.00
- sku 'ABC'
- available_on 1.year.ago
- deleted_at nil
- end
-
- factory :product, :parent => :simple_product do
- tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) }
- shipping_category { |r| Spree::ShippingCategory.first || r.association(:shipping_category) }
- end
-
- factory :product_with_option_types, :parent => :product do
- after_create { |product| FactoryGirl.create(:product_option_type, :product => product) }
- end
-
- factory :custom_product, :class => Spree::Product do
- name "Custom Product"
- price "17.99"
- description { Faker::Lorem.paragraphs(1 + Kernel.rand(5)).join("\n") }
-
- # associations:
- tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) }
- shipping_category { |r| Spree::ShippingCategory.first || r.association(:shipping_category) }
-
- sku 'ABC'
- available_on 1.year.ago
- deleted_at nil
-
- association :taxons
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/product_option_type_factory.rb b/core/lib/spree/core/testing_support/factories/product_option_type_factory.rb
deleted file mode 100644
index 0321eb49cb0..00000000000
--- a/core/lib/spree/core/testing_support/factories/product_option_type_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :product_option_type, :class => Spree::ProductOptionType do
- product { FactoryGirl.create(:product) }
- option_type { FactoryGirl.create(:option_type) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/product_property_factory.rb b/core/lib/spree/core/testing_support/factories/product_property_factory.rb
deleted file mode 100644
index 385d6688e0f..00000000000
--- a/core/lib/spree/core/testing_support/factories/product_property_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :product_property, :class => Spree::ProductProperty do
- product { FactoryGirl.create(:product) }
- property { FactoryGirl.create(:property) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/property_factory.rb b/core/lib/spree/core/testing_support/factories/property_factory.rb
deleted file mode 100644
index bf3259837cc..00000000000
--- a/core/lib/spree/core/testing_support/factories/property_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :property, :class => Spree::Property do
- name 'baseball_cap_color'
- presentation 'cap color'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/prototype_factory.rb b/core/lib/spree/core/testing_support/factories/prototype_factory.rb
deleted file mode 100644
index 69e882f2301..00000000000
--- a/core/lib/spree/core/testing_support/factories/prototype_factory.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryGirl.define do
- factory :prototype, :class => Spree::Prototype do
- name 'Baseball Cap'
- properties { [FactoryGirl.create(:property)] }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/return_authorization_factory.rb b/core/lib/spree/core/testing_support/factories/return_authorization_factory.rb
deleted file mode 100644
index 4446d7fbe3f..00000000000
--- a/core/lib/spree/core/testing_support/factories/return_authorization_factory.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-FactoryGirl.define do
- factory :return_authorization, :class => Spree::ReturnAuthorization do
- number '100'
- amount 100.00
- #order { FactoryGirl.create(:order) }
- order { FactoryGirl.create(:order_with_inventory_unit_shipped) }
- reason 'no particular reason'
- state 'received'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/role_factory.rb b/core/lib/spree/core/testing_support/factories/role_factory.rb
deleted file mode 100644
index 56fcd29dcc6..00000000000
--- a/core/lib/spree/core/testing_support/factories/role_factory.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-FactoryGirl.define do
- sequence(:role_sequence) { |n| "Role ##{n}" }
-
- factory :role, :class => Spree::Role do
- name { FactoryGirl.generate :role_sequence }
- end
-
- factory :admin_role, :parent => :role do
- name 'admin'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/shipment_factory.rb b/core/lib/spree/core/testing_support/factories/shipment_factory.rb
deleted file mode 100644
index a3374b9bd7e..00000000000
--- a/core/lib/spree/core/testing_support/factories/shipment_factory.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-FactoryGirl.define do
- factory :shipment, :class => Spree::Shipment do
- order { FactoryGirl.create(:order) }
- shipping_method { FactoryGirl.create(:shipping_method) }
- tracking 'U10000'
- number '100'
- cost 100.00
- address { FactoryGirl.create(:address) }
- state 'pending'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/shipping_category_factory.rb b/core/lib/spree/core/testing_support/factories/shipping_category_factory.rb
deleted file mode 100644
index 78b3f35d4b8..00000000000
--- a/core/lib/spree/core/testing_support/factories/shipping_category_factory.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-FactoryGirl.define do
- sequence(:shipping_category_sequence) { |n| "ShippingCategory ##{n}" }
-
- factory :shipping_category, :class => Spree::ShippingCategory do
- name { FactoryGirl.generate :shipping_category_sequence }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/shipping_method_factory.rb b/core/lib/spree/core/testing_support/factories/shipping_method_factory.rb
deleted file mode 100644
index 0ac3d5922a4..00000000000
--- a/core/lib/spree/core/testing_support/factories/shipping_method_factory.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-FactoryGirl.define do
- factory :shipping_method, :class => Spree::ShippingMethod do
- zone { |a| Spree::Zone.find_by_name('GlobalZone') || a.association(:global_zone) }
- name 'UPS Ground'
- calculator { FactoryGirl.build(:calculator) }
- end
-
- factory :free_shipping_method, :class => Spree::ShippingMethod do
- zone { |a| Spree::Zone.find_by_name('GlobalZone') || a.association(:global_zone) }
- name 'UPS Ground'
- calculator { FactoryGirl.build(:no_amount_calculator) }
- end
-
- factory :shipping_method_with_category, :class => Spree::ShippingMethod do
- zone { |a| Spree::Zone.find_by_name('GlobalZone') || a.association(:global_zone) }
- name 'UPS Ground'
- match_none nil
- match_one nil
- match_all nil
- association(:shipping_category, :factory => :shipping_category)
- calculator { FactoryGirl.build(:calculator) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/tax_category_factory.rb b/core/lib/spree/core/testing_support/factories/tax_category_factory.rb
deleted file mode 100644
index 446a38f1657..00000000000
--- a/core/lib/spree/core/testing_support/factories/tax_category_factory.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-FactoryGirl.define do
- factory :tax_category, :class => Spree::TaxCategory do
- name { "TaxCategory - #{rand(999999)}" }
- description { Faker::Lorem.sentence }
- end
-
- factory :tax_category_with_rates, :parent => :tax_category do
- after_create do |tax_category|
- tax_category.tax_rates.create!({
- :amount => 0.05,
- :calculator => Spree::Calculator::DefaultTax.new,
- :zone => Spree::Zone.find_by_name('GlobalZone') || FactoryGirl.create(:global_zone)
- }, :without_protection => true)
- end
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/tax_rate_factory.rb b/core/lib/spree/core/testing_support/factories/tax_rate_factory.rb
deleted file mode 100644
index c24821a0fad..00000000000
--- a/core/lib/spree/core/testing_support/factories/tax_rate_factory.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-FactoryGirl.define do
- factory :tax_rate, :class => Spree::TaxRate do
- zone { FactoryGirl.create(:zone) }
- amount 100.00
- tax_category { FactoryGirl.create(:tax_category) }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/taxon_factory.rb b/core/lib/spree/core/testing_support/factories/taxon_factory.rb
deleted file mode 100644
index 2e09cd3dbed..00000000000
--- a/core/lib/spree/core/testing_support/factories/taxon_factory.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-FactoryGirl.define do
- factory :taxon, :class => Spree::Taxon do
- name 'Ruby on Rails'
- taxonomy { FactoryGirl.create(:taxonomy) }
- parent_id nil
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/taxonomy_factory.rb b/core/lib/spree/core/testing_support/factories/taxonomy_factory.rb
deleted file mode 100644
index c71873a9aca..00000000000
--- a/core/lib/spree/core/testing_support/factories/taxonomy_factory.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-FactoryGirl.define do
- factory :taxonomy, :class => Spree::Taxonomy do
- name 'Brand'
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/tracker_factory.rb b/core/lib/spree/core/testing_support/factories/tracker_factory.rb
deleted file mode 100644
index e0d25cce4fc..00000000000
--- a/core/lib/spree/core/testing_support/factories/tracker_factory.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-FactoryGirl.define do
- factory :tracker, :class => Spree::Tracker do
- environment { Rails.env }
- analytics_id 'A100'
- active true
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/user_factory.rb b/core/lib/spree/core/testing_support/factories/user_factory.rb
deleted file mode 100644
index ede76b713df..00000000000
--- a/core/lib/spree/core/testing_support/factories/user_factory.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-FactoryGirl.define do
- sequence :user_authentication_token do |n|
- "xxxx#{Time.now.to_i}#{rand(1000)}#{n}xxxxxxxxxxxxx"
- end
-
- factory :user, :class => Spree.user_class do
- email { Faker::Internet.email }
- login { email }
- password 'secret'
- password_confirmation 'secret'
- authentication_token { FactoryGirl.generate(:user_authentication_token) } if Spree.user_class.attribute_method? :authentication_token
- end
-
- factory :admin_user, :parent => :user do
- spree_roles { [Spree::Role.find_by_name('admin') || FactoryGirl.create(:role, :name => 'admin')] }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/variant_factory.rb b/core/lib/spree/core/testing_support/factories/variant_factory.rb
deleted file mode 100644
index a025a928b0e..00000000000
--- a/core/lib/spree/core/testing_support/factories/variant_factory.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-FactoryGirl.define do
- factory :variant, :class => Spree::Variant do
- price 19.99
- cost_price 17.00
- sku { Faker::Lorem.sentence }
- weight { BigDecimal.new("#{rand(200)}.#{rand(99)}") }
- height { BigDecimal.new("#{rand(200)}.#{rand(99)}") }
- width { BigDecimal.new("#{rand(200)}.#{rand(99)}") }
- depth { BigDecimal.new("#{rand(200)}.#{rand(99)}") }
- on_hand 5
-
- # associations:
- product { |p| p.association(:product) }
- option_values { [FactoryGirl.create(:option_value)] }
- end
-end
diff --git a/core/lib/spree/core/testing_support/factories/zone_factory.rb b/core/lib/spree/core/testing_support/factories/zone_factory.rb
deleted file mode 100644
index 8df83174545..00000000000
--- a/core/lib/spree/core/testing_support/factories/zone_factory.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-FactoryGirl.define do
- factory :global_zone, :class => Spree::Zone do
- name 'GlobalZone'
- description { Faker::Lorem.sentence }
- zone_members do |proxy|
- zone = proxy.instance_eval { @instance }
- Spree::Country.all.map do |c|
- zone_member = Spree::ZoneMember.create(:zoneable => c, :zone => zone)
- end
- end
- end
-
- factory :zone, :class => Spree::Zone do
- name { Faker::Lorem.sentence }
- description { Faker::Lorem.sentence }
- end
-end
diff --git a/core/lib/spree/core/testing_support/fixtures.rb b/core/lib/spree/core/testing_support/fixtures.rb
deleted file mode 100644
index bc2e7ea217a..00000000000
--- a/core/lib/spree/core/testing_support/fixtures.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'active_record/fixtures'
-
-fixtures_dir = File.expand_path('../../../../../db/default', __FILE__)
-ActiveRecord::Fixtures.create_fixtures(fixtures_dir, ['spree/countries', 'spree/zones', 'spree/zone_members', 'spree/states', 'spree/roles'])
\ No newline at end of file
diff --git a/core/lib/spree/core/testing_support/flash.rb b/core/lib/spree/core/testing_support/flash.rb
deleted file mode 100644
index 5ab2ae05251..00000000000
--- a/core/lib/spree/core/testing_support/flash.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Spree
- module Core
- module TestingSupport
- module Flash
- def assert_flash_success(flash)
- flash = convert_flash(flash)
-
- within("[class='flash success']") do
- page.should have_content(flash)
- end
- end
-
- def assert_successful_update_message(resource)
- flash = I18n.t(:successfully_updated, :resource => I18n.t(resource))
- assert_flash_success(flash)
- end
-
- private
-
- def convert_flash(flash)
- if flash.is_a?(Symbol)
- flash = I18n.t(flash)
- end
- flash
- end
- end
- end
- end
-end
diff --git a/core/lib/spree/core/testing_support/preferences.rb b/core/lib/spree/core/testing_support/preferences.rb
deleted file mode 100644
index f2c2c94bc06..00000000000
--- a/core/lib/spree/core/testing_support/preferences.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module Spree
- module Core
- module TestingSupport
- module Preferences
- # Resets all preferences to default values, you can
- # pass a block to override the defaults with a block
- #
- # reset_spree_preferences do |config|
- # config.site_name = "my fancy pants store"
- # end
- #
- def reset_spree_preferences
- Spree::Preferences::Store.instance.persistence = false
- config = Rails.application.config.spree.preferences
- config.reset
- yield(config) if block_given?
- end
-
- def assert_preference_unset(preference)
- find("#preferences_#{preference}")['checked'].should be_false
- Spree::Config[preference].should be_false
- end
- end
- end
- end
-end
diff --git a/core/lib/spree/core/token_resource.rb b/core/lib/spree/core/token_resource.rb
deleted file mode 100644
index 48697f29b2a..00000000000
--- a/core/lib/spree/core/token_resource.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Spree
- module Core
- module TokenResource
- module ClassMethods
- def token_resource
- has_one :tokenized_permission, :as => :permissable
- delegate :token, :to => :tokenized_permission, :allow_nil => true
- after_create :create_token
- end
- end
-
- def create_token
- permission = build_tokenized_permission
- permission.token = token = ::SecureRandom::hex(8)
- permission.save!
- token
- end
-
- def self.included(receiver)
- receiver.extend ClassMethods
- end
- end
- end
-end
-
-ActiveRecord::Base.class_eval { include Spree::Core::TokenResource }
-
diff --git a/core/lib/spree/core/user_banners.rb b/core/lib/spree/core/user_banners.rb
deleted file mode 100644
index d7d7ec16276..00000000000
--- a/core/lib/spree/core/user_banners.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# Utility methods for dealing with user banners and saving
-# an array of dismissed banners per user
-# use symbols as banner id
-module Spree
- module Core
- module UserBanners
- def self.included(base)
- base.preference :dismissed_banners, :string, :default => ''
- end
-
- def dismissed_banner_ids
- dismissed = self.preferred_dismissed_banners
- dismissed.split(',').map(&:to_sym)
- end
-
- def dismiss_banner(banner_id)
- self.preferred_dismissed_banners = dismissed_banner_ids.push(banner_id.to_sym).uniq.join(',')
- end
-
- def dismissed_banner?(banner_id)
- dismissed_banner_ids.include? banner_id.to_sym
- end
- end
- end
-end
\ No newline at end of file
diff --git a/core/lib/spree/core/validators/email.rb b/core/lib/spree/core/validators/email.rb
index 793f0db77c4..997d5c1dc4e 100644
--- a/core/lib/spree/core/validators/email.rb
+++ b/core/lib/spree/core/validators/email.rb
@@ -1,23 +1,7 @@
-# Borrowed from http://my.rails-royce.org/2010/07/21/email-validation-in-ruby-on-rails-without-regexp/
-# Mentioned in tweet here: https://twitter.com/_sohara/status/177120126083141633
-require 'mail'
class EmailValidator < ActiveModel::EachValidator
def validate_each(record,attribute,value)
- begin
- m = Mail::Address.new(value)
- # We must check that value contains a domain and that value is an email address
- r = m.domain && m.address == value
- t = m.__send__(:tree)
- # We need to dig into treetop
- # A valid domain must have dot_atom_text elements size > 1
- # user@localhost is excluded
- # treetop must respond to domain
- # We exclude valid email values like
- # Hence we use m.__send__(tree).domain
- r &&= (t.domain.dot_atom_text.elements.size > 1)
- rescue Exception => e
- r = false
+ unless value =~ /\A([^@\.]|[^@\.]([^@\s]*)[^@\.])@([^@\s]+\.)+[^@\s]+\z/
+ record.errors.add(attribute, :invalid, {:value => value}.merge!(options))
end
- record.errors[attribute] << (options[:message] || "is invalid") unless r
end
end
diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb
index f8568b08528..b6c759db741 100644
--- a/core/lib/spree/core/version.rb
+++ b/core/lib/spree/core/version.rb
@@ -1,5 +1,5 @@
module Spree
def self.version
- "2.0.0.beta"
+ '2.4.11.beta'
end
end
diff --git a/core/lib/spree/i18n.rb b/core/lib/spree/i18n.rb
new file mode 100644
index 00000000000..fd289d8122e
--- /dev/null
+++ b/core/lib/spree/i18n.rb
@@ -0,0 +1,37 @@
+require 'i18n'
+require 'active_support/core_ext/array/extract_options'
+require 'spree/i18n/base'
+
+module Spree
+ extend ActionView::Helpers::TranslationHelper
+ extend ActionView::Helpers::TagHelper
+
+ class << self
+ # Add spree namespace and delegate to Rails TranslationHelper for some nice
+ # extra functionality. e.g return reasonable strings for missing translations
+ def translate(*args)
+ @virtual_path = virtual_path
+
+ options = args.extract_options!
+ options[:scope] = [*options[:scope]].unshift(:spree)
+ args << options
+ super(*args)
+ end
+
+ alias_method :t, :translate
+
+ def context
+ Spree::ViewContext.context
+ end
+
+ def virtual_path
+ if context
+ path = context.instance_variable_get("@virtual_path")
+
+ if path
+ path.gsub(/spree/, '')
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/i18n/base.rb b/core/lib/spree/i18n/base.rb
new file mode 100644
index 00000000000..765c8ad169b
--- /dev/null
+++ b/core/lib/spree/i18n/base.rb
@@ -0,0 +1,17 @@
+module Spree
+ module ViewContext
+ def self.context=(context)
+ @context = context
+ end
+
+ def self.context
+ @context
+ end
+
+ def view_context
+ super.tap do |context|
+ Spree::ViewContext.context = context
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/i18n/initializer.rb b/core/lib/spree/i18n/initializer.rb
new file mode 100644
index 00000000000..79f5917cb2b
--- /dev/null
+++ b/core/lib/spree/i18n/initializer.rb
@@ -0,0 +1 @@
+Spree::BaseController.send(:include, Spree::ViewContext)
diff --git a/core/lib/spree/localized_number.rb b/core/lib/spree/localized_number.rb
new file mode 100644
index 00000000000..0fb62a19be5
--- /dev/null
+++ b/core/lib/spree/localized_number.rb
@@ -0,0 +1,20 @@
+module Spree
+ class LocalizedNumber
+
+ # Strips all non-price-like characters from the number, taking into account locale settings.
+ def self.parse(number)
+ return number unless number.is_a?(String)
+
+ separator, delimiter = I18n.t([:'number.currency.format.separator', :'number.currency.format.delimiter'])
+ non_number_characters = /[^0-9\-#{separator}]/
+
+ # strip everything else first
+ number.gsub!(non_number_characters, '')
+ # then replace the locale-specific decimal separator with the standard separator if necessary
+ number.gsub!(separator, '.') unless separator == '.'
+
+ number.to_d
+ end
+
+ end
+end
diff --git a/core/lib/spree/migrations.rb b/core/lib/spree/migrations.rb
new file mode 100644
index 00000000000..1f19d2dadc9
--- /dev/null
+++ b/core/lib/spree/migrations.rb
@@ -0,0 +1,68 @@
+module Spree
+ class Migrations
+ attr_reader :config, :engine_name
+
+ # Takes the engine config block and engine name
+ def initialize(config, engine_name)
+ @config, @engine_name = config, engine_name
+ end
+
+ # Puts warning when any engine migration is not present on the Rails app
+ # db/migrate dir
+ #
+ # First split:
+ #
+ # ["20131128203548", "update_name_fields_on_spree_credit_cards.spree.rb"]
+ #
+ # Second split should give the engine_name of the migration
+ #
+ # ["update_name_fields_on_spree_credit_cards", "spree.rb"]
+ #
+ # Shouldn't run on test mode because migrations inside engine don't have
+ # engine name on the file name
+ def check
+ if File.exists?("config/spree.yml") && File.directory?("db/migrate")
+ engine_in_app = app_migrations.map do |file_name|
+ name, engine = file_name.split(".", 2)
+ next unless match_engine?(engine)
+ name
+ end.compact! || []
+
+ missing_migrations = engine_migrations.sort - engine_in_app.sort
+ unless missing_migrations.empty?
+ puts "[#{engine_name.capitalize} WARNING] Missing migrations."
+ missing_migrations.each do |migration|
+ puts "[#{engine_name.capitalize} WARNING] #{migration} from #{engine_name} is missing."
+ end
+ puts "[#{engine_name.capitalize} WARNING] Run `bundle exec rake railties:install:migrations` to get them.\n\n"
+ true
+ end
+ end
+ end
+
+ private
+ def engine_migrations
+ Dir.entries("#{config.root}/db/migrate").map do |file_name|
+ name = file_name.split("_", 2).last.split(".", 2).first
+ name.empty? ? next : name
+ end.compact! || []
+ end
+
+ def app_migrations
+ Dir.entries("db/migrate").map do |file_name|
+ next if [".", ".."].include? file_name
+ name = file_name.split("_", 2).last
+ name.empty? ? next : name
+ end.compact! || []
+ end
+
+ def match_engine?(engine)
+ if engine_name == "spree"
+ # Avoid stores upgrading from 1.3 getting wrong warnings
+ ["spree.rb", "spree_promo.rb"].include? engine
+ else
+ engine == "#{engine_name}.rb"
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/money.rb b/core/lib/spree/money.rb
index 7cf950e1e7c..a6a4badbab1 100644
--- a/core/lib/spree/money.rb
+++ b/core/lib/spree/money.rb
@@ -1,25 +1,45 @@
+# encoding: utf-8
+
require 'money'
module Spree
class Money
attr_reader :money
+ delegate :cents, to: :money
+
def initialize(amount, options={})
- @money = ::Money.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
+ @money = Monetize.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
@options = {}
- @options[:with_currency] = true if Spree::Config[:display_currency]
+ @options[:with_currency] = Spree::Config[:display_currency]
@options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
- @options[:no_cents] = true if Spree::Config[:hide_cents]
+ @options[:no_cents] = Spree::Config[:hide_cents]
+ @options[:decimal_mark] = Spree::Config[:currency_decimal_mark]
+ @options[:thousands_separator] = Spree::Config[:currency_thousands_separator]
+ @options[:sign_before_symbol] = Spree::Config[:currency_sign_before_symbol]
@options.merge!(options)
# Must be a symbol because the Money gem doesn't do the conversion
@options[:symbol_position] = @options[:symbol_position].to_sym
-
end
def to_s
@money.format(@options)
end
+ def to_html(options = { html: true })
+ output = @money.format(@options.merge(options))
+ if options[:html]
+ # 1) prevent blank, breaking spaces
+ # 2) prevent escaping of HTML character entities
+ output = output.sub(" ", " ").html_safe
+ end
+ output
+ end
+
+ def as_json(*)
+ to_s
+ end
+
def ==(obj)
@money == obj.money
end
diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb
new file mode 100644
index 00000000000..825aebc63ea
--- /dev/null
+++ b/core/lib/spree/permitted_attributes.rb
@@ -0,0 +1,112 @@
+module Spree
+ module PermittedAttributes
+ ATTRIBUTES = [
+ :address_attributes,
+ :checkout_attributes,
+ :customer_return_attributes,
+ :image_attributes,
+ :inventory_unit_attributes,
+ :line_item_attributes,
+ :option_type_attributes,
+ :option_value_attributes,
+ :payment_attributes,
+ :product_attributes,
+ :product_properties_attributes,
+ :property_attributes,
+ :return_authorization_attributes,
+ :shipment_attributes,
+ :source_attributes,
+ :stock_item_attributes,
+ :stock_location_attributes,
+ :stock_movement_attributes,
+ :store_attributes,
+ :taxon_attributes,
+ :taxonomy_attributes,
+ :user_attributes,
+ :variant_attributes
+ ]
+
+ mattr_reader *ATTRIBUTES
+
+ @@address_attributes = [
+ :id, :firstname, :lastname, :first_name, :last_name,
+ :address1, :address2, :city, :country_id, :state_id,
+ :zipcode, :phone, :state_name, :alternative_phone, :company,
+ country: [:iso, :name, :iso3, :iso_name],
+ state: [:name, :abbr]
+ ]
+
+ @@checkout_attributes = [
+ :coupon_code, :email, :shipping_method_id, :special_instructions, :use_billing
+ ]
+
+ @@customer_return_attributes = [:stock_location_id, return_items_attributes: [:id, :inventory_unit_id, :return_authorization_id, :returned, :pre_tax_amount, :acceptance_status, :exchange_variant_id]]
+
+ @@image_attributes = [:alt, :attachment, :position, :viewable_type, :viewable_id]
+
+ @@inventory_unit_attributes = [:shipment, :variant_id]
+
+ @@line_item_attributes = [:id, :variant_id, :quantity]
+
+ @@option_type_attributes = [:name, :presentation, :option_values_attributes]
+
+ @@option_value_attributes = [:name, :presentation]
+
+ @@payment_attributes = [:amount, :payment_method_id, :payment_method]
+
+ @@product_properties_attributes = [:property_name, :value, :position]
+
+ @@product_attributes = [
+ :name, :description, :available_on, :permalink, :meta_description,
+ :meta_keywords, :price, :sku, :deleted_at, :prototype_id,
+ :option_values_hash, :weight, :height, :width, :depth,
+ :shipping_category_id, :tax_category_id,
+ :taxon_ids, :cost_currency, :cost_price,
+ option_type_ids: []
+ ]
+
+ @@property_attributes = [:name, :presentation]
+
+ @@return_authorization_attributes = [:amount, :memo, :stock_location_id, :inventory_units_attributes, :return_authorization_reason_id]
+
+ @@shipment_attributes = [
+ :order, :special_instructions, :stock_location_id, :id,
+ :tracking, :address, :inventory_units, :selected_shipping_rate_id]
+
+ # month / year may be provided by some sources, or others may elect to use one field
+ @@source_attributes = [
+ :number, :month, :year, :expiry, :verification_value,
+ :first_name, :last_name, :cc_type, :gateway_customer_profile_id,
+ :gateway_payment_profile_id, :last_digits, :name, :encrypted_data]
+
+ @@stock_item_attributes = [:variant, :stock_location, :backorderable, :variant_id]
+
+ @@stock_location_attributes = [
+ :name, :active, :address1, :address2, :city, :zipcode,
+ :backorderable_default, :state_name, :state_id, :country_id, :phone,
+ :propagate_all_variants]
+
+ @@stock_movement_attributes = [
+ :quantity, :stock_item, :stock_item_id, :originator, :action]
+
+ @@store_attributes = [:name, :url, :seo_title, :meta_keywords,
+ :meta_description, :default_currency, :mail_from_address]
+
+ @@taxonomy_attributes = [:name]
+
+ @@taxon_attributes = [
+ :name, :parent_id, :position, :icon, :description, :permalink, :taxonomy_id,
+ :meta_description, :meta_keywords, :meta_title, :child_index]
+
+ # TODO Should probably use something like Spree.user_class.attributes
+ @@user_attributes = [:email, :password, :password_confirmation]
+
+ @@variant_attributes = [
+ :name, :presentation, :cost_price, :lock_version,
+ :position, :track_inventory,
+ :product_id, :product, :option_values_attributes, :price,
+ :weight, :height, :width, :depth, :sku, :cost_currency,
+ options: [:name, :value], option_value_ids: []
+ ]
+ end
+end
diff --git a/core/lib/spree/product_filters.rb b/core/lib/spree/product_filters.rb
deleted file mode 100644
index cf106d489bf..00000000000
--- a/core/lib/spree/product_filters.rb
+++ /dev/null
@@ -1,197 +0,0 @@
-module Spree
- # THIS FILE SHOULD BE OVER-RIDDEN IN YOUR SITE EXTENSION!
- # the exact code probably won't be useful, though you're welcome to modify and reuse
- # the current contents are mainly for testing and documentation
-
- # To override this file...
- # 1) Make a copy of it in your sites local /lib/spree folder
- # 2) Add it to the config load path, or require it in an initializer, e.g...
- #
- # # config/initializers/spree.rb
- # require 'spree/product_filters'
- #
-
- # set up some basic filters for use with products
- #
- # Each filter has two parts
- # * a parametrized named scope which expects a list of labels
- # * an object which describes/defines the filter
- #
- # The filter description has three components
- # * a name, for displaying on pages
- # * a named scope which will 'execute' the filter
- # * a mapping of presentation labels to the relevant condition (in the context of the named scope)
- # * an optional list of labels and values (for use with object selection - see taxons examples below)
- #
- # The named scopes here have a suffix '_any', following SearchLogic's convention for a
- # scope which returns results which match any of the inputs. This is purely a convention,
- # but might be a useful reminder.
- #
- # When creating a form, the name of the checkbox group for a filter F should be
- # the name of F's scope with [] appended, eg "price_range_any[]", and for
- # each label you should have a checkbox with the label as its value. On submission,
- # Rails will send the action a hash containing (among other things) an array named
- # after the scope whose values are the active labels.
- #
- # SearchLogic will then convert this array to a call to the named scope with the array
- # contents, and the named scope will build a query with the disjunction of the conditions
- # relating to the labels, all relative to the scope's context.
- #
- # The details of how/when filters are used is a detail for specific models (eg products
- # or taxons), eg see the taxon model/controller.
-
- # See specific filters below for concrete examples.
-
- # This module is included by Taxon. In development mode that inclusion does not
- # happen until Taxon class is loaded. Ensure that Taxon class is loaded before
- # you try something like Product.price_range_any
- module ProductFilters
- # Example: filtering by price
- # The named scope just maps incoming labels onto their conditions, and builds the conjunction
- # 'price' is in the base scope's context (ie, "select foo from products where ...") so
- # we can access the field right away
- # The filter identifies which scope to use, then sets the conditions for each price range
- #
- # If user checks off three different price ranges then the argument passed to
- # below scope would be something like ["$10 - $15", "$15 - $18", "$18 - $20"]
- #
- Spree::Product.add_search_scope :price_range_any do |*opts|
- conds = opts.map {|o| Spree::ProductFilters.price_filter[:conds][o]}.reject {|c| c.nil?}
- scope = conds.shift
- conds.each do |new_scope|
- scope = scope.or(new_scope)
- end
- Spree::Product.joins(:master => :default_price).where(scope)
- end
-
- def ProductFilters.format_price(amount)
- Spree::Money.new(amount)
- end
-
- def ProductFilters.price_filter
- v = Spree::Price.arel_table
- conds = [ [ I18n.t(:under_price, :price => format_price(10)) , v[:amount].lteq(10)],
- [ "#{format_price(10)} - #{format_price(15)}" , v[:amount].in(10..15)],
- [ "#{format_price(15)} - #{format_price(18)}" , v[:amount].in(15..18)],
- [ "#{format_price(18)} - #{format_price(20)}" , v[:amount].in(18..20)],
- [ I18n.t(:or_over_price, :price => format_price(20)) , v[:amount].gteq(20)]]
- { :name => I18n.t(:price_range),
- :scope => :price_range_any,
- :conds => Hash[*conds.flatten],
- :labels => conds.map {|k,v| [k,k]}
- }
- end
-
-
- # Example: filtering by possible brands
- #
- # First, we define the scope. Two interesting points here: (a) we run our conditions
- # in the scope where the info for the 'brand' property has been loaded; and (b)
- # because we may want to filter by other properties too, we give this part of the
- # query a unique name (which must be used in the associated conditions too).
- #
- # Secondly, the filter. Instead of a static list of values, we pull out all existing
- # brands from the db, and then build conditions which test for string equality on
- # the (uniquely named) field "p_brand.value". There's also a test for brand info
- # being blank: note that this relies on with_property doing a left outer join
- # rather than an inner join.
- if Spree::Property.table_exists?
- Spree::Product.add_search_scope :brand_any do |*opts|
- conds = opts.map {|o| ProductFilters.brand_filter[:conds][o]}.reject {|c| c.nil?}
- scope = conds.shift
- conds.each do |new_scope|
- scope = scope.or(new_scope)
- end
- Spree::Product.with_property("brand").where(scope)
- end
-
- def ProductFilters.brand_filter
- brand_property = Spree::Property.find_by_name("brand")
- brands = Spree::ProductProperty.where(:property_id => brand_property).pluck(:value).uniq
- pp = Spree::ProductProperty.arel_table
- conds = Hash[*brands.map { |b| [b, pp[:value].eq(b)] }.flatten]
- { :name => "Brands",
- :scope => :brand_any,
- :conds => conds,
- :labels => (brands.sort).map { |k| [k, k] }
- }
- end
- end
-
- # Example: a parameterized filter
- # The filter above may show brands which aren't applicable to the current taxon,
- # so this one only shows the brands that are relevant to a particular taxon and
- # its descendants.
- #
- # We don't have to give a new scope since the conditions here are a subset of the
- # more general filter, so decoding will still work - as long as the filters on a
- # page all have unique names (ie, you can't use the two brand filters together
- # if they use the same scope). To be safe, the code uses a copy of the scope.
- #
- # HOWEVER: what happens if we want a more precise scope? we can't pass
- # parametrized scope names to SearchLogic, only atomic names, so couldn't ask
- # for taxon T's customized filter to be used. BUT: we can arrange for the form
- # to pass back a hash instead of an array, where the key acts as the (taxon)
- # parameter and value is its label array, and then get a modified named scope
- # to get its conditions from a particular filter.
- #
- # The brand-finding code can be simplified if a few more named scopes were added to
- # the product properties model.
- if Spree::Property.table_exists?
- Spree::Product.add_search_scope :selective_brand_any do |*opts|
- Spree::Product.brand_any(*opts)
- end
-
- def ProductFilters.selective_brand_filter(taxon = nil)
- taxon ||= Spree::Taxonomy.first.root
- brand_property = Spree::Property.find_by_name("brand")
- scope = Spree::ProductProperty.where(:property_id => brand_property).
- joins(:product => :taxons).
- where("#{Spree::Taxon.table_name}.id" => [taxon] + taxon.descendants).
- scoped
- brands = scope.pluck(:value).uniq
- {
- :name => "Applicable Brands",
- :scope => :selective_brand_any,
- :labels => brands.sort.map { |k| [k,k] }
- }
- end
- end
-
- # Provide filtering on the immediate children of a taxon
- #
- # This doesn't fit the pattern of the examples above, so there's a few changes.
- # Firstly, it uses an existing scope which was not built for filtering - and so
- # has no need of a conditions mapping, and secondly, it has a mapping of name
- # to the argument type expected by the other scope.
- #
- # This technique is useful for filtering on objects (by passing ids) or with a
- # scope that can be used directly (eg. testing only ever on a single property).
- #
- # This scope selects products in any of the active taxons or their children.
- #
- def ProductFilters.taxons_below(taxon)
- return Spree::ProductFilters.all_taxons if taxon.nil?
- { :name => "Taxons under " + taxon.name,
- :scope => :taxons_id_in_tree_any,
- :labels => taxon.children.sort_by(&:position).map {|t| [t.name, t.id]},
- :conds => nil
- }
- end
-
- # Filtering by the list of all taxons
- #
- # Similar idea as above, but we don't want the descendants' products, hence
- # it uses one of the auto-generated scopes from SearchLogic.
- #
- # idea: expand the format to allow nesting of labels?
- def ProductFilters.all_taxons
- taxons = Spree::Taxonomy.all.map {|t| [t.root] + t.root.descendants }.flatten
- { :name => "All taxons",
- :scope => :taxons_id_equals_any,
- :labels => taxons.sort_by(&:name).map {|t| [t.name, t.id]},
- :conds => nil # not needed
- }
- end
- end
-end
diff --git a/promo/lib/spree/promo/environment.rb b/core/lib/spree/promo/environment.rb
similarity index 100%
rename from promo/lib/spree/promo/environment.rb
rename to core/lib/spree/promo/environment.rb
diff --git a/core/lib/spree/core/responder.rb b/core/lib/spree/responder.rb
similarity index 100%
rename from core/lib/spree/core/responder.rb
rename to core/lib/spree/responder.rb
diff --git a/core/lib/spree/scopes/dynamic.rb b/core/lib/spree/scopes/dynamic.rb
deleted file mode 100644
index a0067c1f8be..00000000000
--- a/core/lib/spree/scopes/dynamic.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module Spree
- module Scopes
- # This module is extended by ProductScope
- module Dynamic
- module_function
-
- # Sample dynamic scope generating from set of products
- # generates 0 or (2..scope_limit) scopes for prices, based
- # on number of products (uses Math.log, to guess number of scopes)
- def price_scopes_for(products, scope_limit=5)
- scopes = []
-
- # Price based scopes
- all_prices = products.map(&:price).sort
-
- ranges = [Math.log(products.length).floor, scope_limit].max
-
- if ranges >= 2
- l = all_prices.length / ranges
- scopes << ProductScope.new({:name => "master_price_lte", :arguments => [all_prices[l]] })
-
- (ranges - 2).times do |x|
- scopes << ProductScope.new({:name => "price_between",
- :arguments => [ all_prices[l*(x+1)+1], all_prices[l*(x+2)] ] })
- end
- scopes << ProductScope.new({:name => "master_price_gte", :arguments => [all_prices[l*(ranges-1)+1]] })
- end
-
- scopes
- end
- end
- end
-end
diff --git a/core/lib/spree/testing_support/ability_helpers.rb b/core/lib/spree/testing_support/ability_helpers.rb
new file mode 100644
index 00000000000..89c7a122b05
--- /dev/null
+++ b/core/lib/spree/testing_support/ability_helpers.rb
@@ -0,0 +1,105 @@
+shared_examples_for 'access granted' do
+ it 'should allow read' do
+ expect(ability).to be_able_to(:read, resource, token) if token
+ expect(ability).to be_able_to(:read, resource) unless token
+ end
+
+ it 'should allow create' do
+ expect(ability).to be_able_to(:create, resource, token) if token
+ expect(ability).to be_able_to(:create, resource) unless token
+ end
+
+ it 'should allow update' do
+ expect(ability).to be_able_to(:update, resource, token) if token
+ expect(ability).to be_able_to(:update, resource) unless token
+ end
+end
+
+shared_examples_for 'access denied' do
+ it 'should not allow read' do
+ expect(ability).to_not be_able_to(:read, resource)
+ end
+
+ it 'should not allow create' do
+ expect(ability).to_not be_able_to(:create, resource)
+ end
+
+ it 'should not allow update' do
+ expect(ability).to_not be_able_to(:update, resource)
+ end
+end
+
+shared_examples_for 'admin granted' do
+ it 'should allow admin' do
+ expect(ability).to be_able_to(:admin, resource, token) if token
+ expect(ability).to be_able_to(:admin, resource) unless token
+ end
+end
+
+shared_examples_for 'admin denied' do
+ it 'should not allow admin' do
+ expect(ability).to_not be_able_to(:admin, resource)
+ end
+end
+
+shared_examples_for 'index allowed' do
+ it 'should allow index' do
+ expect(ability).to be_able_to(:index, resource)
+ end
+end
+
+shared_examples_for 'no index allowed' do
+ it 'should not allow index' do
+ expect(ability).to_not be_able_to(:index, resource)
+ end
+end
+
+shared_examples_for 'create only' do
+ it 'should allow create' do
+ expect(ability).to be_able_to(:create, resource)
+ end
+
+ it 'should not allow read' do
+ expect(ability).to_not be_able_to(:read, resource)
+ end
+
+ it 'should not allow update' do
+ expect(ability).to_not be_able_to(:update, resource)
+ end
+
+ it 'should not allow index' do
+ expect(ability).to_not be_able_to(:index, resource)
+ end
+end
+
+shared_examples_for 'read only' do
+ it 'should not allow create' do
+ expect(ability).to_not be_able_to(:create, resource)
+ end
+
+ it 'should not allow update' do
+ expect(ability).to_not be_able_to(:update, resource)
+ end
+
+ it 'should allow index' do
+ expect(ability).to be_able_to(:index, resource)
+ end
+end
+
+shared_examples_for 'update only' do
+ it 'should not allow create' do
+ expect(ability).to_not be_able_to(:create, resource)
+ end
+
+ it 'should not allow read' do
+ expect(ability).to_not be_able_to(:read, resource)
+ end
+
+ it 'should allow update' do
+ expect(ability).to be_able_to(:update, resource)
+ end
+
+ it 'should not allow index' do
+ expect(ability).to_not be_able_to(:index, resource)
+ end
+end
diff --git a/core/lib/spree/testing_support/authorization_helpers.rb b/core/lib/spree/testing_support/authorization_helpers.rb
new file mode 100644
index 00000000000..766d94362b4
--- /dev/null
+++ b/core/lib/spree/testing_support/authorization_helpers.rb
@@ -0,0 +1,63 @@
+module Spree
+ module TestingSupport
+ module AuthorizationHelpers
+ module CustomAbility
+ def build_ability(&block)
+ block ||= proc{ |u| can :manage, :all }
+ Class.new do
+ include CanCan::Ability
+ define_method(:initialize, block)
+ end
+ end
+ end
+
+ module Controller
+ include CustomAbility
+
+ def stub_authorization!(&block)
+ ability_class = build_ability(&block)
+ before do
+ allow(controller).to receive(:current_ability).and_return(ability_class.new(nil))
+ end
+ end
+ end
+
+ module Request
+ include CustomAbility
+
+ def stub_authorization!
+ ability = build_ability
+
+ after(:all) do
+ Spree::Ability.remove_ability(ability)
+ end
+
+ before(:all) do
+ Spree::Ability.register_ability(ability)
+ end
+
+ before do
+ allow(Spree.user_class).to receive(:find_by).
+ with(hash_including(:spree_api_key)).
+ and_return(Spree.user_class.new)
+ end
+ end
+
+ def custom_authorization!(&block)
+ ability = build_ability(&block)
+ after(:all) do
+ Spree::Ability.remove_ability(ability)
+ end
+ before(:all) do
+ Spree::Ability.register_ability(ability)
+ end
+ end
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.extend Spree::TestingSupport::AuthorizationHelpers::Controller, type: :controller
+ config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :feature
+end
diff --git a/core/lib/spree/testing_support/bar_ability.rb b/core/lib/spree/testing_support/bar_ability.rb
new file mode 100644
index 00000000000..ad23f1e4360
--- /dev/null
+++ b/core/lib/spree/testing_support/bar_ability.rb
@@ -0,0 +1,14 @@
+# Fake ability for testing administration
+class BarAbility
+ include CanCan::Ability
+
+ def initialize(user)
+ user ||= Spree::User.new
+ if user.has_spree_role? 'bar'
+ # allow dispatch to :admin, :index, and :show on Spree::Order
+ can [:admin, :index, :show], Spree::Order
+ # allow dispatch to :index, :show, :create and :update shipments on the admin
+ can [:admin, :manage], Spree::Shipment
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/caching.rb b/core/lib/spree/testing_support/caching.rb
new file mode 100644
index 00000000000..8405e9f4eff
--- /dev/null
+++ b/core/lib/spree/testing_support/caching.rb
@@ -0,0 +1,47 @@
+module Spree
+ module TestingSupport
+ module Caching
+ def cache_writes
+ @cache_write_events
+ end
+
+ def assert_written_to_cache(key)
+ unless @cache_write_events.detect { |event| event[:key].starts_with?(key) }
+ fail %Q{Expected to find #{key} in the cache, but didn't.
+
+ Cache writes:
+ #{@cache_write_events.join("\n")}
+ }
+ end
+ end
+
+ def clear_cache_events
+ @cache_read_events = []
+ @cache_write_events = []
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include Spree::TestingSupport::Caching, :caching => true
+
+ config.before(:each, :caching => true) do
+ ActionController::Base.perform_caching = true
+
+ ActiveSupport::Notifications.subscribe("read_fragment.action_controller") do |event, start_time, finish_time, _, details|
+ @cache_read_events ||= []
+ @cache_read_events << details
+ end
+
+ ActiveSupport::Notifications.subscribe("write_fragment.action_controller") do |event, start_time, finish_time, _, details|
+ @cache_write_events ||= []
+ @cache_write_events << details
+ end
+ end
+
+ config.after(:each, :caching => true) do
+ ActionController::Base.perform_caching = false
+ Rails.cache.clear
+ end
+end
\ No newline at end of file
diff --git a/core/lib/spree/testing_support/capybara_ext.rb b/core/lib/spree/testing_support/capybara_ext.rb
new file mode 100644
index 00000000000..39e20243015
--- /dev/null
+++ b/core/lib/spree/testing_support/capybara_ext.rb
@@ -0,0 +1,163 @@
+module CapybaraExt
+ def page!
+ save_and_open_page
+ end
+
+ def click_icon(type)
+ find(".fa-#{type}").click
+ end
+
+ def eventually_fill_in(field, options={})
+ expect(page).to have_css('#' + field)
+ fill_in field, options
+ end
+
+ def within_row(num, &block)
+ if RSpec.current_example.metadata[:js]
+ within("table.index tbody tr:nth-child(#{num})", &block)
+ else
+ within(:xpath, all("table.index tbody tr")[num-1].path, &block)
+ end
+ end
+
+ def column_text(num)
+ if RSpec.current_example.metadata[:js]
+ find("td:nth-child(#{num})").text
+ else
+ all("td")[num-1].text
+ end
+ end
+
+ def set_select2_field(field, value)
+ page.execute_script %Q{$('#{field}').select2('val', '#{value}')}
+ end
+
+ def select2_search(value, options)
+ label = find_label_by_text(options[:from])
+ within label.first(:xpath,".//..") do
+ options[:from] = "##{find(".select2-container")["id"]}"
+ end
+ targetted_select2_search(value, options)
+ end
+
+ def targetted_select2_search(value, options)
+ page.execute_script %Q{$('#{options[:from]}').select2('open')}
+ page.execute_script "$('#{options[:dropdown_css]} input.select2-input').val('#{value}').trigger('keyup-change');"
+ select_select2_result(value)
+ end
+
+ def select2(value, options)
+ label = find_label_by_text(options[:from])
+
+ within label.first(:xpath,".//..") do
+ options[:from] = "##{find(".select2-container")["id"]}"
+ end
+ targetted_select2(value, options)
+ end
+
+ def select2_no_label value, options={}
+ raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
+
+ placeholder = options[:from]
+ minlength = options[:minlength] || 4
+
+ click_link placeholder
+
+ select_select2_result(value)
+ end
+
+ def targetted_select2(value, options)
+ # find select2 element and click it
+ find(options[:from]).find('a').click
+ select_select2_result(value)
+ end
+
+ def select_select2_result(value)
+ # results are in a div appended to the end of the document
+ within(:xpath, '//body') do
+ page.find("div.select2-result-label", text: %r{#{Regexp.escape(value)}}i).click
+ end
+ end
+
+ def find_label_by_text(text)
+ label = find_label(text)
+ counter = 0
+
+ # Because JavaScript testing is prone to errors...
+ while label.nil? && counter < 10
+ sleep(1)
+ counter += 1
+ label = find_label(text)
+ end
+
+ if label.nil?
+ raise "Could not find label by text #{text}"
+ end
+
+ label
+ end
+
+ def find_label(text)
+ first(:xpath, "//label[text()[contains(.,'#{text}')]]")
+ end
+
+ def wait_for_ajax
+ counter = 0
+ while page.evaluate_script("typeof($) === 'undefined' || $.active > 0")
+ counter += 1
+ sleep(0.1)
+ raise "AJAX request took longer than 5 seconds." if counter >= 50
+ end
+ end
+
+ def accept_alert
+ page.evaluate_script('window.confirm = function() { return true; }')
+ yield
+ end
+
+ def dismiss_alert
+ page.evaluate_script('window.confirm = function() { return false; }')
+ yield
+ # Restore existing default
+ page.evaluate_script('window.confirm = function() { return true; }')
+ end
+end
+
+Capybara.configure do |config|
+ config.match = :prefer_exact
+ config.ignore_hidden_elements = true
+end
+
+RSpec::Matchers.define :have_meta do |name, expected|
+ match do |actual|
+ has_css?("meta[name='#{name}'][content='#{expected}']", visible: false)
+ end
+
+ failure_message do |actual|
+ actual = first("meta[name='#{name}']")
+ if actual
+ "expected that meta #{name} would have content='#{expected}' but was '#{actual[:content]}'"
+ else
+ "expected that meta #{name} would exist with content='#{expected}'"
+ end
+ end
+end
+
+RSpec::Matchers.define :have_title do |expected|
+ match do |actual|
+ has_css?("title", text: expected, visible: false)
+ end
+
+ failure_message do |actual|
+ actual = first("title")
+ if actual
+ "expected that title would have been '#{expected}' but was '#{actual.text}'"
+ else
+ "expected that title would exist with '#{expected}'"
+ end
+ end
+end
+
+RSpec.configure do |c|
+ c.include CapybaraExt
+end
diff --git a/core/lib/spree/testing_support/common_rake.rb b/core/lib/spree/testing_support/common_rake.rb
new file mode 100644
index 00000000000..745182e1a11
--- /dev/null
+++ b/core/lib/spree/testing_support/common_rake.rb
@@ -0,0 +1,50 @@
+unless defined?(Spree::InstallGenerator)
+ require 'generators/spree/install/install_generator'
+end
+
+require 'generators/spree/dummy/dummy_generator'
+
+desc "Generates a dummy app for testing"
+namespace :common do
+ task :test_app, :user_class do |t, args|
+ args.with_defaults(:user_class => "Spree::LegacyUser")
+ require "#{ENV['LIB_NAME']}"
+
+ ENV["RAILS_ENV"] = 'test'
+
+ Spree::DummyGenerator.start ["--lib_name=#{ENV['LIB_NAME']}", "--quiet"]
+ Spree::InstallGenerator.start ["--lib_name=#{ENV['LIB_NAME']}", "--auto-accept", "--migrate=false", "--seed=false", "--sample=false", "--quiet", "--user_class=#{args[:user_class]}"]
+
+ puts "Setting up dummy database..."
+ cmd = "bundle exec rake db:drop db:create db:migrate"
+
+ if RUBY_PLATFORM =~ /mswin/ #windows
+ cmd += " >nul"
+ else
+ cmd += " >/dev/null"
+ end
+
+ system(cmd)
+
+ begin
+ require "generators/#{ENV['LIB_NAME']}/install/install_generator"
+ puts 'Running extension installation generator...'
+ "#{ENV['LIB_NAME'].camelize}::Generators::InstallGenerator".constantize.start(["--auto-run-migrations"])
+ rescue LoadError
+ puts 'Skipping installation no generator to run...'
+ end
+ end
+
+ task :seed do |t, args|
+ puts "Seeding ..."
+ cmd = "bundle exec rake db:seed RAILS_ENV=test"
+
+ if RUBY_PLATFORM =~ /mswin/ #windows
+ cmd += " >nul"
+ else
+ cmd += " >/dev/null"
+ end
+
+ system(cmd)
+ end
+end
diff --git a/core/lib/spree/testing_support/controller_requests.rb b/core/lib/spree/testing_support/controller_requests.rb
new file mode 100644
index 00000000000..cdb331d391c
--- /dev/null
+++ b/core/lib/spree/testing_support/controller_requests.rb
@@ -0,0 +1,79 @@
+# Use this module to easily test Spree actions within Spree components
+# or inside your application to test routes for the mounted Spree engine.
+#
+# Inside your spec_helper.rb, include this module inside the RSpec.configure
+# block by doing this:
+#
+# require 'spree/testing_support/controller_requests'
+# RSpec.configure do |c|
+# c.include Spree::TestingSupport::ControllerRequests, :type => :controller
+# end
+#
+# Then, in your controller tests, you can access spree routes like this:
+#
+# require 'spec_helper'
+#
+# describe Spree::ProductsController do
+# it "can see all the products" do
+# spree_get :index
+# end
+# end
+#
+# Use spree_get, spree_post, spree_put or spree_delete to make requests
+# to the Spree engine, and use regular get, post, put or delete to make
+# requests to your application.
+#
+module Spree
+ module TestingSupport
+ module ControllerRequests
+ def spree_get(action, parameters = nil, session = nil, flash = nil)
+ process_spree_action(action, parameters, session, flash, "GET")
+ end
+
+ # Executes a request simulating POST HTTP method and set/volley the response
+ def spree_post(action, parameters = nil, session = nil, flash = nil)
+ process_spree_action(action, parameters, session, flash, "POST")
+ end
+
+ # Executes a request simulating PUT HTTP method and set/volley the response
+ def spree_put(action, parameters = nil, session = nil, flash = nil)
+ process_spree_action(action, parameters, session, flash, "PUT")
+ end
+
+ # Executes a request simulating DELETE HTTP method and set/volley the response
+ def spree_delete(action, parameters = nil, session = nil, flash = nil)
+ process_spree_action(action, parameters, session, flash, "DELETE")
+ end
+
+ def spree_xhr_get(action, parameters = nil, session = nil, flash = nil)
+ process_spree_xhr_action(action, parameters, session, flash, :get)
+ end
+
+ def spree_xhr_post(action, parameters = nil, session = nil, flash = nil)
+ process_spree_xhr_action(action, parameters, session, flash, :post)
+ end
+
+ def spree_xhr_put(action, parameters = nil, session = nil, flash = nil)
+ process_spree_xhr_action(action, parameters, session, flash, :put)
+ end
+
+ def spree_xhr_delete(action, parameters = nil, session = nil, flash = nil)
+ process_spree_xhr_action(action, parameters, session, flash, :delete)
+ end
+
+ private
+
+ def process_spree_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
+ parameters ||= {}
+ process(action, method, parameters.merge!(:use_route => :spree), session, flash)
+ end
+
+ def process_spree_xhr_action(action, parameters = nil, session = nil, flash = nil, method = :get)
+ parameters ||= {}
+ parameters.reverse_merge!(:format => :json)
+ parameters.merge!(:use_route => :spree)
+ xml_http_request(method, action, parameters, session, flash)
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/extension_rake.rb b/core/lib/spree/testing_support/extension_rake.rb
new file mode 100644
index 00000000000..10959ffce5d
--- /dev/null
+++ b/core/lib/spree/testing_support/extension_rake.rb
@@ -0,0 +1,10 @@
+require 'spree/testing_support/common_rake'
+
+desc "Generates a dummy app for testing an extension"
+namespace :extension do
+ task :test_app, [:user_class] do |t, args|
+ Spree::DummyGeneratorHelper.inject_extension_requirements = true
+ Rake::Task['common:test_app'].invoke
+ end
+end
+
diff --git a/core/lib/spree/testing_support/factories.rb b/core/lib/spree/testing_support/factories.rb
new file mode 100644
index 00000000000..15208c88d36
--- /dev/null
+++ b/core/lib/spree/testing_support/factories.rb
@@ -0,0 +1,19 @@
+require 'factory_girl'
+
+Spree::Zone.class_eval do
+ def self.global
+ find_by(name: 'GlobalZone') || FactoryGirl.create(:global_zone)
+ end
+end
+
+Dir["#{File.dirname(__FILE__)}/factories/**"].each do |f|
+ require File.expand_path(f)
+end
+
+FactoryGirl.define do
+ sequence(:random_string) { Faker::Lorem.sentence }
+ sequence(:random_description) { Faker::Lorem.paragraphs(1 + Kernel.rand(5)).join("\n") }
+ sequence(:random_email) { Faker::Internet.email }
+
+ sequence(:sku) { |n| "SKU-#{n}" }
+end
diff --git a/core/lib/spree/testing_support/factories/address_factory.rb b/core/lib/spree/testing_support/factories/address_factory.rb
new file mode 100644
index 00000000000..be339bd4c4c
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/address_factory.rb
@@ -0,0 +1,22 @@
+FactoryGirl.define do
+ factory :address, aliases: [:bill_address, :ship_address], class: Spree::Address do
+ firstname 'John'
+ lastname 'Doe'
+ company 'Company'
+ address1 '10 Lovely Street'
+ address2 'Northwest'
+ city 'Herndon'
+ zipcode '35005'
+ phone '555-555-0199'
+ alternative_phone '555-555-0199'
+
+ state { |address| address.association(:state) }
+ country do |address|
+ if address.state
+ address.state.country
+ else
+ address.association(:country)
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/adjustment_factory.rb b/core/lib/spree/testing_support/factories/adjustment_factory.rb
new file mode 100644
index 00000000000..596c977e7a1
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/adjustment_factory.rb
@@ -0,0 +1,26 @@
+FactoryGirl.define do
+ factory :adjustment, class: Spree::Adjustment do
+ association(:adjustable, factory: :order)
+ amount 100.0
+ label 'Shipping'
+ association(:source, factory: :tax_rate)
+ eligible true
+ end
+
+ factory :tax_adjustment, class: Spree::Adjustment do
+ association(:adjustable, factory: :line_item)
+ amount 10.0
+ label 'VAT 5%'
+ association(:source, factory: :tax_rate)
+ eligible true
+
+ after(:create) do |adjustment|
+ # Set correct tax category, so that adjustment amount is not 0
+ if adjustment.adjustable.is_a?(Spree::LineItem)
+ adjustment.source.tax_category = adjustment.adjustable.tax_category
+ adjustment.source.save
+ adjustment.update!
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/calculator_factory.rb b/core/lib/spree/testing_support/factories/calculator_factory.rb
new file mode 100644
index 00000000000..66d65203ae6
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/calculator_factory.rb
@@ -0,0 +1,20 @@
+FactoryGirl.define do
+ factory :calculator, class: Spree::Calculator::FlatRate do
+ after(:create) { |c| c.set_preference(:amount, 10.0) }
+ end
+
+ factory :no_amount_calculator, class: Spree::Calculator::FlatRate do
+ after(:create) { |c| c.set_preference(:amount, 0) }
+ end
+
+ factory :default_tax_calculator, class: Spree::Calculator::DefaultTax do
+ end
+
+ factory :shipping_calculator, class: Spree::Calculator::Shipping::FlatRate do
+ after(:create) { |c| c.set_preference(:amount, 10.0) }
+ end
+
+ factory :shipping_no_amount_calculator, class: Spree::Calculator::Shipping::FlatRate do
+ after(:create) { |c| c.set_preference(:amount, 0) }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/configuration_factory.rb b/core/lib/spree/testing_support/factories/configuration_factory.rb
new file mode 100644
index 00000000000..ca71ce69829
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/configuration_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :configuration, class: Spree::Configuration do
+ name 'Default Configuration'
+ type 'app_configuration'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/country_factory.rb b/core/lib/spree/testing_support/factories/country_factory.rb
new file mode 100644
index 00000000000..04953808329
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/country_factory.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :country, class: Spree::Country do
+ iso_name 'UNITED STATES'
+ name 'United States of America'
+ iso 'US'
+ iso3 'USA'
+ numcode 840
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/credit_card_factory.rb b/core/lib/spree/testing_support/factories/credit_card_factory.rb
new file mode 100644
index 00000000000..f91335f7879
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/credit_card_factory.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :credit_card, class: Spree::CreditCard do
+ verification_value 123
+ month 12
+ year { 1.year.from_now.year }
+ number '4111111111111111'
+ name 'Spree Commerce'
+ association(:payment_method, factory: :credit_card_payment_method)
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/customer_return_factory.rb b/core/lib/spree/testing_support/factories/customer_return_factory.rb
new file mode 100644
index 00000000000..3f1042e53d3
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/customer_return_factory.rb
@@ -0,0 +1,31 @@
+FactoryGirl.define do
+
+ factory :customer_return, class: Spree::CustomerReturn do
+ association(:stock_location, factory: :stock_location)
+
+ transient do
+ line_items_count 1
+ return_items_count { line_items_count }
+ end
+
+ before(:create) do |customer_return, evaluator|
+ shipped_order = create(:shipped_order, line_items_count: evaluator.line_items_count)
+
+ shipped_order.inventory_units.take(evaluator.return_items_count).each do |inventory_unit|
+ customer_return.return_items << build(:return_item, inventory_unit: inventory_unit)
+ end
+ end
+
+ factory :customer_return_with_accepted_items do
+ after(:create) do |customer_return|
+ customer_return.return_items.each(&:accept!)
+ end
+ end
+ end
+
+ # for the case when you want to supply existing return items instead of generating some
+ factory :customer_return_without_return_items, class: Spree::CustomerReturn do
+ association(:stock_location, factory: :stock_location)
+ end
+
+end
diff --git a/core/lib/spree/testing_support/factories/inventory_unit_factory.rb b/core/lib/spree/testing_support/factories/inventory_unit_factory.rb
new file mode 100644
index 00000000000..6486c6c7a99
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/inventory_unit_factory.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :inventory_unit, class: Spree::InventoryUnit do
+ variant
+ order
+ line_item
+ state 'on_hand'
+ association(:shipment, factory: :shipment, state: 'pending')
+ # return_authorization
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/line_item_factory.rb b/core/lib/spree/testing_support/factories/line_item_factory.rb
new file mode 100644
index 00000000000..f9c85d53f38
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/line_item_factory.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :line_item, class: Spree::LineItem do
+ quantity 1
+ price { BigDecimal.new('10.00') }
+ pre_tax_amount { price }
+ order
+ transient do
+ association :product
+ end
+ variant{ product.master }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/options_factory.rb b/core/lib/spree/testing_support/factories/options_factory.rb
new file mode 100644
index 00000000000..0e4ba0e1a29
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/options_factory.rb
@@ -0,0 +1,13 @@
+FactoryGirl.define do
+ factory :option_value, class: Spree::OptionValue do
+ sequence(:name) { |n| "Size-#{n}" }
+
+ presentation 'S'
+ option_type
+ end
+
+ factory :option_type, class: Spree::OptionType do
+ sequence(:name) { |n| "foo-size-#{n}" }
+ presentation 'Size'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/order_factory.rb b/core/lib/spree/testing_support/factories/order_factory.rb
new file mode 100644
index 00000000000..c6cdf49c790
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/order_factory.rb
@@ -0,0 +1,90 @@
+FactoryGirl.define do
+ factory :order, class: Spree::Order do
+ user
+ bill_address
+ completed_at nil
+ email { user.email }
+ store
+
+ transient do
+ line_items_price BigDecimal.new(10)
+ end
+
+ factory :order_with_totals do
+ after(:create) do |order, evaluator|
+ create(:line_item, order: order, price: evaluator.line_items_price)
+ order.line_items.reload # to ensure order.line_items is accessible after
+ end
+ end
+
+ factory :order_with_line_item_quantity do
+ transient do
+ line_items_quantity 1
+ end
+
+ after(:create) do |order, evaluator|
+ create(:line_item, order: order, price: evaluator.line_items_price, quantity: evaluator.line_items_quantity)
+ order.line_items.reload # to ensure order.line_items is accessible after
+ end
+ end
+
+ factory :order_with_line_items do
+ bill_address
+ ship_address
+
+ transient do
+ line_items_count 1
+ shipment_cost 100
+ end
+
+ after(:create) do |order, evaluator|
+ create_list(:line_item, evaluator.line_items_count, order: order, price: evaluator.line_items_price)
+ order.line_items.reload
+
+ create(:shipment, order: order, cost: evaluator.shipment_cost)
+ order.shipments.reload
+
+ order.update!
+ end
+
+ factory :completed_order_with_totals do
+ state 'complete'
+
+ after(:create) do |order|
+ order.refresh_shipment_rates
+ order.update_column(:completed_at, Time.now)
+ end
+
+ factory :completed_order_with_pending_payment do
+ after(:create) do |order|
+ create(:payment, amount: order.total, order: order)
+ end
+ end
+
+ factory :order_ready_to_ship do
+ payment_state 'paid'
+ shipment_state 'ready'
+
+ after(:create) do |order|
+ create(:payment, amount: order.total, order: order, state: 'completed')
+ order.shipments.each do |shipment|
+ shipment.inventory_units.update_all state: 'on_hand'
+ shipment.update_column('state', 'ready')
+ end
+ order.reload
+ end
+
+ factory :shipped_order do
+ after(:create) do |order|
+ order.shipments.each do |shipment|
+ shipment.inventory_units.update_all state: 'shipped'
+ shipment.update_column('state', 'shipped')
+ end
+ order.reload
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/payment_factory.rb b/core/lib/spree/testing_support/factories/payment_factory.rb
new file mode 100644
index 00000000000..dfdc433520c
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/payment_factory.rb
@@ -0,0 +1,23 @@
+FactoryGirl.define do
+ factory :payment, class: Spree::Payment do
+ amount 45.75
+ association(:payment_method, factory: :credit_card_payment_method)
+ association(:source, factory: :credit_card)
+ order
+ state 'checkout'
+ response_code '12345'
+
+ factory :payment_with_refund do
+ state 'completed'
+ after :create do |payment|
+ create(:refund, amount: 5, payment: payment)
+ end
+ end
+ end
+
+ factory :check_payment, class: Spree::Payment do
+ amount 45.75
+ association(:payment_method, factory: :check_payment_method)
+ order
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/payment_method_factory.rb b/core/lib/spree/testing_support/factories/payment_method_factory.rb
new file mode 100644
index 00000000000..3417eb9acbe
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/payment_method_factory.rb
@@ -0,0 +1,18 @@
+FactoryGirl.define do
+ factory :check_payment_method, class: Spree::PaymentMethod::Check do
+ name 'Check'
+ environment 'test'
+ end
+
+ factory :credit_card_payment_method, class: Spree::Gateway::Bogus do
+ name 'Credit Card'
+ environment 'test'
+ end
+
+ # authorize.net was moved to spree_gateway.
+ # Leaving this factory in place with bogus in case anyone is using it.
+ factory :simple_credit_card_payment_method, class: Spree::Gateway::BogusSimple do
+ name 'Credit Card'
+ environment 'test'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/price_factory.rb b/core/lib/spree/testing_support/factories/price_factory.rb
new file mode 100644
index 00000000000..1612eb11b15
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/price_factory.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :price, class: Spree::Price do
+ variant
+ amount 19.99
+ currency 'USD'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/product_factory.rb b/core/lib/spree/testing_support/factories/product_factory.rb
new file mode 100644
index 00000000000..d679fb9fff6
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/product_factory.rb
@@ -0,0 +1,36 @@
+FactoryGirl.define do
+ factory :base_product, class: Spree::Product do
+ sequence(:name) { |n| "Product ##{n} - #{Kernel.rand(9999)}" }
+ description { generate(:random_description) }
+ price 19.99
+ cost_price 17.00
+ sku { generate(:sku) }
+ available_on { 1.year.ago }
+ deleted_at nil
+ shipping_category { |r| Spree::ShippingCategory.first || r.association(:shipping_category) }
+
+ # ensure stock item will be created for this products master
+ before(:create) { create(:stock_location) if Spree::StockLocation.count == 0 }
+
+ factory :custom_product do
+ name 'Custom Product'
+ price 17.99
+
+ tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) }
+ end
+
+ factory :product do
+ tax_category { |r| Spree::TaxCategory.first || r.association(:tax_category) }
+
+ factory :product_in_stock do
+ after :create do |product|
+ product.master.stock_items.first.adjust_count_on_hand(10)
+ end
+ end
+
+ factory :product_with_option_types do
+ after(:create) { |product| create(:product_option_type, product: product) }
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/product_option_type_factory.rb b/core/lib/spree/testing_support/factories/product_option_type_factory.rb
new file mode 100644
index 00000000000..ddb8a0bb1e4
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/product_option_type_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :product_option_type, class: Spree::ProductOptionType do
+ product
+ option_type
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/product_property_factory.rb b/core/lib/spree/testing_support/factories/product_property_factory.rb
new file mode 100644
index 00000000000..68f4f439c3e
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/product_property_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :product_property, class: Spree::ProductProperty do
+ product
+ property
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/promotion_category_factory.rb b/core/lib/spree/testing_support/factories/promotion_category_factory.rb
new file mode 100644
index 00000000000..ebde03a97ad
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/promotion_category_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :promotion_category, class: Spree::PromotionCategory do
+ name 'Promotion Category'
+ end
+end
+
diff --git a/core/lib/spree/testing_support/factories/promotion_factory.rb b/core/lib/spree/testing_support/factories/promotion_factory.rb
new file mode 100644
index 00000000000..e97a75bf889
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/promotion_factory.rb
@@ -0,0 +1,52 @@
+FactoryGirl.define do
+ factory :promotion, class: Spree::Promotion do
+ name 'Promo'
+
+ trait :with_line_item_adjustment do
+ transient do
+ adjustment_rate 10
+ end
+
+ after(:create) do |promotion, evaluator|
+ calculator = Spree::Calculator::FlatRate.new
+ calculator.preferred_amount = evaluator.adjustment_rate
+ Spree::Promotion::Actions::CreateItemAdjustments.create!(calculator: calculator, promotion: promotion)
+ end
+ end
+ factory :promotion_with_item_adjustment, traits: [:with_line_item_adjustment]
+
+ trait :with_order_adjustment do
+ transient do
+ weighted_order_adjustment_amount 10
+ end
+
+ after(:create) do |promotion, evaluator|
+ calculator = Spree::Calculator::FlatRate.new
+ calculator.preferred_amount = evaluator.weighted_order_adjustment_amount
+ action = Spree::Promotion::Actions::CreateAdjustment.create!(calculator: calculator)
+ promotion.actions << action
+ promotion.save!
+ end
+ end
+ factory :promotion_with_order_adjustment, traits: [:with_order_adjustment]
+
+ trait :with_item_total_rule do
+ transient do
+ item_total_threshold_amount 10
+ end
+
+ after(:create) do |promotion, evaluator|
+ rule = Spree::Promotion::Rules::ItemTotal.create!(
+ preferred_operator_min: 'gte',
+ preferred_operator_max: 'lte',
+ preferred_amount_min: evaluator.item_total_threshold_amount,
+ preferred_amount_max: evaluator.item_total_threshold_amount + 100
+ )
+ promotion.rules << rule
+ promotion.save!
+ end
+ end
+ factory :promotion_with_item_total_rule, traits: [:with_item_total_rule]
+
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/property_factory.rb b/core/lib/spree/testing_support/factories/property_factory.rb
new file mode 100644
index 00000000000..793f3d5f67c
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/property_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :property, class: Spree::Property do
+ name 'baseball_cap_color'
+ presentation 'cap color'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/prototype_factory.rb b/core/lib/spree/testing_support/factories/prototype_factory.rb
new file mode 100644
index 00000000000..897a86a4f56
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/prototype_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :prototype, class: Spree::Prototype do
+ name 'Baseball Cap'
+ properties { [create(:property)] }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/refund_factory.rb b/core/lib/spree/testing_support/factories/refund_factory.rb
new file mode 100644
index 00000000000..1a79c4a02c1
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/refund_factory.rb
@@ -0,0 +1,14 @@
+FactoryGirl.define do
+ sequence(:refund_transaction_id) { |n| "fake-refund-transaction-#{n}"}
+
+ factory :refund, class: Spree::Refund do
+ amount 100.00
+ transaction_id { generate(:refund_transaction_id) }
+ association(:payment, state: 'completed')
+ association(:reason, factory: :refund_reason)
+ end
+
+ factory :refund_reason, class: Spree::RefundReason do
+ sequence(:name) { |n| "Refund for return ##{n}" }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/reimbursement_factory.rb b/core/lib/spree/testing_support/factories/reimbursement_factory.rb
new file mode 100644
index 00000000000..aed755effc9
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/reimbursement_factory.rb
@@ -0,0 +1,16 @@
+FactoryGirl.define do
+ factory :reimbursement, class: Spree::Reimbursement do
+ transient do
+ return_items_count 1
+ end
+
+ customer_return { create(:customer_return_with_accepted_items, line_items_count: return_items_count) }
+
+ before(:create) do |reimbursement, evaluator|
+ reimbursement.order ||= reimbursement.customer_return.order
+ if reimbursement.return_items.empty?
+ reimbursement.return_items = reimbursement.customer_return.return_items
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/reimbursement_type_factory.rb b/core/lib/spree/testing_support/factories/reimbursement_type_factory.rb
new file mode 100644
index 00000000000..74495bbfb59
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/reimbursement_type_factory.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :reimbursement_type, class: Spree::ReimbursementType do
+ sequence(:name) { |n| "Reimbursement Type #{n}" }
+ active true
+ mutable true
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/return_authorization_factory.rb b/core/lib/spree/testing_support/factories/return_authorization_factory.rb
new file mode 100644
index 00000000000..c0f14f2eb75
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/return_authorization_factory.rb
@@ -0,0 +1,18 @@
+FactoryGirl.define do
+ factory :return_authorization, class: Spree::ReturnAuthorization do
+ association(:order, factory: :shipped_order)
+ association(:stock_location, factory: :stock_location)
+ association(:reason, factory: :return_authorization_reason)
+ memo 'Items were broken'
+ end
+
+ factory :new_return_authorization, class: Spree::ReturnAuthorization do
+ association(:order, factory: :shipped_order)
+ association(:stock_location, factory: :stock_location)
+ association(:reason, factory: :return_authorization_reason)
+ end
+
+ factory :return_authorization_reason, class: Spree::ReturnAuthorizationReason do
+ sequence(:name) { |n| "Defect ##{n}" }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/return_item_factory.rb b/core/lib/spree/testing_support/factories/return_item_factory.rb
new file mode 100644
index 00000000000..271b6826218
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/return_item_factory.rb
@@ -0,0 +1,10 @@
+FactoryGirl.define do
+ factory :return_item, class: Spree::ReturnItem do
+ association(:inventory_unit, factory: :inventory_unit, state: :shipped)
+ association(:return_authorization, factory: :return_authorization)
+
+ factory :exchange_return_item do
+ association(:exchange_variant, factory: :variant)
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/role_factory.rb b/core/lib/spree/testing_support/factories/role_factory.rb
new file mode 100644
index 00000000000..4974b645a00
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/role_factory.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :role, class: Spree::Role do
+ sequence(:name) { |n| "Role ##{n}" }
+
+ factory :admin_role do
+ name 'admin'
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/shipment_factory.rb b/core/lib/spree/testing_support/factories/shipment_factory.rb
new file mode 100644
index 00000000000..06c1c96e888
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/shipment_factory.rb
@@ -0,0 +1,23 @@
+FactoryGirl.define do
+ factory :shipment, class: Spree::Shipment do
+ tracking 'U10000'
+ cost 100.00
+ state 'pending'
+ order
+ stock_location
+
+ after(:create) do |shipment, evalulator|
+ shipment.add_shipping_method(create(:shipping_method), true)
+
+ shipment.order.line_items.each do |line_item|
+ line_item.quantity.times do
+ shipment.inventory_units.create(
+ order_id: shipment.order_id,
+ variant_id: line_item.variant_id,
+ line_item_id: line_item.id
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/shipping_category_factory.rb b/core/lib/spree/testing_support/factories/shipping_category_factory.rb
new file mode 100644
index 00000000000..c3ea75c9f03
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/shipping_category_factory.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :shipping_category, class: Spree::ShippingCategory do
+ sequence(:name) { |n| "ShippingCategory ##{n}" }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/shipping_method_factory.rb b/core/lib/spree/testing_support/factories/shipping_method_factory.rb
new file mode 100644
index 00000000000..acab43731f5
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/shipping_method_factory.rb
@@ -0,0 +1,21 @@
+FactoryGirl.define do
+ factory :base_shipping_method, class: Spree::ShippingMethod do
+ zones { |a| [Spree::Zone.global] }
+ name 'UPS Ground'
+ code 'UPS_GROUND'
+
+ before(:create) do |shipping_method, evaluator|
+ if shipping_method.shipping_categories.empty?
+ shipping_method.shipping_categories << (Spree::ShippingCategory.first || create(:shipping_category))
+ end
+ end
+
+ factory :shipping_method, class: Spree::ShippingMethod do
+ association(:calculator, factory: :shipping_calculator, strategy: :build)
+ end
+
+ factory :free_shipping_method, class: Spree::ShippingMethod do
+ association(:calculator, factory: :shipping_no_amount_calculator, strategy: :build)
+ end
+ end
+end
diff --git a/core/lib/spree/core/testing_support/factories/state_factory.rb b/core/lib/spree/testing_support/factories/state_factory.rb
similarity index 83%
rename from core/lib/spree/core/testing_support/factories/state_factory.rb
rename to core/lib/spree/testing_support/factories/state_factory.rb
index 23d7107cbe1..c5ca779c507 100644
--- a/core/lib/spree/core/testing_support/factories/state_factory.rb
+++ b/core/lib/spree/testing_support/factories/state_factory.rb
@@ -1,5 +1,5 @@
FactoryGirl.define do
- factory :state, :class => Spree::State do
+ factory :state, class: Spree::State do
name 'Alabama'
abbr 'AL'
country do |country|
diff --git a/core/lib/spree/testing_support/factories/stock_factory.rb b/core/lib/spree/testing_support/factories/stock_factory.rb
new file mode 100644
index 00000000000..adabf9cee14
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/stock_factory.rb
@@ -0,0 +1,31 @@
+FactoryGirl.define do
+ # must use build()
+ factory :stock_packer, class: Spree::Stock::Packer do
+ transient do
+ stock_location { build(:stock_location) }
+ contents []
+ end
+
+ initialize_with { new(stock_location, contents) }
+ end
+
+ factory :stock_package, class: Spree::Stock::Package do
+ transient do
+ stock_location { build(:stock_location) }
+ contents { [] }
+ variants_contents { {} }
+ end
+
+ initialize_with { new(stock_location, contents) }
+
+ after(:build) do |package, evaluator|
+ evaluator.variants_contents.each do |variant, count|
+ package.add_multiple build_list(:inventory_unit, count, variant: variant)
+ end
+ end
+
+ factory :stock_package_fulfilled do
+ transient { variants_contents { { build(:variant) => 2 } } }
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/stock_item_factory.rb b/core/lib/spree/testing_support/factories/stock_item_factory.rb
new file mode 100644
index 00000000000..1a24818de1c
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/stock_item_factory.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :stock_item, class: Spree::StockItem do
+ backorderable true
+ stock_location
+ variant
+
+ after(:create) { |object| object.adjust_count_on_hand(10) }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/stock_location_factory.rb b/core/lib/spree/testing_support/factories/stock_location_factory.rb
new file mode 100644
index 00000000000..61251381f49
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/stock_location_factory.rb
@@ -0,0 +1,28 @@
+FactoryGirl.define do
+ factory :stock_location, class: Spree::StockLocation do
+ name 'NY Warehouse'
+ address1 '1600 Pennsylvania Ave NW'
+ city 'Washington'
+ zipcode '20500'
+ phone '(202) 456-1111'
+ active true
+ backorderable_default true
+
+ country { |stock_location| Spree::Country.first || stock_location.association(:country) }
+ state do |stock_location|
+ stock_location.country.states.first || stock_location.association(:state, :country => stock_location.country)
+ end
+
+ factory :stock_location_with_items do
+ after(:create) do |stock_location, evaluator|
+ # variant will add itself to all stock_locations in an after_create
+ # creating a product will automatically create a master variant
+ product_1 = create(:product)
+ product_2 = create(:product)
+
+ stock_location.stock_items.where(:variant_id => product_1.master.id).first.adjust_count_on_hand(10)
+ stock_location.stock_items.where(:variant_id => product_2.master.id).first.adjust_count_on_hand(20)
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/stock_movement_factory.rb b/core/lib/spree/testing_support/factories/stock_movement_factory.rb
new file mode 100644
index 00000000000..04b5b0cc3c5
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/stock_movement_factory.rb
@@ -0,0 +1,11 @@
+FactoryGirl.define do
+ factory :stock_movement, class: Spree::StockMovement do
+ quantity 1
+ action 'sold'
+ stock_item
+ end
+
+ trait :received do
+ action 'received'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/store_factory.rb b/core/lib/spree/testing_support/factories/store_factory.rb
new file mode 100644
index 00000000000..092490de34d
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/store_factory.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :store, class: Spree::Store do
+ sequence(:code) { |i| "spree_#{i}" }
+ name 'Spree Test Store'
+ url 'www.example.com'
+ mail_from_address 'spree@example.org'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/tax_category_factory.rb b/core/lib/spree/testing_support/factories/tax_category_factory.rb
new file mode 100644
index 00000000000..57e3652689b
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/tax_category_factory.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :tax_category, class: Spree::TaxCategory do
+ name { "TaxCategory - #{rand(999999)}" }
+ description { generate(:random_string) }
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/tax_rate_factory.rb b/core/lib/spree/testing_support/factories/tax_rate_factory.rb
new file mode 100644
index 00000000000..60cb9db804c
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/tax_rate_factory.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :tax_rate, class: Spree::TaxRate do
+ zone
+ amount 0.1
+ tax_category
+ association(:calculator, factory: :default_tax_calculator)
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/taxon_factory.rb b/core/lib/spree/testing_support/factories/taxon_factory.rb
new file mode 100644
index 00000000000..2353d1927c2
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/taxon_factory.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :taxon, class: Spree::Taxon do
+ name 'Ruby on Rails'
+ taxonomy
+ parent_id nil
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/taxonomy_factory.rb b/core/lib/spree/testing_support/factories/taxonomy_factory.rb
new file mode 100644
index 00000000000..c4e8681d4af
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/taxonomy_factory.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :taxonomy, class: Spree::Taxonomy do
+ name 'Brand'
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/tracker_factory.rb b/core/lib/spree/testing_support/factories/tracker_factory.rb
new file mode 100644
index 00000000000..9f0bc890b04
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/tracker_factory.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :tracker, class: Spree::Tracker do
+ environment { Rails.env }
+ analytics_id 'A100'
+ active true
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/user_factory.rb b/core/lib/spree/testing_support/factories/user_factory.rb
new file mode 100644
index 00000000000..b9544caa8ff
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/user_factory.rb
@@ -0,0 +1,22 @@
+FactoryGirl.define do
+ sequence :user_authentication_token do |n|
+ "xxxx#{Time.now.to_i}#{rand(1000)}#{n}xxxxxxxxxxxxx"
+ end
+
+ factory :user, class: Spree.user_class do
+ email { generate(:random_email) }
+ login { email }
+ password 'secret'
+ password_confirmation { password }
+ authentication_token { generate(:user_authentication_token) } if Spree.user_class.attribute_method? :authentication_token
+
+ factory :admin_user do
+ spree_roles { [Spree::Role.find_by(name: 'admin') || create(:role, name: 'admin')] }
+ end
+
+ factory :user_with_addreses do
+ ship_address
+ bill_address
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/variant_factory.rb b/core/lib/spree/testing_support/factories/variant_factory.rb
new file mode 100644
index 00000000000..93d43499c84
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/variant_factory.rb
@@ -0,0 +1,39 @@
+FactoryGirl.define do
+ sequence(:random_float) { BigDecimal.new("#{rand(200)}.#{rand(99)}") }
+
+ factory :base_variant, class: Spree::Variant do
+ price 19.99
+ cost_price 17.00
+ sku { generate(:sku) }
+ weight { generate(:random_float) }
+ height { generate(:random_float) }
+ width { generate(:random_float) }
+ depth { generate(:random_float) }
+ is_master 0
+ track_inventory true
+
+ product { |p| p.association(:base_product) }
+ option_values { [create(:option_value)] }
+
+ # ensure stock item will be created for this variant
+ before(:create) { create(:stock_location) if Spree::StockLocation.count == 0 }
+
+ factory :variant do
+ # on_hand 5
+ product { |p| p.association(:product) }
+ end
+
+ factory :master_variant do
+ is_master 1
+ end
+
+ factory :on_demand_variant do
+ track_inventory false
+
+ factory :on_demand_master_variant do
+ is_master 1
+ end
+ end
+
+ end
+end
diff --git a/core/lib/spree/testing_support/factories/zone_factory.rb b/core/lib/spree/testing_support/factories/zone_factory.rb
new file mode 100644
index 00000000000..db3c3e583dd
--- /dev/null
+++ b/core/lib/spree/testing_support/factories/zone_factory.rb
@@ -0,0 +1,17 @@
+FactoryGirl.define do
+ factory :global_zone, class: Spree::Zone do
+ name 'GlobalZone'
+ description { generate(:random_string) }
+ zone_members do |proxy|
+ zone = proxy.instance_eval { @instance }
+ Spree::Country.all.map do |c|
+ zone_member = Spree::ZoneMember.create(zoneable: c, zone: zone)
+ end
+ end
+ end
+
+ factory :zone, class: Spree::Zone do
+ name { generate(:random_string) }
+ description { generate(:random_string) }
+ end
+end
diff --git a/core/lib/spree/testing_support/flash.rb b/core/lib/spree/testing_support/flash.rb
new file mode 100644
index 00000000000..703bdb4f6f0
--- /dev/null
+++ b/core/lib/spree/testing_support/flash.rb
@@ -0,0 +1,27 @@
+module Spree
+ module TestingSupport
+ module Flash
+ def assert_flash_success(flash)
+ flash = convert_flash(flash)
+
+ within("[class='flash success']") do
+ expect(page).to have_content(flash)
+ end
+ end
+
+ def assert_successful_update_message(resource)
+ flash = Spree.t(:successfully_updated, resource: Spree.t(resource))
+ assert_flash_success(flash)
+ end
+
+ private
+
+ def convert_flash(flash)
+ if flash.is_a?(Symbol)
+ flash = Spree.t(flash)
+ end
+ flash
+ end
+ end
+ end
+end
diff --git a/core/lib/spree/testing_support/i18n.rb b/core/lib/spree/testing_support/i18n.rb
new file mode 100644
index 00000000000..786e2f1cf91
--- /dev/null
+++ b/core/lib/spree/testing_support/i18n.rb
@@ -0,0 +1,97 @@
+# This file exists solely to test whether or not there are missing translations
+# within the code that Spree's test suite covers.
+#
+# If there is a translation referenced which has no corresponding key within the
+# .yml file, then there will be a message output at the end of the suite showing
+# that.
+#
+# If there is a translation within the locale file which *isn't* used in the
+# test, this will also be shown at the end of the suite run.
+module Spree
+ class << self
+ attr_accessor :used_translations, :missing_translation_messages,
+ :unused_translations, :unused_translation_messages
+ alias_method :normal_t, :t
+ end
+
+ def self.t(*args)
+ original_args = args.dup
+ options = args.extract_options!
+ self.used_translations ||= []
+ [*args.first].each do |translation_key|
+ key = ([*options[:scope]] << translation_key).join('.')
+ self.used_translations << key
+ end
+ normal_t(*original_args)
+ end
+
+ def self.check_missing_translations
+ self.missing_translation_messages = []
+ self.used_translations ||= []
+ used_translations.map { |a| a.split('.') }.each do |translation_keys|
+ root = translations
+ processed_keys = []
+ translation_keys.each do |key|
+ begin
+ root = root.fetch(key.to_sym)
+ processed_keys << key.to_sym
+ rescue KeyError
+ error = "#{(processed_keys << key).join('.')} (#{I18n.locale})"
+ unless Spree.missing_translation_messages.include?(error)
+ Spree.missing_translation_messages << error
+ end
+ end
+ end
+ end
+ end
+
+ def self.check_unused_translations
+ self.used_translations ||= []
+ self.unused_translation_messages = []
+ self.unused_translations = []
+ self.load_translations(translations)
+ translation_diff = unused_translations - used_translations
+ translation_diff.each do |translation|
+ Spree.unused_translation_messages << "#{translation} (#{I18n.locale})"
+ end
+ end
+
+ private
+
+ def self.load_translations(hash, root=[])
+ hash.each do |k,v|
+ if v.is_a?(Hash)
+ load_translations(v, root.dup << k)
+ else
+ key = (root + [k]).join('.')
+ self.unused_translations << key
+ end
+ end
+ end
+
+ def self.translations
+ @translations ||= I18n.backend.send(:translations)[I18n.locale][:spree]
+ end
+end
+
+RSpec.configure do |config|
+ # Need to check here again because this is used in i18n_spec too.
+ if ENV['CHECK_TRANSLATIONS']
+ config.after :suite do
+ Spree.check_missing_translations
+ if Spree.missing_translation_messages.any?
+ puts "\nThere are missing translations within Spree:"
+ puts Spree.missing_translation_messages.sort
+ exit(1)
+ end
+
+ Spree.check_unused_translations
+ if false && Spree.unused_translation_messages.any?
+ puts "\nThere are unused translations within Spree:"
+ puts Spree.unused_translation_messages.sort
+ exit(1)
+ end
+ end
+ end
+end
+
diff --git a/core/lib/spree/testing_support/order_walkthrough.rb b/core/lib/spree/testing_support/order_walkthrough.rb
new file mode 100644
index 00000000000..5982915e3ba
--- /dev/null
+++ b/core/lib/spree/testing_support/order_walkthrough.rb
@@ -0,0 +1,67 @@
+class OrderWalkthrough
+ def self.up_to(state)
+ # A payment method must exist for an order to proceed through the Address state
+ unless Spree::PaymentMethod.exists?
+ FactoryGirl.create(:check_payment_method)
+ end
+
+ # Need to create a valid zone too...
+ zone = FactoryGirl.create(:zone)
+ country = FactoryGirl.create(:country)
+ zone.members << Spree::ZoneMember.create(:zoneable => country)
+ country.states << FactoryGirl.create(:state, :country => country)
+
+ # A shipping method must exist for rates to be displayed on checkout page
+ unless Spree::ShippingMethod.exists?
+ FactoryGirl.create(:shipping_method).tap do |sm|
+ sm.calculator.preferred_amount = 10
+ sm.calculator.preferred_currency = Spree::Config[:currency]
+ sm.calculator.save
+ end
+ end
+
+ order = Spree::Order.create!(email: "spree@example.com")
+ add_line_item!(order)
+ order.next!
+
+ end_state_position = states.index(state.to_sym)
+ states[0..end_state_position].each do |state|
+ send(state, order)
+ end
+
+ order
+ end
+
+ private
+
+ def self.add_line_item!(order)
+ FactoryGirl.create(:line_item, order: order)
+ order.reload
+ end
+
+ def self.address(order)
+ order.bill_address = FactoryGirl.create(:address, :country_id => Spree::Zone.global.members.first.zoneable.id)
+ order.ship_address = FactoryGirl.create(:address, :country_id => Spree::Zone.global.members.first.zoneable.id)
+ order.next!
+ end
+
+ def self.delivery(order)
+ order.next!
+ end
+
+ def self.payment(order)
+ order.payments.create!(payment_method: Spree::PaymentMethod.first, amount: order.total)
+ # TODO: maybe look at some way of making this payment_state change automatic
+ order.payment_state = 'paid'
+ order.next!
+ end
+
+ def self.complete(order)
+ #noop?
+ end
+
+ def self.states
+ [:address, :delivery, :payment, :complete]
+ end
+
+end
diff --git a/core/lib/spree/testing_support/preferences.rb b/core/lib/spree/testing_support/preferences.rb
new file mode 100644
index 00000000000..7c6c0fbf75c
--- /dev/null
+++ b/core/lib/spree/testing_support/preferences.rb
@@ -0,0 +1,31 @@
+module Spree
+ module TestingSupport
+ module Preferences
+ # Resets all preferences to default values, you can
+ # pass a block to override the defaults with a block
+ #
+ # reset_spree_preferences do |config|
+ # config.track_inventory_levels = false
+ # end
+ #
+ def reset_spree_preferences(&config_block)
+ Spree::Preferences::Store.instance.persistence = false
+ Spree::Preferences::Store.instance.clear_cache
+
+ config = Rails.application.config.spree.preferences
+ configure_spree_preferences &config_block if block_given?
+ end
+
+ def configure_spree_preferences
+ config = Rails.application.config.spree.preferences
+ yield(config) if block_given?
+ end
+
+ def assert_preference_unset(preference)
+ find("#preferences_#{preference}")['checked'].should be false
+ Spree::Config[preference].should be false
+ end
+ end
+ end
+end
+
diff --git a/core/lib/spree/core/url_helpers.rb b/core/lib/spree/testing_support/url_helpers.rb
similarity index 84%
rename from core/lib/spree/core/url_helpers.rb
rename to core/lib/spree/testing_support/url_helpers.rb
index 16f74992907..d238624cc68 100644
--- a/core/lib/spree/core/url_helpers.rb
+++ b/core/lib/spree/testing_support/url_helpers.rb
@@ -1,5 +1,5 @@
module Spree
- module Core
+ module TestingSupport
module UrlHelpers
def spree
Spree::Core::Engine.routes.url_helpers
diff --git a/core/lib/tasks/core.rake b/core/lib/tasks/core.rake
index fdc031d2c63..7ce6b5aa2f4 100644
--- a/core/lib/tasks/core.rake
+++ b/core/lib/tasks/core.rake
@@ -1,43 +1,31 @@
require 'active_record'
-require 'spree/core/custom_fixtures'
namespace :db do
desc %q{Loads a specified fixture file:
-For .yml/.csv use rake db:load_file[spree/filename.yml,/absolute/path/to/parent/]
-For .rb use rake db:load_file[/absolute/path/to/sample/filename.rb]}
+use rake db:load_file[/absolute/path/to/sample/filename.rb]}
task :load_file , [:file, :dir] => :environment do |t, args|
file = Pathname.new(args.file)
- if %w{.csv .yml}.include? file.extname
- puts "loading fixture #{Pathname.new(args.dir).join(file)}"
- Spree::Core::Fixtures.create_fixtures(args.dir, file.to_s.sub(file.extname, ""))
- elsif file.exist?
- puts "loading ruby #{file}"
- require file
- end
+ puts "loading ruby #{file}"
+ require file
end
desc "Loads fixtures from the the dir you specify using rake db:load_dir[loadfrom]"
- task :load_dir , [:dir] => :environment do |t , args|
+ task :load_dir , [:dir] => :environment do |t, args|
dir = args.dir
dir = File.join(Rails.root, "db", dir) if Pathname.new(dir).relative?
- fixtures = ActiveSupport::OrderedHash.new
- ruby_files = ActiveSupport::OrderedHash.new
- Dir.glob(File.join(dir , '**/*.{yml,csv,rb}')).each do |fixture_file|
+ ruby_files = {}
+ Dir.glob(File.join(dir , '**/*.{rb}')).each do |fixture_file|
ext = File.extname fixture_file
- if ext == ".rb"
- ruby_files[File.basename(fixture_file, '.*')] = fixture_file
- else
- fixtures[fixture_file.sub(dir, "")[1..-1]] = fixture_file
- end
- end
- fixtures.sort.each do |relative_path , fixture_file|
- # an invoke will only execute the task once
- Rake::Task["db:load_file"].execute( Rake::TaskArguments.new([:file, :dir], [relative_path, dir]) )
+ ruby_files[File.basename(fixture_file, '.*')] = fixture_file
end
ruby_files.sort.each do |fixture , ruby_file|
+ # If file is exists within application it takes precendence.
+ if File.exists?(File.join(Rails.root, "db/default/spree", "#{fixture}.rb"))
+ ruby_file = File.expand_path(File.join(Rails.root, "db/default/spree", "#{fixture}.rb"))
+ end
# an invoke will only execute the task once
Rake::Task["db:load_file"].execute( Rake::TaskArguments.new([:file], [ruby_file]) )
end
@@ -102,8 +90,8 @@ For .rb use rake db:load_file[/absolute/path/to/sample/filename.rb]}
end
if load_sample
- #prevent errors for missing attributes (since rails 3.1 upgrade)
-
+ # Reload models' attributes in case they were loaded in old migrations with wrong attributes
+ ActiveRecord::Base.descendants.each(&:reset_column_information)
Rake::Task["spree_sample:load"].invoke
end
diff --git a/core/lib/tasks/email.rake b/core/lib/tasks/email.rake
new file mode 100644
index 00000000000..5b1db4a970e
--- /dev/null
+++ b/core/lib/tasks/email.rake
@@ -0,0 +1,7 @@
+namespace :email do
+ desc 'Sends test email to specified address - Example: EMAIL=spree@example.com bundle exec rake test:email'
+ task :test => :environment do
+ raise ArgumentError, "Must pass EMAIL environment variable. Example: EMAIL=spree@example.com bundle exec rake test:email" unless ENV['EMAIL'].present?
+ Spree::TestMailer.test_email(ENV['EMAIL']).deliver!
+ end
+end
diff --git a/core/lib/tasks/exchanges.rake b/core/lib/tasks/exchanges.rake
new file mode 100644
index 00000000000..a5561041282
--- /dev/null
+++ b/core/lib/tasks/exchanges.rake
@@ -0,0 +1,70 @@
+namespace :exchanges do
+ desc %q{Takes unreturned exchanged items and creates a new order to charge
+ the customer for not returning them}
+ task charge_unreturned_items: :environment do
+
+ unreturned_return_items = Spree::ReturnItem.awaiting_return.exchange_processed.joins(:exchange_inventory_unit).where([
+ "spree_inventory_units.created_at < :days_ago AND spree_inventory_units.state = :iu_state",
+ days_ago: Spree::Config[:expedited_exchanges_days_window].days.ago, iu_state: "shipped"
+ ]).to_a
+
+ # Determine that a return item has already been deemed unreturned and therefore charged
+ # by the fact that its exchange inventory unit has popped off to a different order
+ unreturned_return_items.select! { |ri| ri.inventory_unit.order_id == ri.exchange_inventory_unit.order_id }
+
+ failed_orders = []
+
+ unreturned_return_items.group_by(&:exchange_shipment).each do |shipment, return_items|
+ begin
+ inventory_units = return_items.map(&:exchange_inventory_unit)
+
+ original_order = shipment.order
+ order_attributes = {
+ bill_address: original_order.bill_address,
+ ship_address: original_order.ship_address,
+ email: original_order.email
+ }
+ order_attributes[:store_id] = original_order.store_id
+ order = Spree::Order.create!(order_attributes)
+
+ order.associate_user!(original_order.user) if original_order.user
+
+ return_items.group_by(&:exchange_variant).map do |variant, variant_return_items|
+ variant_inventory_units = variant_return_items.map(&:exchange_inventory_unit)
+ line_item = Spree::LineItem.create!(variant: variant, quantity: variant_return_items.count, order: order)
+ variant_inventory_units.each { |i| i.update_attributes!(line_item_id: line_item.id, order_id: order.id) }
+ end
+
+ order.reload.update!
+ while order.state != order.checkout_steps[-2] && order.next; end
+
+ unless order.payments.present?
+ card_to_reuse = original_order.valid_credit_cards.first
+ card_to_reuse = original_order.user.credit_cards.default.first if !card_to_reuse && original_order.user
+ Spree::Payment.create!(order: order,
+ payment_method_id: card_to_reuse.try(:payment_method_id),
+ source: card_to_reuse,
+ amount: order.total)
+ end
+
+ # the order builds a shipment on its own on transition to delivery, but we want
+ # the original exchange shipment, not the built one
+ order.shipments.destroy_all
+ shipment.update_attributes!(order_id: order.id)
+ order.update_attributes!(state: "confirm")
+
+ order.reload.next!
+ order.update!
+ order.finalize!
+
+ failed_orders << order unless order.completed? && order.valid?
+ rescue
+ failed_orders << order
+ end
+ end
+ failure_message = failed_orders.map { |o| "#{o.number} - #{o.errors.full_messages}" }.join(", ")
+ raise UnableToChargeForUnreturnedItems.new(failure_message) if failed_orders.present?
+ end
+end
+
+class UnableToChargeForUnreturnedItems < StandardError; end
diff --git a/core/spec/controllers/controller_helpers_spec.rb b/core/spec/controllers/controller_helpers_spec.rb
deleted file mode 100644
index cfea32146d7..00000000000
--- a/core/spec/controllers/controller_helpers_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-# In this file, we want to test that the controller helpers function correctly
-# So we need to use one of the controllers inside Spree.
-# ProductsController is good.
-describe Spree::ProductsController do
-
- before do
- I18n.stub(:available_locales => [:en, :de])
- Rails.application.config.i18n.default_locale = :de
- end
-
- after do
- Rails.application.config.i18n.default_locale = :en
- I18n.locale = :en
- end
-
- # Regression test for #1184
- it "sets the default locale based off config.i18n.default_locale" do
- I18n.locale.should == :en
- spree_get :index
- I18n.locale.should == :de
- end
-end
diff --git a/core/spec/controllers/spree/admin/base_controller_spec.rb b/core/spec/controllers/spree/admin/base_controller_spec.rb
deleted file mode 100644
index 5c1757b1f48..00000000000
--- a/core/spec/controllers/spree/admin/base_controller_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# Spree's rpsec controller tests get the Spree::ControllerHacks
-# we don't need those for the anonymous controller here, so
-# we call process directly instead of get
-require 'spec_helper'
-
-describe Spree::Admin::BaseController do
-
- controller(Spree::Admin::BaseController) do
- def index
- render :text => 'test'
- end
- end
-
- describe "check alerts" do
- it "checks alerts with before_filter" do
- controller.should_receive :check_alerts
- process :index
- end
-
- it "saves alerts into session" do
- controller.stub(:should_check_alerts? => true)
- Spree::Alert.should_receive(:current).and_return([Spree::Alert.new(:message => "test alert", :severity => 'release')])
- process :index
- session[:alerts].first.message.should eq "test alert"
- end
-
- describe "should_check_alerts?" do
- before do
- Rails.env.stub(:production? => true)
- Spree::Config[:check_for_spree_alerts] = true
- Spree::Config[:last_check_for_spree_alerts] = nil
- end
-
- it "only checks alerts if production and preference is true" do
- controller.send(:should_check_alerts?).should be_true
- end
-
- it "only checks for production" do
- Rails.env.stub(:production? => false)
- controller.send(:should_check_alerts?).should be_false
- end
-
- it "only checks if preference is true" do
- Spree::Config[:check_for_spree_alerts] = false
- controller.send(:should_check_alerts?).should be_false
- end
- end
- end
-end
diff --git a/core/spec/controllers/spree/admin/image_settings_controller_spec.rb b/core/spec/controllers/spree/admin/image_settings_controller_spec.rb
deleted file mode 100644
index ab8d6a77dfe..00000000000
--- a/core/spec/controllers/spree/admin/image_settings_controller_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::ImageSettingsController do
- stub_authorization!
-
- context "updating image settings" do
- it "should be able to update paperclip settings" do
- spree_put :update, { :preferences => {
- "attachment_path" => "foo/bar",
- "attachment_default_url" => "baz/bar"
- }
- }
- Spree::Config[:attachment_path].should == "foo/bar"
- Spree::Config[:attachment_default_url].should == "baz/bar"
- end
-
- context "paperclip styles" do
- it "should be able to update the paperclip styles" do
- spree_put :update, { "attachment_styles" => { "thumb" => "25x25>" } }
- updated_styles = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles])
- updated_styles["thumb"].should == "25x25>"
- end
-
- it "should be able to add a new style" do
- spree_put :update, { "attachment_styles" => { }, "new_attachment_styles" => { "1" => { "name" => "jumbo", "value" => "2000x2000>" } } }
- styles = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles])
- styles["jumbo"].should == "2000x2000>"
- end
- end
-
- context "amazon s3" do
- after(:all) do
- Spree::Image.attachment_definitions[:attachment].delete :storage
- end
-
- it "should be able to update s3 settings" do
- spree_put :update, { :preferences => {
- "use_s3" => "1",
- "s3_access_key" => "a_valid_key",
- "s3_secret" => "a_secret",
- "s3_bucket" => "some_bucket"
- }
- }
- Spree::Config[:use_s3].should be_true
- Spree::Config[:s3_access_key].should == "a_valid_key"
- Spree::Config[:s3_secret].should == "a_secret"
- Spree::Config[:s3_bucket].should == "some_bucket"
- end
-
- context "headers" do
- before(:each) { Spree::Config[:use_s3] = true }
-
- it "should be able to update the s3 headers" do
- spree_put :update, { :preferences => { "use_s3" => "1" }, "s3_headers" => { "Cache-Control" => "max-age=1111" } }
- headers = ActiveSupport::JSON.decode(Spree::Config[:s3_headers])
- headers["Cache-Control"].should == "max-age=1111"
- end
-
- it "should be able to add a new header" do
- spree_put :update, { "s3_headers" => { }, "new_s3_headers" => { "1" => { "name" => "Charset", "value" => "utf-8" } } }
- headers = ActiveSupport::JSON.decode(Spree::Config[:s3_headers])
- headers["Charset"].should == "utf-8"
- end
- end
- end
- end
-end
-
diff --git a/core/spec/controllers/spree/admin/mail_methods_controller_spec.rb b/core/spec/controllers/spree/admin/mail_methods_controller_spec.rb
deleted file mode 100644
index 19dd44fc2d3..00000000000
--- a/core/spec/controllers/spree/admin/mail_methods_controller_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::MailMethodsController do
- stub_authorization!
-
- let(:mail_method) { mock_model(Spree::MailMethod).as_null_object }
-
- before do
- Spree::MailMethod.stub :find => mail_method
- request.env["HTTP_REFERER"] = "/"
- end
-
- context "#create" do
- it "should reinitialize the mail settings" do
- Spree::Core::MailSettings.should_receive :init
- spree_put :create, { :id => "456", :mail_method => {:environment => "foo"}}
- end
- end
-
- context "#update" do
- it "should reinitialize the mail settings" do
- Spree::Core::MailSettings.should_receive :init
- spree_put :update, { :id => "456", :mail_method => {:environment => "foo"}}
- end
- end
-
- it "can trigger testmail without current_user" do
- spree_post :testmail, :id => create(:mail_method).id
- flash[:error].should_not include("undefined local variable or method `current_user'")
- end
-end
diff --git a/core/spec/controllers/spree/admin/orders_controller_spec.rb b/core/spec/controllers/spree/admin/orders_controller_spec.rb
deleted file mode 100644
index 098db000c8f..00000000000
--- a/core/spec/controllers/spree/admin/orders_controller_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::OrdersController do
- stub_authorization!
-
- let(:order) { mock_model(Spree::Order, :complete? => true, :total => 100) }
-
- before do
- Spree::Order.stub :find_by_number! => order
- request.env["HTTP_REFERER"] = "http://localhost:3000"
- end
-
- context "#fire" do
- it "should fire the requested event on the payment" do
- order.should_receive(:foo).and_return true
- spree_put :fire, {:id => "R1234567", :e => "foo"}
- end
-
- it "should respond with a flash message if the event cannot be fired" do
- order.stub :foo => false
- spree_put :fire, {:id => "R1234567", :e => "foo"}
- flash[:error].should_not be_nil
- end
- end
-
- context "pagination" do
- it "can page through the orders" do
- spree_get :index, :page => 2, :per_page => 10
- assigns[:orders].offset_value.should == 10
- assigns[:orders].limit_value.should == 10
- end
- end
-end
diff --git a/core/spec/controllers/spree/admin/payment_methods_controller_spec.rb b/core/spec/controllers/spree/admin/payment_methods_controller_spec.rb
deleted file mode 100644
index a1acff2e198..00000000000
--- a/core/spec/controllers/spree/admin/payment_methods_controller_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-module Spree
- class GatewayWithPassword < PaymentMethod
- attr_accessible :preferred_password
-
- preference :password, :string, :default => "password"
- end
-end
-
-module Spree
- describe Admin::PaymentMethodsController do
- stub_authorization!
-
- let(:payment_method) { Spree::GatewayWithPassword.create!(:name => "Bogus", :preferred_password => "haxme") }
-
- # regression test for #2094
- it "does not clear password on update" do
- payment_method.preferred_password.should == "haxme"
- spree_put :update, :id => payment_method.id, :payment_method => { :type => payment_method.class.to_s, :preferred_password => "" }
- response.should redirect_to(spree.edit_admin_payment_method_path(payment_method))
-
- payment_method.reload
- payment_method.preferred_password.should == "haxme"
- end
-
- end
-end
diff --git a/core/spec/controllers/spree/admin/products_controller_spec.rb b/core/spec/controllers/spree/admin/products_controller_spec.rb
deleted file mode 100644
index 4109954edb5..00000000000
--- a/core/spec/controllers/spree/admin/products_controller_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::ProductsController do
- stub_authorization!
-
- context "#index" do
- # Regression test for #1259
- it "can find a product by SKU" do
- product = create(:product, :sku => "ABC123")
- spree_get :index, :q => { :sku_start => "ABC123" }
- assigns[:collection].should_not be_empty
- assigns[:collection].should include(product)
- end
- end
-
- context "creating a product" do
-
- include_context "product prototype"
-
- it "should create product" do
- spree_get :new
- response.should render_template("admin/products/new")
- end
-
- it "should create product from prototype" do
- spree_post :create, :product => product_attributes.merge(:prototype_id => prototype.id)
- product = Spree::Product.last
- response.should redirect_to(spree.edit_admin_product_path(product))
- prototype.properties.each do |property|
- product.properties.should include(property)
- end
- prototype.option_types.each do |ot|
- product.option_types.should include(ot)
- end
- product.variants_including_master.length.should == 1
- end
-
- it "should create product from prototype with option values hash" do
- spree_post :create, :product => product_attributes.merge(:prototype_id => prototype.id, :option_values_hash => option_values_hash)
- product = Spree::Product.last
- response.should redirect_to(spree.edit_admin_product_path(product))
- option_values_hash.each do |option_type_id, option_value_ids|
- Spree::ProductOptionType.where(:product_id => product.id, :option_type_id => option_type_id).first.should_not be_nil
- end
- product.variants.length.should == 3
- end
-
- end
-
- # regression test for #1370
- context "adding properties to a product" do
- let!(:product) { create(:product) }
- specify do
- spree_put :update, :id => product.to_param, :product => { :product_properties_attributes => { "1" => { :property_name => "Foo", :value => "bar" } } }
- flash[:success].should == "Product #{product.name.inspect} has been successfully updated!"
- end
-
- end
-
-
- # regression test for #801
- context "destroying a product" do
- let(:product) do
- product = create(:product)
- create(:variant, :product => product)
- product
- end
-
- it "deletes all the variants (including master) for the product" do
- spree_delete :destroy, :id => product
- product.reload.deleted_at.should_not be_nil
- product.variants_including_master.each do |variant|
- varaint.reload.deleted_at.should_not be_nil
- end
- end
- end
-end
diff --git a/core/spec/controllers/spree/admin/resource_controller_spec.rb b/core/spec/controllers/spree/admin/resource_controller_spec.rb
deleted file mode 100644
index 4b2892b6a79..00000000000
--- a/core/spec/controllers/spree/admin/resource_controller_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::ResourceController do
- stub_authorization!
-
- describe 'POST#update_positions' do
-
- before do
- Spree::Admin::ResourceController.any_instance.stub(:model_class).and_return Spree::Variant
- end
-
- it 'returns Ok on json' do
- variant = create(:variant)
- variant2 = create(:variant)
- expect {
- spree_post :update_positions, :id => variant.id, :positions => { variant.id => '2', variant2.id => '1' }, :format => "js"
- variant.reload
- }.to change(variant, :position).from(nil).to(2)
- end
-
- end
-end
\ No newline at end of file
diff --git a/core/spec/controllers/spree/admin/return_authorizations_controller_spec.rb b/core/spec/controllers/spree/admin/return_authorizations_controller_spec.rb
deleted file mode 100644
index 02701e2f88a..00000000000
--- a/core/spec/controllers/spree/admin/return_authorizations_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::ReturnAuthorizationsController do
- stub_authorization!
-
- # Regression test for #1370 #3
- let!(:order) { create(:order) }
- it "can create a return authorization" do
- spree_post :create, :order_id => order.to_param, :return_authorization => { :amount => 0.0, :reason => "" }
- response.should be_success
- end
-end
diff --git a/core/spec/controllers/spree/admin/search_controller_spec.rb b/core/spec/controllers/spree/admin/search_controller_spec.rb
deleted file mode 100644
index 4dfe2e6a928..00000000000
--- a/core/spec/controllers/spree/admin/search_controller_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::SearchController do
- stub_authorization!
- let(:user) { create(:user) }
-
- before do
- user.ship_address = create(:address)
- user.bill_address = create(:address)
- user.save
- end
-
- it "can find a user by their email "do
- spree_xhr_get :users, :q => user.email
- assigns[:users].should include(user)
- end
-
- it "can find a user by their ship address's first name" do
- spree_xhr_get :users, :q => user.ship_address.firstname
- assigns[:users].should include(user)
- end
-
- it "can find a user by their ship address's last name" do
- spree_xhr_get :users, :q => user.ship_address.lastname
- assigns[:users].should include(user)
- end
-
- it "can find a user by their bill address's first name" do
- spree_xhr_get :users, :q => user.bill_address.firstname
- assigns[:users].should include(user)
- end
-
- it "can find a user by their bill address's last name" do
- spree_xhr_get :users, :q => user.bill_address.lastname
- assigns[:users].should include(user)
- end
-
-end
diff --git a/core/spec/controllers/spree/admin/shipping_methods_controller_spec.rb b/core/spec/controllers/spree/admin/shipping_methods_controller_spec.rb
deleted file mode 100644
index caded40be4e..00000000000
--- a/core/spec/controllers/spree/admin/shipping_methods_controller_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Admin::ShippingMethodsController do
- stub_authorization!
-
- # Regression test for #1240
- it "should not hard-delete shipping methods" do
- Spree::ShippingMethod.should_receive(:find).and_return(shipping_method = stub_model(Spree::ShippingMethod))
- shipping_method.should_not_receive(:destroy)
- shipping_method.deleted_at.should be_nil
- spree_delete :destroy, :id => 1
- shipping_method.deleted_at.should_not be_nil
- end
-end
diff --git a/core/spec/controllers/spree/checkout_controller_spec.rb b/core/spec/controllers/spree/checkout_controller_spec.rb
deleted file mode 100644
index 23534450d19..00000000000
--- a/core/spec/controllers/spree/checkout_controller_spec.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-require 'spec_helper'
-
-describe Spree::CheckoutController do
- let(:order) do
- mock_model(Spree::Order, :checkout_allowed? => true,
- :user => nil,
- :email => nil,
- :completed? => false,
- :update_attributes => true,
- :payment? => false,
- :insufficient_stock_lines => [],
- :coupon_code => nil).as_null_object
- end
-
- before { controller.stub :current_order => order }
-
- context "#edit" do
-
- it "should redirect to the cart path unless checkout_allowed?" do
- order.stub :checkout_allowed? => false
- spree_get :edit, { :state => "delivery" }
- response.should redirect_to(spree.cart_path)
- end
-
- it "should redirect to the cart path if current_order is nil" do
- controller.stub!(:current_order).and_return(nil)
- spree_get :edit, { :state => "delivery" }
- response.should redirect_to(spree.cart_path)
- end
-
- it "should change to the requested state" do
- order.should_receive(:state=).with("payment").and_return true
- spree_get :edit, { :state => "payment" }
- end
-
- it "should redirect to cart if order is completed" do
- order.stub(:completed? => true)
- spree_get :edit, {:state => "address"}
- response.should redirect_to(spree.cart_path)
- end
-
- it "should associate the order with a user" do
- user = double("User", :last_incomplete_spree_order => nil)
- controller.stub(:spree_current_user => user)
- order.should_receive(:associate_user!).with(user)
- spree_get :edit, {}, :order_id => 1
- end
-
- it "should fire the spree.user.signup event if user has just signed up" do
- user = double("User", :last_incomplete_spree_order => nil)
- controller.stub(:spree_current_user => user)
- controller.should_receive(:fire_event).with("spree.user.signup", :user => user, :order => order)
- spree_get :edit, {}, :spree_user_signup => true
- end
-
- end
-
- context "#update" do
-
- context "save successful" do
- before do
- order.stub(:update_attribute).and_return true
- order.should_receive(:update_attributes).and_return true
- end
-
- it "should assign order" do
- order.stub :state => "address"
- spree_post :update, {:state => "confirm"}
- assigns[:order].should_not be_nil
- end
-
- it "should change to requested state" do
- order.stub :state => "address"
- order.should_receive(:state=).with('confirm')
- spree_post :update, {:state => "confirm"}
- end
-
- context "with next state" do
- before { order.stub :next => true }
-
- it "should advance the state" do
- order.stub :state => "address"
- order.should_receive(:next).and_return true
- spree_post :update, {:state => "delivery"}
- end
-
- it "should redirect the next state" do
- order.stub :state => "payment"
- spree_post :update, {:state => "delivery"}
- response.should redirect_to spree.checkout_state_path("payment")
- end
-
- context "when in the confirm state" do
- before { order.stub :state => "complete" }
-
- it "should redirect to the order view" do
- spree_post :update, {:state => "confirm"}
- response.should redirect_to spree.order_path(order)
- end
-
- it "should populate the flash message" do
- spree_post :update, {:state => "confirm"}
- flash.notice.should == I18n.t(:order_processed_successfully)
- end
-
- it "should remove completed order from the session" do
- spree_post :update, {:state => "confirm"}, {:order_id => "foofah"}
- session[:order_id].should be_nil
- end
-
- end
-
- end
- end
-
- context "save unsuccessful" do
- before { order.should_receive(:update_attributes).and_return false }
-
- it "should assign order" do
- spree_post :update, {:state => "confirm"}
- assigns[:order].should_not be_nil
- end
-
- it "should not change the order state" do
- order.should_not_receive(:update_attribute)
- spree_post :update, { :state => 'confirm' }
- end
-
- it "should render the edit template" do
- spree_post :update, { :state => 'confirm' }
- response.should render_template :edit
- end
- end
-
- context "when current_order is nil" do
- before { controller.stub! :current_order => nil }
- it "should not change the state if order is completed" do
- order.should_not_receive(:update_attribute)
- spree_post :update, {:state => "confirm"}
- end
-
- it "should redirect to the cart_path" do
- spree_post :update, {:state => "confirm"}
- response.should redirect_to spree.cart_path
- end
- end
-
- context "Spree::Core::GatewayError" do
-
- before do
- order.stub(:update_attributes).and_raise(Spree::Core::GatewayError)
- spree_post :update, {:state => "whatever"}
- end
-
- it "should render the edit template" do
- response.should render_template :edit
- end
-
- it "should set appropriate flash message" do
- flash[:error].should == I18n.t(:spree_gateway_error_flash_for_checkout)
- end
-
- end
-
- end
-
- context "When last inventory item has been purchased" do
- let(:product) { mock_model(Spree::Product, :name => "Amazing Object") }
- let(:variant) { mock_model(Spree::Variant, :on_hand => 0) }
- let(:line_item) { mock_model Spree::LineItem, :insufficient_stock? => true }
- let(:order) { create(:order) }
-
- before do
- order.stub(:line_items => [line_item])
-
- reset_spree_preferences do |config|
- config.track_inventory_levels = true
- config.allow_backorders = false
- end
-
- end
-
- context "and back orders == false" do
- before do
- spree_post :update, {:state => "payment"}
- end
-
- it "should render edit template" do
- response.should redirect_to spree.cart_path
- end
-
- it "should set flash message for no inventory" do
- flash[:error].should == I18n.t(:spree_inventory_error_flash_for_insufficient_quantity , :names => "'#{product.name}'" )
- end
-
- end
-
- end
-
-end
diff --git a/core/spec/controllers/spree/content_controller_spec.rb b/core/spec/controllers/spree/content_controller_spec.rb
deleted file mode 100644
index 779fe6ca52b..00000000000
--- a/core/spec/controllers/spree/content_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-describe Spree::ContentController do
- it "should not display a local file" do
- spree_get :show, :path => "../../Gemfile"
- response.response_code.should == 404
- end
-
- it "should display CVV page" do
- spree_get :cvv
- response.response_code.should == 200
- end
-end
diff --git a/core/spec/controllers/spree/home_controller_spec.rb b/core/spec/controllers/spree/home_controller_spec.rb
deleted file mode 100644
index b5342dde718..00000000000
--- a/core/spec/controllers/spree/home_controller_spec.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'spec_helper'
-
-describe Spree::HomeController do
- it "should provide the current user to the searcher class" do
- user = stub(:last_incomplete_spree_order => nil)
- controller.stub :spree_current_user => user
- Spree::Config.searcher_class.any_instance.should_receive(:current_user=).with(user)
- spree_get :index
- response.status.should == 200
- end
-end
diff --git a/core/spec/controllers/spree/orders_controller_extension_spec.rb b/core/spec/controllers/spree/orders_controller_extension_spec.rb
deleted file mode 100644
index 3bcac3a191b..00000000000
--- a/core/spec/controllers/spree/orders_controller_extension_spec.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-require 'spec_helper'
-
-describe Spree::OrdersController do
- after do
- Spree::OrdersController.clear_overrides!
- end
-
- context "extension testing" do
- context "update" do
-
- context "specify symbol for handler instead of Proc" do
- before do
- @order = create(:order)
- Spree::OrdersController.class_eval do
- respond_override({:update => {:html => {:success => :success_method}}})
-
- private
-
- def success_method
- render :text => 'success!!!'
- end
- end
- end
- describe "POST" do
- it "has value success" do
- spree_put :update, {}, {:order_id => @order.id}
- response.should be_success
- assert (response.body =~ /success!!!/)
- end
- end
- end
-
- context "render" do
- before do
- @order = create(:order)
- Spree::OrdersController.instance_eval do
- respond_override({:update => {:html => {:success => lambda { render(:text => 'success!!!') }}}})
- respond_override({:update => {:html => {:failure => lambda { render(:text => 'failure!!!') }}}})
- end
- end
- describe "POST" do
- it "has value success" do
- spree_put :update, {}, {:order_id => @order.id}
- response.should be_success
- assert (response.body =~ /success!!!/)
- end
- end
- end
-
- context "redirect" do
- before do
- @order = create(:order)
- Spree::OrdersController.instance_eval do
- respond_override({:update => {:html => {:success => lambda { redirect_to(Spree::Order.first) }}}})
- respond_override({:update => {:html => {:failure => lambda { render(:text => 'failure!!!') }}}})
- end
- end
- describe "POST" do
- it "has value success" do
- spree_put :update, {}, {:order_id => @order.id}
- response.should be_redirect
- end
- end
- end
-
- context "validation error" do
- before do
- @order = create(:order)
- Spree::Order.update_all :state => 'address'
- Spree::OrdersController.instance_eval do
- respond_override({:update => {:html => {:success => lambda { render(:text => 'success!!!') }}}})
- respond_override({:update => {:html => {:failure => lambda { render(:text => 'failure!!!') }}}})
- end
- end
- describe "POST" do
- it "has value success" do
- spree_put :update, {:order => {:email => ''}}, {:order_id => @order.id}
- response.should be_success
- assert (response.body =~ /failure!!!/)
- end
- end
- end
-
- context 'A different controllers respond_override. Regression test for #1301' do
- before do
- @order = create(:order)
- Spree::Admin::OrdersController.instance_eval do
- respond_override({:update => {:html => {:success => lambda { render(:text => 'success!!!') }}}})
- end
- end
- describe "POST" do
- it "should not effect the wrong controller" do
- Spree::Responder.should_not_receive(:call)
- spree_put :update, {}, {:order_id => @order.id}
- response.should redirect_to(spree.cart_path)
- end
- end
- end
-
- end
- end
-
-end
diff --git a/core/spec/controllers/spree/orders_controller_spec.rb b/core/spec/controllers/spree/orders_controller_spec.rb
deleted file mode 100644
index 6f3213b88ae..00000000000
--- a/core/spec/controllers/spree/orders_controller_spec.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require 'spec_helper'
-
-describe Spree::OrdersController do
- let(:user) { create(:user) }
- let(:order) { mock_model(Spree::Order, :number => "R123", :reload => nil, :save! => true, :coupon_code => nil, :user => user, :completed? => false, :currency => "USD")}
- before do
- Spree::Order.stub(:find).with(1).and_return(order)
- #ensure no respond_overrides are in effect
- if Spree::BaseController.spree_responders[:OrdersController].present?
- Spree::BaseController.spree_responders[:OrdersController].clear
- end
- end
-
- context "#populate" do
- before { Spree::Order.stub(:new).and_return(order) }
-
- it "should create a new order when none specified" do
- Spree::Order.should_receive(:new).and_return order
- spree_post :populate, {}, {}
- session[:order_id].should == order.id
- end
-
- context "with Variant" do
- before do
- @variant = mock_model(Spree::Variant)
- Spree::Variant.should_receive(:find).and_return @variant
- end
-
- it "should handle single variant/quantity pair" do
- order.should_receive(:add_variant).with(@variant, 2, order.currency)
- spree_post :populate, {:order_id => 1, :variants => {@variant.id => 2}}
- end
- it "should handle multiple variant/quantity pairs with shared quantity" do
- @variant.stub(:product_id).and_return(10)
- order.should_receive(:add_variant).with(@variant, 1, order.currency)
- spree_post :populate, {:order_id => 1, :products => {@variant.product_id => @variant.id}, :quantity => 1}
- end
- it "should handle multiple variant/quantity pairs with specific quantity" do
- @variant.stub(:product_id).and_return(10)
- order.should_receive(:add_variant).with(@variant, 3, order.currency)
- spree_post :populate, {:order_id => 1, :products => {@variant.product_id => @variant.id}, :quantity => {@variant.id.to_s => 3}}
- end
- end
- end
-
- context "#update" do
- before do
- order.stub(:update_attributes).and_return true
- order.stub(:line_items).and_return([])
- order.stub(:line_items=).with([])
- Spree::Order.stub(:find_by_id_and_currency).and_return(order)
- end
-
- it "should not result in a flash success" do
- spree_put :update, {}, {:order_id => 1}
- flash[:success].should be_nil
- end
-
- it "should render the edit view (on failure)" do
- order.stub(:update_attributes).and_return false
- order.stub(:errors).and_return({:number => "has some error"})
- spree_put :update, {}, {:order_id => 1}
- response.should render_template :edit
- end
-
- it "should redirect to cart path (on success)" do
- order.stub(:update_attributes).and_return true
- spree_put :update, {}, {:order_id => 1}
- response.should redirect_to(spree.cart_path)
- end
- end
-
- context "#empty" do
- it "should destroy line items in the current order" do
- controller.stub!(:current_order).and_return(order)
- order.should_receive(:empty!)
- spree_put :empty
- response.should redirect_to(spree.cart_path)
- end
- end
-
- #TODO - move some of the assigns tests based on session, etc. into a shared example group once new block syntax released
-end
diff --git a/core/spec/controllers/spree/orders_controller_transitions_spec.rb b/core/spec/controllers/spree/orders_controller_transitions_spec.rb
deleted file mode 100644
index 4d24b7952ed..00000000000
--- a/core/spec/controllers/spree/orders_controller_transitions_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-Spree::Order.class_eval do
- attr_accessor :did_transition
-end
-
-module Spree
- describe OrdersController do
- # Regression test for #2004
- context "with a transition callback on first state" do
- let(:order) { Spree::Order.new }
-
- before do
- controller.stub :current_order => order
-
- first_state, _ = Spree::Order.checkout_steps.first
- Spree::Order.state_machine.after_transition :to => first_state do |order|
- order.did_transition = true
- end
- end
-
- it "correctly calls the transition callback" do
- order.did_transition.should be_nil
- spree_put :update, { :checkout => "checkout" }, { :order_id => 1}
- order.did_transition.should be_true
- end
- end
- end
-end
diff --git a/core/spec/controllers/spree/products_controller_spec.rb b/core/spec/controllers/spree/products_controller_spec.rb
deleted file mode 100644
index a6c974e4d15..00000000000
--- a/core/spec/controllers/spree/products_controller_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ProductsController do
- let!(:product) { create(:product, :available_on => 1.year.from_now) }
-
- # Regression test for #1390
- it "allows admins to view non-active products" do
- controller.stub :spree_current_user => stub(:has_spree_role? => true, :last_incomplete_spree_order => nil)
- spree_get :show, :id => product.to_param
- response.status.should == 200
- end
-
- it "cannot view non-active products" do
- spree_get :show, :id => product.to_param
- response.status.should == 404
- end
-
- it "should provide the current user to the searcher class" do
- user = stub(:last_incomplete_spree_order => nil)
- controller.stub :spree_current_user => user
- Spree::Config.searcher_class.any_instance.should_receive(:current_user=).with(user)
- spree_get :index
- response.status.should == 200
- end
-
- # Regression test for #2249
- it "doesn't error when given an invalid referer" do
- controller.stub :spree_current_user => stub(:has_spree_role? => true, :last_incomplete_spree_order => nil)
- request.env['HTTP_REFERER'] = "not|a$url"
- lambda { spree_get :show, :id => product.to_param }.should_not raise_error(URI::InvalidURIError)
- end
-end
diff --git a/core/spec/controllers/spree/states_controller_spec.rb b/core/spec/controllers/spree/states_controller_spec.rb
deleted file mode 100644
index 2253be9a5d7..00000000000
--- a/core/spec/controllers/spree/states_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe Spree::StatesController do
- before(:each) do
- state = create(:state)
- end
-
- it 'should display state mapper' do
- spree_get :index, { :format => :js }
- assigns[:state_info].should_not be_empty
- end
-end
diff --git a/core/spec/controllers/spree/taxons_controller_spec.rb b/core/spec/controllers/spree/taxons_controller_spec.rb
deleted file mode 100644
index 9ca6cb7da74..00000000000
--- a/core/spec/controllers/spree/taxons_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe Spree::TaxonsController do
- it "should provide the current user to the searcher class" do
- taxon = create(:taxon, :permalink => "test")
- user = stub(:last_incomplete_spree_order => nil)
- controller.stub :spree_current_user => user
- Spree::Config.searcher_class.any_instance.should_receive(:current_user=).with(user)
- spree_get :show, :id => taxon.permalink
- response.status.should == 200
- end
-end
diff --git a/core/spec/fixtures/thinking-cat.jpg b/core/spec/fixtures/thinking-cat.jpg
new file mode 100644
index 00000000000..7e8524d367b
Binary files /dev/null and b/core/spec/fixtures/thinking-cat.jpg differ
diff --git a/core/spec/helpers/base_helper_spec.rb b/core/spec/helpers/base_helper_spec.rb
index bae571bde02..2c3dc3779b5 100644
--- a/core/spec/helpers/base_helper_spec.rb
+++ b/core/spec/helpers/base_helper_spec.rb
@@ -1,8 +1,10 @@
require 'spec_helper'
-describe Spree::BaseHelper do
+describe Spree::BaseHelper, :type => :helper do
include Spree::BaseHelper
+ let(:current_store){ create :store }
+
context "available_countries" do
let(:country) { create(:country) }
@@ -16,7 +18,7 @@
end
it "return complete list of countries" do
- available_countries.count.should == Spree::Country.count
+ expect(available_countries.count).to eq(Spree::Country.count)
end
end
@@ -29,7 +31,7 @@
end
it "return only the countries defined by the checkout zone" do
- available_countries.should == [country]
+ expect(available_countries).to eq([country])
end
end
@@ -42,23 +44,15 @@
end
it "return complete list of countries" do
- available_countries.count.should == Spree::Country.count
+ expect(available_countries.count).to eq(Spree::Country.count)
end
end
end
end
- # Regression test for #889
- context "seo_url" do
- let(:taxon) { stub(:permalink => "bam") }
- it "provides the correct URL" do
- seo_url(taxon).should == "/t/bam"
- end
- end
-
# Regression test for #1436
context "defining custom image helpers" do
- let(:product) { mock_model(Spree::Product, :images => []) }
+ let(:product) { mock_model(Spree::Product, :images => [], :variant_images => []) }
before do
Spree::Image.class_eval do
attachment_definitions[:attachment][:styles].merge!({:very_strange => '1x1'})
@@ -66,42 +60,110 @@
end
it "should not raise errors when style exists" do
- lambda { very_strange_image(product) }.should_not raise_error
+ expect { very_strange_image(product) }.not_to raise_error
end
it "should raise NoMethodError when style is not exists" do
- lambda { another_strange_image(product) }.should raise_error(NoMethodError)
+ expect { another_strange_image(product) }.to raise_error(NoMethodError)
end
end
# Regression test for #2034
context "flash_message" do
- let(:flash) { {:notice => "ok", :foo => "foo", :bar => "bar"} }
+ let(:flash) { {"notice" => "ok", "foo" => "foo", "bar" => "bar"} }
it "should output all flash content" do
flash_messages
html = Nokogiri::HTML(helper.output_buffer)
- html.css(".notice").text.should == "ok"
- html.css(".foo").text.should == "foo"
- html.css(".bar").text.should == "bar"
+ expect(html.css(".notice").text).to eq("ok")
+ expect(html.css(".foo").text).to eq("foo")
+ expect(html.css(".bar").text).to eq("bar")
end
it "should output flash content except one key" do
flash_messages(:ignore_types => :bar)
html = Nokogiri::HTML(helper.output_buffer)
- html.css(".notice").text.should == "ok"
- html.css(".foo").text.should == "foo"
- html.css(".bar").text.should be_empty
+ expect(html.css(".notice").text).to eq("ok")
+ expect(html.css(".foo").text).to eq("foo")
+ expect(html.css(".bar").text).to be_empty
end
it "should output flash content except some keys" do
flash_messages(:ignore_types => [:foo, :bar])
html = Nokogiri::HTML(helper.output_buffer)
- html.css(".notice").text.should == "ok"
- html.css(".foo").text.should be_empty
- html.css(".bar").text.should be_empty
- helper.output_buffer.should == "
")
+ end
+ end
+
+ context "link_to_tracking" do
+ it "returns tracking link if available" do
+ a = link_to_tracking_html(shipping_method: true, tracking: '123', tracking_url: 'http://g.c/?t=123').css('a')
+
+ expect(a.text).to eq '123'
+ expect(a.attr('href').value).to eq 'http://g.c/?t=123'
+ end
+
+ it "returns tracking without link if link unavailable" do
+ html = link_to_tracking_html(shipping_method: true, tracking: '123', tracking_url: nil)
+ expect(html.css('span').text).to eq '123'
+ end
+
+ it "returns nothing when no shipping method" do
+ html = link_to_tracking_html(shipping_method: nil, tracking: '123')
+ expect(html.css('span').text).to eq ''
+ end
+
+ it "returns nothing when no tracking" do
+ html = link_to_tracking_html(tracking: nil)
+ expect(html.css('span').text).to eq ''
+ end
+
+ def link_to_tracking_html(options = {})
+ node = link_to_tracking(double(:shipment, options))
+ Nokogiri::HTML(node.to_s)
+ end
+ end
+
+ # Regression test for #2396
+ context "meta_data_tags" do
+ it "truncates a product description to 160 characters" do
+ # Because the controller_name method returns "test"
+ # controller_name is used by this method to infer what it is supposed
+ # to be generating meta_data_tags for
+ text = Faker::Lorem.paragraphs(2).join(" ")
+ @test = Spree::Product.new(:description => text)
+ tags = Nokogiri::HTML.parse(meta_data_tags)
+ content = tags.css("meta[name=description]").first["content"]
+ assert content.length <= 160, "content length is not truncated to 160 characters"
+ end
+ end
+
+ # Regression test for #5384
+ context "custom image helpers conflict with inproper statements" do
+ let(:product) { mock_model(Spree::Product, :images => [], :variant_images => []) }
+ before do
+ Spree::Image.class_eval do
+ attachment_definitions[:attachment][:styles].merge!({:foobar => '1x1'})
+ end
+ end
+
+ it "should not raise errors when helper method called" do
+ expect { foobar_image(product) }.not_to raise_error
+ end
+
+ it "should raise NoMethodError when statement with name equal to style name called" do
+ expect { foobar(product) }.to raise_error(NoMethodError)
+ end
+
+ end
+
+ context "pretty_time" do
+ it "prints in a format" do
+ expect(pretty_time(DateTime.new(2012, 5, 6, 13, 33))).to eq "May 06, 2012 1:33 PM"
end
end
end
diff --git a/core/spec/helpers/navigation_helper_spec.rb b/core/spec/helpers/navigation_helper_spec.rb
deleted file mode 100644
index 37b01563353..00000000000
--- a/core/spec/helpers/navigation_helper_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# coding: UTF-8
-require 'spec_helper'
-
-module Spree
- describe Admin::NavigationHelper do
- describe "#tab" do
- context "creating an admin tab" do
- it "should capitalize the first letter of each word in the tab's label" do
- admin_tab = tab(:orders)
- admin_tab.should include("Orders")
- end
- end
-
- it "should accept options with label and capitalize each word of it" do
- admin_tab = tab(:orders, :label => "delivered orders")
- admin_tab.should include("Delivered Orders")
- end
-
- it "should capitalize words with unicode characters" do
- admin_tab = tab(:orders, :label => "přehled") # overview
- admin_tab.should include("Přehled")
- end
- end
- end
-end
diff --git a/core/spec/helpers/order_helper_spec.rb b/core/spec/helpers/order_helper_spec.rb
new file mode 100644
index 00000000000..58bbf51cc61
--- /dev/null
+++ b/core/spec/helpers/order_helper_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+module Spree
+ describe Spree::OrdersHelper, :type => :helper do
+ # Regression test for #2518 + #2323
+ it "truncates HTML correctly in product description" do
+ product = double(:description => "" + ("a" * 95) + " This content is invisible.")
+ expected = "" + ("a" * 95) + "..."
+ expect(truncated_product_description(product)).to eq(expected)
+ end
+ end
+end
diff --git a/core/spec/helpers/products_helper_spec.rb b/core/spec/helpers/products_helper_spec.rb
index 1c9ebfe9db2..0192a8493d3 100644
--- a/core/spec/helpers/products_helper_spec.rb
+++ b/core/spec/helpers/products_helper_spec.rb
@@ -3,14 +3,14 @@
require 'spec_helper'
module Spree
- describe ProductsHelper do
+ describe ProductsHelper, :type => :helper do
include ProductsHelper
let(:product) { create(:product) }
let(:currency) { 'USD' }
before do
- helper.stub(:current_currency) { currency }
+ allow(helper).to receive(:current_currency) { currency }
end
context "#variant_price_diff" do
@@ -21,27 +21,35 @@ module Spree
@variant = create(:variant, :product => product)
product.price = 15
@variant.price = 10
- product.stub(:amount_in) { product_price }
- @variant.stub(:amount_in) { variant_price }
+ allow(product).to receive(:amount_in) { product_price }
+ allow(@variant).to receive(:amount_in) { variant_price }
end
subject { helper.variant_price(@variant) }
context "when variant is same as master" do
- it { should be_nil }
+ it { is_expected.to be_nil }
+ end
+
+ context "when the master has no price" do
+ let(:product_price) { nil }
+
+ it { is_expected.to be_nil }
end
context "when currency is default" do
context "when variant is more than master" do
let(:variant_price) { 15 }
- it { should == "(Add: $5.00)" }
+ it { is_expected.to eq("(Add: $5.00)") }
+ # Regression test for #2737
+ it { is_expected.to be_html_safe }
end
context "when variant is less than master" do
let(:product_price) { 15 }
- it { should == "(Subtract: $5.00)" }
+ it { is_expected.to eq("(Subtract: $5.00)") }
end
end
@@ -53,13 +61,13 @@ module Spree
context "when variant is more than master" do
let(:variant_price) { 150 }
- it { should == "(Add: ¥50)" }
+ it { is_expected.to eq("(Add: ¥50)") }
end
context "when variant is less than master" do
let(:product_price) { 150 }
- it { should == "(Subtract: ¥50)" }
+ it { is_expected.to eq("(Subtract: ¥50)") }
end
end
end
@@ -76,8 +84,8 @@ module Spree
product.price = 10
@variant1.price = 15
@variant2.price = 20
- helper.variant_price(@variant1).should == "$15.00"
- helper.variant_price(@variant2).should == "$20.00"
+ expect(helper.variant_price(@variant1)).to eq("$15.00")
+ expect(helper.variant_price(@variant2)).to eq("$20.00")
end
end
@@ -96,7 +104,7 @@ module Spree
it "should return the variant price if the price is different than master" do
product.price = 100
@variant1.price = 150
- helper.variant_price(@variant1).should == "¥150"
+ expect(helper.variant_price(@variant1)).to eq("¥150")
end
end
@@ -104,8 +112,8 @@ module Spree
product.price = 10
@variant1.default_price.update_column(:amount, 10)
@variant2.default_price.update_column(:amount, 10)
- helper.variant_price(@variant1).should be_nil
- helper.variant_price(@variant2).should be_nil
+ expect(helper.variant_price(@variant1)).to be_nil
+ expect(helper.variant_price(@variant2)).to be_nil
end
end
@@ -121,7 +129,7 @@ module Spree
}
description = product_description(product)
- description.strip.should == product.description.strip
+ expect(description.strip).to eq(product.description.strip)
end
it "renders a product description with automatic paragraph breaks" do
@@ -131,9 +139,86 @@ module Spree
"IT CHANGED MY LIFE" - Sue, MD}
description = product_description(product)
- description.strip.should == %Q{
\nTHIS IS THE BEST PRODUCT EVER!
"IT CHANGED MY LIFE" - Sue, MD}
+ expect(description.strip).to eq(%Q{
\nTHIS IS THE BEST PRODUCT EVER!
"IT CHANGED MY LIFE" - Sue, MD})
+ end
+
+ it "renders a product description without any formatting based on configuration" do
+ initialDescription = %Q{
+
hello world
+
+
tihs is completely awesome and it works
+
+
why so many spaces in the code. and why some more formatting afterwards?
+ }
+
+ product.description = initialDescription
+
+ Spree::Config[:show_raw_product_description] = true
+ description = product_description(product)
+ expect(description).to eq(initialDescription)
+ end
+
+ end
+
+ shared_examples_for "line item descriptions" do
+ context 'variant has a blank description' do
+ let(:description) { nil }
+ it { is_expected.to eq(Spree.t(:product_has_no_description)) }
+ end
+ context 'variant has a description' do
+ let(:description) { 'test_desc' }
+ it { is_expected.to eq(description) }
+ end
+ context 'description has nonbreaking spaces' do
+ let(:description) { 'test desc' }
+ it { is_expected.to eq('test desc') }
end
+ context 'description has line endings' do
+ let(:description) { "test\n\r\ndesc" }
+ it { is_expected.to eq('test desc') }
+ end
+ end
+
+ context "#line_item_description" do
+ let(:variant) { create(:variant, :product => product, description: description) }
+ subject { line_item_description_text(variant.product.description) }
+
+ it_should_behave_like "line item descriptions"
+ end
+ context '#line_item_description_text' do
+ subject { line_item_description_text description }
+
+ it_should_behave_like "line item descriptions"
+ end
+
+ context '#cache_key_for_products' do
+ subject { helper.cache_key_for_products }
+ before(:each) do
+ @products = double('products collection')
+ allow(helper).to receive(:params) { {:page => 10} }
+ end
+
+ context 'when there is a maximum updated date' do
+ let(:updated_at) { Date.new(2011, 12, 13) }
+ before :each do
+ allow(@products).to receive(:count) { 5 }
+ allow(@products).to receive(:maximum).with(:updated_at) { updated_at }
+ end
+
+ it { is_expected.to eq('en/USD/spree/products/all-10-20111213-5') }
+ end
+
+ context 'when there is no considered maximum updated date' do
+ let(:today) { Date.new(2013, 12, 11) }
+ before :each do
+ allow(@products).to receive(:count) { 1234567 }
+ allow(@products).to receive(:maximum).with(:updated_at) { nil }
+ allow(Date).to receive(:today) { today }
+ end
+
+ it { is_expected.to eq('en/USD/spree/products/all-10-20131211-1234567') }
+ end
end
end
end
diff --git a/core/spec/helpers/taxons_helper_spec.rb b/core/spec/helpers/taxons_helper_spec.rb
new file mode 100644
index 00000000000..11a4a87ab54
--- /dev/null
+++ b/core/spec/helpers/taxons_helper_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Spree::TaxonsHelper, :type => :helper do
+ # Regression test for #4382
+ it "#taxon_preview" do
+ taxon = create(:taxon)
+ child_taxon = create(:taxon, parent: taxon)
+ product_1 = create(:product)
+ product_2 = create(:product)
+ product_3 = create(:product)
+ taxon.products << product_1
+ taxon.products << product_2
+ child_taxon.products << product_3
+
+ expect(taxon_preview(taxon.reload)).to eql([product_1, product_2, product_3])
+ end
+end
diff --git a/core/spec/lib/calculated_adjustments_spec.rb b/core/spec/lib/calculated_adjustments_spec.rb
index 9c00de09a58..d0e66a0f16f 100644
--- a/core/spec/lib/calculated_adjustments_spec.rb
+++ b/core/spec/lib/calculated_adjustments_spec.rb
@@ -1,69 +1,7 @@
require 'spec_helper'
-# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction with an actual class that
-# extends ActiveRecord::Base and has a corresponding table in the database. So we'll just test it using Order and
-# ShippingMethod instead since those classes are including the module.
-describe Spree::Core::CalculatedAdjustments do
-
- let(:calculator) { mock_model(Spree::Calculator, :compute => 10, :[]= => nil) }
-
+describe Spree::CalculatedAdjustments do
it "should add has_one :calculator relationship" do
assert Spree::ShippingMethod.reflect_on_all_associations(:has_one).map(&:name).include?(:calculator)
end
-
- let(:tax_rate) { Spree::TaxRate.new({:calculator => calculator}, :without_protection => true) }
-
- context "#create_adjustment and its resulting adjustment" do
- let(:order) { Spree::Order.create }
- let(:target) { order }
-
- it "should be associated with the target" do
- target.adjustments.should_receive(:create)
- tax_rate.create_adjustment("foo", target, order)
- end
-
- it "should have the correct originator and an amount derived from the calculator and supplied calculable" do
- adjustment = tax_rate.create_adjustment("foo", target, order)
- adjustment.should_not be_nil
- adjustment.amount.should == 10
- adjustment.source.should == order
- adjustment.originator.should == tax_rate
- end
-
- it "should be mandatory if true is supplied for that parameter" do
- adjustment = tax_rate.create_adjustment("foo", target, order, true)
- adjustment.should be_mandatory
- end
-
- context "when the calculator returns 0" do
- before { calculator.stub :compute => 0 }
-
- context "when adjustment is mandatory" do
- before { tax_rate.create_adjustment("foo", target, order, true) }
-
- it "should create an adjustment" do
- Spree::Adjustment.count.should == 1
- end
- end
-
- context "when adjustment is not mandatory" do
- before { tax_rate.create_adjustment("foo", target, order, false) }
-
- it "should not create an adjustment" do
- Spree::Adjustment.count.should == 0
- end
- end
- end
-
- end
-
- context "#update_adjustment" do
- it "should update the adjustment using its calculator (and the specified source)" do
- adjustment = mock(:adjustment).as_null_object
- calculable = mock :calculable
- adjustment.should_receive(:update_attribute_without_callbacks).with(:amount, 10)
- tax_rate.update_adjustment(adjustment, calculable)
- end
- end
-
end
diff --git a/core/spec/lib/ext_spec.rb b/core/spec/lib/ext_spec.rb
deleted file mode 100644
index 089dd14b343..00000000000
--- a/core/spec/lib/ext_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'spec_helper'
-
-describe 'Core extensions' do
-
- describe 'ActiveRecord::Base' do
-
- let(:order) { Spree::Order.create }
-
- context "update_attribute_without_callbacks" do
-
- it "sets the attribute" do
- order.update_attribute_without_callbacks 'state', 'address'
- order.state.should == 'address'
- end
-
- it "updates the attribute in the database" do
- order.update_attribute_without_callbacks 'state', 'address'
- order.reload
- order.state.should == 'address'
- end
-
- it "doesn't call valid" do
- order.should_not_receive(:valid?)
- order.update_attribute_without_callbacks 'state', 'address'
- end
-
- end
-
- context "update_attributes_without_callbacks" do
-
- it "sets the attributes" do
- order.update_attributes_without_callbacks :state => 'address', :email => 'spree@example.com'
- order.state.should == 'address'
- order.email.should == 'spree@example.com'
- end
-
- it "updates the attributes in the database" do
- order.update_attributes_without_callbacks :state => 'address', :email => 'spree@example.com'
- order.reload
- order.state.should == 'address'
- order.email.should == 'spree@example.com'
- end
-
- it "doesn't call valid" do
- order.should_not_receive(:valid?)
- order.update_attributes_without_callbacks :state => 'address', :email => 'spree@example.com'
- end
-
- end
-
- end
-
-end
diff --git a/core/spec/lib/i18n_spec.rb b/core/spec/lib/i18n_spec.rb
new file mode 100644
index 00000000000..8962749b805
--- /dev/null
+++ b/core/spec/lib/i18n_spec.rb
@@ -0,0 +1,123 @@
+require 'rspec/expectations'
+require 'spree/i18n'
+require 'spree/testing_support/i18n'
+
+describe "i18n" do
+ before do
+ I18n.backend.store_translations(:en,
+ {
+ :spree => {
+ :foo => "bar",
+ :bar => {
+ :foo => "bar within bar scope",
+ :invalid => nil,
+ :legacy_translation => "back in the day..."
+ },
+ :invalid => nil,
+ :legacy_translation => "back in the day..."
+ }
+ })
+ end
+
+ it "translates within the spree scope" do
+ expect(Spree.normal_t(:foo)).to eql("bar")
+ expect(Spree.translate(:foo)).to eql("bar")
+ end
+
+ it "translates within the spree scope using a path" do
+ allow(Spree).to receive(:virtual_path).and_return('bar')
+
+ expect(Spree.normal_t('.legacy_translation')).to eql("back in the day...")
+ expect(Spree.translate('.legacy_translation')).to eql("back in the day...")
+ end
+
+ it "raise error without any context when using a path" do
+ expect {
+ Spree.normal_t('.legacy_translation')
+ }.to raise_error
+
+ expect {
+ Spree.translate('.legacy_translation')
+ }.to raise_error
+ end
+
+ it "prepends a string scope" do
+ expect(Spree.normal_t(:foo, :scope => "bar")).to eql("bar within bar scope")
+ end
+
+ it "prepends to an array scope" do
+ expect(Spree.normal_t(:foo, :scope => ["bar"])).to eql("bar within bar scope")
+ end
+
+ it "returns two translations" do
+ expect(Spree.normal_t([:foo, 'bar.foo'])).to eql(["bar", "bar within bar scope"])
+ end
+
+ it "returns reasonable string for missing translations" do
+ expect(Spree.t(:missing_entry)).to include(" [:else, :where])
+ Spree.check_missing_translations
+ assert_missing_translation("else")
+ assert_missing_translation("else.where")
+ assert_missing_translation("else.where.missing")
+ end
+
+ it "does not log present translations" do
+ Spree.t(:foo)
+ Spree.check_missing_translations
+ expect(Spree.missing_translation_messages).to be_empty
+ end
+
+ it "does not break when asked for multiple translations" do
+ Spree.t [:foo, 'bar.foo']
+ Spree.check_missing_translations
+ expect(Spree.missing_translation_messages).to be_empty
+ end
+ end
+
+ context "unused translations" do
+ def assert_unused_translation(key)
+ key = key_with_locale(key)
+ message = Spree.unused_translation_messages.detect { |m| m == key }
+ expect(message).not_to(be_nil, "expected '#{key}' to be unused, but it was used.")
+ end
+
+ def assert_used_translation(key)
+ key = key_with_locale(key)
+ message = Spree.unused_translation_messages.detect { |m| m == key }
+ expect(message).to(be_nil, "expected '#{key}' to be used, but it wasn't.")
+ end
+
+ it "logs translations that aren't used" do
+ Spree.check_unused_translations
+ assert_unused_translation("bar.legacy_translation")
+ assert_unused_translation("legacy_translation")
+ end
+
+ it "does not log used translations" do
+ Spree.t(:foo)
+ Spree.check_unused_translations
+ assert_used_translation("foo")
+ end
+ end
+ end
+end
diff --git a/core/spec/lib/mail_interceptor_spec.rb b/core/spec/lib/mail_interceptor_spec.rb
deleted file mode 100644
index 0f6cda641e6..00000000000
--- a/core/spec/lib/mail_interceptor_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-# We'll use the OrderMailer as a quick and easy way to test. IF it works here - it works for all email (in theory.)
-describe Spree::OrderMailer do
- let(:mail_method) { mock("mail_method", :preferred_mails_from => nil, :preferred_intercept_email => nil, :preferred_mail_bcc => nil) }
- let(:order) { Spree::Order.new(:email => "customer@example.com") }
- let(:message) { Spree::OrderMailer.confirm_email(order) }
- #let(:email) { mock "email" }
-
- before(:all) do
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries.clear
- end
-
- context "#deliver" do
- before do
- ActionMailer::Base.delivery_method = :test
- Spree::MailMethod.stub :current => mail_method
- end
- after { ActionMailer::Base.deliveries.clear }
-
- it "should use the from address specified in the preference" do
- mail_method.stub :preferred_mails_from => "no-reply@foobar.com"
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.from.should == ["no-reply@foobar.com"]
- end
-
- it "should use the provided from address" do
- mail_method.stub :preferred_mails_from => "preference@foobar.com"
- message = ActionMailer::Base.mail(:from => "override@foobar.com", :to => "test@test.com")
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.from.should == ["override@foobar.com"]
- end
-
- it "should add the bcc email when provided" do
- mail_method.stub :preferred_mail_bcc => "bcc-foo@foobar.com"
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.bcc.should == ["bcc-foo@foobar.com"]
- end
-
- context "when intercept_email is provided" do
- it "should strip the bcc recipients" do
- message.bcc.should be_blank
- end
-
- it "should strip the cc recipients" do
- message.cc.should be_blank
- end
-
- it "should replace the receipient with the specified address" do
- mail_method.stub :preferred_intercept_email => "intercept@foobar.com"
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.to.should == ["intercept@foobar.com"]
- end
- it "should modify the subject to include the original email" do
- mail_method.stub :preferred_intercept_email => "intercept@foobar.com"
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.subject.match(/customer@example\.com/).should be_true
- end
- end
-
- context "when intercept_mode is not provided" do
- before { mail_method.stub :preferred_intercept_email => "" }
-
- it "should not modify the recipient" do
- message.deliver
- @email = ActionMailer::Base.deliveries.first
- @email.to.should == ["customer@example.com"]
- end
- end
- end
-end
diff --git a/core/spec/lib/mail_settings_spec.rb b/core/spec/lib/mail_settings_spec.rb
deleted file mode 100644
index b3a4de498e1..00000000000
--- a/core/spec/lib/mail_settings_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Core::MailSettings do
- let(:mail_method) { Spree::MailMethod.new(:environment => "test") }
-
- context "init" do
- before { Spree::MailMethod.stub :current => mail_method }
-
- context "perform_delivery preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:enable_mail_delivery, false)
- Spree::Core::MailSettings.init
- ActionMailer::Base.perform_deliveries.should be_false
- mail_method.set_preference(:enable_mail_delivery, true)
- end
- end
-
- context "when delivery is true" do
- before { mail_method.set_preference(:enable_mail_delivery, true) }
-
- context "when mail_auth_type is other than none" do
- before { mail_method.set_preference(:mail_auth_type, "login") }
-
- context "mail_auth_type preference" do
- it "should override the application defaults" do
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:authentication].should == "login"
- end
- end
-
- context "mail_host preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:mail_host, "smtp.example.com")
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:address].should == "smtp.example.com"
- end
- end
-
- context "mail_domain preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:mail_domain, "example.com")
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:domain].should == "example.com"
- end
- end
-
- context "mail_port preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:mail_port, 123)
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:port].should == 123
- end
- end
-
- context "smtp_username preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:smtp_username, "schof")
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:user_name].should == "schof"
- end
- end
-
- context "smtp_password preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:smtp_password, "hellospree!")
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:password].should == "hellospree!"
- end
- end
-
- context "secure_connection_type preference" do
- it "should override the application defaults" do
- mail_method.set_preference(:secure_connection_type, "TLS")
- Spree::Core::MailSettings.init
- ActionMailer::Base.smtp_settings[:enable_starttls_auto].should be_true
- end
- end
- end
- end
-
- end
-end
diff --git a/core/spec/lib/money_spec.rb b/core/spec/lib/money_spec.rb
deleted file mode 100644
index c7378ab8235..00000000000
--- a/core/spec/lib/money_spec.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-#encoding: UTF-8
-require 'spec_helper'
-
-module Spree
- describe Money do
- before do
- reset_spree_preferences do |config|
- config.currency = "USD"
- config.currency_symbol_position = :before
- config.display_currency = false
- end
- end
-
- it "formats correctly" do
- money = Spree::Money.new(10)
- money.to_s.should == "$10.00"
- end
-
- context "with currency" do
- it "passed in option" do
- money = Spree::Money.new(10, :with_currency => true)
- money.to_s.should == "$10.00 USD"
- end
-
- it "config option" do
- Spree::Config[:display_currency] = true
- money = Spree::Money.new(10)
- money.to_s.should == "$10.00 USD"
- end
- end
-
- context "hide cents" do
- it "hides cents suffix" do
- Spree::Config[:hide_cents] = true
- money = Spree::Money.new(10)
- money.to_s.should == "$10"
- end
-
- it "shows cents suffix" do
- Spree::Config[:hide_cents] = false
- money = Spree::Money.new(10)
- money.to_s.should == "$10.00"
- end
- end
-
- context "currency parameter" do
- context "when currency is specified in Canadian Dollars" do
- it "uses the currency param over the global configuration" do
- money = Spree::Money.new(10, :currency => 'CAD', :with_currency => true)
- money.to_s.should == "$10.00 CAD"
- end
- end
-
- context "when currency is specified in Japanese Yen" do
- it "uses the currency param over the global configuration" do
- money = Spree::Money.new(100, :currency => 'JPY')
- money.to_s.should == "¥100"
- end
- end
- end
-
- context "symbol positioning" do
- it "passed in option" do
- money = Spree::Money.new(10, :symbol_position => :after)
- money.to_s.should == "10.00 $"
- end
-
- it "passed in option string" do
- money = Spree::Money.new(10, :symbol_position => "after")
- money.to_s.should == "10.00 $"
- end
-
- it "config option" do
- Spree::Config[:currency_symbol_position] = :after
- money = Spree::Money.new(10)
- money.to_s.should == "10.00 $"
- end
- end
-
- context "JPY" do
- before do
- reset_spree_preferences do |config|
- config.currency = "JPY"
- config.currency_symbol_position = :before
- config.display_currency = false
- end
- end
-
- it "formats correctly" do
- money = Spree::Money.new(1000)
- money.to_s.should == "¥1,000"
- end
- end
- end
-end
diff --git a/core/spec/lib/search/base_spec.rb b/core/spec/lib/search/base_spec.rb
index cae3b2e78b3..c1fa3cc19b2 100644
--- a/core/spec/lib/search/base_spec.rb
+++ b/core/spec/lib/search/base_spec.rb
@@ -3,65 +3,84 @@
describe Spree::Core::Search::Base do
before do
- include ::Spree::ProductFilters
- @product1 = create(:product, :name => "RoR Mug", :price => 9.00, :on_hand => 1)
- @product2 = create(:product, :name => "RoR Shirt", :price => 11.00, :on_hand => 1)
+ include Spree::Core::ProductFilters
+ @taxon = create(:taxon, name: "Ruby on Rails")
+
+ @product1 = create(:product, name: "RoR Mug", price: 9.00)
+ @product1.taxons << @taxon
+ @product2 = create(:product, name: "RoR Shirt", price: 11.00)
end
it "returns all products by default" do
params = { :per_page => "" }
searcher = Spree::Core::Search::Base.new(params)
- searcher.retrieve_products.count.should == 2
+ expect(searcher.retrieve_products.count).to eq(2)
+ end
+
+ context "when include_images is included in the initalization params" do
+ let(:params) { { include_images: true, keyword: @product1.name, taxon: @taxon.id } }
+ subject { described_class.new(params).retrieve_products }
+
+ before do
+ @product1.master.images.create(attachment_file_name: "Test", position: 2)
+ @product1.master.images.create(attachment_file_name: "Test1", position: 1)
+ @product1.reload
+ end
+
+ it "returns images in correct order" do
+ expect(subject.first).to eq @product1
+ expect(subject.first.images).to eq @product1.master.images
+ end
end
it "switches to next page according to the page parameter" do
- @product3 = create(:product, :name => "RoR Pants", :price => 14.00, :on_hand => 1)
+ @product3 = create(:product, :name => "RoR Pants", :price => 14.00)
params = { :per_page => "2" }
searcher = Spree::Core::Search::Base.new(params)
- searcher.retrieve_products.count.should == 2
+ expect(searcher.retrieve_products.count).to eq(2)
params.merge! :page => "2"
searcher = Spree::Core::Search::Base.new(params)
- searcher.retrieve_products.count.should == 1
+ expect(searcher.retrieve_products.count).to eq(1)
end
it "maps search params to named scopes" do
params = { :per_page => "",
:search => { "price_range_any" => ["Under $10.00"] }}
searcher = Spree::Core::Search::Base.new(params)
- searcher.send(:get_base_scope).to_sql.should match /<= 10/
- searcher.retrieve_products.count.should == 1
+ expect(searcher.send(:get_base_scope).to_sql).to match /<= 10/
+ expect(searcher.retrieve_products.count).to eq(1)
end
it "maps multiple price_range_any filters" do
params = { :per_page => "",
:search => { "price_range_any" => ["Under $10.00", "$10.00 - $15.00"] }}
searcher = Spree::Core::Search::Base.new(params)
- searcher.send(:get_base_scope).to_sql.should match /<= 10/
- searcher.send(:get_base_scope).to_sql.should match /between 10 and 15/i
- searcher.retrieve_products.count.should == 2
+ expect(searcher.send(:get_base_scope).to_sql).to match /<= 10/
+ expect(searcher.send(:get_base_scope).to_sql).to match /between 10 and 15/i
+ expect(searcher.retrieve_products.count).to eq(2)
end
it "uses ransack if scope not found" do
params = { :per_page => "",
:search => { "name_not_cont" => "Shirt" }}
searcher = Spree::Core::Search::Base.new(params)
- searcher.retrieve_products.count.should == 1
+ expect(searcher.retrieve_products.count).to eq(1)
end
it "accepts a current user" do
- user = stub
+ user = double
searcher = Spree::Core::Search::Base.new({})
searcher.current_user = user
- searcher.current_user.should eql(user)
+ expect(searcher.current_user).to eql(user)
end
it "finds products in alternate currencies" do
price = create(:price, :currency => 'EUR', :variant => @product1.master)
searcher = Spree::Core::Search::Base.new({})
searcher.current_currency = 'EUR'
- searcher.retrieve_products.should == [@product1]
+ expect(searcher.retrieve_products).to eq([@product1])
end
end
diff --git a/core/spec/lib/spree/core/controller_helpers/auth_spec.rb b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb
new file mode 100644
index 00000000000..4d2d94483e7
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/auth_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::Auth
+ def index; render text: 'index'; end
+end
+
+describe Spree::Core::ControllerHelpers::Auth, type: :controller do
+ controller(FakesController) {}
+
+ describe '#current_ability' do
+ it 'returns Spree::Ability instance' do
+ expect(controller.current_ability.class).to eq Spree::Ability
+ end
+ end
+
+ describe '#redirect_back_or_default' do
+ controller(FakesController) do
+ def index; redirect_back_or_default('/'); end
+ end
+ it 'redirects to session url' do
+ session[:spree_user_return_to] = '/redirect'
+ get :index
+ expect(response).to redirect_to('/redirect')
+ end
+ it 'redirects to default page' do
+ get :index
+ expect(response).to redirect_to('/')
+ end
+ end
+
+ describe '#set_guest_token' do
+ controller(FakesController) do
+ def index
+ set_guest_token
+ render text: 'index'
+ end
+ end
+ it 'sends cookie header' do
+ get :index
+ expect(response.cookies['guest_token']).not_to be_nil
+ end
+ end
+
+ describe '#store_location' do
+ it 'sets session return url' do
+ allow(controller).to receive_messages(request: double(fullpath: '/redirect'))
+ controller.store_location
+ expect(session[:spree_user_return_to]).to eq '/redirect'
+ end
+ end
+
+ describe '#try_spree_current_user' do
+ it 'calls spree_current_user when define spree_current_user method' do
+ expect(controller).to receive(:spree_current_user)
+ controller.try_spree_current_user
+ end
+ it 'calls current_spree_user when define current_spree_user method' do
+ expect(controller).to receive(:current_spree_user)
+ controller.try_spree_current_user
+ end
+ it 'returns nil' do
+ expect(controller.try_spree_current_user).to eq nil
+ end
+ end
+
+ describe '#redirect_unauthorized_access' do
+ controller(FakesController) do
+ def index; redirect_unauthorized_access; end
+ end
+ context 'when logged in' do
+ before do
+ allow(controller).to receive_messages(try_spree_current_user: double('User', id: 1, last_incomplete_spree_order: nil))
+ end
+ it 'redirects unauthorized path' do
+ get :index
+ expect(response).to redirect_to('/unauthorized')
+ end
+ end
+ context 'when guest user' do
+ before do
+ allow(controller).to receive_messages(try_spree_current_user: nil)
+ end
+ it 'redirects login path' do
+ allow(controller).to receive_messages(spree_login_path: '/login')
+ get :index
+ expect(response).to redirect_to('/login')
+ end
+ it 'redirects root path' do
+ allow(controller).to receive_message_chain(:spree, :root_path).and_return('/root_path')
+ get :index
+ expect(response).to redirect_to('/root_path')
+ end
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/controller_helpers/order_spec.rb b/core/spec/lib/spree/core/controller_helpers/order_spec.rb
new file mode 100644
index 00000000000..7254bcdd424
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/order_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::Order
+end
+
+describe Spree::Core::ControllerHelpers::Order, type: :controller do
+ controller(FakesController) {}
+
+ let(:user) { create(:user) }
+ let(:order) { create(:order, user: user) }
+ let(:store) { create(:store) }
+
+ describe '#simple_current_order' do
+ before { allow(controller).to receive_messages(try_spree_current_user: user) }
+ it "returns an empty order" do
+ expect(controller.simple_current_order.item_count).to eq 0
+ end
+ it 'returns Spree::Order instance' do
+ allow(controller).to receive_messages(cookies: double(signed: { guest_token: order.guest_token }))
+ expect(controller.simple_current_order).to eq order
+ end
+ end
+
+ describe '#current_order' do
+ before {
+ allow(controller).to receive_messages(current_store: store)
+ allow(controller).to receive_messages(try_spree_current_user: user)
+ }
+ context 'create_order_if_necessary option is false' do
+ let!(:order) { create :order, user: user }
+ it 'returns current order' do
+ expect(controller.current_order).to eq order
+ end
+ end
+ context 'create_order_if_necessary option is true' do
+ it 'creates new order' do
+ expect {
+ controller.current_order(create_order_if_necessary: true)
+ }.to change(Spree::Order, :count).to(1)
+ end
+
+ it 'assigns the current_store id' do
+ controller.current_order(create_order_if_necessary: true)
+ expect(Spree::Order.last.store_id).to eq store.id
+ end
+ end
+ end
+
+ describe '#associate_user' do
+ before do
+ allow(controller).to receive_messages(current_order: order, try_spree_current_user: user)
+ end
+ context "user's email is blank" do
+ let(:user) { create(:user, email: '') }
+ it 'calls Spree::Order#associate_user! method' do
+ expect_any_instance_of(Spree::Order).to receive(:associate_user!)
+ controller.associate_user
+ end
+ end
+ context "user isn't blank" do
+ it 'does not calls Spree::Order#associate_user! method' do
+ expect_any_instance_of(Spree::Order).not_to receive(:associate_user!)
+ controller.associate_user
+ end
+ end
+ end
+
+ describe '#set_current_order' do
+ let(:incomplete_order) { create(:order, user: user) }
+ before { allow(controller).to receive_messages(try_spree_current_user: user) }
+
+ context 'when current order not equal to users incomplete orders' do
+ before { allow(controller).to receive_messages(current_order: order, last_incomplete_order: incomplete_order, cookies: double(signed: { guest_token: 'guest_token' })) }
+
+ it 'calls Spree::Order#merge! method' do
+ expect(order).to receive(:merge!).with(incomplete_order, user)
+ controller.set_current_order
+ end
+ end
+ end
+
+ describe '#current_currency' do
+ it 'returns current currency' do
+ Spree::Config[:currency] = 'USD'
+ expect(controller.current_currency).to eq 'USD'
+ end
+ end
+
+ describe '#ip_address' do
+ it 'returns remote ip' do
+ expect(controller.ip_address).to eq request.remote_ip
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/controller_helpers/search_spec.rb b/core/spec/lib/spree/core/controller_helpers/search_spec.rb
new file mode 100644
index 00000000000..84b7fadda5a
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/search_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::Search
+end
+
+describe Spree::Core::ControllerHelpers::Search, type: :controller do
+ controller(FakesController) {}
+
+ describe '#build_searcher' do
+ it 'returns Spree::Core::Search::Base instance' do
+ allow(controller).to receive_messages(try_spree_current_user: create(:user),
+ current_currency: 'USD')
+ expect(controller.build_searcher({}).class).to eq Spree::Core::Search::Base
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/controller_helpers/ssl_spec.rb b/core/spec/lib/spree/core/controller_helpers/ssl_spec.rb
new file mode 100644
index 00000000000..3ef3cb3ec62
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/ssl_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::SSL
+ def index; render text: 'index'; end
+ def create; end
+ def ssl_supported?; true; end
+end
+
+describe Spree::Core::ControllerHelpers::SSL, type: :controller do
+
+ describe 'redirect to http' do
+ before { Spree::Config[:redirect_https_to_http] = true }
+ after { Spree::Config[:redirect_https_to_http] = false }
+ before { request.env['HTTPS'] = 'on' }
+
+ describe 'allowed two actions' do
+ controller(FakesController) do
+ ssl_allowed :index
+ ssl_allowed :foobar
+ end
+
+ it '#ssl_allowed_actions returns both' do
+ expect(controller.ssl_allowed_actions).to eq [:index, :foobar]
+ end
+
+ it 'should allow https access' do
+ expect(get(:index)).to be_success
+ end
+ end
+
+ context 'allowed a single action' do
+ controller(FakesController) do
+ ssl_allowed :index
+ end
+ specify{ expect(controller.ssl_allowed_actions).to eq([:index]) }
+ specify{ expect(get(:index)).to be_success }
+ end
+
+ context 'allowed all actions' do
+ controller(FakesController) do
+ ssl_allowed
+ end
+ specify{ expect(controller.ssl_allowed_actions).to eq([]) }
+ specify{ expect(get(:index)).to be_success }
+ end
+
+ context 'ssl not allowed' do
+ controller(FakesController) { }
+ specify{ expect(get(:index)).to be_redirect }
+ end
+
+ context 'using a post returns a HTTP status 426' do
+ controller(FakesController) { }
+ specify do
+ post(:create)
+ expect(response.body).to eq("Please switch to using HTTP (rather than HTTPS) and retry this request.")
+ expect(response.status).to eq(426)
+ end
+ end
+ end
+
+ describe 'redirect to https' do
+ context 'required a single action' do
+ controller(FakesController) do
+ ssl_required :index
+ end
+ specify{ expect(controller.ssl_allowed_actions).to eq([:index]) }
+ specify{ expect(get(:index)).to be_redirect }
+ end
+
+ context 'required all actions' do
+ controller(FakesController) do
+ ssl_required
+ end
+ specify{ expect(controller.ssl_allowed_actions).to eq([]) }
+ specify{ expect(get(:index)).to be_redirect }
+ end
+
+ context 'not required' do
+ controller(FakesController) { }
+ specify{ expect(get(:index)).to be_success }
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/controller_helpers/store_spec.rb b/core/spec/lib/spree/core/controller_helpers/store_spec.rb
new file mode 100644
index 00000000000..03c47a7b7f1
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/store_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::Store
+end
+
+describe Spree::Core::ControllerHelpers::Store, type: :controller do
+ controller(FakesController) {}
+
+ describe '#current_store' do
+ let!(:store) { create :store, default: true }
+ it 'returns current store' do
+ expect(controller.current_store).to eq store
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb b/core/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb
new file mode 100644
index 00000000000..f72f5fe733f
--- /dev/null
+++ b/core/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+class FakesController < ApplicationController
+ include Spree::Core::ControllerHelpers::StrongParameters
+end
+
+describe Spree::Core::ControllerHelpers::StrongParameters, type: :controller do
+ controller(FakesController) {}
+
+ describe '#permitted_attributes' do
+ it 'returns Spree::PermittedAttributes module' do
+ expect(controller.permitted_attributes).to eq Spree::PermittedAttributes
+ end
+ end
+
+ describe '#permitted_payment_attributes' do
+ it 'returns Array class' do
+ expect(controller.permitted_payment_attributes.class).to eq Array
+ end
+ end
+
+ describe '#permitted_checkout_attributes' do
+ it 'returns Array class' do
+ expect(controller.permitted_checkout_attributes.class).to eq Array
+ end
+ end
+
+ describe '#permitted_order_attributes' do
+ it 'returns Array class' do
+ expect(controller.permitted_order_attributes.class).to eq Array
+ end
+ end
+
+ describe '#permitted_product_attributes' do
+ it 'returns Array class' do
+ expect(controller.permitted_product_attributes.class).to eq Array
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/delegate_belongs_to_spec.rb b/core/spec/lib/spree/core/delegate_belongs_to_spec.rb
new file mode 100644
index 00000000000..0d053f6bfd6
--- /dev/null
+++ b/core/spec/lib/spree/core/delegate_belongs_to_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+# This is a bit of a insane spec I have to admit
+# Chosed the spree_payment_methods table because it has a `name` column
+# already. Stubs wouldn't work here (the delegation runs before this spec is
+# loaded) and adding a column here might make the test even crazy so here we go
+module Spree
+ class DelegateBelongsToStubModel < Spree::Base
+ self.table_name = "spree_payment_methods"
+ belongs_to :product
+ delegate_belongs_to :product, :name
+ end
+
+ describe DelegateBelongsToStubModel do
+ context "model has column attr delegated to associated object" do
+ it "doesnt touch the associated object" do
+ expect(subject).not_to receive(:product)
+ subject.name
+ end
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/importer/order_spec.rb b/core/spec/lib/spree/core/importer/order_spec.rb
new file mode 100644
index 00000000000..a1dc9d3ca36
--- /dev/null
+++ b/core/spec/lib/spree/core/importer/order_spec.rb
@@ -0,0 +1,472 @@
+require 'spec_helper'
+
+module Spree
+ module Core
+ describe Importer::Order do
+
+ let!(:country) { create(:country) }
+ let!(:state) { country.states.first || create(:state, :country => country) }
+ let!(:stock_location) { create(:stock_location, admin_name: 'Admin Name') }
+
+ let(:user) { stub_model(LegacyUser, :email => 'fox@mudler.com') }
+ let(:shipping_method) { create(:shipping_method) }
+ let(:payment_method) { create(:check_payment_method) }
+
+ let(:product) { product = Spree::Product.create(:name => 'Test',
+ :sku => 'TEST-1',
+ :price => 33.22)
+ product.shipping_category = create(:shipping_category)
+ product.save
+ product }
+
+ let(:variant) { variant = product.master
+ variant.stock_items.each { |si| si.update_attribute(:count_on_hand, 10) }
+ variant }
+
+ let(:sku) { variant.sku }
+ let(:variant_id) { variant.id }
+
+ let(:line_items) {{ "0" => { :variant_id => variant.id, :quantity => 5 }}}
+ let(:ship_address) {{
+ :address1 => '123 Testable Way',
+ :firstname => 'Fox',
+ :lastname => 'Mulder',
+ :city => 'Washington',
+ :country_id => country.id,
+ :state_id => state.id,
+ :zipcode => '66666',
+ :phone => '666-666-6666'
+ }}
+
+ it 'can import an order number' do
+ params = { number: '123-456-789' }
+ order = Importer::Order.import(user, params)
+ expect(order.number).to eq '123-456-789'
+ end
+
+ it 'optionally add completed at' do
+ params = { email: 'test@test.com',
+ completed_at: Time.now,
+ line_items_attributes: line_items }
+
+ order = Importer::Order.import(user,params)
+ expect(order).to be_completed
+ expect(order.state).to eq 'complete'
+ end
+
+ it "assigns order[email] over user email to order" do
+ params = { email: 'wooowww@test.com' }
+ order = Importer::Order.import(user,params)
+ expect(order.email).to eq params[:email]
+ end
+
+ context "assigning a user to an order" do
+ let(:other_user) { stub_model(LegacyUser, :email => 'dana@scully.com') }
+
+ context "as an admin" do
+ before { allow(user).to receive_messages :has_spree_role? => true }
+
+ context "a user's id is not provided" do
+ # this is a regression spec for an issue we ran into at Bonobos
+ it "doesn't unassociate the admin from the order" do
+ params = { }
+ order = Importer::Order.import(user, params)
+ expect(order.user_id).to eq(user.id)
+ end
+ end
+ end
+
+ context "as a user" do
+ before { allow(user).to receive_messages :has_spree_role? => false }
+ it "does not assign the order to the other user" do
+ params = { user_id: other_user.id }
+ order = Importer::Order.import(user, params)
+ expect(order.user_id).to eq(user.id)
+ end
+ end
+ end
+
+ it 'can build an order from API with just line items' do
+ params = { :line_items_attributes => line_items }
+
+ expect(Importer::Order).to receive(:ensure_variant_id_from_params).and_return({variant_id: variant.id, quantity: 5})
+ order = Importer::Order.import(user,params)
+ expect(order.user).to eq(nil)
+ line_item = order.line_items.first
+ expect(line_item.quantity).to eq(5)
+ expect(line_item.variant_id).to eq(variant_id)
+ end
+
+ it 'handles line_item building exceptions' do
+ line_items['0'][:variant_id] = 'XXX'
+ params = { :line_items_attributes => line_items }
+
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error /XXX/
+ end
+
+ it 'handles line_item updating exceptions' do
+ line_items['0'][:currency] = 'GBP'
+ params = { :line_items_attributes => line_items }
+
+ expect {
+ order = Importer::Order.import(user, params)
+ }.to raise_error /Validation failed/
+ end
+
+ it 'can build an order from API with variant sku' do
+ params = { :line_items_attributes => {
+ "0" => { :sku => sku, :quantity => 5 } }}
+
+ order = Importer::Order.import(user,params)
+
+ line_item = order.line_items.first
+ expect(line_item.variant_id).to eq(variant_id)
+ expect(line_item.quantity).to eq(5)
+ end
+
+ it 'handles exceptions when sku is not found' do
+ params = { :line_items_attributes => {
+ "0" => { :sku => 'XXX', :quantity => 5 } }}
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error /XXX/
+ end
+
+ it 'can build an order from API shipping address' do
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+
+ order = Importer::Order.import(user,params)
+ expect(order.ship_address.address1).to eq '123 Testable Way'
+ end
+
+ it 'can build an order from API with country attributes' do
+ ship_address.delete(:country_id)
+ ship_address[:country] = { 'iso' => 'US' }
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+
+ order = Importer::Order.import(user,params)
+ expect(order.ship_address.country.iso).to eq 'US'
+ end
+
+ it 'handles country lookup exceptions' do
+ ship_address.delete(:country_id)
+ ship_address[:country] = { 'iso' => 'XXX' }
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error /XXX/
+ end
+
+ it 'can build an order from API with state attributes' do
+ ship_address.delete(:state_id)
+ ship_address[:state] = { 'name' => state.name }
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+
+ order = Importer::Order.import(user,params)
+ expect(order.ship_address.state.name).to eq 'Alabama'
+ end
+
+ context "with a different currency" do
+ before { variant.price_in("GBP").update_attribute(:price, 18.99) }
+
+ it "sets the order currency" do
+ params = {
+ currency: "GBP"
+ }
+ order = Importer::Order.import(user,params)
+ expect(order.currency).to eq "GBP"
+ end
+
+ it "can handle it when a line order price is specified" do
+ params = {
+ currency: "GBP",
+ line_items_attributes: line_items
+ }
+ line_items["0"].merge! currency: "GBP", price: 1.99
+ order = Importer::Order.import(user, params)
+ expect(order.currency).to eq "GBP"
+ expect(order.line_items.first.price).to eq 1.99
+ expect(order.line_items.first.currency).to eq "GBP"
+ end
+ end
+
+ context "state passed is not associated with country" do
+ let(:params) do
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+ end
+
+ let(:other_state) { create(:state, name: "Uhuhuh", country: create(:country)) }
+
+ before do
+ ship_address.delete(:state_id)
+ ship_address[:state] = { 'name' => other_state.name }
+ end
+
+ it 'sets states name instead of state id' do
+ order = Importer::Order.import(user,params)
+ expect(order.ship_address.state_name).to eq other_state.name
+ end
+ end
+
+ it 'sets state name if state record not found' do
+ ship_address.delete(:state_id)
+ ship_address[:state] = { 'name' => 'XXX' }
+ params = { :ship_address_attributes => ship_address,
+ :line_items_attributes => line_items }
+
+ order = Importer::Order.import(user,params)
+ expect(order.ship_address.state_name).to eq 'XXX'
+ end
+
+ context 'variant not deleted' do
+ it 'ensures variant id from api' do
+ hash = { sku: variant.sku }
+ Importer::Order.ensure_variant_id_from_params(hash)
+ expect(hash[:variant_id]).to eq variant.id
+ end
+ end
+
+ context 'variant was deleted' do
+ it 'raise error as variant shouldnt be found' do
+ variant.product.destroy
+ hash = { sku: variant.sku }
+ expect {
+ Importer::Order.ensure_variant_id_from_params(hash)
+ }.to raise_error
+ end
+ end
+
+ it 'ensures_country_id for country fields' do
+ [:name, :iso, :iso_name, :iso3].each do |field|
+ address = { :country => { field => country.send(field) }}
+ Importer::Order.ensure_country_id_from_params(address)
+ expect(address[:country_id]).to eq country.id
+ end
+ end
+
+ it "raises with proper message when cant find country" do
+ address = { :country => { "name" => "NoNoCountry" } }
+ expect {
+ Importer::Order.ensure_country_id_from_params(address)
+ }.to raise_error /NoNoCountry/
+ end
+
+ it 'ensures_state_id for state fields' do
+ [:name, :abbr].each do |field|
+ address = { country_id: country.id, :state => { field => state.send(field) }}
+ Importer::Order.ensure_state_id_from_params(address)
+ expect(address[:state_id]).to eq state.id
+ end
+ end
+
+ context "shipments" do
+ let(:params) do
+ { :shipments_attributes => [
+ { :tracking => '123456789',
+ :cost => '14.99',
+ :shipping_method => shipping_method.name,
+ :stock_location => stock_location.name,
+ :inventory_units => [{ :sku => sku }]
+ }
+ ] }
+ end
+
+ it 'ensures variant exists and is not deleted' do
+ expect(Importer::Order).to receive(:ensure_variant_id_from_params)
+ order = Importer::Order.import(user,params)
+ end
+
+ it 'builds them properly' do
+ order = Importer::Order.import(user, params)
+ shipment = order.shipments.first
+
+ expect(shipment.cost.to_f).to eq 14.99
+ expect(shipment.inventory_units.first.variant_id).to eq product.master.id
+ expect(shipment.tracking).to eq '123456789'
+ expect(shipment.shipping_rates.first.cost).to eq 14.99
+ expect(shipment.selected_shipping_rate).to eq(shipment.shipping_rates.first)
+ expect(shipment.stock_location).to eq stock_location
+ expect(order.shipment_total.to_f).to eq 14.99
+ end
+
+ it "accepts admin name for stock location" do
+ params[:shipments_attributes][0][:stock_location] = stock_location.admin_name
+ order = Importer::Order.import(user, params)
+ shipment = order.shipments.first
+
+ expect(shipment.stock_location).to eq stock_location
+ end
+
+ it "raises if cant find stock location" do
+ params[:shipments_attributes][0][:stock_location] = "doesnt exist"
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error
+ end
+
+ context 'when completed_at and shipped_at present' do
+ let(:params) do
+ {
+ :completed_at => 2.days.ago,
+ :shipments_attributes => [
+ { :tracking => '123456789',
+ :cost => '4.99',
+ :shipped_at => 1.day.ago,
+ :shipping_method => shipping_method.name,
+ :stock_location => stock_location.name,
+ :inventory_units => [{ :sku => sku }]
+ }
+ ]
+ }
+ end
+
+ it 'builds them properly' do
+ order = Importer::Order.import(user, params)
+ shipment = order.shipments.first
+
+ expect(shipment.cost.to_f).to eq 4.99
+ expect(shipment.inventory_units.first.variant_id).to eq product.master.id
+ expect(shipment.tracking).to eq '123456789'
+ expect(shipment.shipped_at).to be_present
+ expect(shipment.shipping_rates.first.cost).to eq 4.99
+ expect(shipment.selected_shipping_rate).to eq(shipment.shipping_rates.first)
+ expect(shipment.stock_location).to eq stock_location
+ expect(shipment.state).to eq('shipped')
+ expect(shipment.inventory_units.all?(&:shipped?)).to be true
+ expect(order.shipment_state).to eq('shipped')
+ expect(order.shipment_total.to_f).to eq 4.99
+ end
+ end
+ end
+
+ it 'handles shipment building exceptions' do
+ params = { :shipments_attributes => [{ tracking: '123456789',
+ cost: '4.99',
+ shipping_method: 'XXX',
+ inventory_units: [{ sku: sku }]
+ }] }
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error /XXX/
+ end
+
+ it 'adds adjustments' do
+ params = { :adjustments_attributes => [
+ { label: 'Shipping Discount', amount: -4.99 },
+ { label: 'Promotion Discount', amount: -3.00 }] }
+
+ order = Importer::Order.import(user,params)
+ expect(order.adjustments.all?(&:closed?)).to be true
+ expect(order.adjustments.first.label).to eq 'Shipping Discount'
+ expect(order.adjustments.first.amount).to eq -4.99
+ end
+
+ it "calculates final order total correctly" do
+ params = {
+ adjustments_attributes: [
+ { label: 'Promotion Discount', amount: -3.00 }
+ ],
+ line_items_attributes: {
+ "0" => {
+ variant_id: variant.id,
+ quantity: 5
+ }
+ }
+ }
+
+ order = Importer::Order.import(user,params)
+ expect(order.item_total).to eq(166.1)
+ expect(order.total).to eq(163.1) # = item_total (166.1) - adjustment_total (3.00)
+
+ end
+
+ it 'handles adjustment building exceptions' do
+ params = { :adjustments_attributes => [
+ { amount: 'XXX' },
+ { label: 'Promotion Discount', amount: '-3.00' }] }
+
+ expect {
+ order = Importer::Order.import(user,params)
+ }.to raise_error /XXX/
+ end
+
+ it 'builds a payment using state' do
+ params = { :payments_attributes => [{ amount: '4.99',
+ payment_method: payment_method.name,
+ state: 'completed' }] }
+ order = Importer::Order.import(user,params)
+ expect(order.payments.first.amount).to eq 4.99
+ end
+
+ it 'builds a payment using status as fallback' do
+ params = { :payments_attributes => [{ amount: '4.99',
+ payment_method: payment_method.name,
+ status: 'completed' }] }
+ order = Importer::Order.import(user,params)
+ expect(order.payments.first.amount).to eq 4.99
+ end
+
+ it 'handles payment building exceptions' do
+ params = { :payments_attributes => [{ amount: '4.99',
+ payment_method: 'XXX' }] }
+ expect {
+ order = Importer::Order.import(user, params)
+ }.to raise_error /XXX/
+ end
+
+ it 'build a source payment using years and month' do
+ params = { :payments_attributes => [{
+ amount: '4.99',
+ payment_method: payment_method.name,
+ status: 'completed',
+ source: {
+ name: 'Fox',
+ last_digits: "7424",
+ cc_type: "visa",
+ year: '2022',
+ month: "5"
+ }
+ }]}
+
+ order = Importer::Order.import(user, params)
+ expect(order.payments.first.source.last_digits).to eq '7424'
+ end
+
+ it 'handles source building exceptions when do not have years and month' do
+ params = { :payments_attributes => [{
+ amount: '4.99',
+ payment_method: payment_method.name,
+ status: 'completed',
+ source: {
+ name: 'Fox',
+ last_digits: "7424",
+ cc_type: "visa"
+ }
+ }]}
+
+ expect {
+ order = Importer::Order.import(user, params)
+ }.to raise_error /Validation failed: Credit card Month is not a number, Credit card Year is not a number/
+ end
+
+ context "raises error" do
+ it "clears out order from db" do
+ params = { :payments_attributes => [{ payment_method: "XXX" }] }
+ count = Order.count
+
+ expect { order = Importer::Order.import(user,params) }.to raise_error
+ expect(Order.count).to eq count
+ end
+ end
+
+ end
+ end
+end
diff --git a/core/spec/lib/spree/core/validators/email_spec.rb b/core/spec/lib/spree/core/validators/email_spec.rb
new file mode 100644
index 00000000000..68e0a4a78bc
--- /dev/null
+++ b/core/spec/lib/spree/core/validators/email_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe EmailValidator do
+
+ class Tester
+ include ActiveModel::Validations
+ attr_accessor :email_address
+ validates :email_address, email: true
+ end
+
+ let(:valid_emails) {[
+ 'valid@email.com',
+ 'valid@email.com.uk',
+ 'e@email.com',
+ 'valid+email@email.com',
+ 'valid-email@email.com',
+ 'valid_email@email.com',
+ 'valid.email@email.com'
+ ]}
+ let(:invalid_emails) {[
+ 'invalid email@email.com',
+ '.invalid.email@email.com',
+ 'invalid.email.@email.com',
+ '@email.com',
+ '.@email.com',
+ 'invalidemailemail.com',
+ '@invalid.email@email.com',
+ 'invalid@email@email.com',
+ 'invalid.email@@email.com'
+ ]}
+
+ it 'validates valid email addresses' do
+ tester = Tester.new
+ valid_emails.each do |email|
+ tester.email_address = email
+ expect(tester.valid?).to be true
+ end
+ end
+
+ it 'validates invalid email addresses' do
+ tester = Tester.new
+ invalid_emails.each do |email|
+ tester.email_address = email
+ expect(tester.valid?).to be false
+ end
+ end
+
+end
diff --git a/core/spec/lib/spree/localized_number_spec.rb b/core/spec/lib/spree/localized_number_spec.rb
new file mode 100644
index 00000000000..9e789179f9a
--- /dev/null
+++ b/core/spec/lib/spree/localized_number_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Spree::LocalizedNumber do
+
+ context ".parse" do
+ before do
+ I18n.enforce_available_locales = false
+ I18n.locale = I18n.default_locale
+ I18n.backend.store_translations(:de, { :number => { :currency => { :format => { :delimiter => '.', :separator => ',' } } } })
+ end
+
+ after do
+ I18n.locale = I18n.default_locale
+ I18n.enforce_available_locales = true
+ end
+
+ context "with decimal point" do
+ it "captures the proper amount for a formatted price" do
+ expect(subject.class.parse('1,599.99')).to eql 1599.99
+ end
+ end
+
+ context "with decimal comma" do
+ it "captures the proper amount for a formatted price" do
+ I18n.locale = :de
+ expect(subject.class.parse('1.599,99')).to eql 1599.99
+ end
+ end
+
+ context "with a numeric price" do
+ it "uses the price as is" do
+ I18n.locale = :de
+ expect(subject.class.parse(1599.99)).to eql 1599.99
+ end
+ end
+ end
+
+end
diff --git a/core/spec/lib/spree/migrations_spec.rb b/core/spec/lib/spree/migrations_spec.rb
new file mode 100644
index 00000000000..11835df8ad5
--- /dev/null
+++ b/core/spec/lib/spree/migrations_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+module Spree
+ describe Migrations do
+ let(:app_migrations) { [".", "34_add_title.rb", "52_add_text.rb"] }
+ let(:engine_migrations) { [".", "334_create_orders.spree.rb", "777_create_products.spree.rb"] }
+
+ let(:config) { double("Config", root: "dir") }
+
+ subject { described_class.new(config, "spree") }
+
+ before do
+ expect(File).to receive(:exists?).with("config/spree.yml").and_return true
+ expect(File).to receive(:directory?).with("db/migrate").and_return true
+ end
+
+ it "warns about missing migrations" do
+ expect(Dir).to receive(:entries).with("db/migrate").and_return app_migrations
+ expect(Dir).to receive(:entries).with("dir/db/migrate").and_return engine_migrations
+
+ silence_stream(STDOUT) {
+ expect(subject.check).to eq true
+ }
+ end
+
+ context "no missing migrations" do
+ it "says nothing" do
+ expect(Dir).to receive(:entries).with("dir/db/migrate").and_return engine_migrations
+ expect(Dir).to receive(:entries).with("db/migrate").and_return (app_migrations + engine_migrations)
+ expect(subject.check).to eq nil
+ end
+ end
+ end
+end
diff --git a/core/spec/lib/spree/money_spec.rb b/core/spec/lib/spree/money_spec.rb
new file mode 100644
index 00000000000..b3fc1b38485
--- /dev/null
+++ b/core/spec/lib/spree/money_spec.rb
@@ -0,0 +1,168 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe Spree::Money do
+ before do
+ configure_spree_preferences do |config|
+ config.currency = "USD"
+ config.currency_symbol_position = :before
+ config.display_currency = false
+ end
+ end
+
+ it "formats correctly" do
+ money = Spree::Money.new(10)
+ expect(money.to_s).to eq("$10.00")
+ end
+
+ it "can get cents" do
+ money = Spree::Money.new(10)
+ expect(money.cents).to eq(1000)
+ end
+
+ context "with currency" do
+ it "passed in option" do
+ money = Spree::Money.new(10, :with_currency => true, :html => false)
+ expect(money.to_s).to eq("$10.00 USD")
+ end
+
+ it "config option" do
+ Spree::Config[:display_currency] = true
+ money = Spree::Money.new(10, :html => false)
+ expect(money.to_s).to eq("$10.00 USD")
+ end
+ end
+
+ context "hide cents" do
+ it "hides cents suffix" do
+ Spree::Config[:hide_cents] = true
+ money = Spree::Money.new(10)
+ expect(money.to_s).to eq("$10")
+ end
+
+ it "shows cents suffix" do
+ Spree::Config[:hide_cents] = false
+ money = Spree::Money.new(10)
+ expect(money.to_s).to eq("$10.00")
+ end
+ end
+
+ context "currency parameter" do
+ context "when currency is specified in Canadian Dollars" do
+ it "uses the currency param over the global configuration" do
+ money = Spree::Money.new(10, :currency => 'CAD', :with_currency => true, :html => false)
+ expect(money.to_s).to eq("$10.00 CAD")
+ end
+ end
+
+ context "when currency is specified in Japanese Yen" do
+ it "uses the currency param over the global configuration" do
+ money = Spree::Money.new(100, :currency => 'JPY', :html => false)
+ expect(money.to_s).to eq("¥100")
+ end
+ end
+ end
+
+ context "symbol positioning" do
+ it "passed in option" do
+ money = Spree::Money.new(10, :symbol_position => :after, :html => false)
+ expect(money.to_s).to eq("10.00 $")
+ end
+
+ it "passed in option string" do
+ money = Spree::Money.new(10, :symbol_position => "after", :html => false)
+ expect(money.to_s).to eq("10.00 $")
+ end
+
+ it "config option" do
+ Spree::Config[:currency_symbol_position] = :after
+ money = Spree::Money.new(10, :html => false)
+ expect(money.to_s).to eq("10.00 $")
+ end
+ end
+
+ context "sign before symbol" do
+ it "defaults to -$10.00" do
+ money = Spree::Money.new(-10)
+ expect(money.to_s).to eq("-$10.00")
+ end
+
+ it "passed in option" do
+ money = Spree::Money.new(-10, :sign_before_symbol => false)
+ expect(money.to_s).to eq("$-10.00")
+ end
+
+ it "config option" do
+ Spree::Config[:currency_sign_before_symbol] = false
+ money = Spree::Money.new(-10)
+ expect(money.to_s).to eq("$-10.00")
+ end
+ end
+
+ context "JPY" do
+ before do
+ configure_spree_preferences do |config|
+ config.currency = "JPY"
+ config.currency_symbol_position = :before
+ config.display_currency = false
+ end
+ end
+
+ it "formats correctly" do
+ money = Spree::Money.new(1000, :html => false)
+ expect(money.to_s).to eq("¥1,000")
+ end
+ end
+
+ context "EUR" do
+ before do
+ configure_spree_preferences do |config|
+ config.currency = "EUR"
+ config.currency_symbol_position = :after
+ config.display_currency = false
+ end
+ end
+
+ # Regression test for #2634
+ it "formats as plain by default" do
+ money = Spree::Money.new(10)
+ expect(money.to_s).to eq("10.00 €")
+ end
+
+ # Regression test for #2632
+ it "acknowledges decimal mark option" do
+ Spree::Config[:currency_decimal_mark] = ","
+ money = Spree::Money.new(10)
+ expect(money.to_s).to eq("10,00 €")
+ end
+
+ # Regression test for #2632
+ it "acknowledges thousands separator option" do
+ Spree::Config[:currency_thousands_separator] = "."
+ money = Spree::Money.new(1000)
+ expect(money.to_s).to eq("1.000.00 €")
+ end
+
+ it "formats as HTML if asked (nicely) to" do
+ money = Spree::Money.new(10)
+ # The HTML'ified version of "10.00 €"
+ expect(money.to_html).to eq("10.00 €")
+ end
+
+ it "formats as HTML with currency" do
+ Spree::Config[:display_currency] = true
+ money = Spree::Money.new(10)
+ # The HTML'ified version of "10.00 €"
+ expect(money.to_html).to eq("10.00 € EUR")
+ end
+ end
+
+ describe "#as_json" do
+ let(:options) { double('options') }
+
+ it "returns the expected string" do
+ money = Spree::Money.new(10)
+ expect(money.as_json(options)).to eq("$10.00")
+ end
+ end
+end
diff --git a/core/spec/lib/tasks/exchanges_spec.rb b/core/spec/lib/tasks/exchanges_spec.rb
new file mode 100644
index 00000000000..deb51bc31a4
--- /dev/null
+++ b/core/spec/lib/tasks/exchanges_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe "exchanges:charge_unreturned_items" do
+ include_context "rake"
+
+ describe '#prerequisites' do
+ it { expect(subject.prerequisites).to include("environment") }
+ end
+
+ context "there are no unreturned items" do
+ it { expect { subject.invoke }.not_to change { Spree::Order.count } }
+ end
+
+ context "there are unreturned items" do
+ let!(:order) { create(:shipped_order, line_items_count: 2) }
+ let(:return_item_1) { create(:exchange_return_item, inventory_unit: order.inventory_units.first) }
+ let(:return_item_2) { create(:exchange_return_item, inventory_unit: order.inventory_units.last) }
+ let!(:rma) { create(:return_authorization, order: order, return_items: [return_item_1, return_item_2]) }
+ let!(:tax_rate) { create(:tax_rate, zone: order.tax_zone, tax_category: return_item_2.exchange_variant.tax_category) }
+
+ before do
+ @original_expedited_exchanges_pref = Spree::Config[:expedited_exchanges]
+ Spree::Config[:expedited_exchanges] = true
+ Spree::StockItem.update_all(count_on_hand: 10)
+ rma.save!
+ Spree::Shipment.last.ship!
+ return_item_1.receive!
+ Timecop.travel travel_time
+ end
+
+ after do
+ Timecop.return
+ Spree::Config[:expedited_exchanges] = @original_expedited_exchanges_pref
+ end
+
+ context "fewer than the config allowed days have passed" do
+ let(:travel_time) { (Spree::Config[:expedited_exchanges_days_window] - 1).days }
+
+ it "does not create a new order" do
+ expect { subject.invoke }.not_to change { Spree::Order.count }
+ end
+ end
+
+ context "more than the config allowed days have passed" do
+
+ let(:travel_time) { (Spree::Config[:expedited_exchanges_days_window] + 1).days }
+
+ it "creates a new completed order" do
+ expect { subject.invoke }.to change { Spree::Order.count }
+ expect(Spree::Order.last).to be_completed
+ end
+
+ it "moves the shipment for the unreturned items to the new order" do
+ subject.invoke
+ new_order = Spree::Order.last
+ expect(new_order.shipments.count).to eq 1
+ expect(return_item_2.reload.exchange_shipment.order).to eq Spree::Order.last
+ end
+
+ it "creates line items on the order for the unreturned items" do
+ subject.invoke
+ expect(Spree::Order.last.line_items.map(&:variant)).to eq [return_item_2.exchange_variant]
+ end
+
+ it "associates the exchanges inventory units with the new line items" do
+ subject.invoke
+ expect(return_item_2.reload.exchange_inventory_unit.try(:line_item).try(:order)).to eq Spree::Order.last
+ end
+
+ it "uses the credit card from the previous order" do
+ subject.invoke
+ new_order = Spree::Order.last
+ expect(new_order.credit_cards).to be_present
+ expect(new_order.credit_cards.first).to eq order.valid_credit_cards.first
+ end
+
+ it "authorizes the order for the full amount of the unreturned items including taxes" do
+ expect { subject.invoke }.to change { Spree::Payment.count }.by(1)
+ new_order = Spree::Order.last
+ expected_amount = return_item_2.reload.exchange_variant.price + new_order.additional_tax_total + new_order.included_tax_total
+ expect(new_order.total).to eq expected_amount
+ payment = new_order.payments.first
+ expect(payment.amount).to eq expected_amount
+ expect(payment).to be_pending
+ expect(new_order.item_total).to eq return_item_2.reload.exchange_variant.price
+ end
+
+ it "does not attempt to create a new order for the item more than once" do
+ subject.invoke
+ subject.reenable
+ expect { subject.invoke }.not_to change { Spree::Order.count }
+ end
+
+ it "associates the store of the original order with the exchange order" do
+ allow_any_instance_of(Spree::Order).to receive(:store_id).and_return(123)
+
+ expect(Spree::Order).to receive(:create!).once.with(hash_including({store_id: 123})) { |attrs| Spree::Order.new(attrs.except(:store_id)).tap(&:save!) }
+ subject.invoke
+ end
+
+ context "there is no card from the previous order" do
+ let!(:credit_card) { create(:credit_card, user: order.user, default: true, gateway_customer_profile_id: "BGS-123") }
+ before { allow_any_instance_of(Spree::Order).to receive(:valid_credit_cards) { [] } }
+
+ it "attempts to use the user's default card" do
+ expect { subject.invoke }.to change { Spree::Payment.count }.by(1)
+ new_order = Spree::Order.last
+ expect(new_order.credit_cards).to be_present
+ expect(new_order.credit_cards.first).to eq credit_card
+ end
+ end
+
+ context "it is unable to authorize the credit card" do
+ before { allow_any_instance_of(Spree::Payment).to receive(:authorize!).and_raise(RuntimeError) }
+
+ it "raises an error with the order" do
+ expect { subject.invoke }.to raise_error(UnableToChargeForUnreturnedItems)
+ end
+ end
+
+ context "the exchange inventory unit is not shipped" do
+ before { return_item_2.reload.exchange_inventory_unit.update_columns(state: "on hand") }
+ it "does not create a new order" do
+ expect { subject.invoke }.not_to change { Spree::Order.count }
+ end
+ end
+
+ context "the exchange inventory unit has been returned" do
+ before { return_item_2.reload.exchange_inventory_unit.update_columns(state: "returned") }
+ it "does not create a new order" do
+ expect { subject.invoke }.not_to change { Spree::Order.count }
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/lib/token_resource_spec.rb b/core/spec/lib/token_resource_spec.rb
deleted file mode 100644
index 40ac8f838ab..00000000000
--- a/core/spec/lib/token_resource_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction with an actual class that
-# extends ActiveRecord::Base and has a corresponding table in the database. So we'll just test it using Order instead
-# since those classes are including the module.
-describe Spree::Core::TokenResource do
- let(:order) { Spree::Order.new }
- let(:permission) { mock_model(Spree::TokenizedPermission) }
-
- it 'should add has_one :tokenized_permission relationship' do
- assert Spree::Order.reflect_on_all_associations(:has_one).map(&:name).include?(:tokenized_permission)
- end
-
- context '#token' do
- it 'should return the token of the associated permission' do
- order.stub :tokenized_permission => permission
- permission.stub :token => 'foo'
- order.token.should == 'foo'
- end
-
- it 'should return nil if there is no associated permission' do
- order.token.should be_nil
- end
- end
-
- context '#create_token' do
- it 'should create a randomized 16 character token' do
- token = order.create_token
- token.size.should == 16
- end
- end
-end
diff --git a/core/spec/mailers/order_mailer_spec.rb b/core/spec/mailers/order_mailer_spec.rb
index 295488d48a6..cec201d0dc3 100644
--- a/core/spec/mailers/order_mailer_spec.rb
+++ b/core/spec/mailers/order_mailer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'email_spec'
-describe Spree::OrderMailer do
+describe Spree::OrderMailer, :type => :mailer do
include EmailSpec::Helpers
include EmailSpec::Matchers
@@ -9,70 +9,82 @@
order = stub_model(Spree::Order)
product = stub_model(Spree::Product, :name => %Q{The "BEST" product})
variant = stub_model(Spree::Variant, :product => product)
- price = stub_model(Spree::Price, :variant => variant)
- line_item = stub_model(Spree::LineItem, :variant => variant, :order => order, :quantity => 1, :price => 5)
- variant.stub(:default_price => price)
- order.stub(:line_items => [line_item])
+ price = stub_model(Spree::Price, :variant => variant, :amount => 5.00)
+ line_item = stub_model(Spree::LineItem, :variant => variant, :order => order, :quantity => 1, :price => 4.99)
+ allow(variant).to receive_messages(:default_price => price)
+ allow(order).to receive_messages(:line_items => [line_item])
order
end
+ context ":from not set explicitly" do
+ it "falls back to spree config" do
+ message = Spree::OrderMailer.confirm_email(order)
+ expect(message.from).to eq([Spree::Config[:mails_from]])
+ end
+ end
+
it "doesn't aggressively escape double quotes in confirmation body" do
confirmation_email = Spree::OrderMailer.confirm_email(order)
- confirmation_email.body.should_not include(""")
+ expect(confirmation_email.body).not_to include(""")
+ end
+
+ it "confirm_email accepts an order id as an alternative to an Order object" do
+ expect(Spree::Order).to receive(:find).with(order.id).and_return(order)
+ expect {
+ confirmation_email = Spree::OrderMailer.confirm_email(order.id)
+ }.not_to raise_error
+ end
+
+ it "cancel_email accepts an order id as an alternative to an Order object" do
+ expect(Spree::Order).to receive(:find).with(order.id).and_return(order)
+ expect {
+ cancel_email = Spree::OrderMailer.cancel_email(order.id)
+ }.not_to raise_error
end
context "only shows eligible adjustments in emails" do
before do
- order.adjustments.create({:label => "Eligible Adjustment",
- :amount => 10,
- :eligible => true}, :without_protection => true)
-
- order.adjustments.create!({:label => "Ineligible Adjustment",
- :amount => -10,
- :eligible => false}, :without_protection => true)
+ create(:adjustment, :order => order, :eligible => true, :label => "Eligible Adjustment")
+ create(:adjustment, :order => order, :eligible => false, :label => "Ineligible Adjustment")
end
let!(:confirmation_email) { Spree::OrderMailer.confirm_email(order) }
let!(:cancel_email) { Spree::OrderMailer.cancel_email(order) }
specify do
- confirmation_email.body.should_not include("Ineligible Adjustment")
+ expect(confirmation_email.body).not_to include("Ineligible Adjustment")
end
specify do
- cancel_email.body.should_not include("Ineligible Adjustment")
+ expect(cancel_email.body).not_to include("Ineligible Adjustment")
end
end
- context "emails must be translatable" do
- context "en locale" do
- before do
- en_confirm_mail = { :order_mailer => { :confirm_email => { :dear_customer => 'Dear Customer,' } } }
- en_cancel_mail = { :order_mailer => { :cancel_email => { :order_summary_canceled => 'Order Summary [CANCELED]' } } }
- I18n.backend.store_translations :en, en_confirm_mail
- I18n.backend.store_translations :en, en_cancel_mail
- I18n.locale = :en
- end
+ context "displays unit costs from line item" do
+ # Regression test for #2772
- context "confirm_email" do
- specify do
- confirmation_email = Spree::OrderMailer.confirm_email(order)
- confirmation_email.body.should include("Dear Customer,")
- end
- end
+ # Tests mailer view spree/order_mailer/confirm_email.text.erb
+ specify do
+ confirmation_email = Spree::OrderMailer.confirm_email(order)
+ expect(confirmation_email).to have_body_text("4.99")
+ expect(confirmation_email).to_not have_body_text("5.00")
+ end
- context "cancel_email" do
- specify do
- cancel_email = Spree::OrderMailer.cancel_email(order)
- cancel_email.body.should include("Order Summary [CANCELED]")
- end
- end
+ # Tests mailer view spree/order_mailer/cancel_email.text.erb
+ specify do
+ cancel_email = Spree::OrderMailer.cancel_email(order)
+ expect(cancel_email).to have_body_text("4.99")
+ expect(cancel_email).to_not have_body_text("5.00")
end
+ end
+
+ context "emails must be translatable" do
context "pt-BR locale" do
before do
- pt_br_confirm_mail = { :order_mailer => { :confirm_email => { :dear_customer => 'Caro Cliente,' } } }
- pt_br_cancel_mail = { :order_mailer => { :cancel_email => { :order_summary_canceled => 'Resumo da Pedido [CANCELADA]' } } }
+ I18n.enforce_available_locales = false
+ pt_br_confirm_mail = { :spree => { :order_mailer => { :confirm_email => { :dear_customer => 'Caro Cliente,' } } } }
+ pt_br_cancel_mail = { :spree => { :order_mailer => { :cancel_email => { :order_summary_canceled => 'Resumo da Pedido [CANCELADA]' } } } }
I18n.backend.store_translations :'pt-BR', pt_br_confirm_mail
I18n.backend.store_translations :'pt-BR', pt_br_cancel_mail
I18n.locale = :'pt-BR'
@@ -80,21 +92,31 @@
after do
I18n.locale = I18n.default_locale
+ I18n.enforce_available_locales = true
end
context "confirm_email" do
specify do
confirmation_email = Spree::OrderMailer.confirm_email(order)
- confirmation_email.body.should include("Caro Cliente,")
+ expect(confirmation_email).to have_body_text("Caro Cliente,")
end
end
context "cancel_email" do
specify do
cancel_email = Spree::OrderMailer.cancel_email(order)
- cancel_email.body.should include("Resumo da Pedido [CANCELADA]")
+ expect(cancel_email).to have_body_text("Resumo da Pedido [CANCELADA]")
end
end
end
end
+
+ context "with preference :send_core_emails set to false" do
+ it "sends no email" do
+ Spree::Config.set(:send_core_emails, false)
+ message = Spree::OrderMailer.confirm_email(order)
+ expect(message.body).to be_blank
+ end
+ end
+
end
diff --git a/core/spec/mailers/reimbursement_mailer_spec.rb b/core/spec/mailers/reimbursement_mailer_spec.rb
new file mode 100644
index 00000000000..063160ad738
--- /dev/null
+++ b/core/spec/mailers/reimbursement_mailer_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+require 'email_spec'
+
+describe Spree::ReimbursementMailer, :type => :mailer do
+ include EmailSpec::Helpers
+ include EmailSpec::Matchers
+
+ let(:reimbursement) { create(:reimbursement) }
+
+ context ":from not set explicitly" do
+ it "falls back to spree config" do
+ message = Spree::ReimbursementMailer.reimbursement_email(reimbursement)
+ expect(message.from).to eq [Spree::Config[:mails_from]]
+ end
+ end
+
+ it "accepts a reimbursement id as an alternative to a Reimbursement object" do
+ expect(Spree::Reimbursement).to receive(:find).with(reimbursement.id).and_return(reimbursement)
+
+ expect {
+ reimbursement_email = Spree::ReimbursementMailer.reimbursement_email(reimbursement.id)
+ }.not_to raise_error
+ end
+
+ context "emails must be translatable" do
+ context "reimbursement_email" do
+ context "pt-BR locale" do
+ before do
+ I18n.enforce_available_locales = false
+ pt_br_shipped_email = { :spree => { :reimbursement_mailer => { :reimbursement_email => { :dear_customer => 'Caro Cliente,' } } } }
+ I18n.backend.store_translations :'pt-BR', pt_br_shipped_email
+ I18n.locale = :'pt-BR'
+ end
+
+ after do
+ I18n.locale = I18n.default_locale
+ I18n.enforce_available_locales = true
+ end
+
+ specify do
+ reimbursement_email = Spree::ReimbursementMailer.reimbursement_email(reimbursement)
+ expect(reimbursement_email.body).to include("Caro Cliente,")
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/mailers/shipment_mailer_spec.rb b/core/spec/mailers/shipment_mailer_spec.rb
index a4882e4cfbc..39939347cca 100644
--- a/core/spec/mailers/shipment_mailer_spec.rb
+++ b/core/spec/mailers/shipment_mailer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'email_spec'
-describe Spree::ShipmentMailer do
+describe Spree::ShipmentMailer, :type => :mailer do
include EmailSpec::Helpers
include EmailSpec::Matchers
@@ -9,47 +9,51 @@
order = stub_model(Spree::Order)
product = stub_model(Spree::Product, :name => %Q{The "BEST" product})
variant = stub_model(Spree::Variant, :product => product)
- variant.stub(:in_stock? => false)
line_item = stub_model(Spree::LineItem, :variant => variant, :order => order, :quantity => 1, :price => 5)
shipment = stub_model(Spree::Shipment)
- shipment.stub(:line_items => [line_item], :order => order)
+ allow(shipment).to receive_messages(:line_items => [line_item], :order => order)
+ allow(shipment).to receive_messages(:tracking_url => "TRACK_ME")
shipment
end
+ context ":from not set explicitly" do
+ it "falls back to spree config" do
+ message = Spree::ShipmentMailer.shipped_email(shipment)
+ expect(message.from).to eq([Spree::Config[:mails_from]])
+ end
+ end
+
# Regression test for #2196
it "doesn't include out of stock in the email body" do
shipment_email = Spree::ShipmentMailer.shipped_email(shipment)
- shipment_email.body.should_not include(%Q{Out of Stock})
+ expect(shipment_email.body).not_to include(%Q{Out of Stock})
+ end
+
+ it "shipment_email accepts an shipment id as an alternative to an Shipment object" do
+ expect(Spree::Shipment).to receive(:find).with(shipment.id).and_return(shipment)
+ expect {
+ shipped_email = Spree::ShipmentMailer.shipped_email(shipment.id)
+ }.not_to raise_error
end
context "emails must be translatable" do
context "shipped_email" do
- context "en locale" do
- before do
- en_shipped_email = { :shipment_mailer => { :shipped_email => { :dear_customer => 'Dear Customer,' } } }
- I18n.backend.store_translations :en, en_shipped_email
- I18n.locale = :en
- end
-
- specify do
- shipped_email = Spree::ShipmentMailer.shipped_email(shipment)
- shipped_email.body.should include("Dear Customer,")
- end
- end
context "pt-BR locale" do
before do
- pt_br_shipped_email = { :shipment_mailer => { :shipped_email => { :dear_customer => 'Caro Cliente,' } } }
+ I18n.enforce_available_locales = false
+ pt_br_shipped_email = { :spree => { :shipment_mailer => { :shipped_email => { :dear_customer => 'Caro Cliente,' } } } }
I18n.backend.store_translations :'pt-BR', pt_br_shipped_email
I18n.locale = :'pt-BR'
end
after do
I18n.locale = I18n.default_locale
+ I18n.enforce_available_locales = true
end
specify do
shipped_email = Spree::ShipmentMailer.shipped_email(shipment)
- shipped_email.body.should include("Caro Cliente,")
+ expect(shipped_email).to have_body_text("Caro Cliente,")
end
end
end
diff --git a/core/spec/mailers/test_mailer_spec.rb b/core/spec/mailers/test_mailer_spec.rb
new file mode 100644
index 00000000000..ef5601d44ff
--- /dev/null
+++ b/core/spec/mailers/test_mailer_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+require 'email_spec'
+
+describe Spree::TestMailer, :type => :mailer do
+ include EmailSpec::Helpers
+ include EmailSpec::Matchers
+
+ let(:user) { create(:user) }
+
+ context ":from not set explicitly" do
+ it "falls back to spree config" do
+ message = Spree::TestMailer.test_email('test@example.com')
+ expect(message.from).to eq([Spree::Config[:mails_from]])
+ end
+ end
+
+ it "confirm_email accepts a user id as an alternative to a User object" do
+ expect {
+ test_email = Spree::TestMailer.test_email('test@example.com')
+ }.not_to raise_error
+ end
+end
diff --git a/core/spec/models/activator_spec.rb b/core/spec/models/activator_spec.rb
deleted file mode 100644
index 96a63d6124d..00000000000
--- a/core/spec/models/activator_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Activator do
-
- context "register_event_name" do
- it "adds the name to event_names" do
- Spree::Activator.register_event_name('spree.new_event')
- Spree::Activator.event_names.should include('spree.new_event')
- end
- end
-
-end
diff --git a/core/spec/models/address_spec.rb b/core/spec/models/address_spec.rb
deleted file mode 100644
index ee229d6a359..00000000000
--- a/core/spec/models/address_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Address do
- describe "clone" do
- it "creates a copy of the address with the exception of the id, updated_at and created_at attributes" do
- state = create(:state)
- original = create(:address,
- :address1 => 'address1',
- :address2 => 'address2',
- :alternative_phone => 'alternative_phone',
- :city => 'city',
- :country => Spree::Country.first,
- :firstname => 'firstname',
- :lastname => 'lastname',
- :company => 'company',
- :phone => 'phone',
- :state_id => state.id,
- :state_name => state.name,
- :zipcode => 'zip_code')
-
- cloned = original.clone
-
- cloned.address1.should == original.address1
- cloned.address2.should == original.address2
- cloned.alternative_phone.should == original.alternative_phone
- cloned.city.should == original.city
- cloned.country_id.should == original.country_id
- cloned.firstname.should == original.firstname
- cloned.lastname.should == original.lastname
- cloned.company.should == original.company
- cloned.phone.should == original.phone
- cloned.state_id.should == original.state_id
- cloned.state_name.should == original.state_name
- cloned.zipcode.should == original.zipcode
-
- cloned.id.should_not == original.id
- cloned.created_at.should_not == original.created_at
- cloned.updated_at.should_not == original.updated_at
- end
- end
-
- context "validation" do
- before do
- reset_spree_preferences do |config|
- config.address_requires_state = true
- end
- end
-
- let(:country) { mock_model(Spree::Country, :states => [state], :states_required => true) }
- let(:state) { stub_model(Spree::State, :name => 'maryland', :abbr => 'md') }
- let(:address) { FactoryGirl.build(:address, :country => country) }
-
- before do
- country.states.stub :find_all_by_name_or_abbr => [state]
- end
-
- it "state_name is not nil and country does not have any states" do
- address.state = nil
- address.state_name = 'alabama'
- address.should be_valid
- end
-
- it "errors when state_name is nil" do
- address.state_name = nil
- address.state = nil
- address.should_not be_valid
- end
-
- it "full state name is in state_name and country does contain that state" do
- address.state_name = 'alabama'
- # called by state_validate to set up state_id.
- # Perhaps this should be a before_validation instead?
- address.should be_valid
- address.state.should_not be_nil
- address.state_name.should be_nil
- end
-
- it "state abbr is in state_name and country does contain that state" do
- address.state_name = state.abbr
- address.should be_valid
- address.state_id.should_not be_nil
- address.state_name.should be_nil
- end
-
- it "state is entered but country does not contain that state" do
- address.state = state
- address.country = stub_model(Spree::Country)
- address.valid?
- address.errors["state"].should == ['is invalid']
- end
-
- it "both state and state_name are entered but country does not contain the state" do
- address.state = state
- address.state_name = 'maryland'
- address.country = stub_model(Spree::Country)
- address.should be_valid
- address.state_id.should be_nil
- end
-
- it "both state and state_name are entered and country does contain the state" do
- address.state = state
- address.state_name = 'maryland'
- address.should be_valid
- address.state_name.should be_nil
- end
-
- it "address_requires_state preference is false" do
- Spree::Config.set :address_requires_state => false
- address.state = nil
- address.state_name = nil
- address.should be_valid
- end
-
- end
-
- context ".default" do
- before do
- @default_country_id = Spree::Config[:default_country_id]
- new_country = create(:country)
- Spree::Config[:default_country_id] = new_country.id
- end
-
- after do
- Spree::Config[:default_country_id] = @default_country_id
- end
- it "sets up a new record with Spree::Config[:default_country_id]" do
- Spree::Address.default.country.should == Spree::Country.find(Spree::Config[:default_country_id])
- end
-
- # Regression test for #1142
- it "uses the first available country if :default_country_id is set to an invalid value" do
- Spree::Config[:default_country_id] = "0"
- Spree::Address.default.country.should == Spree::Country.first
- end
- end
-
- context '#full_name' do
- context 'both first and last names are present' do
- let(:address) { stub_model(Spree::Address, :firstname => 'Michael', :lastname => 'Jackson') }
- specify { address.full_name.should == 'Michael Jackson' }
- end
-
- context 'first name is blank' do
- let(:address) { stub_model(Spree::Address, :firstname => nil, :lastname => 'Jackson') }
- specify { address.full_name.should == 'Jackson' }
- end
-
- context 'last name is blank' do
- let(:address) { stub_model(Spree::Address, :firstname => 'Michael', :lastname => nil) }
- specify { address.full_name.should == 'Michael' }
- end
-
- context 'both first and last names are blank' do
- let(:address) { stub_model(Spree::Address, :firstname => nil, :lastname => nil) }
- specify { address.full_name.should == '' }
- end
-
- end
-
- context '#state_text' do
- context 'state is blank' do
- let(:address) { stub_model(Spree::Address, :state => nil, :state_name => 'virginia') }
- specify { address.state_text.should == 'virginia' }
- end
-
- context 'both name and abbr is present' do
- let(:state) { stub_model(Spree::State, :name => 'virginia', :abbr => 'va') }
- let(:address) { stub_model(Spree::Address, :state => state) }
- specify { address.state_text.should == 'va' }
- end
-
- context 'only name is present' do
- let(:state) { stub_model(Spree::State, :name => 'virginia', :abbr => nil) }
- let(:address) { stub_model(Spree::Address, :state => state) }
- specify { address.state_text.should == 'virginia' }
- end
-
- end
-end
diff --git a/core/spec/models/adjustment_spec.rb b/core/spec/models/adjustment_spec.rb
deleted file mode 100644
index de67186751c..00000000000
--- a/core/spec/models/adjustment_spec.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-# encoding: utf-8
-#
-
-require 'spec_helper'
-
-describe Spree::Adjustment do
-
- let(:order) { mock_model(Spree::Order, :update! => nil) }
- let(:adjustment) { Spree::Adjustment.new }
-
- context "#update!" do
- context "when originator present" do
- let(:originator) { mock("originator", :update_adjustment => nil) }
- before do
- originator.stub :update_amount => true
- adjustment.stub :originator => originator
- end
- it "should do nothing when locked" do
- adjustment.locked = true
- originator.should_not_receive(:update_adjustment)
- adjustment.update!
- end
- it "should set the eligibility" do
- adjustment.should_receive(:set_eligibility)
- adjustment.update!
- end
- it "should ask the originator to update_adjustment" do
- originator.should_receive(:update_adjustment)
- adjustment.update!
- end
- end
- it "should do nothing when originator is nil" do
- adjustment.stub :originator => nil
- adjustment.should_not_receive(:amount=)
- adjustment.update!
- end
- end
-
- context "#eligible? after #set_eligibility" do
- context "when amount is 0" do
- before { adjustment.amount = 0 }
- it "should be eligible if mandatory?" do
- adjustment.mandatory = true
- adjustment.set_eligibility
- adjustment.should be_eligible
- end
- it "should not be eligible unless mandatory?" do
- adjustment.mandatory = false
- adjustment.set_eligibility
- adjustment.should_not be_eligible
- end
- end
- context "when amount is greater than 0" do
- before { adjustment.amount = 25.00 }
- it "should be eligible if mandatory?" do
- adjustment.mandatory = true
- adjustment.set_eligibility
- adjustment.should be_eligible
- end
- it "should be eligible if not mandatory and eligible for the originator" do
- adjustment.mandatory = false
- adjustment.stub(:eligible_for_originator? => true)
- adjustment.set_eligibility
- adjustment.should be_eligible
- end
- it "should not be eligible if not mandatory not eligible for the originator" do
- adjustment.mandatory = false
- adjustment.stub(:eligible_for_originator? => false)
- adjustment.set_eligibility
- adjustment.should_not be_eligible
- end
- end
- end
-
- context "#save" do
- it "should call order#update!" do
- adjustment = Spree::Adjustment.new({:adjustable => order, :amount => 10, :label => "Foo"}, :without_protection => true)
- order.should_receive(:update!)
- adjustment.save
- end
- end
-
-
- context "#eligible_for_originator?" do
- context "with no originator" do
- specify { adjustment.should be_eligible_for_originator }
- end
- context "with originator that doesn't have 'eligible?'" do
- before { adjustment.originator = mock_model(Spree::TaxRate) }
- specify { adjustment.should be_eligible_for_originator }
- end
- context "with originator that has 'eligible?'" do
- let(:originator) { Spree::TaxRate.new }
- before { adjustment.originator = originator }
- context "and originator is eligible for order" do
- before { originator.stub(:eligible? => true) }
- specify { adjustment.should be_eligible_for_originator }
- end
- context "and originator is not eligible for order" do
- before { originator.stub(:eligible? => false) }
- specify { adjustment.should_not be_eligible_for_originator }
- end
- end
- end
-
- context "#display_amount" do
- before { adjustment.amount = 10.55 }
-
- context "with display_currency set to true" do
- before { Spree::Config[:display_currency] = true }
-
- it "shows the currency" do
- adjustment.display_amount.should == "$10.55 USD"
- end
- end
-
- context "with display_currency set to false" do
- before { Spree::Config[:display_currency] = false }
-
- it "does not include the currency" do
- adjustment.display_amount.should == "$10.55"
- end
- end
-
- context "with currency set to JPY" do
- context "when adjustable is set to an order" do
- before do
- order.stub(:currency) { 'JPY' }
- adjustment.adjustable = order
- end
-
- it "displays in JPY" do
- adjustment.display_amount.should == "¥11"
- end
- end
-
- context "when adjustable is nil" do
- it "displays in the default currency" do
- adjustment.display_amount.should == "$10.55"
- end
- end
- end
- end
-
- context '#currency' do
- it 'returns the globally configured currency' do
- adjustment.currency.should == 'USD'
- end
- end
-end
diff --git a/core/spec/models/app_configuration_spec.rb b/core/spec/models/app_configuration_spec.rb
deleted file mode 100644
index bca2f74a66b..00000000000
--- a/core/spec/models/app_configuration_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe Spree::AppConfiguration do
-
- let (:prefs) { Rails.application.config.spree.preferences }
-
- it "should be available from the environment" do
- prefs.site_name = "TEST SITE NAME"
- prefs.site_name.should eq "TEST SITE NAME"
- end
-
- it "should be available as Spree::Config for legacy access" do
- Spree::Config.site_name = "Spree::Config TEST SITE NAME"
- Spree::Config.site_name.should eq "Spree::Config TEST SITE NAME"
- end
-
- it "uses base searcher class by default" do
- prefs.searcher_class = nil
- prefs.searcher_class.should eq Spree::Core::Search::Base
- end
-
-end
-
diff --git a/core/spec/models/calculator/default_tax_spec.rb b/core/spec/models/calculator/default_tax_spec.rb
deleted file mode 100644
index da0ec7dfcaf..00000000000
--- a/core/spec/models/calculator/default_tax_spec.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Calculator::DefaultTax do
- let!(:tax_category) { create(:tax_category, :tax_rates => []) }
- let!(:rate) { mock_model(Spree::TaxRate, :tax_category => tax_category, :amount => 0.05) }
- let!(:calculator) { Spree::Calculator::DefaultTax.new({:calculable => rate}, :without_protection => true) }
- let!(:order) { create(:order) }
- let!(:product_1) { create(:product) }
- let!(:product_2) { create(:product) }
- let!(:line_item_1) { create(:line_item, :product => product_1, :price => 10, :quantity => 3) }
- let!(:line_item_2) { create(:line_item, :product => product_2, :price => 5, :quantity => 1) }
-
- context "#compute" do
- context "when given an order" do
- before do
- order.stub :line_items => [line_item_1, line_item_2]
- end
-
- context "when no line items match the tax category" do
- before do
- product_1.tax_category = nil
- product_2.tax_category = nil
- end
-
- it "should be 0" do
- calculator.compute(order).should == 0
- end
- end
-
- context "when one item matches the tax category" do
- before do
- product_1.tax_category = tax_category
- product_2.tax_category = nil
- end
-
- it "should be equal to the item total * rate" do
- calculator.compute(order).should == 1.5
- end
-
- context "correctly rounds to within two decimal places" do
- before do
- line_item_1.price = 10.333
- line_item_1.quantity = 1
- end
-
- specify do
- # Amount is 0.51665, which will be rounded to...
- calculator.compute(order).should == 0.52
- end
-
- end
- end
-
-
- context "when more than one item matches the tax category" do
- it "should be equal to the sum of the item totals * rate" do
- calculator.compute(order).should == 1.75
- end
- end
- end
-
- context "when given a line item" do
- context "when the variant matches the tax category" do
- it "should be equal to the item total * rate" do
- calculator.compute(line_item_1).should == 1.43
- end
- end
-
- context "when the variant does not match the tax category" do
- before do
- line_item_2.product.tax_category = nil
- end
-
- it "should be 0" do
- calculator.compute(line_item_2).should == 0
- end
- end
- end
- end
-end
diff --git a/core/spec/models/calculator/flat_percent_item_total_spec.rb b/core/spec/models/calculator/flat_percent_item_total_spec.rb
deleted file mode 100644
index c8f516310bb..00000000000
--- a/core/spec/models/calculator/flat_percent_item_total_spec.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Calculator::FlatPercentItemTotal do
- let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new }
- let(:order) { mock_model Spree::Order, :line_items => [mock_model(Spree::LineItem, :amount => 10), mock_model(Spree::LineItem, :amount => 20)] }
-
- before { calculator.stub :preferred_flat_percent => 10 }
-
- context "compute" do
- it "should compute amount correctly" do
- calculator.compute(order).should == 3.0
- end
-
- it "should round result correctly" do
- order.stub :line_items => [mock_model(Spree::LineItem, :amount => 10.56), mock_model(Spree::LineItem, :amount => 20.49)]
- calculator.compute(order).should == 3.11
-
- order.stub :line_items => [mock_model(Spree::LineItem, :amount => 10.56), mock_model(Spree::LineItem, :amount => 20.48)]
- calculator.compute(order).should == 3.10
- end
- end
-end
diff --git a/core/spec/models/calculator/flexi_rate_spec.rb b/core/spec/models/calculator/flexi_rate_spec.rb
deleted file mode 100644
index cc7b342a8bb..00000000000
--- a/core/spec/models/calculator/flexi_rate_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Calculator::FlexiRate do
- let(:calculator) { Spree::Calculator::FlexiRate.new }
- let(:order) { mock_model Spree::Order, :line_items => [mock_model(Spree::LineItem, :amount => 10, :quantity => 4), mock_model(Spree::LineItem, :amount => 20, :quantity => 6)] }
-
- context "compute" do
- it "should compute amount correctly when all fees are 0" do
- calculator.compute(order).round(2).should == 0.0
- end
-
- it "should compute amount correctly when first_item has a value" do
- calculator.stub :preferred_first_item => 1.0
- calculator.compute(order).round(2).should == 1.0
- end
-
- it "should compute amount correctly when additional_items has a value" do
- calculator.stub :preferred_additional_item => 1.0
- calculator.compute(order).round(2).should == 9.0
- end
-
- it "should compute amount correctly when additional_items and first_item have values" do
- calculator.stub :preferred_first_item => 5.0, :preferred_additional_item => 1.0
- calculator.compute(order).round(2).should == 14.0
- end
-
- it "should compute amount correctly when additional_items and first_item have values AND max items has value" do
- calculator.stub :preferred_first_item => 5.0, :preferred_additional_item => 1.0, :preferred_max_items => 3
- calculator.compute(order).round(2).should == 26.0
- end
-
- it "should allow creation of new object with all the attributes" do
- Spree::Calculator::FlexiRate.new(:preferred_first_item => 1, :preferred_additional_item => 1, :preferred_max_items => 1)
- end
- end
-end
diff --git a/core/spec/models/calculator/per_item_spec.rb b/core/spec/models/calculator/per_item_spec.rb
deleted file mode 100644
index 211dc0e7b79..00000000000
--- a/core/spec/models/calculator/per_item_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Calculator::PerItem do
- # Like an order object, but not quite...
- let!(:product1) { double("Product") }
- let!(:product2) { double("Product") }
- let!(:line_items) { [double("LineItem", :quantity => 5, :product => product1), double("LineItem", :quantity => 3, :product => product2)] }
- let!(:object) { double("Order", :line_items => line_items) }
-
- let!(:shipping_calculable) { double("Calculable") }
- let!(:promotion_calculable) { double("Calculable", :promotion => promotion) }
-
- let!(:promotion) { double("Promotion", :rules => [double("Rule", :products => [product1])]) }
-
- let!(:calculator) { Spree::Calculator::PerItem.new(:preferred_amount => 10) }
-
- # regression test for #1414
- it "correctly calculates per item shipping" do
- calculator.stub(:calculable => shipping_calculable)
- calculator.compute(object).to_f.should == 80 # 5 x 10 + 3 x 10
- end
-
- it "correctly calculates per item promotion" do
- calculator.stub(:calculable => promotion_calculable)
- calculator.compute(object).to_f.should == 50 # 5 x 10
- end
-
- it "returns 0 when no object passed" do
- calculator.stub(:calculable => shipping_calculable)
- calculator.compute.should == 0
- end
-
- it "returns 0 when no object passed" do
- calculator.stub(:calculable => promotion_calculable)
- calculator.compute.should == 0
- end
-
-end
diff --git a/core/spec/models/calculator/price_sack_spec.rb b/core/spec/models/calculator/price_sack_spec.rb
deleted file mode 100644
index 21d6cabe13f..00000000000
--- a/core/spec/models/calculator/price_sack_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Calculator::PriceSack do
- let(:calculator) do
- calculator = Spree::Calculator::PriceSack.new
- calculator.preferred_minimal_amount = 5
- calculator.preferred_normal_amount = 10
- calculator.preferred_discount_amount = 1
- calculator
- end
-
- let(:order) { stub_model(Spree::Order) }
- let(:shipment) { stub_model(Spree::Shipment) }
-
- # Regression test for #714 and #739
- it "computes with an order object" do
- calculator.compute(order)
- end
-
- # Regression test for #1156
- it "computes with a shipment object" do
- calculator.compute(shipment)
- end
-
- # Regression test for #2055
- it "computes the correct amount" do
- calculator.compute(2).should == calculator.preferred_normal_amount
- calculator.compute(6).should == calculator.preferred_discount_amount
- end
-end
diff --git a/core/spec/models/configuration_spec.rb b/core/spec/models/configuration_spec.rb
deleted file mode 100644
index 7ebc496cda3..00000000000
--- a/core/spec/models/configuration_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Configuration do
-
-end
diff --git a/core/spec/models/country_spec.rb b/core/spec/models/country_spec.rb
deleted file mode 100644
index c147f236b6f..00000000000
--- a/core/spec/models/country_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Country do
- it "can find all countries group by states required" do
- country_states_required= Spree::Country.create({:name => "Canada", :iso_name => "CAN", :states_required => true})
- country_states_not_required= Spree::Country.create({:name => "France", :iso_name => "FR", :states_required => false})
- states_required = Spree::Country.states_required_by_country_id
- states_required[country_states_required.id.to_s].should be_true
- states_required[country_states_not_required.id.to_s].should be_false
- end
-
- it "returns that the states are required for an invalid country" do
- Spree::Country.states_required_by_country_id['i do not exit'].should be_true
- end
-end
diff --git a/core/spec/models/credit_card_spec.rb b/core/spec/models/credit_card_spec.rb
deleted file mode 100644
index 01517a48e46..00000000000
--- a/core/spec/models/credit_card_spec.rb
+++ /dev/null
@@ -1,216 +0,0 @@
-require 'spec_helper'
-
-describe Spree::CreditCard do
-
- let(:valid_credit_card_attributes) { {:number => '4111111111111111', :verification_value => '123', :month => 12, :year => 2014} }
-
- def stub_rails_env(environment)
- Rails.stub(:env => ActiveSupport::StringInquirer.new(environment))
- end
-
- let(:credit_card) { Spree::CreditCard.new }
-
- before(:each) do
-
- @order = create(:order)
- @payment = Spree::Payment.create({:amount => 100, :order => @order}, :without_protection => true)
-
- @success_response = mock('gateway_response', :success? => true, :authorization => '123', :avs_result => {'code' => 'avs-code'})
- @fail_response = mock('gateway_response', :success? => false)
-
- @payment_gateway = mock_model(Spree::PaymentMethod,
- :payment_profiles_supported? => true,
- :authorize => @success_response,
- :purchase => @success_response,
- :capture => @success_response,
- :void => @success_response,
- :credit => @success_response,
- :environment => 'test'
- )
-
- @payment.stub :payment_method => @payment_gateway
- end
-
- context "#can_capture?" do
- it "should be true if payment state is pending" do
- payment = mock_model(Spree::Payment, :state => 'pending', :created_at => Time.now)
- credit_card.can_capture?(payment).should be_true
- end
- end
-
- context "when transaction is more than 12 hours old" do
- let(:payment) { mock_model(Spree::Payment, :state => "completed",
- :created_at => Time.now - 14.hours,
- :amount => 99.00,
- :credit_allowed => 100.00,
- :order => mock_model(Spree::Order, :payment_state => 'credit_owed')) }
-
- context "#can_credit?" do
-
- it "should be true when payment state is 'completed' and order payment_state is 'credit_owed' and credit_allowed is greater than amount" do
- credit_card.can_credit?(payment).should be_true
- end
-
- it "should be false when order payment_state is not 'credit_owed'" do
- payment.order.stub(:payment_state => 'paid')
- credit_card.can_credit?(payment).should be_false
- end
-
- it "should be false when credit_allowed is zero" do
- payment.stub(:credit_allowed => 0)
- credit_card.can_credit?(payment).should be_false
- end
-
- (PAYMENT_STATES - ['completed']).each do |state|
- it "should be false if payment state is #{state}" do
- payment.stub :state => state
- credit_card.can_credit?(payment).should be_false
- end
- end
-
- end
-
- context "#can_void?" do
- (PAYMENT_STATES - ['void']).each do |state|
- it "should be true if payment state is #{state}" do
- payment.stub :state => state
- payment.stub :void? => false
- credit_card.can_void?(payment).should be_true
- end
- end
-
- it "should be valse if payment state is void" do
- payment.stub :state => 'void'
- credit_card.can_void?(payment).should be_false
- end
- end
- end
-
- context "when transaction is less than 12 hours old" do
- let(:payment) { mock_model(Spree::Payment, :state => 'completed') }
-
- context "#can_void?" do
- (PAYMENT_STATES - ['void']).each do |state|
- it "should be true if payment state is #{state}" do
- payment.stub :state => state
- credit_card.can_void?(payment).should be_true
- end
- end
-
- it "should be false if payment state is void" do
- payment.stub :state => 'void'
- credit_card.can_void?(payment).should be_false
- end
-
- end
- end
-
- context "#valid?" do
- it "should validate presence of number" do
- credit_card.attributes = valid_credit_card_attributes.except(:number)
- credit_card.should_not be_valid
- credit_card.errors[:number].should == ["can't be blank"]
- end
-
- it "should validate presence of security code" do
- credit_card.attributes = valid_credit_card_attributes.except(:verification_value)
- credit_card.should_not be_valid
- credit_card.errors[:verification_value].should == ["can't be blank"]
- end
-
- it "should only validate on create" do
- credit_card.attributes = valid_credit_card_attributes
- credit_card.save
- credit_card.should be_valid
- end
- end
-
- context "#save" do
- before do
- credit_card.attributes = valid_credit_card_attributes
- credit_card.save!
- end
-
- let!(:persisted_card) { Spree::CreditCard.find(credit_card.id) }
-
- it "should not actually store the number" do
- persisted_card.number.should be_blank
- end
-
- it "should not actually store the security code" do
- persisted_card.verification_value.should be_blank
- end
- end
-
- context "#spree_cc_type" do
- before do
- credit_card.attributes = valid_credit_card_attributes
- end
-
- context "in development mode" do
- before do
- stub_rails_env("production")
- end
-
- it "should return visa" do
- credit_card.save
- credit_card.spree_cc_type.should == "visa"
- end
- end
-
- context "in production mode" do
- before do
- stub_rails_env("production")
- end
-
- it "should return the actual cc_type for a valid number" do
- credit_card.number = "378282246310005"
- credit_card.save
- credit_card.spree_cc_type.should == "american_express"
- end
- end
- end
-
- context "#set_card_type" do
- before :each do
- stub_rails_env("production")
- credit_card.attributes = valid_credit_card_attributes
- end
-
- it "stores the credit card type after validation" do
- credit_card.number = "6011000990139424"
- credit_card.save
- credit_card.spree_cc_type.should == "discover"
- end
-
- it "does not overwrite the credit card type when loaded and saved" do
- credit_card.number = "5105105105105100"
- credit_card.save
- credit_card.number = "XXXXXXXXXXXX5100"
- credit_card.save
- credit_card.spree_cc_type.should == "master"
- end
- end
-
- context "#number=" do
- it "should strip non-numeric characters from card input" do
- credit_card.number = "6011000990139424"
- credit_card.number.should == "6011000990139424"
-
- credit_card.number = " 6011-0009-9013-9424 "
- credit_card.number.should == "6011000990139424"
- end
-
- it "should not raise an exception on non-string input" do
- credit_card.number = Hash.new
- credit_card.number.should be_nil
- end
- end
-
- context "#associations" do
- it "should be able to access its payments" do
- lambda { credit_card.payments.all }.should_not raise_error ActiveRecord::StatementInvalid
- end
- end
-end
-
diff --git a/core/spec/models/image_spec.rb b/core/spec/models/image_spec.rb
deleted file mode 100644
index 886dcbaf72c..00000000000
--- a/core/spec/models/image_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Image do
-
-end
diff --git a/core/spec/models/inventory_unit_spec.rb b/core/spec/models/inventory_unit_spec.rb
deleted file mode 100644
index a6a08ece3d4..00000000000
--- a/core/spec/models/inventory_unit_spec.rb
+++ /dev/null
@@ -1,315 +0,0 @@
-require 'spec_helper'
-
-describe Spree::InventoryUnit do
- before(:each) do
- reset_spree_preferences
- end
-
- let(:variant) { mock_model(Spree::Variant, :on_hand => 95, :on_demand => false) }
- let(:line_item) { mock_model(Spree::LineItem, :variant => variant, :quantity => 5) }
- let(:order) { mock_model(Spree::Order, :line_items => [line_item], :inventory_units => [], :shipments => mock('shipments'), :completed? => true) }
-
- context "#assign_opening_inventory" do
- context "when order is complete" do
-
- it "should increase inventory" do
- Spree::InventoryUnit.should_receive(:increase).with(order, variant, 5).and_return([])
- Spree::InventoryUnit.assign_opening_inventory(order)
- end
-
- end
-
- context "when order is not complete" do
- before { order.stub(:completed?).and_return(false) }
-
- it "should not do anything" do
- Spree::InventoryUnit.should_not_receive(:increase)
- Spree::InventoryUnit.assign_opening_inventory(order).should == []
- end
-
- end
- end
-
- context "#increase" do
- context "when :track_inventory_levels is true" do
- before do
- Spree::Config.set :track_inventory_levels => true
- Spree::InventoryUnit.stub(:create_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_receive(:decrement!).with(:count_on_hand, 5)
- Spree::InventoryUnit.increase(order, variant, 5)
- end
-
- end
-
- context "when :track_inventory_levels is false" do
- before do
- Spree::Config.set :track_inventory_levels => false
- Spree::InventoryUnit.stub(:create_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_not_receive(:decrement!)
- Spree::InventoryUnit.increase(order, variant, 5)
- end
-
- end
-
- context "when on_demand is true" do
- before do
- variant.stub(:on_demand).and_return(true)
- Spree::InventoryUnit.stub(:create_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_not_receive(:decrement!)
- Spree::InventoryUnit.increase(order, variant, 5)
- end
-
- end
-
- context "when :create_inventory_units is true" do
- before do
- Spree::Config.set :create_inventory_units => true
- variant.stub(:decrement!)
- end
-
- it "should create units" do
- Spree::InventoryUnit.should_receive(:create_units)
- Spree::InventoryUnit.increase(order, variant, 5)
- end
-
- end
-
- context "when :create_inventory_units is false" do
- before do
- Spree::Config.set :create_inventory_units => false
- variant.stub(:decrement!)
- end
-
- it "should not create units" do
- Spree::InventoryUnit.should_not_receive(:create_units)
- Spree::InventoryUnit.increase(order, variant, 5)
- end
-
- end
-
- end
-
- context "#decrease" do
- context "when :track_inventory_levels is true" do
- before do
- Spree::Config.set :track_inventory_levels => true
- Spree::InventoryUnit.stub(:destroy_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_receive(:increment!).with(:count_on_hand, 5)
- Spree::InventoryUnit.decrease(order, variant, 5)
- end
-
- end
-
- context "when :track_inventory_levels is false" do
- before do
- Spree::Config.set :track_inventory_levels => false
- Spree::InventoryUnit.stub(:destroy_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_not_receive(:increment!)
- Spree::InventoryUnit.decrease(order, variant, 5)
- end
-
- end
-
- context "when on_demand is true" do
- before do
- variant.stub(:on_demand).and_return(true)
- Spree::InventoryUnit.stub(:destroy_units)
- end
-
- it "should decrement count_on_hand" do
- variant.should_not_receive(:increment!)
- Spree::InventoryUnit.decrease(order, variant, 5)
- end
-
- end
-
- context "when :create_inventory_units is true" do
- before do
- Spree::Config.set :create_inventory_units => true
- variant.stub(:increment!)
- end
-
- it "should destroy units" do
- Spree::InventoryUnit.should_receive(:destroy_units).with(order, variant, 5)
- Spree::InventoryUnit.decrease(order, variant, 5)
- end
-
- end
-
- context "when :create_inventory_units is false" do
- before do
- Spree::Config.set :create_inventory_units => false
- variant.stub(:increment!)
- end
-
- it "should destroy units" do
- Spree::InventoryUnit.should_not_receive(:destroy_units)
- Spree::InventoryUnit.decrease(order, variant, 5)
- end
-
- end
-
- end
-
- context "#determine_backorder" do
- context "when :track_inventory_levels is true" do
- before { Spree::Config.set :create_inventory_units => true }
-
- context "and all units are in stock" do
- it "should return zero back orders" do
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 0
- end
- end
-
- context "and partial units are in stock" do
- before { variant.stub(:on_hand).and_return(2) }
-
- it "should return correct back order amount" do
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 3
- end
- end
-
- context "and zero units are in stock" do
- before { variant.stub(:on_hand).and_return(0) }
-
- it "should return correct back order amount" do
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 5
- end
- end
-
- context "and less than zero units are in stock" do
- before { variant.stub(:on_hand).and_return(-9) }
-
- it "should return entire amount as back order" do
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 5
- end
- end
- end
-
- context "when :track_inventory_levels is false" do
- before { Spree::Config.set :track_inventory_levels => false }
-
- it "should return zero back orders" do
- variant.stub(:on_hand).and_return(nil)
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 0
- end
- end
-
- context "when :on_demand is true" do
- before { variant.stub(:on_demand).and_return(true) }
-
- it "should return zero back orders" do
- variant.stub(:on_hand).and_return(nil)
- Spree::InventoryUnit.determine_backorder(order, variant, 5).should == 0
- end
- end
-
- end
-
- context "#create_units" do
- let(:shipment) { mock_model(Spree::Shipment) }
- before { order.shipments.stub :detect => shipment }
-
- context "when :allow_backorders is true" do
- before { Spree::Config.set :allow_backorders => true }
-
- it "should create both sold and backordered units" do
- order.inventory_units.should_receive(:create).with({:variant => variant, :state => "sold", :shipment => shipment}, :without_protection => true).exactly(2).times
- order.inventory_units.should_receive(:create).with({:variant => variant, :state => "backordered", :shipment => shipment}, :without_protection => true).exactly(3).times
- Spree::InventoryUnit.create_units(order, variant, 2, 3)
- end
-
- end
-
- context "when :allow_backorders is false" do
- before { Spree::Config.set :allow_backorders => false }
-
- it "should create sold items" do
- order.inventory_units.should_receive(:create).with({:variant => variant, :state => "sold", :shipment => shipment}, :without_protection => true).exactly(2).times
- Spree::InventoryUnit.create_units(order, variant, 2, 0)
- end
-
- end
-
- end
-
- context "#destroy_units" do
- before { order.stub(:inventory_units => [mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => "sold")]) }
-
- it "should call destroy correct number of units" do
- order.inventory_units.each { |unit| unit.should_receive(:destroy) }
- Spree::InventoryUnit.destroy_units(order, variant, 1)
- end
-
- context "when inventory_units contains backorders" do
- before { order.stub(:inventory_units => [ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'backordered'),
- mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'sold'),
- mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'backordered') ]) }
-
- it "should destroy backordered units first" do
- order.inventory_units[0].should_receive(:destroy)
- order.inventory_units[1].should_not_receive(:destroy)
- order.inventory_units[2].should_receive(:destroy)
- Spree::InventoryUnit.destroy_units(order, variant, 2)
- end
- end
-
- context "when inventory_units contains sold and shipped" do
- before { order.stub(:inventory_units => [ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'shipped'),
- mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'sold') ]) }
- # Regression test for #1652
- it "should not destroy shipped" do
- order.inventory_units[0].should_not_receive(:destroy)
- order.inventory_units[1].should_receive(:destroy)
- Spree::InventoryUnit.destroy_units(order, variant, 1)
- end
- end
- end
-
- context "return!" do
- let(:inventory_unit) { Spree::InventoryUnit.create({:state => "shipped", :variant => mock_model(Spree::Variant, :on_hand => 95, :on_demand => false)}, :without_protection => true) }
-
- it "should update on_hand for variant" do
- inventory_unit.variant.should_receive(:on_hand=).with(96)
- inventory_unit.variant.should_receive(:save)
- inventory_unit.return!
- end
-
- # Regression test for #2074
- context "with inventory tracking disabled" do
- before { Spree::Config[:track_inventory_levels] = false }
-
- it "does not update on_hand for variant" do
- inventory_unit.variant.should_not_receive(:on_hand=).with(96)
- inventory_unit.variant.should_not_receive(:save)
- inventory_unit.return!
- end
- end
- context "when on_demand is true" do
- before { inventory_unit.variant.stub(:on_demand).and_return(true) }
-
- it "does not update on_hand for variant" do
- inventory_unit.variant.should_not_receive(:on_hand=).with(96)
- inventory_unit.variant.should_not_receive(:save)
- inventory_unit.return!
- end
- end
-
- end
-end
-
diff --git a/core/spec/models/line_item_spec.rb b/core/spec/models/line_item_spec.rb
deleted file mode 100644
index fa9c377fa0d..00000000000
--- a/core/spec/models/line_item_spec.rb
+++ /dev/null
@@ -1,262 +0,0 @@
-require 'spec_helper'
-
-describe Spree::LineItem do
- before(:each) do
- reset_spree_preferences
- end
-
- let(:variant) { mock_model(Spree::Variant, :count_on_hand => 95, :price => 9.99) }
- let(:line_item) { Spree::LineItem.new(:quantity => 5) }
- let(:order) do
- shipments = mock(:shipments, :reduce => 0)
- mock_model(Spree::Order, :line_items => [line_item],
- :inventory_units => [],
- :shipments => shipments,
- :completed? => true,
- :update! => true)
- end
-
- before do
- line_item.stub(:order => order, :variant => variant, :new_record? => false)
- variant.stub(:currency => "USD")
- Spree::Config.set :allow_backorders => true
- end
-
- context '#save' do
- it 'should update inventory, totals, and tax' do
- Spree::InventoryUnit.stub(:increase)
- line_item.should_receive(:update_inventory)
- # Regression check for #1481
- order.should_receive(:create_tax_charge!)
- order.should_receive(:update!)
- line_item.save
- end
-
- context 'when order#completed? is true' do
- # We don't care about this method for these tests
- before { line_item.stub(:update_order) }
-
- context 'and line_item is a new record' do
- before { line_item.stub(:new_record? => true) }
-
- it 'should increase inventory' do
- Spree::InventoryUnit.stub(:increase)
- Spree::InventoryUnit.should_receive(:increase).with(order, variant, 5)
- # We don't care about this method for this test
- line_item.stub(:update_order)
- line_item.save
- end
- end
-
- context 'and quantity is increased' do
- before { line_item.stub(:changed_attributes => {'quantity' => 5}, :quantity => 6) }
-
- it 'should increase inventory' do
- Spree::InventoryUnit.should_not_receive(:decrease)
- Spree::InventoryUnit.should_receive(:increase).with(order, variant, 1)
- line_item.save
- end
- end
-
- context 'and quantity is decreased' do
- before { line_item.stub(:changed_attributes => {'quantity' => 5}, :quantity => 3) }
-
- it 'should decrease inventory' do
- Spree::InventoryUnit.should_not_receive(:increase)
- Spree::InventoryUnit.should_receive(:decrease).with(order, variant, 2)
- line_item.save
- end
- end
-
- context 'and quantity is not changed' do
-
- it 'should not manager inventory' do
- Spree::InventoryUnit.should_not_receive(:increase)
- Spree::InventoryUnit.should_not_receive(:decrease)
- line_item.save
- end
- end
- end
-
- context 'when order#completed? is false' do
- before do
- order.stub(:completed? => false)
- # We don't care about this method for this test
- line_item.stub(:update_order)
- end
-
- it 'should not manage inventory' do
- Spree::InventoryUnit.should_not_receive(:increase)
- Spree::InventoryUnit.should_not_receive(:decrease)
- line_item.save
- end
- end
- end
-
- context '#destroy' do
- # Regression test for #1481
- it "applies tax adjustments" do
- # We don't care about this method for this test
- line_item.stub(:remove_inventory)
- order.should_receive(:create_tax_charge!)
- line_item.destroy
- end
-
- context 'when order.completed? is true' do
- it 'should remove inventory' do
- # We don't care about this method for this test
- line_item.stub(:update_order)
- Spree::InventoryUnit.should_receive(:decrease).with(order, variant, 5)
- line_item.destroy
- end
- end
-
- context 'when order.completed? is false' do
- before { order.stub(:completed? => false) }
-
- it 'should not remove inventory' do
- Spree::InventoryUnit.should_not_receive(:decrease)
- end
- end
-
- context 'with inventory units' do
- let(:inventory_unit) { mock_model(Spree::InventoryUnit, :variant_id => variant.id, :shipped? => false) }
- before do
- order.stub(:inventory_units => [inventory_unit])
- line_item.stub(:order => order, :variant_id => variant.id)
- end
-
- it 'should allow destroy when no units have shipped' do
- # We don't care about this method for this test
- line_item.stub(:update_order)
- line_item.should_receive(:remove_inventory)
- line_item.destroy.should be_true
- end
-
- it 'should not allow destroy when units have shipped' do
- inventory_unit.stub(:shipped? => true)
- line_item.should_not_receive(:remove_inventory)
- line_item.destroy.should be_false
- end
- end
- end
-
- context '(in)sufficient_stock?' do
- context 'when backordering is disabled' do
- before { Spree::Config.set :allow_backorders => false }
-
- it 'should report insufficient stock when variant is out of stock' do
- line_item.stub_chain :variant, :on_hand => 0
- line_item.insufficient_stock?.should be_true
- line_item.sufficient_stock?.should be_false
- end
-
- it 'should report insufficient stock when variant has less on_hand that line_item quantity' do
- line_item.stub_chain :variant, :on_hand => 3
- line_item.insufficient_stock?.should be_true
- line_item.sufficient_stock?.should be_false
- end
-
- it 'should report sufficient stock when variant has enough on_hand' do
- line_item.stub_chain :variant, :on_hand => 300
- line_item.insufficient_stock?.should be_false
- line_item.sufficient_stock?.should be_true
- end
-
- context 'when line item has been saved' do
- before { line_item.stub(:new_record? => false) }
-
- it 'should report sufficient stock when reducing purchased quantity' do
- line_item.stub(:changed_attributes => {'quantity' => 6}, :quantity => 5)
- line_item.stub_chain :variant, :on_hand => 0
- line_item.insufficient_stock?.should be_false
- line_item.sufficient_stock?.should be_true
- end
-
- it 'should report sufficient stock when increasing purchased quantity and variant has enough on_hand' do
- line_item.stub(:changed_attributes => {'quantity' => 5}, :quantity => 6)
- line_item.stub_chain :variant, :on_hand => 1
- line_item.insufficient_stock?.should be_false
- line_item.sufficient_stock?.should be_true
- end
-
- it 'should report insufficient stock when increasing purchased quantity and new units is more than variant on_hand' do
- line_item.stub(:changed_attributes => {'quantity' => 5}, :quantity => 7)
- line_item.stub_chain :variant, :on_hand => 1
- line_item.insufficient_stock?.should be_true
- line_item.sufficient_stock?.should be_false
- end
- end
- end
-
- context 'when backordering is enabled' do
- before { Spree::Config.set :allow_backorders => true }
-
- it 'should report sufficient stock regardless of on_hand value' do
- [-99,0,99].each do |i|
- line_item.stub_chain :variant, :on_hand => i
- line_item.insufficient_stock?.should be_false
- line_item.sufficient_stock?.should be_true
- end
- end
- end
- end
-
- context 'after shipment made' do
- before do
- shipping_method = mock_model(Spree::ShippingMethod, :calculator => mock(:calculator))
- shipment = Spree::Shipment.new :order => order, :shipping_method => shipping_method
- shipment.stub(:state => 'shipped')
- shipped_inventory_units = 5.times.map { Spree::InventoryUnit.new({ :variant => line_item.variant, :state => 'shipped' }, :without_protection => true) }
- unshipped_inventory_units = 2.times.map { Spree::InventoryUnit.new({ :variant => line_item.variant, :state => 'sold' }, :without_protection => true) }
- inventory_units = shipped_inventory_units + unshipped_inventory_units
- order.stub(:shipments => [shipment])
- shipment.stub(:inventory_units => inventory_units)
- inventory_units.stub(:shipped => shipped_inventory_units)
- shipped_inventory_units.stub(:where).with(:variant_id => line_item.variant_id).and_return(shipped_inventory_units)
- # We don't care about this method for these test
- line_item.stub(:update_order)
- end
-
- it 'should not allow quantity to be adjusted lower than already shipped units' do
- line_item.quantity = 4
- line_item.save.should be_false
- line_item.errors.size.should == 1
- end
-
- it "should allow quantity to be adjusted higher than already shipped units" do
- line_item.quantity = 6
- line_item.save.should be_true
- end
- end
-
- context "destroying" do
- # Regression test for #1233
- it "removes related adjustments" do
- line_item = create(:line_item)
- adjustment = line_item.adjustments.create(:amount => 10, :label => "test")
- line_item.destroy
- lambda { adjustment.reload }.should raise_error(ActiveRecord::RecordNotFound)
- end
- end
-
- describe '.currency' do
- it 'returns the globally configured currency' do
- line_item.currency == 'USD'
- end
- end
-
- describe ".money" do
- before { line_item.price = 3.50 }
- it "returns a Spree::Money representing the total for this line item" do
- line_item.money.to_s.should == "$17.50"
- end
- end
-
- describe '.single_money' do
- before { line_item.price = 3.50 }
- it "returns a Spree::Money representing the price for one variant" do
- line_item.single_money.to_s.should == "$3.50"
- end
- end
-end
diff --git a/core/spec/models/mail_method_spec.rb b/core/spec/models/mail_method_spec.rb
deleted file mode 100644
index 1659d699f8f..00000000000
--- a/core/spec/models/mail_method_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-describe Spree::MailMethod do
- context "current" do
- it "should return the first active mail method corresponding to the current environment" do
- method = Spree::MailMethod.create(:environment => "test")
- Spree::MailMethod.current.should == method
- end
- end
-
- context "valid?" do
- it "should be false when missing an environment value" do
- method = Spree::MailMethod.new
- method.valid?.should be_false
- end
- it "should be valid if it has an environment" do
- method = Spree::MailMethod.new(:environment => "foo")
- method.valid?.should be_true
- end
- end
-end
diff --git a/core/spec/models/order/address_spec.rb b/core/spec/models/order/address_spec.rb
deleted file mode 100644
index 2efbc20be6d..00000000000
--- a/core/spec/models/order/address_spec.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Order do
- let(:order) { Spree::Order.new }
-
- context 'validation' do
- context "when @use_billing is populated" do
- before do
- order.bill_address = stub_model(Spree::Address)
- order.ship_address = nil
- end
-
- context "with true" do
- before { order.use_billing = true }
-
- it "clones the bill address to the ship address" do
- order.valid?
- order.ship_address.should == order.bill_address
- end
- end
-
- context "with 'true'" do
- before { order.use_billing = 'true' }
-
- it "clones the bill address to the shipping" do
- order.valid?
- order.ship_address.should == order.bill_address
- end
- end
-
- context "with '1'" do
- before { order.use_billing = '1' }
-
- it "clones the bill address to the shipping" do
- order.valid?
- order.ship_address.should == order.bill_address
- end
- end
-
- context "with something other than a 'truthful' value" do
- before { order.use_billing = '0' }
-
- it "does not clone the bill address to the shipping" do
- order.valid?
- order.ship_address.should be_nil
- end
- end
- end
- end
-end
diff --git a/core/spec/models/order/adjustments_spec.rb b/core/spec/models/order/adjustments_spec.rb
deleted file mode 100644
index 0ed7e00fc47..00000000000
--- a/core/spec/models/order/adjustments_spec.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-require 'spec_helper'
-describe Spree::Order do
- let(:order) { Spree::Order.new }
-
- context "clear_adjustments" do
- it "should destroy all previous tax adjustments" do
- adjustment = stub
- adjustment.should_receive :destroy
-
- order.stub_chain :adjustments, :tax => [adjustment]
- order.clear_adjustments!
- end
-
- it "should destroy all price adjustments" do
- adjustment = stub
- adjustment.should_receive :destroy
-
- order.stub :price_adjustments => [adjustment]
- order.clear_adjustments!
- end
- end
-
- context "totaling adjustments" do
- let(:adjustment1) { mock_model(Spree::Adjustment, :amount => 5) }
- let(:adjustment2) { mock_model(Spree::Adjustment, :amount => 10) }
-
- context "#ship_total" do
- it "should return the correct amount" do
- order.stub_chain :adjustments, :shipping => [adjustment1, adjustment2]
- order.ship_total.should == 15
- end
- end
-
- context "#tax_total" do
- it "should return the correct amount" do
- order.stub_chain :adjustments, :tax => [adjustment1, adjustment2]
- order.tax_total.should == 15
- end
- end
- end
-
-
- context "#price_adjustment_totals" do
- before { @order = Spree::Order.create! }
-
-
- context "when there are no price adjustments" do
- before { @order.stub :price_adjustments => [] }
-
- it "should return an empty hash" do
- @order.price_adjustment_totals.should == {}
- end
- end
-
- context "when there are two adjustments with different labels" do
- let(:adj1) { mock_model Spree::Adjustment, :amount => 10, :label => "Foo" }
- let(:adj2) { mock_model Spree::Adjustment, :amount => 20, :label => "Bar" }
-
- before do
- @order.stub :price_adjustments => [adj1, adj2]
- end
-
- it "should return exactly two totals" do
- @order.price_adjustment_totals.size.should == 2
- end
-
- it "should return the correct totals" do
- @order.price_adjustment_totals["Foo"].should == 10
- @order.price_adjustment_totals["Bar"].should == 20
- end
- end
-
- context "when there are two adjustments with one label and a single adjustment with another" do
- let(:adj1) { mock_model Spree::Adjustment, :amount => 10, :label => "Foo" }
- let(:adj2) { mock_model Spree::Adjustment, :amount => 20, :label => "Bar" }
- let(:adj3) { mock_model Spree::Adjustment, :amount => 40, :label => "Bar" }
-
- before do
- @order.stub :price_adjustments => [adj1, adj2, adj3]
- end
-
- it "should return exactly two totals" do
- @order.price_adjustment_totals.size.should == 2
- end
- it "should return the correct totals" do
- @order.price_adjustment_totals["Foo"].should == 10
- @order.price_adjustment_totals["Bar"].should == 60
- end
- end
- end
-
- context "#price_adjustments" do
- before do
- @order = Spree::Order.create!
- @order.stub :line_items => [line_item1, line_item2]
- end
-
- let(:line_item1) { create(:line_item, :order => @order) }
- let(:line_item2) { create(:line_item, :order => @order) }
-
- context "when there are no line item adjustments" do
- it "should return nothing if line items have no adjustments" do
- @order.price_adjustments.should be_empty
- end
- end
-
- context "when only one line item has adjustments" do
- before do
- @adj1 = line_item1.adjustments.create({:amount => 2, :source => line_item1, :label => "VAT 5%"}, :without_protection => true)
- @adj2 = line_item1.adjustments.create({:amount => 5, :source => line_item1, :label => "VAT 10%"}, :without_protection => true)
- end
-
- it "should return the adjustments for that line item" do
- @order.price_adjustments.should =~ [@adj1, @adj2]
- end
- end
-
- context "when more than one line item has adjustments" do
- before do
- @adj1 = line_item1.adjustments.create({:amount => 2, :source => line_item1, :label => "VAT 5%"}, :without_protection => true)
- @adj2 = line_item2.adjustments.create({:amount => 5, :source => line_item2, :label => "VAT 10%"}, :without_protection => true)
- end
-
- it "should return the adjustments for each line item" do
- @order.price_adjustments.should == [@adj1, @adj2]
- end
- end
- end
-end
-
diff --git a/core/spec/models/order/callbacks_spec.rb b/core/spec/models/order/callbacks_spec.rb
deleted file mode 100644
index f7f676a3257..00000000000
--- a/core/spec/models/order/callbacks_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Order do
- let(:order) { stub_model(Spree::Order) }
- before do
- Spree::Order.define_state_machine!
- end
-
- context "validations" do
- context "email validation" do
- # Regression test for #1238
- it "o'brien@gmail.com is a valid email address" do
- order.state = 'address'
- order.email = "o'brien@gmail.com"
- order.should be_valid
- end
- end
- end
-
- context "#save" do
- context "when associated with a registered user" do
- let(:user) { stub(:user, :email => "test@example.com") }
-
- before do
- order.stub :user => user
- end
-
- it "should assign the email address of the user" do
- order.run_callbacks(:create)
- order.email.should == user.email
- end
- end
- end
-
- context "in the cart state" do
- it "should not validate email address" do
- order.state = "cart"
- order.email = nil
- order.should be_valid
- end
- end
-end
diff --git a/core/spec/models/order/checkout_spec.rb b/core/spec/models/order/checkout_spec.rb
deleted file mode 100644
index a18f7ed2213..00000000000
--- a/core/spec/models/order/checkout_spec.rb
+++ /dev/null
@@ -1,207 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Order do
- let(:order) { Spree::Order.new }
-
- context "with default state machine" do
- it "has the following transitions" do
- transitions = [
- { :address => :delivery },
- { :delivery => :payment },
- { :payment => :confirm },
- { :confirm => :complete },
- { :payment => :complete },
- { :delivery => :complete }
- ]
- transitions.each do |transition|
- transition = Spree::Order.find_transition(:from => transition.keys.first, :to => transition.values.first)
- transition.should_not be_nil
- end
- end
-
- it "does not have a transition from delivery to confirm" do
- transition = Spree::Order.find_transition(:from => :delivery, :to => :confirm)
- transition.should be_nil
- end
-
- context "#checkout_steps" do
- context "when confirmation not required" do
- before do
- order.stub :confirmation_required? => false
- order.stub :payment_required? => true
- end
-
- specify do
- order.checkout_steps.should == %w(address delivery payment)
- end
- end
-
- context "when confirmation required" do
- before do
- order.stub :confirmation_required? => true
- order.stub :payment_required? => true
- end
-
- specify do
- order.checkout_steps.should == %w(address delivery payment confirm)
- end
- end
-
- context "when payment not required" do
- before { order.stub :payment_required? => false }
- specify do
- order.checkout_steps.should == %w(address delivery complete)
- end
- end
-
- context "when payment required" do
- before { order.stub :payment_required? => true }
- specify do
- order.checkout_steps.should == %w(address delivery payment)
- end
- end
- end
-
- it "starts out at cart" do
- order.state.should == "cart"
- end
-
- it "transitions to address" do
- order.next!
- order.state.should == "address"
- end
-
- context "from address" do
- before do
- order.state = 'address'
- end
-
- it "transitions to delivery" do
- order.stub(:has_available_payment)
- order.next!
- order.state.should == "delivery"
- end
- end
-
- context "from delivery" do
- before do
- order.state = 'delivery'
- end
-
- context "with payment required" do
- before do
- order.stub :payment_required? => true
- end
-
- it "transitions to payment" do
- order.next!
- order.state.should == 'payment'
- end
- end
-
- context "without payment required" do
- before do
- order.stub :payment_required? => false
- end
-
- it "transitions to complete" do
- order.next!
- order.state.should == "complete"
- end
- end
- end
-
- context "from payment" do
- before do
- order.state = 'payment'
- end
-
- context "with confirmation required" do
- before do
- order.stub :confirmation_required? => true
- end
-
- it "transitions to confirm" do
- order.next!
- order.state.should == "confirm"
- end
- end
-
- context "without confirmation required" do
- before do
- order.stub :confirmation_required? => false
- order.stub :payment_required? => true
- order.stub :paid? => true
- end
-
- it "transitions to complete" do
- order.should_receive(:process_payments!).once
- order.next!
- order.state.should == "complete"
- end
- end
-
- # Regression test for #2028
- context "when payment is not required" do
- before do
- order.stub :payment_required? => false
- end
-
- it "does not call process payments" do
- order.should_not_receive(:process_payments!)
- order.next!
- order.state.should == "complete"
- end
- end
- end
- end
-
- context "subclassed order" do
- # This causes another test above to fail, but fixing this test should make
- # the other test pass
- class SubclassedOrder < Spree::Order
- checkout_flow do
- go_to_state :payment
- go_to_state :complete
- end
- end
-
- it "should only call default transitions once when checkout_flow is redefined" do
- order = SubclassedOrder.new
- order.stub :payment_required? => true
- order.should_receive(:process_payments!).once
- order.state = "payment"
- order.next!
- order.state.should == "complete"
- end
- end
-
- context "re-define checkout flow" do
- before do
- @old_checkout_flow = Spree::Order.checkout_flow
- Spree::Order.class_eval do
- checkout_flow do
- go_to_state :payment
- go_to_state :complete
- end
- end
- end
-
- after do
- Spree::Order.checkout_flow = @old_checkout_flow
- end
-
- it "should not keep old event transitions when checkout_flow is redefined" do
- Spree::Order.next_event_transitions.should == [{:cart=>:payment}, {:payment=>:complete}]
- end
-
- it "should not keep old events when checkout_flow is redefined" do
- state_machine = Spree::Order.state_machine
- state_machine.states.any? { |s| s.name == :address }.should be_false
- known_states = state_machine.events[:next].branches.map(&:known_states).flatten
- known_states.should_not include(:address)
- known_states.should_not include(:delivery)
- known_states.should_not include(:confirm)
- end
- end
-end
diff --git a/core/spec/models/order/payment_spec.rb b/core/spec/models/order/payment_spec.rb
deleted file mode 100644
index ef20ca31572..00000000000
--- a/core/spec/models/order/payment_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-module Spree
- describe Order do
- let(:order) { stub_model(Order) }
- let(:updater) { Spree::OrderUpdater.new(order) }
-
- before do
- # So that Payment#purchase! is called during processing
- Spree::Config[:auto_capture] = true
-
- order.stub_chain(:line_items, :empty?).and_return(false)
- order.stub :total => 100
- end
-
- it 'processes all payments' do
- payment_1 = create(:payment, :amount => 50)
- payment_2 = create(:payment, :amount => 50)
- order.stub(:pending_payments).and_return([payment_1, payment_2])
-
- order.process_payments!
- updater.update_payment_state
- order.payment_state.should == 'paid'
-
- payment_1.should be_completed
- payment_2.should be_completed
- end
-
- it 'does not go over total for order' do
- payment_1 = create(:payment, :amount => 50)
- payment_2 = create(:payment, :amount => 50)
- payment_3 = create(:payment, :amount => 50)
- order.stub(:pending_payments).and_return([payment_1, payment_2, payment_3])
-
- order.process_payments!
- updater.update_payment_state
- order.payment_state.should == 'paid'
-
- payment_1.should be_completed
- payment_2.should be_completed
- payment_3.should be_pending
- end
-
- it "does not use failed payments" do
- payment_1 = create(:payment, :amount => 50)
- payment_2 = create(:payment, :amount => 50, :state => 'failed')
- order.stub(:pending_payments).and_return([payment_1])
-
- payment_2.should_not_receive(:process!)
-
- order.process_payments!
- end
- end
-end
diff --git a/core/spec/models/order/state_machine_spec.rb b/core/spec/models/order/state_machine_spec.rb
deleted file mode 100644
index 3f9f34db2e5..00000000000
--- a/core/spec/models/order/state_machine_spec.rb
+++ /dev/null
@@ -1,214 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Order do
- let(:order) { Spree::Order.new }
- before do
- # Ensure state machine has been re-defined correctly
- Spree::Order.define_state_machine!
- # We don't care about this validation here
- order.stub(:require_email)
- end
-
- context "#next!" do
- context "when current state is confirm" do
- before { order.state = "confirm" }
- it "should finalize order when transitioning to complete state" do
- order.run_callbacks(:create)
- order.should_receive(:finalize!)
- order.next!
- end
-
- context "when credit card payment fails" do
- before do
- order.stub(:process_payments!).and_raise(Spree::Core::GatewayError)
- order.stub :payment_required? => true
- end
-
- context "when not configured to allow failed payments" do
- before do
- Spree::Config.set :allow_checkout_on_gateway_error => false
- end
-
- it "should not complete the order" do
- order.next
- order.state.should == "confirm"
- end
- end
-
- context "when configured to allow failed payments" do
- before do
- Spree::Config.set :allow_checkout_on_gateway_error => true
- end
-
- it "should complete the order" do
- order.stub :paid? => true
- order.next!
- order.state.should == "complete"
- end
-
- end
-
- end
- end
-
- context "when current state is address" do
- before do
- order.stub(:has_available_payment)
- order.state = "address"
- end
-
- it "adjusts tax rates when transitioning to delivery" do
- # Once because the record is being saved
- # Twice because it is transitioning to the delivery state
- Spree::TaxRate.should_receive(:adjust).twice
- order.next!
- end
- end
-
- context "when current state is delivery" do
- before do
- order.state = "delivery"
- order.stub :total => 10.0
- end
-
- context "when transitioning to payment state" do
- it "should create a shipment" do
- order.should_receive(:create_shipment!)
- order.next!
- order.state.should == 'payment'
- end
- end
- end
-
- end
-
- context "#can_cancel?" do
-
- %w(pending backorder ready).each do |shipment_state|
- it "should be true if shipment_state is #{shipment_state}" do
- order.stub :completed? => true
- order.shipment_state = shipment_state
- order.can_cancel?.should be_true
- end
- end
-
- (SHIPMENT_STATES - %w(pending backorder ready)).each do |shipment_state|
- it "should be false if shipment_state is #{shipment_state}" do
- order.stub :completed? => true
- order.shipment_state = shipment_state
- order.can_cancel?.should be_false
- end
- end
-
- end
-
- context "#cancel" do
- let!(:variant) { stub_model(Spree::Variant, :on_hand => 0) }
- let!(:inventory_units) { [stub_model(Spree::InventoryUnit, :variant => variant),
- stub_model(Spree::InventoryUnit, :variant => variant) ]}
- let!(:shipment) do
- shipment = stub_model(Spree::Shipment)
- shipment.stub :inventory_units => inventory_units
- order.stub :shipments => [shipment]
- shipment
- end
-
- before do
- order.stub :line_items => [stub_model(Spree::LineItem, :variant => variant, :quantity => 2)]
- order.line_items.stub :find_by_variant_id => order.line_items.first
-
- order.stub :completed? => true
- order.stub :allow_cancel? => true
- end
-
- it "should send a cancel email" do
- # Stub methods that cause side-effects in this test
- order.stub :has_available_shipment
- order.stub :restock_items!
- mail_message = mock "Mail::Message"
- Spree::OrderMailer.should_receive(:cancel_email).with(order).and_return mail_message
- mail_message.should_receive :deliver
- order.cancel!
- end
-
- context "restocking inventory" do
- before do
- shipment.stub(:ensure_correct_adjustment)
- shipment.stub(:update_order)
- Spree::OrderMailer.stub(:cancel_email).and_return(mail_message = stub)
- mail_message.stub :deliver
-
- order.stub :has_available_shipment
- end
-
- # Regression fix for #729
- specify do
- Spree::InventoryUnit.should_receive(:decrease).with(order, variant, 2).once
- order.cancel!
- end
- end
-
- context "resets payment state" do
- before do
- # TODO: This is ugly :(
- # Stubs methods that cause unwanted side effects in this test
- Spree::OrderMailer.stub(:cancel_email).and_return(mail_message = stub)
- mail_message.stub :deliver
- order.stub :has_available_shipment
- order.stub :restock_items!
- end
-
- context "without shipped items" do
- it "should set payment state to 'credit owed'" do
- order.cancel!
- order.payment_state.should == 'credit_owed'
- end
- end
-
- context "with shipped items" do
- before do
- order.stub :shipment_state => 'partial'
- end
-
- it "should not alter the payment state" do
- order.cancel!
- order.payment_state.should be_nil
- end
- end
- end
- end
-
-
- # Another regression test for #729
- context "#resume" do
- before do
- order.stub :email => "user@spreecommerce.com"
- order.stub :state => "canceled"
- order.stub :allow_resume? => true
-
- # Stubs method that cause unwanted side effects in this test
- order.stub :has_available_shipment
- end
-
- context "unstocks inventory" do
- let(:variant) { stub_model(Spree::Variant) }
-
- before do
- shipment = stub_model(Spree::Shipment)
- line_item = stub_model(Spree::LineItem, :variant => variant, :quantity => 2)
- order.stub :line_items => [line_item]
- order.line_items.stub :find_by_variant_id => line_item
-
- order.stub :shipments => [shipment]
- shipment.stub :inventory_units => [stub_model(Spree::InventoryUnit, :variant => variant),
- stub_model(Spree::InventoryUnit, :variant => variant) ]
- end
-
- specify do
- Spree::InventoryUnit.should_receive(:increase).with(order, variant, 2).once
- order.resume!
- end
- end
-
- end
-end
diff --git a/core/spec/models/order/tax_spec.rb b/core/spec/models/order/tax_spec.rb
deleted file mode 100644
index 0b097411bbd..00000000000
--- a/core/spec/models/order/tax_spec.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-require 'spec_helper'
-
-module Spree
- describe Order do
- let(:order) { stub_model(Spree::Order) }
-
- context "#tax_zone" do
- let(:bill_address) { Factory :address }
- let(:ship_address) { Factory :address }
- let(:order) { Spree::Order.create(:ship_address => ship_address, :bill_address => bill_address) }
- let(:zone) { Factory :zone }
-
- context "when no zones exist" do
- before { Spree::Zone.destroy_all }
-
- it "should return nil" do
- order.tax_zone.should be_nil
- end
- end
-
- context "when :tax_using_ship_address => true" do
- before { Spree::Config.set(:tax_using_ship_address => true) }
-
- it "should calculate using ship_address" do
- Spree::Zone.should_receive(:match).at_least(:once).with(ship_address)
- Spree::Zone.should_not_receive(:match).with(bill_address)
- order.tax_zone
- end
- end
-
- context "when :tax_using_ship_address => false" do
- before { Spree::Config.set(:tax_using_ship_address => false) }
-
- it "should calculate using bill_address" do
- Spree::Zone.should_receive(:match).at_least(:once).with(bill_address)
- Spree::Zone.should_not_receive(:match).with(ship_address)
- order.tax_zone
- end
- end
-
- context "when there is a default tax zone" do
- before do
- @default_zone = create(:zone, :name => "foo_zone")
- Spree::Zone.stub :default_tax => @default_zone
- end
-
- context "when there is a matching zone" do
- before { Spree::Zone.stub(:match => zone) }
-
- it "should return the matching zone" do
- order.tax_zone.should == zone
- end
- end
-
- context "when there is no matching zone" do
- before { Spree::Zone.stub(:match => nil) }
-
- it "should return the default tax zone" do
- order.tax_zone.should == @default_zone
- end
- end
- end
-
- context "when no default tax zone" do
- before { Spree::Zone.stub :default_tax => nil }
-
- context "when there is a matching zone" do
- before { Spree::Zone.stub(:match => zone) }
-
- it "should return the matching zone" do
- order.tax_zone.should == zone
- end
- end
-
- context "when there is no matching zone" do
- before { Spree::Zone.stub(:match => nil) }
-
- it "should return nil" do
- order.tax_zone.should be_nil
- end
- end
- end
- end
-
-
- context "#exclude_tax?" do
- before do
- @order = create(:order)
- @default_zone = create(:zone)
- Spree::Zone.stub :default_tax => @default_zone
- end
-
- context "when prices include tax" do
- before { Spree::Config.set(:prices_inc_tax => true) }
-
- it "should be true when tax_zone is not the same as the default" do
- @order.stub :tax_zone => create(:zone, :name => "other_zone")
- @order.exclude_tax?.should be_true
- end
-
- it "should be false when tax_zone is the same as the default" do
- @order.stub :tax_zone => @default_zone
- @order.exclude_tax?.should be_false
- end
- end
-
- context "when prices do not include tax" do
- before { Spree::Config.set(:prices_inc_tax => false) }
-
- it "should be false" do
- @order.exclude_tax?.should be_false
- end
- end
- end
- end
-end
-
-
diff --git a/core/spec/models/order/validations_spec.rb b/core/spec/models/order/validations_spec.rb
deleted file mode 100644
index f96e6bac44f..00000000000
--- a/core/spec/models/order/validations_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-module Spree
- describe Order do
- context "validations" do
- # Regression test for #2214
- it "does not return two error messages when email is blank" do
- order = Order.new
- order.stub(:require_email => true)
- order.valid?
- order.errors[:email].should == ["can't be blank"]
- end
- end
- end
-end
diff --git a/core/spec/models/order_spec.rb b/core/spec/models/order_spec.rb
deleted file mode 100644
index 1425a5115bd..00000000000
--- a/core/spec/models/order_spec.rb
+++ /dev/null
@@ -1,462 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-
-class FakeCalculator < Spree::Calculator
- def compute(computable)
- 5
- end
-end
-
-describe Spree::Order do
- before(:each) do
- reset_spree_preferences
- end
-
- let(:user) { stub_model(Spree::LegacyUser, :email => "spree@example.com") }
- let(:order) { stub_model(Spree::Order, :user => user) }
-
- before do
- Spree::LegacyUser.stub(:current => mock_model(Spree::LegacyUser, :id => 123))
- end
-
- context "#products" do
- before :each do
- @variant1 = mock_model(Spree::Variant, :product => "product1")
- @variant2 = mock_model(Spree::Variant, :product => "product2")
- @line_items = [mock_model(Spree::LineItem, :variant => @variant1, :variant_id => @variant1.id, :quantity => 1),
- mock_model(Spree::LineItem, :variant => @variant2, :variant_id => @variant2.id, :quantity => 2)]
- order.stub(:line_items => @line_items)
- end
-
- it "should return ordered products" do
- order.products.should == ['product1', 'product2']
- end
-
- it "contains?" do
- order.contains?(@variant1).should be_true
- end
-
- it "gets the quantity of a given variant" do
- order.quantity_of(@variant1).should == 1
-
- @variant3 = mock_model(Spree::Variant, :product => "product3")
- order.quantity_of(@variant3).should == 0
- end
-
- it "can find a line item matching a given variant" do
- order.find_line_item_by_variant(@variant1).should_not be_nil
- order.find_line_item_by_variant(mock_model(Spree::Variant)).should be_nil
- end
- end
-
- context "#generate_order_number" do
- it "should generate a random string" do
- order.generate_order_number.is_a?(String).should be_true
- (order.generate_order_number.to_s.length > 0).should be_true
- end
- end
-
- context "#associate_user!" do
- it "should associate a user with this order" do
- order.user = nil
- order.email = nil
- order.associate_user!(user)
- order.user.should == user
- order.email.should == user.email
- end
- end
-
- context "#create" do
- it "should assign an order number" do
- order = Spree::Order.create
- order.number.should_not be_nil
- end
- end
-
- context "#finalize!" do
- let(:order) { Spree::Order.create }
- it "should set completed_at" do
- order.should_receive(:touch).with(:completed_at)
- order.finalize!
- end
-
- it "should sell inventory units" do
- Spree::InventoryUnit.should_receive(:assign_opening_inventory).with(order)
- order.finalize!
- end
-
- it "should change the shipment state to ready if order is paid" do
- order.stub :shipping_method => mock_model(Spree::ShippingMethod, :create_adjustment => true)
- order.create_shipment!
- order.stub(:paid? => true, :complete? => true)
- order.finalize!
- order.reload # reload so we're sure the changes are persisted
- order.shipment.state.should == 'ready'
- order.shipment_state.should == 'ready'
- end
-
- after { Spree::Config.set :track_inventory_levels => true }
- it "should not sell inventory units if track_inventory_levels is false" do
- Spree::Config.set :track_inventory_levels => false
- Spree::InventoryUnit.should_not_receive(:sell_units)
- order.finalize!
- end
-
- it "should send an order confirmation email" do
- mail_message = mock "Mail::Message"
- Spree::OrderMailer.should_receive(:confirm_email).with(order).and_return mail_message
- mail_message.should_receive :deliver
- order.finalize!
- end
-
- it "should continue even if confirmation email delivery fails" do
- Spree::OrderMailer.should_receive(:confirm_email).with(order).and_raise 'send failed!'
- order.finalize!
- end
-
- it "should freeze all adjustments" do
- # Stub this method as it's called due to a callback
- # and it's irrelevant to this test
- order.stub :has_available_shipment
-
- Spree::OrderMailer.stub_chain :confirm_email, :deliver
- adjustment1 = mock_model(Spree::Adjustment, :mandatory => true)
- adjustment2 = mock_model(Spree::Adjustment, :mandatory => false)
- order.stub :adjustments => [adjustment1, adjustment2]
- adjustment1.should_receive(:update_column).with("locked", true)
- adjustment2.should_receive(:update_column).with("locked", true)
- order.finalize!
- end
-
- it "should log state event" do
- order.state_changes.should_receive(:create).exactly(3).times #order, shipment & payment state changes
- order.finalize!
- end
- end
-
- context "#process_payments!" do
- it "should process the payments" do
- order.stub(:total).and_return(10)
- payment = stub_model(Spree::Payment)
- payments = [payment]
- order.stub(:payments).and_return(payments)
- payments.first.should_receive(:process!)
- order.process_payments!
- end
- end
-
- context "#outstanding_balance" do
- it "should return positive amount when payment_total is less than total" do
- order.payment_total = 20.20
- order.total = 30.30
- order.outstanding_balance.should == 10.10
- end
- it "should return negative amount when payment_total is greater than total" do
- order.total = 8.20
- order.payment_total = 10.20
- order.outstanding_balance.should be_within(0.001).of(-2.00)
- end
-
- end
-
- context "#outstanding_balance?" do
- it "should be true when total greater than payment_total" do
- order.total = 10.10
- order.payment_total = 9.50
- order.outstanding_balance?.should be_true
- end
- it "should be true when total less than payment_total" do
- order.total = 8.25
- order.payment_total = 10.44
- order.outstanding_balance?.should be_true
- end
- it "should be false when total equals payment_total" do
- order.total = 10.10
- order.payment_total = 10.10
- order.outstanding_balance?.should be_false
- end
- end
-
- context "#complete?" do
- it "should indicate if order is complete" do
- order.completed_at = nil
- order.complete?.should be_false
-
- order.completed_at = Time.now
- order.completed?.should be_true
- end
- end
-
- context "#backordered?" do
- it "should indicate whether any units in the order are backordered" do
- order.stub_chain(:inventory_units, :backordered).and_return []
- order.backordered?.should be_false
- order.stub_chain(:inventory_units, :backordered).and_return [mock_model(Spree::InventoryUnit)]
- order.backordered?.should be_true
- end
-
- it "should always be false when inventory tracking is disabled" do
- Spree::Config.set :track_inventory_levels => false
- order.stub_chain(:inventory_units, :backordered).and_return [mock_model(Spree::InventoryUnit)]
- order.backordered?.should be_false
- end
- end
-
- context "#payment_method" do
- it "should return payment.payment_method if payment is present" do
- payments = [create(:payment)]
- payments.stub(:completed => payments)
- order.stub(:payments => payments)
- order.payment_method.should == order.payments.first.payment_method
- end
-
- it "should return the first payment method from available_payment_methods if payment is not present" do
- create(:payment_method, :environment => 'test')
- order.payment_method.should == order.available_payment_methods.first
- end
- end
-
- context "#allow_checkout?" do
- it "should be true if there are line_items in the order" do
- order.stub_chain(:line_items, :count => 1)
- order.checkout_allowed?.should be_true
- end
- it "should be false if there are no line_items in the order" do
- order.stub_chain(:line_items, :count => 0)
- order.checkout_allowed?.should be_false
- end
- end
-
- context "#item_count" do
- before do
- @order = create(:order, :user => user)
- @order.line_items = [ create(:line_item, :quantity => 2), create(:line_item, :quantity => 1) ]
- end
- it "should return the correct number of items" do
- @order.item_count.should == 3
- end
- end
-
- context "#amount" do
- before do
- @order = create(:order, :user => user)
- @order.line_items = [ create(:line_item, :price => 1.0, :quantity => 2), create(:line_item, :price => 1.0, :quantity => 1) ]
- end
- it "should return the correct lum sum of items" do
- @order.amount.should == 3.0
- end
- end
-
- context "#can_cancel?" do
- it "should be false for completed order in the canceled state" do
- order.state = 'canceled'
- order.shipment_state = 'ready'
- order.completed_at = Time.now
- order.can_cancel?.should be_false
- end
-
- it "should be true for completed order with no shipment" do
- order.state = 'complete'
- order.shipment_state = nil
- order.completed_at = Time.now
- order.can_cancel?.should be_true
- end
- end
-
- context "rate_hash" do
- let(:shipping_method_1) { mock_model Spree::ShippingMethod, :name => 'Air Shipping', :id => 1, :calculator => mock('calculator') }
- let(:shipping_method_2) { mock_model Spree::ShippingMethod, :name => 'Ground Shipping', :id => 2, :calculator => mock('calculator') }
-
- before do
- shipping_method_1.calculator.stub(:compute).and_return(10.0)
- shipping_method_2.calculator.stub(:compute).and_return(0.0)
- order.stub(:available_shipping_methods => [ shipping_method_1, shipping_method_2 ])
- end
-
- it "should return shipping methods sorted by cost" do
- rate_1, rate_2 = order.rate_hash
-
- rate_1.shipping_method.should == shipping_method_2
- rate_1.cost.should == 0.0
- rate_1.name.should == "Ground Shipping"
- rate_1.id.should == 2
-
- rate_2.shipping_method.should == shipping_method_1
- rate_2.cost.should == 10.0
- rate_2.name.should == "Air Shipping"
- rate_2.id.should == 1
- end
-
- it "should not return shipping methods with nil cost" do
- shipping_method_1.calculator.stub(:compute).and_return(nil)
- order.rate_hash.count.should == 1
- rate_1 = order.rate_hash.first
-
- rate_1.shipping_method.should == shipping_method_2
- rate_1.cost.should == 0
- rate_1.name.should == "Ground Shipping"
- rate_1.id.should == 2
- end
-
- end
-
- context "insufficient_stock_lines" do
- let(:line_item) { mock_model Spree::LineItem, :insufficient_stock? => true }
-
- before { order.stub(:line_items => [line_item]) }
-
- it "should return line_item that has insufficent stock on hand" do
- order.insufficient_stock_lines.size.should == 1
- order.insufficient_stock_lines.include?(line_item).should be_true
- end
-
- end
-
- context "#add_variant" do
- it "should update order totals" do
- order = Spree::Order.create
-
- order.item_total.to_f.should == 0.00
- order.total.to_f.should == 0.00
-
- product = Spree::Product.create!(:name => 'Test', :sku => 'TEST-1', :price => 22.25)
- order.add_variant(product.master)
-
- order.item_total.to_f.should == 22.25
- order.total.to_f.should == 22.25
- end
- end
-
- context "empty!" do
- it "should clear out all line items and adjustments" do
- order = stub_model(Spree::Order)
- order.stub(:line_items => line_items = [])
- order.stub(:adjustments => adjustments = [])
- order.line_items.should_receive(:destroy_all)
- order.adjustments.should_receive(:destroy_all)
-
- order.empty!
- end
- end
-
- context "#display_outstanding_balance" do
- it "returns the value as a spree money" do
- order.stub(:outstanding_balance) { 10.55 }
- order.display_outstanding_balance.should == Spree::Money.new(10.55)
- end
- end
-
- context "#display_item_total" do
- it "returns the value as a spree money" do
- order.stub(:item_total) { 10.55 }
- order.display_item_total.should == Spree::Money.new(10.55)
- end
- end
-
- context "#display_adjustment_total" do
- it "returns the value as a spree money" do
- order.adjustment_total = 10.55
- order.display_adjustment_total.should == Spree::Money.new(10.55)
- end
- end
-
- context "#display_total" do
- it "returns the value as a spree money" do
- order.total = 10.55
- order.display_total.should == Spree::Money.new(10.55)
- end
- end
-
- context "#currency" do
- context "when object currency is ABC" do
- before { order.currency = "ABC" }
-
- it "returns the currency from the object" do
- order.currency.should == "ABC"
- end
- end
-
- context "when object currency is nil" do
- before { order.currency = nil }
-
- it "returns the globally configured currency" do
- order.currency.should == "USD"
- end
- end
- end
-
- # Regression tests for #2179
- context "#merge!" do
- let(:variant) { Factory(:variant) }
- let(:order_1) { Spree::Order.create }
- let(:order_2) { Spree::Order.create }
-
- it "destroys the other order" do
- order_1.merge!(order_2)
- lambda { order_2.reload }.should raise_error(ActiveRecord::RecordNotFound)
- end
-
- context "merging together two orders with line items for the same variant" do
- before do
- order_1.add_variant(variant)
- order_2.add_variant(variant)
- end
-
- specify do
- order_1.merge!(order_2)
- order_1.line_items.count.should == 1
-
- line_item = order_1.line_items.first
- line_item.quantity.should == 2
- line_item.variant_id.should == variant.id
- end
- end
-
- context "merging together two orders with different line items" do
- let(:variant_2) { Factory(:variant) }
-
- before do
- order_1.add_variant(variant)
- order_2.add_variant(variant_2)
- end
-
- specify do
- order_1.merge!(order_2)
- line_items = order_1.line_items
- line_items.count.should == 2
-
- # No guarantee on ordering of line items, so we do this:
- line_items.map(&:quantity).should =~ [1,1]
- line_items.map(&:variant_id).should =~ [variant.id, variant_2.id]
- end
- end
- end
-
- # Regression test for #2191
- context "when an order has an adjustment that zeroes the total, but another adjustment for shipping that raises it above zero" do
- let!(:persisted_order) { create(:order) }
- let!(:line_item) { create(:line_item) }
- let!(:shipping_method) do
- sm = create(:shipping_method)
- sm.calculator.preferred_amount = 10
- sm.save
- sm
- end
-
- before do
- # Don't care about available payment methods in this test
- persisted_order.stub(:has_available_payment => false)
- persisted_order.line_items << line_item
- persisted_order.adjustments.create(:amount => -line_item.amount, :label => "Promotion")
- persisted_order.state = 'delivery'
- persisted_order.save # To ensure new state_change event
- end
-
- it "transitions from delivery to payment" do
- persisted_order.shipping_method = shipping_method
- persisted_order.next!
- persisted_order.state.should == "payment"
- end
- end
-end
diff --git a/core/spec/models/order_updater_spec.rb b/core/spec/models/order_updater_spec.rb
deleted file mode 100644
index a44c494af04..00000000000
--- a/core/spec/models/order_updater_spec.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-require 'spec_helper'
-
-module Spree
- describe OrderUpdater do
- let(:order) { stub_model(Spree::Order) }
- let(:updater) { Spree::OrderUpdater.new(order) }
-
- it "updates totals" do
- payments = [stub(:amount => 5), stub(:amount => 5)]
- order.stub_chain(:payments, :completed).and_return(payments)
-
- line_items = [stub(:amount => 10), stub(:amount => 20)]
- order.stub :line_items => line_items
-
- adjustments = [stub(:amount => 10), stub(:amount => -20)]
- order.stub_chain(:adjustments, :eligible).and_return(adjustments)
-
- updater.update_totals
- order.payment_total.should == 10
- order.item_total.should == 30
- order.adjustment_total.should == -10
- order.total.should == 20
- end
-
- context "updating shipment state" do
- before do
- order.stub_chain(:shipments, :shipped, :count).and_return(0)
- order.stub_chain(:shipments, :ready, :count).and_return(0)
- order.stub_chain(:shipments, :pending, :count).and_return(0)
- end
-
- it "is backordered" do
- order.stub :backordered? => true
- updater.update_shipment_state
-
- order.shipment_state.should == 'backorder'
- end
-
- it "is nil" do
- order.stub_chain(:shipments, :count).and_return(0)
-
- updater.update_shipment_state
- order.shipment_state.should be_nil
- end
-
-
- [:shipped, :ready, :pending].each do |state|
- it "is #{state}" do
- order.stub_chain(:shipments, :count).and_return(1)
- order.stub_chain(:shipments, state, :count).and_return(1)
-
- updater.update_shipment_state
- order.shipment_state.should == state.to_s
- end
- end
-
- it "is partial" do
- order.stub_chain(:shipments, :count).and_return(2)
- order.stub_chain(:shipments, :ready, :count).and_return(1)
- order.stub_chain(:shipments, :pending, :count).and_return(1)
-
- updater.update_shipment_state
- order.shipment_state.should == 'partial'
- end
- end
-
- context "updating payment state" do
- it "is failed if last payment failed" do
- order.stub_chain(:payments, :last, :state).and_return('failed')
-
- updater.update_payment_state
- order.payment_state.should == 'failed'
- end
-
- it "is balance due with no line items" do
- order.stub_chain(:line_items, :empty?).and_return(true)
-
- updater.update_payment_state
- order.payment_state.should == 'balance_due'
- end
-
- it "is credit owed if payment is above total" do
- order.stub_chain(:line_items, :empty?).and_return(false)
- order.stub :payment_total => 31
- order.stub :total => 30
-
- updater.update_payment_state
- order.payment_state.should == 'credit_owed'
- end
-
- it "is paid if order is paid in full" do
- order.stub_chain(:line_items, :empty?).and_return(false)
- order.stub :payment_total => 30
- order.stub :total => 30
-
- updater.update_payment_state
- order.payment_state.should == 'paid'
- end
- end
-
-
- it "state change" do
- order.shipment_state = 'shipped'
- state_changes = stub
- order.stub :state_changes => state_changes
- state_changes.should_receive(:create).with({
- :previous_state => nil,
- :next_state => 'shipped',
- :name => 'shipment',
- :user_id => nil
- }, :without_protection => true)
-
- order.state_changed('shipment')
- end
-
- it "updates each shipment" do
- shipment = stub_model(Shipment)
- shipments = [shipment]
- order.stub :shipments => shipments
- shipments.stub :ready => []
- shipments.stub :pending => []
- shipments.stub :shipped => []
-
- shipment.should_receive(:update!).with(order)
-
- updater.update
- end
-
- it "updates totals twice" do
- updater.should_receive(:update_totals).twice
-
- updater.update
- end
- end
-end
diff --git a/core/spec/models/payment_spec.rb b/core/spec/models/payment_spec.rb
deleted file mode 100644
index fab72b765df..00000000000
--- a/core/spec/models/payment_spec.rb
+++ /dev/null
@@ -1,557 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Payment do
- let(:order) do
- order = Spree::Order.new(:bill_address => Spree::Address.new,
- :ship_address => Spree::Address.new)
- end
-
- let(:gateway) do
- gateway = Spree::Gateway::Bogus.new({:environment => 'test', :active => true}, :without_protection => true)
- gateway.stub :source_required => true
- gateway
- end
-
- let(:card) do
- mock_model(Spree::CreditCard, :number => "4111111111111111",
- :has_payment_profile? => true)
- end
-
- let(:payment) do
- payment = Spree::Payment.new
- payment.source = card
- payment.order = order
- payment.payment_method = gateway
- payment
- end
-
- let(:amount_in_cents) { payment.amount.to_f * 100 }
-
- let!(:success_response) do
- mock('success_response', :success? => true,
- :authorization => '123',
- :avs_result => { 'code' => 'avs-code' })
- end
-
- let(:failed_response) { mock('gateway_response', :success? => false) }
-
- before(:each) do
- # So it doesn't create log entries every time a processing method is called
- payment.log_entries.stub(:create)
- end
-
- # Regression test for https://github.com/spree/spree/pull/2224
- context 'failure' do
-
- it 'should transition to failed from pending state' do
- payment.state = 'pending'
- payment.failure
- payment.state.should eql('failed')
- end
-
- it 'should transition to failed from processing state' do
- payment.state = 'processing'
- payment.failure
- payment.state.should eql('failed')
- end
-
- end
-
- context "processing" do
- before do
- payment.stub(:update_order)
- payment.stub(:create_payment_profile)
- end
-
- context "#process!" do
- it "should purchase if with auto_capture" do
- Spree::Config[:auto_capture] = true
- payment.should_receive(:purchase!)
- payment.process!
- end
-
- it "should authorize without auto_capture" do
- Spree::Config[:auto_capture] = false
- payment.should_receive(:authorize!)
- payment.process!
- end
-
- it "should make the state 'processing'" do
- payment.should_receive(:started_processing!)
- payment.process!
- end
-
- end
-
- context "#authorize" do
- it "should call authorize on the gateway with the payment amount" do
- payment.payment_method.should_receive(:authorize).with(amount_in_cents,
- card,
- anything).and_return(success_response)
- payment.authorize!
- end
-
- it "should call authorize on the gateway with the currency code" do
- payment.stub :currency => 'GBP'
- payment.payment_method.should_receive(:authorize).with(amount_in_cents,
- card,
- hash_including({:currency => "GBP"})).and_return(success_response)
- payment.authorize!
- end
-
- it "should log the response" do
- payment.log_entries.should_receive(:create).with({:details => anything}, {:without_protection => true})
- payment.authorize!
- end
-
- context "when gateway does not match the environment" do
- it "should raise an exception" do
- gateway.stub :environment => "foo"
- lambda { payment.authorize! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "if successful" do
- before do
- payment.payment_method.should_receive(:authorize).with(amount_in_cents,
- card,
- anything).and_return(success_response)
- end
-
- it "should store the response_code and avs_response" do
- payment.authorize!
- payment.response_code.should == '123'
- payment.avs_response.should == 'avs-code'
- end
-
- it "should make payment pending" do
- payment.should_receive(:pend!)
- payment.authorize!
- end
- end
-
- context "if unsuccessful" do
- it "should mark payment as failed" do
- gateway.stub(:authorize).and_return(failed_response)
- payment.should_receive(:failure)
- payment.should_not_receive(:pend)
- lambda {
- payment.authorize!
- }.should raise_error(Spree::Core::GatewayError)
- end
- end
- end
-
- context "purchase" do
- it "should call purchase on the gateway with the payment amount" do
- gateway.should_receive(:purchase).with(amount_in_cents, card, anything).and_return(success_response)
- payment.purchase!
- end
-
- it "should log the response" do
- payment.log_entries.should_receive(:create).with({:details => anything}, {:without_protection => true})
- payment.purchase!
- end
-
- context "when gateway does not match the environment" do
- it "should raise an exception" do
- gateway.stub :environment => "foo"
- lambda { payment.purchase! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "if successful" do
- before do
- payment.payment_method.should_receive(:purchase).with(amount_in_cents,
- card,
- anything).and_return(success_response)
- end
-
- it "should store the response_code and avs_response" do
- payment.purchase!
- payment.response_code.should == '123'
- payment.avs_response.should == 'avs-code'
- end
-
- it "should make payment complete" do
- payment.should_receive(:complete!)
- payment.purchase!
- end
- end
-
- context "if unsuccessful" do
- it "should make payment failed" do
- gateway.stub(:purchase).and_return(failed_response)
- payment.should_receive(:failure)
- payment.should_not_receive(:pend)
- lambda { payment.purchase! }.should raise_error(Spree::Core::GatewayError)
- end
- end
- end
-
- context "#capture" do
- before do
- payment.stub(:complete).and_return(true)
- end
-
- context "when payment is pending" do
- before do
- payment.state = 'pending'
- end
-
- context "if successful" do
- before do
- payment.payment_method.should_receive(:capture).with(payment, card, anything).and_return(success_response)
- end
-
- it "should make payment complete" do
- payment.should_receive(:complete)
- payment.capture!
- end
-
- it "should store the response_code" do
- gateway.stub :capture => success_response
- payment.capture!
- payment.response_code.should == '123'
- end
- end
-
- context "if unsuccessful" do
- it "should not make payment complete" do
- gateway.stub :capture => failed_response
- payment.should_receive(:failure)
- payment.should_not_receive(:complete)
- lambda { payment.capture! }.should raise_error(Spree::Core::GatewayError)
- end
- end
- end
-
- # Regression test for #2119
- context "when payment is completed" do
- before do
- payment.state = 'completed'
- end
-
- it "should do nothing" do
- payment.should_not_receive(:complete)
- payment.payment_method.should_not_receive(:capture)
- payment.log_entries.should_not_receive(:create)
- payment.capture!
- end
- end
- end
-
- context "#void" do
- before do
- payment.response_code = '123'
- payment.state = 'pending'
- end
-
- context "when profiles are supported" do
- it "should call payment_gateway.void with the payment's response_code" do
- gateway.stub :payment_profiles_supported? => true
- gateway.should_receive(:void).with('123', card, anything).and_return(success_response)
- payment.void_transaction!
- end
- end
-
- context "when profiles are not supported" do
- it "should call payment_gateway.void with the payment's response_code" do
- gateway.stub :payment_profiles_supported? => false
- gateway.should_receive(:void).with('123', anything).and_return(success_response)
- payment.void_transaction!
- end
- end
-
- it "should log the response" do
- payment.log_entries.should_receive(:create).with({:details => anything}, {:without_protection => true})
- payment.void_transaction!
- end
-
- context "when gateway does not match the environment" do
- it "should raise an exception" do
- gateway.stub :environment => "foo"
- lambda { payment.void_transaction! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "if successful" do
- it "should update the response_code with the authorization from the gateway" do
- # Change it to something different
- payment.response_code = 'abc'
- payment.void_transaction!
- payment.response_code.should == '12345'
- end
- end
-
- context "if unsuccessful" do
- it "should not void the payment" do
- gateway.stub :void => failed_response
- payment.should_not_receive(:void)
- lambda { payment.void_transaction! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- # Regression test for #2119
- context "if payment is already voided" do
- before do
- payment.state = 'void'
- end
-
- it "should not void the payment" do
- payment.payment_method.should_not_receive(:void)
- payment.void_transaction!
- end
- end
- end
-
- context "#credit" do
- before do
- payment.state = 'complete'
- payment.response_code = '123'
- end
-
- context "when outstanding_balance is less than payment amount" do
- before do
- payment.order.stub :outstanding_balance => 10
- payment.stub :credit_allowed => 1000
- end
-
- it "should call credit on the gateway with the credit amount and response_code" do
- gateway.should_receive(:credit).with(1000, card, '123', anything).and_return(success_response)
- payment.credit!
- end
- end
-
- context "when outstanding_balance is equal to payment amount" do
- before do
- payment.order.stub :outstanding_balance => payment.amount
- end
-
- it "should call credit on the gateway with the credit amount and response_code" do
- gateway.should_receive(:credit).with(amount_in_cents, card, '123', anything).and_return(success_response)
- payment.credit!
- end
- end
-
- context "when outstanding_balance is greater than payment amount" do
- before do
- payment.order.stub :outstanding_balance => 101
- end
-
- it "should call credit on the gateway with the original payment amount and response_code" do
- gateway.should_receive(:credit).with(amount_in_cents.to_f, card, '123', anything).and_return(success_response)
- payment.credit!
- end
- end
-
- it "should log the response" do
- payment.log_entries.should_receive(:create).with({:details => anything}, {:without_protection => true})
- payment.credit!
- end
-
- context "when gateway does not match the environment" do
- it "should raise an exception" do
- gateway.stub :environment => "foo"
- lambda { payment.credit! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "when response is successful" do
- it "should create an offsetting payment" do
- Spree::Payment.should_receive(:create)
- payment.credit!
- end
-
- it "resulting payment should have correct values" do
- payment.order.stub :outstanding_balance => 100
- payment.stub :credit_allowed => 10
-
- offsetting_payment = payment.credit!
- offsetting_payment.amount.to_f.should == -10
- offsetting_payment.should be_completed
- offsetting_payment.response_code.should == '12345'
- offsetting_payment.source.should == payment
- end
- end
- end
- end
-
- context "when response is unsuccessful" do
- it "should not create a payment" do
- gateway.stub :credit => failed_response
- Spree::Payment.should_not_receive(:create)
- lambda { payment.credit! }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "when already processing" do
- it "should return nil without trying to process the source" do
- payment.state = 'processing'
-
- payment.should_not_receive(:authorize!)
- payment.should_not_receive(:purchase!)
- payment.process!.should == nil
- end
- end
-
- context "with source required" do
- context "raises an error if no source is specified" do
- before do
- payment.source = nil
- end
-
- specify do
- lambda { payment.process! }.should raise_error(Spree::Core::GatewayError, I18n.t(:payment_processing_failed))
- end
- end
- end
-
- context "with source optional" do
- context "raises no error if source is not specified" do
- before do
- payment.source = nil
- payment.payment_method.stub(:source_required? => false)
- end
-
- specify do
- lambda { payment.process! }.should_not raise_error(Spree::Core::GatewayError)
- end
- end
- end
-
- context "#credit_allowed" do
- it "is the difference between offsets total and payment amount" do
- payment.amount = 100
- payment.stub(:offsets_total).and_return(0)
- payment.credit_allowed.should == 100
- payment.stub(:offsets_total).and_return(80)
- payment.credit_allowed.should == 20
- end
- end
-
- context "#can_credit?" do
- it "is true if credit_allowed > 0" do
- payment.stub(:credit_allowed).and_return(100)
- payment.can_credit?.should be_true
- end
- it "is false if credit_allowed is 0" do
- payment.stub(:credit_allowed).and_return(0)
- payment.can_credit?.should be_false
- end
- end
-
- context "#credit" do
- context "when amount <= credit_allowed" do
- it "makes the state processing" do
- payment.state = 'completed'
- payment.stub(:credit_allowed).and_return(10)
- payment.partial_credit(10)
- payment.should be_processing
- end
- it "calls credit on the source with the payment and amount" do
- payment.state = 'completed'
- payment.stub(:credit_allowed).and_return(10)
- payment.should_receive(:credit!).with(10)
- payment.partial_credit(10)
- end
- end
- context "when amount > credit_allowed" do
- it "should not call credit on the source" do
- payment.state = 'completed'
- payment.stub(:credit_allowed).and_return(10)
- payment.partial_credit(20)
- payment.should be_completed
- end
- end
- end
-
- context "#save" do
- it "should call order#update!" do
- payment = Spree::Payment.create({:amount => 100, :order => order}, :without_protection => true)
- order.should_receive(:update!)
- payment.save
- end
-
- context "when profiles are supported" do
- before do
- gateway.stub :payment_profiles_supported? => true
- payment.source.stub :has_payment_profile? => false
- end
-
-
- context "when there is an error connecting to the gateway" do
- it "should call gateway_error " do
- gateway.should_receive(:create_profile).and_raise(ActiveMerchant::ConnectionError)
- lambda { Spree::Payment.create({:amount => 100, :order => order, :source => card, :payment_method => gateway}, :without_protection => true) }.should raise_error(Spree::Core::GatewayError)
- end
- end
-
- context "when successfully connecting to the gateway" do
- it "should create a payment profile" do
- payment.payment_method.should_receive :create_profile
- payment = Spree::Payment.create({:amount => 100, :order => order, :source => card, :payment_method => gateway}, :without_protection => true)
- end
- end
-
-
- end
-
- context "when profiles are not supported" do
- before { gateway.stub :payment_profiles_supported? => false }
-
- it "should not create a payment profile" do
- gateway.should_not_receive :create_profile
- payment = Spree::Payment.create({:amount => 100, :order => order, :source => card, :payment_method => gateway}, :without_protection => true)
- end
- end
- end
-
- context "#build_source" do
- it "should build the payment's source" do
- params = { :amount => 100, :payment_method => gateway,
- :source_attributes => {:year=>"2012", :month =>"1", :number => '1234567890123',:verification_value => '123'}}
-
- payment = Spree::Payment.new(params, :without_protection => true)
- payment.should be_valid
- payment.source.should_not be_nil
- end
-
- context "with the params hash ordered differently" do
- it "should build the payment's source" do
- params = {
- :source_attributes => {:year=>"2012", :month =>"1", :number => '1234567890123',:verification_value => '123'},
- :amount => 100, :payment_method => gateway
- }
-
- payment = Spree::Payment.new(params, :without_protection => true)
- payment.should be_valid
- payment.source.should_not be_nil
- end
- end
-
- it "errors when payment source not valid" do
- params = { :amount => 100, :payment_method => gateway,
- :source_attributes => {:year=>"2012", :month =>"1" }}
-
- payment = Spree::Payment.new(params, :without_protection => true)
- payment.should_not be_valid
- payment.source.should_not be_nil
- payment.source.should have(1).error_on(:number)
- payment.source.should have(1).error_on(:verification_value)
- end
- end
-
- context "#currency" do
- before { order.stub(:currency) { "ABC" } }
- it "returns the order currency" do
- payment.currency.should == "ABC"
- end
- end
-
- context "#display_amount" do
- it "returns a Spree::Money for this amount" do
- payment.display_amount.should == Spree::Money.new(payment.amount)
- end
- end
-end
diff --git a/core/spec/models/preference_spec.rb b/core/spec/models/preference_spec.rb
deleted file mode 100644
index 2b2aab754bd..00000000000
--- a/core/spec/models/preference_spec.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Preference do
-
- it "should require a key" do
- @preference = Spree::Preference.new
- @preference.key = :test
- @preference.value_type = :boolean
- @preference.value = true
- @preference.should be_valid
- end
-
- describe "type coversion for values" do
- def round_trip_preference(key, value, value_type)
- p = Spree::Preference.new
- p.value = value
- p.value_type = value_type
- p.key = key
- p.save
-
- Spree::Preference.find_by_key(key)
- end
-
- it ":boolean" do
- value_type = :boolean
- value = true
- key = "boolean_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it "false :boolean" do
- value_type = :boolean
- value = false
- key = "boolean_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":integer" do
- value_type = :integer
- value = 10
- key = "integer_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":decimal" do
- value_type = :decimal
- value = 1.5
- key = "decimal_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":string" do
- value_type = :string
- value = "This is a string"
- key = "string_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":text" do
- value_type = :text
- value = "This is a string stored as text"
- key = "text_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":password" do
- value_type = :password
- value = "This is a password"
- key = "password_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- it ":any" do
- value_type = :any
- value = [1, 2]
- key = "any_key"
- pref = round_trip_preference(key, value, value_type)
- pref.value.should eq value
- pref.value_type.should == value_type.to_s
- end
-
- end
-
- describe "converting old values" do
-
- it "converts true" do
- p = Spree::Preference.new
- p.value = 't'
- p.value_type = TrueClass.to_s
- Spree::Preference.convert_old_value_types(p)
- p.value_type.should == 'boolean'
- p.value.should == true
- end
-
- it "converts false" do
- p = Spree::Preference.new
- p.value = 'f'
- p.value_type = FalseClass.to_s
- Spree::Preference.convert_old_value_types(p)
- p.value_type.should == 'boolean'
- p.value.should == false
- end
-
- end
-
-end
diff --git a/core/spec/models/preferences/configuration_spec.rb b/core/spec/models/preferences/configuration_spec.rb
deleted file mode 100644
index a1b45d05341..00000000000
--- a/core/spec/models/preferences/configuration_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Preferences::Configuration do
-
- before :all do
- class AppConfig < Spree::Preferences::Configuration
- preference :color, :string, :default => :blue
- end
- @config = AppConfig.new
- end
-
- it "has named methods to access preferences" do
- @config.color = 'orange'
- @config.color.should eq 'orange'
- end
-
- it "uses [ ] to access preferences" do
- @config[:color] = 'red'
- @config[:color].should eq 'red'
- end
-
- it "uses set/get to access preferences" do
- @config.set :color, 'green'
- @config.get(:color).should eq 'green'
- end
-
-end
-
-
-
diff --git a/core/spec/models/preferences/preferable_spec.rb b/core/spec/models/preferences/preferable_spec.rb
deleted file mode 100644
index 0961f570963..00000000000
--- a/core/spec/models/preferences/preferable_spec.rb
+++ /dev/null
@@ -1,326 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Preferences::Preferable do
-
- before :all do
- class A
- include Spree::Preferences::Preferable
- attr_reader :id
-
- def initialize
- @id = rand(999)
- end
-
- preference :color, :string, :default => 'green', :description => "My Favorite Color"
- end
-
- class B < A
- preference :flavor, :string
- end
- end
-
- before :each do
- @a = A.new
- @a.stub(:persisted? => true)
- @b = B.new
- @b.stub(:persisted? => true)
- end
-
- describe "preference definitions" do
- it "parent should not see child definitions" do
- @a.has_preference?(:color).should be_true
- @a.has_preference?(:flavor).should_not be_true
- end
-
- it "child should have parent and own definitions" do
- @b.has_preference?(:color).should be_true
- @b.has_preference?(:flavor).should be_true
- end
-
- it "instances have defaults" do
- @a.preferred_color.should eq 'green'
- @b.preferred_color.should eq 'green'
- @b.preferred_flavor.should be_nil
- end
-
- it "can be asked if it has a preference definition" do
- @a.has_preference?(:color).should be_true
- @a.has_preference?(:bad).should be_false
- end
-
- it "can be asked and raises" do
- lambda {
- @a.has_preference! :flavor
- }.should raise_error(NoMethodError, "flavor preference not defined")
- end
-
- it "has a type" do
- @a.preferred_color_type.should eq :string
- @a.preference_type(:color).should eq :string
- end
-
- it "has a default" do
- @a.preferred_color_default.should eq 'green'
- @a.preference_default(:color).should eq 'green'
- end
-
- it "has a description" do
- @a.preferred_color_description.should eq "My Favorite Color"
- @a.preference_description(:color).should eq "My Favorite Color"
- end
-
- it "raises if not defined" do
- lambda {
- @a.get_preference :flavor
- }.should raise_error(NoMethodError, "flavor preference not defined")
- end
-
- end
-
- describe "preference access" do
- it "handles ghost methods for preferences" do
- @a.preferred_color = 'blue'
- @a.preferred_color.should eq 'blue'
-
- @a.prefers_color = 'green'
- @a.prefers_color?.should eq 'green'
- end
-
- it "has genric readers" do
- @a.preferred_color = 'red'
- @a.prefers?(:color).should eq 'red'
- @a.preferred(:color).should eq 'red'
- end
-
- it "parent and child instances have their own prefs" do
- @a.preferred_color = 'red'
- @b.preferred_color = 'blue'
-
- @a.preferred_color.should eq 'red'
- @b.preferred_color.should eq 'blue'
- end
-
- it "raises when preference not defined" do
- lambda {
- @a.set_preference(:bad, :bone)
- }.should raise_exception(NoMethodError, "bad preference not defined")
- end
-
- it "builds a hash of preferences" do
- @b.preferred_flavor = :strawberry
- @b.preferences[:flavor].should eq 'strawberry'
- @b.preferences[:color].should eq 'green' #default from A
- end
-
- context "database fallback" do
- before do
- @a.instance_variable_set("@pending_preferences", {})
- end
-
- it "retrieves a preference from the database before falling back to default" do
- preference = mock(:value => "chatreuse")
- Spree::Preference.should_receive(:find_by_key).with("color").and_return(preference)
- @a.preferred_color.should == 'chatreuse'
- end
-
- it "defaults if no database key exists" do
- Spree::Preference.should_receive(:find_by_key).and_return(nil)
- @a.preferred_color.should == 'green'
- end
- end
-
-
- context "converts integer preferences to integer values" do
- before do
- A.preference :is_integer, :integer
- end
-
- it "with strings" do
- @a.set_preference(:is_integer, '3')
- @a.preferences[:is_integer].should == 3
-
- @a.set_preference(:is_integer, '')
- @a.preferences[:is_integer].should == 0
- end
-
- end
-
- context "converts decimal preferences to BigDecimal values" do
- before do
- A.preference :if_decimal, :decimal
- end
-
- it "returns a BigDecimal" do
- @a.set_preference(:if_decimal, 3.3)
- @a.preferences[:if_decimal].class.should == BigDecimal
- end
-
- it "with strings" do
- @a.set_preference(:if_decimal, '3.3')
- @a.preferences[:if_decimal].should == 3.3
-
- @a.set_preference(:if_decimal, '')
- @a.preferences[:if_decimal].should == 0.0
- end
- end
-
- context "converts boolean preferences to boolean values" do
- before do
- A.preference :is_boolean, :boolean, :default => true
- end
-
- it "with strings" do
- @a.set_preference(:is_boolean, '0')
- @a.preferences[:is_boolean].should be_false
- @a.set_preference(:is_boolean, 'f')
- @a.preferences[:is_boolean].should be_false
- @a.set_preference(:is_boolean, 't')
- @a.preferences[:is_boolean].should be_true
- end
-
- it "with integers" do
- @a.set_preference(:is_boolean, 0)
- @a.preferences[:is_boolean].should be_false
- @a.set_preference(:is_boolean, 1)
- @a.preferences[:is_boolean].should be_true
- end
-
- it "with an empty string" do
- @a.set_preference(:is_boolean, '')
- @a.preferences[:is_boolean].should be_false
- end
-
- it "with an empty hash" do
- @a.set_preference(:is_boolean, [])
- @a.preferences[:is_boolean].should be_false
- end
- end
-
- context "converts any preferences to any values" do
- before do
- A.preference :product_ids, :any, :default => []
- A.preference :product_attributes, :any, :default => {}
- end
-
- it "with array" do
- @a.preferences[:product_ids].should == []
- @a.set_preference(:product_ids, [1, 2])
- @a.preferences[:product_ids].should == [1, 2]
- end
-
- it "with hash" do
- @a.preferences[:product_attributes].should == {}
- @a.set_preference(:product_attributes, {:id => 1, :name => 2})
- @a.preferences[:product_attributes].should == {:id => 1, :name => 2}
- end
- end
-
- end
-
- describe "persisted preferables" do
- before(:all) do
- class CreatePrefTest < ActiveRecord::Migration
- def self.up
- create_table :pref_tests do |t|
- t.string :col
- end
- end
-
- def self.down
- drop_table :pref_tests
- end
- end
-
- @migration_verbosity = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
- CreatePrefTest.migrate(:up)
-
- class PrefTest < ActiveRecord::Base
- preference :pref_test_pref, :string, :default => 'abc'
- preference :pref_test_any, :any, :default => []
- end
- end
-
- after(:all) do
- CreatePrefTest.migrate(:down)
- ActiveRecord::Migration.verbose = @migration_verbosity
- end
-
- before(:each) do
- @pt = PrefTest.create
- end
-
- describe "pending preferences for new activerecord objects" do
- it "saves preferences after record is saved" do
- pr = PrefTest.new
- pr.set_preference(:pref_test_pref, 'XXX')
- pr.get_preference(:pref_test_pref).should == 'XXX'
- pr.save!
- pr.get_preference(:pref_test_pref).should == 'XXX'
- end
-
- it "saves preferences for serialized object" do
- pr = PrefTest.new
- pr.set_preference(:pref_test_any, [1, 2])
- pr.get_preference(:pref_test_any).should == [1, 2]
- pr.save!
- pr.get_preference(:pref_test_any).should == [1, 2]
- end
- end
-
- describe "requires a valid id" do
- it "for cache_key" do
- pref_test = PrefTest.new
- pref_test.preference_cache_key(:pref_test_pref).should be_nil
-
- pref_test.save
- pref_test.preference_cache_key(:pref_test_pref).should_not be_nil
- end
-
- it "but returns default values" do
- pref_test = PrefTest.new
- pref_test.get_preference(:pref_test_pref).should == 'abc'
- end
-
- it "adds prefs in a pending hash until after_create" do
- pref_test = PrefTest.new
- pref_test.should_receive(:add_pending_preference).with(:pref_test_pref, 'XXX')
- pref_test.set_preference(:pref_test_pref, 'XXX')
- end
- end
-
- it "clear preferences" do
- @pt.set_preference(:pref_test_pref, 'xyz')
- @pt.preferred_pref_test_pref.should == 'xyz'
- @pt.clear_preferences
- @pt.preferred_pref_test_pref.should == 'abc'
- end
-
- it "clear preferences when record is deleted" do
- @pt.save!
- @pt.preferred_pref_test_pref = 'lmn'
- @pt.save!
- @pt.destroy
- @pt1 = PrefTest.new({:col => 'aaaa'}, :without_protection => true)
- @pt1.id = @pt.id
- @pt1.save!
- @pt1.get_preference(:pref_test_pref).should_not == 'lmn'
- @pt1.get_preference(:pref_test_pref).should == 'abc'
- end
- end
-
- it "builds cache keys" do
- @a.preference_cache_key(:color).should match /a\/color\/\d+/
- end
-
- it "can add and remove preferences" do
- A.preference :test_temp, :boolean, :default => true
- @a.preferred_test_temp.should be_true
- A.remove_preference :test_temp
- @a.has_preference?(:test_temp).should be_false
- @a.respond_to?(:preferred_test_temp).should be_false
- end
-
-end
-
-
diff --git a/core/spec/models/preferences/store_spec.rb b/core/spec/models/preferences/store_spec.rb
deleted file mode 100644
index 2d5a091934e..00000000000
--- a/core/spec/models/preferences/store_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Preferences::Store do
- before :each do
- @store = Spree::Preferences::StoreInstance.new
- end
-
- it "sets and gets a key" do
- @store.set :test, 1, :integer
- @store.exist?(:test).should be_true
- @store.get(:test).should eq 1
- end
-
- it "can set and get false values when cache return nil" do
- @store.set :test, false, :boolean
- @store.get(:test).should be_false
- end
-
- it "returns the correct preference value when the cache is empty" do
- @store.set :test, "1", :string
- Rails.cache.clear
- @store.get(:test).should == "1"
- end
-end
diff --git a/core/spec/models/product/scopes_spec.rb b/core/spec/models/product/scopes_spec.rb
deleted file mode 100644
index cff66ad7ad5..00000000000
--- a/core/spec/models/product/scopes_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe "Product scopes" do
- let!(:product) { create(:product) }
-
- context "A product assigned to parent and child taxons" do
- before do
- @taxonomy = create(:taxonomy)
- @root_taxon = @taxonomy.root
-
- @parent_taxon = create(:taxon, :name => 'Parent', :taxonomy_id => @taxonomy.id, :parent => @root_taxon)
- @child_taxon = create(:taxon, :name =>'Child 1', :taxonomy_id => @taxonomy.id, :parent => @parent_taxon)
- @parent_taxon.reload # Need to reload for descendents to show up
-
- product.taxons << @parent_taxon
- product.taxons << @child_taxon
- end
-
- it "calling Product.in_taxon should not return duplicate records" do
- Spree::Product.in_taxon(@parent_taxon).to_a.count.should == 1
- end
- end
-
- context "on_hand" do
- # Regression test for #2111
- context "A product with a deleted variant" do
- before do
- variant = product.variants.create!({:price => 10, :count_on_hand => 300}, :without_protection => true)
- variant.update_column(:deleted_at, Time.now)
- end
-
- it "does not include the deleted variant in on_hand summary" do
- Spree::Product.on_hand.should be_empty
- end
- end
- end
-end
diff --git a/core/spec/models/product_filter_spec.rb b/core/spec/models/product_filter_spec.rb
deleted file mode 100644
index fc965744b14..00000000000
--- a/core/spec/models/product_filter_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-require 'spree/product_filters'
-
-describe 'product filters' do
- # Regression test for #1709
- context 'finds products filtered by brand' do
- let(:product) { create(:product) }
- before do
- property = Spree::Property.create!(:name => "brand", :presentation => "brand")
- product.set_property("brand", "Nike")
- end
-
- it "does not attempt to call value method on Arel::Table" do
- lambda { Spree::ProductFilters.brand_filter }.should_not raise_error(NoMethodError)
- end
-
- it "can find products in the 'Nike' brand" do
- Spree::Product.brand_any("Nike").should include(product)
- end
- end
-end
diff --git a/core/spec/models/product_option_type_spec.rb b/core/spec/models/product_option_type_spec.rb
deleted file mode 100644
index 8bf18700137..00000000000
--- a/core/spec/models/product_option_type_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ProductOptionType do
-
-end
diff --git a/core/spec/models/product_property_spec.rb b/core/spec/models/product_property_spec.rb
deleted file mode 100644
index e979efb7699..00000000000
--- a/core/spec/models/product_property_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ProductProperty do
-
- context "validations" do
- it "should validate length of value" do
- pp = create(:product_property)
- pp.value = "x" * 256
- pp.should_not be_valid
- end
-
- end
-
-end
diff --git a/core/spec/models/product_spec.rb b/core/spec/models/product_spec.rb
deleted file mode 100644
index 2d5a70f4564..00000000000
--- a/core/spec/models/product_spec.rb
+++ /dev/null
@@ -1,422 +0,0 @@
-# coding: UTF-8
-
-require 'spec_helper'
-
-describe Spree::Product do
- before(:each) do
- reset_spree_preferences
- end
-
- context "#on_hand=" do
- it "should not complain of a missing master" do
- product = Spree::Product.new
- product.on_hand = 5
- end
- end
-
- it "should always have a master variant" do
- product = Spree::Product.new
- product.master.should_not be_nil
- end
-
- context 'product instance' do
- let(:product) { create(:product) }
-
- context '#duplicate' do
- before do
- product.stub :taxons => [create(:taxon)]
- end
-
- it 'duplicates product' do
- clone = product.duplicate
- clone.name.should == 'COPY OF ' + product.name
- clone.master.sku.should == 'COPY OF ' + product.master.sku
- clone.taxons.should == product.taxons
- clone.images.size.should == product.images.size
- end
- end
-
- context "product has no variants" do
- context "#delete" do
- it "should set deleted_at value" do
- product.delete
- product.reload
- product.deleted_at.should_not be_nil
- product.master.deleted_at.should_not be_nil
- end
- end
- end
-
- context "product has variants" do
- before do
- create(:variant, :product => product)
- end
-
- context "#delete" do
- it "should set deleted_at value" do
- product.delete
- product.deleted_at.should_not be_nil
- product.variants_including_master.all? { |v| !v.deleted_at.nil? }.should be_true
- end
- end
- end
-
- context "#on_hand" do
- # Regression test for #898
- context 'returns the correct number of products on hand' do
- before do
- Spree::Config.set :track_inventory_levels => true
- product.master.stub :on_hand => 2
- end
- specify { product.on_hand.should == 2 }
- end
- end
-
- # Test for #2167
- context "#on_display?" do
- it "is on display if product has stock" do
- product.stub :has_stock? => true
- assert product.on_display?
- end
-
- it "is on display if show_zero_stock_products preference is set to true" do
- Spree::Config[:show_zero_stock_products] = true
- assert product.on_display?
- end
- end
-
- # Test for #2167
- context "#on_sale?" do
- it "is on sale if the product has stock" do
- product.stub :has_stock? => true
- assert product.on_sale?
- end
-
- it "is on sale if allow_backorders preference is set to true" do
- Spree::Config[:allow_backorders] = true
- assert product.on_sale?
- end
- end
-
- context "#price" do
- # Regression test for #1173
- it 'strips non-price characters' do
- product.price = "$10"
- product.price.should == 10.0
- end
- end
-
- context "#display_price" do
- before { product.price = 10.55 }
-
- context "with display_currency set to true" do
- before { Spree::Config[:display_currency] = true }
-
- it "shows the currency" do
- product.display_price.should == "$10.55 USD"
- end
- end
-
- context "with display_currency set to false" do
- before { Spree::Config[:display_currency] = false }
-
- it "does not include the currency" do
- product.display_price.should == "$10.55"
- end
- end
-
- context "with currency set to JPY" do
- before do
- product.master.default_price.currency = 'JPY'
- product.master.default_price.save!
- Spree::Config[:currency] = 'JPY'
- end
-
- it "displays the currency in yen" do
- product.display_price.to_s.should == "¥11"
- end
- end
- end
-
- context "#available?" do
- it "should be available if date is in the past" do
- product.available_on = 1.day.ago
- product.should be_available
- end
-
- it "should not be available if date is nil or in the future" do
- product.available_on = nil
- product.should_not be_available
-
- product.available_on = 1.day.from_now
- product.should_not be_available
- end
- end
- end
-
- context "validations" do
- context "find_by_param" do
-
- context "permalink should be incremented until the value is not taken" do
- before do
- @other_product = create(:product, :name => 'zoo')
- @product1 = create(:product, :name => 'foo')
- @product2 = create(:product, :name => 'foo')
- @product3 = create(:product, :name => 'foo')
- end
- it "should have valid permalink" do
- @product1.permalink.should == 'foo'
- @product2.permalink.should == 'foo-1'
- @product3.permalink.should == 'foo-2'
- end
- end
-
- context "permalink should be incremented until the value is not taken when there are more than 10 products" do
- before do
- @products = 0.upto(11).map do
- create(:product, :name => 'foo')
- end
- end
- it "should have valid permalink" do
- @products[11].permalink.should == 'foo-11'
- end
- end
-
- context "permalink should be incremented until the value is not taken for similar names" do
- before do
- @other_product = create(:product, :name => 'foo bar')
- @product1 = create(:product, :name => 'foo')
- @product2 = create(:product, :name => 'foo')
- @product3 = create(:product, :name => 'foo')
- end
- it "should have valid permalink" do
- @product1.permalink.should == 'foo-1'
- @product2.permalink.should == 'foo-2'
- @product3.permalink.should == 'foo-3'
- end
- end
-
- context "permalink should be incremented until the value is not taken for similar names when there are more than 10 products" do
- before do
- @other_product = create(:product, :name => 'foo a')
- @products = 0.upto(11).map do
- create(:product, :name => 'foo')
- end
- end
- it "should have valid permalink" do
- @products[11].permalink.should == 'foo-12'
- end
- end
-
- context "permalink with quotes" do
- it "should be saved correctly" do
- product = create(:product, :name => "Joe's", :permalink => "joe's")
- product.permalink.should == "joe's"
- end
-
- context "existing" do
- before do
- create(:product, :name => "Joe's", :permalink => "joe's")
- end
-
- it "should be detected" do
- product = create(:product, :name => "Joe's", :permalink => "joe's")
- product.permalink.should == "joe's-1"
- end
- end
- end
-
- context "make_permalink should declare validates_uniqueness_of" do
- before do
- @product1 = create(:product, :name => 'foo')
- @product2 = create(:product, :name => 'foo')
- @product2.update_attributes(:permalink => 'foo')
- end
-
- it "should have an error" do
- @product2.errors.size.should == 1
- end
-
- it "should have error message that permalink is already taken" do
- @product2.errors.full_messages.first.should == 'Permalink has already been taken'
- end
- end
-
- end
- end
-
- context "permalink generation" do
- it "supports Chinese" do
- @product = create(:product, :name => "你好")
- @product.permalink.should == "ni-hao"
- end
- end
-
- context "manual permalink override" do
- it "calling save_permalink with a parameter" do
- @product = create(:product, :name => "foo")
- @product.permalink.should == "foo"
- @product.name = "foobar"
- @product.save
- @product.permalink.should == "foo"
- @product.save_permalink(@product.name)
- @product.permalink.should == "foobar"
- end
-
- it "should be incremented until not taken with a parameter" do
- @product = create(:product, :name => "foo")
- @product2 = create(:product, :name => "foobar")
- @product.permalink.should == "foo"
- @product.name = "foobar"
- @product.save
- @product.permalink.should == "foo"
- @product.save_permalink(@product.name)
- @product.permalink.should == "foobar-1"
- end
- end
-
- context "properties" do
- it "should properly assign properties" do
- product = FactoryGirl.create :product
- product.set_property('the_prop', 'value1')
- product.property('the_prop').should == 'value1'
-
- product.set_property('the_prop', 'value2')
- product.property('the_prop').should == 'value2'
- end
-
- it "should not create duplicate properties when set_property is called" do
- product = FactoryGirl.create :product
-
- lambda {
- product.set_property('the_prop', 'value2')
- product.save
- product.reload
- }.should_not change(product.properties, :length)
-
- lambda {
- product.set_property('the_prop_new', 'value')
- product.save
- product.reload
- product.property('the_prop_new').should == 'value'
- }.should change { product.properties.length }.by(1)
- end
- end
-
- context '#create' do
- before do
- @prototype = create(:prototype)
- @product = Spree::Product.new(:name => "Foo", :price => 1.99)
- end
-
- context "when prototype is supplied" do
- before { @product.prototype_id = @prototype.id }
-
- it "should create properties based on the prototype" do
- @product.save
- @product.properties.count.should == 1
- end
-
- end
-
- context "when prototype with option types is supplied" do
-
- include_context "product prototype"
-
- before { @product.prototype_id = prototype.id }
-
- it "should create option types based on the prototype" do
- @product.save
- @product.option_type_ids.length.should == 1
- @product.option_type_ids.should == prototype.option_type_ids
- end
-
- it "should create product option types based on the prototype" do
- @product.save
- @product.product_option_types.map(&:option_type_id).should == prototype.option_type_ids
- end
-
- it "should create variants from an option values hash with one option type" do
- @product.option_values_hash = option_values_hash
- @product.save
- @product.variants.length.should == 3
- end
-
- it "should still create variants when option_values_hash is given but prototype id is nil" do
- @product.option_values_hash = option_values_hash
- @product.prototype_id = nil
- @product.save
- @product.option_type_ids.length.should == 1
- @product.option_type_ids.should == prototype.option_type_ids
- @product.variants.length.should == 3
- end
-
- it "should create variants from an option values hash with multiple option types" do
- color = build_option_type_with_values("color", %w(Red Green Blue))
- logo = build_option_type_with_values("logo", %w(Ruby Rails Nginx))
- option_values_hash[color.id.to_s] = color.option_value_ids
- option_values_hash[logo.id.to_s] = logo.option_value_ids
- @product.option_values_hash = option_values_hash
- @product.save
- @product = @product.reload
- @product.option_type_ids.length.should == 3
- @product.variants.length.should == 27
- end
- end
-
- end
-
- context '#has_stock?' do
- let(:product) do
- product = stub_model(Spree::Product)
- product.stub :master => stub_model(Spree::Variant)
- product
- end
-
- context 'nothing in stock' do
- before do
- Spree::Config.set :track_inventory_levels => true
- product.master.stub :on_hand => 0
- end
- specify { product.has_stock?.should be_false }
- end
-
- context 'master variant has items in stock' do
- before do
- product.master.on_hand = 100
- end
- specify { product.has_stock?.should be_true }
- end
-
- context 'variant has items in stock' do
- before do
- Spree::Config.set :track_inventory_levels => true
- product.master.stub :on_hand => 0
- product.variants.build(:on_hand => 100)
- product.stub :has_variants? => true
- end
- specify { product.has_stock?.should be_true }
- end
- end
-
- context "#images" do
- let(:product) { create(:product) }
-
- before do
- image = File.open(File.expand_path('../../../app/assets/images/noimage/product.png', __FILE__))
- Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 2", :attachment => image, :position => 2})
- Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1})
- Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'ThirdParty::Extension', :alt => "position 1", :attachment => image, :position => 2})
- end
-
- it "should only look for variant images to support third-party extensions" do
- product.images.size.should == 2
- end
-
- it "should be sorted by position" do
- product.images.map(&:alt).should eq(["position 1", "position 2"])
- end
-
- end
-
-end
diff --git a/core/spec/models/return_authorization_spec.rb b/core/spec/models/return_authorization_spec.rb
deleted file mode 100644
index d14e9f61332..00000000000
--- a/core/spec/models/return_authorization_spec.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ReturnAuthorization do
- let(:inventory_unit) { Spree::InventoryUnit.create({:variant => mock_model(Spree::Variant)}, :without_protection => true) }
- let(:order) { mock_model(Spree::Order, :inventory_units => [inventory_unit], :awaiting_return? => false) }
- let(:return_authorization) { Spree::ReturnAuthorization.new({:order => order}, :without_protection => true) }
-
- before { inventory_unit.stub(:shipped?).and_return(true) }
-
- context "save" do
- it "should be invalid when order has no inventory units" do
- inventory_unit.stub(:shipped?).and_return(false)
- return_authorization.save
- return_authorization.errors[:order].should == ["has no shipped units"]
- end
-
- it "should generate RMA number" do
- return_authorization.should_receive(:generate_number)
- return_authorization.save
- end
- end
-
- context "add_variant" do
- context "on empty rma" do
- it "should associate inventory unit" do
- order.stub(:authorize_return!)
- return_authorization.add_variant(inventory_unit.variant.id, 1)
- return_authorization.inventory_units.size.should == 1
- inventory_unit.return_authorization.should == return_authorization
- end
-
- it "should update order state" do
- order.should_receive(:authorize_return!)
- return_authorization.add_variant(inventory_unit.variant.id, 1)
- end
- end
-
- context "on rma that already has inventory_units" do
- let(:inventory_unit_2) { Spree::InventoryUnit.create({:variant => inventory_unit.variant}, :without_protection => true) }
- before { order.stub(:inventory_units => [inventory_unit, inventory_unit_2], :awaiting_return? => true) }
-
- it "should associate inventory unit" do
- order.stub(:authorize_return!)
- return_authorization.add_variant(inventory_unit.variant.id, 2)
- return_authorization.inventory_units.size.should == 2
- inventory_unit_2.return_authorization.should == return_authorization
- end
-
- it "should not update order state" do
- order.should_not_receive(:authorize_return!)
- return_authorization.add_variant(inventory_unit.variant.id, 1)
- end
-
- end
-
- end
-
- context "can_receive?" do
- it "should allow_receive when inventory units assigned" do
- return_authorization.stub(:inventory_units => [inventory_unit])
- return_authorization.can_receive?.should be_true
- end
-
- it "should not allow_receive with no inventory units" do
- return_authorization.can_receive?.should be_false
- end
- end
-
- context "receive!" do
- before do
- inventory_unit.stub(:state => "shipped", :return! => true)
- return_authorization.stub(:inventory_units => [inventory_unit], :amount => -20)
- Spree::Adjustment.stub(:create)
- order.stub(:update!)
- end
-
- it "should mark all inventory units are returned" do
- inventory_unit.should_receive(:return!)
- return_authorization.receive!
- end
-
- it "should add credit for specified amount" do
- mock_adjustment = mock
- mock_adjustment.should_receive(:source=).with(return_authorization)
- mock_adjustment.should_receive(:adjustable=).with(order)
- mock_adjustment.should_receive(:save)
- Spree::Adjustment.should_receive(:new).with(:amount => -20, :label => I18n.t(:rma_credit)).and_return(mock_adjustment)
- return_authorization.receive!
- end
-
- it "should update order state" do
- order.should_receive :update!
- return_authorization.receive!
- end
- end
-
- context "force_positive_amount" do
- it "should ensure the amount is always positive" do
- return_authorization.amount = -10
- return_authorization.send :force_positive_amount
- return_authorization.amount.should == 10
- end
- end
-
- context "after_save" do
- it "should run correct callbacks" do
- return_authorization.should_receive(:force_positive_amount)
- return_authorization.run_callbacks(:save, :after)
- end
- end
-
- context "currency" do
- before { order.stub(:currency) { "ABC" } }
- it "returns the order currency" do
- return_authorization.currency.should == "ABC"
- end
- end
-
- context "display_amount" do
- it "retuns a Spree::Money" do
- return_authorization.amount = 21.22
- return_authorization.display_amount.should == Spree::Money.new(21.22)
- end
- end
-end
diff --git a/core/spec/models/shipment_spec.rb b/core/spec/models/shipment_spec.rb
deleted file mode 100644
index ecc76d89f8d..00000000000
--- a/core/spec/models/shipment_spec.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Shipment do
- before(:each) do
- reset_spree_preferences
- end
-
- let(:order) { mock_model Spree::Order, :backordered? => false, :complete? => true, :currency => "USD" }
- let(:shipping_method) { mock_model Spree::ShippingMethod, :calculator => mock('calculator') }
- let(:shipment) do
- shipment = Spree::Shipment.new :order => order, :shipping_method => shipping_method
- shipment.state = 'pending'
- shipment
- end
-
- let(:charge) { mock_model Spree::Adjustment, :amount => 10, :source => shipment }
-
- context "#cost" do
- it "should return the amount of any shipping charges that it originated" do
- shipment.stub_chain :adjustment, :amount => 10
- shipment.cost.should == 10
- end
-
- it "should return 0 if there are no relevant shipping adjustments" do
- shipment.cost.should == 0
- end
- end
-
- context "#update!" do
-
- shared_examples_for "immutable once shipped" do
- it "should remain in shipped state once shipped" do
- shipment.state = "shipped"
- shipment.should_receive(:update_column).with("state", "shipped")
- shipment.update!(order)
- end
- end
-
- shared_examples_for "pending if backordered" do
- it "should have a state of pending if backordered" do
- shipment.stub(:inventory_units => [mock_model(Spree::InventoryUnit, :backordered? => true)] )
- shipment.should_receive(:update_column).with("state", "pending")
- shipment.update!(order)
- end
- end
-
- context "when order is incomplete" do
- before { order.stub :complete? => false }
- it "should result in a 'pending' state" do
- shipment.should_receive(:update_column).with("state", "pending")
- shipment.update!(order)
- end
- end
-
- context "when order is paid" do
- before { order.stub :paid? => true }
- it "should result in a 'ready' state" do
- shipment.should_receive(:update_column).with("state", "ready")
- shipment.update!(order)
- end
- it_should_behave_like "immutable once shipped"
- it_should_behave_like "pending if backordered"
- end
-
- context "when order has balance due" do
- before { order.stub :paid? => false }
- it "should result in a 'pending' state" do
- shipment.state = 'ready'
- shipment.should_receive(:update_column).with("state", "pending")
- shipment.update!(order)
- end
- it_should_behave_like "immutable once shipped"
- it_should_behave_like "pending if backordered"
- end
-
- context "when order has a credit owed" do
- before { order.stub :payment_state => 'credit_owed', :paid? => true }
- it "should result in a 'ready' state" do
- shipment.state = 'pending'
- shipment.should_receive(:update_column).with("state", "ready")
- shipment.update!(order)
- end
- it_should_behave_like "immutable once shipped"
- it_should_behave_like "pending if backordered"
- end
-
- context "when shipment state changes to shipped" do
- it "should call after_ship" do
- shipment.state = "pending"
- shipment.should_receive :after_ship
- shipment.stub :determine_state => 'shipped'
- shipment.should_receive(:update_column).with("state", "shipped")
- shipment.update!(order)
- end
- end
- end
-
- context "when track_inventory is false" do
-
- before { Spree::Config.set :track_inventory_levels => false }
- after { Spree::Config.set :track_inventory_levels => true }
-
- it "should not use the line items from order when track_inventory_levels is false" do
- line_items = [mock_model(Spree::LineItem)]
- order.stub :complete? => true
- order.stub :line_items => line_items
- shipment.line_items.should == line_items
- end
-
- end
-
- context "when order is completed" do
- after { Spree::Config.set :track_inventory_levels => true }
-
- before do
- order.stub :completed? => true
- order.stub :canceled? => false
- end
-
-
- context "with inventory tracking" do
- before { Spree::Config.set :track_inventory_levels => true }
-
- it "should not validate without inventory" do
- shipment.valid?.should be_false
- end
-
- it "should validate with inventory" do
- shipment.inventory_units = [create(:inventory_unit)]
- shipment.valid?.should be_true
- end
-
- end
-
- context "without inventory tracking" do
- before { Spree::Config.set :track_inventory_levels => false }
-
- it "should validate with no inventory" do
- shipment.valid?.should be_true
- end
- end
-
- end
-
- context "#ship" do
- before do
- order.stub(:update!)
- shipment.stub(:require_inventory => false, :update_order => true, :state => 'ready')
- shipping_method.stub(:create_adjustment)
- end
-
- it "should update shipped_at timestamp" do
- shipment.stub(:send_shipped_email)
- shipment.ship!
- shipment.shipped_at.should_not be_nil
- # Ensure value is persisted
- shipment.reload
- shipment.shipped_at.should_not be_nil
- end
-
- it "should send a shipment email" do
- mail_message = mock "Mail::Message"
- Spree::ShipmentMailer.should_receive(:shipped_email).with(shipment).and_return mail_message
- mail_message.should_receive :deliver
- shipment.ship!
- end
- end
-
- context "#ready" do
- # Regression test for #2040
- it "cannot ready a shipment for an order if the order is unpaid" do
- order.stub(:paid? => false)
- assert !shipment.can_ready?
- end
- end
-
- context "ensure_correct_adjustment" do
- before { shipment.stub(:reload) }
-
- it "should create adjustment when not present" do
- shipping_method.should_receive(:create_adjustment).with(I18n.t(:shipping), order, shipment, true)
- shipment.send(:ensure_correct_adjustment)
- end
-
- it "should update originator when adjustment is present" do
- shipment.stub_chain(:adjustment, :originator)
- shipment.adjustment.should_receive(:originator=).with(shipping_method)
- shipment.adjustment.should_receive(:save)
- shipment.send(:ensure_correct_adjustment)
- end
- end
-
- context "update_order" do
- it "should update order" do
- order.should_receive(:update!)
- shipment.send(:update_order)
- end
- end
-
- context "after_save" do
- it "should run correct callbacks" do
- shipment.should_receive(:ensure_correct_adjustment)
- shipment.should_receive(:update_order)
- shipment.run_callbacks(:save, :after)
- end
- end
-
- context "currency" do
- it "returns the order currency" do
- shipment.currency.should == order.currency
- end
- end
-
- context "display_cost" do
- it "retuns a Spree::Money" do
- shipment.stub(:cost) { 21.22 }
- shipment.display_cost.should == Spree::Money.new(21.22)
- end
- end
-end
diff --git a/core/spec/models/shipping_category_spec.rb b/core/spec/models/shipping_category_spec.rb
deleted file mode 100644
index 089f5afc159..00000000000
--- a/core/spec/models/shipping_category_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ShippingCategory do
-
-end
diff --git a/core/spec/models/shipping_method_spec.rb b/core/spec/models/shipping_method_spec.rb
deleted file mode 100644
index 7b9375d8194..00000000000
--- a/core/spec/models/shipping_method_spec.rb
+++ /dev/null
@@ -1,221 +0,0 @@
-require 'spec_helper'
-
-describe Spree::ShippingMethod do
- context 'factory' do
- let(:shipping_method){ Factory :shipping_method }
-
- it "should set calculable correctly" do
- shipping_method.calculator.calculable.should == shipping_method
- end
- end
-
- context 'available?' do
- before(:each) do
- @shipping_method = create(:shipping_method)
- variant = create(:variant, :product => create(:product))
- @order = create(:order, :line_items => [create(:line_item, :variant => variant)])
- end
-
- context "when the calculator is not available" do
- before { @shipping_method.calculator.stub(:available? => false) }
-
- it "should be false" do
- @shipping_method.available?(@order).should be_false
- end
- end
-
- context "when the calculator is available" do
- before { @shipping_method.calculator.stub(:available? => true) }
-
- it "should be true" do
- @shipping_method.available?(@order).should be_true
- end
- end
-
- context "whe the calculator is front_end" do
- before { @shipping_method.display_on = 'front_end' }
-
- context "and the order is processed through the front_end" do
- it "should be true" do
- @shipping_method.available?(@order, :front_end).should be_true
- end
-
- it "should be false" do
- @shipping_method.available?(@order, :back_end).should be_false
- end
- end
- end
-
- context "whe the calculator is back_end" do
- before { @shipping_method.display_on = 'back_end' }
-
- context "and the order is processed through the back_end" do
- it "should be false" do
- @shipping_method.available?(@order, :front_end).should be_false
- end
-
- it "should be true" do
- @shipping_method.available?(@order, :back_end).should be_true
- end
- end
- end
- end
-
- context 'available_to_order?' do
- before(:each) do
- @shipping_method = create(:shipping_method)
- @shipping_method.zone.stub(:include? => true)
- @shipping_method.stub(:available? => true)
- variant = create(:variant, :product => create(:product))
- @order = create(:order, :line_items => [create(:line_item, :variant => variant)])
- end
-
- context "when availability_check is false" do
- before { @shipping_method.stub(:available? => false) }
-
- it "should be false" do
- @shipping_method.available_to_order?(@order).should be_false
- end
- end
-
- context "when zone_check is false" do
- before { @shipping_method.zone.stub(:include? => false) }
-
- it "should be false" do
- @shipping_method.available_to_order?(@order).should be_false
- end
- end
-
- context "when category_check is false" do
- before { @shipping_method.stub(:category_match? => false) }
-
- it "should be false" do
- @shipping_method.available_to_order?(@order).should be_false
- end
- end
-
- context "when currency_check is false" do
- before { @shipping_method.stub(:currency_match? => false) }
-
- it "should be false" do
- @shipping_method.available_to_order?(@order).should be_false
- end
- end
-
- context "when all checks are true" do
- it "should be true" do
- @shipping_method.available_to_order?(@order).should be_true
- end
- end
- end
-
- context "#category_match?" do
- context "when no category is specified" do
- before(:each) do
- @shipping_method = create(:shipping_method)
- end
-
- it "should return true" do
- @shipping_method.category_match?(create(:order)).should be_true
- end
- end
-
- context "when a category is specified" do
- before { @shipping_method = create(:shipping_method_with_category) }
-
- context "when all products match" do
- before(:each) do
- variant = create(:variant, :product => create(:product, :shipping_category => @shipping_method.shipping_category))
- @order = create(:order, :line_items => [create(:line_item, :variant => variant)])
- end
-
- context "when rule is every match" do
- before { @shipping_method.match_all = true }
-
- it "should return true" do
- @shipping_method.category_match?(@order).should be_true
- end
- end
-
- context "when rule is at least one match" do
- before { @shipping_method.match_one = true }
-
- it "should return true" do
- @shipping_method.category_match?(@order).should be_true
- end
- end
-
- context "when rule is none match" do
- before { @shipping_method.match_none = true }
-
- it "should return false" do
- @shipping_method.category_match?(@order).should be_false
- end
- end
- end
-
- context "when no products match" do
- before(:each) do
- variant = create(:variant, :product => create(:product, :shipping_category => create(:shipping_category)))
- @order = create(:order, :line_items => [create(:line_item, :variant => variant)])
- end
-
- context "when rule is every match" do
- before { @shipping_method.match_all = true }
-
- it "should return false" do
- @shipping_method.category_match?(@order).should be_false
- end
- end
-
- context "when rule is at least one match" do
- before { @shipping_method.match_one = true }
-
- it "should return false" do
- @shipping_method.category_match?(@order).should be_false
- end
- end
-
- context "when rule is none match" do
- before { @shipping_method.match_none = true }
-
- it "should return true" do
- @shipping_method.category_match?(@order).should be_true
- end
- end
- end
-
- context "when some products match" do
- before(:each) do
- variant1 = create(:variant, :product => create(:product, :shipping_category => @shipping_method.shipping_category))
- variant2 = create(:variant, :product => create(:product, :shipping_category => create(:shipping_category)))
- @order = create(:order, :line_items => [create(:line_item, :variant => variant1), create(:line_item, :variant => variant2)])
- end
-
- context "when rule is every match" do
- before { @shipping_method.match_all = true }
-
- it "should return false" do
- @shipping_method.category_match?(@order).should be_false
- end
- end
-
- context "when rule is at least one match" do
- before { @shipping_method.match_one = true }
-
- it "should return true" do
- @shipping_method.category_match?(@order).should be_true
- end
- end
-
- context "when rule is none match" do
- before { @shipping_method.match_none = true }
-
- it "should return false" do
- @shipping_method.category_match?(@order).should be_false
- end
- end
- end
- end
- end
-end
diff --git a/core/spec/models/shipping_rate_spec.rb b/core/spec/models/shipping_rate_spec.rb
deleted file mode 100644
index e30524864cb..00000000000
--- a/core/spec/models/shipping_rate_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-
-describe Spree::ShippingRate do
- let(:shipping_rate) { Spree::ShippingRate.new(:cost => 10.55) }
- before { Spree::TaxRate.stub(:default => 0.05) }
-
- context "#display_price" do
- context "when shipment includes VAT" do
- before { Spree::Config[:shipment_inc_vat] = true }
- it "displays the correct price" do
- shipping_rate.display_price.to_s.should == "$11.08" # $10.55 * 1.05 == $11.08
- end
- end
-
- context "when shipment does not include VAT" do
- before { Spree::Config[:shipment_inc_vat] = false }
- it "displays the correct price" do
- shipping_rate.display_price.to_s.should == "$10.55"
- end
- end
-
- context "when the currency is JPY" do
- let(:shipping_rate) { Spree::ShippingRate.new(:cost => 205, :currency => "JPY") }
-
- it "displays the price in yen" do
- shipping_rate.display_price.to_s.should == "¥205"
- end
- end
- end
-end
diff --git a/core/spec/models/spree/ability_spec.rb b/core/spec/models/spree/ability_spec.rb
new file mode 100644
index 00000000000..9007a7cfca6
--- /dev/null
+++ b/core/spec/models/spree/ability_spec.rb
@@ -0,0 +1,246 @@
+require 'spec_helper'
+require 'cancan/matchers'
+require 'spree/testing_support/ability_helpers'
+require 'spree/testing_support/bar_ability'
+
+# Fake ability for testing registration of additional abilities
+class FooAbility
+ include CanCan::Ability
+
+ def initialize(user)
+ # allow anyone to perform index on Order
+ can :index, Spree::Order
+ # allow anyone to update an Order with id of 1
+ can :update, Spree::Order do |order|
+ order.id == 1
+ end
+ end
+end
+
+describe Spree::Ability, :type => :model do
+ let(:user) { create(:user) }
+ let(:ability) { Spree::Ability.new(user) }
+ let(:token) { nil }
+
+ before do
+ user.spree_roles.clear
+ end
+
+ TOKEN = 'token123'
+
+ after(:each) {
+ Spree::Ability.abilities = Set.new
+ user.spree_roles = []
+ }
+
+ context 'register_ability' do
+ it 'should add the ability to the list of abilties' do
+ Spree::Ability.register_ability(FooAbility)
+ expect(Spree::Ability.new(user).abilities).not_to be_empty
+ end
+
+ it 'should apply the registered abilities permissions' do
+ Spree::Ability.register_ability(FooAbility)
+ expect(Spree::Ability.new(user).can?(:update, mock_model(Spree::Order, :id => 1))).to be true
+ end
+ end
+
+ context 'for general resource' do
+ let(:resource) { Object.new }
+
+ context 'with admin user' do
+ before(:each) { allow(user).to receive(:has_spree_role?).and_return(true) }
+ it_should_behave_like 'access granted'
+ it_should_behave_like 'index allowed'
+ end
+
+ context 'with customer' do
+ it_should_behave_like 'access denied'
+ it_should_behave_like 'no index allowed'
+ end
+ end
+
+ context 'for admin protected resources' do
+ let(:resource) { Object.new }
+ let(:resource_shipment) { Spree::Shipment.new }
+ let(:resource_product) { Spree::Product.new }
+ let(:resource_user) { Spree.user_class.new }
+ let(:resource_order) { Spree::Order.new }
+ let(:fakedispatch_user) { Spree.user_class.new }
+ let(:fakedispatch_ability) { Spree::Ability.new(fakedispatch_user) }
+
+ context 'with admin user' do
+ it 'should be able to admin' do
+ user.spree_roles << Spree::Role.find_or_create_by(name: 'admin')
+ expect(ability).to be_able_to :admin, resource
+ expect(ability).to be_able_to :index, resource_order
+ expect(ability).to be_able_to :show, resource_product
+ expect(ability).to be_able_to :create, resource_user
+ end
+ end
+
+ context 'with fakedispatch user' do
+ it 'should be able to admin on the order and shipment pages' do
+ user.spree_roles << Spree::Role.find_or_create_by(name: 'bar')
+
+ Spree::Ability.register_ability(BarAbility)
+
+ expect(ability).not_to be_able_to :admin, resource
+
+ expect(ability).to be_able_to :admin, resource_order
+ expect(ability).to be_able_to :index, resource_order
+ expect(ability).not_to be_able_to :update, resource_order
+ # ability.should_not be_able_to :create, resource_order # Fails
+
+ expect(ability).to be_able_to :admin, resource_shipment
+ expect(ability).to be_able_to :index, resource_shipment
+ expect(ability).to be_able_to :create, resource_shipment
+
+ expect(ability).not_to be_able_to :admin, resource_product
+ expect(ability).not_to be_able_to :update, resource_product
+ # ability.should_not be_able_to :show, resource_product # Fails
+
+ expect(ability).not_to be_able_to :admin, resource_user
+ expect(ability).not_to be_able_to :update, resource_user
+ expect(ability).to be_able_to :update, user
+ # ability.should_not be_able_to :create, resource_user # Fails
+ # It can create new users if is has access to the :admin, User!!
+
+ # TODO change the Ability class so only users and customers get the extra premissions?
+
+ Spree::Ability.remove_ability(BarAbility)
+ end
+ end
+
+ context 'with customer' do
+ it 'should not be able to admin' do
+ expect(ability).not_to be_able_to :admin, resource
+ expect(ability).not_to be_able_to :admin, resource_order
+ expect(ability).not_to be_able_to :admin, resource_product
+ expect(ability).not_to be_able_to :admin, resource_user
+ end
+ end
+ end
+
+ context 'as Guest User' do
+
+ context 'for Country' do
+ let(:resource) { Spree::Country.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for OptionType' do
+ let(:resource) { Spree::OptionType.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for OptionValue' do
+ let(:resource) { Spree::OptionType.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for Order' do
+ let(:resource) { Spree::Order.new }
+
+ context 'requested by same user' do
+ before(:each) { resource.user = user }
+ it_should_behave_like 'access granted'
+ it_should_behave_like 'no index allowed'
+ end
+
+ context 'requested by other user' do
+ before(:each) { resource.user = Spree.user_class.new }
+ it_should_behave_like 'create only'
+ end
+
+ context 'requested with proper token' do
+ let(:token) { 'TOKEN123' }
+ before(:each) { allow(resource).to receive_messages guest_token: 'TOKEN123' }
+ it_should_behave_like 'access granted'
+ it_should_behave_like 'no index allowed'
+ end
+
+ context 'requested with inproper token' do
+ let(:token) { 'FAIL' }
+ before(:each) { allow(resource).to receive_messages guest_token: 'TOKEN123' }
+ it_should_behave_like 'create only'
+ end
+ end
+
+ context 'for Product' do
+ let(:resource) { Spree::Product.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for ProductProperty' do
+ let(:resource) { Spree::Product.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for Property' do
+ let(:resource) { Spree::Product.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for State' do
+ let(:resource) { Spree::State.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for Taxons' do
+ let(:resource) { Spree::Taxon.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for Taxonomy' do
+ let(:resource) { Spree::Taxonomy.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for User' do
+ context 'requested by same user' do
+ let(:resource) { user }
+ it_should_behave_like 'access granted'
+ it_should_behave_like 'no index allowed'
+ end
+ context 'requested by other user' do
+ let(:resource) { Spree.user_class.new }
+ it_should_behave_like 'create only'
+ end
+ end
+
+ context 'for Variant' do
+ let(:resource) { Spree::Variant.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ context 'for Zone' do
+ let(:resource) { Spree::Zone.new }
+ context 'requested by any user' do
+ it_should_behave_like 'read only'
+ end
+ end
+
+ end
+
+end
diff --git a/core/spec/models/spree/address_spec.rb b/core/spec/models/spree/address_spec.rb
new file mode 100644
index 00000000000..9622b8f65a5
--- /dev/null
+++ b/core/spec/models/spree/address_spec.rb
@@ -0,0 +1,284 @@
+require 'spec_helper'
+
+describe Spree::Address, :type => :model do
+
+ subject { Spree::Address }
+
+ describe "clone" do
+ it "creates a copy of the address with the exception of the id, updated_at and created_at attributes" do
+ state = create(:state)
+ original = create(:address,
+ :address1 => 'address1',
+ :address2 => 'address2',
+ :alternative_phone => 'alternative_phone',
+ :city => 'city',
+ :country => Spree::Country.first,
+ :firstname => 'firstname',
+ :lastname => 'lastname',
+ :company => 'company',
+ :phone => 'phone',
+ :state_id => state.id,
+ :state_name => state.name,
+ :zipcode => '10001')
+
+ cloned = original.clone
+
+ expect(cloned.address1).to eq(original.address1)
+ expect(cloned.address2).to eq(original.address2)
+ expect(cloned.alternative_phone).to eq(original.alternative_phone)
+ expect(cloned.city).to eq(original.city)
+ expect(cloned.country_id).to eq(original.country_id)
+ expect(cloned.firstname).to eq(original.firstname)
+ expect(cloned.lastname).to eq(original.lastname)
+ expect(cloned.company).to eq(original.company)
+ expect(cloned.phone).to eq(original.phone)
+ expect(cloned.state_id).to eq(original.state_id)
+ expect(cloned.state_name).to eq(original.state_name)
+ expect(cloned.zipcode).to eq(original.zipcode)
+
+ expect(cloned.id).not_to eq(original.id)
+ expect(cloned.created_at).not_to eq(original.created_at)
+ expect(cloned.updated_at).not_to eq(original.updated_at)
+ end
+ end
+
+ context "aliased attributes" do
+ let(:address) { Spree::Address.new }
+
+ it "first_name" do
+ address.firstname = "Ryan"
+ expect(address.first_name).to eq("Ryan")
+ end
+
+ it "last_name" do
+ address.lastname = "Bigg"
+ expect(address.last_name).to eq("Bigg")
+ end
+ end
+
+ context "validation" do
+ before do
+ configure_spree_preferences do |config|
+ config.address_requires_state = true
+ end
+ end
+
+ let(:country) { mock_model(Spree::Country, :states => [state], :states_required => true) }
+ let(:state) { stub_model(Spree::State, :name => 'maryland', :abbr => 'md') }
+ let(:address) { build(:address, :country => country) }
+
+ before do
+ allow(country.states).to receive_messages :find_all_by_name_or_abbr => [state]
+ end
+
+ it "state_name is not nil and country does not have any states" do
+ address.state = nil
+ address.state_name = 'alabama'
+ expect(address).to be_valid
+ end
+
+ it "errors when state_name is nil" do
+ address.state_name = nil
+ address.state = nil
+ expect(address).not_to be_valid
+ end
+
+ it "full state name is in state_name and country does contain that state" do
+ address.state_name = 'alabama'
+ # called by state_validate to set up state_id.
+ # Perhaps this should be a before_validation instead?
+ expect(address).to be_valid
+ expect(address.state).not_to be_nil
+ expect(address.state_name).to be_nil
+ end
+
+ it "state abbr is in state_name and country does contain that state" do
+ address.state_name = state.abbr
+ expect(address).to be_valid
+ expect(address.state_id).not_to be_nil
+ expect(address.state_name).to be_nil
+ end
+
+ it "state is entered but country does not contain that state" do
+ address.state = state
+ address.country = stub_model(Spree::Country, :states_required => true)
+ address.valid?
+ expect(address.errors["state"]).to eq(['is invalid'])
+ end
+
+ it "both state and state_name are entered but country does not contain the state" do
+ address.state = state
+ address.state_name = 'maryland'
+ address.country = stub_model(Spree::Country, :states_required => true)
+ expect(address).to be_valid
+ expect(address.state_id).to be_nil
+ end
+
+ it "both state and state_name are entered and country does contain the state" do
+ address.state = state
+ address.state_name = 'maryland'
+ expect(address).to be_valid
+ expect(address.state_name).to be_nil
+ end
+
+ it "address_requires_state preference is false" do
+ Spree::Config.set :address_requires_state => false
+ address.state = nil
+ address.state_name = nil
+ expect(address).to be_valid
+ end
+
+ it "requires phone" do
+ address.phone = ""
+ address.valid?
+ expect(address.errors["phone"]).to eq(["can't be blank"])
+ end
+
+ it "requires zipcode" do
+ address.zipcode = ""
+ address.valid?
+ expect(address.errors['zipcode']).to include("can't be blank")
+ end
+
+ context "zipcode validation" do
+ it "validates the zipcode" do
+ allow(address.country).to receive(:iso).and_return('US')
+ address.zipcode = 'abc'
+ address.valid?
+ expect(address.errors['zipcode']).to include('is invalid')
+ end
+
+ context 'does not validate' do
+ it 'does not have a country' do
+ address.country = nil
+ address.valid?
+ expect(address.errors['zipcode']).not_to include('is invalid')
+ end
+
+ it 'does not have an iso' do
+ allow(address.country).to receive(:iso).and_return(nil)
+ address.valid?
+ expect(address.errors['zipcode']).not_to include('is invalid')
+ end
+
+ it 'does not have a zipcode' do
+ address.zipcode = ""
+ address.valid?
+ expect(address.errors['zipcode']).not_to include('is invalid')
+ end
+
+ it 'does not have a supported country iso' do
+ allow(address.country).to receive(:iso).and_return('BO')
+ address.valid?
+ expect(address.errors['zipcode']).not_to include('is invalid')
+ end
+ end
+ end
+
+ context "phone not required" do
+ before { allow(address).to receive_messages require_phone?: false }
+
+ it "shows no errors when phone is blank" do
+ address.phone = ""
+ address.valid?
+ expect(address.errors[:phone].size).to eq 0
+ end
+ end
+
+ context "zipcode not required" do
+ before { allow(address).to receive_messages require_zipcode?: false }
+
+ it "shows no errors when phone is blank" do
+ address.zipcode = ""
+ address.valid?
+ expect(address.errors[:zipcode].size).to eq 0
+ end
+ end
+ end
+
+ context ".default" do
+ context "no user given" do
+ before do
+ @default_country_id = Spree::Config[:default_country_id]
+ new_country = create(:country)
+ Spree::Config[:default_country_id] = new_country.id
+ end
+
+ after do
+ Spree::Config[:default_country_id] = @default_country_id
+ end
+
+ it "sets up a new record with Spree::Config[:default_country_id]" do
+ expect(Spree::Address.default.country).to eq(Spree::Country.find(Spree::Config[:default_country_id]))
+ end
+
+ # Regression test for #1142
+ it "uses the first available country if :default_country_id is set to an invalid value" do
+ Spree::Config[:default_country_id] = "0"
+ expect(Spree::Address.default.country).to eq(Spree::Country.first)
+ end
+ end
+
+ context "user given" do
+ let(:bill_address) { Spree::Address.new(phone: Time.now.to_i) }
+ let(:ship_address) { double("ShipAddress") }
+ let(:user) { double("User", bill_address: bill_address, ship_address: ship_address) }
+
+ it "returns a copy of that user bill address" do
+ expect(subject.default(user).phone).to eq bill_address.phone
+ end
+
+ it "falls back to build default when user has no address" do
+ allow(user).to receive_messages(bill_address: nil)
+ expect(subject.default(user)).to eq subject.build_default
+ end
+ end
+ end
+
+ context '#full_name' do
+ context 'both first and last names are present' do
+ let(:address) { stub_model(Spree::Address, :firstname => 'Michael', :lastname => 'Jackson') }
+ specify { expect(address.full_name).to eq('Michael Jackson') }
+ end
+
+ context 'first name is blank' do
+ let(:address) { stub_model(Spree::Address, :firstname => nil, :lastname => 'Jackson') }
+ specify { expect(address.full_name).to eq('Jackson') }
+ end
+
+ context 'last name is blank' do
+ let(:address) { stub_model(Spree::Address, :firstname => 'Michael', :lastname => nil) }
+ specify { expect(address.full_name).to eq('Michael') }
+ end
+
+ context 'both first and last names are blank' do
+ let(:address) { stub_model(Spree::Address, :firstname => nil, :lastname => nil) }
+ specify { expect(address.full_name).to eq('') }
+ end
+
+ end
+
+ context '#state_text' do
+ context 'state is blank' do
+ let(:address) { stub_model(Spree::Address, :state => nil, :state_name => 'virginia') }
+ specify { expect(address.state_text).to eq('virginia') }
+ end
+
+ context 'both name and abbr is present' do
+ let(:state) { stub_model(Spree::State, :name => 'virginia', :abbr => 'va') }
+ let(:address) { stub_model(Spree::Address, :state => state) }
+ specify { expect(address.state_text).to eq('va') }
+ end
+
+ context 'only name is present' do
+ let(:state) { stub_model(Spree::State, :name => 'virginia', :abbr => nil) }
+ let(:address) { stub_model(Spree::Address, :state => state) }
+ specify { expect(address.state_text).to eq('virginia') }
+ end
+ end
+
+ context "defines require_phone? helper method" do
+ let(:address) { stub_model(Spree::Address) }
+ specify { expect(address.instance_eval{ require_phone? }).to be true}
+ end
+end
diff --git a/core/spec/models/spree/adjustment_spec.rb b/core/spec/models/spree/adjustment_spec.rb
new file mode 100644
index 00000000000..49cfd813c76
--- /dev/null
+++ b/core/spec/models/spree/adjustment_spec.rb
@@ -0,0 +1,140 @@
+# encoding: utf-8
+#
+
+require 'spec_helper'
+
+describe Spree::Adjustment, :type => :model do
+
+ let(:order) { Spree::Order.new }
+
+ before do
+ allow(order).to receive(:update!)
+ end
+
+ let(:adjustment) { Spree::Adjustment.create!(label: 'Adjustment', adjustable: order, order: order, amount: 5) }
+
+ context '#create & #destroy' do
+ let(:adjustment) { Spree::Adjustment.new(label: "Adjustment", amount: 5, order: order, adjustable: create(:line_item)) }
+
+ it 'calls #update_adjustable_adjustment_total' do
+ expect(adjustment).to receive(:update_adjustable_adjustment_total).twice
+ adjustment.save
+ adjustment.destroy
+ end
+ end
+
+ context '#save' do
+ let(:adjustment) { Spree::Adjustment.create(label: "Adjustment", amount: 5, order: order, adjustable: create(:line_item)) }
+
+ it 'touches the adjustable' do
+ expect(adjustment.adjustable).to receive(:touch)
+ adjustment.save
+ end
+ end
+
+ describe 'non_tax scope' do
+ subject do
+ Spree::Adjustment.non_tax.to_a
+ end
+
+ let!(:tax_adjustment) { create(:adjustment, order: order, source: create(:tax_rate)) }
+ let!(:non_tax_adjustment_with_source) { create(:adjustment, order: order, source_type: 'Spree::Order', source_id: nil) }
+ let!(:non_tax_adjustment_without_source) { create(:adjustment, order: order, source: nil) }
+
+ it 'select non-tax adjustments' do
+ expect(subject).to_not include tax_adjustment
+ expect(subject).to include non_tax_adjustment_with_source
+ expect(subject).to include non_tax_adjustment_without_source
+ end
+ end
+
+ context "adjustment state" do
+ let(:adjustment) { create(:adjustment, order: order, state: 'open') }
+
+ context "#closed?" do
+ it "is true when adjustment state is closed" do
+ adjustment.state = "closed"
+ expect(adjustment).to be_closed
+ end
+
+ it "is false when adjustment state is open" do
+ adjustment.state = "open"
+ expect(adjustment).to_not be_closed
+ end
+ end
+ end
+
+ context '#currency' do
+ it 'returns the globally configured currency' do
+ expect(adjustment.currency).to eq 'USD'
+ end
+ end
+
+ context "#display_amount" do
+ before { adjustment.amount = 10.55 }
+
+ context "with display_currency set to true" do
+ before { Spree::Config[:display_currency] = true }
+
+ it "shows the currency" do
+ expect(adjustment.display_amount.to_s).to eq "$10.55 USD"
+ end
+ end
+
+ context "with display_currency set to false" do
+ before { Spree::Config[:display_currency] = false }
+
+ it "does not include the currency" do
+ expect(adjustment.display_amount.to_s).to eq "$10.55"
+ end
+ end
+
+ context "with currency set to JPY" do
+ context "when adjustable is set to an order" do
+ before do
+ expect(order).to receive(:currency).and_return('JPY')
+ adjustment.adjustable = order
+ end
+
+ it "displays in JPY" do
+ expect(adjustment.display_amount.to_s).to eq "¥11"
+ end
+ end
+
+ context "when adjustable is nil" do
+ it "displays in the default currency" do
+ expect(adjustment.display_amount.to_s).to eq "$10.55"
+ end
+ end
+ end
+ end
+
+ context '#update!' do
+ # Regression test for #6689
+ it "correctly calculates for adjustments with no source" do
+ expect(adjustment.update!).to eq 5
+ end
+
+ context "when adjustment is closed" do
+ before { expect(adjustment).to receive(:closed?).and_return(true) }
+
+ it "does not update the adjustment" do
+ expect(adjustment).to_not receive(:update_column)
+ adjustment.update!
+ end
+ end
+
+ context "when adjustment is open" do
+ before { expect(adjustment).to receive(:closed?).and_return(false) }
+
+ it "updates the amount" do
+ expect(adjustment).to receive(:adjustable).and_return(double("Adjustable")).at_least(1).times
+ expect(adjustment).to receive(:source).and_return(double("Source")).at_least(1).times
+ expect(adjustment.source).to receive("compute_amount").with(adjustment.adjustable).and_return(5)
+ expect(adjustment).to receive(:update_columns).with(amount: 5, updated_at: kind_of(Time))
+ adjustment.update!
+ end
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/app_configuration_spec.rb b/core/spec/models/spree/app_configuration_spec.rb
new file mode 100644
index 00000000000..880b51c6d85
--- /dev/null
+++ b/core/spec/models/spree/app_configuration_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Spree::AppConfiguration, :type => :model do
+
+ let (:prefs) { Rails.application.config.spree.preferences }
+
+ it "should be available from the environment" do
+ prefs.layout = "my/layout"
+ expect(prefs.layout).to eq "my/layout"
+ end
+
+ it "should be available as Spree::Config for legacy access" do
+ Spree::Config.layout = "my/layout"
+ expect(Spree::Config.layout).to eq "my/layout"
+ end
+
+ it "uses base searcher class by default" do
+ prefs.searcher_class = nil
+ expect(prefs.searcher_class).to eq Spree::Core::Search::Base
+ end
+
+end
+
diff --git a/core/spec/models/spree/asset_spec.rb b/core/spec/models/spree/asset_spec.rb
new file mode 100644
index 00000000000..f88bdf88018
--- /dev/null
+++ b/core/spec/models/spree/asset_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Spree::Asset, :type => :model do
+ describe "#viewable" do
+ it "touches association" do
+ product = create(:custom_product)
+ asset = Spree::Asset.create! { |a| a.viewable = product.master }
+
+ expect do
+ asset.save
+ end.to change { product.reload.updated_at }
+ end
+ end
+
+ describe "#acts_as_list scope" do
+ it "should start from first position for different viewables" do
+ asset1 = Spree::Asset.create(viewable_type: 'Spree::Image', viewable_id: 1)
+ asset2 = Spree::Asset.create(viewable_type: 'Spree::LineItem', viewable_id: 1)
+
+ expect(asset1.position).to eq 1
+ expect(asset2.position).to eq 1
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/calculator/default_tax_spec.rb b/core/spec/models/spree/calculator/default_tax_spec.rb
new file mode 100644
index 00000000000..57467fd78ce
--- /dev/null
+++ b/core/spec/models/spree/calculator/default_tax_spec.rb
@@ -0,0 +1,127 @@
+require 'spec_helper'
+
+describe Spree::Calculator::DefaultTax, :type => :model do
+ let!(:country) { create(:country) }
+ let!(:zone) { create(:zone, :name => "Country Zone", :default_tax => true, :zone_members => []) }
+ let!(:tax_category) { create(:tax_category, :tax_rates => []) }
+ let!(:rate) { create(:tax_rate, :tax_category => tax_category, :amount => 0.05, :included_in_price => included_in_price) }
+ let(:included_in_price) { false }
+ let!(:calculator) { Spree::Calculator::DefaultTax.new(:calculable => rate ) }
+ let!(:order) { create(:order) }
+ let!(:line_item) { create(:line_item, :price => 10, :quantity => 3, :tax_category => tax_category) }
+ let!(:shipment) { create(:shipment, :cost => 15) }
+
+ context "#compute" do
+ context "when given an order" do
+ let!(:line_item_1) { line_item }
+ let!(:line_item_2) { create(:line_item, :price => 10, :quantity => 3, :tax_category => tax_category) }
+
+ before do
+ allow(order).to receive_messages :line_items => [line_item_1, line_item_2]
+ end
+
+ context "when no line items match the tax category" do
+ before do
+ line_item_1.tax_category = nil
+ line_item_2.tax_category = nil
+ end
+
+ it "should be 0" do
+ expect(calculator.compute(order)).to eq(0)
+ end
+ end
+
+ context "when one item matches the tax category" do
+ before do
+ line_item_1.tax_category = tax_category
+ line_item_2.tax_category = nil
+ end
+
+ it "should be equal to the item total * rate" do
+ expect(calculator.compute(order)).to eq(1.5)
+ end
+
+ context "correctly rounds to within two decimal places" do
+ before do
+ line_item_1.price = 10.333
+ line_item_1.quantity = 1
+ end
+
+ specify do
+ # Amount is 0.51665, which will be rounded to...
+ expect(calculator.compute(order)).to eq(0.52)
+ end
+
+ end
+ end
+
+ context "when more than one item matches the tax category" do
+ it "should be equal to the sum of the item totals * rate" do
+ expect(calculator.compute(order)).to eq(3)
+ end
+ end
+
+ context "when tax is included in price" do
+ let(:included_in_price) { true }
+
+ it "will return the deducted amount from the totals" do
+ # total price including 5% tax = $60
+ # ex pre-tax = $57.14
+ # 57.14 + %5 = 59.997 (or "close enough" to $60)
+ # 60 - 57.14 = $2.86
+ expect(calculator.compute(order).to_f).to eql 2.86
+ end
+ end
+ end
+
+ context "when tax is included in price" do
+ let(:included_in_price) { true }
+ context "when the variant matches the tax category" do
+
+ context "when line item is discounted" do
+ before do
+ line_item.promo_total = -1
+ Spree::TaxRate.store_pre_tax_amount(line_item, [rate])
+ end
+
+ it "should be equal to the item's discounted total * rate" do
+ expect(calculator.compute(line_item)).to eql 1.38
+ end
+ end
+
+ it "should be equal to the item's full price * rate" do
+ Spree::TaxRate.store_pre_tax_amount(line_item, [rate])
+ expect(calculator.compute(line_item)).to eql 1.43
+ end
+ end
+ end
+
+ context "when tax is not included in price" do
+ context "when the line item is discounted" do
+ before { line_item.promo_total = -1 }
+
+ it "should be equal to the item's pre-tax total * rate" do
+ expect(calculator.compute(line_item)).to eq(1.45)
+ end
+ end
+
+ context "when the variant matches the tax category" do
+ it "should be equal to the item pre-tax total * rate" do
+ expect(calculator.compute(line_item)).to eq(1.50)
+ end
+ end
+ end
+
+ context "when given a shipment" do
+ it "should be 5% of 15" do
+ expect(calculator.compute(shipment)).to eq(0.75)
+ end
+
+ it "takes discounts into consideration" do
+ shipment.promo_total = -1
+ # 5% of 14
+ expect(calculator.compute(shipment)).to eq(0.7)
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/flat_percent_item_total_spec.rb b/core/spec/models/spree/calculator/flat_percent_item_total_spec.rb
new file mode 100644
index 00000000000..404f38aa2c1
--- /dev/null
+++ b/core/spec/models/spree/calculator/flat_percent_item_total_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Spree::Calculator::FlatPercentItemTotal, :type => :model do
+ let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new }
+ let(:line_item) { mock_model Spree::LineItem }
+
+ before { allow(calculator).to receive_messages preferred_flat_percent: 10 }
+
+ context "compute" do
+ it "should round result correctly" do
+ allow(line_item).to receive_messages amount: 31.08
+ expect(calculator.compute(line_item)).to eq 3.11
+
+ allow(line_item).to receive_messages amount: 31.00
+ expect(calculator.compute(line_item)).to eq 3.10
+ end
+
+ it 'returns object.amount if computed amount is greater' do
+ allow(calculator).to receive_messages preferred_flat_percent: 110
+ allow(line_item).to receive_messages amount: 30.00
+
+ expect(calculator.compute(line_item)).to eq 30.0
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/flat_rate_spec.rb b/core/spec/models/spree/calculator/flat_rate_spec.rb
new file mode 100644
index 00000000000..40bb97e8ef8
--- /dev/null
+++ b/core/spec/models/spree/calculator/flat_rate_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Spree::Calculator::FlatRate, type: :model do
+ let(:calculator) { Spree::Calculator::FlatRate.new }
+
+ let(:order) do
+ mock_model(
+ Spree::Order, quantity: 10, currency: "USD"
+ )
+ end
+
+ context "compute" do
+ it "should compute the amount as the rate when currency matches the order's currency" do
+ calculator.preferred_amount = 25.0
+ calculator.preferred_currency = "GBP"
+ allow(order).to receive_messages currency: "GBP"
+ expect(calculator.compute(order).round(2)).to eq(25.0)
+ end
+
+ it "should compute the amount as 0 when currency does not match the order's currency" do
+ calculator.preferred_amount = 100.0
+ calculator.preferred_currency = "GBP"
+ allow(order).to receive_messages currency: "USD"
+ expect(calculator.compute(order).round(2)).to eq(0.0)
+ end
+
+ it "should compute the amount as 0 when currency is blank" do
+ calculator.preferred_amount = 100.0
+ calculator.preferred_currency = ""
+ allow(order).to receive_messages currency: "GBP"
+ expect(calculator.compute(order).round(2)).to eq(0.0)
+ end
+
+ it "should compute the amount as the rate when the currencies use different casing" do
+ calculator.preferred_amount = 100.0
+ calculator.preferred_currency = "gBp"
+ allow(order).to receive_messages currency: "GBP"
+ expect(calculator.compute(order).round(2)).to eq(100.0)
+ end
+
+ it "should compute the amount as 0 when there is no object" do
+ calculator.preferred_amount = 100.0
+ calculator.preferred_currency = "GBP"
+ expect(calculator.compute.round(2)).to eq(0.0)
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/flexi_rate_spec.rb b/core/spec/models/spree/calculator/flexi_rate_spec.rb
new file mode 100644
index 00000000000..1d720a814fa
--- /dev/null
+++ b/core/spec/models/spree/calculator/flexi_rate_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Spree::Calculator::FlexiRate, :type => :model do
+ let(:calculator) { Spree::Calculator::FlexiRate.new }
+
+ let(:order) do
+ mock_model(
+ Spree::Order, quantity: 10
+ )
+ end
+
+ context "compute" do
+ it "should compute amount correctly when all fees are 0" do
+ expect(calculator.compute(order).round(2)).to eq(0.0)
+ end
+
+ it "should compute amount correctly when first_item has a value" do
+ allow(calculator).to receive_messages :preferred_first_item => 1.0
+ expect(calculator.compute(order).round(2)).to eq(1.0)
+ end
+
+ it "should compute amount correctly when additional_items has a value" do
+ allow(calculator).to receive_messages :preferred_additional_item => 1.0
+ expect(calculator.compute(order).round(2)).to eq(9.0)
+ end
+
+ it "should compute amount correctly when additional_items and first_item have values" do
+ allow(calculator).to receive_messages :preferred_first_item => 5.0, :preferred_additional_item => 1.0
+ expect(calculator.compute(order).round(2)).to eq(14.0)
+ end
+
+ it "should compute amount correctly when additional_items and first_item have values AND max items has value" do
+ allow(calculator).to receive_messages :preferred_first_item => 5.0, :preferred_additional_item => 1.0, :preferred_max_items => 3
+ expect(calculator.compute(order).round(2)).to eq(7.0)
+ end
+
+ it "should allow creation of new object with all the attributes" do
+ Spree::Calculator::FlexiRate.new(:preferred_first_item => 1, :preferred_additional_item => 1, :preferred_max_items => 1)
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/percent_on_line_item_spec.rb b/core/spec/models/spree/calculator/percent_on_line_item_spec.rb
new file mode 100644
index 00000000000..88f8aa095af
--- /dev/null
+++ b/core/spec/models/spree/calculator/percent_on_line_item_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+module Spree
+ class Calculator
+ describe PercentOnLineItem, :type => :model do
+ let(:line_item) { double("LineItem", amount: 100) }
+
+ before { subject.preferred_percent = 15 }
+
+ it "computes based on item price and quantity" do
+ expect(subject.compute(line_item)).to eq 15
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/price_sack_spec.rb b/core/spec/models/spree/calculator/price_sack_spec.rb
new file mode 100644
index 00000000000..d4105c50089
--- /dev/null
+++ b/core/spec/models/spree/calculator/price_sack_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Spree::Calculator::PriceSack, :type => :model do
+ let(:calculator) do
+ calculator = Spree::Calculator::PriceSack.new
+ calculator.preferred_minimal_amount = 5
+ calculator.preferred_normal_amount = 10
+ calculator.preferred_discount_amount = 1
+ calculator
+ end
+
+ let(:order) { stub_model(Spree::Order) }
+ let(:shipment) { stub_model(Spree::Shipment, :amount => 10) }
+
+ # Regression test for #714 and #739
+ it "computes with an order object" do
+ calculator.compute(order)
+ end
+
+ # Regression test for #1156
+ it "computes with a shipment object" do
+ calculator.compute(shipment)
+ end
+
+ # Regression test for #2055
+ it "computes the correct amount" do
+ expect(calculator.compute(2)).to eq(calculator.preferred_normal_amount)
+ expect(calculator.compute(6)).to eq(calculator.preferred_discount_amount)
+ end
+end
diff --git a/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb b/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb
new file mode 100644
index 00000000000..4c9a94eebbc
--- /dev/null
+++ b/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Spree::Calculator::Returns::DefaultRefundAmount, :type => :model do
+ let(:order) { create(:order) }
+ let(:line_item_quantity) { 2 }
+ let(:pre_tax_amount) { 100.0 }
+ let(:line_item) { create(:line_item, price: 100.0, quantity: line_item_quantity, pre_tax_amount: pre_tax_amount) }
+ let(:inventory_unit) { build(:inventory_unit, order: order, line_item: line_item) }
+ let(:return_item) { build(:return_item, inventory_unit: inventory_unit ) }
+ let(:calculator) { Spree::Calculator::Returns::DefaultRefundAmount.new }
+
+ before { order.line_items << line_item }
+
+ subject { calculator.compute(return_item) }
+
+ context "not an exchange" do
+ context "no promotions or taxes" do
+ it { is_expected.to eq pre_tax_amount / line_item_quantity }
+ end
+
+ context "order adjustments" do
+ let(:adjustment_amount) { -10.0 }
+
+ before do
+ order.adjustments << create(:adjustment, order: order, amount: adjustment_amount, eligible: true, label: 'Adjustment', source_type: 'Spree::Order')
+ order.adjustments.first.update_attributes(amount: adjustment_amount)
+ end
+
+ it { is_expected.to eq (pre_tax_amount - adjustment_amount.abs) / line_item_quantity }
+ end
+
+ context "shipping adjustments" do
+ let(:adjustment_total) { -50.0 }
+
+ before { order.shipments << Spree::Shipment.new(adjustment_total: adjustment_total) }
+
+ it { is_expected.to eq pre_tax_amount / line_item_quantity }
+ end
+ end
+
+ context "an exchange" do
+ let(:return_item) { build(:exchange_return_item) }
+
+ it { is_expected.to eq 0.0 }
+ end
+
+ context "pre_tax_amount is zero" do
+ let(:pre_tax_amount) { 0.0 }
+ it { should eq 0.0 }
+ end
+end
diff --git a/core/spec/models/spree/calculator/shipping.rb b/core/spec/models/spree/calculator/shipping.rb
new file mode 100644
index 00000000000..cb1e78a6b3d
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping.rb
@@ -0,0 +1,8 @@
+require_dependency 'spree/calculator'
+
+module Spree
+ class Calculator::Shipping < Calculator
+ def compute(content_items)
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb b/core/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb
new file mode 100644
index 00000000000..a0e5323bff3
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+module Spree
+ module Calculator::Shipping
+ describe FlatPercentItemTotal, :type => :model do
+ let(:variant1) { build(:variant, :price => 10.11) }
+ let(:variant2) { build(:variant, :price => 20.2222) }
+
+ let(:line_item1) { build(:line_item, variant: variant1) }
+ let(:line_item2) { build(:line_item, variant: variant2) }
+
+ let(:package) do
+ build(:stock_package, variants_contents: { variant1 => 2, variant2 => 1 })
+ end
+
+ subject { FlatPercentItemTotal.new(:preferred_flat_percent => 10) }
+
+ it "should round result correctly" do
+ expect(subject.compute(package)).to eq(4.04)
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/shipping/flat_rate_spec.rb b/core/spec/models/spree/calculator/shipping/flat_rate_spec.rb
new file mode 100644
index 00000000000..41b84ed9c9b
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping/flat_rate_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+module Spree
+ module Calculator::Shipping
+ describe FlatRate, :type => :model do
+ subject { Calculator::Shipping::FlatRate.new(:preferred_amount => 4.00) }
+
+ it 'always returns the same rate' do
+ expect(subject.compute(build(:stock_package_fulfilled))).to eql 4.00
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/shipping/flexi_rate_spec.rb b/core/spec/models/spree/calculator/shipping/flexi_rate_spec.rb
new file mode 100644
index 00000000000..f4bda317135
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping/flexi_rate_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+module Spree
+ module Calculator::Shipping
+ describe FlexiRate, :type => :model do
+ let(:variant1) { build(:variant, :price => 10) }
+ let(:variant2) { build(:variant, :price => 20) }
+
+ let(:package) do
+ build(:stock_package, variants_contents: { variant1 => 4, variant2 => 6 })
+ end
+
+ let(:subject) { FlexiRate.new }
+
+ context "compute" do
+ it "should compute amount correctly when all fees are 0" do
+ expect(subject.compute(package).round(2)).to eq(0.0)
+ end
+
+ it "should compute amount correctly when first_item has a value" do
+ subject.preferred_first_item = 1.0
+ expect(subject.compute(package).round(2)).to eq(1.0)
+ end
+
+ it "should compute amount correctly when additional_items has a value" do
+ subject.preferred_additional_item = 1.0
+ expect(subject.compute(package).round(2)).to eq(9.0)
+ end
+
+ it "should compute amount correctly when additional_items and first_item have values" do
+ subject.preferred_first_item = 5.0
+ subject.preferred_additional_item = 1.0
+ expect(subject.compute(package).round(2)).to eq(14.0)
+ end
+
+ it "should compute amount correctly when additional_items and first_item have values AND max items has value" do
+ subject.preferred_first_item = 5.0
+ subject.preferred_additional_item = 1.0
+ subject.preferred_max_items = 3
+ expect(subject.compute(package).round(2)).to eq(26.0)
+ end
+
+ it "should allow creation of new object with all the attributes" do
+ FlexiRate.new(:preferred_first_item => 1,
+ :preferred_additional_item => 1,
+ :preferred_max_items => 1)
+ end
+ end
+ end
+ end
+end
+
diff --git a/core/spec/models/spree/calculator/shipping/per_item_spec.rb b/core/spec/models/spree/calculator/shipping/per_item_spec.rb
new file mode 100644
index 00000000000..91446f37e88
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping/per_item_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+module Spree
+ module Calculator::Shipping
+ describe PerItem, :type => :model do
+ let(:variant1) { build(:variant) }
+ let(:variant2) { build(:variant) }
+
+ let(:package) do
+ build(:stock_package, variants_contents: { variant1 => 5, variant2 => 3 })
+ end
+
+ subject { PerItem.new(:preferred_amount => 10) }
+
+ it "correctly calculates per item shipping" do
+ expect(subject.compute(package).to_f).to eq(80) # 5 x 10 + 3 x 10
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator/shipping/price_sack_spec.rb b/core/spec/models/spree/calculator/shipping/price_sack_spec.rb
new file mode 100644
index 00000000000..d4105c50089
--- /dev/null
+++ b/core/spec/models/spree/calculator/shipping/price_sack_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Spree::Calculator::PriceSack, :type => :model do
+ let(:calculator) do
+ calculator = Spree::Calculator::PriceSack.new
+ calculator.preferred_minimal_amount = 5
+ calculator.preferred_normal_amount = 10
+ calculator.preferred_discount_amount = 1
+ calculator
+ end
+
+ let(:order) { stub_model(Spree::Order) }
+ let(:shipment) { stub_model(Spree::Shipment, :amount => 10) }
+
+ # Regression test for #714 and #739
+ it "computes with an order object" do
+ calculator.compute(order)
+ end
+
+ # Regression test for #1156
+ it "computes with a shipment object" do
+ calculator.compute(shipment)
+ end
+
+ # Regression test for #2055
+ it "computes the correct amount" do
+ expect(calculator.compute(2)).to eq(calculator.preferred_normal_amount)
+ expect(calculator.compute(6)).to eq(calculator.preferred_discount_amount)
+ end
+end
diff --git a/core/spec/models/spree/calculator/tiered_flat_rate_spec.rb b/core/spec/models/spree/calculator/tiered_flat_rate_spec.rb
new file mode 100644
index 00000000000..8c974ed8a7b
--- /dev/null
+++ b/core/spec/models/spree/calculator/tiered_flat_rate_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Spree::Calculator::TieredFlatRate, :type => :model do
+ let(:calculator) { Spree::Calculator::TieredFlatRate.new }
+
+ describe "#valid?" do
+ subject { calculator.valid? }
+ context "when tiers is not a hash" do
+ before { calculator.preferred_tiers = ["nope", 0] }
+ it { is_expected.to be false }
+ end
+ context "when tiers is a hash" do
+ context "and one of the keys is not a positive number" do
+ before { calculator.preferred_tiers = { "nope" => 20 } }
+ it { is_expected.to be false }
+ end
+ end
+ end
+
+ describe "#compute" do
+ let(:line_item) { mock_model Spree::LineItem, amount: amount }
+ before do
+ calculator.preferred_base_amount = 10
+ calculator.preferred_tiers = {
+ 100 => 15,
+ 200 => 20
+ }
+ end
+ subject { calculator.compute(line_item) }
+ context "when amount falls within the first tier" do
+ let(:amount) { 50 }
+ it { is_expected.to eq 10 }
+ end
+ context "when amount falls within the second tier" do
+ let(:amount) { 150 }
+ it { is_expected.to eq 15 }
+ end
+ end
+end
+
diff --git a/core/spec/models/spree/calculator/tiered_percent_spec.rb b/core/spec/models/spree/calculator/tiered_percent_spec.rb
new file mode 100644
index 00000000000..9b2bd4b45be
--- /dev/null
+++ b/core/spec/models/spree/calculator/tiered_percent_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Spree::Calculator::TieredPercent, :type => :model do
+ let(:calculator) { Spree::Calculator::TieredPercent.new }
+
+ describe "#valid?" do
+ subject { calculator.valid? }
+ context "when base percent is less than zero" do
+ before { calculator.preferred_base_percent = -1 }
+ it { is_expected.to be false }
+ end
+ context "when base percent is greater than 100" do
+ before { calculator.preferred_base_percent = 110 }
+ it { is_expected.to be false }
+ end
+ context "when tiers is not a hash" do
+ before { calculator.preferred_tiers = ["nope", 0] }
+ it { is_expected.to be false }
+ end
+ context "when tiers is a hash" do
+ context "and one of the keys is not a positive number" do
+ before { calculator.preferred_tiers = { "nope" => 20 } }
+ it { is_expected.to be false }
+ end
+ context "and one of the values is not a percent" do
+ before { calculator.preferred_tiers = { 10 => 110 } }
+ it { is_expected.to be false }
+ end
+ end
+ end
+
+ describe "#compute" do
+ let(:line_item) { mock_model Spree::LineItem, amount: amount }
+ before do
+ calculator.preferred_base_percent = 10
+ calculator.preferred_tiers = {
+ 100 => 15,
+ 200 => 20
+ }
+ end
+ subject { calculator.compute(line_item) }
+ context "when amount falls within the first tier" do
+ let(:amount) { 50 }
+ it { is_expected.to eq 5 }
+ end
+ context "when amount falls within the second tier" do
+ let(:amount) { 150 }
+ it { is_expected.to eq 22 }
+ end
+ end
+end
diff --git a/core/spec/models/spree/calculator_spec.rb b/core/spec/models/spree/calculator_spec.rb
new file mode 100644
index 00000000000..6d68089472b
--- /dev/null
+++ b/core/spec/models/spree/calculator_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Spree::Calculator, :type => :model do
+
+ let(:order) { create(:order) }
+ let!(:line_item) { create(:line_item, :order => order) }
+ let(:shipment) { create(:shipment, :order => order, :stock_location => create(:stock_location_with_items)) }
+
+ context "with computable" do
+ context "and compute methods stubbed out" do
+ context "with a Spree::LineItem" do
+ it "calls compute_line_item" do
+ expect(subject).to receive(:compute_line_item).with(line_item)
+ subject.compute(line_item)
+ end
+ end
+
+ context "with a Spree::Order" do
+ it "calls compute_order" do
+ expect(subject).to receive(:compute_order).with(order)
+ subject.compute(order)
+ end
+ end
+
+ context "with a Spree::Shipment" do
+ it "calls compute_shipment" do
+ expect(subject).to receive(:compute_shipment).with(shipment)
+ subject.compute(shipment)
+ end
+ end
+
+ context "with a arbitray object" do
+ it "calls the correct compute" do
+ s = "Calculator can all"
+ expect(subject).to receive(:compute_string).with(s)
+ subject.compute(s)
+ end
+ end
+ end
+
+ context "with no stubbing" do
+ context "with a Spree::LineItem" do
+ it "raises NotImplementedError" do
+ expect{subject.compute(line_item)}.to raise_error NotImplementedError, /Please implement \'compute_line_item\(line_item\)\' in your calculator/
+ end
+ end
+
+ context "with a Spree::Order" do
+ it "raises NotImplementedError" do
+ expect{subject.compute(order)}.to raise_error NotImplementedError, /Please implement \'compute_order\(order\)\' in your calculator/
+ end
+ end
+
+ context "with a Spree::Shipment" do
+ it "raises NotImplementedError" do
+ expect{subject.compute(shipment)}.to raise_error NotImplementedError, /Please implement \'compute_shipment\(shipment\)\' in your calculator/
+ end
+ end
+
+ context "with a arbitray object" do
+ it "raises NotImplementedError" do
+ s = "Calculator can all"
+ expect{subject.compute(s)}.to raise_error NotImplementedError, /Please implement \'compute_string\(string\)\' in your calculator/
+ end
+ end
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/classification_spec.rb b/core/spec/models/spree/classification_spec.rb
new file mode 100644
index 00000000000..1ff5ea6bef1
--- /dev/null
+++ b/core/spec/models/spree/classification_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+module Spree
+ describe Classification, :type => :model do
+ # Regression test for #3494
+ it "cannot link the same taxon to the same product more than once" do
+ product = create(:product)
+ taxon = create(:taxon)
+ add_taxon = lambda { product.taxons << taxon }
+ expect(add_taxon).not_to raise_error
+ expect(add_taxon).to raise_error(ActiveRecord::RecordInvalid)
+ end
+
+ let(:taxon_with_5_products) do
+ products = []
+ 5.times do
+ products << create(:base_product)
+ end
+
+ create(:taxon, products: products)
+ end
+
+ def positions_to_be_valid(taxon)
+ positions = taxon.reload.classifications.map(&:position)
+ expect(positions).to eq((1..taxon.classifications.count).to_a)
+ end
+
+ it "has a valid fixtures" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ expect(Spree::Classification.count).to eq 5
+ end
+
+ context "removing product from taxon" do
+ before :each do
+ p = taxon_with_5_products.products[1]
+ expect(p.classifications.first.position).to eq(2)
+ taxon_with_5_products.products.destroy(p)
+ end
+
+ it "resets positions" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ end
+ end
+
+ context "replacing taxon's products" do
+ before :each do
+ products = taxon_with_5_products.products.to_a
+ products.pop(1)
+ taxon_with_5_products.products = products
+ taxon_with_5_products.save!
+ end
+
+ it "resets positions" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ end
+ end
+
+ context "removing taxon from product" do
+ before :each do
+ p = taxon_with_5_products.products[1]
+ p.taxons.destroy(taxon_with_5_products)
+ p.save!
+ end
+
+ it "resets positions" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ end
+ end
+
+ context "replacing product's taxons" do
+ before :each do
+ p = taxon_with_5_products.products[1]
+ p.taxons = []
+ p.save!
+ end
+
+ it "resets positions" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ end
+ end
+
+ context "destroying classification" do
+ before :each do
+ classification = taxon_with_5_products.classifications[1]
+ classification.destroy
+ end
+
+ it "resets positions" do
+ expect positions_to_be_valid(taxon_with_5_products)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/core/spec/models/spree/configuration_spec.rb b/core/spec/models/spree/configuration_spec.rb
new file mode 100644
index 00000000000..826a213b1e6
--- /dev/null
+++ b/core/spec/models/spree/configuration_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::Configuration, :type => :model do
+
+end
diff --git a/core/spec/models/spree/country_spec.rb b/core/spec/models/spree/country_spec.rb
new file mode 100644
index 00000000000..3074a2678c6
--- /dev/null
+++ b/core/spec/models/spree/country_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Spree::Country, :type => :model do
+ it "can find all countries group by states required" do
+ country_states_required= Spree::Country.create({:name => "Canada", :iso_name => "CAN", :states_required => true})
+ country_states_not_required= Spree::Country.create({:name => "France", :iso_name => "FR", :states_required => false})
+ states_required = Spree::Country.states_required_by_country_id
+ expect(states_required[country_states_required.id.to_s]).to be true
+ expect(states_required[country_states_not_required.id.to_s]).to be false
+ end
+
+ it "returns that the states are required for an invalid country" do
+ expect(Spree::Country.states_required_by_country_id['i do not exit']).to be true
+ end
+end
diff --git a/core/spec/models/spree/credit_card_spec.rb b/core/spec/models/spree/credit_card_spec.rb
new file mode 100644
index 00000000000..5f9bbb8f098
--- /dev/null
+++ b/core/spec/models/spree/credit_card_spec.rb
@@ -0,0 +1,382 @@
+require 'spec_helper'
+
+describe Spree::CreditCard, type: :model do
+ let(:valid_credit_card_attributes) do
+ {
+ number: '4111111111111111',
+ verification_value: '123',
+ expiry: "12 / #{(Time.now.year + 1).to_s.last(2)}",
+ name: 'Spree Commerce'
+ }
+ end
+
+ def self.payment_states
+ Spree::Payment.state_machine.states.keys
+ end
+
+ def stub_rails_env(environment)
+ allow(Rails).to receive_messages(env: ActiveSupport::StringInquirer.new(environment))
+ end
+
+ let(:credit_card) { Spree::CreditCard.new }
+
+ before(:each) do
+
+ @order = create(:order)
+ @payment = Spree::Payment.create(:amount => 100, :order => @order)
+
+ @success_response = double('gateway_response', success?: true, authorization: '123', avs_result: { 'code' => 'avs-code' })
+ @fail_response = double('gateway_response', success?: false)
+
+ @payment_gateway = mock_model(Spree::PaymentMethod,
+ payment_profiles_supported?: true,
+ authorize: @success_response,
+ purchase: @success_response,
+ capture: @success_response,
+ void: @success_response,
+ credit: @success_response,
+ environment: 'test'
+ )
+
+ allow(@payment).to receive_messages payment_method: @payment_gateway
+ end
+
+ context "#can_capture?" do
+ it "should be true if payment is pending" do
+ payment = mock_model(Spree::Payment, pending?: true, created_at: Time.now)
+ expect(credit_card.can_capture?(payment)).to be true
+ end
+
+ it "should be true if payment is checkout" do
+ payment = mock_model(Spree::Payment, pending?: false, checkout?: true, created_at: Time.now)
+ expect(credit_card.can_capture?(payment)).to be true
+ end
+ end
+
+ context "#can_void?" do
+ it "should be true if payment is not void" do
+ payment = mock_model(Spree::Payment, failed?: false, void?: false)
+ expect(credit_card.can_void?(payment)).to be true
+ end
+ end
+
+ context "#can_credit?" do
+ it "should be false if payment is not completed" do
+ payment = mock_model(Spree::Payment, completed?: false)
+ expect(credit_card.can_credit?(payment)).to be false
+ end
+
+ it "should be false when credit_allowed is zero" do
+ payment = mock_model(Spree::Payment, completed?: true, credit_allowed: 0, order: mock_model(Spree::Order, payment_state: 'credit_owed'))
+ expect(credit_card.can_credit?(payment)).to be false
+ end
+ end
+
+ context "#valid?" do
+ it "should validate presence of number" do
+ credit_card.attributes = valid_credit_card_attributes.except(:number)
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:number]).to eq(["can't be blank"])
+ end
+
+ it "should validate presence of security code" do
+ credit_card.attributes = valid_credit_card_attributes.except(:verification_value)
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:verification_value]).to eq(["can't be blank"])
+ end
+
+ it "validates name presence" do
+ credit_card.valid?
+ expect(credit_card.error_on(:name).size).to eq(1)
+ end
+
+ # Regression spec for #4971
+ it "should not bomb out when given an invalid expiry" do
+ credit_card.month = 13
+ credit_card.year = Time.now.year + 1
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:base]).to eq(["Card expiration is invalid"])
+ end
+
+ it "should validate expiration is not in the past" do
+ credit_card.month = 1.month.ago.month
+ credit_card.year = 1.month.ago.year
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:base]).to eq(["Card has expired"])
+ end
+
+ it "should not be expired expiring on the current month" do
+ credit_card.attributes = valid_credit_card_attributes
+ credit_card.month = Time.zone.now.month
+ credit_card.year = Time.zone.now.year
+ expect(credit_card).to be_valid
+ end
+
+ it "should handle TZ correctly" do
+ # The card is valid according to the system clock's local time
+ # (Time.now).
+ # However it has expired in rails's configured time zone (Time.current),
+ # which is the value we should be respecting.
+ time = Time.new(2014, 04, 30, 23, 0, 0, "-07:00")
+ Timecop.freeze(time) do
+ credit_card.month = 1.month.ago.month
+ credit_card.year = 1.month.ago.year
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:base]).to eq(["Card has expired"])
+ end
+ end
+
+ it "does not run expiration in the past validation if month is not set" do
+ credit_card.month = nil
+ credit_card.year = Time.now.year
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:base]).to be_blank
+ end
+
+ it "does not run expiration in the past validation if year is not set" do
+ credit_card.month = Time.now.month
+ credit_card.year = nil
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:base]).to be_blank
+ end
+
+ it "does not run expiration in the past validation if year and month are empty" do
+ credit_card.year = ""
+ credit_card.month = ""
+ expect(credit_card).not_to be_valid
+ expect(credit_card.errors[:card]).to be_blank
+ end
+
+ it "should only validate on create" do
+ credit_card.attributes = valid_credit_card_attributes
+ credit_card.save
+ expect(credit_card).to be_valid
+ end
+
+ context "encrypted data is present" do
+ it "does not validate presence of number or cvv" do
+ credit_card.encrypted_data = "$fdgsfgdgfgfdg&gfdgfdgsf-"
+ credit_card.valid?
+ expect(credit_card.errors[:number]).to be_empty
+ expect(credit_card.errors[:verification_value]).to be_empty
+ end
+ end
+
+ context "imported is true" do
+ it "does not validate presence of number or cvv" do
+ credit_card.imported = true
+ credit_card.valid?
+ expect(credit_card.errors[:number]).to be_empty
+ expect(credit_card.errors[:verification_value]).to be_empty
+ end
+ end
+ end
+
+ context "#save" do
+ before do
+ credit_card.attributes = valid_credit_card_attributes
+ credit_card.save!
+ end
+
+ let!(:persisted_card) { Spree::CreditCard.find(credit_card.id) }
+
+ it "should not actually store the number" do
+ expect(persisted_card.number).to be_blank
+ end
+
+ it "should not actually store the security code" do
+ expect(persisted_card.verification_value).to be_blank
+ end
+ end
+
+ context "#number=" do
+ it "should strip non-numeric characters from card input" do
+ credit_card.number = "6011000990139424"
+ expect(credit_card.number).to eq("6011000990139424")
+
+ credit_card.number = " 6011-0009-9013-9424 "
+ expect(credit_card.number).to eq("6011000990139424")
+ end
+
+ it "should not raise an exception on non-string input" do
+ credit_card.number = Hash.new
+ expect(credit_card.number).to be_nil
+ end
+ end
+
+ # Regression test for #3847 & #3896
+ context "#expiry=" do
+ it "can set with a 2-digit month and year" do
+ credit_card.expiry = '04 / 14'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "can set with a 2-digit month and 4-digit year" do
+ credit_card.expiry = '04 / 2014'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "can set with a 2-digit month and 4-digit year without whitespace" do
+ credit_card.expiry = '04/14'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "can set with a 2-digit month and 4-digit year without whitespace" do
+ credit_card.expiry = '04/2014'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "can set with a 2-digit month and 4-digit year without whitespace and slash" do
+ credit_card.expiry = '042014'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "can set with a 2-digit month and 2-digit year without whitespace and slash" do
+ credit_card.expiry = '0414'
+ expect(credit_card.month).to eq(4)
+ expect(credit_card.year).to eq(2014)
+ end
+
+ it "does not blow up when passed an empty string" do
+ expect { credit_card.expiry = '' }.not_to raise_error
+ end
+
+ # Regression test for #4725
+ it "does not blow up when passed one number" do
+ expect { credit_card.expiry = '12' }.not_to raise_error
+ end
+
+ end
+
+ context "#cc_type=" do
+ it "converts between the different types" do
+ credit_card.cc_type = 'mastercard'
+ expect(credit_card.cc_type).to eq('master')
+
+ credit_card.cc_type = 'maestro'
+ expect(credit_card.cc_type).to eq('master')
+
+ credit_card.cc_type = 'amex'
+ expect(credit_card.cc_type).to eq('american_express')
+
+ credit_card.cc_type = 'dinersclub'
+ expect(credit_card.cc_type).to eq('diners_club')
+
+ credit_card.cc_type = 'some_outlandish_cc_type'
+ expect(credit_card.cc_type).to eq('some_outlandish_cc_type')
+ end
+
+ it "assigns the type based on card number in the event of js failure" do
+ credit_card.number = '4242424242424242'
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('visa')
+
+ credit_card.number = '5555555555554444'
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('master')
+
+ credit_card.number = '378282246310005'
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('american_express')
+
+ credit_card.number = '30569309025904'
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('diners_club')
+
+ credit_card.number = '3530111333300000'
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('jcb')
+
+ credit_card.number = ''
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('')
+
+ credit_card.number = nil
+ credit_card.cc_type = ''
+ expect(credit_card.cc_type).to eq('')
+ end
+ end
+
+ context "#associations" do
+ it "should be able to access its payments" do
+ expect { credit_card.payments.to_a }.not_to raise_error
+ end
+ end
+
+ context "#first_name" do
+ before do
+ credit_card.name = "Ludwig van Beethoven"
+ end
+
+ it "extracts the first name" do
+ expect(credit_card.first_name).to eq "Ludwig"
+ end
+ end
+
+ context "#last_name" do
+ before do
+ credit_card.name = "Ludwig van Beethoven"
+ end
+
+ it "extracts the last name" do
+ expect(credit_card.last_name).to eq "van Beethoven"
+ end
+ end
+
+ context "#to_active_merchant" do
+ before do
+ credit_card.number = "4111111111111111"
+ credit_card.year = Time.now.year
+ credit_card.month = Time.now.month
+ credit_card.name = "Ludwig van Beethoven"
+ credit_card.verification_value = 123
+ end
+
+ it "converts to an ActiveMerchant::Billing::CreditCard object" do
+ am_card = credit_card.to_active_merchant
+ expect(am_card.number).to eq("4111111111111111")
+ expect(am_card.year).to eq(Time.now.year)
+ expect(am_card.month).to eq(Time.now.month)
+ expect(am_card.first_name).to eq("Ludwig")
+ expect(am_card.last_name).to eq("van Beethoven")
+ expect(am_card.verification_value).to eq(123)
+ end
+ end
+
+ it 'ensures only one credit card per user is default at a time' do
+ user = FactoryGirl.create(:user)
+ first = FactoryGirl.create(:credit_card, user: user, default: true)
+ second = FactoryGirl.create(:credit_card, user: user, default: true)
+
+ expect(first.reload.default).to eq false
+ expect(second.reload.default).to eq true
+
+ first.default = true
+ first.save!
+
+ expect(first.reload.default).to eq true
+ expect(second.reload.default).to eq false
+ end
+
+ it 'allows default credit cards for different users' do
+ first = FactoryGirl.create(:credit_card, user: FactoryGirl.create(:user), default: true)
+ second = FactoryGirl.create(:credit_card, user: FactoryGirl.create(:user), default: true)
+
+ expect(first.reload.default).to eq true
+ expect(second.reload.default).to eq true
+ end
+
+ it 'allows this card to save even if the previously default card has expired' do
+ user = FactoryGirl.create(:user)
+ first = FactoryGirl.create(:credit_card, user: user, default: true)
+ second = FactoryGirl.create(:credit_card, user: user, default: false)
+ first.update_columns(year: DateTime.now.year, month: 1.month.ago.month)
+
+ expect { second.update_attributes!(default: true) }.not_to raise_error
+ end
+end
diff --git a/core/spec/models/spree/customer_return_spec.rb b/core/spec/models/spree/customer_return_spec.rb
new file mode 100644
index 00000000000..5f75acd9875
--- /dev/null
+++ b/core/spec/models/spree/customer_return_spec.rb
@@ -0,0 +1,262 @@
+require 'spec_helper'
+
+describe Spree::CustomerReturn, :type => :model do
+ before do
+ allow_any_instance_of(Spree::Order).to receive_messages(return!: true)
+ end
+
+ describe ".validation" do
+ describe "#must_have_return_authorization" do
+ let(:customer_return) { build(:customer_return) }
+
+ let(:inventory_unit) { build(:inventory_unit) }
+ let(:return_item) { build(:return_item, inventory_unit: inventory_unit) }
+
+ subject { customer_return.valid? }
+
+ before do
+ customer_return.return_items << return_item
+ end
+
+ context "return item does not belong to return authorization" do
+ before do
+ return_item.return_authorization = nil
+ end
+
+ it "is not valid" do
+ expect(subject).to eq false
+ end
+
+ it "adds an error message" do
+ subject
+ expect(customer_return.errors.full_messages).to include(Spree.t(:missing_return_authorization, item_name: inventory_unit.variant.name))
+ end
+ end
+
+ context "return item belongs to return authorization" do
+ it "is valid" do
+ expect(subject).to eq true
+ end
+ end
+ end
+
+ describe "#return_items_belong_to_same_order" do
+ let(:customer_return) { build(:customer_return) }
+
+ let(:first_inventory_unit) { build(:inventory_unit) }
+ let(:first_return_item) { build(:return_item, inventory_unit: first_inventory_unit) }
+
+ let(:second_inventory_unit) { build(:inventory_unit, order: second_order) }
+ let(:second_return_item) { build(:return_item, inventory_unit: second_inventory_unit) }
+
+ subject { customer_return.valid? }
+
+ before do
+ customer_return.return_items << first_return_item
+ customer_return.return_items << second_return_item
+ end
+
+ context "return items are part of different orders" do
+ let(:second_order) { create(:order) }
+
+ it "is not valid" do
+ expect(subject).to eq false
+ end
+
+ it "adds an error message" do
+ subject
+ expect(customer_return.errors.full_messages).to include(Spree.t(:return_items_cannot_be_associated_with_multiple_orders))
+ end
+ end
+
+ context "return items are part of the same order" do
+ let(:second_order) { first_inventory_unit.order }
+
+ it "is valid" do
+ expect(subject).to eq true
+ end
+ end
+ end
+ end
+
+ describe ".before_create" do
+ describe "#generate_number" do
+ context "number is assigned" do
+ let(:customer_return) { Spree::CustomerReturn.new(number: '123') }
+
+ it "should return the assigned number" do
+ customer_return.save
+ expect(customer_return.number).to eq('123')
+ end
+ end
+
+ context "number is not assigned" do
+ let(:customer_return) { Spree::CustomerReturn.new(number: nil) }
+
+ before do
+ allow(customer_return).to receive_messages(valid?: true, process_return!: true)
+ end
+
+ it "should assign number with random CR number" do
+ customer_return.save
+ expect(customer_return.number).to match(/CR\d{9}/)
+ end
+ end
+ end
+ end
+
+ describe "#pre_tax_total" do
+ let(:pre_tax_amount) { 15.0 }
+ let(:customer_return) { create(:customer_return, line_items_count: 2) }
+
+ before do
+ Spree::ReturnItem.where(customer_return_id: customer_return.id).update_all(pre_tax_amount: pre_tax_amount)
+ end
+
+ subject { customer_return.pre_tax_total }
+
+ it "returns the sum of the return item's pre_tax_amount" do
+ expect(subject).to eq (pre_tax_amount * 2)
+ end
+ end
+
+ describe "#display_pre_tax_total" do
+ let(:customer_return) { Spree::CustomerReturn.new }
+
+ it "returns a Spree::Money" do
+ allow(customer_return).to receive_messages(pre_tax_total: 21.22)
+ expect(customer_return.display_pre_tax_total).to eq(Spree::Money.new(21.22))
+ end
+ end
+
+ describe "#order" do
+ let(:return_item) { create(:return_item) }
+ let(:customer_return) { build(:customer_return, return_items: [return_item]) }
+
+ subject { customer_return.order }
+
+ it "returns the order associated with the return item's inventory unit" do
+ expect(subject).to eq return_item.inventory_unit.order
+ end
+ end
+
+ describe "#order_id" do
+ subject { customer_return.order_id }
+
+ context "return item is not associated yet" do
+ let(:customer_return) { build(:customer_return) }
+
+ it "is nil" do
+ expect(subject).to be_nil
+ end
+ end
+
+ context "has an associated return item" do
+ let(:return_item) { create(:return_item) }
+ let(:customer_return) { build(:customer_return, return_items: [return_item]) }
+
+ it "is the return item's inventory unit's order id" do
+ expect(subject).to eq return_item.inventory_unit.order.id
+ end
+ end
+ end
+
+ context ".after_save" do
+ let(:inventory_unit) { create(:inventory_unit, state: 'shipped', order: create(:shipped_order)) }
+ let(:return_item) { create(:return_item, inventory_unit: inventory_unit) }
+
+ context "to the initial stock location" do
+
+ it "should mark all inventory units are returned" do
+ create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
+ expect(inventory_unit.reload.state).to eq 'returned'
+ end
+
+ it "should update the stock item counts in the stock location" do
+ expect do
+ create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
+ end.to change { inventory_unit.find_stock_item.count_on_hand }.by(1)
+ end
+
+ context 'with Config.track_inventory_levels == false' do
+ before do
+ Spree::Config.track_inventory_levels = false
+ expect(Spree::StockItem).not_to receive(:find_by)
+ expect(Spree::StockMovement).not_to receive(:create!)
+ end
+
+ it "should NOT update the stock item counts in the stock location" do
+ count_on_hand = inventory_unit.find_stock_item.count_on_hand
+ create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
+ expect(inventory_unit.find_stock_item.count_on_hand).to eql count_on_hand
+ end
+ end
+ end
+
+ context "to a different stock location" do
+ let(:new_stock_location) { create(:stock_location, :name => "other") }
+
+ it "should update the stock item counts in new stock location" do
+ expect {
+ create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
+ }.to change {
+ Spree::StockItem.where(variant_id: inventory_unit.variant_id, stock_location_id: new_stock_location.id).first.count_on_hand
+ }.by(1)
+ end
+
+ it "should NOT raise an error when no stock item exists in the stock location" do
+ inventory_unit.find_stock_item.destroy
+ expect { create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id) }.not_to raise_error
+ end
+
+ it "should not update the stock item counts in the original stock location" do
+ count_on_hand = inventory_unit.find_stock_item.count_on_hand
+ create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
+ expect(inventory_unit.find_stock_item.count_on_hand).to eq(count_on_hand)
+ end
+ end
+ end
+
+ describe '#fully_reimbursed?' do
+ let(:customer_return) { create(:customer_return) }
+
+ let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }
+
+ subject { customer_return.fully_reimbursed? }
+
+ context 'when some return items are undecided' do
+ it { is_expected.to be false }
+ end
+
+ context 'when all return items are decided' do
+
+ context 'when all return items are rejected' do
+ before { customer_return.return_items.each(&:reject!) }
+
+ it { is_expected.to be true }
+ end
+
+ context 'when all return items are accepted' do
+ before { customer_return.return_items.each(&:accept!) }
+
+ context 'when some return items have no reimbursement' do
+ it { is_expected.to be false }
+ end
+
+ context 'when all return items have a reimbursement' do
+ let!(:reimbursement) { create(:reimbursement, customer_return: customer_return) }
+
+ context 'when some reimbursements are not reimbursed' do
+ it { is_expected.to be false }
+ end
+
+ context 'when all reimbursements are reimbursed' do
+ before { reimbursement.perform! }
+
+ it { is_expected.to be true }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/exchange_spec.rb b/core/spec/models/spree/exchange_spec.rb
new file mode 100644
index 00000000000..66c895b09a9
--- /dev/null
+++ b/core/spec/models/spree/exchange_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+module Spree
+ describe Exchange, :type => :model do
+ let(:order) { Spree::Order.new }
+
+ let(:return_item_1) { build(:exchange_return_item) }
+ let(:return_item_2) { build(:exchange_return_item) }
+ let(:return_items) { [return_item_1, return_item_2] }
+ let(:exchange) { Exchange.new(order, return_items) }
+
+ describe "#description" do
+ before do
+ allow(return_item_1).to receive(:variant) { double(options_text: "foo") }
+ allow(return_item_1).to receive(:exchange_variant) { double(options_text: "bar") }
+ allow(return_item_2).to receive(:variant) { double(options_text: "baz") }
+ allow(return_item_2).to receive(:exchange_variant) { double(options_text: "qux") }
+ end
+
+ it "describes the return items' change in options" do
+ expect(exchange.description).to match /foo => bar/
+ expect(exchange.description).to match /baz => qux/
+ end
+ end
+
+ describe "#display_amount" do
+ it "is the total amount of all return items" do
+ expect(exchange.display_amount).to eq Spree::Money.new(0.0)
+ end
+ end
+
+ describe "#perform!" do
+ let(:return_item) { create(:exchange_return_item) }
+ let(:return_items) { [return_item] }
+ let(:order) { return_item.return_authorization.order }
+ subject { exchange.perform! }
+ before { return_item.exchange_variant.stock_items.first.adjust_count_on_hand(20) }
+
+ it "creates shipments for the order with the return items exchange inventory units" do
+ expect { subject }.to change { order.shipments.count }.by(1)
+ new_shipment = order.shipments.last
+ expect(new_shipment).to be_ready
+ new_inventory_units = new_shipment.inventory_units
+ expect(new_inventory_units.count).to eq 1
+ expect(new_inventory_units.first.original_return_item).to eq return_item
+ expect(new_inventory_units.first.line_item).to eq return_item.inventory_unit.line_item
+ end
+
+ context "when it cannot create shipments for all items" do
+ before do
+ StockItem.where(:variant_id => return_item.exchange_variant_id).destroy_all
+ end
+
+ it 'raises an UnableToCreateShipments error' do
+ expect {
+ subject
+ }.to raise_error(Spree::Exchange::UnableToCreateShipments)
+ end
+ end
+ end
+
+ describe "#to_key" do # for dom_id
+ it { expect(Exchange.new(nil, nil).to_key).to be_nil }
+ end
+
+ describe ".param_key" do # for dom_id
+ it { expect(Exchange.param_key).to eq "spree_exchange" }
+ end
+
+ describe ".model_name" do # for dom_id
+ it { expect(Exchange.model_name).to eq Spree::Exchange }
+ end
+
+ end
+end
diff --git a/core/spec/models/spree/gateway/bogus_simple.rb b/core/spec/models/spree/gateway/bogus_simple.rb
new file mode 100644
index 00000000000..1d632fce4b1
--- /dev/null
+++ b/core/spec/models/spree/gateway/bogus_simple.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Spree::Gateway::BogusSimple, :type => :model do
+
+ subject { Spree::Gateway::BogusSimple.new }
+
+ # regression test for #3824
+ describe "#capture" do
+ it "returns success with the right response code" do
+ response = subject.capture(123, '12345', {})
+ expect(response.message).to include("success")
+ end
+
+ it "returns failure with the wrong response code" do
+ response = subject.capture(123, 'wrong', {})
+ expect(response.message).to include("failure")
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/core/spec/models/spree/gateway/bogus_spec.rb b/core/spec/models/spree/gateway/bogus_spec.rb
new file mode 100644
index 00000000000..89ca1dd045a
--- /dev/null
+++ b/core/spec/models/spree/gateway/bogus_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+module Spree
+ describe Gateway::Bogus, :type => :model do
+ let(:bogus) { create(:credit_card_payment_method) }
+ let!(:cc) { create(:credit_card, payment_method: bogus, gateway_customer_profile_id: "BGS-RERTERT") }
+
+ it "disable recurring contract by destroying payment source" do
+ bogus.disable_customer_profile(cc)
+ expect(cc.gateway_customer_profile_id).to be_nil
+ end
+ end
+end
diff --git a/core/spec/models/spree/gateway_spec.rb b/core/spec/models/spree/gateway_spec.rb
new file mode 100644
index 00000000000..02007473282
--- /dev/null
+++ b/core/spec/models/spree/gateway_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Spree::Gateway, :type => :model do
+ class Provider
+ def initialize(options)
+ end
+
+ def imaginary_method
+
+ end
+ end
+
+ class TestGateway < Spree::Gateway
+ def provider_class
+ Provider
+ end
+ end
+
+ it "passes through all arguments on a method_missing call" do
+ gateway = TestGateway.new
+ expect(gateway.provider).to receive(:imaginary_method).with('foo')
+ gateway.imaginary_method('foo')
+ end
+
+ context "fetching payment sources" do
+ let(:order) { Spree::Order.create(user_id: 1) }
+
+ let(:has_card) { create(:credit_card_payment_method) }
+ let(:no_card) { create(:credit_card_payment_method) }
+
+ let(:cc) do
+ create(:credit_card, payment_method: has_card, gateway_customer_profile_id: "EFWE")
+ end
+
+ let(:payment) do
+ create(:payment, order: order, source: cc, payment_method: has_card)
+ end
+
+ it "finds credit cards associated on a order completed" do
+ allow(payment.order).to receive_messages completed?: true
+
+ expect(no_card.reusable_sources(payment.order)).to be_empty
+ expect(has_card.reusable_sources(payment.order)).not_to be_empty
+ end
+
+ it "finds credit cards associated with the order user" do
+ cc.update_column :user_id, 1
+ allow(payment.order).to receive_messages completed?: false
+
+ expect(no_card.reusable_sources(payment.order)).to be_empty
+ expect(has_card.reusable_sources(payment.order)).not_to be_empty
+ end
+ end
+end
diff --git a/core/spec/models/spree/image_spec.rb b/core/spec/models/spree/image_spec.rb
new file mode 100644
index 00000000000..394de852b8d
--- /dev/null
+++ b/core/spec/models/spree/image_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::Image, :type => :model do
+
+end
diff --git a/core/spec/models/spree/inventory_unit_spec.rb b/core/spec/models/spree/inventory_unit_spec.rb
new file mode 100644
index 00000000000..8f8285f18bd
--- /dev/null
+++ b/core/spec/models/spree/inventory_unit_spec.rb
@@ -0,0 +1,242 @@
+require 'spec_helper'
+
+describe Spree::InventoryUnit, :type => :model do
+ let(:stock_location) { create(:stock_location_with_items) }
+ let(:stock_item) { stock_location.stock_items.order(:id).first }
+
+ context "#backordered_for_stock_item" do
+ let(:order) do
+ order = create(:order, state: 'complete', ship_address: create(:ship_address))
+ order.completed_at = Time.now
+ create(:shipment, order: order, stock_location: stock_location)
+ order.shipments.reload
+ create(:line_item, order: order, variant: stock_item.variant)
+ order.line_items.reload
+ order.tap(&:save!)
+ end
+
+ let(:shipment) do
+ order.shipments.first
+ end
+
+ let(:shipping_method) do
+ shipment.shipping_methods.first
+ end
+
+ let!(:unit) do
+ unit = shipment.inventory_units.first
+ unit.state = 'backordered'
+ unit.tap(&:save!)
+ end
+
+ before do
+ stock_item.set_count_on_hand(-2)
+ end
+
+ # Regression for #3066
+ it "returns modifiable objects" do
+ units = Spree::InventoryUnit.backordered_for_stock_item(stock_item)
+ expect { units.first.save! }.to_not raise_error
+ end
+
+ it "finds inventory units from its stock location when the unit's variant matches the stock item's variant" do
+ expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item)).to match_array([unit])
+ end
+
+ it "does not find inventory units that aren't backordered" do
+ on_hand_unit = shipment.inventory_units.build
+ on_hand_unit.state = 'on_hand'
+ on_hand_unit.variant_id = 1
+ on_hand_unit.save!
+
+ expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item)).not_to include(on_hand_unit)
+ end
+
+ it "does not find inventory units that don't match the stock item's variant" do
+ other_variant_unit = shipment.inventory_units.build
+ other_variant_unit.state = 'backordered'
+ other_variant_unit.variant = create(:variant)
+ other_variant_unit.save!
+
+ expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item)).not_to include(other_variant_unit)
+ end
+
+ it "does not change shipping cost when fulfilling the order" do
+ current_shipment_cost = shipment.cost
+ shipping_method.calculator.set_preference(:amount, current_shipment_cost + 5.0)
+ stock_item.set_count_on_hand(0)
+ expect(shipment.reload.cost).to eq(current_shipment_cost)
+ end
+
+ context "other shipments" do
+ let(:other_order) do
+ order = create(:order)
+ order.state = 'payment'
+ order.completed_at = nil
+ order.tap(&:save!)
+ end
+
+ let(:other_shipment) do
+ shipment = Spree::Shipment.new
+ shipment.stock_location = stock_location
+ shipment.shipping_methods << create(:shipping_method)
+ shipment.order = other_order
+ # We don't care about this in this test
+ allow(shipment).to receive(:ensure_correct_adjustment)
+ shipment.tap(&:save!)
+ end
+
+ let!(:other_unit) do
+ unit = other_shipment.inventory_units.build
+ unit.state = 'backordered'
+ unit.variant_id = stock_item.variant.id
+ unit.order_id = other_order.id
+ unit.tap(&:save!)
+ end
+
+ it "does not find inventory units belonging to incomplete orders" do
+ expect(Spree::InventoryUnit.backordered_for_stock_item(stock_item)).not_to include(other_unit)
+ end
+
+ end
+
+ end
+
+ context "variants deleted" do
+ let!(:unit) do
+ Spree::InventoryUnit.create(variant: stock_item.variant)
+ end
+
+ it "can still fetch variant" do
+ unit.variant.destroy
+ expect(unit.reload.variant).to be_a Spree::Variant
+ end
+
+ it "can still fetch variants by eager loading (remove default_scope)" do
+ skip "find a way to remove default scope when eager loading associations"
+ unit.variant.destroy
+ expect(Spree::InventoryUnit.joins(:variant).includes(:variant).first.variant).to be_a Spree::Variant
+ end
+ end
+
+ context "#finalize_units!" do
+ let!(:stock_location) { create(:stock_location) }
+ let(:variant) { create(:variant) }
+ let(:inventory_units) { [
+ create(:inventory_unit, variant: variant),
+ create(:inventory_unit, variant: variant)
+ ] }
+
+ it "should create a stock movement" do
+ Spree::InventoryUnit.finalize_units!(inventory_units)
+ expect(inventory_units.any?(&:pending)).to be false
+ end
+ end
+
+ describe "#current_or_new_return_item" do
+ before { allow(inventory_unit).to receive_messages(pre_tax_amount: 100.0) }
+
+ subject { inventory_unit.current_or_new_return_item }
+
+ context "associated with a return item" do
+ let(:return_item) { create(:return_item) }
+ let(:inventory_unit) { return_item.inventory_unit }
+
+ it "returns a persisted return item" do
+ expect(subject).to be_persisted
+ end
+
+ it "returns it's associated return_item" do
+ expect(subject).to eq return_item
+ end
+ end
+
+ context "no associated return item" do
+ let(:inventory_unit) { create(:inventory_unit) }
+
+ it "returns a new return item" do
+ expect(subject).to_not be_persisted
+ end
+
+ it "associates itself to the new return_item" do
+ expect(subject.inventory_unit).to eq inventory_unit
+ end
+ end
+ end
+
+ describe '#additional_tax_total' do
+ let(:quantity) { 2 }
+ let(:line_item_additional_tax_total) { 10.00 }
+ let(:line_item) do
+ build(:line_item, {
+ quantity: quantity,
+ additional_tax_total: line_item_additional_tax_total,
+ })
+ end
+
+ subject do
+ build(:inventory_unit, line_item: line_item)
+ end
+
+ it 'is the correct amount' do
+ expect(subject.additional_tax_total).to eq line_item_additional_tax_total / quantity
+ end
+ end
+
+ describe '#included_tax_total' do
+ let(:quantity) { 2 }
+ let(:line_item_included_tax_total) { 10.00 }
+ let(:line_item) do
+ build(:line_item, {
+ quantity: quantity,
+ included_tax_total: line_item_included_tax_total,
+ })
+ end
+
+ subject do
+ build(:inventory_unit, line_item: line_item)
+ end
+
+ it 'is the correct amount' do
+ expect(subject.included_tax_total).to eq line_item_included_tax_total / quantity
+ end
+ end
+
+ describe '#additional_tax_total' do
+ let(:quantity) { 2 }
+ let(:line_item_additional_tax_total) { 10.00 }
+ let(:line_item) do
+ build(:line_item, {
+ quantity: quantity,
+ additional_tax_total: line_item_additional_tax_total,
+ })
+ end
+
+ subject do
+ build(:inventory_unit, line_item: line_item)
+ end
+
+ it 'is the correct amount' do
+ expect(subject.additional_tax_total).to eq line_item_additional_tax_total / quantity
+ end
+ end
+
+ describe '#included_tax_total' do
+ let(:quantity) { 2 }
+ let(:line_item_included_tax_total) { 10.00 }
+ let(:line_item) do
+ build(:line_item, {
+ quantity: quantity,
+ included_tax_total: line_item_included_tax_total,
+ })
+ end
+
+ subject do
+ build(:inventory_unit, line_item: line_item)
+ end
+
+ it 'is the correct amount' do
+ expect(subject.included_tax_total).to eq line_item_included_tax_total / quantity
+ end
+ end
+end
diff --git a/core/spec/models/spree/item_adjustments_spec.rb b/core/spec/models/spree/item_adjustments_spec.rb
new file mode 100644
index 00000000000..3f874118e6c
--- /dev/null
+++ b/core/spec/models/spree/item_adjustments_spec.rb
@@ -0,0 +1,273 @@
+require 'spec_helper'
+
+module Spree
+ describe ItemAdjustments, :type => :model do
+ let(:order) { create :order_with_line_items, line_items_count: 1 }
+ let(:line_item) { order.line_items.first }
+
+ let(:subject) { ItemAdjustments.new(line_item) }
+ let(:order_subject) { ItemAdjustments.new(order) }
+
+ context '#update' do
+ it "updates a linked adjustment" do
+ tax_rate = create(:tax_rate, :amount => 0.05)
+ adjustment = create(:adjustment, order: order, source: tax_rate, adjustable: line_item)
+ line_item.price = 10
+ line_item.tax_category = tax_rate.tax_category
+
+ subject.update
+ expect(line_item.adjustment_total).to eq(0.5)
+ expect(line_item.additional_tax_total).to eq(0.5)
+ end
+ end
+
+ context "taxes and promotions" do
+ let!(:tax_rate) do
+ create(:tax_rate, :amount => 0.05)
+ end
+
+ let!(:promotion) do
+ Spree::Promotion.create(:name => "$10 off")
+ end
+
+ let!(:promotion_action) do
+ calculator = Calculator::FlatRate.new(:preferred_amount => 10)
+ Promotion::Actions::CreateItemAdjustments.create calculator: calculator, promotion: promotion
+ end
+
+ before do
+ line_item.price = 20
+ line_item.tax_category = tax_rate.tax_category
+ line_item.save
+ create(:adjustment, order: order, source: promotion_action, adjustable: line_item)
+ end
+
+ context "tax included in price" do
+ before do
+ create(:adjustment,
+ :source => tax_rate,
+ :adjustable => line_item,
+ :order => order,
+ :included => true
+ )
+ end
+
+ it "tax has no bearing on final price" do
+ subject.update_adjustments
+ line_item.reload
+ expect(line_item.included_tax_total).to eq(0.5)
+ expect(line_item.additional_tax_total).to eq(0)
+ expect(line_item.promo_total).to eq(-10)
+ expect(line_item.adjustment_total).to eq(-10)
+ end
+
+ it "tax linked to order" do
+ order_subject.update_adjustments
+ order.reload
+ expect(order.included_tax_total).to eq(0.5)
+ expect(order.additional_tax_total).to eq(00)
+ end
+ end
+
+ context "tax excluded from price" do
+ before do
+ create(:adjustment,
+ :source => tax_rate,
+ :adjustable => line_item,
+ :order => order,
+ :included => false
+ )
+ end
+
+ it "tax applies to line item" do
+ subject.update_adjustments
+ line_item.reload
+ # Taxable amount is: $20 (base) - $10 (promotion) = $10
+ # Tax rate is 5% (of $10).
+ expect(line_item.included_tax_total).to eq(0)
+ expect(line_item.additional_tax_total).to eq(0.5)
+ expect(line_item.promo_total).to eq(-10)
+ expect(line_item.adjustment_total).to eq(-9.5)
+ end
+
+ it "tax linked to order" do
+ order_subject.update_adjustments
+ order.reload
+ expect(order.included_tax_total).to eq(0)
+ expect(order.additional_tax_total).to eq(0.5)
+ end
+ end
+ end
+
+ context "best promotion is always applied" do
+ let(:calculator) { Calculator::FlatRate.new(:preferred_amount => 10) }
+
+ let(:source) { Promotion::Actions::CreateItemAdjustments.create calculator: calculator }
+
+ def create_adjustment(label, amount)
+ create(:adjustment, :order => order,
+ :adjustable => line_item,
+ :source => source,
+ :amount => amount,
+ :state => "closed",
+ :label => label,
+ :mandatory => false)
+ end
+
+ it "should make all but the most valuable promotion adjustment ineligible, leaving non promotion adjustments alone" do
+ create_adjustment("Promotion A", -100)
+ create_adjustment("Promotion B", -200)
+ create_adjustment("Promotion C", -300)
+ create(:adjustment, :order => order,
+ :adjustable => line_item,
+ :source => nil,
+ :amount => -500,
+ :state => "closed",
+ :label => "Some other credit")
+ line_item.adjustments.each {|a| a.update_column(:eligible, true)}
+
+ subject.choose_best_promotion_adjustment
+
+ expect(line_item.adjustments.promotion.eligible.count).to eq(1)
+ expect(line_item.adjustments.promotion.eligible.first.label).to eq('Promotion C')
+ end
+
+ it "should choose the most recent promotion adjustment when amounts are equal" do
+ # Using Timecop is a regression test
+ Timecop.freeze do
+ create_adjustment("Promotion A", -200)
+ create_adjustment("Promotion B", -200)
+ end
+ line_item.adjustments.each {|a| a.update_column(:eligible, true)}
+
+ subject.choose_best_promotion_adjustment
+
+ expect(line_item.adjustments.promotion.eligible.count).to eq(1)
+ expect(line_item.adjustments.promotion.eligible.first.label).to eq('Promotion B')
+ end
+
+ context "when previously ineligible promotions become available" do
+ let(:order_promo1) { create(:promotion, :with_order_adjustment, :with_item_total_rule, weighted_order_adjustment_amount: 5, item_total_threshold_amount: 10) }
+ let(:order_promo2) { create(:promotion, :with_order_adjustment, :with_item_total_rule, weighted_order_adjustment_amount: 10, item_total_threshold_amount: 20) }
+ let(:order_promos) { [ order_promo1, order_promo2 ] }
+ let(:line_item_promo1) { create(:promotion, :with_line_item_adjustment, :with_item_total_rule, adjustment_rate: 2.5, item_total_threshold_amount: 10) }
+ let(:line_item_promo2) { create(:promotion, :with_line_item_adjustment, :with_item_total_rule, adjustment_rate: 5, item_total_threshold_amount: 20) }
+ let(:line_item_promos) { [ line_item_promo1, line_item_promo2 ] }
+ let(:order) { create(:order_with_line_items, line_items_count: 1) }
+
+ # Apply promotions in different sequences. Results should be the same.
+ promo_sequences = [
+ [ 0, 1 ],
+ [ 1, 0 ]
+ ]
+
+ promo_sequences.each do |promo_sequence|
+ it "should pick the best order-level promo according to current eligibility" do
+ # apply both promos to the order, even though only promo1 is eligible
+ order_promos[promo_sequence[0]].activate order: order
+ order_promos[promo_sequence[1]].activate order: order
+
+ order.reload
+ expect(order.all_adjustments.count).to eq(2), "Expected two adjustments (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.count).to eq(1), "Expected one elegible adjustment (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.first.source.promotion).to eq(order_promo1), "Expected promo1 to be used (using sequence #{promo_sequence})"
+
+ order.contents.add create(:variant, price: 10), 1
+ order.save
+
+ order.reload
+ expect(order.all_adjustments.count).to eq(2), "Expected two adjustments (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.count).to eq(1), "Expected one elegible adjustment (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.first.source.promotion).to eq(order_promo2), "Expected promo2 to be used (using sequence #{promo_sequence})"
+ end
+ end
+
+ promo_sequences.each do |promo_sequence|
+ it "should pick the best line-item-level promo according to current eligibility" do
+ # apply both promos to the order, even though only promo1 is eligible
+ line_item_promos[promo_sequence[0]].activate order: order
+ line_item_promos[promo_sequence[1]].activate order: order
+
+ order.reload
+ expect(order.all_adjustments.count).to eq(1), "Expected one adjustment (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.count).to eq(1), "Expected one elegible adjustment (using sequence #{promo_sequence})"
+ # line_item_promo1 is the only one that has thus far met the order total threshold, it is the only promo which should be applied.
+ expect(order.all_adjustments.first.source.promotion).to eq(line_item_promo1), "Expected line_item_promo1 to be used (using sequence #{promo_sequence})"
+
+ order.contents.add create(:variant, price: 10), 1
+ order.save
+
+ order.reload
+ expect(order.all_adjustments.count).to eq(4), "Expected four adjustments (using sequence #{promo_sequence})"
+ expect(order.all_adjustments.eligible.count).to eq(2), "Expected two elegible adjustments (using sequence #{promo_sequence})"
+ order.all_adjustments.eligible.each do |adjustment|
+ expect(adjustment.source.promotion).to eq(line_item_promo2), "Expected line_item_promo2 to be used (using sequence #{promo_sequence})"
+ end
+ end
+ end
+ end
+
+ context "multiple adjustments and the best one is not eligible" do
+ let!(:promo_a) { create_adjustment("Promotion A", -100) }
+ let!(:promo_c) { create_adjustment("Promotion C", -300) }
+
+ before do
+ promo_a.update_column(:eligible, true)
+ promo_c.update_column(:eligible, false)
+ end
+
+ # regression for #3274
+ it "still makes the previous best eligible adjustment valid" do
+ subject.choose_best_promotion_adjustment
+ expect(line_item.adjustments.promotion.eligible.first.label).to eq('Promotion A')
+ end
+ end
+
+ it "should only leave one adjustment even if 2 have the same amount" do
+ create_adjustment("Promotion A", -100)
+ create_adjustment("Promotion B", -200)
+ create_adjustment("Promotion C", -200)
+
+ subject.choose_best_promotion_adjustment
+
+ expect(line_item.adjustments.promotion.eligible.count).to eq(1)
+ expect(line_item.adjustments.promotion.eligible.first.amount.to_i).to eq(-200)
+ end
+ end
+
+ # For #4483
+ context "callbacks" do
+ class SuperItemAdjustments < Spree::ItemAdjustments
+ attr_accessor :before_promo_adjustments_called,
+ :after_promo_adjustments_called,
+ :before_tax_adjustments_called,
+ :after_tax_adjustments_called
+
+ set_callback :promo_adjustments, :before do |object|
+ @before_promo_adjustments_called = true
+ end
+
+ set_callback :promo_adjustments, :after do |object|
+ @after_promo_adjustments_called = true
+ end
+
+ set_callback :tax_adjustments, :before do |object|
+ @before_tax_adjustments_called = true
+ end
+
+ set_callback :tax_adjustments, :after do |object|
+ @after_tax_adjustments_called = true
+ end
+ end
+ let(:subject) { SuperItemAdjustments.new(line_item) }
+
+ it "calls all the callbacks" do
+ subject.update_adjustments
+ expect(subject.before_promo_adjustments_called).to be true
+ expect(subject.after_promo_adjustments_called).to be true
+ expect(subject.before_tax_adjustments_called).to be true
+ expect(subject.after_tax_adjustments_called).to be true
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/line_item_spec.rb b/core/spec/models/spree/line_item_spec.rb
new file mode 100644
index 00000000000..107d6b17567
--- /dev/null
+++ b/core/spec/models/spree/line_item_spec.rb
@@ -0,0 +1,257 @@
+require 'spec_helper'
+
+describe Spree::LineItem, :type => :model do
+ let(:order) { create :order_with_line_items, line_items_count: 1 }
+ let(:line_item) { order.line_items.first }
+
+ context '#save' do
+ it 'touches the order' do
+ expect(line_item.order).to receive(:touch)
+ line_item.save
+ end
+ end
+
+ context '#destroy' do
+ it "fetches deleted products" do
+ line_item.product.destroy
+ expect(line_item.reload.product).to be_a Spree::Product
+ end
+
+ it "fetches deleted variants" do
+ line_item.variant.destroy
+ expect(line_item.reload.variant).to be_a Spree::Variant
+ end
+
+ it "returns inventory when a line item is destroyed" do
+ expect_any_instance_of(Spree::OrderInventory).to receive(:verify)
+ line_item.destroy
+ end
+
+ it "deletes inventory units" do
+ expect { line_item.destroy }.to change { line_item.inventory_units.count }.from(1).to(0)
+ end
+ end
+
+ context "#save" do
+ context "line item changes" do
+ before do
+ line_item.quantity = line_item.quantity + 1
+ end
+
+ it "triggers adjustment total recalculation" do
+ expect(line_item).to receive(:update_tax_charge) # Regression test for https://github.com/spree/spree/issues/4671
+ expect(line_item).to receive(:recalculate_adjustments)
+ line_item.save
+ end
+ end
+
+ context "line item does not change" do
+ it "does not trigger adjustment total recalculation" do
+ expect(line_item).not_to receive(:recalculate_adjustments)
+ line_item.save
+ end
+ end
+
+ context "target_shipment is provided" do
+ it "verifies inventory" do
+ line_item.target_shipment = Spree::Shipment.new
+ expect_any_instance_of(Spree::OrderInventory).to receive(:verify)
+ line_item.save
+ end
+ end
+ end
+
+ context "#create" do
+ let(:variant) { create(:variant) }
+
+ before do
+ create(:tax_rate, :zone => order.tax_zone, :tax_category => variant.tax_category)
+ end
+
+ context "when order has a tax zone" do
+ before do
+ expect(order.tax_zone).to be_present
+ end
+
+ it "creates a tax adjustment" do
+ order.contents.add(variant)
+ line_item = order.find_line_item_by_variant(variant)
+ expect(line_item.adjustments.tax.count).to eq(1)
+ end
+ end
+
+ context "when order does not have a tax zone" do
+ before do
+ order.bill_address = nil
+ order.ship_address = nil
+ order.save
+ expect(order.reload.tax_zone).to be_nil
+ end
+
+ it "does not create a tax adjustment" do
+ order.contents.add(variant)
+ line_item = order.find_line_item_by_variant(variant)
+ expect(line_item.adjustments.tax.count).to eq(0)
+ end
+ end
+ end
+
+ # Test for #3391
+ context '#copy_price' do
+ it "copies over a variant's prices" do
+ line_item.price = nil
+ line_item.cost_price = nil
+ line_item.currency = nil
+ line_item.copy_price
+ variant = line_item.variant
+ expect(line_item.price).to eq(variant.price)
+ expect(line_item.cost_price).to eq(variant.cost_price)
+ expect(line_item.currency).to eq(variant.currency)
+ end
+ end
+
+ # Test for #3481
+ context '#copy_tax_category' do
+ it "copies over a variant's tax category" do
+ line_item.tax_category = nil
+ line_item.copy_tax_category
+ expect(line_item.tax_category).to eq(line_item.variant.tax_category)
+ end
+ end
+
+ describe '.discounted_amount' do
+ it "returns the amount minus any discounts" do
+ line_item.price = 10
+ line_item.quantity = 2
+ line_item.promo_total = -5
+ expect(line_item.discounted_amount).to eq(15)
+ end
+ end
+
+ describe "#discounted_money" do
+ it "should return a money object with the discounted amount" do
+ expect(line_item.discounted_money.to_s).to eq "$10.00"
+ end
+ end
+
+ describe '.currency' do
+ it 'returns the globally configured currency' do
+ line_item.currency == 'USD'
+ end
+ end
+
+ describe ".money" do
+ before do
+ line_item.price = 3.50
+ line_item.quantity = 2
+ end
+
+ it "returns a Spree::Money representing the total for this line item" do
+ expect(line_item.money.to_s).to eq("$7.00")
+ end
+ end
+
+ describe '.single_money' do
+ before { line_item.price = 3.50 }
+ it "returns a Spree::Money representing the price for one variant" do
+ expect(line_item.single_money.to_s).to eq("$3.50")
+ end
+ end
+
+ context "has inventory (completed order so items were already unstocked)" do
+ let(:order) { Spree::Order.create(email: 'spree@example.com') }
+ let(:variant) { create(:variant) }
+
+ context "nothing left on stock" do
+ before do
+ variant.stock_items.update_all count_on_hand: 5, backorderable: false
+ order.contents.add(variant, 5)
+ order.create_proposed_shipments
+ order.finalize!
+ end
+
+ it "allows to decrease item quantity" do
+ line_item = order.line_items.first
+ line_item.quantity -= 1
+ line_item.target_shipment = order.shipments.first
+
+ line_item.save
+ expect(line_item.errors_on(:quantity).size).to eq(0)
+ end
+
+ it "doesnt allow to increase item quantity" do
+ line_item = order.line_items.first
+ line_item.quantity += 2
+ line_item.target_shipment = order.shipments.first
+
+ line_item.save
+ expect(line_item.errors_on(:quantity).size).to eq(1)
+ end
+ end
+
+ context "2 items left on stock" do
+ before do
+ variant.stock_items.update_all count_on_hand: 7, backorderable: false
+ order.contents.add(variant, 5)
+ order.create_proposed_shipments
+ order.finalize!
+ end
+
+ it "allows to increase quantity up to stock availability" do
+ line_item = order.line_items.first
+ line_item.quantity += 2
+ line_item.target_shipment = order.shipments.first
+
+ line_item.save
+ expect(line_item.errors_on(:quantity).size).to eq(0)
+ end
+
+ it "doesnt allow to increase quantity over stock availability" do
+ line_item = order.line_items.first
+ line_item.quantity += 3
+ line_item.target_shipment = order.shipments.first
+
+ line_item.save
+ expect(line_item.errors_on(:quantity).size).to eq(1)
+ end
+ end
+ end
+
+ context "currency same as order.currency" do
+ it "is a valid line item" do
+ line_item = order.line_items.first
+ line_item.currency = order.currency
+ line_item.valid?
+
+ expect(line_item.error_on(:currency).size).to eq(0)
+ end
+ end
+
+ context "currency different than order.currency" do
+ it "is not a valid line item" do
+ line_item = order.line_items.first
+ line_item.currency = "no currency"
+ line_item.valid?
+
+ expect(line_item.error_on(:currency).size).to eq(1)
+ end
+ end
+
+ describe "#options=" do
+ it "can handle updating a blank line item with no order" do
+ line_item.options = { price: 123 }
+ end
+
+ it "updates the data provided in the options" do
+ line_item.options = { price: 123 }
+ expect(line_item.price).to eq 123
+ end
+
+ it "updates the price based on the options provided" do
+ expect(line_item).to receive(:gift_wrap=).with(true)
+ expect(line_item.variant).to receive(:gift_wrap_price_modifier_amount_in).with("USD", true).and_return 1.99
+ line_item.options = { gift_wrap: true }
+ expect(line_item.price).to eq 21.98
+ end
+ end
+end
diff --git a/core/spec/models/spree/option_type_spec.rb b/core/spec/models/spree/option_type_spec.rb
new file mode 100644
index 00000000000..ddd1f225d24
--- /dev/null
+++ b/core/spec/models/spree/option_type_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Spree::OptionType, :type => :model do
+ context "touching" do
+ it "should touch a product" do
+ product_option_type = create(:product_option_type)
+ option_type = product_option_type.option_type
+ product = product_option_type.product
+ product.update_column(:updated_at, 1.day.ago)
+ option_type.touch
+ expect(product.reload.updated_at).to be_within(3.seconds).of(Time.now)
+ end
+ end
+end
\ No newline at end of file
diff --git a/core/spec/models/spree/option_value_spec.rb b/core/spec/models/spree/option_value_spec.rb
new file mode 100644
index 00000000000..b79f61b9266
--- /dev/null
+++ b/core/spec/models/spree/option_value_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe Spree::OptionValue, :type => :model do
+ context "touching" do
+ it "should touch a variant" do
+ variant = create(:variant)
+ option_value = variant.option_values.first
+ variant.update_column(:updated_at, 1.day.ago)
+ option_value.touch
+ expect(variant.reload.updated_at).to be_within(3.seconds).of(Time.now)
+ end
+ end
+end
\ No newline at end of file
diff --git a/core/spec/models/spree/order/address_spec.rb b/core/spec/models/spree/order/address_spec.rb
new file mode 100644
index 00000000000..9f95df5bb23
--- /dev/null
+++ b/core/spec/models/spree/order/address_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ let(:order) { Spree::Order.new }
+
+ context 'validation' do
+ context "when @use_billing is populated" do
+ before do
+ order.bill_address = stub_model(Spree::Address)
+ order.ship_address = nil
+ end
+
+ context "with true" do
+ before { order.use_billing = true }
+
+ it "clones the bill address to the ship address" do
+ order.valid?
+ expect(order.ship_address).to eq(order.bill_address)
+ end
+ end
+
+ context "with 'true'" do
+ before { order.use_billing = 'true' }
+
+ it "clones the bill address to the shipping" do
+ order.valid?
+ expect(order.ship_address).to eq(order.bill_address)
+ end
+ end
+
+ context "with '1'" do
+ before { order.use_billing = '1' }
+
+ it "clones the bill address to the shipping" do
+ order.valid?
+ expect(order.ship_address).to eq(order.bill_address)
+ end
+ end
+
+ context "with something other than a 'truthful' value" do
+ before { order.use_billing = '0' }
+
+ it "does not clone the bill address to the shipping" do
+ order.valid?
+ expect(order.ship_address).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/adjustments_spec.rb b/core/spec/models/spree/order/adjustments_spec.rb
new file mode 100644
index 00000000000..690d88e4786
--- /dev/null
+++ b/core/spec/models/spree/order/adjustments_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Spree::Order do
+ context "when an order has an adjustment that zeroes the total, but another adjustment for shipping that raises it above zero" do
+ let!(:persisted_order) { create(:order) }
+ let!(:line_item) { create(:line_item) }
+ let!(:shipping_method) do
+ sm = create(:shipping_method)
+ sm.calculator.preferred_amount = 10
+ sm.save
+ sm
+ end
+
+ before do
+ # Don't care about available payment methods in this test
+ allow(persisted_order).to receive_messages(:has_available_payment => false)
+ persisted_order.line_items << line_item
+ create(:adjustment, amount: -line_item.amount, label: "Promotion", adjustable: line_item, order: persisted_order)
+ persisted_order.state = 'delivery'
+ persisted_order.save # To ensure new state_change event
+ end
+
+ it "transitions from delivery to payment" do
+ allow(persisted_order).to receive_messages(payment_required?: true)
+ persisted_order.next!
+ expect(persisted_order.state).to eq("payment")
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/callbacks_spec.rb b/core/spec/models/spree/order/callbacks_spec.rb
new file mode 100644
index 00000000000..43eecb3abac
--- /dev/null
+++ b/core/spec/models/spree/order/callbacks_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ let(:order) { stub_model(Spree::Order) }
+ before do
+ Spree::Order.define_state_machine!
+ end
+
+ context "validations" do
+ context "email validation" do
+ # Regression test for #1238
+ it "o'brien@gmail.com is a valid email address" do
+ order.state = 'address'
+ order.email = "o'brien@gmail.com"
+ expect(order.error_on(:email).size).to eq(0)
+ end
+ end
+ end
+
+ context "#save" do
+ context "when associated with a registered user" do
+ let(:user) { double(:user, :email => "test@example.com") }
+
+ before do
+ allow(order).to receive_messages :user => user
+ end
+
+ it "should assign the email address of the user" do
+ order.run_callbacks(:create)
+ expect(order.email).to eq(user.email)
+ end
+ end
+ end
+
+ context "in the cart state" do
+ it "should not validate email address" do
+ order.state = "cart"
+ order.email = nil
+ expect(order.error_on(:email).size).to eq(0)
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/checkout_spec.rb b/core/spec/models/spree/order/checkout_spec.rb
new file mode 100644
index 00000000000..bd390bfb5ea
--- /dev/null
+++ b/core/spec/models/spree/order/checkout_spec.rb
@@ -0,0 +1,749 @@
+require 'spec_helper'
+require 'spree/testing_support/order_walkthrough'
+
+describe Spree::Order, :type => :model do
+ let(:order) { Spree::Order.new }
+
+ def assert_state_changed(order, from, to)
+ state_change_exists = order.state_changes.where(:previous_state => from, :next_state => to).exists?
+ assert state_change_exists, "Expected order to transition from #{from} to #{to}, but didn't."
+ end
+
+ context "with default state machine" do
+ let(:transitions) do
+ [
+ { :address => :delivery },
+ { :delivery => :payment },
+ { :payment => :confirm },
+ { :confirm => :complete },
+ { :payment => :complete },
+ { :delivery => :complete }
+ ]
+ end
+
+ it "has the following transitions" do
+ transitions.each do |transition|
+ transition = Spree::Order.find_transition(:from => transition.keys.first, :to => transition.values.first)
+ expect(transition).not_to be_nil
+ end
+ end
+
+ it "does not have a transition from delivery to confirm" do
+ transition = Spree::Order.find_transition(:from => :delivery, :to => :confirm)
+ expect(transition).to be_nil
+ end
+
+ it '.find_transition when contract was broken' do
+ expect(Spree::Order.find_transition({foo: :bar, baz: :dog})).to be_falsey
+ end
+
+ it '.remove_transition' do
+ options = {:from => transitions.first.keys.first, :to => transitions.first.values.first}
+ allow(Spree::Order).to receive(:next_event_transition).and_return([options])
+ expect(Spree::Order.remove_transition(options)).to be_truthy
+ end
+
+ it '.remove_transition when contract was broken' do
+ expect(Spree::Order.remove_transition(nil)).to be_falsey
+ end
+
+ it "always return integer on checkout_step_index" do
+ expect(order.checkout_step_index("imnotthere")).to be_a Integer
+ expect(order.checkout_step_index("delivery")).to be > 0
+ end
+
+ it "passes delivery state when transitioning from address over delivery to payment" do
+ allow(order).to receive_messages :payment_required? => true
+ order.state = "address"
+ expect(order.passed_checkout_step?("delivery")).to be false
+ order.state = "delivery"
+ expect(order.passed_checkout_step?("delivery")).to be false
+ order.state = "payment"
+ expect(order.passed_checkout_step?("delivery")).to be true
+ end
+
+ context "#checkout_steps" do
+ context "when confirmation not required" do
+ before do
+ allow(order).to receive_messages :confirmation_required? => false
+ allow(order).to receive_messages :payment_required? => true
+ end
+
+ specify do
+ expect(order.checkout_steps).to eq(%w(address delivery payment complete))
+ end
+ end
+
+ context "when confirmation required" do
+ before do
+ allow(order).to receive_messages :confirmation_required? => true
+ allow(order).to receive_messages :payment_required? => true
+ end
+
+ specify do
+ expect(order.checkout_steps).to eq(%w(address delivery payment confirm complete))
+ end
+ end
+
+ context "when payment not required" do
+ before { allow(order).to receive_messages :payment_required? => false }
+ specify do
+ expect(order.checkout_steps).to eq(%w(address delivery complete))
+ end
+ end
+
+ context "when payment required" do
+ before { allow(order).to receive_messages :payment_required? => true }
+ specify do
+ expect(order.checkout_steps).to eq(%w(address delivery payment complete))
+ end
+ end
+ end
+
+ it "starts out at cart" do
+ expect(order.state).to eq("cart")
+ end
+
+ context "to address" do
+ before do
+ order.email = "user@example.com"
+ order.save!
+ end
+
+ context "with a line item" do
+ before do
+ order.line_items << FactoryGirl.create(:line_item)
+ end
+
+ it "transitions to address" do
+ order.next!
+ assert_state_changed(order, 'cart', 'address')
+ expect(order.state).to eq("address")
+ end
+
+ it "doesn't raise an error if the default address is invalid" do
+ order.user = mock_model(Spree::LegacyUser, ship_address: Spree::Address.new, bill_address: Spree::Address.new)
+ expect { order.next! }.to_not raise_error
+ end
+
+ context "with default addresses" do
+ let(:default_address) { FactoryGirl.create(:address) }
+
+ before do
+ order.user = FactoryGirl.create(:user, "#{address_kind}_address" => default_address)
+ order.next!
+ order.reload
+ end
+
+ shared_examples "it cloned the default address" do
+ it do
+ default_attributes = default_address.attributes
+ order_attributes = order.send("#{address_kind}_address".to_sym).try(:attributes) || {}
+
+ expect(order_attributes.except('id', 'created_at', 'updated_at')).to eql(default_attributes.except('id', 'created_at', 'updated_at'))
+ end
+ end
+
+ it_behaves_like "it cloned the default address" do
+ let(:address_kind) { 'ship' }
+ end
+
+ it_behaves_like "it cloned the default address" do
+ let(:address_kind) { 'bill' }
+ end
+ end
+ end
+
+ it "cannot transition to address without any line items" do
+ expect(order.line_items).to be_blank
+ expect { order.next! }.to raise_error(StateMachine::InvalidTransition, /#{Spree.t(:there_are_no_items_for_this_order)}/)
+ end
+ end
+
+ context "from address" do
+ before do
+ order.state = 'address'
+ allow(order).to receive(:has_available_payment)
+ shipment = FactoryGirl.create(:shipment, :order => order)
+ order.email = "user@example.com"
+ order.save!
+ end
+
+ it "updates totals" do
+ allow(order).to receive_messages(:ensure_available_shipping_rates => true)
+ line_item = FactoryGirl.create(:line_item, :price => 10, :adjustment_total => 10)
+ order.line_items << line_item
+ tax_rate = create(:tax_rate, :tax_category => line_item.tax_category, :amount => 0.05)
+ allow(Spree::TaxRate).to receive_messages :match => [tax_rate]
+ FactoryGirl.create(:tax_adjustment, :adjustable => line_item, :source => tax_rate, order: order)
+ order.email = "user@example.com"
+ order.next!
+ expect(order.adjustment_total).to eq(0.5)
+ expect(order.additional_tax_total).to eq(0.5)
+ expect(order.included_tax_total).to eq(0)
+ expect(order.total).to eq(10.5)
+ end
+
+ it "transitions to delivery" do
+ allow(order).to receive_messages(:ensure_available_shipping_rates => true)
+ order.next!
+ assert_state_changed(order, 'address', 'delivery')
+ expect(order.state).to eq("delivery")
+ end
+
+ it "does not call persist_order_address if there is no address on the order" do
+ # otherwise, it will crash
+ allow(order).to receive_messages(:ensure_available_shipping_rates => true)
+
+ order.user = FactoryGirl.create(:user)
+ order.save!
+
+ expect(order.user).to_not receive(:persist_order_address).with(order)
+ order.next!
+ end
+
+ it "calls persist_order_address on the order's user" do
+ allow(order).to receive_messages(:ensure_available_shipping_rates => true)
+
+ order.user = FactoryGirl.create(:user)
+ order.ship_address = FactoryGirl.create(:address)
+ order.bill_address = FactoryGirl.create(:address)
+ order.save!
+
+ expect(order.user).to receive(:persist_order_address).with(order)
+ order.next!
+ end
+
+ it "does not call persist_order_address on the order's user for a temporary address" do
+ allow(order).to receive_messages(:ensure_available_shipping_rates => true)
+
+ order.user = FactoryGirl.create(:user)
+ order.temporary_address = true
+ order.save!
+
+ expect(order.user).to_not receive(:persist_order_address)
+ order.next!
+ end
+
+ context "cannot transition to delivery" do
+ context "with an existing shipment" do
+ before do
+ line_item = FactoryGirl.create(:line_item, :price => 10)
+ order.line_items << line_item
+ end
+
+ context "if there are no shipping rates for any shipment" do
+ it "raises an InvalidTransitionError" do
+ transition = lambda { order.next! }
+ expect(transition).to raise_error(StateMachine::InvalidTransition, /#{Spree.t(:items_cannot_be_shipped)}/)
+ end
+
+ it "deletes all the shipments" do
+ order.next
+ expect(order.shipments).to be_empty
+ end
+ end
+ end
+ end
+ end
+
+ context "to delivery" do
+ context 'when order has default selected_shipping_rate_id' do
+ let(:shipment) { create(:shipment, order: order) }
+ let(:shipping_method) { create(:shipping_method) }
+ let(:shipping_rate) { [
+ Spree::ShippingRate.create!(shipping_method: shipping_method, cost: 10.00, shipment: shipment)
+ ] }
+
+ before do
+ order.state = 'address'
+ shipment.selected_shipping_rate_id = shipping_rate.first.id
+ order.email = "user@example.com"
+ order.save!
+
+ allow(order).to receive(:has_available_payment)
+ allow(order).to receive(:create_proposed_shipments)
+ allow(order).to receive(:ensure_available_shipping_rates) { true }
+ end
+
+ it 'should invoke set_shipment_cost' do
+ expect(order).to receive(:set_shipments_cost)
+ order.next!
+ end
+
+ it 'should update shipment_total' do
+ expect { order.next! }.to change{ order.shipment_total }.by(10.00)
+ end
+ end
+ end
+
+ context "from delivery" do
+ before do
+ order.state = 'delivery'
+ allow(order).to receive(:apply_free_shipping_promotions)
+ end
+
+ it "attempts to apply free shipping promotions" do
+ expect(order).to receive(:apply_free_shipping_promotions)
+ order.next!
+ end
+
+ context "with payment required" do
+ before do
+ allow(order).to receive_messages :payment_required? => true
+ end
+
+ it "transitions to payment" do
+ expect(order).to receive(:set_shipments_cost)
+ order.next!
+ assert_state_changed(order, 'delivery', 'payment')
+ expect(order.state).to eq('payment')
+ end
+ end
+
+ context "without payment required" do
+ before do
+ allow(order).to receive_messages :payment_required? => false
+ end
+
+ it "transitions to complete" do
+ order.next!
+ expect(order.state).to eq("complete")
+ end
+ end
+
+ context "correctly determining payment required based on shipping information" do
+ let(:shipment) do
+ FactoryGirl.create(:shipment)
+ end
+
+ before do
+ # Needs to be set here because we're working with a persisted order object
+ order.email = "test@example.com"
+ order.save!
+ order.shipments << shipment
+ end
+
+ context "with a shipment that has a price" do
+ before do
+ shipment.shipping_rates.first.update_column(:cost, 10)
+ order.set_shipments_cost
+ end
+
+ it "transitions to payment" do
+ order.next!
+ expect(order.state).to eq("payment")
+ end
+ end
+
+ context "with a shipment that is free" do
+ before do
+ shipment.shipping_rates.first.update_column(:cost, 0)
+ order.set_shipments_cost
+ end
+
+ it "skips payment, transitions to complete" do
+ order.next!
+ expect(order.state).to eq("complete")
+ end
+ end
+ end
+ end
+
+ context "to payment" do
+ before do
+ @default_credit_card = FactoryGirl.create(:credit_card)
+ order.user = mock_model(Spree::LegacyUser, default_credit_card: @default_credit_card, email: 'spree@example.org')
+
+ allow(order).to receive_messages(payment_required?: true)
+ order.state = 'delivery'
+ order.save!
+ end
+
+ it "assigns the user's default credit card" do
+ order.next!
+ order.reload
+
+ expect(order.state).to eq 'payment'
+ expect(order.payments.count).to eq 1
+ expect(order.payments.first.source).to eq @default_credit_card
+ end
+ end
+
+ context "from payment" do
+ before do
+ order.state = 'payment'
+ end
+
+ context "with confirmation required" do
+ before do
+ allow(order).to receive_messages :confirmation_required? => true
+ end
+
+ it "transitions to confirm" do
+ order.next!
+ assert_state_changed(order, 'payment', 'confirm')
+ expect(order.state).to eq("confirm")
+ end
+ end
+
+ context "without confirmation required" do
+ before do
+ order.email = "spree@example.com"
+ allow(order).to receive_messages :confirmation_required? => false
+ allow(order).to receive_messages :payment_required? => true
+ order.payments << FactoryGirl.create(:payment, state: payment_state, order: order)
+ end
+
+ context 'when there is at least one valid payment' do
+ let(:payment_state) { 'checkout' }
+
+ before do
+ expect(order).to receive(:process_payments!).once { true }
+ end
+
+ it "transitions to complete" do
+ order.next!
+ assert_state_changed(order, 'payment', 'complete')
+ expect(order.state).to eq('complete')
+ end
+ end
+
+ context 'when there is only an invalid payment' do
+ let(:payment_state) { 'failed' }
+
+ it "raises a StateMachine::InvalidTransition" do
+ expect {
+ order.next!
+ }.to raise_error(StateMachine::InvalidTransition, /#{Spree.t(:no_payment_found)}/)
+
+ expect(order.errors[:base]).to include(Spree.t(:no_payment_found))
+ end
+ end
+ end
+
+ # Regression test for #2028
+ context "when payment is not required" do
+ before do
+ allow(order).to receive_messages :payment_required? => false
+ end
+
+ it "does not call process payments" do
+ expect(order).not_to receive(:process_payments!)
+ order.next!
+ assert_state_changed(order, 'payment', 'complete')
+ expect(order.state).to eq("complete")
+ end
+ end
+ end
+ end
+
+ context "to complete" do
+ before do
+ order.state = 'confirm'
+ order.save!
+ end
+
+ context "default credit card" do
+ before do
+ order.user = FactoryGirl.create(:user)
+ order.email = 'spree@example.org'
+ order.payments << FactoryGirl.create(:payment)
+
+ # make sure we will actually capture a payment
+ allow(order).to receive_messages(payment_required?: true)
+ order.line_items << FactoryGirl.create(:line_item)
+ Spree::OrderUpdater.new(order).update
+
+ order.save!
+ end
+
+ it "makes the current credit card a user's default credit card" do
+ order.next!
+ expect(order.state).to eq 'complete'
+ expect(order.user.reload.default_credit_card.try(:id)).to eq(order.credit_cards.first.id)
+ end
+
+ it "does not assign a default credit card if temporary_credit_card is set" do
+ order.temporary_credit_card = true
+ order.next!
+ expect(order.user.reload.default_credit_card).to be_nil
+ end
+ end
+ end
+
+ context "subclassed order" do
+ # This causes another test above to fail, but fixing this test should make
+ # the other test pass
+ class SubclassedOrder < Spree::Order
+ checkout_flow do
+ go_to_state :payment
+ go_to_state :complete
+ end
+ end
+
+ skip "should only call default transitions once when checkout_flow is redefined" do
+ order = SubclassedOrder.new
+ allow(order).to receive_messages :payment_required? => true
+ expect(order).to receive(:process_payments!).once
+ order.state = "payment"
+ order.next!
+ assert_state_changed(order, 'payment', 'complete')
+ expect(order.state).to eq("complete")
+ end
+ end
+
+ context "re-define checkout flow" do
+ before do
+ @old_checkout_flow = Spree::Order.checkout_flow
+ Spree::Order.class_eval do
+ checkout_flow do
+ go_to_state :payment
+ go_to_state :complete
+ end
+ end
+ end
+
+ after do
+ Spree::Order.checkout_flow(&@old_checkout_flow)
+ end
+
+ it "should not keep old event transitions when checkout_flow is redefined" do
+ expect(Spree::Order.next_event_transitions).to eq([{:cart=>:payment}, {:payment=>:complete}])
+ end
+
+ it "should not keep old events when checkout_flow is redefined" do
+ state_machine = Spree::Order.state_machine
+ expect(state_machine.states.any? { |s| s.name == :address }).to be false
+ known_states = state_machine.events[:next].branches.map(&:known_states).flatten
+ expect(known_states).not_to include(:address)
+ expect(known_states).not_to include(:delivery)
+ expect(known_states).not_to include(:confirm)
+ end
+ end
+
+ # Regression test for #3665
+ context "with only a complete step" do
+ before do
+ @old_checkout_flow = Spree::Order.checkout_flow
+ Spree::Order.class_eval do
+ checkout_flow do
+ go_to_state :complete
+ end
+ end
+ end
+
+ after do
+ Spree::Order.checkout_flow(&@old_checkout_flow)
+ end
+
+ it "does not attempt to process payments" do
+ allow(order).to receive_message_chain(:line_items, :present?) { true }
+ allow(order).to receive(:ensure_line_items_are_in_stock) { true }
+ allow(order).to receive(:ensure_line_item_variants_are_not_deleted) { true }
+ expect(order).not_to receive(:payment_required?)
+ expect(order).not_to receive(:process_payments!)
+ order.next!
+ assert_state_changed(order, 'cart', 'complete')
+ end
+
+ end
+
+ context "insert checkout step" do
+ before do
+ @old_checkout_flow = Spree::Order.checkout_flow
+ Spree::Order.class_eval do
+ insert_checkout_step :new_step, before: :address
+ end
+ end
+
+ after do
+ Spree::Order.checkout_flow(&@old_checkout_flow)
+ end
+
+ it "should maintain removed transitions" do
+ transition = Spree::Order.find_transition(:from => :delivery, :to => :confirm)
+ expect(transition).to be_nil
+ end
+
+ context "before" do
+ before do
+ Spree::Order.class_eval do
+ insert_checkout_step :before_address, before: :address
+ end
+ end
+
+ specify do
+ order = Spree::Order.new
+ expect(order.checkout_steps).to eq(%w(new_step before_address address delivery complete))
+ end
+ end
+
+ context "after" do
+ before do
+ Spree::Order.class_eval do
+ insert_checkout_step :after_address, after: :address
+ end
+ end
+
+ specify do
+ order = Spree::Order.new
+ expect(order.checkout_steps).to eq(%w(new_step address after_address delivery complete))
+ end
+ end
+ end
+
+ context "remove checkout step" do
+ before do
+ @old_checkout_flow = Spree::Order.checkout_flow
+ Spree::Order.class_eval do
+ remove_checkout_step :address
+ end
+ end
+
+ after do
+ Spree::Order.checkout_flow(&@old_checkout_flow)
+ end
+
+ it "should maintain removed transitions" do
+ transition = Spree::Order.find_transition(:from => :delivery, :to => :confirm)
+ expect(transition).to be_nil
+ end
+
+ specify do
+ order = Spree::Order.new
+ expect(order.checkout_steps).to eq(%w(delivery complete))
+ end
+ end
+
+ describe "payment processing" do
+ self.use_transactional_fixtures = false
+ before do
+ Spree::PaymentMethod.destroy_all # TODO data is leaking between specs as database_cleaner or rspec 3 was broken in Rails 4.1.6 & 4.0.10
+ # Turn off transactional fixtures so that we can test that
+ # processing state is persisted.
+ DatabaseCleaner.strategy = :truncation
+ end
+
+ after do
+ DatabaseCleaner.clean
+ # Turn on transactional fixtures again.
+ self.use_transactional_fixtures = true
+ end
+
+ let(:order) { OrderWalkthrough.up_to(:payment) }
+ let(:creditcard) { create(:credit_card) }
+ let!(:payment_method) { create(:credit_card_payment_method, environment: 'test') }
+
+ it "does not process payment within transaction" do
+ # Make sure we are not already in a transaction
+ expect(ActiveRecord::Base.connection.open_transactions).to eq 0
+
+ expect_any_instance_of(Spree::Payment).to receive(:authorize!) do
+ expect(ActiveRecord::Base.connection.open_transactions).to eq 0
+ end
+
+ order.next!
+ end
+ end
+
+ describe 'update_from_params' do
+ let(:permitted_params) { {} }
+ let(:params) { {} }
+
+ it 'calls update_atributes without order params' do
+ expect(order).to receive(:update_attributes).with({})
+ order.update_from_params( params, permitted_params)
+ end
+
+ it 'runs the callbacks' do
+ expect(order).to receive(:run_callbacks).with(:updating_from_params)
+ order.update_from_params( params, permitted_params)
+ end
+
+ context "passing a credit card" do
+ let(:permitted_params) do
+ Spree::PermittedAttributes.checkout_attributes +
+ [payments_attributes: Spree::PermittedAttributes.payment_attributes]
+ end
+
+ let(:credit_card) { create(:credit_card, user_id: order.user_id) }
+
+ let(:params) do
+ ActionController::Parameters.new(
+ order: { payments_attributes: [{payment_method_id: 1}], existing_card: credit_card.id },
+ cvc_confirm: "737",
+ payment_source: {
+ "1" => { name: "Luis Braga",
+ number: "4111 1111 1111 1111",
+ expiry: "06 / 2016",
+ verification_value: "737",
+ cc_type: "" }
+ }
+ )
+ end
+
+ before { order.user_id = 3 }
+
+ it "sets confirmation value when its available via :cvc_confirm" do
+ allow(Spree::CreditCard).to receive_messages find: credit_card
+ expect(credit_card).to receive(:verification_value=)
+ order.update_from_params(params, permitted_params)
+ end
+
+ it "sets existing card as source for new payment" do
+ expect {
+ order.update_from_params(params, permitted_params)
+ }.to change { Spree::Payment.count }.by(1)
+
+ expect(Spree::Payment.last.source).to eq credit_card
+ end
+
+ it "sets request_env on payment" do
+ request_env = { "USER_AGENT" => "Firefox" }
+
+ expected_hash = { "payments_attributes" => [hash_including("request_env" => request_env)] }
+ expect(order).to receive(:update_attributes).with expected_hash
+
+ order.update_from_params(params, permitted_params, request_env)
+ end
+
+ it "dont let users mess with others users cards" do
+ credit_card.update_column :user_id, 5
+
+ expect {
+ order.update_from_params(params, permitted_params)
+ }.to raise_error
+ end
+ end
+
+ context 'has params' do
+ let(:permitted_params) { [ :good_param ] }
+ let(:params) { ActionController::Parameters.new(order: { bad_param: 'okay' } ) }
+
+ it 'does not let through unpermitted attributes' do
+ expect(order).to receive(:update_attributes).with({})
+ order.update_from_params(params, permitted_params)
+ end
+
+ context 'has allowed params' do
+ let(:params) { ActionController::Parameters.new(order: { good_param: 'okay' } ) }
+
+ it 'accepts permitted attributes' do
+ expect(order).to receive(:update_attributes).with({"good_param" => 'okay'})
+ order.update_from_params(params, permitted_params)
+ end
+ end
+
+ context 'callbacks halt' do
+ before do
+ expect(order).to receive(:update_params_payment_source).and_return false
+ end
+ it 'does not let through unpermitted attributes' do
+ expect(order).not_to receive(:update_attributes).with({})
+ order.update_from_params(params, permitted_params)
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/currency_updater_spec.rb b/core/spec/models/spree/order/currency_updater_spec.rb
new file mode 100644
index 00000000000..c515aee98ce
--- /dev/null
+++ b/core/spec/models/spree/order/currency_updater_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ context 'CurrencyUpdater' do
+ context "when changing order currency" do
+ let!(:line_item) { create(:line_item) }
+ let!(:euro_price) { create(:price, variant: line_item.variant, amount: 8, currency: 'EUR') }
+
+ context "#homogenize_line_item_currencies" do
+ it "succeeds without error" do
+ expect { line_item.order.update_attributes!(currency: 'EUR') }.to_not raise_error
+ end
+
+ it "changes the line_item currencies" do
+ expect { line_item.order.update_attributes!(currency: 'EUR') }.to change{ line_item.reload.currency }.from('USD').to('EUR')
+ end
+
+ it "changes the line_item amounts" do
+ expect { line_item.order.update_attributes!(currency: 'EUR') }.to change{ line_item.reload.amount }.to(8)
+ end
+
+ it "fails to change the order currency when no prices are available in that currency" do
+ expect { line_item.order.update_attributes!(currency: 'GBP') }.to raise_error
+ end
+
+ it "calculates the item total in the order.currency" do
+ expect { line_item.order.update_attributes!(currency: 'EUR') }.to change{ line_item.order.item_total }.to(8)
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/finalizing_spec.rb b/core/spec/models/spree/order/finalizing_spec.rb
new file mode 100644
index 00000000000..29648a8b0ae
--- /dev/null
+++ b/core/spec/models/spree/order/finalizing_spec.rb
@@ -0,0 +1,115 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ let(:order) { stub_model("Spree::Order") }
+
+ context "#finalize!" do
+ let(:order) { Spree::Order.create(email: 'test@example.com') }
+
+ before do
+ order.update_column :state, 'complete'
+ end
+
+ it "should set completed_at" do
+ expect(order).to receive(:touch).with(:completed_at)
+ order.finalize!
+ end
+
+ it "should sell inventory units" do
+ order.shipments.each do |shipment|
+ expect(shipment).to receive(:update!)
+ expect(shipment).to receive(:finalize!)
+ end
+ order.finalize!
+ end
+
+ it "should decrease the stock for each variant in the shipment" do
+ order.shipments.each do |shipment|
+ expect(shipment.stock_location).to receive(:decrease_stock_for_variant)
+ end
+ order.finalize!
+ end
+
+ it "should change the shipment state to ready if order is paid" do
+ Spree::Shipment.create(order: order, stock_location: create(:stock_location))
+ order.shipments.reload
+
+ allow(order).to receive_messages(paid?: true, complete?: true)
+ order.finalize!
+ order.reload # reload so we're sure the changes are persisted
+ expect(order.shipment_state).to eq('ready')
+ end
+
+ after { Spree::Config.set track_inventory_levels: true }
+ it "should not sell inventory units if track_inventory_levels is false" do
+ Spree::Config.set track_inventory_levels: false
+ expect(Spree::InventoryUnit).not_to receive(:sell_units)
+ order.finalize!
+ end
+
+ it "should send an order confirmation email" do
+ mail_message = double "Mail::Message"
+ expect(Spree::OrderMailer).to receive(:confirm_email).with(order.id).and_return mail_message
+ expect(mail_message).to receive :deliver
+ order.finalize!
+ end
+
+ it "sets confirmation delivered when finalizing" do
+ expect(order.confirmation_delivered?).to be false
+ order.finalize!
+ expect(order.confirmation_delivered?).to be true
+ end
+
+ it "should not send duplicate confirmation emails" do
+ allow(order).to receive_messages(:confirmation_delivered? => true)
+ expect(Spree::OrderMailer).not_to receive(:confirm_email)
+ order.finalize!
+ end
+
+ it "should freeze all adjustments" do
+ # Stub this method as it's called due to a callback
+ # and it's irrelevant to this test
+ allow(order).to receive :has_available_shipment
+ allow(Spree::OrderMailer).to receive_message_chain :confirm_email, :deliver
+ adjustments = [double]
+ expect(order).to receive(:all_adjustments).and_return(adjustments)
+ adjustments.each do |adj|
+ expect(adj).to receive(:close)
+ end
+ order.finalize!
+ end
+
+ context "order is considered risky" do
+ before do
+ allow(order).to receive_messages :is_risky? => true
+ end
+
+ it "should change state to risky" do
+ expect(order).to receive(:considered_risky!)
+ order.finalize!
+ end
+
+ context "and order is approved" do
+ before do
+ allow(order).to receive_messages :approved? => true
+ end
+
+ it "should leave order in complete state" do
+ order.finalize!
+ expect(order.state).to eq 'complete'
+ end
+ end
+ end
+
+ context "order is not considered risky" do
+ before do
+ allow(order).to receive_messages :is_risky? => false
+ end
+
+ it "should set completed_at" do
+ order.finalize!
+ expect(order.completed_at).to be_present
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/helpers_spec.rb b/core/spec/models/spree/order/helpers_spec.rb
new file mode 100644
index 00000000000..2a1064b986a
--- /dev/null
+++ b/core/spec/models/spree/order/helpers_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ let(:order) { stub_model(Spree::Order) }
+end
\ No newline at end of file
diff --git a/core/spec/models/spree/order/payment_spec.rb b/core/spec/models/spree/order/payment_spec.rb
new file mode 100644
index 00000000000..0d9f8084ad8
--- /dev/null
+++ b/core/spec/models/spree/order/payment_spec.rb
@@ -0,0 +1,212 @@
+require 'spec_helper'
+
+module Spree
+ describe Spree::Order, :type => :model do
+ let(:order) { stub_model(Spree::Order) }
+ let(:updater) { Spree::OrderUpdater.new(order) }
+
+ context "processing payments" do
+ before do
+ # So that Payment#purchase! is called during processing
+ Spree::Config[:auto_capture] = true
+
+ allow(order).to receive_message_chain(:line_items, :empty?).and_return(false)
+ allow(order).to receive_messages :total => 100
+ end
+
+ it 'processes all checkout payments' do
+ payment_1 = create(:payment, :amount => 50)
+ payment_2 = create(:payment, :amount => 50)
+ allow(order).to receive(:unprocessed_payments).and_return([payment_1, payment_2])
+
+ order.process_payments!
+ updater.update_payment_state
+ expect(order.payment_state).to eq('paid')
+
+ expect(payment_1).to be_completed
+ expect(payment_2).to be_completed
+ end
+
+ it 'does not go over total for order' do
+ payment_1 = create(:payment, :amount => 50)
+ payment_2 = create(:payment, :amount => 50)
+ payment_3 = create(:payment, :amount => 50)
+ allow(order).to receive(:unprocessed_payments).and_return([payment_1, payment_2, payment_3])
+
+ order.process_payments!
+ updater.update_payment_state
+ expect(order.payment_state).to eq('paid')
+
+ expect(payment_1).to be_completed
+ expect(payment_2).to be_completed
+ expect(payment_3).to be_checkout
+ end
+
+ it "does not use failed payments" do
+ payment_1 = create(:payment, :amount => 50)
+ payment_2 = create(:payment, :amount => 50, :state => 'failed')
+ allow(order).to receive(:pending_payments).and_return([payment_1])
+
+ expect(payment_2).not_to receive(:process!)
+
+ order.process_payments!
+ end
+ end
+
+ context "ensure source attributes stick around" do
+ # For the reason of this test, please see spree/spree_gateway#132
+ it "does not have inverse_of defined" do
+ expect(Spree::Order.reflections[:payments].options[:inverse_of]).to be_nil
+ end
+
+ it "keeps source attributes after updating" do
+ persisted_order = Spree::Order.create
+ credit_card_payment_method = create(:credit_card_payment_method)
+ attributes = {
+ :payments_attributes => [
+ {
+ :payment_method_id => credit_card_payment_method.id,
+ :source_attributes => {
+ :name => "Ryan Bigg",
+ :number => "41111111111111111111",
+ :expiry => "01 / 15",
+ :verification_value => "123"
+ }
+ }
+ ]
+ }
+
+ persisted_order.update_attributes(attributes)
+ expect(persisted_order.unprocessed_payments.last.source.number).to be_present
+ end
+ end
+
+ context "checking if order is paid" do
+ context "payment_state is paid" do
+ before { allow(order).to receive_messages payment_state: 'paid' }
+ it { expect(order).to be_paid }
+ end
+
+ context "payment_state is credit_owned" do
+ before { allow(order).to receive_messages payment_state: 'credit_owed' }
+ it { expect(order).to be_paid }
+ end
+ end
+
+ context "#process_payments!" do
+ let(:payment) { stub_model(Spree::Payment) }
+ before { allow(order).to receive_messages unprocessed_payments: [payment], total: 10 }
+
+ it "should process the payments" do
+ expect(payment).to receive(:process!)
+ expect(order.process_payments!).to be_truthy
+ end
+
+ # Regression spec for https://github.com/spree/spree/issues/5436
+ it 'should raise an error if there are no payments to process' do
+ allow(order).to receive_messages unprocessed_payments: []
+ expect(payment).to_not receive(:process!)
+ expect(order.process_payments!).to be_falsey
+ end
+
+ context "when a payment raises a GatewayError" do
+ before { expect(payment).to receive(:process!).and_raise(Spree::Core::GatewayError) }
+
+ it "should return true when configured to allow checkout on gateway failures" do
+ Spree::Config.set :allow_checkout_on_gateway_error => true
+ expect(order.process_payments!).to be true
+ end
+
+ it "should return false when not configured to allow checkout on gateway failures" do
+ Spree::Config.set :allow_checkout_on_gateway_error => false
+ expect(order.process_payments!).to be false
+ end
+ end
+ end
+
+ context "#authorize_payments!" do
+ let(:payment) { stub_model(Spree::Payment) }
+ before { allow(order).to receive_messages :unprocessed_payments => [payment], :total => 10 }
+ subject { order.authorize_payments! }
+
+ it "processes payments with attempt_authorization!" do
+ expect(payment).to receive(:authorize!)
+ subject
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "#capture_payments!" do
+ let(:payment) { stub_model(Spree::Payment) }
+ before { allow(order).to receive_messages :unprocessed_payments => [payment], :total => 10 }
+ subject { order.capture_payments! }
+
+ it "processes payments with attempt_authorization!" do
+ expect(payment).to receive(:purchase!)
+ subject
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context "#outstanding_balance" do
+ it "should return positive amount when payment_total is less than total" do
+ order.payment_total = 20.20
+ order.total = 30.30
+ expect(order.outstanding_balance).to eq(10.10)
+ end
+ it "should return negative amount when payment_total is greater than total" do
+ order.total = 8.20
+ order.payment_total = 10.20
+ expect(order.outstanding_balance).to be_within(0.001).of(-2.00)
+ end
+ it 'should incorporate refund reimbursements' do
+ # Creates an order w/total 10
+ reimbursement = create :reimbursement
+ # Set the payment amount to actually be the order total of 10
+ reimbursement.order.payments.first.update_column :amount, 10
+ # Creates a refund of 10
+ create :refund, amount: 10,
+ payment: reimbursement.order.payments.first,
+ reimbursement: reimbursement
+ order = reimbursement.order.reload
+ # Update the order totals so payment_total goes to 0 reflecting the refund..
+ order.update!
+ # Order Total - (Payment Total + Reimbursed)
+ # 10 - (0 + 10) = 0
+ expect(order.outstanding_balance).to eq 0
+ end
+ end
+
+ context "#outstanding_balance?" do
+ it "should be true when total greater than payment_total" do
+ order.total = 10.10
+ order.payment_total = 9.50
+ expect(order.outstanding_balance?).to be true
+ end
+ it "should be true when total less than payment_total" do
+ order.total = 8.25
+ order.payment_total = 10.44
+ expect(order.outstanding_balance?).to be true
+ end
+ it "should be false when total equals payment_total" do
+ order.total = 10.10
+ order.payment_total = 10.10
+ expect(order.outstanding_balance?).to be false
+ end
+ end
+
+ context "payment required?" do
+ context "total is zero" do
+ before { allow(order).to receive_messages(total: 0) }
+ it { expect(order.payment_required?).to be false }
+ end
+
+ context "total > zero" do
+ before { allow(order).to receive_messages(total: 1) }
+ it { expect(order.payment_required?).to be true }
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/risk_assessment_spec.rb b/core/spec/models/spree/order/risk_assessment_spec.rb
new file mode 100644
index 00000000000..1bc5a8e43fa
--- /dev/null
+++ b/core/spec/models/spree/order/risk_assessment_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe Spree::Order, :type => :model do
+ let(:order) { stub_model('Spree::Order') }
+
+ describe ".is_risky?" do
+ context "Not risky order" do
+ let(:order) { FactoryGirl.create(:order, payments: [payment]) }
+ context "with avs_response == D" do
+ let(:payment) { FactoryGirl.create(:payment, avs_response: "D") }
+ it "is not considered risky" do
+ expect(order.is_risky?).to eq(false)
+ end
+ end
+
+ context "with avs_response == M" do
+ let(:payment) { FactoryGirl.create(:payment, avs_response: "M") }
+ it "is not considered risky" do
+ expect(order.is_risky?).to eq(false)
+ end
+ end
+
+ context "with avs_response == ''" do
+ let(:payment) { FactoryGirl.create(:payment, avs_response: "") }
+ it "is not considered risky" do
+ expect(order.is_risky?).to eq(false)
+ end
+ end
+
+ context "with cvv_response_code == M" do
+ let(:payment) { FactoryGirl.create(:payment, cvv_response_code: "M") }
+ it "is not considered risky" do
+ expect(order.is_risky?).to eq(false)
+ end
+ end
+
+ context "with cvv_response_message == ''" do
+ let(:payment) { FactoryGirl.create(:payment, cvv_response_message: "") }
+ it "is not considered risky" do
+ expect(order.is_risky?).to eq(false)
+ end
+ end
+ end
+
+ context "Risky order" do
+ context "AVS response message" do
+ let(:order) { FactoryGirl.create(:order, payments: [FactoryGirl.create(:payment, avs_response: "A")]) }
+ it "returns true if the order has an avs_response" do
+ expect(order.is_risky?).to eq(true)
+ end
+ end
+
+ context "CVV response code" do
+ let(:order) { FactoryGirl.create(:order, payments: [FactoryGirl.create(:payment, cvv_response_code: "N")]) }
+ it "returns true if the order has an cvv_response_code" do
+ expect(order.is_risky?).to eq(true)
+ end
+ end
+
+ context "state == 'failed'" do
+ let(:order) { FactoryGirl.create(:order, payments: [FactoryGirl.create(:payment, state: 'failed')]) }
+ it "returns true if the order has state == 'failed'" do
+ expect(order.is_risky?).to eq(true)
+ end
+ end
+ end
+ end
+
+ context "is considered risky" do
+ let(:order) do
+ order = FactoryGirl.create(:completed_order_with_pending_payment)
+ order.considered_risky!
+ order
+ end
+
+ it "can be approved by a user" do
+ expect(order).to receive(:approve!)
+ order.approved_by(stub_model(Spree::LegacyUser, id: 1))
+ expect(order.approver_id).to eq(1)
+ expect(order.approved_at).to be_present
+ expect(order.approved?).to be true
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/shipments_spec.rb b/core/spec/models/spree/order/shipments_spec.rb
new file mode 100644
index 00000000000..f8871668c13
--- /dev/null
+++ b/core/spec/models/spree/order/shipments_spec.rb
@@ -0,0 +1,43 @@
+describe Spree::Order, type: :model do
+ let(:order) { create(:order_with_totals) }
+
+ context "ensure shipments will be updated" do
+ before { Spree::Shipment.create!(order: order, stock_location: create(:stock_location)) }
+
+ it "destroys current shipments" do
+ order.ensure_updated_shipments
+ expect(order.shipments).to be_empty
+ end
+
+ it "puts order back in address state" do
+ order.ensure_updated_shipments
+ expect(order.state).to eq 'address'
+ end
+
+ it "resets shipment_total" do
+ order.update_column(:shipment_total, 5)
+ order.ensure_updated_shipments
+ expect(order.shipment_total).to eq(0)
+ end
+
+ context "except when order is completed, that's OrderInventory job" do
+ it "doesn't touch anything" do
+ allow(order).to receive_messages completed?: true
+ order.update_column(:shipment_total, 5)
+ order.shipments.create!(stock_location: create(:stock_location))
+
+ expect {
+ order.ensure_updated_shipments
+ }.not_to change { order.shipment_total }
+
+ expect {
+ order.ensure_updated_shipments
+ }.not_to change { order.shipments }
+
+ expect {
+ order.ensure_updated_shipments
+ }.not_to change { order.state }
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/state_machine_spec.rb b/core/spec/models/spree/order/state_machine_spec.rb
new file mode 100644
index 00000000000..e0d924ee334
--- /dev/null
+++ b/core/spec/models/spree/order/state_machine_spec.rb
@@ -0,0 +1,219 @@
+require 'spec_helper'
+
+describe Spree::Order, type: :model do
+ let(:order) { Spree::Order.new }
+ before do
+ # Ensure state machine has been re-defined correctly
+ Spree::Order.define_state_machine!
+ # We don't care about this validation here
+ allow(order).to receive(:require_email)
+ end
+
+ context "#next!" do
+ context "when current state is confirm" do
+ before do
+ order.state = "confirm"
+ order.run_callbacks(:create)
+ allow(order).to receive_messages payment_required?: true
+ allow(order).to receive_messages process_payments!: true
+ allow(order).to receive :has_available_shipment
+ end
+
+ context "when payment processing succeeds" do
+ before do
+ order.payments << FactoryGirl.create(:payment, state: 'checkout', order: order)
+ allow(order).to receive_messages process_payments: true
+ end
+
+ it "should finalize order when transitioning to complete state" do
+ expect(order).to receive(:finalize!)
+ order.next!
+ end
+
+ context "when credit card processing fails" do
+ before { allow(order).to receive_messages process_payments!: false }
+
+ it "should not complete the order" do
+ order.next
+ expect(order.state).to eq("confirm")
+ end
+ end
+ end
+
+ context "when payment processing fails" do
+ before { allow(order).to receive_messages process_payments!: false }
+
+ it "cannot transition to complete" do
+ order.next
+ expect(order.state).to eq("confirm")
+ end
+ end
+ end
+
+ context "when current state is delivery" do
+ before do
+ allow(order).to receive_messages payment_required?: true
+ allow(order).to receive :apply_free_shipping_promotions
+ order.state = "delivery"
+ end
+
+ it "adjusts tax rates when transitioning to delivery" do
+ # Once for the line items
+ expect(Spree::TaxRate).to receive(:adjust).once
+ allow(order).to receive :set_shipments_cost
+ order.next!
+ end
+
+ it "adjusts tax rates twice if there are any shipments" do
+ # Once for the line items, once for the shipments
+ order.shipments.build stock_location: create(:stock_location)
+ expect(Spree::TaxRate).to receive(:adjust).twice
+ allow(order).to receive :set_shipments_cost
+ order.next!
+ end
+ end
+ end
+
+ context "#can_cancel?" do
+
+ %w(pending backorder ready).each do |shipment_state|
+ it "should be true if shipment_state is #{shipment_state}" do
+ allow(order).to receive_messages completed?: true
+ order.shipment_state = shipment_state
+ expect(order.can_cancel?).to be true
+ end
+ end
+
+ (Spree::Shipment.state_machine.states.keys - %w(pending backorder ready)).each do |shipment_state|
+ it "should be false if shipment_state is #{shipment_state}" do
+ allow(order).to receive_messages completed?: true
+ order.shipment_state = shipment_state
+ expect(order.can_cancel?).to be false
+ end
+ end
+
+ end
+
+ context "#cancel" do
+ let!(:variant) { stub_model(Spree::Variant) }
+ let!(:inventory_units) { [stub_model(Spree::InventoryUnit, variant: variant),
+ stub_model(Spree::InventoryUnit, variant: variant)] }
+ let!(:shipment) do
+ shipment = stub_model(Spree::Shipment)
+ allow(shipment).to receive_messages inventory_units: inventory_units, order: order
+ allow(order).to receive_messages shipments: [shipment]
+ shipment
+ end
+
+ before do
+ 2.times do
+ create(:line_item, order: order, price: 10)
+ end
+
+ allow(order.line_items).to receive_messages find_by_variant_id: order.line_items.first
+
+ allow(order).to receive_messages completed?: true
+ allow(order).to receive_messages allow_cancel?: true
+
+ shipments = [shipment]
+ allow(order).to receive_messages shipments: shipments
+ allow(shipments).to receive_messages states: []
+ allow(shipments).to receive_messages ready: []
+ allow(shipments).to receive_messages pending: []
+ allow(shipments).to receive_messages shipped: []
+
+ allow_any_instance_of(Spree::OrderUpdater).to receive(:update_adjustment_total) { 10 }
+ end
+
+ it "should send a cancel email" do
+
+ # Stub methods that cause side-effects in this test
+ allow(shipment).to receive(:cancel!)
+ allow(order).to receive :has_available_shipment
+ allow(order).to receive :restock_items!
+ mail_message = double "Mail::Message"
+ order_id = nil
+ expect(Spree::OrderMailer).to receive(:cancel_email) { |*args|
+ order_id = args[0]
+ mail_message
+ }
+ expect(mail_message).to receive :deliver
+ order.cancel!
+ expect(order_id).to eq(order.id)
+ end
+
+ context "restocking inventory" do
+ before do
+ allow(shipment).to receive(:ensure_correct_adjustment)
+ allow(shipment).to receive(:update_order)
+ allow(Spree::OrderMailer).to receive(:cancel_email).and_return(mail_message = double)
+ allow(mail_message).to receive :deliver
+
+ allow(order).to receive :has_available_shipment
+ end
+ end
+
+ context "resets payment state" do
+ let(:payment) { create(:payment, amount: order.total) }
+
+ before do
+ # TODO: This is ugly :(
+ # Stubs methods that cause unwanted side effects in this test
+ allow(Spree::OrderMailer).to receive(:cancel_email).and_return(mail_message = double)
+ allow(mail_message).to receive :deliver
+ allow(order).to receive :has_available_shipment
+ allow(order).to receive :restock_items!
+ allow(shipment).to receive(:cancel!)
+ allow(payment).to receive(:cancel!)
+ allow(order).to receive_message_chain(:payments, :valid, :size).and_return(1)
+ allow(order).to receive_message_chain(:payments, :completed).and_return([payment])
+ allow(order).to receive_message_chain(:payments, :completed, :includes).and_return([payment])
+ allow(order).to receive_message_chain(:payments, :last).and_return(payment)
+ end
+
+ context "without shipped items" do
+ it "should set payment state to 'void'" do
+ expect { order.cancel! }.to change{ order.reload.payment_state }.to("void")
+ end
+ end
+
+ context "with shipped items" do
+ before do
+ allow(order).to receive_messages shipment_state: 'partial'
+ allow(order).to receive_messages outstanding_balance?: false
+ allow(order).to receive_messages payment_state: "paid"
+ end
+
+ it "should not alter the payment state" do
+ order.cancel!
+ expect(order.payment_state).to eql "paid"
+ end
+ end
+
+ context "with payments" do
+ let(:payment) { create(:payment) }
+
+ it "should automatically refund all payments" do
+ allow(order).to receive_message_chain(:payments, :valid, :size).and_return(1)
+ allow(order).to receive_message_chain(:payments, :completed).and_return([payment])
+ allow(order).to receive_message_chain(:payments, :completed, :includes).and_return([payment])
+ allow(order).to receive_message_chain(:payments, :last).and_return(payment)
+ expect(payment).to receive(:cancel!)
+ order.cancel!
+ end
+ end
+ end
+ end
+
+ # Another regression test for #729
+ context "#resume" do
+ before do
+ allow(order).to receive_messages email: "user@spreecommerce.com"
+ allow(order).to receive_messages state: "canceled"
+ allow(order).to receive_messages allow_resume?: true
+
+ # Stubs method that cause unwanted side effects in this test
+ allow(order).to receive :has_available_shipment
+ end
+ end
+end
diff --git a/core/spec/models/spree/order/tax_spec.rb b/core/spec/models/spree/order/tax_spec.rb
new file mode 100644
index 00000000000..92ec95caaab
--- /dev/null
+++ b/core/spec/models/spree/order/tax_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+module Spree
+ describe Spree::Order, :type => :model do
+ let(:order) { stub_model(Spree::Order) }
+
+ context "#tax_zone" do
+ let(:bill_address) { create :address }
+ let(:ship_address) { create :address }
+ let(:order) { Spree::Order.create(:ship_address => ship_address, :bill_address => bill_address) }
+ let(:zone) { create :zone }
+
+ context "when no zones exist" do
+ it "should return nil" do
+ expect(order.tax_zone).to be_nil
+ end
+ end
+
+ context "when :tax_using_ship_address => true" do
+ before { Spree::Config.set(:tax_using_ship_address => true) }
+
+ it "should calculate using ship_address" do
+ expect(Spree::Zone).to receive(:match).at_least(:once).with(ship_address)
+ expect(Spree::Zone).not_to receive(:match).with(bill_address)
+ order.tax_zone
+ end
+ end
+
+ context "when :tax_using_ship_address => false" do
+ before { Spree::Config.set(:tax_using_ship_address => false) }
+
+ it "should calculate using bill_address" do
+ expect(Spree::Zone).to receive(:match).at_least(:once).with(bill_address)
+ expect(Spree::Zone).not_to receive(:match).with(ship_address)
+ order.tax_zone
+ end
+ end
+
+ context "when there is a default tax zone" do
+ before do
+ @default_zone = create(:zone, :name => "foo_zone")
+ allow(Spree::Zone).to receive_messages :default_tax => @default_zone
+ end
+
+ context "when there is a matching zone" do
+ before { allow(Spree::Zone).to receive_messages(:match => zone) }
+
+ it "should return the matching zone" do
+ expect(order.tax_zone).to eq(zone)
+ end
+ end
+
+ context "when there is no matching zone" do
+ before { allow(Spree::Zone).to receive_messages(:match => nil) }
+
+ it "should return the default tax zone" do
+ expect(order.tax_zone).to eq(@default_zone)
+ end
+ end
+ end
+
+ context "when no default tax zone" do
+ before { allow(Spree::Zone).to receive_messages :default_tax => nil }
+
+ context "when there is a matching zone" do
+ before { allow(Spree::Zone).to receive_messages(:match => zone) }
+
+ it "should return the matching zone" do
+ expect(order.tax_zone).to eq(zone)
+ end
+ end
+
+ context "when there is no matching zone" do
+ before { allow(Spree::Zone).to receive_messages(:match => nil) }
+
+ it "should return nil" do
+ expect(order.tax_zone).to be_nil
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/core/spec/models/spree/order/totals_spec.rb b/core/spec/models/spree/order/totals_spec.rb
new file mode 100644
index 00000000000..35db1ff808b
--- /dev/null
+++ b/core/spec/models/spree/order/totals_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+module Spree
+ describe Order, :type => :model do
+ let(:order) { Order.create }
+ let(:shirt) { create(:variant) }
+
+ context "adds item to cart and activates promo" do
+ let(:promotion) { Promotion.create name: 'Huhu' }
+ let(:calculator) { Calculator::FlatPercentItemTotal.new(:preferred_flat_percent => 10) }
+ let!(:action) { Promotion::Actions::CreateAdjustment.create(promotion: promotion, calculator: calculator) }
+
+ before { order.contents.add(shirt, 1) }
+
+ context "item quantity changes" do
+ it "recalculates order adjustments" do
+ expect {
+ order.contents.add(shirt, 3)
+ }.to change { order.adjustments.eligible.pluck(:amount) }
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/order/updating_spec.rb b/core/spec/models/spree/order/updating_spec.rb
similarity index 83%
rename from core/spec/models/order/updating_spec.rb
rename to core/spec/models/spree/order/updating_spec.rb
index 9b50d029867..8a8dd4112a5 100644
--- a/core/spec/models/order/updating_spec.rb
+++ b/core/spec/models/spree/order/updating_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Spree::Order do
+describe Spree::Order, :type => :model do
let(:order) { stub_model(Spree::Order) }
context "#update!" do
@@ -10,7 +10,7 @@
before { Spree::Order.register_update_hook :foo }
after { Spree::Order.update_hooks.clear }
it "should call each of the update hooks" do
- order.should_receive :foo
+ expect(order).to receive :foo
order.update!
end
end
diff --git a/core/spec/models/spree/order/validations_spec.rb b/core/spec/models/spree/order/validations_spec.rb
new file mode 100644
index 00000000000..f57eb95d78a
--- /dev/null
+++ b/core/spec/models/spree/order/validations_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+module Spree
+ describe Spree::Order, :type => :model do
+ context "validations" do
+ # Regression test for #2214
+ it "does not return two error messages when email is blank" do
+ order = Spree::Order.new
+ allow(order).to receive_messages(:require_email => true)
+ order.valid?
+ expect(order.errors[:email]).to eq(["can't be blank"])
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_contents_spec.rb b/core/spec/models/spree/order_contents_spec.rb
new file mode 100644
index 00000000000..a6c5bce5ac0
--- /dev/null
+++ b/core/spec/models/spree/order_contents_spec.rb
@@ -0,0 +1,227 @@
+require 'spec_helper'
+
+describe Spree::OrderContents, :type => :model do
+ let(:order) { Spree::Order.create }
+ let(:variant) { create(:variant) }
+
+ subject { described_class.new(order) }
+
+ context "#add" do
+ context 'given quantity is not explicitly provided' do
+ it 'should add one line item' do
+ line_item = subject.add(variant)
+ expect(line_item.quantity).to eq(1)
+ expect(order.line_items.size).to eq(1)
+ end
+ end
+
+ context 'given a shipment' do
+ it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do
+ shipment = create(:shipment)
+ expect(subject.order).to_not receive(:ensure_updated_shipments)
+ expect(shipment).to receive(:update_amounts)
+ subject.add(variant, 1, shipment: shipment)
+ end
+ end
+
+ context 'not given a shipment' do
+ it "ensures updated shipments" do
+ expect(subject.order).to receive(:ensure_updated_shipments)
+ subject.add(variant)
+ end
+ end
+
+ it 'should add line item if one does not exist' do
+ line_item = subject.add(variant, 1)
+ expect(line_item.quantity).to eq(1)
+ expect(order.line_items.size).to eq(1)
+ end
+
+ it 'should update line item if one exists' do
+ subject.add(variant, 1)
+ line_item = subject.add(variant, 1)
+ expect(line_item.quantity).to eq(2)
+ expect(order.line_items.size).to eq(1)
+ end
+
+ it "should update order totals" do
+ expect(order.item_total.to_f).to eq(0.00)
+ expect(order.total.to_f).to eq(0.00)
+
+ subject.add(variant, 1)
+
+ expect(order.item_total.to_f).to eq(19.99)
+ expect(order.total.to_f).to eq(19.99)
+ end
+
+ context "running promotions" do
+ let(:promotion) { create(:promotion) }
+ let(:calculator) { Spree::Calculator::FlatRate.new(:preferred_amount => 10) }
+
+ shared_context "discount changes order total" do
+ before { subject.add(variant, 1) }
+ it { expect(subject.order.total).not_to eq variant.price }
+ end
+
+ context "one active order promotion" do
+ let!(:action) { Spree::Promotion::Actions::CreateAdjustment.create(promotion: promotion, calculator: calculator) }
+
+ it "creates valid discount on order" do
+ subject.add(variant, 1)
+ expect(subject.order.adjustments.to_a.sum(&:amount)).not_to eq 0
+ end
+
+ include_context "discount changes order total"
+ end
+
+ context "one active line item promotion" do
+ let!(:action) { Spree::Promotion::Actions::CreateItemAdjustments.create(promotion: promotion, calculator: calculator) }
+
+ it "creates valid discount on order" do
+ subject.add(variant, 1)
+ expect(subject.order.line_item_adjustments.to_a.sum(&:amount)).not_to eq 0
+ end
+
+ include_context "discount changes order total"
+ end
+ end
+ end
+
+ context "#remove" do
+ context "given an invalid variant" do
+ it "raises an exception" do
+ expect {
+ subject.remove(variant, 1)
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'given quantity is not explicitly provided' do
+ it 'should remove one line item' do
+ line_item = subject.add(variant, 3)
+ subject.remove(variant)
+
+ expect(line_item.quantity).to eq(2)
+ end
+ end
+
+ context 'given a shipment' do
+ it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do
+ line_item = subject.add(variant, 1)
+ shipment = create(:shipment)
+ expect(subject.order).to_not receive(:ensure_updated_shipments)
+ expect(shipment).to receive(:update_amounts)
+ subject.remove(variant, 1, shipment: shipment)
+ end
+ end
+
+ context 'not given a shipment' do
+ it "ensures updated shipments" do
+ line_item = subject.add(variant, 1)
+ expect(subject.order).to receive(:ensure_updated_shipments)
+ subject.remove(variant)
+ end
+ end
+
+ it 'should reduce line_item quantity if quantity is less the line_item quantity' do
+ line_item = subject.add(variant, 3)
+ subject.remove(variant, 1)
+
+ expect(line_item.quantity).to eq(2)
+ end
+
+ it 'should remove line_item if quantity matches line_item quantity' do
+ subject.add(variant, 1)
+ removed_line_item = subject.remove(variant, 1)
+
+ # Should reflect the change already in Order#line_item
+ expect(order.line_items).to_not include(removed_line_item)
+ end
+
+ it "should update order totals" do
+ expect(order.item_total.to_f).to eq(0.00)
+ expect(order.total.to_f).to eq(0.00)
+
+ subject.add(variant,2)
+
+ expect(order.item_total.to_f).to eq(39.98)
+ expect(order.total.to_f).to eq(39.98)
+
+ subject.remove(variant,1)
+ expect(order.item_total.to_f).to eq(19.99)
+ expect(order.total.to_f).to eq(19.99)
+ end
+ end
+
+ context "update cart" do
+ let!(:shirt) { subject.add variant, 1 }
+
+ let(:params) do
+ { line_items_attributes: {
+ "0" => { id: shirt.id, quantity: 3 }
+ } }
+ end
+
+ it "changes item quantity" do
+ subject.update_cart params
+ expect(shirt.quantity).to eq 3
+ end
+
+ it "updates order totals" do
+ expect {
+ subject.update_cart params
+ }.to change { subject.order.total }
+ end
+
+ context "submits item quantity 0" do
+ let(:params) do
+ { line_items_attributes: {
+ "0" => { id: shirt.id, quantity: 0 },
+ "1" => { id: "666", quantity: 0}
+ } }
+ end
+
+ it "removes item from order" do
+ expect {
+ subject.update_cart params
+ }.to change { subject.order.line_items.count }
+ end
+
+ it "doesnt try to update unexistent items" do
+ filtered_params = { line_items_attributes: {
+ "0" => { id: shirt.id, quantity: 0 },
+ } }
+ expect(subject.order).to receive(:update_attributes).with(filtered_params)
+ subject.update_cart params
+ end
+
+ it "should not filter if there is only one line item" do
+ single_line_item_params = { line_items_attributes: { id: shirt.id, quantity: 0 } }
+ expect(subject.order).to receive(:update_attributes).with(single_line_item_params)
+ subject.update_cart single_line_item_params
+ end
+
+ end
+
+ it "ensures updated shipments" do
+ expect(subject.order).to receive(:ensure_updated_shipments)
+ subject.update_cart params
+ end
+ end
+
+ context "completed order" do
+ let(:order) { Spree::Order.create! state: 'complete', completed_at: Time.now }
+
+ before { order.shipments.create! stock_location_id: variant.stock_location_ids.first }
+
+ it "updates order payment state" do
+ expect {
+ subject.add variant
+ }.to change { order.payment_state }
+
+ expect {
+ subject.remove variant
+ }.to change { order.payment_state }
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_inventory_spec.rb b/core/spec/models/spree/order_inventory_spec.rb
new file mode 100644
index 00000000000..286d2d4ec47
--- /dev/null
+++ b/core/spec/models/spree/order_inventory_spec.rb
@@ -0,0 +1,228 @@
+require 'spec_helper'
+
+describe Spree::OrderInventory, :type => :model do
+ let(:order) { create :completed_order_with_totals }
+ let(:line_item) { order.line_items.first }
+
+ subject { described_class.new(order, line_item) }
+
+ context "when order is missing inventory units" do
+ before { line_item.update_column(:quantity, 2) }
+
+ it 'creates the proper number of inventory units' do
+ subject.verify
+ expect(subject.inventory_units.count).to eq 2
+ end
+ end
+
+ context "#add_to_shipment" do
+ let(:shipment) { order.shipments.first }
+
+ context "order is not completed" do
+ before { allow(order).to receive_messages completed?: false }
+
+ it "doesn't unstock items" do
+ expect(shipment.stock_location).not_to receive(:unstock)
+ expect(subject.send(:add_to_shipment, shipment, 5)).to eq(5)
+ end
+ end
+
+ context "inventory units state" do
+ before { shipment.inventory_units.destroy_all }
+
+ it 'sets inventory_units state as per stock location availability' do
+ expect(shipment.stock_location).to receive(:fill_status).with(subject.variant, 5).and_return([3, 2])
+
+ expect(subject.send(:add_to_shipment, shipment, 5)).to eq(5)
+
+ units = shipment.inventory_units_for(subject.variant).group_by(&:state)
+ expect(units['backordered'].size).to eq(2)
+ expect(units['on_hand'].size).to eq(3)
+ end
+ end
+
+ context "store doesnt track inventory" do
+ let(:variant) { create(:variant) }
+
+ before { Spree::Config.track_inventory_levels = false }
+
+ it "creates only on hand inventory units" do
+ variant.stock_items.destroy_all
+
+ # The before_save callback in LineItem would verify inventory
+ line_item = order.contents.add variant, 1, shipment: shipment
+
+ units = shipment.inventory_units_for(line_item.variant)
+ expect(units.count).to eq 1
+ expect(units.first).to be_on_hand
+ end
+ end
+
+ context "variant doesnt track inventory" do
+ let(:variant) { create(:variant) }
+ before { variant.track_inventory = false }
+
+ it "creates only on hand inventory units" do
+ variant.stock_items.destroy_all
+
+ line_item = order.contents.add variant, 1
+ subject.verify(shipment)
+
+ units = shipment.inventory_units_for(line_item.variant)
+ expect(units.count).to eq 1
+ expect(units.first).to be_on_hand
+ end
+ end
+
+ it 'should create stock_movement' do
+ expect(subject.send(:add_to_shipment, shipment, 5)).to eq(5)
+
+ stock_item = shipment.stock_location.stock_item(subject.variant)
+ movement = stock_item.stock_movements.last
+ # movement.originator.should == shipment
+ expect(movement.quantity).to eq(-5)
+ end
+ end
+
+ context "#determine_target_shipment" do
+ let(:stock_location) { create :stock_location }
+ let(:variant) { line_item.variant }
+
+ before do
+ subject.verify
+
+ order.shipments.create(:stock_location_id => stock_location.id, :cost => 5)
+
+ shipped = order.shipments.create(:stock_location_id => order.shipments.first.stock_location.id, :cost => 10)
+ shipped.update_column(:state, 'shipped')
+ end
+
+ it 'should select first non-shipped shipment that already contains given variant' do
+ shipment = subject.send(:determine_target_shipment)
+ expect(shipment.shipped?).to be false
+ expect(shipment.inventory_units_for(variant)).not_to be_empty
+
+ expect(variant.stock_location_ids.include?(shipment.stock_location_id)).to be true
+ end
+
+ context "when no shipments already contain this varint" do
+ before do
+ subject.line_item.reload
+ subject.inventory_units.destroy_all
+ end
+
+ it 'selects first non-shipped shipment that leaves from same stock_location' do
+ shipment = subject.send(:determine_target_shipment)
+ shipment.reload
+ expect(shipment.shipped?).to be false
+ expect(shipment.inventory_units_for(variant)).to be_empty
+ expect(variant.stock_location_ids.include?(shipment.stock_location_id)).to be true
+ end
+ end
+ end
+
+ context 'when order has too many inventory units' do
+ before do
+ line_item.quantity = 3
+ line_item.save!
+
+ line_item.update_column(:quantity, 2)
+ subject.line_item.reload
+ end
+
+ it 'should be a messed up order' do
+ expect(order.shipments.first.inventory_units_for(line_item.variant).size).to eq(3)
+ expect(line_item.quantity).to eq(2)
+ end
+
+ it 'should decrease the number of inventory units' do
+ subject.verify
+ expect(subject.inventory_units.count).to eq 2
+ end
+
+ context '#remove_from_shipment' do
+ let(:shipment) { order.shipments.first }
+ let(:variant) { subject.variant }
+
+ context "order is not completed" do
+ before { allow(order).to receive_messages completed?: false }
+
+ it "doesn't restock items" do
+ expect(shipment.stock_location).not_to receive(:restock)
+ expect(subject.send(:remove_from_shipment, shipment, 1)).to eq(1)
+ end
+ end
+
+ it 'should create stock_movement' do
+ expect(subject.send(:remove_from_shipment, shipment, 1)).to eq(1)
+
+ stock_item = shipment.stock_location.stock_item(variant)
+ movement = stock_item.stock_movements.last
+ # movement.originator.should == shipment
+ expect(movement.quantity).to eq(1)
+ end
+
+ it 'should destroy backordered units first' do
+ allow(shipment).to receive_messages(inventory_units_for_item: [
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'backordered'),
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'on_hand'),
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'backordered')
+ ])
+
+ expect(shipment.inventory_units_for_item[0]).to receive(:destroy)
+ expect(shipment.inventory_units_for_item[1]).not_to receive(:destroy)
+ expect(shipment.inventory_units_for_item[2]).to receive(:destroy)
+
+ expect(subject.send(:remove_from_shipment, shipment, 2)).to eq(2)
+ end
+
+ it 'should destroy unshipped units first' do
+ allow(shipment).to receive_messages(inventory_units_for_item: [
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'shipped'),
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'on_hand')
+ ])
+
+ expect(shipment.inventory_units_for_item[0]).not_to receive(:destroy)
+ expect(shipment.inventory_units_for_item[1]).to receive(:destroy)
+
+ expect(subject.send(:remove_from_shipment, shipment, 1)).to eq(1)
+ end
+
+ it 'only attempts to destroy as many units as are eligible, and return amount destroyed' do
+ allow(shipment).to receive_messages(inventory_units_for_item: [
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'shipped'),
+ mock_model(Spree::InventoryUnit, :variant_id => variant.id, :state => 'on_hand')
+ ])
+
+ expect(shipment.inventory_units_for_item[0]).not_to receive(:destroy)
+ expect(shipment.inventory_units_for_item[1]).to receive(:destroy)
+
+ expect(subject.send(:remove_from_shipment, shipment, 1)).to eq(1)
+ end
+
+ it 'should destroy self if not inventory units remain' do
+ allow(shipment.inventory_units).to receive_messages(:count => 0)
+ expect(shipment).to receive(:destroy)
+
+ expect(subject.send(:remove_from_shipment, shipment, 1)).to eq(1)
+ end
+
+ context "inventory unit line item and variant points to different products" do
+ let(:different_line_item) { create(:line_item) }
+
+ let!(:different_inventory) do
+ shipment.set_up_inventory("on_hand", variant, order, different_line_item)
+ end
+
+ context "completed order" do
+ before { order.touch :completed_at }
+
+ it "removes only units that match both line item and variant" do
+ subject.send(:remove_from_shipment, shipment, shipment.inventory_units.count)
+ expect(different_inventory.reload).to be_persisted
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_merger_spec.rb b/core/spec/models/spree/order_merger_spec.rb
new file mode 100644
index 00000000000..5226192c63b
--- /dev/null
+++ b/core/spec/models/spree/order_merger_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+# Regression tests for #2179
+module Spree
+ describe OrderMerger, type: :model do
+ let(:variant) { create(:variant) }
+ let(:order_1) { Spree::Order.create }
+ let(:order_2) { Spree::Order.create }
+ let(:user) { stub_model(Spree::LegacyUser, email: "spree@example.com") }
+ let(:subject) { Spree::OrderMerger.new(order_1) }
+
+ it "destroys the other order" do
+ subject.merge!(order_2)
+ expect { order_2.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it "persist the merge" do
+ expect(subject).to receive(:persist_merge)
+ subject.merge!(order_2)
+ end
+
+ context "user is provided" do
+ it "assigns user to new order" do
+ subject.merge!(order_2, user)
+ expect(order_1.user).to eq user
+ end
+ end
+
+ context "merging together two orders with line items for the same variant" do
+ before do
+ order_1.contents.add(variant, 1)
+ order_2.contents.add(variant, 1)
+ end
+
+ specify do
+ subject.merge!(order_2, user)
+ expect(order_1.line_items.count).to eq(1)
+
+ line_item = order_1.line_items.first
+ expect(line_item.quantity).to eq(2)
+ expect(line_item.variant_id).to eq(variant.id)
+ end
+ end
+
+ context "merging using extension-specific line_item_comparison_hooks" do
+ before do
+ Spree::Order.register_line_item_comparison_hook(:foos_match)
+ allow(Spree::Variant).to receive(:price_modifier_amount).and_return(0.00)
+ end
+
+ after do
+ # reset to avoid test pollution
+ Spree::Order.line_item_comparison_hooks = Set.new
+ end
+
+ context "2 equal line items" do
+ before do
+ @line_item_1 = order_1.contents.add(variant, 1, foos: {})
+ @line_item_2 = order_2.contents.add(variant, 1, foos: {})
+ end
+
+ specify do
+ expect(order_1).to receive(:foos_match).with(@line_item_1, kind_of(Hash)).and_return(true)
+ subject.merge!(order_2)
+ expect(order_1.line_items.count).to eq(1)
+
+ line_item = order_1.line_items.first
+ expect(line_item.quantity).to eq(2)
+ expect(line_item.variant_id).to eq(variant.id)
+ end
+ end
+
+ context "2 different line items" do
+ before do
+ allow(order_1).to receive(:foos_match).and_return(false)
+
+ order_1.contents.add(variant, 1, foos: {})
+ order_2.contents.add(variant, 1, foos: { bar: :zoo })
+ end
+
+ specify do
+ subject.merge!(order_2)
+ expect(order_1.line_items.count).to eq(2)
+
+ line_item = order_1.line_items.first
+ expect(line_item.quantity).to eq(1)
+ expect(line_item.variant_id).to eq(variant.id)
+
+ line_item = order_1.line_items.last
+ expect(line_item.quantity).to eq(1)
+ expect(line_item.variant_id).to eq(variant.id)
+ end
+ end
+ end
+
+ context "merging together two orders with different line items" do
+ let(:variant_2) { create(:variant) }
+
+ before do
+ order_1.contents.add(variant, 1)
+ order_2.contents.add(variant_2, 1)
+ end
+
+ specify do
+ subject.merge!(order_2)
+ line_items = order_1.line_items.reload
+ expect(line_items.count).to eq(2)
+
+ expect(order_1.item_count).to eq 2
+ expect(order_1.item_total).to eq line_items.map(&:amount).sum
+
+ # No guarantee on ordering of line items, so we do this:
+ expect(line_items.pluck(:quantity)).to match_array([1, 1])
+ expect(line_items.pluck(:variant_id)).to match_array([variant.id, variant_2.id])
+ end
+ end
+
+ context "merging together orders with invalid line items" do
+ let(:variant_2) { create(:variant) }
+
+ before do
+ order_1.contents.add(variant, 1)
+ order_2.contents.add(variant_2, 1)
+ end
+
+ it "should create errors with invalid line items" do
+ variant_2.destroy
+ subject.merge!(order_2)
+ expect(order_1.errors.full_messages).not_to be_empty
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_populator_spec.rb b/core/spec/models/spree/order_populator_spec.rb
new file mode 100644
index 00000000000..dab0d91b76f
--- /dev/null
+++ b/core/spec/models/spree/order_populator_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Spree::OrderPopulator, :type => :model do
+ let(:order) { double('Order') }
+ subject { Spree::OrderPopulator.new(order, "USD") }
+
+ context "with stubbed out find_variant" do
+ let(:variant) { double('Variant', name: "T-Shirt", options_text: "Size: M") }
+
+ before do
+ allow(Spree::Variant).to receive(:find).and_return(variant)
+ expect(order).to receive(:contents).at_least(:once).and_return(Spree::OrderContents.new(self))
+ end
+
+ context "can populate an order" do
+ it "can take a list of variants with quantites and add them to the order" do
+ expect(order.contents).to receive(:add).with(variant, 5, currency: subject.currency).and_return(double.as_null_object)
+ subject.populate(2, 5)
+ end
+ end
+
+ context 'with an invalid variant' do
+ let(:line_item) { build(:line_item) }
+
+ before do
+ allow(order.contents).to receive(:add).and_raise(ActiveRecord::RecordInvalid, line_item)
+ end
+
+ it 'has some errors' do
+ subject.populate(2, 5)
+ expect(subject.errors).to_not be_empty
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb
new file mode 100644
index 00000000000..261642605bc
--- /dev/null
+++ b/core/spec/models/spree/order_spec.rb
@@ -0,0 +1,842 @@
+require 'spec_helper'
+
+class FakeCalculator < Spree::Calculator
+ def compute(computable)
+ 5
+ end
+end
+
+describe Spree::Order, :type => :model do
+ let(:user) { stub_model(Spree::LegacyUser, :email => "spree@example.com") }
+ let(:order) { stub_model(Spree::Order, :user => user) }
+
+ before do
+ allow(Spree::LegacyUser).to receive_messages(:current => mock_model(Spree::LegacyUser, :id => 123))
+ end
+
+ context "#cancel" do
+ let(:order) { create(:completed_order_with_totals) }
+ let!(:payment) do
+ create(
+ :payment,
+ order: order,
+ amount: order.total,
+ state: "completed"
+ )
+ end
+ let(:payment_method) { double }
+
+ it "should mark the payments as void" do
+ allow_any_instance_of(Spree::Shipment).to receive(:refresh_rates).and_return(true)
+ order.cancel
+ order.reload
+
+ expect(order.payments.first).to be_void
+ end
+ end
+
+ context "#canceled_by" do
+ let(:admin_user) { create :admin_user }
+ let(:order) { create :order }
+
+ before do
+ allow(order).to receive(:cancel!)
+ end
+
+ subject { order.canceled_by(admin_user) }
+
+ it 'should cancel the order' do
+ expect(order).to receive(:cancel!)
+ subject
+ end
+
+ it 'should save canceler_id' do
+ subject
+ expect(order.reload.canceler_id).to eq(admin_user.id)
+ end
+
+ it 'should save canceled_at' do
+ subject
+ expect(order.reload.canceled_at).to_not be_nil
+ end
+
+ it 'should have canceler' do
+ subject
+ expect(order.reload.canceler).to eq(admin_user)
+ end
+ end
+
+ context "#create" do
+ let(:order) { Spree::Order.create }
+
+ it "should assign an order number" do
+ expect(order.number).not_to be_nil
+ end
+
+ it 'should create a randomized 22 character token' do
+ expect(order.guest_token.size).to eq(22)
+ end
+ end
+
+ context "creates shipments cost" do
+ let(:shipment) { double }
+
+ before { allow(order).to receive_messages shipments: [shipment] }
+
+ it "update and persist totals" do
+ expect(shipment).to receive :update_amounts
+ expect(order.updater).to receive :update_shipment_total
+ expect(order.updater).to receive :persist_totals
+
+ order.set_shipments_cost
+ end
+ end
+
+ context "#finalize!" do
+ let(:order) { Spree::Order.create(email: 'test@example.com') }
+
+ before do
+ order.update_column :state, 'complete'
+ end
+
+ it "should set completed_at" do
+ expect(order).to receive(:touch).with(:completed_at)
+ order.finalize!
+ end
+
+ it "should sell inventory units" do
+ order.shipments.each do |shipment|
+ expect(shipment).to receive(:update!)
+ expect(shipment).to receive(:finalize!)
+ end
+ order.finalize!
+ end
+
+ it "should decrease the stock for each variant in the shipment" do
+ order.shipments.each do |shipment|
+ expect(shipment.stock_location).to receive(:decrease_stock_for_variant)
+ end
+ order.finalize!
+ end
+
+ it "should change the shipment state to ready if order is paid" do
+ Spree::Shipment.create(order: order, stock_location: create(:stock_location))
+ order.shipments.reload
+
+ allow(order).to receive_messages(paid?: true, complete?: true)
+ order.finalize!
+ order.reload # reload so we're sure the changes are persisted
+ expect(order.shipment_state).to eq('ready')
+ end
+
+ after { Spree::Config.set track_inventory_levels: true }
+ it "should not sell inventory units if track_inventory_levels is false" do
+ Spree::Config.set track_inventory_levels: false
+ expect(Spree::InventoryUnit).not_to receive(:sell_units)
+ order.finalize!
+ end
+
+ it "should send an order confirmation email" do
+ mail_message = double "Mail::Message"
+ expect(Spree::OrderMailer).to receive(:confirm_email).with(order.id).and_return mail_message
+ expect(mail_message).to receive :deliver
+ order.finalize!
+ end
+
+ it "sets confirmation delivered when finalizing" do
+ expect(order.confirmation_delivered?).to be false
+ order.finalize!
+ expect(order.confirmation_delivered?).to be true
+ end
+
+ it "should not send duplicate confirmation emails" do
+ allow(order).to receive_messages(:confirmation_delivered? => true)
+ expect(Spree::OrderMailer).not_to receive(:confirm_email)
+ order.finalize!
+ end
+
+ it "should freeze all adjustments" do
+ # Stub this method as it's called due to a callback
+ # and it's irrelevant to this test
+ allow(order).to receive :has_available_shipment
+ allow(Spree::OrderMailer).to receive_message_chain :confirm_email, :deliver
+ adjustments = [double]
+ expect(order).to receive(:all_adjustments).and_return(adjustments)
+ adjustments.each do |adj|
+ expect(adj).to receive(:close)
+ end
+ order.finalize!
+ end
+
+ context "order is considered risky" do
+ before do
+ allow(order).to receive_messages :is_risky? => true
+ end
+
+ it "should change state to risky" do
+ expect(order).to receive(:considered_risky!)
+ order.finalize!
+ end
+
+ context "and order is approved" do
+ before do
+ allow(order).to receive_messages :approved? => true
+ end
+
+ it "should leave order in complete state" do
+ order.finalize!
+ expect(order.state).to eq 'complete'
+ end
+ end
+ end
+ end
+
+ context "insufficient_stock_lines" do
+ let(:line_item) { mock_model Spree::LineItem, :insufficient_stock? => true }
+
+ before { allow(order).to receive_messages(:line_items => [line_item]) }
+
+ it "should return line_item that has insufficient stock on hand" do
+ expect(order.insufficient_stock_lines.size).to eq(1)
+ expect(order.insufficient_stock_lines.include?(line_item)).to be true
+ end
+ end
+
+ describe '#ensure_line_item_variants_are_not_deleted' do
+ subject { order.ensure_line_item_variants_are_not_deleted }
+
+ let(:order) { create :order_with_line_items }
+
+ context 'when variant is destroyed' do
+ before do
+ allow(order).to receive(:restart_checkout_flow)
+ order.line_items.first.variant.destroy
+ end
+
+ it 'should restart checkout flow' do
+ expect(order).to receive(:restart_checkout_flow).once
+ subject
+ end
+
+ it 'should have error message' do
+ subject
+ expect(order.errors[:base]).to include(Spree.t(:deleted_variants_present))
+ end
+
+ it 'should be false' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when no variants are destroyed' do
+ it 'should not restart checkout' do
+ expect(order).to receive(:restart_checkout_flow).never
+ subject
+ end
+
+ it 'should be true' do
+ expect(subject).to be_truthy
+ end
+ end
+ end
+
+ describe '#ensure_line_items_are_in_stock' do
+ subject { order.ensure_line_items_are_in_stock }
+
+ let(:line_item) { mock_model Spree::LineItem, :insufficient_stock? => true }
+
+ before do
+ allow(order).to receive(:restart_checkout_flow)
+ allow(order).to receive_messages(:line_items => [line_item])
+ end
+
+ it 'should restart checkout flow' do
+ expect(order).to receive(:restart_checkout_flow).once
+ subject
+ end
+
+ it 'should have error message' do
+ subject
+ expect(order.errors[:base]).to include(Spree.t(:insufficient_stock_lines_present))
+ end
+
+ it 'should be false' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context "empty!" do
+ let(:order) { stub_model(Spree::Order, item_count: 2) }
+
+ before do
+ allow(order).to receive_messages(:line_items => line_items = [1, 2])
+ allow(order).to receive_messages(:adjustments => adjustments = [])
+ end
+
+ it "clears out line items, adjustments and update totals" do
+ expect(order.line_items).to receive(:destroy_all)
+ expect(order.adjustments).to receive(:destroy_all)
+ expect(order.shipments).to receive(:destroy_all)
+ expect(order.updater).to receive(:update_totals)
+ expect(order.updater).to receive(:persist_totals)
+
+ order.empty!
+ expect(order.item_total).to eq 0
+ end
+ end
+
+ context "#display_outstanding_balance" do
+ it "returns the value as a spree money" do
+ allow(order).to receive(:outstanding_balance) { 10.55 }
+ expect(order.display_outstanding_balance).to eq(Spree::Money.new(10.55))
+ end
+ end
+
+ context "#display_item_total" do
+ it "returns the value as a spree money" do
+ allow(order).to receive(:item_total) { 10.55 }
+ expect(order.display_item_total).to eq(Spree::Money.new(10.55))
+ end
+ end
+
+ context "#display_adjustment_total" do
+ it "returns the value as a spree money" do
+ order.adjustment_total = 10.55
+ expect(order.display_adjustment_total).to eq(Spree::Money.new(10.55))
+ end
+ end
+
+ context "#display_total" do
+ it "returns the value as a spree money" do
+ order.total = 10.55
+ expect(order.display_total).to eq(Spree::Money.new(10.55))
+ end
+ end
+
+ context "#currency" do
+ context "when object currency is ABC" do
+ before { order.currency = "ABC" }
+
+ it "returns the currency from the object" do
+ expect(order.currency).to eq("ABC")
+ end
+ end
+
+ context "when object currency is nil" do
+ before { order.currency = nil }
+
+ it "returns the globally configured currency" do
+ expect(order.currency).to eq("USD")
+ end
+ end
+ end
+
+ context "#confirmation_required?" do
+
+ # Regression test for #4117
+ it "is required if the state is currently 'confirm'" do
+ order = Spree::Order.new
+ assert !order.confirmation_required?
+ order.state = 'confirm'
+ assert order.confirmation_required?
+ end
+
+ context 'Spree::Config[:always_include_confirm_step] == true' do
+
+ before do
+ Spree::Config[:always_include_confirm_step] = true
+ end
+
+ it "returns true if payments empty" do
+ order = Spree::Order.new
+ assert order.confirmation_required?
+ end
+ end
+
+ context 'Spree::Config[:always_include_confirm_step] == false' do
+
+ it "returns false if payments empty and Spree::Config[:always_include_confirm_step] == false" do
+ order = Spree::Order.new
+ assert !order.confirmation_required?
+ end
+
+ it "does not bomb out when an order has an unpersisted payment" do
+ order = Spree::Order.new
+ order.payments.build
+ assert !order.confirmation_required?
+ end
+ end
+ end
+
+
+ context "add_update_hook" do
+ before do
+ Spree::Order.class_eval do
+ register_update_hook :add_awesome_sauce
+ end
+ end
+
+ after do
+ Spree::Order.update_hooks = Set.new
+ end
+
+ it "calls hook during update" do
+ order = create(:order)
+ expect(order).to receive(:add_awesome_sauce)
+ order.update!
+ end
+
+ it "calls hook during finalize" do
+ order = create(:order)
+ expect(order).to receive(:add_awesome_sauce)
+ order.finalize!
+ end
+ end
+
+ describe "#tax_address" do
+ before { Spree::Config[:tax_using_ship_address] = tax_using_ship_address }
+ subject { order.tax_address }
+
+ context "when tax_using_ship_address is true" do
+ let(:tax_using_ship_address) { true }
+
+ it 'returns ship_address' do
+ expect(subject).to eq(order.ship_address)
+ end
+ end
+
+ context "when tax_using_ship_address is not true" do
+ let(:tax_using_ship_address) { false }
+
+ it "returns bill_address" do
+ expect(subject).to eq(order.bill_address)
+ end
+ end
+ end
+
+ describe "#restart_checkout_flow" do
+ it "updates the state column to the first checkout_steps value" do
+ order = create(:order_with_totals, state: "delivery")
+ expect(order.checkout_steps).to eql ["address", "delivery", "complete"]
+ expect{ order.restart_checkout_flow }.to change{order.state}.from("delivery").to("address")
+ end
+
+ context "without line items" do
+ it "updates the state column to cart" do
+ order = create(:order, state: "delivery")
+ expect{ order.restart_checkout_flow }.to change{order.state}.from("delivery").to("cart")
+ end
+ end
+ end
+
+ # Regression tests for #4072
+ context "#state_changed" do
+ let(:order) { FactoryGirl.create(:order) }
+
+ it "logs state changes" do
+ order.update_column(:payment_state, 'balance_due')
+ order.payment_state = 'paid'
+ expect(order.state_changes).to be_empty
+ order.state_changed('payment')
+ state_change = order.state_changes.find_by(:name => 'payment')
+ expect(state_change.previous_state).to eq('balance_due')
+ expect(state_change.next_state).to eq('paid')
+ end
+
+ it "does not do anything if state does not change" do
+ order.update_column(:payment_state, 'balance_due')
+ expect(order.state_changes).to be_empty
+ order.state_changed('payment')
+ expect(order.state_changes).to be_empty
+ end
+ end
+
+ # Regression test for #4199
+ context "#available_payment_methods" do
+ it "includes frontend payment methods" do
+ payment_method = Spree::PaymentMethod.create!({
+ :name => "Fake",
+ :active => true,
+ :display_on => "front_end",
+ :environment => Rails.env
+ })
+ expect(order.available_payment_methods).to include(payment_method)
+ end
+
+ it "includes 'both' payment methods" do
+ payment_method = Spree::PaymentMethod.create!({
+ :name => "Fake",
+ :active => true,
+ :display_on => "both",
+ :environment => Rails.env
+ })
+ expect(order.available_payment_methods).to include(payment_method)
+ end
+
+ it "does not include a payment method twice if display_on is blank" do
+ payment_method = Spree::PaymentMethod.create!({
+ :name => "Fake",
+ :active => true,
+ :display_on => "both",
+ :environment => Rails.env
+ })
+ expect(order.available_payment_methods.count).to eq(1)
+ expect(order.available_payment_methods).to include(payment_method)
+ end
+ end
+
+ context "#apply_free_shipping_promotions" do
+ it "calls out to the FreeShipping promotion handler" do
+ shipment = double('Shipment')
+ allow(order).to receive_messages :shipments => [shipment]
+ expect(Spree::PromotionHandler::FreeShipping).to receive(:new).and_return(handler = double)
+ expect(handler).to receive(:activate)
+
+ expect(Spree::ItemAdjustments).to receive(:new).with(shipment).and_return(adjuster = double)
+ expect(adjuster).to receive(:update)
+
+ expect(order.updater).to receive(:update_shipment_total)
+ expect(order.updater).to receive(:persist_totals)
+ order.apply_free_shipping_promotions
+ end
+ end
+
+
+ context "#products" do
+ before :each do
+ @variant1 = mock_model(Spree::Variant, :product => "product1")
+ @variant2 = mock_model(Spree::Variant, :product => "product2")
+ @line_items = [mock_model(Spree::LineItem, :product => "product1", :variant => @variant1, :variant_id => @variant1.id, :quantity => 1),
+ mock_model(Spree::LineItem, :product => "product2", :variant => @variant2, :variant_id => @variant2.id, :quantity => 2)]
+ allow(order).to receive_messages(:line_items => @line_items)
+ end
+
+ it "contains?" do
+ expect(order.contains?(@variant1)).to be true
+ end
+
+ it "gets the quantity of a given variant" do
+ expect(order.quantity_of(@variant1)).to eq(1)
+
+ @variant3 = mock_model(Spree::Variant, :product => "product3")
+ expect(order.quantity_of(@variant3)).to eq(0)
+ end
+
+ it "can find a line item matching a given variant" do
+ expect(order.find_line_item_by_variant(@variant1)).not_to be_nil
+ expect(order.find_line_item_by_variant(mock_model(Spree::Variant))).to be_nil
+ end
+
+ context "match line item with options" do
+ before do
+ Spree::Order.register_line_item_comparison_hook(:foos_match)
+ end
+
+ after do
+ # reset to avoid test pollution
+ Spree::Order.line_item_comparison_hooks = Set.new
+ end
+
+ it "matches line item when options match" do
+ allow(order).to receive(:foos_match).and_return(true)
+ expect(order.line_item_options_match(@line_items.first, {foos: {bar: :zoo}})).to be true
+ end
+
+ it "does not match line item without options" do
+ allow(order).to receive(:foos_match).and_return(false)
+ expect(order.line_item_options_match(@line_items.first, {})).to be false
+ end
+ end
+ end
+
+ context "#generate_order_number" do
+ context "when no configure" do
+ let(:default_length) { Spree::Order::ORDER_NUMBER_LENGTH + Spree::Order::ORDER_NUMBER_PREFIX.length }
+ subject(:order_number) { order.generate_order_number }
+
+ describe '#class' do
+ subject { super().class }
+ it { is_expected.to eq String }
+ end
+
+ describe '#length' do
+ subject { super().length }
+ it { is_expected.to eq default_length }
+ end
+ it { is_expected.to match /^#{Spree::Order::ORDER_NUMBER_PREFIX}/ }
+ end
+
+ context "when length option is 5" do
+ let(:option_length) { 5 + Spree::Order::ORDER_NUMBER_PREFIX.length }
+ it "should be option length for order number" do
+ expect(order.generate_order_number(length: 5).length).to eq option_length
+ end
+ end
+
+ context "when letters option is true" do
+ it "generates order number include letter" do
+ expect(order.generate_order_number(length: 100, letters: true)).to match /[A-Z]/
+ end
+ end
+
+ context "when prefix option is 'P'" do
+ it "generates order number and it prefix is 'P'" do
+ expect(order.generate_order_number(prefix: 'P')).to match /^P/
+ end
+ end
+ end
+
+ context "#associate_user!" do
+ let!(:user) { FactoryGirl.create(:user) }
+
+ it "should associate a user with a persisted order" do
+ order = FactoryGirl.create(:order_with_line_items, created_by: nil)
+ order.user = nil
+ order.email = nil
+ order.associate_user!(user)
+ expect(order.user).to eq(user)
+ expect(order.email).to eq(user.email)
+ expect(order.created_by).to eq(user)
+
+ # verify that the changes we made were persisted
+ order.reload
+ expect(order.user).to eq(user)
+ expect(order.email).to eq(user.email)
+ expect(order.created_by).to eq(user)
+ end
+
+ it "should not overwrite the created_by if it already is set" do
+ creator = create(:user)
+ order = FactoryGirl.create(:order_with_line_items, created_by: creator)
+
+ order.user = nil
+ order.email = nil
+ order.associate_user!(user)
+ expect(order.user).to eq(user)
+ expect(order.email).to eq(user.email)
+ expect(order.created_by).to eq(creator)
+
+ # verify that the changes we made were persisted
+ order.reload
+ expect(order.user).to eq(user)
+ expect(order.email).to eq(user.email)
+ expect(order.created_by).to eq(creator)
+ end
+
+ it "should associate a user with a non-persisted order" do
+ order = Spree::Order.new
+
+ expect do
+ order.associate_user!(user)
+ end.to change { [order.user, order.email] }.from([nil, nil]).to([user, user.email])
+ end
+
+ it "should not persist an invalid address" do
+ address = Spree::Address.new
+ order.user = nil
+ order.email = nil
+ order.ship_address = address
+ expect do
+ order.associate_user!(user)
+ end.not_to change { address.persisted? }.from(false)
+ end
+ end
+
+ context "#can_ship?" do
+ let(:order) { Spree::Order.create }
+
+ it "should be true for order in the 'complete' state" do
+ allow(order).to receive_messages(:complete? => true)
+ expect(order.can_ship?).to be true
+ end
+
+ it "should be true for order in the 'resumed' state" do
+ allow(order).to receive_messages(:resumed? => true)
+ expect(order.can_ship?).to be true
+ end
+
+ it "should be true for an order in the 'awaiting return' state" do
+ allow(order).to receive_messages(:awaiting_return? => true)
+ expect(order.can_ship?).to be true
+ end
+
+ it "should be true for an order in the 'returned' state" do
+ allow(order).to receive_messages(:returned? => true)
+ expect(order.can_ship?).to be true
+ end
+
+ it "should be false if the order is neither in the 'complete' nor 'resumed' state" do
+ allow(order).to receive_messages(:resumed? => false, :complete? => false)
+ expect(order.can_ship?).to be false
+ end
+ end
+
+ context "#completed?" do
+ it "should indicate if order is completed" do
+ order.completed_at = nil
+ expect(order.completed?).to be false
+
+ order.completed_at = Time.now
+ expect(order.completed?).to be true
+ end
+ end
+
+ context "#allow_checkout?" do
+ it "should be true if there are line_items in the order" do
+ allow(order).to receive_message_chain(:line_items, :count => 1)
+ expect(order.checkout_allowed?).to be true
+ end
+ it "should be false if there are no line_items in the order" do
+ allow(order).to receive_message_chain(:line_items, :count => 0)
+ expect(order.checkout_allowed?).to be false
+ end
+ end
+
+ context "#amount" do
+ before do
+ @order = create(:order, :user => user)
+ @order.line_items = [create(:line_item, :price => 1.0, :quantity => 2),
+ create(:line_item, :price => 1.0, :quantity => 1)]
+ end
+ it "should return the correct lum sum of items" do
+ expect(@order.amount).to eq(3.0)
+ end
+ end
+
+ context "#backordered?" do
+ it 'is backordered if one of the shipments is backordered' do
+ allow(order).to receive_messages(:shipments => [mock_model(Spree::Shipment, :backordered? => false),
+ mock_model(Spree::Shipment, :backordered? => true)])
+ expect(order).to be_backordered
+ end
+ end
+
+ context "#can_cancel?" do
+ it "should be false for completed order in the canceled state" do
+ order.state = 'canceled'
+ order.shipment_state = 'ready'
+ order.completed_at = Time.now
+ expect(order.can_cancel?).to be false
+ end
+
+ it "should be true for completed order with no shipment" do
+ order.state = 'complete'
+ order.shipment_state = nil
+ order.completed_at = Time.now
+ expect(order.can_cancel?).to be true
+ end
+ end
+
+ context "#tax_total" do
+ it "adds included tax and additional tax" do
+ allow(order).to receive_messages(:additional_tax_total => 10, :included_tax_total => 20)
+
+ expect(order.tax_total).to eq 30
+ end
+ end
+
+ # Regression test for #4923
+ context "locking" do
+ let(:order) { Spree::Order.create } # need a persisted in order to test locking
+
+ it 'can lock' do
+ expect { order.with_lock {} }.to_not raise_error
+ end
+ end
+
+ describe "#pre_tax_item_amount" do
+ it "sums all of the line items' pre tax amounts" do
+ subject.line_items = [
+ Spree::LineItem.new(price: 10, quantity: 2, pre_tax_amount: 5.0),
+ Spree::LineItem.new(price: 30, quantity: 1, pre_tax_amount: 14.0),
+ ]
+
+ expect(subject.pre_tax_item_amount).to eq 19.0
+ end
+ end
+
+ describe '#quantity' do
+ # Uses a persisted record, as the quantity is retrieved via a DB count
+ let(:order) { create :order_with_line_items, line_items_count: 3 }
+
+ it 'sums the quantity of all line items' do
+ expect(order.quantity).to eq 3
+ end
+ end
+
+ describe '#has_non_reimbursement_related_refunds?' do
+ subject do
+ order.has_non_reimbursement_related_refunds?
+ end
+
+ context 'no refunds exist' do
+ it { is_expected.to eq false }
+ end
+
+ context 'a non-reimbursement related refund exists' do
+ let(:order) { refund.payment.order }
+ let(:refund) { create(:refund, reimbursement_id: nil, amount: 5) }
+
+ it { is_expected.to eq true }
+ end
+
+ context 'an old-style refund exists' do
+ let(:order) { create(:order_ready_to_ship) }
+ let(:payment) { order.payments.first.tap { |p| allow(p).to receive_messages(profiles_supported: false) } }
+ let!(:refund_payment) {
+ build(:payment, amount: -1, order: order, state: 'completed', source: payment).tap do |p|
+ allow(p).to receive_messages(profiles_supported?: false)
+ p.save!
+ end
+ }
+
+ it { is_expected.to eq true }
+ end
+
+ context 'a reimbursement related refund exists' do
+ let(:order) { refund.payment.order }
+ let(:refund) { create(:refund, reimbursement_id: 123, amount: 5)}
+
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe "#create_proposed_shipments" do
+ it "assigns the coordinator returned shipments to its shipments" do
+ shipment = build(:shipment)
+ allow_any_instance_of(Spree::Stock::Coordinator).to receive(:shipments).and_return([shipment])
+ subject.create_proposed_shipments
+ expect(subject.shipments).to eq [shipment]
+ end
+ end
+
+ describe "#all_inventory_units_returned?" do
+ let(:order) { create(:order_with_line_items, line_items_count: 3) }
+
+ subject { order.all_inventory_units_returned? }
+
+ context "all inventory units are returned" do
+ before { order.inventory_units.update_all(state: 'returned') }
+
+ it "is true" do
+ expect(subject).to eq true
+ end
+ end
+
+ context "some inventory units are returned" do
+ before do
+ order.inventory_units.first.update_attribute(:state, 'returned')
+ end
+
+ it "is false" do
+ expect(subject).to eq false
+ end
+ end
+
+ context "no inventory units are returned" do
+ it "is false" do
+ expect(subject).to eq false
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/order_updater_spec.rb b/core/spec/models/spree/order_updater_spec.rb
new file mode 100644
index 00000000000..e59a1f54d04
--- /dev/null
+++ b/core/spec/models/spree/order_updater_spec.rb
@@ -0,0 +1,283 @@
+require 'spec_helper'
+
+module Spree
+ describe OrderUpdater, type: :model do
+ let(:order) { Spree::Order.create }
+ let(:updater) { Spree::OrderUpdater.new(order) }
+
+ context "order totals" do
+ before do
+ 2.times do
+ create(:line_item, order: order, price: 10)
+ end
+ end
+
+ it "updates payment totals" do
+ create(:payment_with_refund, order: order)
+ Spree::OrderUpdater.new(order).update_payment_total
+ expect(order.payment_total).to eq(40.75)
+ end
+
+ it "update item total" do
+ updater.update_item_total
+ expect(order.item_total).to eq(20)
+ end
+
+ it "update shipment total" do
+ create(:shipment, order: order, cost: 10)
+ updater.update_shipment_total
+ expect(order.shipment_total).to eq(10)
+ end
+
+ context 'with order promotion followed by line item addition' do
+ let(:promotion) { Spree::Promotion.create!(name: "10% off") }
+ let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+
+ let(:promotion_action) do
+ Promotion::Actions::CreateAdjustment.create!({
+ calculator: calculator,
+ promotion: promotion,
+ })
+ end
+
+ before do
+ updater.update
+ create(:adjustment, source: promotion_action, adjustable: order, order: order)
+ create(:line_item, order: order, price: 10) # in addition to the two already created
+ updater.update
+ end
+
+ it "updates promotion total" do
+ expect(order.promo_total).to eq(-3)
+ end
+ end
+
+ it "update order adjustments" do
+ # A line item will not have both additional and included tax,
+ # so please just humour me for now.
+ order.line_items.first.update_columns({
+ adjustment_total: 10.05,
+ additional_tax_total: 0.05,
+ included_tax_total: 0.05,
+ })
+ updater.update_adjustment_total
+ expect(order.adjustment_total).to eq(10.05)
+ expect(order.additional_tax_total).to eq(0.05)
+ expect(order.included_tax_total).to eq(0.05)
+ end
+ end
+
+ context "updating shipment state" do
+ before do
+ allow(order).to receive_messages backordered?: false
+ allow(order).to receive_message_chain(:shipments, :shipped, :count).and_return(0)
+ allow(order).to receive_message_chain(:shipments, :ready, :count).and_return(0)
+ allow(order).to receive_message_chain(:shipments, :pending, :count).and_return(0)
+ end
+
+ it "is backordered" do
+ allow(order).to receive_messages backordered?: true
+ updater.update_shipment_state
+
+ expect(order.shipment_state).to eq('backorder')
+ end
+
+ it "is nil" do
+ allow(order).to receive_message_chain(:shipments, :states).and_return([])
+ allow(order).to receive_message_chain(:shipments, :count).and_return(0)
+
+ updater.update_shipment_state
+ expect(order.shipment_state).to be_nil
+ end
+
+
+ ["shipped", "ready", "pending"].each do |state|
+ it "is #{state}" do
+ allow(order).to receive_message_chain(:shipments, :states).and_return([state])
+ updater.update_shipment_state
+ expect(order.shipment_state).to eq(state.to_s)
+ end
+ end
+
+ it "is partial" do
+ allow(order).to receive_message_chain(:shipments, :states).and_return(["pending", "ready"])
+ updater.update_shipment_state
+ expect(order.shipment_state).to eq('partial')
+ end
+ end
+
+ context "updating payment state" do
+ let(:order) { Order.new }
+ let(:updater) { order.updater }
+
+ it "is failed if no valid payments" do
+ allow(order).to receive_message_chain(:payments, :valid, :size).and_return(0)
+
+ updater.update_payment_state
+ expect(order.payment_state).to eq('failed')
+ end
+
+ context "payment total is greater than order total" do
+ it "is credit_owed" do
+ order.payment_total = 2
+ order.total = 1
+
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'credit_owed'
+ end
+ end
+
+ context "order total is greater than payment total" do
+ it "is balance_due" do
+ order.payment_total = 1
+ order.total = 2
+
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'balance_due'
+ end
+ end
+
+ context "order total equals payment total" do
+ it "is paid" do
+ order.payment_total = 30
+ order.total = 30
+
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'paid'
+ end
+ end
+
+ context "order is canceled" do
+
+ before do
+ order.state = 'canceled'
+ end
+
+ context "and is still unpaid" do
+ it "is void" do
+ order.payment_total = 0
+ order.total = 30
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'void'
+ end
+ end
+
+ context "and is paid" do
+
+ it "is credit_owed" do
+ order.payment_total = 30
+ order.total = 30
+ allow(order).to receive_message_chain(:payments, :valid, :size).and_return(1)
+ allow(order).to receive_message_chain(:payments, :completed, :size).and_return(1)
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'credit_owed'
+ end
+
+ end
+
+ context "and payment is refunded" do
+ it "is void" do
+ order.payment_total = 0
+ order.total = 30
+ expect {
+ updater.update_payment_state
+ }.to change { order.payment_state }.to 'void'
+ end
+ end
+ end
+
+ end
+
+ it "state change" do
+ order.shipment_state = 'shipped'
+ state_changes = double
+ allow(order).to receive_messages state_changes: state_changes
+ expect(state_changes).to receive(:create).with(
+ previous_state: nil,
+ next_state: 'shipped',
+ name: 'shipment',
+ user_id: nil
+ )
+
+ order.state_changed('shipment')
+ end
+
+ context "completed order" do
+ before { allow(order).to receive_messages completed?: true }
+
+ it "updates payment state" do
+ expect(updater).to receive(:update_payment_state)
+ updater.update
+ end
+
+ it "updates shipment state" do
+ expect(updater).to receive(:update_shipment_state)
+ updater.update
+ end
+
+ it "updates each shipment" do
+ shipment = stub_model(Spree::Shipment, order: order)
+ shipments = [shipment]
+ allow(order).to receive_messages shipments: shipments
+ allow(shipments).to receive_messages states: []
+ allow(shipments).to receive_messages ready: []
+ allow(shipments).to receive_messages pending: []
+ allow(shipments).to receive_messages shipped: []
+
+ expect(shipment).to receive(:update!).with(order)
+ updater.update_shipments
+ end
+
+ it "refreshes shipment rates" do
+ shipment = stub_model(Spree::Shipment, order: order)
+ shipments = [shipment]
+ allow(order).to receive_messages shipments: shipments
+
+ expect(shipment).to receive(:refresh_rates)
+ updater.update_shipments
+ end
+
+ it "updates the shipment amount" do
+ shipment = stub_model(Spree::Shipment, order: order)
+ shipments = [shipment]
+ allow(order).to receive_messages shipments: shipments
+
+ expect(shipment).to receive(:update_amounts)
+ updater.update_shipments
+ end
+ end
+
+ context "incompleted order" do
+ before { allow(order).to receive_messages completed?: false }
+
+ it "doesnt update payment state" do
+ expect(updater).not_to receive(:update_payment_state)
+ updater.update
+ end
+
+ it "doesnt update shipment state" do
+ expect(updater).not_to receive(:update_shipment_state)
+ updater.update
+ end
+
+ it "doesnt update each shipment" do
+ shipment = stub_model(Spree::Shipment)
+ shipments = [shipment]
+ allow(order).to receive_messages shipments: shipments
+ allow(shipments).to receive_messages states: []
+ allow(shipments).to receive_messages ready: []
+ allow(shipments).to receive_messages pending: []
+ allow(shipments).to receive_messages shipped: []
+
+ allow(updater).to receive(:update_totals) # Otherwise this gets called and causes a scene
+ expect(updater).not_to receive(:update_shipments).with(order)
+ updater.update
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/payment_method_spec.rb b/core/spec/models/spree/payment_method_spec.rb
new file mode 100644
index 00000000000..1e9ece0484f
--- /dev/null
+++ b/core/spec/models/spree/payment_method_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe Spree::PaymentMethod, :type => :model do
+ describe "#available" do
+ before do
+ [nil, 'both', 'front_end', 'back_end'].each do |display_on|
+ Spree::Gateway::Test.create(
+ :name => 'Display Both',
+ :display_on => display_on,
+ :active => true,
+ :environment => 'test',
+ :description => 'foofah'
+ )
+ end
+ end
+
+ it "should have 4 total methods" do
+ expect(Spree::PaymentMethod.all.size).to eq(4)
+ end
+
+ it "should return all methods available to front-end/back-end when no parameter is passed" do
+ expect(Spree::PaymentMethod.available.size).to eq(2)
+ end
+
+ it "should return all methods available to front-end/back-end when display_on = :both" do
+ expect(Spree::PaymentMethod.available(:both).size).to eq(2)
+ end
+
+ it "should return all methods available to front-end when display_on = :front_end" do
+ expect(Spree::PaymentMethod.available(:front_end).size).to eq(2)
+ end
+
+ it "should return all methods available to back-end when display_on = :back_end" do
+ expect(Spree::PaymentMethod.available(:back_end).size).to eq(2)
+ end
+ end
+
+ describe '#auto_capture?' do
+ class TestGateway < Spree::Gateway
+ def provider_class
+ Provider
+ end
+ end
+
+ let(:gateway) { TestGateway.new }
+
+ subject { gateway.auto_capture? }
+
+ context 'when auto_capture is nil' do
+ before(:each) do
+ expect(Spree::Config).to receive('[]').with(:auto_capture).and_return(auto_capture)
+ end
+
+ context 'and when Spree::Config[:auto_capture] is false' do
+ let(:auto_capture) { false }
+
+ it 'should be false' do
+ expect(gateway.auto_capture).to be_nil
+ expect(subject).to be false
+ end
+ end
+
+ context 'and when Spree::Config[:auto_capture] is true' do
+ let(:auto_capture) { true }
+
+ it 'should be true' do
+ expect(gateway.auto_capture).to be_nil
+ expect(subject).to be true
+ end
+ end
+ end
+
+ context 'when auto_capture is not nil' do
+ before(:each) do
+ gateway.auto_capture = auto_capture
+ end
+
+ context 'and is true' do
+ let(:auto_capture) { true }
+
+ it 'should be true' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'and is false' do
+ let(:auto_capture) { false }
+
+ it 'should be true' do
+ expect(subject).to be false
+ end
+ end
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb
new file mode 100644
index 00000000000..d9e0311eb73
--- /dev/null
+++ b/core/spec/models/spree/payment_spec.rb
@@ -0,0 +1,961 @@
+require 'spec_helper'
+
+describe Spree::Payment, :type => :model do
+ let(:order) { Spree::Order.create }
+ let(:refund_reason) { create(:refund_reason) }
+
+ let(:gateway) do
+ gateway = Spree::Gateway::Bogus.new(:environment => 'test', :active => true)
+ allow(gateway).to receive_messages :source_required => true
+ gateway
+ end
+
+ let(:avs_code) { 'D' }
+ let(:cvv_code) { 'M' }
+
+ let(:card) { create :credit_card }
+
+ let(:payment) do
+ payment = Spree::Payment.new
+ payment.source = card
+ payment.order = order
+ payment.payment_method = gateway
+ payment.amount = 5
+ payment
+ end
+
+ let(:amount_in_cents) { (payment.amount * 100).round }
+
+ let!(:success_response) do
+ ActiveMerchant::Billing::Response.new(true, '', {}, {
+ authorization: '123',
+ cvv_result: cvv_code,
+ avs_result: { code: avs_code }
+ })
+ end
+
+ let(:failed_response) do
+ ActiveMerchant::Billing::Response.new(false, '', {}, {})
+ end
+
+ before(:each) do
+ # So it doesn't create log entries every time a processing method is called
+ allow(payment.log_entries).to receive(:create!)
+ end
+
+ context '.risky' do
+
+ let!(:payment_1) { create(:payment, avs_response: 'Y', cvv_response_code: 'M', cvv_response_message: 'Match') }
+ let!(:payment_2) { create(:payment, avs_response: 'Y', cvv_response_code: 'M', cvv_response_message: '') }
+ let!(:payment_3) { create(:payment, avs_response: 'A', cvv_response_code: 'M', cvv_response_message: 'Match') }
+ let!(:payment_4) { create(:payment, avs_response: 'Y', cvv_response_code: 'N', cvv_response_message: 'No Match') }
+
+ it 'should not return successful responses' do
+ expect(subject.class.risky.to_a).to match_array([payment_3, payment_4])
+ end
+
+ end
+
+ context "#captured_amount" do
+ context "calculates based on capture events" do
+ it "with 0 capture events" do
+ expect(payment.captured_amount).to eq(0)
+ end
+
+ it "with some capture events" do
+ payment.save
+ payment.capture_events.create!(amount: 2.0)
+ payment.capture_events.create!(amount: 3.0)
+ expect(payment.captured_amount).to eq(5)
+ end
+ end
+ end
+
+ context '#uncaptured_amount' do
+ context "calculates based on capture events" do
+ it "with 0 capture events" do
+ expect(payment.uncaptured_amount).to eq(5.0)
+ end
+
+ it "with some capture events" do
+ payment.save
+ payment.capture_events.create!(amount: 2.0)
+ payment.capture_events.create!(amount: 3.0)
+ expect(payment.uncaptured_amount).to eq(0)
+ end
+ end
+ end
+
+ context 'validations' do
+ it "returns useful error messages when source is invalid" do
+ payment.source = Spree::CreditCard.new
+ expect(payment).not_to be_valid
+ cc_errors = payment.errors['Credit Card']
+ expect(cc_errors).to include("Number can't be blank")
+ expect(cc_errors).to include("Month is not a number")
+ expect(cc_errors).to include("Year is not a number")
+ expect(cc_errors).to include("Verification Value can't be blank")
+ end
+ end
+
+ # Regression test for https://github.com/spree/spree/pull/2224
+ context 'failure' do
+ it 'should transition to failed from pending state' do
+ payment.state = 'pending'
+ payment.failure
+ expect(payment.state).to eql('failed')
+ end
+
+ it 'should transition to failed from processing state' do
+ payment.state = 'processing'
+ payment.failure
+ expect(payment.state).to eql('failed')
+ end
+
+ end
+
+ context 'invalidate' do
+ it 'should transition from checkout to invalid' do
+ payment.state = 'checkout'
+ payment.invalidate
+ expect(payment.state).to eq('invalid')
+ end
+ end
+
+ context "processing" do
+ describe "#process!" do
+ it "should purchase if with auto_capture" do
+ expect(payment.payment_method).to receive(:auto_capture?).and_return(true)
+ payment.process!
+ expect(payment).to be_completed
+ end
+
+ it "should authorize without auto_capture" do
+ expect(payment.payment_method).to receive(:auto_capture?).and_return(false)
+ payment.process!
+ expect(payment).to be_pending
+ end
+
+ it "should make the state 'processing'" do
+ expect(payment).to receive(:started_processing!)
+ payment.process!
+ end
+
+ it "should invalidate if payment method doesnt support source" do
+ expect(payment.payment_method).to receive(:supports?).with(payment.source).and_return(false)
+ expect { payment.process!}.to raise_error(Spree::Core::GatewayError)
+ expect(payment.state).to eq('invalid')
+ end
+
+ # Regression test for #4598
+ it "should allow payments with a gateway_customer_profile_id" do
+ allow(payment.source).to receive_messages :gateway_customer_profile_id => "customer_1"
+ expect(payment.payment_method).to receive(:supports?).with(payment.source).and_return(false)
+ expect(payment).to receive(:started_processing!)
+ payment.process!
+ end
+
+ # Another regression test for #4598
+ it "should allow payments with a gateway_payment_profile_id" do
+ allow(payment.source).to receive_messages :gateway_payment_profile_id => "customer_1"
+ expect(payment.payment_method).to receive(:supports?).with(payment.source).and_return(false)
+ expect(payment).to receive(:started_processing!)
+ payment.process!
+ end
+ end
+
+ describe "#authorize!" do
+ it "should call authorize on the gateway with the payment amount" do
+ expect(payment.payment_method).to receive(:authorize).with(amount_in_cents,
+ card,
+ anything).and_return(success_response)
+ payment.authorize!
+ end
+
+ it "should call authorize on the gateway with the currency code" do
+ allow(payment).to receive_messages :currency => 'GBP'
+ expect(payment.payment_method).to receive(:authorize).with(amount_in_cents,
+ card,
+ hash_including({:currency => "GBP"})).and_return(success_response)
+ payment.authorize!
+ end
+
+ it "should log the response" do
+ payment.save!
+ expect(payment.log_entries).to receive(:create!).with(details: anything)
+ payment.authorize!
+ end
+
+ context "when gateway does not match the environment" do
+ it "should raise an exception" do
+ allow(gateway).to receive_messages :environment => "foo"
+ expect { payment.authorize! }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+
+ context "if successful" do
+ before do
+ expect(payment.payment_method).to receive(:authorize).with(amount_in_cents,
+ card,
+ anything).and_return(success_response)
+ end
+
+ it "should store the response_code, avs_response and cvv_response fields" do
+ payment.authorize!
+ expect(payment.response_code).to eq('123')
+ expect(payment.avs_response).to eq(avs_code)
+ expect(payment.cvv_response_code).to eq(cvv_code)
+ expect(payment.cvv_response_message).to eq(ActiveMerchant::Billing::CVVResult::MESSAGES[cvv_code])
+ end
+
+ it "should make payment pending" do
+ expect(payment).to receive(:pend!)
+ payment.authorize!
+ end
+ end
+
+ context "if unsuccessful" do
+ it "should mark payment as failed" do
+ allow(gateway).to receive(:authorize).and_return(failed_response)
+ expect(payment).to receive(:failure)
+ expect(payment).not_to receive(:pend)
+ expect {
+ payment.authorize!
+ }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+ end
+
+ describe "#purchase!" do
+ it "should call purchase on the gateway with the payment amount" do
+ expect(gateway).to receive(:purchase).with(amount_in_cents, card, anything).and_return(success_response)
+ payment.purchase!
+ end
+
+ it "should log the response" do
+ payment.save!
+ expect(payment.log_entries).to receive(:create!).with(details: anything)
+ payment.purchase!
+ end
+
+ context "when gateway does not match the environment" do
+ it "should raise an exception" do
+ allow(gateway).to receive_messages :environment => "foo"
+ expect { payment.purchase! }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+
+ context "if successful" do
+ before do
+ expect(payment.payment_method).to receive(:purchase).with(amount_in_cents,
+ card,
+ anything).and_return(success_response)
+ end
+
+ it "should store the response_code and avs_response" do
+ payment.purchase!
+ expect(payment.response_code).to eq('123')
+ expect(payment.avs_response).to eq(avs_code)
+ end
+
+ it "should make payment complete" do
+ expect(payment).to receive(:complete!)
+ payment.purchase!
+ end
+
+ it "should log a capture event" do
+ payment.purchase!
+ expect(payment.capture_events.count).to eq(1)
+ expect(payment.capture_events.first.amount).to eq(payment.amount)
+ end
+
+ it "should set the uncaptured amount to 0" do
+ payment.purchase!
+ expect(payment.uncaptured_amount).to eq(0)
+ end
+ end
+
+ context "if unsuccessful" do
+ before do
+ allow(gateway).to receive(:purchase).and_return(failed_response)
+ expect(payment).to receive(:failure)
+ expect(payment).not_to receive(:pend)
+ end
+
+ it "should make payment failed" do
+ expect { payment.purchase! }.to raise_error(Spree::Core::GatewayError)
+ end
+
+ it "should not log a capture event" do
+ expect { payment.purchase! }.to raise_error(Spree::Core::GatewayError)
+ expect(payment.capture_events.count).to eq(0)
+ end
+ end
+ end
+
+ describe "#capture!" do
+ context "when payment is pending" do
+ before do
+ payment.amount = 100
+ payment.state = 'pending'
+ payment.response_code = '12345'
+ end
+
+ context "if successful" do
+ context 'for entire amount' do
+ before do
+ expect(payment.payment_method).to receive(:capture).with(payment.display_amount.money.cents, payment.response_code, anything).and_return(success_response)
+ end
+
+ it "should make payment complete" do
+ expect(payment).to receive(:complete!)
+ payment.capture!
+ end
+
+ it "logs capture events" do
+ payment.capture!
+ expect(payment.capture_events.count).to eq(1)
+ expect(payment.capture_events.first.amount).to eq(payment.amount)
+ end
+ end
+
+ context 'for partial amount' do
+ let(:original_amount) { payment.money.money.cents }
+ let(:capture_amount) { original_amount - 100 }
+
+ before do
+ expect(payment.payment_method).to receive(:capture).with(capture_amount, payment.response_code, anything).and_return(success_response)
+ end
+
+ it "should make payment complete & create pending payment for remaining amount" do
+ expect(payment).to receive(:complete!)
+ payment.capture!(capture_amount)
+ order = payment.order
+ payments = order.payments
+
+ expect(payments.size).to eq 2
+ expect(payments.pending.first.amount).to eq 1
+ # Payment stays processing for spec because of receive(:complete!) stub.
+ expect(payments.processing.first.amount).to eq(capture_amount / 100)
+ expect(payments.processing.first.source).to eq(payments.pending.first.source)
+ end
+
+ it "logs capture events" do
+ payment.capture!(capture_amount)
+ expect(payment.capture_events.count).to eq(1)
+ expect(payment.capture_events.first.amount).to eq(capture_amount / 100)
+ end
+ end
+ end
+
+ context "if unsuccessful" do
+ it "should not make payment complete" do
+ allow(gateway).to receive_messages :capture => failed_response
+ expect(payment).to receive(:failure)
+ expect(payment).not_to receive(:complete)
+ expect { payment.capture! }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+ end
+
+ # Regression test for #2119
+ context "when payment is completed" do
+ before do
+ payment.state = 'completed'
+ end
+
+ it "should do nothing" do
+ expect(payment).not_to receive(:complete)
+ expect(payment.payment_method).not_to receive(:capture)
+ expect(payment.log_entries).not_to receive(:create!)
+ payment.capture!
+ end
+ end
+ end
+
+ describe "#void_transaction!" do
+ before do
+ payment.response_code = '123'
+ payment.state = 'pending'
+ end
+
+ context "when profiles are supported" do
+ it "should call payment_gateway.void with the payment's response_code" do
+ allow(gateway).to receive_messages :payment_profiles_supported? => true
+ expect(gateway).to receive(:void).with('123', card, anything).and_return(success_response)
+ payment.void_transaction!
+ end
+ end
+
+ context "when profiles are not supported" do
+ it "should call payment_gateway.void with the payment's response_code" do
+ allow(gateway).to receive_messages :payment_profiles_supported? => false
+ expect(gateway).to receive(:void).with('123', anything).and_return(success_response)
+ payment.void_transaction!
+ end
+ end
+
+ it "should log the response" do
+ expect(payment.log_entries).to receive(:create!).with(:details => anything)
+ payment.void_transaction!
+ end
+
+ context "when gateway does not match the environment" do
+ it "should raise an exception" do
+ allow(gateway).to receive_messages :environment => "foo"
+ expect { payment.void_transaction! }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+
+ context "if successful" do
+ it "should update the response_code with the authorization from the gateway" do
+ # Change it to something different
+ payment.response_code = 'abc'
+ payment.void_transaction!
+ expect(payment.response_code).to eq('12345')
+ end
+ end
+
+ context "if unsuccessful" do
+ it "should not void the payment" do
+ allow(gateway).to receive_messages :void => failed_response
+ expect(payment).not_to receive(:void)
+ expect { payment.void_transaction! }.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+
+ # Regression test for #2119
+ context "if payment is already voided" do
+ before do
+ payment.state = 'void'
+ end
+
+ it "should not void the payment" do
+ expect(payment.payment_method).not_to receive(:void)
+ payment.void_transaction!
+ end
+ end
+ end
+
+ end
+
+ context "when already processing" do
+ it "should return nil without trying to process the source" do
+ payment.state = 'processing'
+
+ expect(payment.process!).to be_nil
+ end
+ end
+
+ context "with source required" do
+ context "raises an error if no source is specified" do
+ before do
+ payment.source = nil
+ end
+
+ specify do
+ expect { payment.process! }.to raise_error(Spree::Core::GatewayError, Spree.t(:payment_processing_failed))
+ end
+ end
+ end
+
+ context "with source optional" do
+ context "raises no error if source is not specified" do
+ before do
+ payment.source = nil
+ allow(payment.payment_method).to receive_messages(:source_required? => false)
+ end
+
+ specify do
+ expect { payment.process! }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#credit_allowed" do
+ # Regression test for #4403 & #4407
+ it "is the difference between offsets total and payment amount" do
+ payment.amount = 100
+ allow(payment).to receive(:offsets_total).and_return(0)
+ expect(payment.credit_allowed).to eq(100)
+ allow(payment).to receive(:offsets_total).and_return(-80)
+ expect(payment.credit_allowed).to eq(20)
+ end
+ end
+
+ describe "#can_credit?" do
+ it "is true if credit_allowed > 0" do
+ allow(payment).to receive(:credit_allowed).and_return(100)
+ expect(payment.can_credit?).to be true
+ end
+
+ it "is false if credit_allowed is 0" do
+ allow(payment).to receive(:credit_allowed).and_return(0)
+ expect(payment.can_credit?).to be false
+ end
+ end
+
+ describe "#save" do
+ context "captured payments" do
+ it "update order payment total" do
+ payment = create(:payment, order: order, state: 'completed')
+ expect(order.payment_total).to eq payment.amount
+ end
+ end
+
+ context "not completed payments" do
+ it "doesn't update order payment total" do
+ expect {
+ Spree::Payment.create(:amount => 100, :order => order)
+ }.not_to change { order.payment_total }
+ end
+
+ it "requires a payment method" do
+ expect(Spree::Payment.create(amount: 100, order: order)).to have(1).error_on(:payment_method)
+ end
+ end
+
+ context 'when the payment was completed but now void' do
+ let(:payment) do
+ Spree::Payment.create(
+ amount: 100,
+ order: order,
+ state: 'completed'
+ )
+ end
+
+ it 'updates order payment total' do
+ payment.void
+ expect(order.payment_total).to eq 0
+ end
+ end
+
+ context "completed orders" do
+ before { allow(order).to receive_messages completed?: true }
+
+ it "updates payment_state and shipments" do
+ expect(order.updater).to receive(:update_payment_state)
+ expect(order.updater).to receive(:update_shipment_state)
+ Spree::Payment.create(amount: 100, order: order, payment_method: gateway)
+ end
+ end
+
+ context "when profiles are supported" do
+ before do
+ allow(gateway).to receive_messages :payment_profiles_supported? => true
+ allow(payment.source).to receive_messages :has_payment_profile? => false
+ end
+
+ context "when there is an error connecting to the gateway" do
+ it "should call gateway_error " do
+ expect(gateway).to receive(:create_profile).and_raise(ActiveMerchant::ConnectionError)
+ expect do
+ Spree::Payment.create(
+ :amount => 100,
+ :order => order,
+ :source => card,
+ :payment_method => gateway
+ )
+ end.to raise_error(Spree::Core::GatewayError)
+ end
+ end
+
+ context "with multiple payment attempts" do
+ let(:attributes) { attributes_for(:credit_card) }
+ it "should not try to create profiles on old failed payment attempts" do
+ allow_any_instance_of(Spree::Payment).to receive(:payment_method) { gateway }
+
+ order.payments.create!(
+ source_attributes: attributes,
+ payment_method: gateway,
+ amount: 100
+ )
+ expect(gateway).to receive(:create_profile).exactly :once
+ expect(order.payments.count).to eq(1)
+ order.payments.create!(
+ source_attributes: attributes,
+ payment_method: gateway,
+ amount: 100
+ )
+ end
+
+ end
+
+ context "when successfully connecting to the gateway" do
+ it "should create a payment profile" do
+ expect(payment.payment_method).to receive :create_profile
+ payment = Spree::Payment.create(
+ :amount => 100,
+ :order => order,
+ :source => card,
+ :payment_method => gateway
+ )
+ end
+ end
+ end
+
+ context "when profiles are not supported" do
+ before { allow(gateway).to receive_messages :payment_profiles_supported? => false }
+
+ it "should not create a payment profile" do
+ expect(gateway).not_to receive :create_profile
+ payment = Spree::Payment.create(
+ :amount => 100,
+ :order => order,
+ :source => card,
+ :payment_method => gateway
+ )
+ end
+ end
+ end
+
+ describe '#invalidate_old_payments' do
+ before {
+ Spree::Payment.skip_callback(:rollback, :after, :persist_invalid)
+ }
+ after {
+ Spree::Payment.set_callback(:rollback, :after, :persist_invalid)
+ }
+
+ it 'should not invalidate other payments if not valid' do
+ payment.save
+ invalid_payment = Spree::Payment.new(:amount => 100, :order => order, :state => 'invalid', :payment_method => gateway)
+ invalid_payment.save
+ expect(payment.reload.state).to eq('checkout')
+ end
+ end
+
+ describe "#build_source" do
+ let(:params) do
+ {
+ :amount => 100,
+ :payment_method => gateway,
+ :source_attributes => {
+ :expiry =>"01 / 99",
+ :number => '1234567890123',
+ :verification_value => '123',
+ :name => 'Spree Commerce'
+ }
+ }
+ end
+
+ it "should build the payment's source" do
+ payment = Spree::Payment.new(params)
+ expect(payment).to be_valid
+ expect(payment.source).not_to be_nil
+ end
+
+ it "assigns user and gateway to payment source" do
+ order = create(:order)
+ source = order.payments.new(params).source
+
+ expect(source.user_id).to eq order.user_id
+ expect(source.payment_method_id).to eq gateway.id
+ end
+
+ it "errors when payment source not valid" do
+ params = { :amount => 100, :payment_method => gateway,
+ :source_attributes => {:expiry => "1 / 12" }}
+
+ payment = Spree::Payment.new(params)
+ expect(payment).not_to be_valid
+ expect(payment.source).not_to be_nil
+ expect(payment.source.error_on(:number).size).to eq(1)
+ expect(payment.source.error_on(:verification_value).size).to eq(1)
+ end
+
+ it "does not build a new source when duplicating the model with source_attributes set" do
+ payment = create(:payment)
+ payment.source_attributes = params[:source_attributes]
+ expect { payment.dup }.to_not change { payment.source }
+ end
+ end
+
+ describe "#currency" do
+ before { allow(order).to receive(:currency) { "ABC" } }
+ it "returns the order currency" do
+ expect(payment.currency).to eq("ABC")
+ end
+ end
+
+ describe "#display_amount" do
+ it "returns a Spree::Money for this amount" do
+ expect(payment.display_amount).to eq(Spree::Money.new(payment.amount))
+ end
+ end
+
+ # Regression test for #2216
+ describe "#gateway_options" do
+ before { allow(order).to receive_messages(:last_ip_address => "192.168.1.1") }
+
+ it "contains an IP" do
+ expect(payment.gateway_options[:ip]).to eq(order.last_ip_address)
+ end
+
+ it "contains the email address from a persisted order" do
+ # Sets the payment's order to a different Ruby object entirely
+ payment.order = Spree::Order.find(payment.order_id)
+ email = 'foo@example.com'
+ order.update_attributes(:email => email)
+ expect(payment.gateway_options[:email]).to eq(email)
+ end
+ end
+
+ describe "#set_unique_identifier" do
+ # Regression test for #1998
+ it "sets a unique identifier on create" do
+ payment.run_callbacks(:create)
+ expect(payment.identifier).not_to be_blank
+ expect(payment.identifier.size).to eq(8)
+ expect(payment.identifier).to be_a(String)
+ end
+
+ # Regression test for #3733
+ it "does not regenerate the identifier on re-save" do
+ payment.save
+ old_identifier = payment.identifier
+ payment.save
+ expect(payment.identifier).to eq(old_identifier)
+ end
+
+ context "other payment exists" do
+ let(:other_payment) {
+ payment = Spree::Payment.new
+ payment.source = card
+ payment.order = order
+ payment.payment_method = gateway
+ payment
+ }
+
+ before { other_payment.save! }
+
+ it "doesn't set duplicate identifier" do
+ expect(payment).to receive(:generate_identifier).and_return(other_payment.identifier)
+ expect(payment).to receive(:generate_identifier).and_call_original
+
+ payment.run_callbacks(:create)
+
+ expect(payment.identifier).not_to be_blank
+ expect(payment.identifier).not_to eq(other_payment.identifier)
+ end
+ end
+ end
+
+ describe "#amount=" do
+ before do
+ subject.amount = amount
+ end
+
+ context "when the amount is a string" do
+ context "amount is a decimal" do
+ let(:amount) { '2.99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+
+ context "amount is an integer" do
+ let(:amount) { '2' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.0'))
+ end
+ end
+
+ context "amount contains a dollar sign" do
+ let(:amount) { '$2.99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+
+ context "amount contains a comma" do
+ let(:amount) { '$2,999.99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2999.99'))
+ end
+ end
+
+ context "amount contains a negative sign" do
+ let(:amount) { '-2.99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('-2.99'))
+ end
+ end
+
+ context "amount is invalid" do
+ let(:amount) { 'invalid' }
+
+ # this is a strange default for ActiveRecord
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('0'))
+ end
+ end
+
+ context "amount is an empty string" do
+ let(:amount) { '' }
+
+ it '#amount' do
+ expect(subject.amount).to be_nil
+ end
+ end
+ end
+
+ context "when the amount is a number" do
+ let(:amount) { 1.55 }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('1.55'))
+ end
+ end
+
+ context "when the locale uses a coma as a decimal separator" do
+ before(:each) do
+ I18n.backend.store_translations(:fr, { :number => { :currency => { :format => { :delimiter => ' ', :separator => ',' } } } })
+ I18n.locale = :fr
+ subject.amount = amount
+ end
+
+ after do
+ I18n.locale = I18n.default_locale
+ end
+
+ context "amount is a decimal" do
+ let(:amount) { '2,99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+
+ context "amount contains a $ sign" do
+ let(:amount) { '2,99 $' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+
+ context "amount is a number" do
+ let(:amount) { 2.99 }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+
+ context "amount contains a negative sign" do
+ let(:amount) { '-2,99 $' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('-2.99'))
+ end
+ end
+
+ context "amount uses a dot as a decimal separator" do
+ let(:amount) { '2.99' }
+
+ it '#amount' do
+ expect(subject.amount).to eql(BigDecimal('2.99'))
+ end
+ end
+ end
+ end
+
+ describe "is_avs_risky?" do
+ it "returns false if avs_response included in NON_RISKY_AVS_CODES" do
+ ('A'..'Z').reject{ |x| subject.class::RISKY_AVS_CODES.include?(x) }.to_a.each do |char|
+ payment.update_attribute(:avs_response, char)
+ expect(payment.is_avs_risky?).to eq false
+ end
+ end
+
+ it "returns false if avs_response.blank?" do
+ payment.update_attribute(:avs_response, nil)
+ expect(payment.is_avs_risky?).to eq false
+ payment.update_attribute(:avs_response, '')
+ expect(payment.is_avs_risky?).to eq false
+ end
+
+ it "returns true if avs_response in RISKY_AVS_CODES" do
+ # should use avs_response_code helper
+ ('A'..'Z').reject{ |x| subject.class::NON_RISKY_AVS_CODES.include?(x) }.to_a.each do |char|
+ payment.update_attribute(:avs_response, char)
+ expect(payment.is_avs_risky?).to eq true
+ end
+ end
+ end
+
+ describe "is_cvv_risky?" do
+ it "returns false if cvv_response_code == 'M'" do
+ payment.update_attribute(:cvv_response_code, "M")
+ expect(payment.is_cvv_risky?).to eq(false)
+ end
+
+ it "returns false if cvv_response_code == nil" do
+ payment.update_attribute(:cvv_response_code, nil)
+ expect(payment.is_cvv_risky?).to eq(false)
+ end
+
+ it "returns false if cvv_response_message == ''" do
+ payment.update_attribute(:cvv_response_message, '')
+ expect(payment.is_cvv_risky?).to eq(false)
+ end
+
+ it "returns true if cvv_response_code == [A-Z], omitting D" do
+ # should use cvv_response_code helper
+ (%w{N P S U} << "").each do |char|
+ payment.update_attribute(:cvv_response_code, char)
+ expect(payment.is_cvv_risky?).to eq(true)
+ end
+ end
+ end
+
+ describe "#editable?" do
+ subject { payment }
+
+ before do
+ subject.state = state
+ end
+
+ context "when the state is 'checkout'" do
+ let(:state) { 'checkout' }
+
+ its(:editable?) { should be(true) }
+ end
+
+ context "when the state is 'pending'" do
+ let(:state) { 'pending' }
+
+ its(:editable?) { should be(true) }
+ end
+
+ %w[processing completed failed void invalid].each do |state|
+ context "when the state is '#{state}'" do
+ let(:state) { state }
+
+ its(:editable?) { should be(false) }
+ end
+ end
+ end
+
+ # Regression test for #4072 (kinda)
+ # The need for this was discovered in the research for #4072
+ context "state changes" do
+ it "are logged to the database" do
+ expect(payment.state_changes).to be_empty
+ expect(payment.process!).to be true
+ expect(payment.state_changes.count).to eq(2)
+ changes = payment.state_changes.map { |change| { change.previous_state => change.next_state} }
+ expect(changes).to match_array([
+ {"checkout" => "processing"},
+ { "processing" => "pending"}
+ ])
+ end
+ end
+end
diff --git a/core/spec/models/spree/preference_spec.rb b/core/spec/models/spree/preference_spec.rb
new file mode 100644
index 00000000000..2d9b9c1159f
--- /dev/null
+++ b/core/spec/models/spree/preference_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Spree::Preference, :type => :model do
+
+ it "should require a key" do
+ @preference = Spree::Preference.new
+ @preference.key = :test
+ @preference.value = true
+ expect(@preference).to be_valid
+ end
+
+ describe "type coversion for values" do
+ def round_trip_preference(key, value)
+ p = Spree::Preference.new
+ p.value = value
+ p.key = key
+ p.save
+
+ Spree::Preference.find_by_key(key)
+ end
+
+ it ":boolean" do
+ value = true
+ key = "boolean_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it "false :boolean" do
+ value = false
+ key = "boolean_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":integer" do
+ value = 10
+ key = "integer_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":decimal" do
+ value = 1.5
+ key = "decimal_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":string" do
+ value = "This is a string"
+ key = "string_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":text" do
+ value = "This is a string stored as text"
+ key = "text_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":password" do
+ value = "This is a password"
+ key = "password_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ it ":any" do
+ value = [1, 2]
+ key = "any_key"
+ pref = round_trip_preference(key, value)
+ expect(pref.value).to eq value
+ end
+
+ end
+
+end
diff --git a/core/spec/models/spree/preferences/configuration_spec.rb b/core/spec/models/spree/preferences/configuration_spec.rb
new file mode 100644
index 00000000000..3a95cc14ea5
--- /dev/null
+++ b/core/spec/models/spree/preferences/configuration_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Spree::Preferences::Configuration, :type => :model do
+
+ before :all do
+ class AppConfig < Spree::Preferences::Configuration
+ preference :color, :string, :default => :blue
+ end
+ @config = AppConfig.new
+ end
+
+ it "has named methods to access preferences" do
+ @config.color = 'orange'
+ expect(@config.color).to eq 'orange'
+ end
+
+ it "uses [ ] to access preferences" do
+ @config[:color] = 'red'
+ expect(@config[:color]).to eq 'red'
+ end
+
+ it "uses set/get to access preferences" do
+ @config.set :color, 'green'
+ expect(@config.get(:color)).to eq 'green'
+ end
+
+end
+
+
+
diff --git a/core/spec/models/spree/preferences/preferable_spec.rb b/core/spec/models/spree/preferences/preferable_spec.rb
new file mode 100644
index 00000000000..cd02e0069a4
--- /dev/null
+++ b/core/spec/models/spree/preferences/preferable_spec.rb
@@ -0,0 +1,348 @@
+require 'spec_helper'
+
+describe Spree::Preferences::Preferable, :type => :model do
+
+ before :all do
+ class A
+ include Spree::Preferences::Preferable
+ attr_reader :id
+
+ def initialize
+ @id = rand(999)
+ end
+
+ def preferences
+ @preferences ||= default_preferences
+ end
+
+ preference :color, :string, :default => 'green'
+ end
+
+ class B < A
+ preference :flavor, :string
+ end
+ end
+
+ before :each do
+ @a = A.new
+ allow(@a).to receive_messages(:persisted? => true)
+ @b = B.new
+ allow(@b).to receive_messages(:persisted? => true)
+
+ # ensure we're persisting as that is the default
+ #
+ store = Spree::Preferences::Store.instance
+ store.persistence = true
+ end
+
+ describe "preference definitions" do
+ it "parent should not see child definitions" do
+ expect(@a.has_preference?(:color)).to be true
+ expect(@a.has_preference?(:flavor)).not_to be true
+ end
+
+ it "child should have parent and own definitions" do
+ expect(@b.has_preference?(:color)).to be true
+ expect(@b.has_preference?(:flavor)).to be true
+ end
+
+ it "instances have defaults" do
+ expect(@a.preferred_color).to eq 'green'
+ expect(@b.preferred_color).to eq 'green'
+ expect(@b.preferred_flavor).to be_nil
+ end
+
+ it "can be asked if it has a preference definition" do
+ expect(@a.has_preference?(:color)).to be true
+ expect(@a.has_preference?(:bad)).to be false
+ end
+
+ it "can be asked and raises" do
+ expect {
+ @a.has_preference! :flavor
+ }.to raise_error(NoMethodError, "flavor preference not defined")
+ end
+
+ it "has a type" do
+ expect(@a.preferred_color_type).to eq :string
+ expect(@a.preference_type(:color)).to eq :string
+ end
+
+ it "has a default" do
+ expect(@a.preferred_color_default).to eq 'green'
+ expect(@a.preference_default(:color)).to eq 'green'
+ end
+
+ it "raises if not defined" do
+ expect {
+ @a.get_preference :flavor
+ }.to raise_error(NoMethodError, "flavor preference not defined")
+ end
+
+ end
+
+ describe "preference access" do
+ it "handles ghost methods for preferences" do
+ @a.preferred_color = 'blue'
+ expect(@a.preferred_color).to eq 'blue'
+ end
+
+ it "parent and child instances have their own prefs" do
+ @a.preferred_color = 'red'
+ @b.preferred_color = 'blue'
+
+ expect(@a.preferred_color).to eq 'red'
+ expect(@b.preferred_color).to eq 'blue'
+ end
+
+ it "raises when preference not defined" do
+ expect {
+ @a.set_preference(:bad, :bone)
+ }.to raise_exception(NoMethodError, "bad preference not defined")
+ end
+
+ it "builds a hash of preferences" do
+ @b.preferred_flavor = :strawberry
+ expect(@b.preferences[:flavor]).to eq 'strawberry'
+ expect(@b.preferences[:color]).to eq 'green' #default from A
+ end
+
+ it "builds a hash of preference defaults" do
+ expect(@b.default_preferences).to eq({
+ flavor: nil,
+ color: 'green'
+ })
+ end
+
+ context "converts integer preferences to integer values" do
+ before do
+ A.preference :is_integer, :integer
+ end
+
+ it "with strings" do
+ @a.set_preference(:is_integer, '3')
+ expect(@a.preferences[:is_integer]).to eq(3)
+
+ @a.set_preference(:is_integer, '')
+ expect(@a.preferences[:is_integer]).to eq(0)
+ end
+
+ end
+
+ context "converts decimal preferences to BigDecimal values" do
+ before do
+ A.preference :if_decimal, :decimal
+ end
+
+ it "returns a BigDecimal" do
+ @a.set_preference(:if_decimal, 3.3)
+ expect(@a.preferences[:if_decimal].class).to eq(BigDecimal)
+ end
+
+ it "with strings" do
+ @a.set_preference(:if_decimal, '3.3')
+ expect(@a.preferences[:if_decimal]).to eq(3.3)
+
+ @a.set_preference(:if_decimal, '')
+ expect(@a.preferences[:if_decimal]).to eq(0.0)
+ end
+ end
+
+ context "converts boolean preferences to boolean values" do
+ before do
+ A.preference :is_boolean, :boolean, :default => true
+ end
+
+ it "with strings" do
+ @a.set_preference(:is_boolean, '0')
+ expect(@a.preferences[:is_boolean]).to be false
+ @a.set_preference(:is_boolean, 'f')
+ expect(@a.preferences[:is_boolean]).to be false
+ @a.set_preference(:is_boolean, 't')
+ expect(@a.preferences[:is_boolean]).to be true
+ end
+
+ it "with integers" do
+ @a.set_preference(:is_boolean, 0)
+ expect(@a.preferences[:is_boolean]).to be false
+ @a.set_preference(:is_boolean, 1)
+ expect(@a.preferences[:is_boolean]).to be true
+ end
+
+ it "with an empty string" do
+ @a.set_preference(:is_boolean, '')
+ expect(@a.preferences[:is_boolean]).to be false
+ end
+
+ it "with an empty hash" do
+ @a.set_preference(:is_boolean, [])
+ expect(@a.preferences[:is_boolean]).to be false
+ end
+ end
+
+ context "converts array preferences to array values" do
+ before do
+ A.preference :is_array, :array, default: []
+ end
+
+ it "with arrays" do
+ @a.set_preference(:is_array, [])
+ expect(@a.preferences[:is_array]).to be_is_a(Array)
+ end
+
+ it "with string" do
+ @a.set_preference(:is_array, "string")
+ expect(@a.preferences[:is_array]).to be_is_a(Array)
+ end
+
+ it "with hash" do
+ @a.set_preference(:is_array, {})
+ expect(@a.preferences[:is_array]).to be_is_a(Array)
+ end
+ end
+
+ context "converts hash preferences to hash values" do
+ before do
+ A.preference :is_hash, :hash, default: {}
+ end
+
+ it "with hash" do
+ @a.set_preference(:is_hash, {})
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ end
+
+ it "with hash and keys are integers" do
+ @a.set_preference(:is_hash, {1 => 2, 3 => 4})
+ expect(@a.preferences[:is_hash]).to eql({1 => 2, 3 => 4})
+ end
+
+ it "with ancestor of a hash" do
+ ancestor_of_hash = ActionController::Parameters.new({ key: :value })
+ @a.set_preference(:is_hash, ancestor_of_hash)
+ expect(@a.preferences[:is_hash]).to eql({"key" => :value})
+ end
+
+ it "with string" do
+ @a.set_preference(:is_hash, "{\"0\"=>{\"answer\"=>\"1\", \"value\"=>\"No\"}}")
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ end
+
+ it "with boolean" do
+ @a.set_preference(:is_hash, false)
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ @a.set_preference(:is_hash, true)
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ end
+
+ it "with simple array" do
+ @a.set_preference(:is_hash, ["key", "value", "another key", "another value"])
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ expect(@a.preferences[:is_hash]["key"]).to eq("value")
+ expect(@a.preferences[:is_hash]["another key"]).to eq("another value")
+ end
+
+ it "with a nested array" do
+ @a.set_preference(:is_hash, [["key", "value"], ["another key", "another value"]])
+ expect(@a.preferences[:is_hash]).to be_is_a(Hash)
+ expect(@a.preferences[:is_hash]["key"]).to eq("value")
+ expect(@a.preferences[:is_hash]["another key"]).to eq("another value")
+ end
+
+ it "with single array" do
+ expect { @a.set_preference(:is_hash, ["key"]) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "converts any preferences to any values" do
+ before do
+ A.preference :product_ids, :any, :default => []
+ A.preference :product_attributes, :any, :default => {}
+ end
+
+ it "with array" do
+ expect(@a.preferences[:product_ids]).to eq([])
+ @a.set_preference(:product_ids, [1, 2])
+ expect(@a.preferences[:product_ids]).to eq([1, 2])
+ end
+
+ it "with hash" do
+ expect(@a.preferences[:product_attributes]).to eq({})
+ @a.set_preference(:product_attributes, {:id => 1, :name => 2})
+ expect(@a.preferences[:product_attributes]).to eq({:id => 1, :name => 2})
+ end
+ end
+
+ end
+
+ describe "persisted preferables" do
+ before(:all) do
+ class CreatePrefTest < ActiveRecord::Migration
+ def self.up
+ create_table :pref_tests do |t|
+ t.string :col
+ t.text :preferences
+ end
+ end
+
+ def self.down
+ drop_table :pref_tests
+ end
+ end
+
+ @migration_verbosity = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ CreatePrefTest.migrate(:up)
+
+ class PrefTest < Spree::Base
+ preference :pref_test_pref, :string, :default => 'abc'
+ preference :pref_test_any, :any, :default => []
+ end
+ end
+
+ after(:all) do
+ CreatePrefTest.migrate(:down)
+ ActiveRecord::Migration.verbose = @migration_verbosity
+ end
+
+ before(:each) do
+ @pt = PrefTest.create
+ end
+
+ describe "pending preferences for new activerecord objects" do
+ it "saves preferences after record is saved" do
+ pr = PrefTest.new
+ pr.set_preference(:pref_test_pref, 'XXX')
+ expect(pr.get_preference(:pref_test_pref)).to eq('XXX')
+ pr.save!
+ expect(pr.get_preference(:pref_test_pref)).to eq('XXX')
+ end
+
+ it "saves preferences for serialized object" do
+ pr = PrefTest.new
+ pr.set_preference(:pref_test_any, [1, 2])
+ expect(pr.get_preference(:pref_test_any)).to eq([1, 2])
+ pr.save!
+ expect(pr.get_preference(:pref_test_any)).to eq([1, 2])
+ end
+ end
+
+ it "clear preferences" do
+ @pt.set_preference(:pref_test_pref, 'xyz')
+ expect(@pt.preferred_pref_test_pref).to eq('xyz')
+ @pt.clear_preferences
+ expect(@pt.preferred_pref_test_pref).to eq('abc')
+ end
+
+ it "clear preferences when record is deleted" do
+ @pt.save!
+ @pt.preferred_pref_test_pref = 'lmn'
+ @pt.save!
+ @pt.destroy
+ @pt1 = PrefTest.new(:col => 'aaaa')
+ @pt1.id = @pt.id
+ @pt1.save!
+ expect(@pt1.get_preference(:pref_test_pref)).to eq('abc')
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/preferences/scoped_store_spec.rb b/core/spec/models/spree/preferences/scoped_store_spec.rb
new file mode 100644
index 00000000000..df5d48eef57
--- /dev/null
+++ b/core/spec/models/spree/preferences/scoped_store_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Spree::Preferences::ScopedStore, :type => :model do
+ let(:scoped_store){ described_class.new(prefix, suffix) }
+ subject{ scoped_store }
+ let(:prefix){ nil }
+ let(:suffix){ nil }
+
+ describe '#store' do
+ subject{ scoped_store.store }
+ it{ is_expected.to be Spree::Preferences::Store.instance }
+ end
+
+ context 'stubbed store' do
+ let(:store){ double(:store) }
+ before do
+ allow(scoped_store).to receive(:store).and_return(store)
+ end
+
+ context "with a prefix" do
+ let(:prefix){ 'my_class' }
+
+ it "can fetch" do
+ expect(store).to receive(:fetch).with('my_class/attr')
+ scoped_store.fetch('attr'){ 'default' }
+ end
+
+ it "can assign" do
+ expect(store).to receive(:[]=).with('my_class/attr', 'val')
+ scoped_store['attr'] = 'val'
+ end
+
+ it "can delete" do
+ expect(store).to receive(:delete).with('my_class/attr')
+ scoped_store.delete('attr')
+ end
+
+ context "and suffix" do
+ let(:suffix){ 123 }
+
+ it "can fetch" do
+ expect(store).to receive(:fetch).with('my_class/attr/123')
+ scoped_store.fetch('attr'){ 'default' }
+ end
+
+ it "can assign" do
+ expect(store).to receive(:[]=).with('my_class/attr/123', 'val')
+ scoped_store['attr'] = 'val'
+ end
+
+ it "can delete" do
+ expect(store).to receive(:delete).with('my_class/attr/123')
+ scoped_store.delete('attr')
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/preferences/store_spec.rb b/core/spec/models/spree/preferences/store_spec.rb
new file mode 100644
index 00000000000..237e6add063
--- /dev/null
+++ b/core/spec/models/spree/preferences/store_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe Spree::Preferences::Store, :type => :model do
+ before :each do
+ @store = Spree::Preferences::StoreInstance.new
+ end
+
+ it "sets and gets a key" do
+ @store.set :test, 1
+ expect(@store.exist?(:test)).to be true
+ expect(@store.get(:test)).to eq 1
+ end
+
+ it "can set and get false values when cache return nil" do
+ @store.set :test, false
+ expect(@store.get(:test)).to be false
+ end
+
+ it "will return db value when cache is emtpy and cache the db value" do
+ preference = Spree::Preference.where(:key => 'test').first_or_initialize
+ preference.value = '123'
+ preference.save
+
+ Rails.cache.clear
+ expect(@store.get(:test)).to eq '123'
+ expect(Rails.cache.read(:test)).to eq '123'
+ end
+
+ it "should return and cache fallback value when supplied" do
+ Rails.cache.clear
+ expect(@store.get(:test){ false }).to be false
+ expect(Rails.cache.read(:test)).to be false
+ end
+
+ it "should return but not cache fallback value when persistence is disabled" do
+ Rails.cache.clear
+ allow(@store).to receive_messages(:should_persist? => false)
+ expect(@store.get(:test){ true }).to be true
+ expect(Rails.cache.exist?(:test)).to be false
+ end
+
+ it "should return nil when key can't be found and fallback value is not supplied" do
+ expect(@store.get(:random_key){ nil }).to be_nil
+ end
+
+end
diff --git a/core/spec/models/spree/price_spec.rb b/core/spec/models/spree/price_spec.rb
new file mode 100644
index 00000000000..a3632f80fa9
--- /dev/null
+++ b/core/spec/models/spree/price_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Spree::Price, :type => :model do
+ describe 'validations' do
+ let(:variant) { stub_model Spree::Variant }
+ subject { Spree::Price.new variant: variant, amount: amount }
+
+ context 'when the amount is nil' do
+ let(:amount) { nil }
+ it { is_expected.to be_valid }
+ end
+
+ context 'when the amount is less than 0' do
+ let(:amount) { -1 }
+
+ it 'has 1 error_on' do
+ expect(subject.error_on(:amount).size).to eq(1)
+ end
+ it 'populates errors' do
+ subject.valid?
+ expect(subject.errors.messages[:amount].first).to eq 'must be greater than or equal to 0'
+ end
+ end
+
+ context 'when the amount is greater than 999,999.99' do
+ let(:amount) { 1_000_000 }
+
+ it 'has 1 error_on' do
+ expect(subject.error_on(:amount).size).to eq(1)
+ end
+ it 'populates errors' do
+ subject.valid?
+ expect(subject.errors.messages[:amount].first).to eq 'must be less than or equal to 999999.99'
+ end
+ end
+
+ context 'when the amount is between 0 and 999,999.99' do
+ let(:amount) { 100 }
+ it { is_expected.to be_valid }
+ end
+ end
+end
diff --git a/core/spec/models/spree/product/scopes_spec.rb b/core/spec/models/spree/product/scopes_spec.rb
new file mode 100644
index 00000000000..a35e1ef8b69
--- /dev/null
+++ b/core/spec/models/spree/product/scopes_spec.rb
@@ -0,0 +1,148 @@
+require 'spec_helper'
+
+describe "Product scopes", :type => :model do
+ let!(:product) { create(:product) }
+
+ context "A product assigned to parent and child taxons" do
+ before do
+ @taxonomy = create(:taxonomy)
+ @root_taxon = @taxonomy.root
+
+ @parent_taxon = create(:taxon, :name => 'Parent', :taxonomy_id => @taxonomy.id, :parent => @root_taxon)
+ @child_taxon = create(:taxon, :name =>'Child 1', :taxonomy_id => @taxonomy.id, :parent => @parent_taxon)
+ @parent_taxon.reload # Need to reload for descendents to show up
+
+ product.taxons << @parent_taxon
+ product.taxons << @child_taxon
+ end
+
+ it "calling Product.in_taxon returns products in child taxons" do
+ product.taxons -= [@child_taxon]
+ expect(product.taxons.count).to eq(1)
+
+ expect(Spree::Product.in_taxon(@parent_taxon)).to include(product)
+ end
+
+ it "calling Product.in_taxon should not return duplicate records" do
+ expect(Spree::Product.in_taxon(@parent_taxon).to_a.count).to eq(1)
+ end
+
+ context 'orders products based on their ordering within the classifications' do
+ let(:other_taxon) { create(:taxon, products: [product]) }
+ let!(:product_2) { create(:product, taxons: [@child_taxon, other_taxon]) }
+
+ it 'by initial ordering' do
+ expect(Spree::Product.in_taxon(@child_taxon)).to eq([product, product_2])
+ expect(Spree::Product.in_taxon(other_taxon)).to eq([product, product_2])
+ end
+
+ it 'after ordering changed' do
+ [@child_taxon, other_taxon].each do |taxon|
+ Spree::Classification.find_by(:taxon => taxon, :product => product).insert_at(2)
+ expect(Spree::Product.in_taxon(taxon)).to eq([product_2, product])
+ end
+ end
+ end
+ end
+
+ context "property scopes" do
+ let(:name) { "A proper tee" }
+ let(:value) { "A proper value"}
+ let!(:property) { create(:property, name: name)}
+
+ before do
+ product.properties << property
+ product.product_properties.find_by(property: property).update_column(:value, value)
+ end
+
+ context "with_property" do
+ let(:with_property) { Spree::Product.method(:with_property) }
+ it "finds by a property's name" do
+ expect(with_property.(name).count).to eq(1)
+ end
+
+ it "doesn't find any properties with an unknown name" do
+ expect(with_property.("fake").count).to eq(0)
+ end
+
+ it "finds by a property" do
+ expect(with_property.(property).count).to eq(1)
+ end
+
+ it "finds by an id" do
+ expect(with_property.(property.id).count).to eq(1)
+ end
+
+ it "cannot find a property with an unknown id" do
+ expect(with_property.(0).count).to eq(0)
+ end
+ end
+
+ context "with_property_value" do
+ let(:with_property_value) { Spree::Product.method(:with_property_value) }
+ it "finds by a property's name" do
+ expect(with_property_value.(name, value).count).to eq(1)
+ end
+
+ it "cannot find by an unknown property's name" do
+ expect(with_property_value.("fake", value).count).to eq(0)
+ end
+
+ it "cannot find with a name by an incorrect value" do
+ expect(with_property_value.(name, "fake").count).to eq(0)
+ end
+
+ it "finds by a property" do
+ expect(with_property_value.(property, value).count).to eq(1)
+ end
+
+ it "cannot find with a property by an incorrect value" do
+ expect(with_property_value.(property, "fake").count).to eq(0)
+ end
+
+ it "finds by an id with a value" do
+ expect(with_property_value.(property.id, value).count).to eq(1)
+ end
+
+ it "cannot find with an invalid id" do
+ expect(with_property_value.(0, value).count).to eq(0)
+ end
+
+ it "cannot find with an invalid value" do
+ expect(with_property_value.(property.id, "fake").count).to eq(0)
+ end
+ end
+ end
+
+ context '#add_simple_scopes' do
+ let(:simple_scopes) { [:ascend_by_updated_at, :descend_by_name] }
+
+ before do
+ Spree::Product.add_simple_scopes(simple_scopes)
+ end
+
+ context 'define scope' do
+ context 'ascend_by_updated_at' do
+ context 'on class' do
+ it { expect(Spree::Product.ascend_by_updated_at.to_sql).to eq Spree::Product.order("#{Spree::Product.quoted_table_name}.updated_at ASC").to_sql }
+ end
+
+ context 'on ActiveRecord::Relation' do
+ it { expect(Spree::Product.limit(2).ascend_by_updated_at.to_sql).to eq Spree::Product.limit(2).order("#{Spree::Product.quoted_table_name}.updated_at ASC").to_sql }
+ it { expect(Spree::Product.limit(2).ascend_by_updated_at.to_sql).to eq Spree::Product.ascend_by_updated_at.limit(2).to_sql }
+ end
+ end
+
+ context 'descend_by_name' do
+ context 'on class' do
+ it { expect(Spree::Product.descend_by_name.to_sql).to eq Spree::Product.order("#{Spree::Product.quoted_table_name}.name DESC").to_sql }
+ end
+
+ context 'on ActiveRecord::Relation' do
+ it { expect(Spree::Product.limit(2).descend_by_name.to_sql).to eq Spree::Product.limit(2).order("#{Spree::Product.quoted_table_name}.name DESC").to_sql }
+ it { expect(Spree::Product.limit(2).descend_by_name.to_sql).to eq Spree::Product.descend_by_name.limit(2).to_sql }
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/product_duplicator_spec.rb b/core/spec/models/spree/product_duplicator_spec.rb
new file mode 100644
index 00000000000..7557049272c
--- /dev/null
+++ b/core/spec/models/spree/product_duplicator_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+module Spree
+
+ describe Spree::ProductDuplicator, :type => :model do
+
+ let(:product) { create(:product, properties: [create(:property, name: "MyProperty")])}
+ let!(:duplicator) { Spree::ProductDuplicator.new(product)}
+
+ let(:image) { File.open(File.expand_path('../../../fixtures/thinking-cat.jpg', __FILE__)) }
+ let(:params) { {:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :attachment => image, :alt => "position 1", :position => 1} }
+
+ before do
+ Spree::Image.create(params)
+ end
+
+ it "will duplicate the product" do
+ expect{duplicator.duplicate}.to change{Spree::Product.count}.by(1)
+ end
+
+ context 'when image duplication enabled' do
+
+ it "will duplicate the product images" do
+ expect{duplicator.duplicate}.to change{Spree::Image.count}.by(1)
+ end
+
+ end
+
+ context 'when image duplication disabled' do
+
+ let!(:duplicator) { Spree::ProductDuplicator.new(product, false) }
+
+ it "will not duplicate the product images" do
+ expect{duplicator.duplicate}.to change{Spree::Image.count}.by(0)
+ end
+
+ end
+
+ context 'image duplication default' do
+
+ context 'when default is set to true' do
+
+ it 'clones images if no flag passed to initializer' do
+ expect{duplicator.duplicate}.to change{Spree::Image.count}.by(1)
+ end
+
+ end
+
+ context 'when default is set to false' do
+
+ before do
+ ProductDuplicator.clone_images_default = false
+ end
+
+ after do
+ ProductDuplicator.clone_images_default = true
+ end
+
+ it 'does not clone images if no flag passed to initializer' do
+ expect{ProductDuplicator.new(product).duplicate}.to change{Spree::Image.count}.by(0)
+ end
+
+ end
+
+ end
+
+ context "product attributes" do
+ let!(:new_product) {duplicator.duplicate}
+
+ it "will set an unique name" do
+ expect(new_product.name).to eql "COPY OF #{product.name}"
+ end
+
+ it "will set an unique sku" do
+ expect(new_product.sku).to include "COPY OF SKU"
+ end
+
+ it "copied the properties" do
+ expect(new_product.product_properties.count).to be 1
+ expect(new_product.product_properties.first.property.name).to eql "MyProperty"
+ end
+ end
+
+ context "with variants" do
+ let(:option_type) { create(:option_type, name: "MyOptionType")}
+ let(:option_value1) { create(:option_value, name: "OptionValue1", option_type: option_type)}
+ let(:option_value2) { create(:option_value, name: "OptionValue2", option_type: option_type)}
+
+ let!(:variant1) { create(:variant, product: product, option_values: [option_value1]) }
+ let!(:variant2) { create(:variant, product: product, option_values: [option_value2]) }
+
+ it "will duplciate the variants" do
+ # will change the count by 3, since there will be a master variant as well
+ expect{duplicator.duplicate}.to change{Spree::Variant.count}.by(3)
+ end
+
+ it "will not duplicate the option values" do
+ expect{duplicator.duplicate}.to change{Spree::OptionValue.count}.by(0)
+ end
+
+ end
+ end
+end
diff --git a/core/spec/models/spree/product_filter_spec.rb b/core/spec/models/spree/product_filter_spec.rb
new file mode 100644
index 00000000000..c61c9ceb2fa
--- /dev/null
+++ b/core/spec/models/spree/product_filter_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require 'spree/core/product_filters'
+
+describe 'product filters', :type => :model do
+ # Regression test for #1709
+ context 'finds products filtered by brand' do
+ let(:product) { create(:product) }
+ before do
+ property = Spree::Property.create!(:name => "brand", :presentation => "brand")
+ product.set_property("brand", "Nike")
+ end
+
+ it "does not attempt to call value method on Arel::Table" do
+ expect { Spree::Core::ProductFilters.brand_filter }.not_to raise_error
+ end
+
+ it "can find products in the 'Nike' brand" do
+ expect(Spree::Product.brand_any("Nike")).to include(product)
+ end
+ it "sorts products without brand specified" do
+ product.set_property("brand", "Nike")
+ create(:product).set_property("brand", nil)
+ expect { Spree::Core::ProductFilters.brand_filter[:labels] }.not_to raise_error
+ end
+ end
+end
diff --git a/core/spec/models/spree/product_option_type_spec.rb b/core/spec/models/spree/product_option_type_spec.rb
new file mode 100644
index 00000000000..e1c7e498757
--- /dev/null
+++ b/core/spec/models/spree/product_option_type_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::ProductOptionType, :type => :model do
+
+end
diff --git a/core/spec/models/spree/product_property_spec.rb b/core/spec/models/spree/product_property_spec.rb
new file mode 100644
index 00000000000..34b4f3fc3f5
--- /dev/null
+++ b/core/spec/models/spree/product_property_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Spree::ProductProperty, :type => :model do
+
+ context "validations" do
+ it "should validate length of value" do
+ pp = create(:product_property)
+ pp.value = "x" * 256
+ expect(pp).not_to be_valid
+ end
+ end
+
+ context "touching" do
+ it "should update product" do
+ pp = create(:product_property)
+ expect(pp.product).to receive(:touch)
+ pp.touch
+ end
+ end
+end
diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb
new file mode 100644
index 00000000000..d1b390fd23e
--- /dev/null
+++ b/core/spec/models/spree/product_spec.rb
@@ -0,0 +1,486 @@
+# coding: UTF-8
+
+require 'spec_helper'
+
+module ThirdParty
+ class Extension < Spree::Base
+ # nasty hack so we don't have to create a table to back this fake model
+ self.table_name = 'spree_products'
+ end
+end
+
+describe Spree::Product, :type => :model do
+
+ context 'product instance' do
+ let(:product) { create(:product) }
+ let(:variant) { create(:variant, :product => product) }
+
+ context '#duplicate' do
+ before do
+ allow(product).to receive_messages :taxons => [create(:taxon)]
+ end
+
+ it 'duplicates product' do
+ clone = product.duplicate
+ expect(clone.name).to eq('COPY OF ' + product.name)
+ expect(clone.master.sku).to eq('COPY OF ' + product.master.sku)
+ expect(clone.taxons).to eq(product.taxons)
+ expect(clone.images.size).to eq(product.images.size)
+ end
+
+ it 'calls #duplicate_extra' do
+ Spree::Product.class_eval do
+ def duplicate_extra(old_product)
+ self.name = old_product.name.reverse
+ end
+ end
+
+ clone = product.duplicate
+ expect(clone.name).to eq(product.name.reverse)
+ end
+ end
+
+ context "master variant" do
+
+ context "when master variant changed" do
+ before do
+ product.master.sku = "Something changed"
+ end
+
+ it "saves the master" do
+ expect(product.master).to receive(:save!)
+ product.save
+ end
+ end
+
+ context "when master default price changed" do
+ before do
+ master = product.master
+ master.default_price.price = 11
+ master.save!
+ product.master.default_price.price = 12
+ end
+
+ it "saves the master" do
+ expect(product.master).to receive(:save!)
+ product.save
+ end
+
+ it "saves the default price" do
+ expect(product.master.default_price).to receive(:save)
+ product.save
+ end
+ end
+
+ context "when master variant and price haven't changed" do
+ it "does not save the master" do
+ expect(product.master).not_to receive(:save!)
+ product.save
+ end
+ end
+ end
+
+ context "product has no variants" do
+ context "#destroy" do
+ it "should set deleted_at value" do
+ product.destroy
+ expect(product.deleted_at).not_to be_nil
+ expect(product.master.reload.deleted_at).not_to be_nil
+ end
+ end
+ end
+
+ context "product has variants" do
+ before do
+ create(:variant, :product => product)
+ end
+
+ context "#destroy" do
+ it "should set deleted_at value" do
+ product.destroy
+ expect(product.deleted_at).not_to be_nil
+ expect(product.variants_including_master.all? { |v| !v.deleted_at.nil? }).to be true
+ end
+ end
+ end
+
+ context "#price" do
+ # Regression test for #1173
+ it 'strips non-price characters' do
+ product.price = "$10"
+ expect(product.price).to eq(10.0)
+ end
+ end
+
+ context "#display_price" do
+ before { product.price = 10.55 }
+
+ context "with display_currency set to true" do
+ before { Spree::Config[:display_currency] = true }
+
+ it "shows the currency" do
+ expect(product.display_price.to_s).to eq("$10.55 USD")
+ end
+ end
+
+ context "with display_currency set to false" do
+ before { Spree::Config[:display_currency] = false }
+
+ it "does not include the currency" do
+ expect(product.display_price.to_s).to eq("$10.55")
+ end
+ end
+
+ context "with currency set to JPY" do
+ before do
+ product.master.default_price.currency = 'JPY'
+ product.master.default_price.save!
+ Spree::Config[:currency] = 'JPY'
+ end
+
+ it "displays the currency in yen" do
+ expect(product.display_price.to_s).to eq("¥11")
+ end
+ end
+ end
+
+ context "#available?" do
+ it "should be available if date is in the past" do
+ product.available_on = 1.day.ago
+ expect(product).to be_available
+ end
+
+ it "should not be available if date is nil or in the future" do
+ product.available_on = nil
+ expect(product).not_to be_available
+
+ product.available_on = 1.day.from_now
+ expect(product).not_to be_available
+ end
+
+ it "should not be available if destroyed" do
+ product.destroy
+ expect(product).not_to be_available
+ end
+ end
+
+ context "variants_and_option_values" do
+ let!(:high) { create(:variant, product: product) }
+ let!(:low) { create(:variant, product: product) }
+
+ before { high.option_values.destroy_all }
+
+ it "returns only variants with option values" do
+ expect(product.variants_and_option_values).to eq([low])
+ end
+ end
+
+ describe 'Variants sorting' do
+ context 'without master variant' do
+ it 'sorts variants by position' do
+ expect(product.variants.to_sql).to match(/ORDER BY (\`|\")spree_variants(\`|\").position ASC/)
+ end
+ end
+
+ context 'with master variant' do
+ it 'sorts variants by position' do
+ expect(product.variants_including_master.to_sql).to match(/ORDER BY (\`|\")spree_variants(\`|\").position ASC/)
+ end
+ end
+ end
+
+ context "has stock movements" do
+ let(:product) { create(:product) }
+ let(:variant) { product.master }
+ let(:stock_item) { variant.stock_items.first }
+
+ it "doesnt raise ReadOnlyRecord error" do
+ Spree::StockMovement.create!(stock_item: stock_item, quantity: 1)
+ expect { product.destroy }.not_to raise_error
+ end
+ end
+
+ # Regression test for #3737
+ context "has stock items" do
+ let(:product) { create(:product) }
+ it "can retrieve stock items" do
+ expect(product.master.stock_items.first).not_to be_nil
+ expect(product.stock_items.first).not_to be_nil
+ end
+ end
+
+ context "slugs" do
+
+ it "normalizes slug on update validation" do
+ product.slug = "hey//joe"
+ product.valid?
+ expect(product.slug).not_to match "/"
+ end
+
+ context "when product destroyed" do
+
+ it "renames slug" do
+ expect { product.destroy }.to change { product.slug }
+ end
+
+ context "when slug is already at or near max length" do
+
+ before do
+ product.slug = "x" * 255
+ product.save!
+ end
+
+ it "truncates renamed slug to ensure it remains within length limit" do
+ product.destroy
+ expect(product.slug.length).to eq 255
+ end
+
+ end
+
+ end
+
+ it "validates slug uniqueness" do
+ existing_product = product
+ new_product = create(:product)
+ new_product.slug = existing_product.slug
+
+ expect(new_product.valid?).to eq false
+ end
+
+ it "falls back to 'name-sku' for slug if regular name-based slug already in use" do
+ product1 = build(:product)
+ product1.name = "test"
+ product1.sku = "123"
+ product1.save!
+
+ product2 = build(:product)
+ product2.name = "test"
+ product2.sku = "456"
+ product2.save!
+
+ expect(product2.slug).to eq 'test-456'
+ end
+ end
+
+ context 'history' do
+ before(:each) do
+ @product = create(:product)
+ end
+
+ it 'should keep the history when the product is destroyed' do
+ @product.destroy
+
+ expect(@product.slugs.with_deleted).to_not be_empty
+ end
+
+ it 'should update the history when the product is restored' do
+ @product.destroy
+
+ @product.restore(recursive: true)
+
+ latest_slug = @product.slugs.find_by slug: @product.slug
+ expect(latest_slug).to_not be_nil
+ end
+ end
+ end
+
+ context "properties" do
+ let(:product) { create(:product) }
+
+ it "should properly assign properties" do
+ product.set_property('the_prop', 'value1')
+ expect(product.property('the_prop')).to eq('value1')
+
+ product.set_property('the_prop', 'value2')
+ expect(product.property('the_prop')).to eq('value2')
+ end
+
+ it "should not create duplicate properties when set_property is called" do
+ expect {
+ product.set_property('the_prop', 'value2')
+ product.save
+ product.reload
+ }.not_to change(product.properties, :length)
+
+ expect {
+ product.set_property('the_prop_new', 'value')
+ product.save
+ product.reload
+ expect(product.property('the_prop_new')).to eq('value')
+ }.to change { product.properties.length }.by(1)
+ end
+
+ # Regression test for #2455
+ it "should not overwrite properties' presentation names" do
+ Spree::Property.where(:name => 'foo').first_or_create!(:presentation => "Foo's Presentation Name")
+ product.set_property('foo', 'value1')
+ product.set_property('bar', 'value2')
+ expect(Spree::Property.where(:name => 'foo').first.presentation).to eq("Foo's Presentation Name")
+ expect(Spree::Property.where(:name => 'bar').first.presentation).to eq("bar")
+ end
+
+ # Regression test for #4416
+ context "#possible_promotions" do
+ let!(:promotion) do
+ create(:promotion, advertise: true, starts_at: 1.day.ago)
+ end
+ let!(:rule) do
+ Spree::Promotion::Rules::Product.create(
+ promotion: promotion,
+ products: [product]
+ )
+ end
+
+ it "lists the promotion as a possible promotion" do
+ expect(product.possible_promotions).to include(promotion)
+ end
+ end
+ end
+
+ context '#create' do
+ let!(:prototype) { create(:prototype) }
+ let!(:product) { Spree::Product.new(name: "Foo", price: 1.99, shipping_category_id: create(:shipping_category).id) }
+
+ before { product.prototype_id = prototype.id }
+
+ context "when prototype is supplied" do
+ it "should create properties based on the prototype" do
+ product.save
+ expect(product.properties.count).to eq(1)
+ end
+ end
+
+ context "when prototype with option types is supplied" do
+ def build_option_type_with_values(name, values)
+ ot = create(:option_type, :name => name)
+ values.each do |val|
+ ot.option_values.create(:name => val.downcase, :presentation => val)
+ end
+ ot
+ end
+
+ let(:prototype) do
+ size = build_option_type_with_values("size", %w(Small Medium Large))
+ create(:prototype, :name => "Size", :option_types => [ size ])
+ end
+
+ let(:option_values_hash) do
+ hash = {}
+ prototype.option_types.each do |i|
+ hash[i.id.to_s] = i.option_value_ids
+ end
+ hash
+ end
+
+ it "should create option types based on the prototype" do
+ product.save
+ expect(product.option_type_ids.length).to eq(1)
+ expect(product.option_type_ids).to eq(prototype.option_type_ids)
+ end
+
+ it "should create product option types based on the prototype" do
+ product.save
+ expect(product.product_option_types.pluck(:option_type_id)).to eq(prototype.option_type_ids)
+ end
+
+ it "should create variants from an option values hash with one option type" do
+ product.option_values_hash = option_values_hash
+ product.save
+ expect(product.variants.length).to eq(3)
+ end
+
+ it "should still create variants when option_values_hash is given but prototype id is nil" do
+ product.option_values_hash = option_values_hash
+ product.prototype_id = nil
+ product.save
+ expect(product.option_type_ids.length).to eq(1)
+ expect(product.option_type_ids).to eq(prototype.option_type_ids)
+ expect(product.variants.length).to eq(3)
+ end
+
+ it "should create variants from an option values hash with multiple option types" do
+ color = build_option_type_with_values("color", %w(Red Green Blue))
+ logo = build_option_type_with_values("logo", %w(Ruby Rails Nginx))
+ option_values_hash[color.id.to_s] = color.option_value_ids
+ option_values_hash[logo.id.to_s] = logo.option_value_ids
+ product.option_values_hash = option_values_hash
+ product.save
+ product.reload
+ expect(product.option_type_ids.length).to eq(3)
+ expect(product.variants.length).to eq(27)
+ end
+ end
+ end
+
+ context "#images" do
+ let(:product) { create(:product) }
+ let(:image) { File.open(File.expand_path('../../../fixtures/thinking-cat.jpg', __FILE__)) }
+ let(:params) { {:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :attachment => image, :alt => "position 2", :position => 2} }
+
+ before do
+ Spree::Image.create(params)
+ Spree::Image.create(params.merge({:alt => "position 1", :position => 1}))
+ Spree::Image.create(params.merge({:viewable_type => 'ThirdParty::Extension', :alt => "position 1", :position => 2}))
+ end
+
+ it "only looks for variant images" do
+ expect(product.images.size).to eq(2)
+ end
+
+ it "should be sorted by position" do
+ expect(product.images.pluck(:alt)).to eq(["position 1", "position 2"])
+ end
+ end
+
+ # Regression tests for #2352
+ context "classifications and taxons" do
+ it "is joined through classifications" do
+ reflection = Spree::Product.reflect_on_association(:taxons)
+ expect(reflection.options[:through]).to eq(:classifications)
+ end
+
+ it "will delete all classifications" do
+ reflection = Spree::Product.reflect_on_association(:classifications)
+ expect(reflection.options[:dependent]).to eq(:delete_all)
+ end
+ end
+
+ context '#total_on_hand' do
+ it 'should be infinite if track_inventory_levels is false' do
+ Spree::Config[:track_inventory_levels] = false
+ expect(build(:product, :variants_including_master => [build(:master_variant)]).total_on_hand).to eql(Float::INFINITY)
+ end
+
+ it 'should be infinite if variant is on demand' do
+ Spree::Config[:track_inventory_levels] = true
+ expect(build(:product, :variants_including_master => [build(:on_demand_master_variant)]).total_on_hand).to eql(Float::INFINITY)
+ end
+
+ it 'should return sum of stock items count_on_hand' do
+ product = create(:product)
+ product.stock_items.first.set_count_on_hand 5
+ product.variants_including_master(true) # force load association
+ expect(product.total_on_hand).to eql(5)
+ end
+
+ it 'should return sum of stock items count_on_hand when variants_including_master is not loaded' do
+ product = create(:product)
+ product.stock_items.first.set_count_on_hand 5
+ expect(product.reload.total_on_hand).to eql(5)
+ end
+ end
+
+ # Regression spec for https://github.com/spree/spree/issues/5588
+ context '#validate_master when duplicate SKUs entered' do
+ let!(:first_product) { create(:product, sku: 'a-sku') }
+ let(:second_product) { build(:product, sku: 'a-sku') }
+
+ subject { second_product }
+ it { is_expected.to be_invalid }
+ end
+
+ it "initializes a master variant when building a product" do
+ product = Spree::Product.new
+ expect(product.master.is_master).to be true
+ end
+end
diff --git a/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb b/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb
new file mode 100644
index 00000000000..c5016416b34
--- /dev/null
+++ b/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Actions::CreateAdjustment, type: :model do
+ let(:order) { create(:order_with_line_items, line_items_count: 1) }
+ let(:promotion) { create(:promotion) }
+ let(:action) { Spree::Promotion::Actions::CreateAdjustment.new }
+ let(:payload) { { order: order } }
+
+ # From promotion spec:
+ context "#perform" do
+ before do
+ action.calculator = Spree::Calculator::FlatRate.new(preferred_amount: 10)
+ promotion.promotion_actions = [action]
+ allow(action).to receive_messages(promotion: promotion)
+ end
+
+ # Regression test for #3966
+ it "does not apply an adjustment if the amount is 0" do
+ action.calculator.preferred_amount = 0
+ action.perform(payload)
+ expect(promotion.credits_count).to eq(0)
+ expect(order.adjustments.count).to eq(0)
+ end
+
+ it "should create a discount with correct negative amount" do
+ order.shipments.create!(cost: 10, stock_location: create(:stock_location))
+
+ action.perform(payload)
+ expect(promotion.credits_count).to eq(1)
+ expect(order.adjustments.count).to eq(1)
+ expect(order.adjustments.first.amount.to_i).to eq(-10)
+ end
+
+ it "should create a discount accessible through both order_id and adjustable_id" do
+ action.perform(payload)
+ expect(order.adjustments.count).to eq(1)
+ expect(order.all_adjustments.count).to eq(1)
+ end
+
+ it "should not create a discount when order already has one from this promotion" do
+ order.shipments.create!(cost: 10, stock_location: create(:stock_location))
+
+ action.perform(payload)
+ action.perform(payload)
+ expect(promotion.credits_count).to eq(1)
+ end
+ end
+
+ context "#destroy" do
+ before(:each) do
+ action.calculator = Spree::Calculator::FlatRate.new(preferred_amount: 10)
+ promotion.promotion_actions = [action]
+ end
+
+ context "when order is not complete" do
+ it "should not keep the adjustment" do
+ action.perform(payload)
+ action.destroy
+ expect(order.adjustments.count).to eq(0)
+ end
+ end
+
+ context "when order is complete" do
+ let(:order) do
+ create(:completed_order_with_totals, line_items_count: 1)
+ end
+
+ before(:each) do
+ action.perform(payload)
+ action.destroy
+ end
+
+ it "should keep the adjustment" do
+ expect(order.adjustments.count).to eq(1)
+ end
+
+ it "should nullify the adjustment source" do
+ expect(order.adjustments.reload.first.source).to be_nil
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb b/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb
new file mode 100644
index 00000000000..b8e6da0f3d1
--- /dev/null
+++ b/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+module Spree
+ class Promotion
+ module Actions
+ describe CreateItemAdjustments, :type => :model do
+ let(:order) { create(:order) }
+ let(:promotion) { create(:promotion) }
+ let(:action) { CreateItemAdjustments.new }
+ let!(:line_item) { create(:line_item, :order => order) }
+ let(:payload) { { order: order, promotion: promotion } }
+
+ before do
+ allow(action).to receive(:promotion).and_return(promotion)
+ promotion.promotion_actions = [action]
+ end
+
+ context "#perform" do
+ # Regression test for #3966
+ context "when calculator computes 0" do
+ before do
+ allow(action).to receive_messages :compute_amount => 0
+ end
+
+ it "does not create an adjustment when calculator returns 0" do
+ action.perform(payload)
+ expect(action.adjustments).to be_empty
+ end
+ end
+
+ context "when calculator returns a non-zero value" do
+ before do
+ promotion.promotion_actions = [action]
+ allow(action).to receive_messages :compute_amount => 10
+ end
+
+ it "creates adjustment with item as adjustable" do
+ action.perform(payload)
+ expect(action.adjustments.count).to eq(1)
+ expect(line_item.reload.adjustments).to eq(action.adjustments)
+ end
+
+ it "creates adjustment with self as source" do
+ action.perform(payload)
+ expect(line_item.reload.adjustments.first.source).to eq action
+ end
+
+ it "does not perform twice on the same item" do
+ 2.times { action.perform(payload) }
+ expect(action.adjustments.count).to eq(1)
+ end
+
+ context "with products rules" do
+ let!(:second_line_item) { create(:line_item, :order => order) }
+ let(:rule) { double Spree::Promotion::Rules::Product }
+
+ before do
+ allow(promotion).to receive(:eligible_rules) { [rule] }
+ allow(rule).to receive(:actionable?).and_return(true, false)
+ end
+
+ it "does not create adjustments for line_items not in product rule" do
+ action.perform(payload)
+ expect(action.adjustments.count).to eql 1
+ expect(line_item.reload.adjustments).to match_array action.adjustments
+ expect(second_line_item.reload.adjustments).to be_empty
+ end
+ end
+ end
+ end
+
+ context "#compute_amount" do
+ before { promotion.promotion_actions = [action] }
+
+ context "when the adjustable is actionable" do
+ it "calls compute on the calculator" do
+ expect(action.calculator).to receive(:compute).with(line_item)
+ action.compute_amount(line_item)
+ end
+
+ context "calculator returns amount greater than item total" do
+ before do
+ expect(action.calculator).to receive(:compute).with(line_item).and_return(300)
+ allow(line_item).to receive_messages(amount: 100)
+ end
+
+ it "does not exceed it" do
+ expect(action.compute_amount(line_item)).to eql(-100)
+ end
+ end
+ end
+
+ context "when the adjustable is not actionable" do
+ before { allow(promotion).to receive(:line_item_actionable?) { false } }
+
+ it 'returns 0' do
+ expect(action.compute_amount(line_item)).to eql(0)
+ end
+ end
+ end
+
+ context "#destroy" do
+ let!(:action) { CreateItemAdjustments.create! }
+ let(:other_action) { CreateItemAdjustments.create! }
+ before { promotion.promotion_actions = [other_action] }
+
+ it "destroys adjustments for incompleted orders" do
+ order = Order.create
+ action.adjustments.create!(label: "Check", amount: 0, order: order, adjustable: order)
+
+ expect {
+ action.destroy
+ }.to change { Adjustment.count }.by(-1)
+ end
+
+ it "nullifies adjustments for completed orders" do
+ order = Order.create(completed_at: Time.now)
+ adjustment = action.adjustments.create!(label: "Check", amount: 0, order: order, adjustable: order)
+
+ expect {
+ action.destroy
+ }.to change { adjustment.reload.source_id }.from(action.id).to nil
+ end
+
+ it "doesnt mess with unrelated adjustments" do
+ other_action.adjustments.create!(label: "Check", amount: 0, order: order, adjustable: order)
+
+ expect {
+ action.destroy
+ }.not_to change { other_action.adjustments.count }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/actions/create_line_items_spec.rb b/core/spec/models/spree/promotion/actions/create_line_items_spec.rb
new file mode 100644
index 00000000000..7c9f7f5b09a
--- /dev/null
+++ b/core/spec/models/spree/promotion/actions/create_line_items_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Actions::CreateLineItems, type: :model do
+ let(:order) { create(:order) }
+ let(:action) { Spree::Promotion::Actions::CreateLineItems.create }
+ let(:promotion) { stub_model(Spree::Promotion) }
+ let(:shirt) { create(:variant) }
+ let(:mug) { create(:variant) }
+ let(:payload) { { order: order } }
+
+ def empty_stock(variant)
+ variant.stock_items.update_all(backorderable: false)
+ variant.stock_items.each(&:reduce_count_on_hand_to_zero)
+ end
+
+ context "#perform" do
+ before do
+ allow(action).to receive_messages promotion: promotion
+ action.promotion_action_line_items.create!(
+ variant: mug,
+ quantity: 1
+ )
+ action.promotion_action_line_items.create!(
+ variant: shirt,
+ quantity: 2
+ )
+ end
+
+ context "order is eligible" do
+ before do
+ allow(promotion).to receive_messages eligible: true
+ end
+
+ it "adds line items to order with correct variant and quantity" do
+ action.perform(payload)
+ expect(order.line_items.count).to eq(2)
+ line_item = order.line_items.find_by_variant_id(mug.id)
+ expect(line_item).not_to be_nil
+ expect(line_item.quantity).to eq(1)
+ end
+
+ it "only adds the delta of quantity to an order" do
+ order.contents.add(shirt, 1)
+ action.perform(payload)
+ line_item = order.line_items.find_by_variant_id(shirt.id)
+ expect(line_item).not_to be_nil
+ expect(line_item.quantity).to eq(2)
+ end
+
+ it "doesn't add if the quantity is greater" do
+ order.contents.add(shirt, 3)
+ action.perform(payload)
+ line_item = order.line_items.find_by_variant_id(shirt.id)
+ expect(line_item).not_to be_nil
+ expect(line_item.quantity).to eq(3)
+ end
+
+ it "doesn't try to add an item if it's out of stock" do
+ empty_stock(mug)
+ empty_stock(shirt)
+
+ expect(order.contents).to_not receive(:add)
+ action.perform(order: order)
+ end
+ end
+ end
+
+ describe "#item_available?" do
+ let(:item_out_of_stock) do
+ action.promotion_action_line_items.create!(variant: mug, quantity: 1)
+ end
+
+ let(:item_in_stock) do
+ action.promotion_action_line_items.create!(variant: shirt, quantity: 1)
+ end
+
+ it "returns false if the item is out of stock" do
+ empty_stock(mug)
+ expect(action.item_available? item_out_of_stock).to be false
+ end
+
+ it "returns true if the item is in stock" do
+ expect(action.item_available? item_in_stock).to be true
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/actions/free_shipping_spec.rb b/core/spec/models/spree/promotion/actions/free_shipping_spec.rb
new file mode 100644
index 00000000000..4234dba2dce
--- /dev/null
+++ b/core/spec/models/spree/promotion/actions/free_shipping_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Actions::FreeShipping, :type => :model do
+ let(:order) { create(:completed_order_with_totals) }
+ let(:promotion) { create(:promotion) }
+ let(:action) { Spree::Promotion::Actions::FreeShipping.create }
+ let(:payload) { { order: order } }
+
+ # From promotion spec:
+ context "#perform" do
+ before do
+ order.shipments << create(:shipment)
+ promotion.promotion_actions << action
+ end
+
+ it "should create a discount with correct negative amount" do
+ expect(order.shipments.count).to eq(2)
+ expect(order.shipments.first.cost).to eq(100)
+ expect(order.shipments.last.cost).to eq(100)
+ expect(action.perform(payload)).to be true
+ expect(promotion.credits_count).to eq(2)
+ expect(order.shipment_adjustments.count).to eq(2)
+ expect(order.shipment_adjustments.first.amount.to_i).to eq(-100)
+ expect(order.shipment_adjustments.last.amount.to_i).to eq(-100)
+ end
+
+ it "should not create a discount when order already has one from this promotion" do
+ expect(action.perform(payload)).to be true
+ expect(action.perform(payload)).to be false
+ expect(promotion.credits_count).to eq(2)
+ expect(order.shipment_adjustments.count).to eq(2)
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/first_order_spec.rb b/core/spec/models/spree/promotion/rules/first_order_spec.rb
new file mode 100644
index 00000000000..e2c58816b06
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/first_order_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::FirstOrder, :type => :model do
+ let(:rule) { Spree::Promotion::Rules::FirstOrder.new }
+ let(:order) { mock_model(Spree::Order, :user => nil, :email => nil) }
+ let(:user) { mock_model(Spree::LegacyUser) }
+
+ context "without a user or email" do
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to login or provide your email before applying this coupon code."
+ end
+ end
+
+ context "first order" do
+ context "for a signed user" do
+ context "with no completed orders" do
+ before(:each) do
+ allow(user).to receive_message_chain(:orders, :complete => [])
+ end
+
+ specify do
+ allow(order).to receive_messages(:user => user)
+ expect(rule).to be_eligible(order)
+ end
+
+ it "should be eligible when user passed in payload data" do
+ expect(rule).to be_eligible(order, :user => user)
+ end
+ end
+
+ context "with completed orders" do
+ before(:each) do
+ allow(order).to receive_messages(:user => user)
+ end
+
+ it "should be eligible when checked against first completed order" do
+ allow(user).to receive_message_chain(:orders, :complete => [order])
+ expect(rule).to be_eligible(order)
+ end
+
+ context "with another order" do
+ before { allow(user).to receive_message_chain(:orders, :complete => [mock_model(Spree::Order)]) }
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can only be applied to your first order."
+ end
+ end
+ end
+ end
+
+ context "for a guest user" do
+ let(:email) { 'user@spreecommerce.com' }
+ before { allow(order).to receive_messages :email => 'user@spreecommerce.com' }
+
+ context "with no other orders" do
+ it { expect(rule).to be_eligible(order) }
+ end
+
+ context "with another order" do
+ before { allow(rule).to receive_messages(:orders_by_email => [mock_model(Spree::Order)]) }
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can only be applied to your first order."
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/item_total_spec.rb b/core/spec/models/spree/promotion/rules/item_total_spec.rb
new file mode 100644
index 00000000000..a230452badd
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/item_total_spec.rb
@@ -0,0 +1,282 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::ItemTotal, :type => :model do
+ let(:rule) { Spree::Promotion::Rules::ItemTotal.new }
+ let(:order) { double(:order) }
+
+ before { rule.preferred_amount_min = 50 }
+ before { rule.preferred_amount_max = 60 }
+
+ context "preferred operator_min set to gt and preferred operator_max set to lt" do
+ before do
+ rule.preferred_operator_min = 'gt'
+ rule.preferred_operator_max = 'lt'
+ end
+
+ context "and item total is lower than prefered maximum amount" do
+
+ context "and item total is higher than prefered minimum amount" do
+ it "should be eligible" do
+ allow(order).to receive_messages item_total: 51
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is equal to the prefered minimum amount" do
+
+ before { allow(order).to receive_messages item_total: 50 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than or equal to $50.00."
+ end
+ end
+
+ context "and item total is lower to the prefered minimum amount" do
+ before { allow(order).to receive_messages item_total: 49 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than or equal to $50.00."
+ end
+ end
+ end
+
+ context "and item total is equal to the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 60 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+
+ context "and item total is higher than the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 61 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+
+ end
+
+ context "preferred operator set to gt and preferred operator_max set to lte" do
+ before do
+ rule.preferred_operator_min = 'gt'
+ rule.preferred_operator_max = 'lte'
+ end
+
+ context "and item total is lower than prefered maximum amount" do
+
+ context "and item total is higher than prefered minimum amount" do
+ it "should be eligible" do
+ allow(order).to receive_messages item_total: 51
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is equal to the prefered minimum amount" do
+
+ before { allow(order).to receive_messages item_total: 50 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than or equal to $50.00."
+ end
+ end
+
+ context "and item total is lower to the prefered minimum amount" do
+ before { allow(order).to receive_messages item_total: 49 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than or equal to $50.00."
+ end
+ end
+ end
+
+ context "and item total is equal to the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 60 }
+
+ it "should not be eligible" do
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is higher than the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 61 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+ end
+
+ context "preferred operator set to gte and preferred operator_max set to lt" do
+ before do
+ rule.preferred_operator_min = 'gte'
+ rule.preferred_operator_max = 'lt'
+ end
+
+ context "and item total is lower than prefered maximum amount" do
+
+ context "and item total is higher than prefered minimum amount" do
+ it "should be eligible" do
+ allow(order).to receive_messages item_total: 51
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is equal to the prefered minimum amount" do
+
+ before { allow(order).to receive_messages item_total: 50 }
+
+ it "should not be eligible" do
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is lower to the prefered minimum amount" do
+ before { allow(order).to receive_messages item_total: 49 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than $50.00."
+ end
+ end
+ end
+
+ context "and item total is equal to the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 60 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+
+ context "and item total is higher than the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 61 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+
+ end
+
+ context "preferred operator set to gte and preferred operator_max set to lte" do
+ before do
+ rule.preferred_operator_min = 'gte'
+ rule.preferred_operator_max = 'lte'
+ end
+
+ context "and item total is lower than prefered maximum amount" do
+ context "and item total is higher than prefered minimum amount" do
+ it "should be eligible" do
+ allow(order).to receive_messages item_total: 51
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is equal to the prefered minimum amount" do
+
+ before { allow(order).to receive_messages item_total: 50 }
+
+ it "should not be eligible" do
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is lower to the prefered minimum amount" do
+ before { allow(order).to receive_messages item_total: 49 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders less than $50.00."
+ end
+ end
+ end
+
+ context "and item total is equal to the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 60 }
+
+ it "should not be eligible" do
+ expect(rule).to be_eligible(order)
+ end
+ end
+
+ context "and item total is higher than the prefered maximum amount" do
+ before { allow(order).to receive_messages item_total: 61 }
+
+ it "should not be eligible" do
+ expect(rule).to_not be_eligible(order)
+ end
+
+ it "set an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied to orders higher than $60.00."
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/one_use_per_user_spec.rb b/core/spec/models/spree/promotion/rules/one_use_per_user_spec.rb
new file mode 100644
index 00000000000..d280919071f
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/one_use_per_user_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::OneUsePerUser, :type => :model do
+ let(:rule) { described_class.new }
+
+ describe '#eligible?(order)' do
+ subject { rule.eligible?(order) }
+ let(:order) { double Spree::Order, user: user }
+ let(:user) { double Spree::LegacyUser }
+ let(:promotion) { stub_model Spree::Promotion, used_by?: used_by }
+ let(:used_by) { false }
+
+ before { rule.promotion = promotion }
+
+ context 'when the order is assigned to a user' do
+ context 'when the user has used this promotion before' do
+ let(:used_by) { true }
+
+ it { is_expected.to be false }
+ it "sets an error message" do
+ subject
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can only be used once per user."
+ end
+ end
+
+ context 'when the user has not used this promotion before' do
+ it { is_expected.to be true }
+ end
+ end
+
+ context 'when the order is not assigned to a user' do
+ let(:user) { nil }
+ it { is_expected.to be false }
+ it "sets an error message" do
+ subject
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to login before applying this coupon code."
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/product_spec.rb b/core/spec/models/spree/promotion/rules/product_spec.rb
new file mode 100644
index 00000000000..e2b4e7324dc
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/product_spec.rb
@@ -0,0 +1,143 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::Product, :type => :model do
+ let(:rule) { Spree::Promotion::Rules::Product.new(rule_options) }
+ let(:rule_options) { {} }
+
+ context "#eligible?(order)" do
+ let(:order) { Spree::Order.new }
+
+ it "should be eligible if there are no products" do
+ allow(rule).to receive_messages(:eligible_products => [])
+ expect(rule).to be_eligible(order)
+ end
+
+ before do
+ 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) }
+ end
+
+ context "with 'any' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'any') }
+
+ it "should be eligible if any of the products is in eligible products" do
+ allow(order).to receive_messages(:products => [@product1, @product2])
+ allow(rule).to receive_messages(:eligible_products => [@product2, @product3])
+ expect(rule).to be_eligible(order)
+ end
+
+ context "when none of the products are eligible products" do
+ before do
+ allow(order).to receive_messages(products: [@product1])
+ allow(rule).to receive_messages(eligible_products: [@product2, @product3])
+ end
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to add an applicable product before applying this coupon code."
+ end
+ end
+ end
+
+ context "with 'all' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'all') }
+
+ it "should be eligible if all of the eligible products are ordered" do
+ allow(order).to receive_messages(:products => [@product3, @product2, @product1])
+ allow(rule).to receive_messages(:eligible_products => [@product2, @product3])
+ expect(rule).to be_eligible(order)
+ end
+
+ context "when any of the eligible products is not ordered" do
+ before do
+ allow(order).to receive_messages(products: [@product1, @product2])
+ allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3])
+ end
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart."
+ end
+ end
+ end
+
+ context "with 'none' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'none') }
+
+ it "should be eligible if none of the order's products are in eligible products" do
+ allow(order).to receive_messages(:products => [@product1])
+ allow(rule).to receive_messages(:eligible_products => [@product2, @product3])
+ expect(rule).to be_eligible(order)
+ end
+
+ context "when any of the order's products are in eligible products" do
+ before do
+ allow(order).to receive_messages(products: [@product1, @product2])
+ allow(rule).to receive_messages(eligible_products: [@product2, @product3])
+ end
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "Your cart contains a product that prevents this coupon code from being applied."
+ end
+ end
+ end
+ end
+
+ describe '#actionable?' do
+ subject do
+ rule.actionable?(line_item)
+ end
+
+ let(:rule_line_item) { Spree::LineItem.new(product: rule_product) }
+ let(:other_line_item) { Spree::LineItem.new(product: other_product) }
+
+ let(:rule_options) { super().merge(products: [rule_product]) }
+ let(:rule_product) { mock_model(Spree::Product) }
+ let(:other_product) { mock_model(Spree::Product) }
+
+ context "with 'any' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'any') }
+
+ context 'for product in rule' do
+ let(:line_item) { rule_line_item }
+ it { should be_truthy }
+ end
+
+ context 'for product not in rule' do
+ let(:line_item) { other_line_item }
+ it { should be_falsey }
+ end
+ end
+
+ context "with 'all' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'all') }
+
+ context 'for product in rule' do
+ let(:line_item) { rule_line_item }
+ it { should be_truthy }
+ end
+
+ context 'for product not in rule' do
+ let(:line_item) { other_line_item }
+ it { should be_falsey }
+ end
+ end
+
+ context "with 'none' match policy" do
+ let(:rule_options) { super().merge(preferred_match_policy: 'none') }
+
+ context 'for product in rule' do
+ let(:line_item) { rule_line_item }
+ it { should be_falsey }
+ end
+
+ context 'for product not in rule' do
+ let(:line_item) { other_line_item }
+ it { should be_truthy }
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/taxon_spec.rb b/core/spec/models/spree/promotion/rules/taxon_spec.rb
new file mode 100644
index 00000000000..0fce153f12f
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/taxon_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::Taxon, :type => :model do
+ let(:rule){ subject }
+
+ context '#elegible?(order)' do
+ let(:taxon){ create :taxon, name: 'first' }
+ let(:taxon2){ create :taxon, name: 'second'}
+ let(:order){ create :order_with_line_items }
+
+ before do
+ rule.save
+ end
+
+ context 'with any match policy' do
+ before do
+ rule.preferred_match_policy = 'any'
+ end
+
+ it 'is eligible if order does has any prefered taxon' do
+ order.products.first.taxons << taxon
+ rule.taxons << taxon
+ expect(rule).to be_eligible(order)
+ end
+
+ context 'when order contains items from different taxons' do
+ before do
+ order.products.first.taxons << taxon
+ rule.taxons << taxon
+ end
+
+ it 'should act on a product within the eligible taxon' do
+ expect(rule).to be_actionable(order.line_items.last)
+ end
+
+ it 'should not act on a product in another taxon' do
+ order.line_items << create(:line_item, product: create(:product, taxons: [taxon2]))
+ expect(rule).not_to be_actionable(order.line_items.last)
+ end
+ end
+
+ context "when order does not have any prefered taxon" do
+ before { rule.taxons << taxon2 }
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to add a product from an applicable category before applying this coupon code."
+ end
+ end
+
+ context 'when a product has a taxon child of a taxon rule' do
+ before do
+ taxon.children << taxon2
+ order.products.first.taxons << taxon2
+ rule.taxons << taxon2
+ end
+
+ it{ expect(rule).to be_eligible(order) }
+ end
+ end
+
+ context 'with all match policy' do
+ before do
+ rule.preferred_match_policy = 'all'
+ end
+
+ it 'is eligible order has all prefered taxons' do
+ order.products.first.taxons << taxon2
+ order.products.last.taxons << taxon
+
+ rule.taxons = [taxon, taxon2]
+
+ expect(rule).to be_eligible(order)
+ end
+
+ context "when order does not have all prefered taxons" do
+ before { rule.taxons << taxon }
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to add a product from all applicable categories before applying this coupon code."
+ end
+ end
+
+ context 'when a product has a taxon child of a taxon rule' do
+ let(:taxon3){ create :taxon }
+
+ before do
+ taxon.children << taxon2
+ order.products.first.taxons << taxon2
+ order.products.last.taxons << taxon3
+ rule.taxons << taxon2
+ rule.taxons << taxon3
+ end
+
+ it{ expect(rule).to be_eligible(order) }
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion/rules/user_logged_in_spec.rb b/core/spec/models/spree/promotion/rules/user_logged_in_spec.rb
new file mode 100644
index 00000000000..d903eb72784
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/user_logged_in_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::UserLoggedIn, :type => :model do
+ let(:rule) { Spree::Promotion::Rules::UserLoggedIn.new }
+
+ context "#eligible?(order)" do
+ let(:order) { Spree::Order.new }
+
+ it "should be eligible if order has an associated user" do
+ user = double('User')
+ allow(order).to receive_messages(:user => user)
+
+ expect(rule).to be_eligible(order)
+ end
+
+ context "when user is not logged in" do
+ before { allow(order).to receive_messages(:user => nil) } # better to be explicit here
+ it { expect(rule).not_to be_eligible(order) }
+ it "sets an error message" do
+ rule.eligible?(order)
+ expect(rule.eligibility_errors.full_messages.first).
+ to eq "You need to login before applying this coupon code."
+ end
+ end
+ end
+end
+
diff --git a/core/spec/models/spree/promotion/rules/user_spec.rb b/core/spec/models/spree/promotion/rules/user_spec.rb
new file mode 100644
index 00000000000..8367dcada4d
--- /dev/null
+++ b/core/spec/models/spree/promotion/rules/user_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Spree::Promotion::Rules::User, :type => :model do
+ let(:rule) { Spree::Promotion::Rules::User.new }
+
+ context "#eligible?(order)" do
+ let(:order) { Spree::Order.new }
+
+ it "should not be eligible if users are not provided" do
+ expect(rule).not_to be_eligible(order)
+ end
+
+ it "should be eligible if users include user placing the order" do
+ user = mock_model(Spree::LegacyUser)
+ users = [user, mock_model(Spree::LegacyUser)]
+ allow(rule).to receive_messages(:users => users)
+ allow(order).to receive_messages(:user => user)
+
+ expect(rule).to be_eligible(order)
+ end
+
+ it "should not be eligible if user placing the order is not listed" do
+ allow(order).to receive_messages(:user => mock_model(Spree::LegacyUser))
+ users = [mock_model(Spree::LegacyUser), mock_model(Spree::LegacyUser)]
+ allow(rule).to receive_messages(:users => users)
+
+ expect(rule).not_to be_eligible(order)
+ end
+
+ # Regression test for #3885
+ it "can assign to user_ids" do
+ user1 = Spree::LegacyUser.create!(:email => "test1@example.com")
+ user2 = Spree::LegacyUser.create!(:email => "test2@example.com")
+ expect { rule.user_ids = "#{user1.id}, #{user2.id}" }.not_to raise_error
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion_action_spec.rb b/core/spec/models/spree/promotion_action_spec.rb
new file mode 100644
index 00000000000..09982dc89ea
--- /dev/null
+++ b/core/spec/models/spree/promotion_action_spec.rb
@@ -0,0 +1,10 @@
+require 'spec_helper'
+
+describe Spree::PromotionAction, :type => :model do
+
+ it "should force developer to implement 'perform' method" do
+ expect { MyAction.new.perform }.to raise_error
+ end
+
+end
+
diff --git a/core/spec/models/spree/promotion_category_spec.rb b/core/spec/models/spree/promotion_category_spec.rb
new file mode 100644
index 00000000000..e5efefbc762
--- /dev/null
+++ b/core/spec/models/spree/promotion_category_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Spree::PromotionCategory, :type => :model do
+ describe 'validation' do
+ let(:name) { 'Nom' }
+ subject { Spree::PromotionCategory.new name: name }
+
+ context 'when all required attributes are specified' do
+ it { is_expected.to be_valid }
+ end
+
+ context 'when name is missing' do
+ let(:name) { nil }
+ it { is_expected.not_to be_valid }
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion_handler/cart_spec.rb b/core/spec/models/spree/promotion_handler/cart_spec.rb
new file mode 100644
index 00000000000..57b0f850178
--- /dev/null
+++ b/core/spec/models/spree/promotion_handler/cart_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+module Spree
+ module PromotionHandler
+ describe Cart, :type => :model do
+ let(:line_item) { create(:line_item) }
+ let(:order) { line_item.order }
+
+ let(:promotion) { Promotion.create(name: "At line items") }
+ let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+
+ subject { Cart.new(order, line_item) }
+
+ context "activates in LineItem level" do
+ let!(:action) { Promotion::Actions::CreateItemAdjustments.create(promotion: promotion, calculator: calculator) }
+ let(:adjustable) { line_item }
+
+ shared_context "creates the adjustment" do
+ it "creates the adjustment" do
+ expect {
+ subject.activate
+ }.to change { adjustable.adjustments.count }.by(1)
+ end
+ end
+
+ context "promotion with no rules" do
+ include_context "creates the adjustment"
+ end
+
+ context "promotion includes item involved" do
+ let!(:rule) { Promotion::Rules::Product.create(products: [line_item.product], promotion: promotion) }
+
+ include_context "creates the adjustment"
+ end
+
+ context "promotion has item total rule" do
+ let(:shirt) { create(:product) }
+ let!(:rule) { Promotion::Rules::ItemTotal.create(preferred_operator_min: 'gt', preferred_amount_min: 50, preferred_operator_max: 'lt', preferred_amount_max: 150, promotion: promotion) }
+
+ before do
+ # Makes the order eligible for this promotion
+ order.item_total = 100
+ order.save
+ end
+
+ include_context "creates the adjustment"
+ end
+ end
+
+ context "activates in Order level" do
+ let!(:action) { Promotion::Actions::CreateAdjustment.create(promotion: promotion, calculator: calculator) }
+ let(:adjustable) { order }
+
+ shared_context "creates the adjustment" do
+ it "creates the adjustment" do
+ expect {
+ subject.activate
+ }.to change { adjustable.adjustments.count }.by(1)
+ end
+ end
+
+ context "promotion with no rules" do
+ before do
+ # Gives the calculator something to discount
+ order.item_total = 10
+ order.save
+ end
+
+ include_context "creates the adjustment"
+ end
+
+ context "promotion has item total rule" do
+ let(:shirt) { create(:product) }
+ let!(:rule) { Promotion::Rules::ItemTotal.create(preferred_operator_min: 'gt', preferred_amount_min: 50, preferred_operator_max: 'lt', preferred_amount_max: 150, promotion: promotion) }
+
+ before do
+ # Makes the order eligible for this promotion
+ order.item_total = 100
+ order.save
+ end
+
+ include_context "creates the adjustment"
+ end
+ end
+
+ context "activates promotions associated with the order" do
+ let(:promo) { create :promotion_with_item_adjustment, adjustment_rate: 5, code: 'promo' }
+ let(:adjustable) { line_item }
+
+ before do
+ order.promotions << promo
+ end
+
+ it "creates the adjustment" do
+ expect {
+ subject.activate
+ }.to change { adjustable.adjustments.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion_handler/coupon_spec.rb b/core/spec/models/spree/promotion_handler/coupon_spec.rb
new file mode 100644
index 00000000000..eb2e87d7bf4
--- /dev/null
+++ b/core/spec/models/spree/promotion_handler/coupon_spec.rb
@@ -0,0 +1,343 @@
+require 'spec_helper'
+
+module Spree
+ module PromotionHandler
+ describe Coupon, :type => :model do
+ let(:order) { double("Order", coupon_code: "10off").as_null_object }
+
+ subject { Coupon.new(order) }
+
+ it "returns self in apply" do
+ expect(subject.apply).to be_a Coupon
+ end
+
+ context 'status messages' do
+ let(:coupon) { Coupon.new(order) }
+
+ describe "#set_success_code" do
+ let(:status) { :coupon_code_applied }
+ subject { coupon.set_success_code status }
+
+ it 'should have status_code' do
+ subject
+ expect(coupon.status_code).to eq(status)
+ end
+
+ it 'should have success message' do
+ subject
+ expect(coupon.success).to eq(Spree.t(status))
+ end
+ end
+
+ describe "#set_error_code" do
+ let(:status) { :coupon_code_not_found }
+
+ subject { coupon.set_error_code status }
+
+ it 'should have status_code' do
+ subject
+ expect(coupon.status_code).to eq(status)
+ end
+
+ it 'should have error message' do
+ subject
+ expect(coupon.error).to eq(Spree.t(status))
+ end
+ end
+ end
+
+ context "coupon code promotion doesnt exist" do
+ before { Promotion.create name: "promo", :code => nil }
+
+ it "doesnt fetch any promotion" do
+ expect(subject.promotion).to be_blank
+ end
+
+ context "with no actions defined" do
+ before { Promotion.create name: "promo", :code => "10off" }
+
+ it "populates error message" do
+ subject.apply
+ expect(subject.error).to eq Spree.t(:coupon_code_not_found)
+ end
+ end
+ end
+
+ context "existing coupon code promotion" do
+ let!(:promotion) { Promotion.create name: "promo", :code => "10off" }
+ let!(:action) { Promotion::Actions::CreateItemAdjustments.create(promotion: promotion, calculator: calculator) }
+ let(:calculator) { Calculator::FlatRate.new(preferred_amount: 10) }
+
+ it "fetches with given code" do
+ expect(subject.promotion).to eq promotion
+ end
+
+ context "with a per-item adjustment action" do
+ let(:order) { create(:order_with_line_items, :line_items_count => 3) }
+
+ context "right coupon given" do
+ context "with correct coupon code casing" do
+ before { allow(order).to receive_messages :coupon_code => "10off" }
+
+ it "successfully activates promo" do
+ expect(order.total).to eq(130)
+ subject.apply
+ expect(subject.success).to be_present
+ order.line_items.each do |line_item|
+ expect(line_item.adjustments.count).to eq(1)
+ end
+ # Ensure that applying the adjustment actually affects the order's total!
+ expect(order.reload.total).to eq(100)
+ end
+
+ context "and first line item is not promotionable" do
+ before(:each) do
+ order.line_items.first.variant.product.update_attributes!(
+ promotionable: false
+ )
+ order.reload
+ end
+
+ it "successfully activates promo" do
+ expect(order.total).to eq(130)
+ subject.apply
+ expect(subject.success).to be_present
+ order.line_items.each do |line_item|
+ expect(line_item.adjustments.count).to eq(1)
+ end
+
+ expect(order.reload.total).to eq(110) # only 2 items
+ end
+ end
+
+ it "coupon already applied to the order" do
+ subject.apply
+ expect(subject.success).to be_present
+ subject.apply
+ expect(subject.error).to eq Spree.t(:coupon_code_already_applied)
+ end
+ end
+
+ # Regression test for #4211
+ context "with incorrect coupon code casing" do
+ before { allow(order).to receive_messages :coupon_code => "10OFF" }
+ it "successfully activates promo" do
+ expect(order.total).to eq(130)
+ subject.apply
+ expect(subject.success).to be_present
+ order.line_items.each do |line_item|
+ expect(line_item.adjustments.count).to eq(1)
+ end
+ # Ensure that applying the adjustment actually affects the order's total!
+ expect(order.reload.total).to eq(100)
+ end
+ end
+ end
+
+ context "coexists with a non coupon code promo" do
+ let!(:order) { Order.create }
+
+ before do
+ allow(order).to receive_messages :coupon_code => "10off"
+ calculator = Calculator::FlatRate.new(preferred_amount: 10)
+ general_promo = Promotion.create name: "General Promo"
+ general_action = Promotion::Actions::CreateItemAdjustments.create(promotion: general_promo, calculator: calculator)
+
+ order.contents.add create(:variant)
+ end
+
+ # regression spec for #4515
+ it "successfully activates promo" do
+ subject.apply
+ expect(subject).to be_successful
+ end
+ end
+ end
+
+ context "with a free-shipping adjustment action" do
+ let!(:action) { Promotion::Actions::FreeShipping.create(promotion: promotion) }
+ context "right coupon code given" do
+ let(:order) { create(:order_with_line_items, :line_items_count => 3) }
+
+ before { allow(order).to receive_messages :coupon_code => "10off" }
+
+ it "successfully activates promo" do
+ expect(order.total).to eq(130)
+ subject.apply
+ expect(subject.success).to be_present
+
+ expect(order.shipment_adjustments.count).to eq(1)
+ end
+
+ it "coupon already applied to the order" do
+ subject.apply
+ expect(subject.success).to be_present
+ subject.apply
+ expect(subject.error).to eq Spree.t(:coupon_code_already_applied)
+ end
+ end
+ end
+
+ context "with a whole-order adjustment action" do
+ let!(:action) { Promotion::Actions::CreateAdjustment.create(promotion: promotion, calculator: calculator) }
+ context "right coupon given" do
+ let(:order) { create(:order) }
+ let(:calculator) { Calculator::FlatRate.new(preferred_amount: 10) }
+
+ before do
+ allow(order).to receive_messages({
+ :coupon_code => "10off",
+ # These need to be here so that promotion adjustment "wins"
+ :item_total => 50,
+ :ship_total => 10
+ })
+ end
+
+ it "successfully activates promo" do
+ subject.apply
+ expect(subject.success).to be_present
+ expect(order.adjustments.count).to eq(1)
+ end
+
+ it "coupon already applied to the order" do
+ subject.apply
+ expect(subject.success).to be_present
+ subject.apply
+ expect(subject.error).to eq Spree.t(:coupon_code_already_applied)
+ end
+
+ it "coupon fails to activate" do
+ allow_any_instance_of(Spree::Promotion).to receive(:activate).and_return false
+ subject.apply
+ expect(subject.error).to eq Spree.t(:coupon_code_unknown_error)
+ end
+
+
+ it "coupon code hit max usage" do
+ promotion.update_column(:usage_limit, 1)
+ coupon = Coupon.new(order)
+ coupon.apply
+ expect(coupon.successful?).to be true
+
+ order_2 = create(:order)
+ allow(order_2).to receive_messages :coupon_code => "10off"
+ coupon = Coupon.new(order_2)
+ coupon.apply
+ expect(coupon.successful?).to be false
+ expect(coupon.error).to eq Spree.t(:coupon_code_max_usage)
+ end
+
+ context "when the a new coupon is less good" do
+ let!(:action_5) { Promotion::Actions::CreateAdjustment.create(promotion: promotion_5, calculator: calculator_5) }
+ let(:calculator_5) { Calculator::FlatRate.new(preferred_amount: 5) }
+ let!(:promotion_5) { Promotion.create name: "promo", :code => "5off" }
+
+ it 'notifies of better deal' do
+ subject.apply
+ allow(order).to receive_messages( { coupon_code: '5off' } )
+ coupon = Coupon.new(order).apply
+ expect(coupon.error).to eq Spree.t(:coupon_code_better_exists)
+ end
+ end
+ end
+ end
+
+ context "for an order with taxable line items" do
+ before(:each) do
+ @country = create(:country)
+ @zone = create(:zone, :name => "Country Zone", :default_tax => true, :zone_members => [])
+ @zone.zone_members.create(:zoneable => @country)
+ @category = Spree::TaxCategory.create :name => "Taxable Foo"
+ @rate1 = Spree::TaxRate.create(
+ :amount => 0.10,
+ :calculator => Spree::Calculator::DefaultTax.create,
+ :tax_category => @category,
+ :zone => @zone
+ )
+
+ @order = Spree::Order.create!
+ allow(@order).to receive_messages :coupon_code => "10off"
+ end
+ context "and the product price is less than promo discount" do
+ before(:each) do
+ 3.times do |i|
+ taxable = create(:product, :tax_category => @category, :price => 9.0)
+ @order.contents.add(taxable.master, 1)
+ end
+ end
+ it "successfully applies the promo" do
+ # 3 * (9 + 0.9)
+ expect(@order.total).to eq(29.7)
+ coupon = Coupon.new(@order)
+ coupon.apply
+ expect(coupon.success).to be_present
+ # 3 * ((9 - [9,10].min) + 0)
+ expect(@order.reload.total).to eq(0)
+ expect(@order.additional_tax_total).to eq(0)
+ end
+ end
+ context "and the product price is greater than promo discount" do
+ before(:each) do
+ 3.times do |i|
+ taxable = create(:product, :tax_category => @category, :price => 11.0)
+ @order.contents.add(taxable.master, 2)
+ end
+ end
+ it "successfully applies the promo" do
+ # 3 * (22 + 2.2)
+ expect(@order.total.to_f).to eq(72.6)
+ coupon = Coupon.new(@order)
+ coupon.apply
+ expect(coupon.success).to be_present
+ # 3 * ( (22 - 10) + 1.2)
+ expect(@order.reload.total).to eq(39.6)
+ expect(@order.additional_tax_total).to eq(3.6)
+ end
+ end
+ context "and multiple quantity per line item" do
+ before(:each) do
+ twnty_off = Promotion.create name: "promo", :code => "20off"
+ twnty_off_calc = Calculator::FlatRate.new(preferred_amount: 20)
+ Promotion::Actions::CreateItemAdjustments.create(promotion: twnty_off,
+ calculator: twnty_off_calc)
+
+ allow(@order).to receive(:coupon_code).and_call_original
+ allow(@order).to receive_messages :coupon_code => "20off"
+ 3.times do |i|
+ taxable = create(:product, :tax_category => @category, :price => 10.0)
+ @order.contents.add(taxable.master, 2)
+ end
+ end
+ it "successfully applies the promo" do
+ # 3 * ((2 * 10) + 2.0)
+ expect(@order.total.to_f).to eq(66)
+ coupon = Coupon.new(@order)
+ coupon.apply
+ expect(coupon.success).to be_present
+ # 0
+ expect(@order.reload.total).to eq(0)
+ expect(@order.additional_tax_total).to eq(0)
+ end
+ end
+ end
+
+ context "with a CreateLineItems action" do
+ let!(:variant) { create(:variant) }
+ let!(:action) { Promotion::Actions::CreateLineItems.create(promotion: promotion, promotion_action_line_items_attributes: { :'0' => { variant_id: variant.id }}) }
+ let(:order) { create(:order) }
+
+ before do
+ allow(order).to receive_messages(coupon_code: "10off")
+ end
+
+ it "successfully activates promo" do
+ subject.apply
+ expect(subject.success).to be_present
+ expect(order.line_items.pluck(:variant_id)).to include(variant.id)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion_handler/free_shipping_spec.rb b/core/spec/models/spree/promotion_handler/free_shipping_spec.rb
new file mode 100644
index 00000000000..f3f6eb86e0f
--- /dev/null
+++ b/core/spec/models/spree/promotion_handler/free_shipping_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+module Spree
+ module PromotionHandler
+ describe FreeShipping, type: :model do
+ let(:order) { create(:order) }
+ let(:shipment) { create(:shipment, order: order ) }
+
+ let(:promotion) { Promotion.create(name: "Free Shipping") }
+ let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
+ let!(:action) { Promotion::Actions::FreeShipping.create(promotion: promotion) }
+
+ subject { Spree::PromotionHandler::FreeShipping.new(order) }
+
+ context "activates in Shipment level" do
+ it "creates the adjustment" do
+ expect { subject.activate }.to change { shipment.adjustments.count }.by(1)
+ end
+ end
+
+ context "if promo has a code" do
+ before do
+ promotion.update_column(:code, "code")
+ end
+
+ it "does adjust the shipment when applied to order" do
+ order.promotions << promotion
+
+ expect { subject.activate }.to change { shipment.adjustments.count }
+ end
+
+ it "does not adjust the shipment when not applied to order" do
+ expect { subject.activate }.to_not change { shipment.adjustments.count }
+ end
+ end
+
+ context "if promo has a path" do
+ before do
+ promotion.update_column(:path, "path")
+ end
+
+ it "does not adjust the shipment" do
+ expect { subject.activate }.to_not change { shipment.adjustments.count }
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/promotion_handler/page_spec.rb b/core/spec/models/spree/promotion_handler/page_spec.rb
new file mode 100644
index 00000000000..0ddc40a0f25
--- /dev/null
+++ b/core/spec/models/spree/promotion_handler/page_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+module Spree
+ module PromotionHandler
+ describe Page, :type => :model do
+ let(:order) { create(:order_with_line_items, :line_items_count => 1) }
+
+ let(:promotion) { Promotion.create(name: "10% off", :path => '10off') }
+ before do
+ calculator = Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)
+ action = Promotion::Actions::CreateItemAdjustments.create(:calculator => calculator)
+ promotion.actions << action
+ end
+
+ it "activates at the right path" do
+ expect(order.line_item_adjustments.count).to eq(0)
+ Spree::PromotionHandler::Page.new(order, '10off').activate
+ expect(order.line_item_adjustments.count).to eq(1)
+ end
+
+ context "when promotion is expired" do
+ before do
+ promotion.update_columns(
+ :starts_at => 1.week.ago,
+ :expires_at => 1.day.ago
+ )
+ end
+
+ it "is not activated" do
+ expect(order.line_item_adjustments.count).to eq(0)
+ Spree::PromotionHandler::Page.new(order, '10off').activate
+ expect(order.line_item_adjustments.count).to eq(0)
+ end
+ end
+
+ it "does not activate at the wrong path" do
+ expect(order.line_item_adjustments.count).to eq(0)
+ Spree::PromotionHandler::Page.new(order, 'wrongpath').activate
+ expect(order.line_item_adjustments.count).to eq(0)
+ end
+ end
+ end
+end
+
diff --git a/core/spec/models/spree/promotion_rule_spec.rb b/core/spec/models/spree/promotion_rule_spec.rb
new file mode 100644
index 00000000000..1fdd60d6942
--- /dev/null
+++ b/core/spec/models/spree/promotion_rule_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+module Spree
+ describe Spree::PromotionRule, :type => :model do
+
+ class BadTestRule < Spree::PromotionRule; end
+
+ class TestRule < Spree::PromotionRule
+ def eligible?
+ true
+ end
+ end
+
+ it "should force developer to implement eligible? method" do
+ expect { BadTestRule.new.eligible? }.to raise_error
+ end
+
+ it "validates unique rules for a promotion" do
+ p1 = TestRule.new
+ p1.promotion_id = 1
+ p1.save
+
+ p2 = TestRule.new
+ p2.promotion_id = 1
+ expect(p2).not_to be_valid
+ end
+
+ end
+end
diff --git a/core/spec/models/spree/promotion_spec.rb b/core/spec/models/spree/promotion_spec.rb
new file mode 100644
index 00000000000..d856c88bdcd
--- /dev/null
+++ b/core/spec/models/spree/promotion_spec.rb
@@ -0,0 +1,603 @@
+require 'spec_helper'
+
+describe Spree::Promotion, :type => :model do
+ let(:promotion) { Spree::Promotion.new }
+
+ describe "validations" do
+ before :each do
+ @valid_promotion = Spree::Promotion.new :name => "A promotion"
+ end
+
+ it "valid_promotion is valid" do
+ expect(@valid_promotion).to be_valid
+ end
+
+ it "validates usage limit" do
+ @valid_promotion.usage_limit = -1
+ expect(@valid_promotion).not_to be_valid
+
+ @valid_promotion.usage_limit = 100
+ expect(@valid_promotion).to be_valid
+ end
+
+ it "validates name" do
+ @valid_promotion.name = nil
+ expect(@valid_promotion).not_to be_valid
+ end
+ end
+
+ describe ".coupons" do
+ it "scopes promotions with coupon code present only" do
+ promotion = Spree::Promotion.create! name: "test", code: ''
+ expect(Spree::Promotion.coupons).to be_empty
+
+ promotion.update_column :code, "check"
+ expect(Spree::Promotion.coupons.first).to eq promotion
+ end
+ end
+
+ describe ".applied" do
+ it "scopes promotions that have been applied to an order only" do
+ promotion = Spree::Promotion.create! name: "test", code: ''
+ expect(Spree::Promotion.applied).to be_empty
+
+ promotion.orders << create(:order)
+ expect(Spree::Promotion.applied.first).to eq promotion
+ end
+ end
+
+ describe ".advertised" do
+ let(:promotion) { create(:promotion) }
+ let(:advertised_promotion) { create(:promotion, :advertise => true) }
+
+ it "only shows advertised promotions" do
+ advertised = Spree::Promotion.advertised
+ expect(advertised).to include(advertised_promotion)
+ expect(advertised).not_to include(promotion)
+ end
+ end
+
+ describe "#destroy" do
+ let(:promotion) { Spree::Promotion.create(:name => "delete me") }
+
+ before(:each) do
+ promotion.actions << Spree::Promotion::Actions::CreateAdjustment.new
+ promotion.rules << Spree::Promotion::Rules::FirstOrder.new
+ promotion.save!
+ promotion.destroy
+ end
+
+ it "should delete actions" do
+ expect(Spree::PromotionAction.count).to eq(0)
+ end
+
+ it "should delete rules" do
+ expect(Spree::PromotionRule.count).to eq(0)
+ end
+ end
+
+ describe "#save" do
+ let(:promotion) { Spree::Promotion.create(:name => "delete me") }
+
+ before(:each) do
+ promotion.actions << Spree::Promotion::Actions::CreateAdjustment.new
+ promotion.rules << Spree::Promotion::Rules::FirstOrder.new
+ promotion.save!
+ end
+
+ it "should deeply autosave records and preferences" do
+ promotion.actions[0].calculator.preferred_flat_percent = 10
+ promotion.save!
+ expect(Spree::Calculator.first.preferred_flat_percent).to eq(10)
+ end
+ end
+
+ describe "#activate" do
+ before do
+ @action1 = Spree::Promotion::Actions::CreateAdjustment.create!
+ @action2 = Spree::Promotion::Actions::CreateAdjustment.create!
+ allow(@action1).to receive_messages perform: true
+ allow(@action2).to receive_messages perform: true
+
+ promotion.promotion_actions = [@action1, @action2]
+ promotion.created_at = 2.days.ago
+
+ @user = stub_model(Spree::LegacyUser, :email => "spree@example.com")
+ @order = Spree::Order.create user: @user
+ @payload = { :order => @order, :user => @user }
+ end
+
+ it "should check path if present" do
+ promotion.path = 'content/cvv'
+ @payload[:path] = 'content/cvv'
+ expect(@action1).to receive(:perform).with(@payload)
+ expect(@action2).to receive(:perform).with(@payload)
+ promotion.activate(@payload)
+ end
+
+ it "does not perform actions against an order in a finalized state" do
+ expect(@action1).not_to receive(:perform).with(@payload)
+
+ @order.state = 'complete'
+ promotion.activate(@payload)
+
+ @order.state = 'awaiting_return'
+ promotion.activate(@payload)
+
+ @order.state = 'returned'
+ promotion.activate(@payload)
+ end
+
+ it "does activate if newer then order" do
+ expect(@action1).to receive(:perform).with(@payload)
+ promotion.created_at = DateTime.now + 2
+ expect(promotion.activate(@payload)).to be true
+ end
+
+ context "keeps track of the orders" do
+ context "when activated" do
+ it "assigns the order" do
+ expect(promotion.orders).to be_empty
+ expect(promotion.activate(@payload)).to be true
+ expect(promotion.orders.first).to eql @order
+ end
+ end
+ context "when not activated" do
+ it "will not assign the order" do
+ @order.state = 'complete'
+ expect(promotion.orders).to be_empty
+ expect(promotion.activate(@payload)).to be_falsey
+ expect(promotion.orders).to be_empty
+ end
+ end
+
+ end
+
+ end
+
+ context "#usage_limit_exceeded" do
+ let(:promotable) { double('Promotable') }
+ it "should not have its usage limit exceeded with no usage limit" do
+ promotion.usage_limit = 0
+ expect(promotion.usage_limit_exceeded?(promotable)).to be false
+ end
+
+ it "should have its usage limit exceeded" do
+ promotion.usage_limit = 2
+ allow(promotion).to receive_messages(:adjusted_credits_count => 2)
+ expect(promotion.usage_limit_exceeded?(promotable)).to be true
+
+ allow(promotion).to receive_messages(:adjusted_credits_count => 3)
+ expect(promotion.usage_limit_exceeded?(promotable)).to be true
+ end
+ end
+
+ context "#expired" do
+ it "should not be exipired" do
+ expect(promotion).not_to be_expired
+ end
+
+ it "should be expired if it hasn't started yet" do
+ promotion.starts_at = Time.now + 1.day
+ expect(promotion).to be_expired
+ end
+
+ it "should be expired if it has already ended" do
+ promotion.expires_at = Time.now - 1.day
+ expect(promotion).to be_expired
+ end
+
+ it "should not be expired if it has started already" do
+ promotion.starts_at = Time.now - 1.day
+ expect(promotion).not_to be_expired
+ end
+
+ it "should not be expired if it has not ended yet" do
+ promotion.expires_at = Time.now + 1.day
+ expect(promotion).not_to be_expired
+ end
+
+ it "should not be expired if current time is within starts_at and expires_at range" do
+ promotion.starts_at = Time.now - 1.day
+ promotion.expires_at = Time.now + 1.day
+ expect(promotion).not_to be_expired
+ end
+
+ it "should not be expired if usage limit is not exceeded" do
+ promotion.usage_limit = 2
+ allow(promotion).to receive_messages(:credits_count => 1)
+ expect(promotion).not_to be_expired
+ end
+ end
+
+ context "#credits_count" do
+ let!(:promotion) do
+ promotion = Spree::Promotion.new
+ promotion.name = "Foo"
+ promotion.code = "XXX"
+ calculator = Spree::Calculator::FlatRate.new
+ promotion.tap(&:save)
+ end
+
+ let!(:action) do
+ calculator = Spree::Calculator::FlatRate.new
+ action_params = { :promotion => promotion, :calculator => calculator }
+ action = Spree::Promotion::Actions::CreateAdjustment.create(action_params)
+ promotion.actions << action
+ action
+ end
+
+ let!(:adjustment) do
+ order = create(:order)
+ Spree::Adjustment.create!(
+ order: order,
+ adjustable: order,
+ source: action,
+ amount: 10,
+ label: 'Promotional adjustment'
+ )
+ end
+
+ it "counts eligible adjustments" do
+ adjustment.update_column(:eligible, true)
+ expect(promotion.credits_count).to eq(1)
+ end
+
+ # Regression test for #4112
+ it "does not count ineligible adjustments" do
+ adjustment.update_column(:eligible, false)
+ expect(promotion.credits_count).to eq(0)
+ end
+ end
+
+ context "#adjusted_credits_count" do
+ let(:order) { create :order }
+ let(:line_item) { create :line_item, order: order }
+ let(:promotion) { Spree::Promotion.create name: "promo", :code => "10off" }
+ let(:order_action) {
+ action = Spree::Promotion::Actions::CreateAdjustment.create(calculator: Spree::Calculator::FlatPercentItemTotal.new)
+ promotion.actions << action
+ action
+ }
+ let(:item_action) {
+ action = Spree::Promotion::Actions::CreateItemAdjustments.create(calculator: Spree::Calculator::FlatPercentItemTotal.new)
+ promotion.actions << action
+ action
+ }
+ let(:order_adjustment) do
+ Spree::Adjustment.create!(
+ :source => order_action,
+ :amount => 10,
+ :adjustable => order,
+ :order => order,
+ :label => "Promotional adjustment"
+ )
+ end
+ let(:item_adjustment) do
+ Spree::Adjustment.create!(
+ :source => item_action,
+ :amount => 10,
+ :adjustable => line_item,
+ :order => order,
+ :label => "Promotional adjustment"
+ )
+ end
+
+ it "counts order level adjustments" do
+ expect(order_adjustment.adjustable).to eq(order)
+ expect(promotion.credits_count).to eq(1)
+ expect(promotion.adjusted_credits_count(order)).to eq(0)
+ end
+
+ it "counts item level adjustments" do
+ expect(item_adjustment.adjustable).to eq(line_item)
+ expect(promotion.credits_count).to eq(1)
+ expect(promotion.adjusted_credits_count(order)).to eq(0)
+ end
+ end
+
+ context "#products" do
+ let(:promotion) { create(:promotion) }
+
+ context "when it has product rules with products associated" do
+ let(:promotion_rule) { Spree::Promotion::Rules::Product.new }
+
+ before do
+ promotion_rule.promotion = promotion
+ promotion_rule.products << create(:product)
+ promotion_rule.save
+ end
+
+ it "should have products" do
+ expect(promotion.reload.products.size).to eq(1)
+ end
+ end
+
+ context "when there's no product rule associated" do
+ it "should not have products but still return an empty array" do
+ expect(promotion.products).to be_blank
+ end
+ end
+ end
+
+ context "#eligible?" do
+ let(:promotable) { create :order }
+ subject { promotion.eligible?(promotable) }
+ context "when promotion is expired" do
+ before { promotion.expires_at = Time.now - 10.days }
+ it { is_expected.to be false }
+ end
+ context "when promotable is a Spree::LineItem" do
+ let(:promotable) { create :line_item }
+ let(:product) { promotable.product }
+ before do
+ product.promotionable = promotionable
+ end
+ context "and product is promotionable" do
+ let(:promotionable) { true }
+ it { is_expected.to be true }
+ end
+ context "and product is not promotionable" do
+ let(:promotionable) { false }
+ it { is_expected.to be false }
+ end
+ end
+ context "when promotable is a Spree::Order" do
+ let(:promotable) { create :order }
+ context "and it is empty" do
+ it { is_expected.to be true }
+ end
+ context "and it contains items" do
+ let!(:line_item) { create(:line_item, order: promotable) }
+ context "and the items are all non-promotionable" do
+ before do
+ line_item.product.update_column(:promotionable, false)
+ end
+ it { is_expected.to be false }
+ end
+ context "and at least one item is promotionable" do
+ it { is_expected.to be true }
+ end
+ end
+ end
+ end
+
+ context "#eligible_rules" do
+ let(:promotable) { double('Promotable') }
+ it "true if there are no rules" do
+ expect(promotion.eligible_rules(promotable)).to eq []
+ end
+
+ it "true if there are no applicable rules" do
+ promotion.promotion_rules = [stub_model(Spree::PromotionRule, :eligible? => true, :applicable? => false)]
+ allow(promotion.promotion_rules).to receive(:for).and_return([])
+ expect(promotion.eligible_rules(promotable)).to eq []
+ end
+
+ context "with 'all' match policy" do
+ let(:promo1) { Spree::PromotionRule.create! }
+ let(:promo2) { Spree::PromotionRule.create! }
+
+ before { promotion.match_policy = 'all' }
+
+ context "when all rules are eligible" do
+ before do
+ allow(promo1).to receive_messages(eligible?: true, applicable?: true)
+ allow(promo2).to receive_messages(eligible?: true, applicable?: true)
+
+ promotion.promotion_rules = [promo1, promo2]
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
+ end
+ it "returns the eligible rules" do
+ expect(promotion.eligible_rules(promotable)).to eq [promo1, promo2]
+ end
+ it "does set anything to eligiblity errors" do
+ promotion.eligible_rules(promotable)
+ expect(promotion.eligibility_errors).to be_nil
+ end
+ end
+
+ context "when any of the rules is not eligible" do
+ let(:errors) { double ActiveModel::Errors, empty?: false }
+ before do
+ allow(promo1).to receive_messages(eligible?: true, applicable?: true, eligibility_errors: nil)
+ allow(promo2).to receive_messages(eligible?: false, applicable?: true, eligibility_errors: errors)
+
+ promotion.promotion_rules = [promo1, promo2]
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
+ end
+ it "returns nil" do
+ expect(promotion.eligible_rules(promotable)).to be_nil
+ end
+ it "sets eligibility errors to the first non-nil one" do
+ promotion.eligible_rules(promotable)
+ expect(promotion.eligibility_errors).to eq errors
+ end
+ end
+ end
+
+ context "with 'any' match policy" do
+ let(:promotion) { Spree::Promotion.create(:name => "Promo", :match_policy => 'any') }
+ let(:promotable) { double('Promotable') }
+
+ it "should have eligible rules if any of the rules are eligible" do
+ allow_any_instance_of(Spree::PromotionRule).to receive_messages(:applicable? => true)
+ true_rule = Spree::PromotionRule.create(:promotion => promotion)
+ allow(true_rule).to receive_messages(:eligible? => true)
+ allow(promotion).to receive_messages(:rules => [true_rule])
+ allow(promotion).to receive_message_chain(:rules, :for).and_return([true_rule])
+ expect(promotion.eligible_rules(promotable)).to eq [true_rule]
+ end
+
+ context "when none of the rules are eligible" do
+ let(:promo) { Spree::PromotionRule.create! }
+ let(:errors) { double ActiveModel::Errors, empty?: false }
+ before do
+ allow(promo).to receive_messages(eligible?: false, applicable?: true, eligibility_errors: errors)
+
+ promotion.promotion_rules = [promo]
+ allow(promotion.promotion_rules).to receive(:for).and_return(promotion.promotion_rules)
+ end
+ it "returns nil" do
+ expect(promotion.eligible_rules(promotable)).to be_nil
+ end
+ it "sets eligibility errors to the first non-nil one" do
+ promotion.eligible_rules(promotable)
+ expect(promotion.eligibility_errors).to eq errors
+ end
+ end
+ end
+ end
+
+ describe '#line_item_actionable?' do
+ let(:order) { double Spree::Order }
+ let(:line_item) { double Spree::LineItem}
+ let(:true_rule) { double Spree::PromotionRule, eligible?: true, applicable?: true, actionable?: true }
+ let(:false_rule) { double Spree::PromotionRule, eligible?: true, applicable?: true, actionable?: false }
+ let(:rules) { [] }
+
+ before do
+ allow(promotion).to receive(:rules) { rules }
+ allow(rules).to receive(:for) { rules }
+ end
+
+ subject { promotion.line_item_actionable? order, line_item }
+
+ context 'when the order is eligible for promotion' do
+ context 'when there are no rules' do
+ it { is_expected.to be }
+ end
+
+ context 'when there are rules' do
+ context 'when the match policy is all' do
+ before { promotion.match_policy = 'all' }
+
+ context 'when all rules allow action on the line item' do
+ let(:rules) { [true_rule] }
+ it { is_expected.to be}
+ end
+
+ context 'when at least one rule does not allow action on the line item' do
+ let(:rules) { [true_rule, false_rule] }
+ it { is_expected.not_to be}
+ end
+ end
+
+ context 'when the match policy is any' do
+ before { promotion.match_policy = 'any' }
+
+ context 'when at least one rule allows action on the line item' do
+ let(:rules) { [true_rule, false_rule] }
+ it { is_expected.to be }
+ end
+
+ context 'when no rules allow action on the line item' do
+ let(:rules) { [false_rule] }
+ it { is_expected.not_to be}
+ end
+ end
+ end
+ end
+
+ context 'when the order is not eligible for the promotion' do
+ before { promotion.starts_at = Time.current + 2.days }
+ it { is_expected.not_to be }
+ end
+ end
+
+ # regression for #4059
+ # admin form posts the code and path as empty string
+ describe "normalize blank values for code & path" do
+ it "will save blank value as nil value instead" do
+ promotion = Spree::Promotion.create(:name => "A promotion", :code => "", :path => "")
+ expect(promotion.code).to be_nil
+ expect(promotion.path).to be_nil
+ end
+ end
+
+ # Regression test for #4081
+ describe "#with_coupon_code" do
+ context "and code stored in uppercase" do
+ let!(:promotion) { create(:promotion, :code => "MY-COUPON-123") }
+ it "finds the code with lowercase" do
+ expect(Spree::Promotion.with_coupon_code("my-coupon-123")).to eql promotion
+ end
+ end
+ end
+
+ describe '#used_by?' do
+ subject { promotion.used_by? user, [excluded_order] }
+
+ let(:promotion) { create :promotion, :with_order_adjustment }
+ let(:user) { create :user }
+ let(:order) { create :order_with_line_items, user: user }
+ let(:excluded_order) { create :order_with_line_items, user: user }
+
+ before do
+ order.user_id = user.id
+ order.save!
+ end
+
+ context 'when the user has used this promo' do
+ before do
+ promotion.activate(order: order)
+ order.update!
+ order.completed_at = Time.now
+ order.save!
+ end
+
+ context 'when the order is complete' do
+ it { is_expected.to be true }
+
+ context 'when the promotion was not eligible' do
+ let(:adjustment) { order.adjustments.first }
+
+ before do
+ adjustment.eligible = false
+ adjustment.save!
+ end
+
+ it { is_expected.to be false }
+ end
+
+ context 'when the only matching order is the excluded order' do
+ let(:excluded_order) { order }
+ it { is_expected.to be false }
+ end
+ end
+
+ context 'when the order is not complete' do
+ let(:order) { create :order, user: user }
+ it { is_expected.to be false }
+ end
+ end
+
+ context 'when the user has not used this promo' do
+ it { is_expected.to be false }
+ end
+ end
+
+ describe "adding items to the cart" do
+ let(:order) { create :order }
+ let(:line_item) { create :line_item, order: order }
+ let(:promo) { create :promotion_with_item_adjustment, adjustment_rate: 5, code: 'promo' }
+ let(:variant) { create :variant }
+
+ it "updates the promotions for new line items" do
+ expect(line_item.adjustments).to be_empty
+ expect(order.adjustment_total).to eq 0
+
+ promo.activate order: order
+ order.update!
+
+ expect(line_item.adjustments.size).to eq(1)
+ expect(order.adjustment_total).to eq -5
+
+ other_line_item = order.contents.add(variant, 1, currency: order.currency)
+
+ expect(other_line_item).not_to eq line_item
+ expect(other_line_item.adjustments.size).to eq(1)
+ expect(order.adjustment_total).to eq -10
+ end
+ end
+end
diff --git a/core/spec/models/spree/property_spec.rb b/core/spec/models/spree/property_spec.rb
new file mode 100644
index 00000000000..999afaea9d2
--- /dev/null
+++ b/core/spec/models/spree/property_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::Property, :type => :model do
+
+end
diff --git a/core/spec/models/spree/prototype_spec.rb b/core/spec/models/spree/prototype_spec.rb
new file mode 100644
index 00000000000..16fff45391d
--- /dev/null
+++ b/core/spec/models/spree/prototype_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::Prototype, :type => :model do
+
+end
diff --git a/core/spec/models/spree/refund_spec.rb b/core/spec/models/spree/refund_spec.rb
new file mode 100644
index 00000000000..30a118ac504
--- /dev/null
+++ b/core/spec/models/spree/refund_spec.rb
@@ -0,0 +1,204 @@
+require 'spec_helper'
+
+describe Spree::Refund, :type => :model do
+
+ describe 'create' do
+ let(:amount) { 100.0 }
+ let(:amount_in_cents) { amount * 100 }
+
+ let(:authorization) { generate(:refund_transaction_id) }
+
+ let(:payment) { create(:payment, amount: payment_amount, payment_method: payment_method) }
+ let(:payment_amount) { amount*2 }
+ let(:payment_method) { create(:credit_card_payment_method, environment: payment_method_environment) }
+ let(:payment_method_environment) { 'test' }
+
+ let(:refund_reason) { create(:refund_reason) }
+
+ let(:gateway_response) {
+ ActiveMerchant::Billing::Response.new(
+ gateway_response_success,
+ gateway_response_message,
+ gateway_response_params,
+ gateway_response_options
+ )
+ }
+ let(:gateway_response_success) { true }
+ let(:gateway_response_message) { "" }
+ let(:gateway_response_params) { {} }
+ let(:gateway_response_options) { {} }
+
+ subject { create(:refund, payment: payment, amount: amount, reason: refund_reason, transaction_id: nil) }
+
+ before do
+ allow(payment.payment_method)
+ .to receive(:credit)
+ .with(amount_in_cents, payment.source, payment.transaction_id, {originator: an_instance_of(Spree::Refund)})
+ .and_return(gateway_response)
+ end
+
+ context "transaction id exists on creation" do
+ let(:transaction_id) { "12kfjas0" }
+ subject { create(:refund, payment: payment, amount: amount, reason: refund_reason, transaction_id: transaction_id) }
+
+ it "creates a refund record" do
+ expect{ subject }.to change { Spree::Refund.count }.by(1)
+ end
+
+ it "maintains the transaction id" do
+ expect(subject.reload.transaction_id).to eq transaction_id
+ end
+
+ it "saves the amount" do
+ expect(subject.reload.amount).to eq amount
+ end
+
+ it "creates a log entry" do
+ expect(subject.log_entries).to be_present
+ end
+
+ it "does not attempt to process a transaction" do
+ expect(payment.payment_method).not_to receive(:credit)
+ subject
+ end
+
+ end
+
+ context "processing is successful" do
+ let(:gateway_response_options) { { authorization: authorization } }
+
+ it 'should create a refund' do
+ expect{ subject }.to change{ Spree::Refund.count }.by(1)
+ end
+
+ it 'return the newly created refund' do
+ expect(subject).to be_a(Spree::Refund)
+ end
+
+ it 'should save the returned authorization value' do
+ expect(subject.reload.transaction_id).to eq authorization
+ end
+
+ it 'should save the passed amount as the refund amount' do
+ expect(subject.amount).to eq amount
+ end
+
+ it 'should create a log entry' do
+ expect(subject.log_entries).to be_present
+ end
+
+ it "attempts to process a transaction" do
+ expect(payment.payment_method).to receive(:credit).once
+ subject
+ end
+
+ it 'should update the payment total' do
+ expect(payment.order.updater).to receive(:update)
+ subject
+ end
+
+ end
+
+ context "processing fails" do
+ let(:gateway_response_success) { false }
+ let(:gateway_response_message) { "failure message" }
+
+ it 'should raise error and not create a refund' do
+ expect do
+ expect { subject }.to raise_error(Spree::Core::GatewayError, gateway_response_message)
+ end.to_not change{ Spree::Refund.count }
+ end
+ end
+
+ context 'without payment profiles supported' do
+ before do
+ allow(payment.payment_method).to receive(:payment_profiles_supported?) { false }
+ end
+
+ it 'should not supply the payment source' do
+ expect(payment.payment_method)
+ .to receive(:credit)
+ .with(amount * 100, payment.transaction_id, {originator: an_instance_of(Spree::Refund)})
+ .and_return(gateway_response)
+
+ subject
+ end
+ end
+
+ context 'with payment profiles supported' do
+ before do
+ allow(payment.payment_method).to receive(:payment_profiles_supported?) { true }
+ end
+
+ it 'should supply the payment source' do
+ expect(payment.payment_method)
+ .to receive(:credit)
+ .with(amount_in_cents, payment.source, payment.transaction_id, {originator: an_instance_of(Spree::Refund)})
+ .and_return(gateway_response)
+
+ subject
+ end
+ end
+
+ context 'with an activemerchant gateway connection error' do
+ before do
+ expect(payment.payment_method)
+ .to receive(:credit)
+ .with(amount_in_cents, payment.source, payment.transaction_id, {originator: an_instance_of(Spree::Refund)})
+ .and_raise(ActiveMerchant::ConnectionError)
+ end
+
+ it 'raises Spree::Core::GatewayError' do
+ expect { subject }.to raise_error(Spree::Core::GatewayError, Spree.t(:unable_to_connect_to_gateway))
+ end
+ end
+
+ context 'with the incorrect payment method environment' do
+ let(:payment_method_environment) { 'development' }
+
+ it 'raises a Spree::Core::GatewayError' do
+ expect { subject }.to raise_error { |error|
+ expect(error).to be_a(ActiveRecord::RecordInvalid)
+ expect(error.record.errors.full_messages).to eq [Spree.t(:gateway_config_unavailable) + " - test"]
+ }
+ end
+ end
+
+ context 'with amount too large' do
+ let(:payment_amount) { 10 }
+ let(:amount) { payment_amount*2 }
+
+ it 'is invalid' do
+ expect { subject }.to raise_error { |error|
+ expect(error).to be_a(ActiveRecord::RecordInvalid)
+ expect(error.record.errors.full_messages).to eq ["Amount #{I18n.t('activerecord.errors.models.spree/refund.attributes.amount.greater_than_allowed')}"]
+ }
+ end
+ end
+ end
+
+ describe 'total_amount_reimbursed_for' do
+ let(:customer_return) { reimbursement.customer_return}
+ let(:reimbursement) { create(:reimbursement) }
+ let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }
+
+ subject { Spree::Refund.total_amount_reimbursed_for(reimbursement) }
+
+ context 'with reimbursements performed' do
+ before { reimbursement.perform! }
+
+ it 'returns the total amount' do
+ amount = Spree::Refund.total_amount_reimbursed_for(reimbursement)
+ expect(amount).to be > 0
+ expect(amount).to eq reimbursement.total
+ end
+ end
+
+ context 'without reimbursements performed' do
+ it 'returns zero' do
+ amount = Spree::Refund.total_amount_reimbursed_for(reimbursement)
+ expect(amount).to eq 0
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement/credit_spec.rb b/core/spec/models/spree/reimbursement/credit_spec.rb
new file mode 100644
index 00000000000..34873999f3b
--- /dev/null
+++ b/core/spec/models/spree/reimbursement/credit_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+module Spree
+ describe Reimbursement::Credit, :type => :model do
+ context 'class methods' do
+ describe '.total_amount_reimbursed_for' do
+ subject { Spree::Reimbursement::Credit.total_amount_reimbursed_for(reimbursement) }
+
+ let(:reimbursement) { create(:reimbursement) }
+ let(:credit_double) { double(amount: 99.99) }
+
+ before { allow(reimbursement).to receive(:credits).and_return([credit_double, credit_double])}
+
+ it 'should sum the amounts of all of the reimbursements credits' do
+ expect(subject).to eq BigDecimal.new('199.98')
+ end
+ end
+ end
+
+ describe '#description' do
+ let(:credit) { Spree::Reimbursement::Credit.new(amount: 100, creditable: mock_model(Spree::PaymentMethod::Check)) }
+
+ it "should be the creditable's class name" do
+ expect(credit.description).to eq 'Check'
+ end
+ end
+
+ describe '#display_amount' do
+ let(:credit) { Spree::Reimbursement::Credit.new(amount: 100) }
+
+ it 'should be a money object' do
+ expect(credit.display_amount).to eq Spree::Money.new(100, currency: "USD")
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb b/core/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb
new file mode 100644
index 00000000000..5844c83b971
--- /dev/null
+++ b/core/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb
@@ -0,0 +1,140 @@
+require 'spec_helper'
+
+module Spree
+ describe Reimbursement::ReimbursementTypeEngine, :type => :model do
+ describe '#calculate_reimbursement_types' do
+ let(:return_item) { create(:return_item) }
+ let(:return_items) { [ return_item ] }
+ let(:reimbursement_type_engine) { Spree::Reimbursement::ReimbursementTypeEngine.new(return_items) }
+ let(:expired_reimbursement_type) { Spree::ReimbursementType::OriginalPayment }
+ let(:override_reimbursement_type) { Spree::ReimbursementType::OriginalPayment.new }
+ let(:preferred_reimbursement_type) { Spree::ReimbursementType::OriginalPayment.new }
+ let(:calculated_reimbursement_types) { subject }
+ let(:all_reimbursement_types) {[
+ reimbursement_type_engine.default_reimbursement_type,
+ reimbursement_type_engine.exchange_reimbursement_type,
+ expired_reimbursement_type,
+ override_reimbursement_type,
+ preferred_reimbursement_type
+ ]}
+
+ subject { reimbursement_type_engine.calculate_reimbursement_types }
+
+ shared_examples_for "reimbursement type hash" do
+ it "contain all keys that respond to reimburse" do
+ calculated_reimbursement_types.keys.each do |r_type|
+ expect(r_type).to respond_to :reimburse
+ end
+ end
+ end
+
+ before do
+ reimbursement_type_engine.expired_reimbursement_type = expired_reimbursement_type
+ allow(return_item.inventory_unit.shipment).to receive(:shipped_at).and_return(Date.yesterday)
+ allow(return_item).to receive(:exchange_required?).and_return(false)
+ end
+
+ context 'the return item requires exchange' do
+ before { allow(return_item).to receive(:exchange_required?).and_return(true) }
+
+ it 'returns a hash with the exchange reimbursement type associated to the return items' do
+ expect(calculated_reimbursement_types[reimbursement_type_engine.exchange_reimbursement_type]).to eq(return_items)
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [reimbursement_type_engine.exchange_reimbursement_type]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+
+ context 'the return item does not require exchange' do
+ context 'the return item has an override reimbursement type' do
+ before { allow(return_item).to receive(:override_reimbursement_type).and_return(override_reimbursement_type) }
+
+ it 'returns a hash with the override reimbursement type associated to the return items' do
+ expect(calculated_reimbursement_types[override_reimbursement_type.class]).to eq(return_items)
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [override_reimbursement_type.class]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+
+ context 'the return item does not have an override reimbursement type' do
+ context 'the return item has a preferred reimbursement type' do
+ before { allow(return_item).to receive(:preferred_reimbursement_type).and_return(preferred_reimbursement_type) }
+
+ context 'the reimbursement type is not valid for the return item' do
+ before { expect(reimbursement_type_engine).to receive(:valid_preferred_reimbursement_type?).and_return(false) }
+
+ it 'returns a hash with no return items associated to the preferred reimbursement type' do
+ expect(calculated_reimbursement_types[preferred_reimbursement_type]).to eq([])
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [preferred_reimbursement_type]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+
+ context 'the reimbursement type is valid for the return item' do
+ it 'returns a hash with the expired reimbursement type associated to the return items' do
+ expect(calculated_reimbursement_types[preferred_reimbursement_type.class]).to eq(return_items)
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [preferred_reimbursement_type.class]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+ end
+
+ context 'the return item does not have a preferred reimbursement type' do
+ context 'the return item is past the time constraint' do
+ before { allow(reimbursement_type_engine).to receive(:past_reimbursable_time_period?).and_return(true) }
+
+ it 'returns a hash with the expired reimbursement type associated to the return items' do
+ expect(calculated_reimbursement_types[expired_reimbursement_type]).to eq(return_items)
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [expired_reimbursement_type]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+
+ context 'the return item is within the time constraint' do
+ it 'returns a hash with the default reimbursement type associated to the return items' do
+ expect(calculated_reimbursement_types[reimbursement_type_engine.default_reimbursement_type]).to eq(return_items)
+ end
+
+ it 'the return items are not included in any of the other reimbursement types' do
+ (all_reimbursement_types - [reimbursement_type_engine.default_reimbursement_type]).each do |r_type|
+ expect(calculated_reimbursement_types[r_type]).to eq([])
+ end
+ end
+
+ it_should_behave_like 'reimbursement type hash'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb b/core/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb
new file mode 100644
index 00000000000..b86b1029432
--- /dev/null
+++ b/core/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+module Spree
+ describe Reimbursement::ReimbursementTypeValidator, :type => :model do
+ class DummyClass
+ include Spree::Reimbursement::ReimbursementTypeValidator
+
+ class_attribute :expired_reimbursement_type
+ self.expired_reimbursement_type = Spree::ReimbursementType::Credit
+
+ class_attribute :refund_time_constraint
+ self.refund_time_constraint = 90.days
+ end
+
+ let(:return_item) do
+ create(
+ :return_item,
+ preferred_reimbursement_type: preferred_reimbursement_type
+ )
+ end
+ let(:dummy) { DummyClass.new }
+ let(:preferred_reimbursement_type) { Spree::ReimbursementType::Credit.new }
+
+ describe '#valid_preferred_reimbursement_type?' do
+ before do
+ allow(dummy).to receive(:past_reimbursable_time_period?).and_return(true)
+ end
+
+ subject { dummy.valid_preferred_reimbursement_type?(return_item) }
+
+ context 'is valid' do
+ it 'if it is not past the reimbursable time period' do
+ allow(dummy).to receive(:past_reimbursable_time_period?).and_return(false)
+ expect(subject).to be true
+ end
+
+ it 'if the return items preferred method of reimbursement is the expired method of reimbursement' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'is invalid' do
+ it 'if the return item is past the eligible time period and the preferred method of reimbursement is not the expired method of reimbursement' do
+ return_item.preferred_reimbursement_type =
+ Spree::ReimbursementType::OriginalPayment.new
+ expect(subject).to be false
+ end
+ end
+ end
+
+ describe '#past_reimbursable_time_period?' do
+ subject { dummy.past_reimbursable_time_period?(return_item) }
+
+ before do
+ allow(return_item).to receive_message_chain(:inventory_unit, :shipment, :shipped_at).and_return(shipped_at)
+ end
+
+ context 'it has not shipped' do
+ let(:shipped_at) { nil }
+
+ it 'is not past the reimbursable time period' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'it has shipped and it is more recent than the time constraint' do
+ let(:shipped_at) { (dummy.refund_time_constraint - 1.day).ago }
+
+ it 'is not past the reimbursable time period' do
+ expect(subject).to be false
+ end
+ end
+
+ context 'it has shipped and it is further in the past than the time constraint' do
+ let(:shipped_at) { (dummy.refund_time_constraint + 1.day).ago }
+
+ it 'is past the reimbursable time period' do
+ expect(subject).to be true
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_performer_spec.rb b/core/spec/models/spree/reimbursement_performer_spec.rb
new file mode 100644
index 00000000000..c237a3e68a0
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_performer_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Spree::ReimbursementPerformer, :type => :model do
+ let(:reimbursement) { create(:reimbursement, return_items_count: 1) }
+ let(:return_item) { reimbursement.return_items.first }
+ let(:reimbursement_type) { double("ReimbursementType") }
+ let(:reimbursement_type_hash) { { reimbursement_type => [return_item]} }
+
+ before do
+ expect(Spree::ReimbursementPerformer).to receive(:calculate_reimbursement_types).and_return(reimbursement_type_hash)
+ end
+
+ describe ".simulate" do
+ subject { Spree::ReimbursementPerformer.simulate(reimbursement) }
+
+ it "reimburses each calculated reimbursement types with the correct return items as a simulation" do
+ expect(reimbursement_type).to receive(:reimburse).with(reimbursement, [return_item], true)
+ subject
+ end
+ end
+
+ describe '.perform' do
+ subject { Spree::ReimbursementPerformer.perform(reimbursement) }
+
+ it "reimburses each calculated reimbursement types with the correct return items as a simulation" do
+ expect(reimbursement_type).to receive(:reimburse).with(reimbursement, [return_item], false)
+ subject
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_spec.rb b/core/spec/models/spree/reimbursement_spec.rb
new file mode 100644
index 00000000000..a8b19b053e5
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_spec.rb
@@ -0,0 +1,216 @@
+require 'spec_helper'
+
+describe Spree::Reimbursement, type: :model do
+
+ describe ".before_create" do
+ describe "#generate_number" do
+ context "number is assigned" do
+ let(:number) { '123' }
+ let(:reimbursement) { Spree::Reimbursement.new(number: number) }
+
+ it "should return the assigned number" do
+ reimbursement.save
+ expect(reimbursement.number).to eq number
+ end
+ end
+
+ context "number is not assigned" do
+ let(:reimbursement) { Spree::Reimbursement.new(number: nil) }
+
+ before do
+ allow(reimbursement).to receive_messages(valid?: true)
+ end
+
+ it "should assign number with random RI number" do
+ reimbursement.save
+ expect(reimbursement.number).to be =~ /RI\d{9}/
+ end
+ end
+ end
+ end
+
+ describe "#display_total" do
+ let(:total) { 100.50 }
+ let(:currency) { "USD" }
+ let(:order) { Spree::Order.new(currency: currency) }
+ let(:reimbursement) { Spree::Reimbursement.new(total: total, order: order) }
+
+ subject { reimbursement.display_total }
+
+ it "returns the value as a Spree::Money instance" do
+ expect(subject).to eq Spree::Money.new(total)
+ end
+
+ it "uses the order's currency" do
+ expect(subject.money.currency.to_s).to eq currency
+ end
+ end
+
+ describe "#perform!" do
+ let!(:adjustments) { [] } # placeholder to ensure it gets run prior the "before" at this level
+
+ let!(:tax_rate) { nil }
+ let!(:tax_zone) { create(:zone, default_tax: true) }
+
+ let(:order) { create(:order_with_line_items, state: 'payment', line_items_count: 1, line_items_price: line_items_price, shipment_cost: 0) }
+ let(:line_items_price) { BigDecimal.new(10) }
+ let(:line_item) { order.line_items.first }
+ let(:inventory_unit) { line_item.inventory_units.first }
+ let(:payment) { build(:payment, amount: payment_amount, order: order, state: 'completed') }
+ let(:payment_amount) { order.total }
+ let(:customer_return) { build(:customer_return, return_items: [return_item]) }
+ let(:return_item) { build(:return_item, inventory_unit: inventory_unit) }
+
+ let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }
+
+ let(:reimbursement) { create(:reimbursement, customer_return: customer_return, order: order, return_items: [return_item]) }
+
+ subject { reimbursement.perform! }
+
+ before do
+ order.shipments.each do |shipment|
+ shipment.inventory_units.update_all state: 'shipped'
+ shipment.update_column('state', 'shipped')
+ end
+ order.reload
+ order.update!
+ if payment
+ payment.save!
+ order.next! # confirm
+ end
+ order.next! # completed
+ customer_return.save!
+ return_item.accept!
+ end
+
+ it "refunds the total amount" do
+ subject
+ expect(reimbursement.unpaid_amount).to eq 0
+ end
+
+ it "creates a refund" do
+ expect {
+ subject
+ }.to change{ Spree::Refund.count }.by(1)
+ expect(Spree::Refund.last.amount).to eq order.total
+ end
+
+ context 'with additional tax' do
+ let!(:tax_rate) { create(:tax_rate, name: "Sales Tax", amount: 0.10, included_in_price: false, zone: tax_zone) }
+
+ it 'saves the additional tax and refunds the total' do
+ expect {
+ subject
+ }.to change { Spree::Refund.count }.by(1)
+ return_item.reload
+ expect(return_item.additional_tax_total).to be > 0
+ expect(return_item.additional_tax_total).to eq line_item.additional_tax_total
+ expect(reimbursement.total).to eq line_item.pre_tax_amount + line_item.additional_tax_total
+ expect(Spree::Refund.last.amount).to eq line_item.pre_tax_amount + line_item.additional_tax_total
+ end
+ end
+
+ context 'with included tax' do
+ let!(:tax_rate) { create(:tax_rate, name: "VAT Tax", amount: 0.1, included_in_price: true, zone: tax_zone) }
+
+ it 'saves the included tax and refunds the total' do
+ expect {
+ subject
+ }.to change { Spree::Refund.count }.by(1)
+ return_item.reload
+ expect(return_item.included_tax_total).to be < 0
+ expect(return_item.included_tax_total).to eq line_item.included_tax_total
+ expect(reimbursement.total).to eq (line_item.pre_tax_amount + line_item.included_tax_total).round(2)
+ expect(Spree::Refund.last.amount).to eq (line_item.pre_tax_amount + line_item.included_tax_total).round(2)
+ end
+ end
+
+ context 'when reimbursement cannot be fully performed' do
+ let!(:non_return_refund) { create(:refund, amount: 1, payment: payment) }
+
+ it 'raises IncompleteReimbursement error' do
+ expect { subject }.to raise_error(Spree::Reimbursement::IncompleteReimbursementError)
+ end
+ end
+
+ context "when exchange is required" do
+ let(:exchange_variant) { build(:variant) }
+ before { return_item.exchange_variant = exchange_variant }
+ it "generates an exchange shipment for the order for the exchange items" do
+ expect { subject }.to change { order.reload.shipments.count }.by 1
+ expect(order.shipments.last.inventory_units.first.variant).to eq exchange_variant
+ end
+ end
+
+ it "triggers the reimbursement mailer to be sent" do
+ expect(Spree::ReimbursementMailer).to receive(:reimbursement_email).with(reimbursement.id) { double(deliver: true) }
+ subject
+ end
+
+ end
+
+ describe "#return_items_requiring_exchange" do
+ it "returns only the return items that require an exchange" do
+ return_items = [double(exchange_required?: true), double(exchange_required?: true),double(exchange_required?: false)]
+ allow(subject).to receive(:return_items) { return_items }
+ expect(subject.return_items_requiring_exchange).to eq return_items.take(2)
+ end
+ end
+
+ describe "#calculated_total" do
+ context 'with return item amounts that would round up if added' do
+ let(:reimbursement) { Spree::Reimbursement.new }
+
+ subject { reimbursement.calculated_total }
+
+ before do
+ reimbursement.return_items << Spree::ReturnItem.new(pre_tax_amount: 10.003)
+ reimbursement.return_items << Spree::ReturnItem.new(pre_tax_amount: 10.003)
+ end
+
+ it 'rounds down' do
+ expect(subject).to eq 20
+ end
+ end
+
+ context 'with a return item amount that should round up' do
+ let(:reimbursement) { Spree::Reimbursement.new }
+
+ subject { reimbursement.calculated_total }
+
+ before do
+ reimbursement.return_items << Spree::ReturnItem.new(pre_tax_amount: 19.998)
+ end
+
+ it 'rounds up' do
+ expect(subject).to eq 20
+ end
+ end
+ end
+
+ describe '.build_from_customer_return' do
+ let(:customer_return) { create(:customer_return, line_items_count: 5) }
+
+ let!(:pending_return_item) { customer_return.return_items.first.tap { |ri| ri.update!(acceptance_status: 'pending') } }
+ let!(:accepted_return_item) { customer_return.return_items.second.tap(&:accept!) }
+ let!(:rejected_return_item) { customer_return.return_items.third.tap(&:reject!) }
+ let!(:manual_intervention_return_item) { customer_return.return_items.fourth.tap(&:require_manual_intervention!) }
+ let!(:already_reimbursed_return_item) { customer_return.return_items.fifth }
+
+ let!(:previous_reimbursement) { create(:reimbursement, order: customer_return.order, return_items: [already_reimbursed_return_item]) }
+
+ subject { Spree::Reimbursement.build_from_customer_return(customer_return) }
+
+ it 'connects to the accepted return items' do
+ expect(subject.return_items.to_a).to eq [accepted_return_item]
+ end
+
+ it 'connects to the order' do
+ expect(subject.order).to eq customer_return.order
+ end
+
+ it 'connects to the customer_return' do
+ expect(subject.customer_return).to eq customer_return
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_tax_calculator_spec.rb b/core/spec/models/spree/reimbursement_tax_calculator_spec.rb
new file mode 100644
index 00000000000..86f4a177c1a
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_tax_calculator_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Spree::ReimbursementTaxCalculator, :type => :model do
+
+ let!(:tax_rate) { nil }
+
+ let(:reimbursement) { create(:reimbursement, return_items_count: 1) }
+ let(:return_item) { reimbursement.return_items.first }
+ let(:line_item) { return_item.inventory_unit.line_item }
+
+ subject do
+ Spree::ReimbursementTaxCalculator.call(reimbursement)
+ end
+
+ context 'without taxes' do
+ let!(:tax_rate) { nil }
+
+ it 'leaves the return items additional_tax_total and included_tax_total at zero' do
+ subject
+
+ expect(return_item.additional_tax_total).to eq 0
+ expect(return_item.included_tax_total).to eq 0
+ end
+ end
+
+ context 'with additional tax' do
+ let!(:tax_rate) { create(:tax_rate, name: "Sales Tax", amount: 0.10, included_in_price: false, zone: tax_zone) }
+ let(:tax_zone) { create(:zone, default_tax: true) }
+
+ it 'sets additional_tax_total on the return items' do
+ subject
+ return_item.reload
+
+ expect(return_item.additional_tax_total).to be > 0
+ expect(return_item.additional_tax_total).to eq line_item.additional_tax_total
+ end
+ end
+
+ context 'with included tax' do
+ let!(:tax_rate) { create(:tax_rate, name: "VAT Tax", amount: 0.1, included_in_price: true, zone: tax_zone) }
+ let(:tax_zone) { create(:zone, default_tax: true) }
+
+ it 'sets included_tax_total on the return items' do
+ subject
+ return_item.reload
+
+ expect(return_item.included_tax_total).to be < 0
+ expect(return_item.included_tax_total).to eq line_item.included_tax_total
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_type/credit_spec.rb b/core/spec/models/spree/reimbursement_type/credit_spec.rb
new file mode 100644
index 00000000000..4b46c28abbc
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_type/credit_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+module Spree
+ describe ReimbursementType::Credit, :type => :model do
+ let(:reimbursement) { create(:reimbursement, return_items_count: 1) }
+ let(:return_item) { reimbursement.return_items.first }
+ let(:payment) { reimbursement.order.payments.first }
+ let(:simulate) { false }
+ let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }
+ let(:creditable) { DummyCreditable.new(amount: 99.99) }
+
+ class DummyCreditable < Spree::Base
+ attr_accessor :amount
+ self.table_name = 'spree_payments' # Your creditable class should not use this table
+ end
+
+ subject { Spree::ReimbursementType::Credit.reimburse(reimbursement, [return_item], simulate)}
+
+ before do
+ reimbursement.update!(total: reimbursement.calculated_total)
+ allow(Spree::ReimbursementType::Credit).to receive(:create_creditable).and_return(creditable)
+ end
+
+ describe '.reimburse' do
+ context 'simulate is true' do
+ let(:simulate) { true }
+
+ it 'creates one readonly lump credit for all outstanding balance payable to the customer' do
+ expect(subject.map(&:class)).to eq [Spree::Reimbursement::Credit]
+ expect(subject.map(&:readonly?)).to eq [true]
+ expect(subject.sum(&:amount)).to eq reimbursement.return_items.to_a.sum(&:total)
+ end
+
+ it 'does not save to the database' do
+ expect { subject }.to_not change { Spree::Reimbursement::Credit.count }
+ end
+ end
+
+ context 'simulate is false' do
+ let(:simulate) { false }
+
+ before do
+ expect(creditable).to receive(:save).and_return(true)
+ end
+
+ it 'creates one lump credit for all outstanding balance payable to the customer' do
+ expect { subject }.to change { Spree::Reimbursement::Credit.count }.by(1)
+ expect(subject.sum(&:amount)).to eq reimbursement.return_items.to_a.sum(&:total)
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_type/exchange_spec.rb b/core/spec/models/spree/reimbursement_type/exchange_spec.rb
new file mode 100644
index 00000000000..ed3cd90381f
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_type/exchange_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+module Spree
+ describe ReimbursementType::Exchange, :type => :model do
+ describe '.reimburse' do
+ let(:reimbursement) { create(:reimbursement, return_items_count: 1) }
+ let(:return_items) { reimbursement.return_items }
+ let(:new_exchange) { double("Exchange") }
+ let(:simulate) { true }
+
+ subject { Spree::ReimbursementType::Exchange.reimburse(reimbursement, return_items, simulate)}
+
+ context 'return items are supplied' do
+ before do
+ expect(Spree::Exchange).to receive(:new).with(reimbursement.order, return_items).and_return(new_exchange)
+ end
+
+ context "simulate is true" do
+
+ it 'does not perform an exchange and returns the exchange object' do
+ expect(new_exchange).not_to receive(:perform!)
+ expect(subject).to eq [new_exchange]
+ end
+ end
+
+ context "simulate is false" do
+ let(:simulate) { false }
+
+ it 'performs an exchange and returns the exchange object' do
+ expect(new_exchange).to receive(:perform!)
+ expect(subject).to eq [new_exchange]
+ end
+ end
+ end
+
+ context "no return items are supplied" do
+ let(:return_items) { [] }
+
+ it 'does not perform an exchange and returns an empty array' do
+ expect(new_exchange).not_to receive(:perform!)
+ expect(subject).to eq []
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/reimbursement_type/original_payment_spec.rb b/core/spec/models/spree/reimbursement_type/original_payment_spec.rb
new file mode 100644
index 00000000000..08384bef4ca
--- /dev/null
+++ b/core/spec/models/spree/reimbursement_type/original_payment_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+module Spree
+ describe ReimbursementType::OriginalPayment, :type => :model do
+ let(:reimbursement) { create(:reimbursement, return_items_count: 1) }
+ let(:return_item) { reimbursement.return_items.first }
+ let(:payment) { reimbursement.order.payments.first }
+ let(:simulate) { false }
+ let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }
+
+ subject { Spree::ReimbursementType::OriginalPayment.reimburse(reimbursement, [return_item], simulate)}
+
+ before { reimbursement.update!(total: reimbursement.calculated_total) }
+
+ describe ".reimburse" do
+ context "simulate is true" do
+ let(:simulate) { true }
+
+ it "returns an array of readonly refunds" do
+ expect(subject.map(&:class)).to eq [Spree::Refund]
+ expect(subject.map(&:readonly?)).to eq [true]
+ end
+ end
+
+ context "simulate is false" do
+ it 'performs the refund' do
+ expect {
+ subject
+ }.to change { payment.refunds.count }.by(1)
+ expect(payment.refunds.sum(:amount)).to eq reimbursement.return_items.to_a.sum(&:total)
+ end
+ end
+
+ context 'when no credit is allowed on the payment' do
+ before do
+ expect_any_instance_of(Spree::Payment).to receive(:credit_allowed).and_return 0
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to eq []
+ end
+ end
+
+ context 'when a payment is negative' do
+ before do
+ expect_any_instance_of(Spree::Payment).to receive(:amount).and_return -100
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to eq []
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_authorization_spec.rb b/core/spec/models/spree/return_authorization_spec.rb
new file mode 100644
index 00000000000..466e8e48b94
--- /dev/null
+++ b/core/spec/models/spree/return_authorization_spec.rb
@@ -0,0 +1,250 @@
+require 'spec_helper'
+
+describe Spree::ReturnAuthorization, :type => :model do
+ let(:order) { create(:shipped_order) }
+ let(:stock_location) { create(:stock_location) }
+ let(:rma_reason) { create(:return_authorization_reason) }
+ let(:inventory_unit_1) { order.inventory_units.first }
+
+ let(:variant) { order.variants.first }
+ let(:return_authorization) do
+ Spree::ReturnAuthorization.new(order: order,
+ stock_location_id: stock_location.id,
+ return_authorization_reason_id: rma_reason.id)
+ end
+
+ context "save" do
+ let(:order) { Spree::Order.create }
+
+ it "should be invalid when order has no inventory units" do
+ return_authorization.save
+ expect(return_authorization.errors[:order]).to eq(["has no shipped units"])
+ end
+
+ context "expedited exchanges are configured" do
+ let(:order) { create(:shipped_order, line_items_count: 2) }
+ let(:exchange_return_item) { create(:exchange_return_item, inventory_unit: order.inventory_units.first) }
+ let(:return_item) { create(:return_item, inventory_unit: order.inventory_units.last) }
+ subject { create(:return_authorization, order: order, return_items: [exchange_return_item, return_item]) }
+
+ before do
+ @expediteted_exchanges_config = Spree::Config[:expedited_exchanges]
+ Spree::Config[:expedited_exchanges] = true
+ @pre_exchange_hooks = subject.class.pre_expedited_exchange_hooks
+ end
+
+ after do
+ Spree::Config[:expedited_exchanges] = @expediteted_exchanges_config
+ subject.class.pre_expedited_exchange_hooks = @pre_exchange_hooks
+ end
+
+ context "no items to exchange" do
+ subject { create(:return_authorization, order: order) }
+
+ it "does not create a reimbursement" do
+ expect{subject.save}.to_not change { Spree::Reimbursement.count }
+ end
+ end
+
+ context "items to exchange" do
+ it "calls pre_expedited_exchange hooks with the return items to exchange" do
+ hook = double(:as_null_object)
+ expect(hook).to receive(:call).with [exchange_return_item]
+ subject.class.pre_expedited_exchange_hooks = [hook]
+ subject.save
+ end
+
+ it "attempts to accept all return items requiring exchange" do
+ expect(exchange_return_item).to receive :attempt_accept
+ expect(return_item).not_to receive :attempt_accept
+ subject.save
+ end
+
+ it "performs an exchange reimbursement for the exchange return items" do
+ subject.save
+ reimbursement = Spree::Reimbursement.last
+ expect(reimbursement.order).to eq subject.order
+ expect(reimbursement.return_items).to eq [exchange_return_item]
+ expect(exchange_return_item.reload.exchange_shipment).to be_present
+ end
+
+ context "the reimbursement fails" do
+ before do
+ allow_any_instance_of(Spree::Reimbursement).to receive(:save) { false }
+ allow_any_instance_of(Spree::Reimbursement).to receive(:errors) { double(full_messages: "foo") }
+ end
+
+ it "puts errors on the return authorization" do
+ subject.save
+ expect(subject.errors[:base]).to include "foo"
+ end
+ end
+ end
+
+ end
+ end
+
+ describe ".before_create" do
+ describe "#generate_number" do
+ context "number is assigned" do
+ let(:return_authorization) { Spree::ReturnAuthorization.new(number: '123') }
+
+ it "should return the assigned number" do
+ return_authorization.save
+ expect(return_authorization.number).to eq('123')
+ end
+ end
+
+ context "number is not assigned" do
+ let(:return_authorization) { Spree::ReturnAuthorization.new(number: nil) }
+
+ before { allow(return_authorization).to receive_messages valid?: true }
+
+ it "should assign number with random RA number" do
+ return_authorization.save
+ expect(return_authorization.number).to match(/RA\d{9}/)
+ end
+ end
+ end
+ end
+
+ context "#currency" do
+ before { allow(order).to receive(:currency) { "ABC" } }
+ it "returns the order currency" do
+ expect(return_authorization.currency).to eq("ABC")
+ end
+ end
+
+ describe "#pre_tax_total" do
+ let(:pre_tax_amount_1) { 15.0 }
+ let!(:return_item_1) { create(:return_item, return_authorization: return_authorization, pre_tax_amount: pre_tax_amount_1) }
+
+ let(:pre_tax_amount_2) { 50.0 }
+ let!(:return_item_2) { create(:return_item, return_authorization: return_authorization, pre_tax_amount: pre_tax_amount_2) }
+
+ let(:pre_tax_amount_3) { 5.0 }
+ let!(:return_item_3) { create(:return_item, return_authorization: return_authorization, pre_tax_amount: pre_tax_amount_3) }
+
+ subject { return_authorization.pre_tax_total }
+
+ it "sums it's associated return_item's pre-tax amounts" do
+ expect(subject).to eq (pre_tax_amount_1 + pre_tax_amount_2 + pre_tax_amount_3)
+ end
+ end
+
+ describe "#display_pre_tax_total" do
+ it "returns a Spree::Money" do
+ allow(return_authorization).to receive_messages(pre_tax_total: 21.22)
+ expect(return_authorization.display_pre_tax_total).to eq(Spree::Money.new(21.22))
+ end
+ end
+
+ describe "#refundable_amount" do
+ let(:weighted_line_item_pre_tax_amount) { 5.0 }
+ let(:line_item_count) { return_authorization.order.line_items.count }
+
+ subject { return_authorization.refundable_amount }
+
+ before do
+ return_authorization.order.line_items.update_all(pre_tax_amount: weighted_line_item_pre_tax_amount)
+ return_authorization.order.update_attribute(:promo_total, promo_total)
+ end
+
+ context "no promotions" do
+ let(:promo_total) { 0.0 }
+ it "returns the pre-tax line item total" do
+ expect(subject).to eq (weighted_line_item_pre_tax_amount * line_item_count)
+ end
+ end
+
+ context "promotions" do
+ let(:promo_total) { -10.0 }
+ it "returns the pre-tax line item total minus the order level promotion value" do
+ expect(subject).to eq (weighted_line_item_pre_tax_amount * line_item_count) + promo_total
+ end
+ end
+ end
+
+ describe "#customer_returned_items?" do
+ before do
+ allow_any_instance_of(Spree::Order).to receive_messages(return!: true)
+ end
+
+ subject { return_authorization.customer_returned_items? }
+
+ context "has associated customer returns" do
+ let(:customer_return) { create(:customer_return) }
+ let(:return_authorization) { customer_return.return_authorizations.first }
+
+ it "returns true" do
+ expect(subject).to eq true
+ end
+ end
+
+ context "does not have associated customer returns" do
+ let(:return_authorization) { create(:return_authorization) }
+
+ it "returns false" do
+ expect(subject).to eq false
+ end
+ end
+ end
+
+ describe 'cancel_return_items' do
+ let(:return_authorization) { create(:return_authorization, return_items: return_items) }
+ let(:return_items) { [return_item] }
+ let(:return_item) { create(:return_item) }
+
+ subject {
+ return_authorization.cancel!
+ }
+
+ it 'cancels the associated return items' do
+ subject
+ expect(return_item.reception_status).to eq 'cancelled'
+ end
+
+ context 'some return items cannot be cancelled' do
+ let(:return_items) { [return_item, return_item_2] }
+ let(:return_item_2) { create(:return_item, reception_status: 'received') }
+
+ it 'cancels those that can be cancelled' do
+ subject
+ expect(return_item.reception_status).to eq 'cancelled'
+ expect(return_item_2.reception_status).to eq 'received'
+ end
+ end
+ end
+
+ describe '#can_cancel?' do
+ subject { create(:return_authorization, return_items: return_items).can_cancel? }
+ let(:return_items) { [return_item_1, return_item_2] }
+ let(:return_item_1) { create(:return_item) }
+ let(:return_item_2) { create(:return_item) }
+
+ context 'all items can be cancelled' do
+ it 'returns true' do
+ expect(subject).to eq true
+ end
+ end
+
+ context 'at least one return item can be cancelled' do
+ let(:return_item_2) { create(:return_item, reception_status: 'received') }
+
+ it { should eq true }
+ end
+
+ context 'no items can be cancelled' do
+ let(:return_item_1) { create(:return_item, reception_status: 'received') }
+ let(:return_item_2) { create(:return_item, reception_status: 'received') }
+
+ it { should eq false }
+ end
+
+ context 'when return_authorization has no return_items' do
+ let(:return_items) { [] }
+
+ it { should eq true }
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item/eligibility_validator/default_spec.rb b/core/spec/models/spree/return_item/eligibility_validator/default_spec.rb
new file mode 100644
index 00000000000..cf463319f17
--- /dev/null
+++ b/core/spec/models/spree/return_item/eligibility_validator/default_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Spree::ReturnItem::EligibilityValidator::Default, :type => :model do
+ let(:return_item) { create(:return_item) }
+ let(:validator) { Spree::ReturnItem::EligibilityValidator::Default.new(return_item) }
+
+ let(:time_eligibility_class) { double("TimeEligibilityValidatorClass") }
+ let(:rma_eligibility_class) { double("RMAEligibilityValidatorClass") }
+
+ let(:time_eligibility_instance) { double(errors: time_error) }
+ let(:rma_eligibility_instance) { double(errors: rma_error) }
+
+ let(:time_error) {{}}
+ let(:rma_error) {{}}
+
+ before do
+ validator.permitted_eligibility_validators = [ time_eligibility_class, rma_eligibility_class ]
+
+ expect(time_eligibility_class).to receive(:new).and_return(time_eligibility_instance)
+ expect(rma_eligibility_class).to receive(:new).and_return(rma_eligibility_instance)
+ end
+
+ describe "#eligible_for_return?" do
+ subject { validator.eligible_for_return? }
+
+ it "checks that all permitted eligibility validators are eligible for return" do
+ expect(time_eligibility_instance).to receive(:eligible_for_return?).and_return(true)
+ expect(rma_eligibility_instance).to receive(:eligible_for_return?).and_return(true)
+
+ expect(subject).to be true
+ end
+ end
+
+ describe "#requires_manual_intervention?" do
+ subject { validator.requires_manual_intervention? }
+
+ context "any of the permitted eligibility validators require manual intervention" do
+ it "returns true" do
+ expect(time_eligibility_instance).to receive(:requires_manual_intervention?).and_return(false)
+ expect(rma_eligibility_instance).to receive(:requires_manual_intervention?).and_return(true)
+
+ expect(subject).to be true
+ end
+ end
+
+ context "no permitted eligibility validators require manual intervention" do
+ it "returns false" do
+ expect(time_eligibility_instance).to receive(:requires_manual_intervention?).and_return(false)
+ expect(rma_eligibility_instance).to receive(:requires_manual_intervention?).and_return(false)
+
+ expect(subject).to be false
+ end
+ end
+ end
+
+ describe "#errors" do
+ subject { validator.errors }
+
+ context "the validator errors are empty" do
+ it "returns an empty hash" do
+ expect(subject).to eq({})
+ end
+ end
+
+ context "the validators have errors" do
+ let(:time_error) { { time: time_error_text }}
+ let(:rma_error) { { rma: rma_error_text }}
+
+ let(:time_error_text) { "Time eligibility error" }
+ let(:rma_error_text) { "RMA eligibility error" }
+
+ it "gathers all errors from permitted eligibility validators into a single errors hash" do
+ expect(subject).to eq({time: time_error_text, rma: rma_error_text})
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb b/core/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb
new file mode 100644
index 00000000000..10c18b79fc7
--- /dev/null
+++ b/core/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Spree::ReturnItem::EligibilityValidator::OrderCompleted do
+ let(:inventory_unit) { create(:inventory_unit, order: order) }
+ let(:return_item) { create(:return_item, inventory_unit: inventory_unit) }
+ let(:validator) { Spree::ReturnItem::EligibilityValidator::OrderCompleted.new(return_item) }
+
+ describe "#eligible_for_return?" do
+ subject { validator.eligible_for_return? }
+
+ context "the order was completed" do
+ let(:order) { create(:completed_order_with_totals) }
+
+ it "returns true" do
+ expect(subject).to be true
+ end
+ end
+
+ context "the order is not completed" do
+ let(:order) { create(:order) }
+
+ it "returns false" do
+ expect(subject).to be false
+ end
+
+ it "sets an error" do
+ subject
+ expect(validator.errors[:order_not_completed]).to eq Spree.t('return_item_order_not_completed')
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb b/core/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb
new file mode 100644
index 00000000000..bee343e3e79
--- /dev/null
+++ b/core/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Spree::ReturnItem::EligibilityValidator::RMARequired, :type => :model do
+ let(:return_item) { create(:return_item) }
+ let(:validator) { Spree::ReturnItem::EligibilityValidator::RMARequired.new(return_item) }
+
+ describe "#eligible_for_return?" do
+ subject { validator.eligible_for_return? }
+
+ context "there is an rma on the return item" do
+ it "returns true" do
+ expect(subject).to be true
+ end
+ end
+
+ context "there is no rma on the return item" do
+ before { allow(return_item).to receive(:return_authorization).and_return(nil) }
+
+ it "returns false" do
+ expect(subject).to be false
+ end
+
+ it "sets an error" do
+ subject
+ expect(validator.errors[:rma_required]).to eq Spree.t('return_item_rma_ineligible')
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb b/core/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb
new file mode 100644
index 00000000000..e8341b04029
--- /dev/null
+++ b/core/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Spree::ReturnItem::EligibilityValidator::TimeSincePurchase, :type => :model do
+ let(:inventory_unit) { create(:inventory_unit, order: create(:shipped_order)) }
+ let(:return_item) { create(:return_item, inventory_unit: inventory_unit) }
+ let(:validator) { Spree::ReturnItem::EligibilityValidator::TimeSincePurchase.new(return_item) }
+
+ describe "#eligible_for_return?" do
+ subject { validator.eligible_for_return? }
+
+ context "it is within the return timeframe" do
+ it "returns true" do
+ completed_at = return_item.inventory_unit.order.completed_at - (Spree::Config[:return_eligibility_number_of_days].days / 2)
+ return_item.inventory_unit.order.update_attributes(completed_at: completed_at)
+ expect(subject).to be true
+ end
+ end
+
+ context "it is past the return timeframe" do
+ before do
+ completed_at = return_item.inventory_unit.order.completed_at - Spree::Config[:return_eligibility_number_of_days].days - 1.day
+ return_item.inventory_unit.order.update_attributes(completed_at: completed_at)
+ end
+
+ it "returns false" do
+ expect(subject).to be false
+ end
+
+ it "sets an error" do
+ subject
+ expect(validator.errors[:number_of_days]).to eq Spree.t('return_item_time_period_ineligible')
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb b/core/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb
new file mode 100644
index 00000000000..70f368298ad
--- /dev/null
+++ b/core/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+module Spree
+ module ReturnItem::ExchangeVariantEligibility
+ describe SameOptionValue, :type => :model do
+ describe ".eligible_variants" do
+ let(:color_option_type) { create(:option_type, name: "color") }
+ let(:waist_option_type) { create(:option_type, name: "waist") }
+ let(:inseam_option_type) { create(:option_type, name: "inseam") }
+
+ let(:blue_option_value) { create(:option_value, name: "blue", option_type: color_option_type) }
+ let(:red_option_value) { create(:option_value, name: "red", option_type: color_option_type) }
+
+ let(:three_two_waist_option_value) { create(:option_value, name: 32, option_type: waist_option_type) }
+ let(:three_four_waist_option_value) { create(:option_value, name: 34, option_type: waist_option_type) }
+
+ let(:three_zero_inseam_option_value) { create(:option_value, name: 30, option_type: inseam_option_type) }
+ let(:three_one_inseam_option_value) { create(:option_value, name: 31, option_type: inseam_option_type) }
+
+ let(:product) { create(:product, option_types: [color_option_type, waist_option_type, inseam_option_type]) }
+
+ let!(:variant) { create(:variant, product: product, option_values: [blue_option_value, three_two_waist_option_value, three_zero_inseam_option_value]) }
+ let!(:same_option_values_variant) { create(:variant, product: product, option_values: [blue_option_value, three_two_waist_option_value, three_one_inseam_option_value]) }
+ let!(:different_color_option_value_variant) { create(:variant, product: product, option_values: [red_option_value, three_two_waist_option_value, three_one_inseam_option_value]) }
+ let!(:different_waist_option_value_variant) { create(:variant, product: product, option_values: [blue_option_value, three_four_waist_option_value, three_one_inseam_option_value]) }
+
+ before do
+ @original_option_type_restrictions = SameOptionValue.option_type_restrictions
+ SameOptionValue.option_type_restrictions = ["color", "waist"]
+ end
+
+ after { SameOptionValue.option_type_restrictions = @original_option_type_restrictions }
+
+ subject { SameOptionValue.eligible_variants(variant.reload) }
+
+ it "returns all other variants for the same product with the same option value for the specified option type" do
+ Spree::StockItem.update_all(count_on_hand: 10)
+
+ expect(subject.sort).to eq [variant, same_option_values_variant].sort
+ end
+
+ it "does not return variants for another product" do
+ other_product_variant = create(:variant)
+ expect(subject).not_to include other_product_variant
+ end
+
+ context "no option value restrictions are specified" do
+ before do
+ @original_option_type_restrictions = SameOptionValue.option_type_restrictions
+ SameOptionValue.option_type_restrictions = []
+ end
+
+ after { SameOptionValue.option_type_restrictions = @original_option_type_restrictions }
+
+ it "returns all variants for the product" do
+ Spree::StockItem.update_all(count_on_hand: 10)
+
+ expect(subject.sort).to eq [variant, same_option_values_variant, different_waist_option_value_variant, different_color_option_value_variant].sort
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/core/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb b/core/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb
new file mode 100644
index 00000000000..d5d06454e3d
--- /dev/null
+++ b/core/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+module Spree
+ module ReturnItem::ExchangeVariantEligibility
+ describe SameProduct, :type => :model do
+ describe ".eligible_variants" do
+
+ context "product has no variants" do
+ it "returns the master variant for the same product" do
+ product = create(:product)
+ product.master.stock_items.first.update_column(:count_on_hand, 10)
+
+ expect(SameProduct.eligible_variants(product.master)).to eq [product.master]
+ end
+ end
+
+ context "product has variants" do
+ it "returns all variants for the same product" do
+ product = create(:product, variants: 3.times.map { create(:variant) })
+ product.variants.map { |v| v.stock_items.first.update_column(:count_on_hand, 10) }
+
+ expect(SameProduct.eligible_variants(product.variants.first).sort).to eq product.variants.sort
+ end
+ end
+
+ it "does not return variants for another product" do
+ variant = create(:variant)
+ other_product_variant = create(:variant)
+ expect(SameProduct.eligible_variants(variant)).not_to include other_product_variant
+ end
+
+ it "only returns variants that are on hand" do
+ product = create(:product, variants: 2.times.map { create(:variant) })
+ in_stock_variant = product.variants.first
+
+ in_stock_variant.stock_items.first.update_column(:count_on_hand, 10)
+ expect(SameProduct.eligible_variants(in_stock_variant)).to eq [in_stock_variant]
+ end
+ end
+
+ end
+ end
+end
diff --git a/core/spec/models/spree/return_item_spec.rb b/core/spec/models/spree/return_item_spec.rb
new file mode 100644
index 00000000000..c072165162c
--- /dev/null
+++ b/core/spec/models/spree/return_item_spec.rb
@@ -0,0 +1,599 @@
+require 'spec_helper'
+
+shared_examples "an invalid state transition" do |status, expected_status|
+ let(:status) { status }
+
+ it "cannot transition to #{expected_status}" do
+ expect { subject }.to raise_error(StateMachine::InvalidTransition)
+ end
+end
+
+describe Spree::ReturnItem, :type => :model do
+
+ all_reception_statuses = Spree::ReturnItem.state_machines[:reception_status].states.map(&:name).map(&:to_s)
+ all_acceptance_statuses = Spree::ReturnItem.state_machines[:acceptance_status].states.map(&:name).map(&:to_s)
+
+ before do
+ allow_any_instance_of(Spree::Order).to receive_messages(return!: true)
+ end
+
+ describe '#receive!' do
+ let(:now) { Time.now }
+ let(:inventory_unit) { create(:inventory_unit, state: 'shipped') }
+ let(:return_item) { create(:return_item, inventory_unit: inventory_unit) }
+
+ before do
+ inventory_unit.update_attributes!(state: 'shipped')
+ return_item.update_attributes!(reception_status: 'awaiting')
+ allow(return_item).to receive(:eligible_for_return?).and_return(true)
+ end
+
+ subject { return_item.receive! }
+
+
+ it 'returns the inventory unit' do
+ subject
+ expect(inventory_unit.reload.state).to eq 'returned'
+ end
+
+ it 'attempts to accept the return item' do
+ expect(return_item).to receive(:attempt_accept)
+ subject
+ end
+
+ context 'with a stock location' do
+ let(:stock_item) { inventory_unit.find_stock_item }
+ let!(:customer_return) { create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id) }
+
+ before do
+ inventory_unit.update_attributes!(state: 'shipped')
+ return_item.update_attributes!(reception_status: 'awaiting')
+ end
+
+ it 'increases the count on hand' do
+ expect { subject }.to change { stock_item.reload.count_on_hand }.by(1)
+ end
+
+ context 'when variant does not track inventory' do
+ before do
+ inventory_unit.update_attributes!(state: 'shipped')
+ inventory_unit.variant.update_attributes!(track_inventory: false)
+ return_item.update_attributes!(reception_status: 'awaiting')
+ end
+
+ it 'does not increase the count on hand' do
+ expect { subject }.to_not change { stock_item.reload.count_on_hand }
+ end
+ end
+
+ context 'when the restock_inventory preference is false' do
+ before do
+ Spree::Config[:restock_inventory] = false
+ end
+
+ it 'does not increase the count on hand' do
+ expect { subject }.to_not change { stock_item.reload.count_on_hand }
+ end
+ end
+ end
+ end
+
+ describe "#display_pre_tax_amount" do
+ let(:pre_tax_amount) { 21.22 }
+ let(:return_item) { build(:return_item, pre_tax_amount: pre_tax_amount) }
+
+ it "returns a Spree::Money" do
+ expect(return_item.display_pre_tax_amount).to eq(Spree::Money.new(pre_tax_amount))
+ end
+ end
+
+ describe ".default_refund_amount_calculator" do
+ it "defaults to the default refund amount calculator" do
+ expect(Spree::ReturnItem.refund_amount_calculator).to eq Spree::Calculator::Returns::DefaultRefundAmount
+ end
+ end
+
+ describe "pre_tax_amount calculations on create" do
+ let(:inventory_unit) { build(:inventory_unit) }
+ before { subject.save! }
+
+ context "pre tax amount is not specified" do
+ subject { build(:return_item, inventory_unit: inventory_unit) }
+
+ context "not an exchange" do
+ it { expect(subject.pre_tax_amount).to eq Spree::Calculator::Returns::DefaultRefundAmount.new.compute(subject) }
+ end
+
+ context "an exchange" do
+ subject { build(:exchange_return_item) }
+
+ it { expect(subject.pre_tax_amount).to eq 0.0 }
+ end
+ end
+
+ context "pre tax amount is specified" do
+ subject { build(:return_item, inventory_unit: inventory_unit, pre_tax_amount: 100) }
+
+ it { expect(subject.pre_tax_amount).to eq 100 }
+ end
+ end
+
+ describe ".from_inventory_unit" do
+ let(:inventory_unit) { build(:inventory_unit) }
+
+ subject { Spree::ReturnItem.from_inventory_unit(inventory_unit) }
+
+ context "with a cancelled return item" do
+ let!(:return_item) { create(:return_item, inventory_unit: inventory_unit, reception_status: 'cancelled') }
+
+ it { is_expected.not_to be_persisted }
+ end
+
+ context "with a non-cancelled return item" do
+ let!(:return_item) { create(:return_item, inventory_unit: inventory_unit) }
+
+ it { is_expected.to be_persisted }
+ end
+ end
+
+ describe "reception_status state_machine" do
+ subject(:return_item) { create(:return_item) }
+
+ it "starts off in the awaiting state" do
+ expect(return_item).to be_awaiting
+ end
+ end
+
+ describe "acceptance_status state_machine" do
+ subject(:return_item) { create(:return_item) }
+
+ it "starts off in the pending state" do
+ expect(return_item).to be_pending
+ end
+ end
+
+ describe "#receive" do
+ let(:inventory_unit) { create(:inventory_unit, order: create(:shipped_order)) }
+ let(:return_item) { create(:return_item, reception_status: status, inventory_unit: inventory_unit) }
+
+ subject { return_item.receive! }
+
+ context "awaiting status" do
+ let(:status) { 'awaiting' }
+
+ before do
+ expect(return_item.inventory_unit).to receive(:return!)
+ end
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_received
+ end
+ end
+
+ (all_reception_statuses - ['awaiting']).each do |invalid_transition_status|
+ context "return_item has a reception status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'received'
+ end
+ end
+ end
+
+ describe "#cancel" do
+ let(:return_item) { create(:return_item, reception_status: status) }
+
+ subject { return_item.cancel! }
+
+ context "awaiting status" do
+ let(:status) { 'awaiting' }
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_cancelled
+ end
+ end
+
+ (all_reception_statuses - ['awaiting']).each do |invalid_transition_status|
+ context "return_item has a reception status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'cancelled'
+ end
+ end
+ end
+
+ describe "#give" do
+ let(:return_item) { create(:return_item, reception_status: status) }
+
+ subject { return_item.give! }
+
+ context "awaiting status" do
+ let(:status) { 'awaiting' }
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_given_to_customer
+ end
+ end
+
+ (all_reception_statuses - ['awaiting']).each do |invalid_transition_status|
+ context "return_item has a reception status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'give_to_customer'
+ end
+ end
+ end
+
+ describe "#attempt_accept" do
+ let(:return_item) { create(:return_item, acceptance_status: status) }
+ let(:validator_errors) { {} }
+ let(:validator_double) { double(errors: validator_errors) }
+
+ subject { return_item.attempt_accept! }
+
+ before do
+ allow(return_item).to receive(:validator).and_return(validator_double)
+ end
+
+ context "pending status" do
+ let(:status) { 'pending' }
+
+ before do
+ allow(return_item).to receive(:eligible_for_return?).and_return(true)
+ subject
+ end
+
+ it "transitions successfully" do
+ expect(return_item).to be_accepted
+ end
+
+ it "has no acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to be_empty
+ end
+ end
+
+ (all_acceptance_statuses - ['accepted', 'pending']).each do |invalid_transition_status|
+ context "return_item has an acceptance status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'accepted'
+ end
+ end
+
+ context "not eligible for return" do
+ let(:status) { 'pending' }
+ let(:validator_errors) { { number_of_days: "Return Item is outside the eligible time period" } }
+
+ before do
+ allow(return_item).to receive(:eligible_for_return?).and_return(false)
+ end
+
+ context "manual intervention required" do
+ before do
+ allow(return_item).to receive(:requires_manual_intervention?).and_return(true)
+ subject
+ end
+
+ it "transitions to manual intervention required" do
+ expect(return_item).to be_manual_intervention_required
+ end
+
+ it "sets the acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to eq validator_errors
+ end
+ end
+
+ context "manual intervention not required" do
+ before do
+ allow(return_item).to receive(:requires_manual_intervention?).and_return(false)
+ subject
+ end
+
+ it "transitions to rejected" do
+ expect(return_item).to be_rejected
+ end
+
+ it "sets the acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to eq validator_errors
+ end
+ end
+ end
+ end
+
+ describe "#reject" do
+ let(:return_item) { create(:return_item, acceptance_status: status) }
+
+ subject { return_item.reject! }
+
+ context "pending status" do
+ let(:status) { 'pending' }
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_rejected
+ end
+
+ it "has no acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to be_empty
+ end
+ end
+
+ (all_acceptance_statuses - ['accepted', 'pending', 'manual_intervention_required']).each do |invalid_transition_status|
+ context "return_item has an acceptance status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'rejected'
+ end
+ end
+ end
+
+ describe "#accept" do
+ let(:return_item) { create(:return_item, acceptance_status: status) }
+
+ subject { return_item.accept! }
+
+ context "pending status" do
+ let(:status) { 'pending' }
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_accepted
+ end
+
+ it "has no acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to be_empty
+ end
+ end
+
+ (all_acceptance_statuses - ['accepted', 'pending', 'manual_intervention_required']).each do |invalid_transition_status|
+ context "return_item has an acceptance status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'accepted'
+ end
+ end
+ end
+
+ describe "#require_manual_intervention" do
+ let(:return_item) { create(:return_item, acceptance_status: status) }
+
+ subject { return_item.require_manual_intervention! }
+
+ context "pending status" do
+ let(:status) { 'pending' }
+
+ before { subject }
+
+ it "transitions successfully" do
+ expect(return_item).to be_manual_intervention_required
+ end
+
+ it "has no acceptance status errors" do
+ expect(return_item.acceptance_status_errors).to be_empty
+ end
+ end
+
+ (all_acceptance_statuses - ['accepted', 'pending', 'manual_intervention_required']).each do |invalid_transition_status|
+ context "return_item has an acceptance status of #{invalid_transition_status}" do
+ it_behaves_like "an invalid state transition", invalid_transition_status, 'manual_intervention_required'
+ end
+ end
+ end
+
+ describe 'validity for reimbursements' do
+ let(:return_item) { create(:return_item, acceptance_status: acceptance_status) }
+ let(:acceptance_status) { 'pending' }
+
+ before { return_item.reimbursement = build(:reimbursement) }
+
+ subject { return_item }
+
+ context 'when acceptance_status is accepted' do
+ let(:acceptance_status) { 'accepted' }
+
+ it 'is valid' do
+ expect(subject).to be_valid
+ end
+ end
+
+ context 'when acceptance_status is accepted' do
+ let(:acceptance_status) { 'pending' }
+
+ it 'is valid' do
+ expect(subject).to_not be_valid
+ expect(subject.errors.messages).to eq({reimbursement: [I18n.t(:cannot_be_associated_unless_accepted, scope: 'activerecord.errors.models.spree/return_item.attributes.reimbursement')]})
+ end
+ end
+ end
+
+ describe "#exchange_requested?" do
+ context "exchange variant exists" do
+ before { allow(subject).to receive(:exchange_variant) { mock_model(Spree::Variant) } }
+ it { expect(subject.exchange_requested?).to eq true }
+ end
+ context "exchange variant does not exist" do
+ before { allow(subject).to receive(:exchange_variant) { nil } }
+ it { expect(subject.exchange_requested?).to eq false }
+ end
+ end
+
+ describe "#exchange_processed?" do
+ context "exchange inventory unit exists" do
+ before { allow(subject).to receive(:exchange_inventory_unit) { mock_model(Spree::InventoryUnit) } }
+ it { expect(subject.exchange_processed?).to eq true }
+ end
+ context "exchange inventory unit does not exist" do
+ before { allow(subject).to receive(:exchange_inventory_unit) { nil } }
+ it { expect(subject.exchange_processed?).to eq false }
+ end
+ end
+
+ describe "#exchange_required?" do
+ context "exchange has been requested and not yet processed" do
+ before do
+ allow(subject).to receive(:exchange_requested?) { true }
+ allow(subject).to receive(:exchange_processed?) { false }
+ end
+
+ it { expect(subject.exchange_required?).to be true }
+ end
+
+ context "exchange has not been requested" do
+ before { allow(subject).to receive(:exchange_requested?) { false } }
+ it { expect(subject.exchange_required?).to be false }
+ end
+
+ context "exchange has been requested and processed" do
+ before do
+ allow(subject).to receive(:exchange_requested?) { true }
+ allow(subject).to receive(:exchange_processed?) { true }
+ end
+ it { expect(subject.exchange_required?).to be false }
+ end
+ end
+
+ describe "#eligible_exchange_variants" do
+ it "uses the exchange variant calculator to compute possible variants to exchange for" do
+ return_item = build(:return_item)
+ expect(Spree::ReturnItem.exchange_variant_engine).to receive(:eligible_variants).with(return_item.variant)
+ return_item.eligible_exchange_variants
+ end
+ end
+
+ describe ".exchange_variant_engine" do
+ it "defaults to the same product calculator" do
+ expect(Spree::ReturnItem.exchange_variant_engine).to eq Spree::ReturnItem::ExchangeVariantEligibility::SameProduct
+ end
+ end
+
+ describe "exchange pre_tax_amount" do
+ let(:return_item) { build(:return_item) }
+
+ context "the return item is intended to be exchanged" do
+ before { return_item.exchange_variant = build(:variant) }
+ it do
+ return_item.pre_tax_amount = 5.0
+ return_item.save!
+ expect(return_item.reload.pre_tax_amount).to eq 0.0
+ end
+ end
+
+ context "the return item is not intended to be exchanged" do
+ it do
+ return_item.pre_tax_amount = 5.0
+ return_item.save!
+ expect(return_item.reload.pre_tax_amount).to eq 5.0
+ end
+ end
+ end
+
+ describe "#build_exchange_inventory_unit" do
+ let(:return_item) { build(:return_item) }
+ subject { return_item.build_exchange_inventory_unit }
+
+ context "the return item is intended to be exchanged" do
+ before { allow(return_item).to receive(:exchange_variant).and_return(mock_model(Spree::Variant)) }
+
+ context "an exchange inventory unit already exists" do
+ before { allow(return_item).to receive(:exchange_inventory_unit).and_return(mock_model(Spree::InventoryUnit)) }
+ it { expect(subject).to be_nil }
+ end
+
+ context "no exchange inventory unit exists" do
+ it "builds a pending inventory unit with references to the return item, variant, and previous inventory unit" do
+ expect(subject.variant).to eq return_item.exchange_variant
+ expect(subject.pending).to eq true
+ expect(subject).not_to be_persisted
+ expect(subject.original_return_item).to eq return_item
+ expect(subject.line_item).to eq return_item.inventory_unit.line_item
+ expect(subject.order).to eq return_item.inventory_unit.order
+ end
+ end
+ end
+
+ context "the return item is not intended to be exchanged" do
+ it { expect(subject).to be_nil }
+ end
+ end
+
+ describe "#exchange_shipment" do
+ it "returns the exchange inventory unit's shipment" do
+ inventory_unit = build(:inventory_unit)
+ subject.exchange_inventory_unit = inventory_unit
+ expect(subject.exchange_shipment).to eq inventory_unit.shipment
+ end
+ end
+
+ describe "#shipment" do
+ it "returns the inventory unit's shipment" do
+ inventory_unit = build(:inventory_unit)
+ subject.inventory_unit = inventory_unit
+ expect(subject.shipment).to eq inventory_unit.shipment
+ end
+ end
+
+ describe 'inventory_unit uniqueness' do
+ let!(:old_return_item) { create(:return_item, reception_status: old_reception_status) }
+ let(:old_reception_status) { 'awaiting' }
+
+ subject do
+ build(:return_item, {
+ return_authorization: old_return_item.return_authorization,
+ inventory_unit: old_return_item.inventory_unit,
+ })
+ end
+
+ context 'with other awaiting return items exist for the same inventory unit' do
+ let(:old_reception_status) { 'awaiting' }
+
+ it 'cancels the others' do
+ expect {
+ subject.save!
+ }.to change { old_return_item.reload.reception_status }.from('awaiting').to('cancelled')
+ end
+
+ it 'does not cancel itself' do
+ subject.save!
+ expect(subject).to be_awaiting
+ end
+ end
+
+ context 'with other cancelled return items exist for the same inventory unit' do
+ let(:old_reception_status) { 'cancelled' }
+
+ it 'succeeds' do
+ expect { subject.save! }.to_not raise_error
+ end
+ end
+
+ context 'with other received return items exist for the same inventory unit' do
+ let(:old_reception_status) { 'received' }
+
+ it 'is invalid' do
+ expect(subject).to_not be_valid
+ expect(subject.errors.to_a).to eq ["Inventory unit #{subject.inventory_unit_id} has already been taken by return item #{old_return_item.id}"]
+ end
+ end
+
+ context 'with other given_to_customer return items exist for the same inventory unit' do
+ let(:old_reception_status) { 'given_to_customer' }
+
+ it 'is invalid' do
+ expect(subject).to_not be_valid
+ expect(subject.errors.to_a).to eq ["Inventory unit #{subject.inventory_unit_id} has already been taken by return item #{old_return_item.id}"]
+ end
+ end
+ end
+
+ describe "included tax in total" do
+ let(:inventory_unit) { create(:inventory_unit, state: 'shipped') }
+ let(:return_item) do
+ create(
+ :return_item,
+ inventory_unit: inventory_unit,
+ included_tax_total: 10
+ )
+ end
+
+ it 'includes included tax total' do
+ expect(return_item.pre_tax_amount).to eq 10
+ expect(return_item.included_tax_total).to eq 10
+ expect(return_item.total).to eq 20
+ end
+ end
+end
diff --git a/core/spec/models/spree/returns_calculator_spec.rb b/core/spec/models/spree/returns_calculator_spec.rb
new file mode 100644
index 00000000000..7bed34d2263
--- /dev/null
+++ b/core/spec/models/spree/returns_calculator_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+module Spree
+ describe ReturnsCalculator, :type => :model do
+ let(:return_item) { build(:return_item) }
+ subject { ReturnsCalculator.new }
+
+ it 'compute_shipment must be overridden' do
+ expect {
+ subject.compute(return_item)
+ }.to raise_error
+ end
+ end
+end
diff --git a/core/spec/models/spree/shipment_spec.rb b/core/spec/models/spree/shipment_spec.rb
new file mode 100644
index 00000000000..2bc3b0ee8fd
--- /dev/null
+++ b/core/spec/models/spree/shipment_spec.rb
@@ -0,0 +1,732 @@
+require 'spec_helper'
+require 'benchmark'
+
+describe Spree::Shipment, :type => :model do
+ let(:order) { mock_model Spree::Order, backordered?: false,
+ canceled?: false,
+ can_ship?: true,
+ currency: 'USD',
+ number: 'S12345',
+ paid?: false,
+ touch: true }
+ let(:shipping_method) { create(:shipping_method, name: "UPS") }
+ let(:shipment) do
+ shipment = Spree::Shipment.new(cost: 1, state: 'pending', stock_location: create(:stock_location))
+ allow(shipment).to receive_messages order: order
+ allow(shipment).to receive_messages shipping_method: shipping_method
+ shipment.save
+ shipment
+ end
+
+ let(:variant) { mock_model(Spree::Variant) }
+ let(:line_item) { mock_model(Spree::LineItem, variant: variant) }
+
+ def create_shipment(order, stock_location)
+ order.shipments.create({ stock_location_id: stock_location.id }).inventory_units.create(
+ order_id: order.id,
+ variant_id: order.line_items.first.variant_id,
+ line_item_id: order.line_items.first.id
+ )
+ end
+
+ # Regression test for #4063
+ context "number generation" do
+ before do
+ allow(order).to receive :update!
+ end
+
+ it "generates a number containing a letter + 11 numbers" do
+ shipment.save
+ expect(shipment.number[0]).to eq("H")
+ expect(/\d{11}/.match(shipment.number)).not_to be_nil
+ expect(shipment.number.length).to eq(12)
+ end
+ end
+
+ it 'is backordered if one if its inventory_units is backordered' do
+ allow(shipment).to receive_messages(inventory_units: [
+ mock_model(Spree::InventoryUnit, backordered?: false),
+ mock_model(Spree::InventoryUnit, backordered?: true)
+ ])
+ expect(shipment).to be_backordered
+ end
+
+ context '#determine_state' do
+ it 'returns canceled if order is canceled?' do
+ allow(order).to receive_messages canceled?: true
+ expect(shipment.determine_state(order)).to eq 'canceled'
+ end
+
+ it 'returns pending unless order.can_ship?' do
+ allow(order).to receive_messages can_ship?: false
+ expect(shipment.determine_state(order)).to eq 'pending'
+ end
+
+ it 'returns pending if backordered' do
+ allow(shipment).to receive_messages inventory_units: [mock_model(Spree::InventoryUnit, backordered?: true)]
+ expect(shipment.determine_state(order)).to eq 'pending'
+ end
+
+ it 'returns shipped when already shipped' do
+ allow(shipment).to receive_messages state: 'shipped'
+ expect(shipment.determine_state(order)).to eq 'shipped'
+ end
+
+ it 'returns pending when unpaid' do
+ expect(shipment.determine_state(order)).to eq 'pending'
+ end
+
+ it 'returns ready when paid' do
+ allow(order).to receive_messages paid?: true
+ expect(shipment.determine_state(order)).to eq 'ready'
+ end
+
+ it 'returns ready when Config.auto_capture_on_dispatch' do
+ Spree::Config.auto_capture_on_dispatch = true
+ expect(shipment.determine_state(order)).to eq 'ready'
+ end
+ end
+
+ context "display_amount" do
+ it "retuns a Spree::Money" do
+ allow(shipment).to receive(:cost) { 21.22 }
+ expect(shipment.display_amount).to eq(Spree::Money.new(21.22))
+ end
+ end
+
+ context "display_final_price" do
+ it "retuns a Spree::Money" do
+ allow(shipment).to receive(:final_price) { 21.22 }
+ expect(shipment.display_final_price).to eq(Spree::Money.new(21.22))
+ end
+ end
+
+ context "display_item_cost" do
+ it "retuns a Spree::Money" do
+ allow(shipment).to receive(:item_cost) { 21.22 }
+ expect(shipment.display_item_cost).to eq(Spree::Money.new(21.22))
+ end
+ end
+
+ context "#item_cost" do
+ it 'should equal shipment line items amount with tax' do
+ order = create(:order_with_line_item_quantity, line_items_quantity: 2)
+
+ stock_location = create(:stock_location)
+
+ create_shipment(order, stock_location)
+ create_shipment(order, stock_location)
+
+ create :tax_adjustment, adjustable: order.line_items.first, order: order
+
+ expect(order.shipments.first.item_cost).to eql(11.0)
+ expect(order.shipments.last.item_cost).to eql(11.0)
+ end
+
+ it 'should equal line items final amount with tax' do
+ shipment = create(:shipment, order: create(:order_with_line_item_quantity, line_items_quantity: 2))
+ create :tax_adjustment, adjustable: shipment.order.line_items.first, order: shipment.order
+ expect(shipment.item_cost).to eql(22.0)
+ end
+ end
+
+ it "#discounted_cost" do
+ shipment = create(:shipment)
+ shipment.cost = 10
+ shipment.promo_total = -1
+ expect(shipment.discounted_cost).to eq(9)
+ end
+
+ it "#tax_total with included taxes" do
+ shipment = Spree::Shipment.new
+ expect(shipment.tax_total).to eq(0)
+ shipment.included_tax_total = 10
+ expect(shipment.tax_total).to eq(10)
+ end
+
+ it "#tax_total with additional taxes" do
+ shipment = Spree::Shipment.new
+ expect(shipment.tax_total).to eq(0)
+ shipment.additional_tax_total = 10
+ expect(shipment.tax_total).to eq(10)
+ end
+
+ it "#final_price" do
+ shipment = Spree::Shipment.new
+ shipment.cost = 10
+ shipment.adjustment_total = -2
+ shipment.included_tax_total = 1
+ expect(shipment.final_price).to eq(8)
+ end
+
+ context "manifest" do
+ let(:order) { Spree::Order.create }
+ let(:variant) { create(:variant) }
+ let!(:line_item) { order.contents.add variant }
+ let!(:shipment) { order.create_proposed_shipments.first }
+
+ it "returns variant expected" do
+ expect(shipment.manifest.first.variant).to eq variant
+ end
+
+ context "variant was removed" do
+ before { variant.destroy }
+
+ it "still returns variant expected" do
+ expect(shipment.manifest.first.variant).to eq variant
+ end
+ end
+ end
+
+ context 'shipping_rates' do
+ let(:shipment) { create(:shipment) }
+ let(:shipping_method1) { create(:shipping_method) }
+ let(:shipping_method2) { create(:shipping_method) }
+ let(:shipping_rates) { [
+ Spree::ShippingRate.new(shipping_method: shipping_method1, cost: 10.00, selected: true),
+ Spree::ShippingRate.new(shipping_method: shipping_method2, cost: 20.00)
+ ] }
+
+ it 'returns shipping_method from selected shipping_rate' do
+ shipment.shipping_rates.delete_all
+ shipment.shipping_rates.create shipping_method: shipping_method1, cost: 10.00, selected: true
+ expect(shipment.shipping_method).to eq shipping_method1
+ end
+
+ context 'refresh_rates' do
+ let(:mock_estimator) { double('estimator', shipping_rates: shipping_rates) }
+ before { allow(shipment).to receive(:can_get_rates?){ true } }
+
+ it 'should request new rates, and maintain shipping_method selection' do
+ expect(Spree::Stock::Estimator).to receive(:new).with(shipment.order).and_return(mock_estimator)
+ allow(shipment).to receive_messages(shipping_method: shipping_method2)
+
+ expect(shipment.refresh_rates).to eq(shipping_rates)
+ expect(shipment.reload.selected_shipping_rate.shipping_method_id).to eq(shipping_method2.id)
+ end
+
+ it 'should handle no shipping_method selection' do
+ expect(Spree::Stock::Estimator).to receive(:new).with(shipment.order).and_return(mock_estimator)
+ allow(shipment).to receive_messages(shipping_method: nil)
+ expect(shipment.refresh_rates).to eq(shipping_rates)
+ expect(shipment.reload.selected_shipping_rate).not_to be_nil
+ end
+
+ it 'should not refresh if shipment is shipped' do
+ expect(Spree::Stock::Estimator).not_to receive(:new)
+ shipment.shipping_rates.delete_all
+ allow(shipment).to receive_messages(shipped?: true)
+ expect(shipment.refresh_rates).to eq([])
+ end
+
+ it "can't get rates without a shipping address" do
+ shipment.order(ship_address: nil)
+ expect(shipment.refresh_rates).to eq([])
+ end
+
+ context 'to_package' do
+ let(:inventory_units) do
+ [build(:inventory_unit, line_item: line_item, variant: variant, state: 'on_hand'),
+ build(:inventory_unit, line_item: line_item, variant: variant, state: 'backordered')]
+ end
+
+ before do
+ allow(shipment).to receive(:inventory_units) { inventory_units }
+ allow(inventory_units).to receive_message_chain(:includes, :joins).and_return inventory_units
+ end
+
+ it 'should use symbols for states when adding contents to package' do
+ package = shipment.to_package
+ expect(package.on_hand.count).to eq 1
+ expect(package.backordered.count).to eq 1
+ end
+ end
+ end
+ end
+
+ context "#update!" do
+ shared_examples_for "immutable once shipped" do
+ it "should remain in shipped state once shipped" do
+ shipment.state = 'shipped'
+ expect(shipment).to receive(:update_columns).with(state: 'shipped', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ end
+
+ shared_examples_for "pending if backordered" do
+ it "should have a state of pending if backordered" do
+ allow(shipment).to receive_messages(inventory_units: [mock_model(Spree::InventoryUnit, backordered?: true)])
+ expect(shipment).to receive(:update_columns).with(state: 'pending', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ end
+
+ context "when order cannot ship" do
+ before { allow(order).to receive_messages can_ship?: false }
+ it "should result in a 'pending' state" do
+ expect(shipment).to receive(:update_columns).with(state: 'pending', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ end
+
+ context "when order is paid" do
+ before { allow(order).to receive_messages paid?: true }
+ it "should result in a 'ready' state" do
+ expect(shipment).to receive(:update_columns).with(state: 'ready', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ it_should_behave_like 'immutable once shipped'
+ it_should_behave_like 'pending if backordered'
+ end
+
+ context "when order has balance due" do
+ before { allow(order).to receive_messages paid?: false }
+ it "should result in a 'pending' state" do
+ shipment.state = 'ready'
+ expect(shipment).to receive(:update_columns).with(state: 'pending', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ it_should_behave_like 'immutable once shipped'
+ it_should_behave_like 'pending if backordered'
+ end
+
+ context "when order has a credit owed" do
+ before { allow(order).to receive_messages payment_state: 'credit_owed', paid?: true }
+ it "should result in a 'ready' state" do
+ shipment.state = 'pending'
+ expect(shipment).to receive(:update_columns).with(state: 'ready', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+ it_should_behave_like 'immutable once shipped'
+ it_should_behave_like 'pending if backordered'
+ end
+
+ context "when shipment state changes to shipped" do
+ before do
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:send_shipped_email)
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:update_order_shipment_state)
+ end
+
+ it "should call after_ship" do
+ shipment.state = 'pending'
+ expect(shipment).to receive :after_ship
+ allow(shipment).to receive_messages determine_state: 'shipped'
+ expect(shipment).to receive(:update_columns).with(state: 'shipped', updated_at: kind_of(Time))
+ shipment.update!(order)
+ end
+
+ context "when using the default shipment handler" do
+ it "should call the 'perform' method" do
+ shipment.state = 'pending'
+ allow(shipment).to receive_messages determine_state: 'shipped'
+ expect_any_instance_of(Spree::ShipmentHandler).to receive(:perform)
+ shipment.update!(order)
+ end
+ end
+
+ context "when using a custom shipment handler" do
+ before do
+ Spree::ShipmentHandler::UPS = Class.new {
+ def initialize(shipment) true end
+ def perform() true end
+ }
+ end
+
+ it "should call the custom handler's 'perform' method" do
+ shipment.state = 'pending'
+ allow(shipment).to receive_messages determine_state: 'shipped'
+ expect_any_instance_of(Spree::ShipmentHandler::UPS).to receive(:perform)
+ shipment.update!(order)
+ end
+
+ after do
+ Spree::ShipmentHandler.send(:remove_const, :UPS)
+ end
+ end
+
+ # Regression test for #4347
+ context "with adjustments" do
+ before do
+ shipment.adjustments << Spree::Adjustment.create(order: order, label: "Label", amount: 5)
+ end
+
+ it "transitions to shipped" do
+ shipment.update_column(:state, "ready")
+ expect { shipment.ship! }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ context "when order is completed" do
+ after { Spree::Config.set track_inventory_levels: true }
+
+ before do
+ allow(order).to receive_messages completed?: true
+ allow(order).to receive_messages canceled?: false
+ end
+
+ context "with inventory tracking" do
+ before { Spree::Config.set track_inventory_levels: true }
+
+ it "should validate with inventory" do
+ shipment.inventory_units = [create(:inventory_unit)]
+ expect(shipment.valid?).to be true
+ end
+ end
+
+ context "without inventory tracking" do
+ before { Spree::Config.set track_inventory_levels: false }
+
+ it "should validate with no inventory" do
+ expect(shipment.valid?).to be true
+ end
+ end
+ end
+
+ context "#cancel" do
+ it 'cancels the shipment' do
+ allow(shipment.order).to receive(:update!)
+
+ shipment.state = 'pending'
+ expect(shipment).to receive(:after_cancel)
+ shipment.cancel!
+ expect(shipment.state).to eq 'canceled'
+ end
+
+ it 'restocks the items' do
+ allow(shipment).to receive_message_chain(inventory_units: [mock_model(Spree::InventoryUnit, state: "on_hand", line_item: line_item, variant: variant)])
+ shipment.stock_location = mock_model(Spree::StockLocation)
+ expect(shipment.stock_location).to receive(:restock).with(variant, 1, shipment)
+ shipment.after_cancel
+ end
+
+ context "with backordered inventory units" do
+ let(:order) { create(:order) }
+ let(:variant) { create(:variant) }
+ let(:other_order) { create(:order) }
+
+ before do
+ order.contents.add variant
+ order.create_proposed_shipments
+
+ other_order.contents.add variant
+ other_order.create_proposed_shipments
+ end
+
+ it "doesn't fill backorders when restocking inventory units" do
+ shipment = order.shipments.first
+ expect(shipment.inventory_units.count).to eq 1
+ expect(shipment.inventory_units.first).to be_backordered
+
+ other_shipment = other_order.shipments.first
+ expect(other_shipment.inventory_units.count).to eq 1
+ expect(other_shipment.inventory_units.first).to be_backordered
+
+ expect {
+ shipment.cancel!
+ }.not_to change { other_shipment.inventory_units.first.state }
+ end
+ end
+ end
+
+ context "#resume" do
+ it 'will determine new state based on order' do
+ allow(shipment.order).to receive(:update!)
+
+ shipment.state = 'canceled'
+ expect(shipment).to receive(:determine_state).and_return(:ready)
+ expect(shipment).to receive(:after_resume)
+ shipment.resume!
+ expect(shipment.state).to eq 'ready'
+ end
+
+ it 'unstocks them items' do
+ allow(shipment).to receive_message_chain(inventory_units: [mock_model(Spree::InventoryUnit, line_item: line_item, variant: variant)])
+ shipment.stock_location = mock_model(Spree::StockLocation)
+ expect(shipment.stock_location).to receive(:unstock).with(variant, 1, shipment)
+ shipment.after_resume
+ end
+
+ it 'will determine new state based on order' do
+ allow(shipment.order).to receive(:update!)
+
+ shipment.state = 'canceled'
+ expect(shipment).to receive(:determine_state).twice.and_return('ready')
+ expect(shipment).to receive(:after_resume)
+ shipment.resume!
+ # Shipment is pending because order is already paid
+ expect(shipment.state).to eq 'pending'
+ end
+ end
+
+ context "#ship" do
+ context "when the shipment is canceled" do
+ let(:shipment_with_inventory_units) { create(:shipment, order: create(:order_with_line_items), state: 'canceled') }
+ let(:subject) { shipment_with_inventory_units.ship! }
+ before do
+ allow(order).to receive(:update!)
+ allow(shipment_with_inventory_units).to receive_messages(require_inventory: false, update_order: true)
+ end
+
+ it 'unstocks them items' do
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:update_order_shipment_state)
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:send_shipped_email)
+
+ expect(shipment_with_inventory_units.stock_location).to receive(:unstock)
+ subject
+ end
+ end
+
+ ['ready', 'canceled'].each do |state|
+ context "from #{state}" do
+ before do
+ allow(order).to receive(:update!)
+ allow(shipment).to receive_messages(require_inventory: false, update_order: true, state: state)
+ end
+
+ it "should update shipped_at timestamp" do
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:update_order_shipment_state)
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:send_shipped_email)
+
+ shipment.ship!
+ expect(shipment.shipped_at).not_to be_nil
+ # Ensure value is persisted
+ shipment.reload
+ expect(shipment.shipped_at).not_to be_nil
+ end
+
+ it "should send a shipment email" do
+ mail_message = double 'Mail::Message'
+ shipment_id = nil
+ expect(Spree::ShipmentMailer).to receive(:shipped_email) { |*args|
+ shipment_id = args[0]
+ mail_message
+ }
+ expect(mail_message).to receive :deliver
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:update_order_shipment_state)
+
+ shipment.ship!
+ expect(shipment_id).to eq(shipment.id)
+ end
+
+ it "finalizes adjustments" do
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:update_order_shipment_state)
+ allow_any_instance_of(Spree::ShipmentHandler).to receive(:send_shipped_email)
+
+ shipment.adjustments.each do |adjustment|
+ expect(adjustment).to receive(:finalize!)
+ end
+ shipment.ship!
+ end
+ end
+ end
+ end
+
+ context "#ready" do
+ context 'with Config.auto_capture_on_dispatch == false' do
+ # Regression test for #2040
+ it "cannot ready a shipment for an order if the order is unpaid" do
+ allow(order).to receive_messages(paid?: false)
+ assert !shipment.can_ready?
+ end
+ end
+
+ context 'with Config.auto_capture_on_dispatch == true' do
+ before do
+ Spree::Config[:auto_capture_on_dispatch] = true
+ @order = create :completed_order_with_pending_payment
+ @shipment = @order.shipments.first
+ @shipment.cost = @order.ship_total
+ end
+
+ it "shipments ready for an order if the order is unpaid" do
+ expect(@shipment.ready?).to be true
+ end
+
+ it "tells the order to process payment in #after_ship" do
+ expect(@shipment).to receive(:process_order_payments)
+ @shipment.ship!
+ end
+
+ context "order has pending payments" do
+ let(:payment) do
+ payment = @order.payments.first
+ payment.update_attribute :state, 'pending'
+ payment
+ end
+
+ it "can fully capture an authorized payment" do
+ payment.update_attribute(:amount, @order.total)
+
+ expect(payment.amount).to eq payment.uncaptured_amount
+ @shipment.ship!
+ expect(payment.reload.uncaptured_amount.to_f).to eq 0
+ end
+
+ it "can partially capture an authorized payment" do
+ payment.update_attribute(:amount, @order.total + 50)
+
+ expect(payment.amount).to eq payment.uncaptured_amount
+ @shipment.ship!
+ expect(payment.captured_amount).to eq @order.total
+ expect(payment.captured_amount).to eq payment.amount
+ expect(payment.order.payments.pending.first.amount).to eq 50
+ end
+ end
+ end
+ end
+
+ context "updates cost when selected shipping rate is present" do
+ let(:shipment) { create(:shipment) }
+
+ before { allow(shipment).to receive_message_chain :selected_shipping_rate, cost: 5 }
+
+ it "updates shipment totals" do
+ shipment.update_amounts
+ expect(shipment.reload.cost).to eq(5)
+ end
+
+ it "factors in additional adjustments to adjustment total" do
+ shipment.adjustments.create!(
+ order: order,
+ label: "Additional",
+ amount: 5,
+ included: false,
+ state: "closed"
+ )
+ shipment.update_amounts
+ expect(shipment.reload.adjustment_total).to eq(5)
+ end
+
+ it "does not factor in included adjustments to adjustment total" do
+ shipment.adjustments.create!(
+ order: order,
+ label: "Included",
+ amount: 5,
+ included: true,
+ state: "closed"
+ )
+ shipment.update_amounts
+ expect(shipment.reload.adjustment_total).to eq(0)
+ end
+ end
+
+ context "changes shipping rate via general update" do
+ let(:order) do
+ Spree::Order.create(
+ payment_total: 100, payment_state: 'paid', total: 100, item_total: 100
+ )
+ end
+
+ let(:shipment) { Spree::Shipment.create order_id: order.id, stock_location: create(:stock_location) }
+
+ let(:shipping_rate) do
+ Spree::ShippingRate.create shipment_id: shipment.id, cost: 10
+ end
+
+ before do
+ shipment.update_attributes_and_order selected_shipping_rate_id: shipping_rate.id
+ end
+
+ it "updates everything around order shipment total and state" do
+ expect(shipment.cost.to_f).to eq 10
+ expect(shipment.state).to eq 'pending'
+ expect(shipment.order.total.to_f).to eq 110
+ expect(shipment.order.payment_state).to eq 'balance_due'
+ end
+ end
+
+ context "after_save" do
+ context "line item changes" do
+ before do
+ shipment.cost = shipment.cost + 10
+ end
+
+ it "triggers adjustment total recalculation" do
+ expect(shipment).to receive(:recalculate_adjustments)
+ shipment.save
+ end
+
+ it "does not trigger adjustment recalculation if shipment has shipped" do
+ shipment.state = 'shipped'
+ expect(shipment).not_to receive(:recalculate_adjustments)
+ shipment.save
+ end
+ end
+
+ context "line item does not change" do
+ it "does not trigger adjustment total recalculation" do
+ expect(shipment).not_to receive(:recalculate_adjustments)
+ shipment.save
+ end
+ end
+ end
+
+ context "currency" do
+ it "returns the order currency" do
+ expect(shipment.currency).to eq(order.currency)
+ end
+ end
+
+ context "nil costs" do
+ it "sets cost to 0" do
+ shipment = Spree::Shipment.new
+ shipment.valid?
+ expect(shipment.cost).to eq 0
+ end
+ end
+
+ context "#tracking_url" do
+ it "uses shipping method to determine url" do
+ expect(shipping_method).to receive(:build_tracking_url).with('1Z12345').and_return(:some_url)
+ shipment.tracking = '1Z12345'
+
+ expect(shipment.tracking_url).to eq(:some_url)
+ end
+ end
+
+ context "set up new inventory units" do
+ # let(:line_item) { double(
+ let(:variant) { double("Variant", id: 9) }
+
+ let(:inventory_units) { double }
+
+ let(:params) do
+ { variant_id: variant.id, state: 'on_hand', order_id: order.id, line_item_id: line_item.id }
+ end
+
+ before { allow(shipment).to receive_messages inventory_units: inventory_units }
+
+ it "associates variant and order" do
+ expect(inventory_units).to receive(:create).with(params)
+ unit = shipment.set_up_inventory('on_hand', variant, order, line_item)
+ end
+ end
+
+ # Regression test for #3349
+ context "#destroy" do
+ it "destroys linked shipping_rates" do
+ reflection = Spree::Shipment.reflect_on_association(:shipping_rates)
+ expect(reflection.options[:dependent]).to be(:delete_all)
+ end
+ end
+
+ # Regression test for #4072 (kinda)
+ # The need for this was discovered in the research for #4702
+ context "state changes" do
+ before do
+ # Must be stubbed so transition can succeed
+ allow(order).to receive_messages :paid? => true
+ end
+
+ it "are logged to the database" do
+ expect(shipment.state_changes).to be_empty
+ expect(shipment.ready!).to be true
+ expect(shipment.state_changes.count).to eq(1)
+ state_change = shipment.state_changes.first
+ expect(state_change.previous_state).to eq('pending')
+ expect(state_change.next_state).to eq('ready')
+ end
+ end
+end
diff --git a/core/spec/models/spree/shipping_calculator_spec.rb b/core/spec/models/spree/shipping_calculator_spec.rb
new file mode 100644
index 00000000000..3469a686c69
--- /dev/null
+++ b/core/spec/models/spree/shipping_calculator_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+module Spree
+ describe ShippingCalculator, :type => :model do
+ let(:variant1) { build(:variant, :price => 10) }
+ let(:variant2) { build(:variant, :price => 20) }
+
+ let(:package) do
+ build(:stock_package, variants_contents: { variant1 => 2, variant2 => 1 })
+ end
+
+ subject { ShippingCalculator.new }
+
+ it 'computes with a shipment' do
+ shipment = mock_model(Spree::Shipment)
+ expect(subject).to receive(:compute_shipment).with(shipment)
+ subject.compute(shipment)
+ end
+
+ it 'computes with a package' do
+ expect(subject).to receive(:compute_package).with(package)
+ subject.compute(package)
+ end
+
+ it 'compute_shipment must be overridden' do
+ expect {
+ subject.compute_shipment(shipment)
+ }.to raise_error
+ end
+
+ it 'compute_package must be overridden' do
+ expect {
+ subject.compute_package(package)
+ }.to raise_error
+ end
+
+ it 'checks availability for a package' do
+ expect(subject.available?(package)).to be true
+ end
+
+ it 'calculates totals for content_items' do
+ expect(subject.send(:total, package.contents)).to eq 40.00
+ end
+ end
+end
diff --git a/core/spec/models/spree/shipping_category_spec.rb b/core/spec/models/spree/shipping_category_spec.rb
new file mode 100644
index 00000000000..4db54c8dffb
--- /dev/null
+++ b/core/spec/models/spree/shipping_category_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe Spree::ShippingCategory, :type => :model do
+
+end
diff --git a/core/spec/models/spree/shipping_method_spec.rb b/core/spec/models/spree/shipping_method_spec.rb
new file mode 100644
index 00000000000..e07fee08722
--- /dev/null
+++ b/core/spec/models/spree/shipping_method_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+class DummyShippingCalculator < Spree::ShippingCalculator
+end
+
+describe Spree::ShippingMethod, :type => :model do
+ let(:shipping_method){ create(:shipping_method) }
+
+ context 'calculators' do
+ it "Should reject calculators that don't inherit from Spree::ShippingCalculator" do
+ allow(Spree::ShippingMethod).to receive_message_chain(:spree_calculators, :shipping_methods).and_return([
+ Spree::Calculator::Shipping::FlatPercentItemTotal,
+ Spree::Calculator::Shipping::PriceSack,
+ Spree::Calculator::DefaultTax,
+ DummyShippingCalculator # included as regression test for https://github.com/spree/spree/issues/3109
+ ])
+
+ expect(Spree::ShippingMethod.calculators).to eq([Spree::Calculator::Shipping::FlatPercentItemTotal, Spree::Calculator::Shipping::PriceSack, DummyShippingCalculator ])
+ expect(Spree::ShippingMethod.calculators).not_to eq([Spree::Calculator::DefaultTax])
+ end
+ end
+
+ # Regression test for #4492
+ context "#shipments" do
+ let!(:shipping_method) { create(:shipping_method) }
+ let!(:shipment) do
+ shipment = create(:shipment)
+ shipment.shipping_rates.create!(:shipping_method => shipping_method)
+ shipment
+ end
+
+ it "can gather all the related shipments" do
+ expect(shipping_method.shipments).to include(shipment)
+ end
+ end
+
+ context "validations" do
+ before { subject.valid? }
+
+ it "validates presence of name" do
+ expect(subject.error_on(:name).size).to eq(1)
+ end
+
+ context "shipping category" do
+ it "validates presence of at least one" do
+ expect(subject.error_on(:base).size).to eq(1)
+ end
+
+ context "one associated" do
+ before { subject.shipping_categories.push create(:shipping_category) }
+ it { expect(subject.error_on(:base).size).to eq(0) }
+ end
+ end
+ end
+
+ context 'factory' do
+ it "should set calculable correctly" do
+ expect(shipping_method.calculator.calculable).to eq(shipping_method)
+ end
+ end
+
+ context "generating tracking URLs" do
+ context "shipping method has a tracking URL mask on file" do
+ let(:tracking_url) { "https://track-o-matic.com/:tracking" }
+ before { allow(subject).to receive(:tracking_url) { tracking_url } }
+
+ context 'tracking number has spaces' do
+ let(:tracking_numbers) { ["1234 5678 9012 3456", "a bcdef"] }
+ let(:expectations) { %w[https://track-o-matic.com/1234%205678%209012%203456 https://track-o-matic.com/a%20bcdef] }
+
+ it "should return a single URL with '%20' in lieu of spaces" do
+ tracking_numbers.each_with_index do |num, i|
+ expect(subject.build_tracking_url(num)).to eq(expectations[i])
+ end
+ end
+ end
+ end
+ end
+
+ # Regression test for #4320
+ context "soft deletion" do
+ let(:shipping_method) { create(:shipping_method) }
+ it "soft-deletes when destroy is called" do
+ shipping_method.destroy
+ expect(shipping_method.deleted_at).not_to be_blank
+ end
+ end
+end
diff --git a/core/spec/models/spree/shipping_rate_spec.rb b/core/spec/models/spree/shipping_rate_spec.rb
new file mode 100644
index 00000000000..0cf319e2fdd
--- /dev/null
+++ b/core/spec/models/spree/shipping_rate_spec.rb
@@ -0,0 +1,141 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Spree::ShippingRate, :type => :model do
+ let(:shipment) { create(:shipment) }
+ let(:shipping_method) { create(:shipping_method) }
+ let(:shipping_rate) { Spree::ShippingRate.new(:shipment => shipment,
+ :shipping_method => shipping_method,
+ :cost => 10) }
+
+ context "#display_price" do
+ context "when tax included in price" do
+ context "when the tax rate is from the default zone" do
+ let!(:zone) { create(:zone, :default_tax => true) }
+ let(:tax_rate) do
+ create(:tax_rate,
+ :name => "VAT",
+ :amount => 0.1,
+ :included_in_price => true,
+ :zone => zone)
+ end
+
+ before { shipping_rate.tax_rate = tax_rate }
+
+ it "shows correct tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$10.00 (incl. $0.91 #{tax_rate.name})")
+ end
+
+ context "when cost is zero" do
+ before do
+ shipping_rate.cost = 0
+ end
+
+ it "shows no tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$0.00")
+ end
+ end
+ end
+
+ context "when the tax rate is from a non-default zone" do
+ let!(:default_zone) { create(:zone, :default_tax => true) }
+ let!(:non_default_zone) { create(:zone, :default_tax => false) }
+ let(:tax_rate) do
+ create(:tax_rate,
+ :name => "VAT",
+ :amount => 0.1,
+ :included_in_price => true,
+ :zone => non_default_zone)
+ end
+ before { shipping_rate.tax_rate = tax_rate }
+
+ it "shows correct tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$10.00 (excl. $0.91 #{tax_rate.name})")
+ end
+
+ context "when cost is zero" do
+ before do
+ shipping_rate.cost = 0
+ end
+
+ it "shows no tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$0.00")
+ end
+ end
+ end
+ end
+
+ context "when tax is additional to price" do
+ let(:tax_rate) { create(:tax_rate, :name => "Sales Tax", :amount => 0.1) }
+ before { shipping_rate.tax_rate = tax_rate }
+
+ it "shows correct tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$10.00 (+ $1.00 #{tax_rate.name})")
+ end
+
+ context "when cost is zero" do
+ before do
+ shipping_rate.cost = 0
+ end
+
+ it "shows no tax amount" do
+ expect(shipping_rate.display_price.to_s).to eq("$0.00")
+ end
+ end
+ end
+
+ context "when the currency is JPY" do
+ let(:shipping_rate) { shipping_rate = Spree::ShippingRate.new(:cost => 205)
+ allow(shipping_rate).to receive_messages(:currency => "JPY")
+ shipping_rate }
+
+ it "displays the price in yen" do
+ expect(shipping_rate.display_price.to_s).to eq("¥205")
+ end
+ end
+ end
+
+ # Regression test for #3829
+ context "#shipping_method" do
+ it "can be retrieved" do
+ expect(shipping_rate.shipping_method.reload).to eq(shipping_method)
+ end
+
+ it "can be retrieved even when deleted" do
+ shipping_method.update_column(:deleted_at, Time.now)
+ shipping_rate.save
+ shipping_rate.reload
+ expect(shipping_rate.shipping_method).to eq(shipping_method)
+ end
+ end
+
+ context "#tax_rate" do
+ let!(:tax_rate) { create(:tax_rate) }
+
+ before do
+ shipping_rate.tax_rate = tax_rate
+ end
+
+ it "can be retrieved" do
+ expect(shipping_rate.tax_rate.reload).to eq(tax_rate)
+ end
+
+ it "can be retrieved even when deleted" do
+ tax_rate.update_column(:deleted_at, Time.now)
+ shipping_rate.save
+ shipping_rate.reload
+ expect(shipping_rate.tax_rate).to eq(tax_rate)
+ end
+ end
+
+ context "#shipping_method_code" do
+ before do
+ shipping_method.code = "THE_CODE"
+ end
+
+ it 'should be shipping_method.code' do
+ expect(shipping_rate.shipping_method_code).to eq("THE_CODE")
+ end
+ end
+end
diff --git a/core/spec/models/spree/state_spec.rb b/core/spec/models/spree/state_spec.rb
new file mode 100644
index 00000000000..bf8db223ffd
--- /dev/null
+++ b/core/spec/models/spree/state_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Spree::State, :type => :model do
+ it "can find a state by name or abbr" do
+ state = create(:state, :name => "California", :abbr => "CA")
+ expect(Spree::State.find_all_by_name_or_abbr("California")).to include(state)
+ expect(Spree::State.find_all_by_name_or_abbr("CA")).to include(state)
+ end
+
+ it "can find all states group by country id" do
+ state = create(:state)
+ expect(Spree::State.states_group_by_country_id).to eq({ state.country_id.to_s => [[state.id, state.name]] })
+ end
+end
diff --git a/core/spec/models/spree/stock/availability_validator_spec.rb b/core/spec/models/spree/stock/availability_validator_spec.rb
new file mode 100644
index 00000000000..6e89871a0de
--- /dev/null
+++ b/core/spec/models/spree/stock/availability_validator_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe AvailabilityValidator, :type => :model do
+ let!(:line_item) { double(quantity: 5, variant_id: 1, variant: double.as_null_object, errors: double('errors'), inventory_units: []) }
+
+ subject { described_class.new }
+
+ it 'should be valid when supply is sufficient' do
+ allow_any_instance_of(Stock::Quantifier).to receive_messages(can_supply?: true)
+ expect(line_item).not_to receive(:errors)
+ subject.validate(line_item)
+ end
+
+ it 'should be invalid when supply is insufficent' do
+ allow_any_instance_of(Stock::Quantifier).to receive_messages(can_supply?: false)
+ expect(line_item.errors).to receive(:[]).with(:quantity).and_return []
+ subject.validate(line_item)
+ end
+
+ it 'should consider existing inventory_units sufficient' do
+ allow_any_instance_of(Stock::Quantifier).to receive_messages(can_supply?: false)
+ expect(line_item).not_to receive(:errors)
+ allow(line_item).to receive_messages(inventory_units: [double] * 5)
+ subject.validate(line_item)
+ end
+
+ it 'should be valid when the quantity is zero' do
+ expect(line_item).to receive(:quantity).and_return(0)
+ expect(line_item.errors).to_not receive(:[]).with(:quantity)
+ subject.validate(line_item)
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/coordinator_spec.rb b/core/spec/models/spree/stock/coordinator_spec.rb
new file mode 100644
index 00000000000..6d159c792a2
--- /dev/null
+++ b/core/spec/models/spree/stock/coordinator_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Coordinator, :type => :model do
+ let!(:order) { create(:order_with_line_items) }
+
+ subject { Coordinator.new(order) }
+
+ context "packages" do
+ it "builds, prioritizes and estimates" do
+ expect(subject).to receive(:build_packages).ordered
+ expect(subject).to receive(:prioritize_packages).ordered
+ expect(subject).to receive(:estimate_packages).ordered
+ subject.packages
+ end
+ end
+
+ describe "#shipments" do
+ let(:packages) { [build(:stock_package_fulfilled), build(:stock_package_fulfilled)] }
+
+ before { allow(subject).to receive(:packages).and_return(packages) }
+
+ it "turns packages into shipments" do
+ shipments = subject.shipments
+ expect(shipments.count).to eq packages.count
+ shipments.each { |shipment| expect(shipment).to be_a Shipment }
+ end
+
+ it "puts the order's ship address on the shipments" do
+ shipments = subject.shipments
+ shipments.each { |shipment| expect(shipment.address).to eq order.ship_address }
+ end
+ end
+
+ context "build packages" do
+ it "builds a package for every stock location" do
+ subject.packages.count == StockLocation.count
+ end
+
+ context "missing stock items in stock location" do
+ let!(:another_location) { create(:stock_location, propagate_all_variants: false) }
+
+ it "builds packages only for valid stock locations" do
+ expect(subject.build_packages.count).to eq(StockLocation.count - 1)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/differentiator_spec.rb b/core/spec/models/spree/stock/differentiator_spec.rb
new file mode 100644
index 00000000000..643ca133bc3
--- /dev/null
+++ b/core/spec/models/spree/stock/differentiator_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Differentiator, :type => :model do
+ let(:variant1) { mock_model(Variant) }
+ let(:variant2) { mock_model(Variant) }
+
+ let(:line_item1) { build(:line_item, variant: variant1, quantity: 2) }
+ let(:line_item2) { build(:line_item, variant: variant2, quantity: 2) }
+
+ let(:stock_location) { mock_model(StockLocation) }
+
+ let(:inventory_unit1) { build(:inventory_unit, variant: variant1, line_item: line_item1) }
+ let(:inventory_unit2) { build(:inventory_unit, variant: variant2, line_item: line_item2) }
+
+ let(:order) { mock_model(Order, line_items: [line_item1, line_item2]) }
+
+ let(:package1) do
+ Package.new(stock_location).tap { |p| p.add(inventory_unit1) }
+ end
+
+ let(:package2) do
+ Package.new(stock_location).tap { |p| p.add(inventory_unit2) }
+ end
+
+ let(:packages) { [package1, package2] }
+
+ subject { Differentiator.new(order, packages) }
+
+ it { is_expected.to be_missing }
+
+ it 'calculates the missing items' do
+ expect(subject.missing[variant1]).to eq 1
+ expect(subject.missing[variant2]).to eq 1
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/estimator_spec.rb b/core/spec/models/spree/stock/estimator_spec.rb
new file mode 100644
index 00000000000..ed6e9f5e7d9
--- /dev/null
+++ b/core/spec/models/spree/stock/estimator_spec.rb
@@ -0,0 +1,154 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Estimator, :type => :model do
+ let!(:shipping_method) { create(:shipping_method) }
+ let(:package) { build(:stock_package, contents: inventory_units.map { |i| ContentItem.new(inventory_unit) }) }
+ let(:order) { build(:order_with_line_items) }
+ let(:inventory_units) { order.inventory_units }
+
+ subject { Estimator.new(order) }
+
+ context "#shipping rates" do
+ before(:each) do
+ shipping_method.zones.first.members.create(:zoneable => order.ship_address.country)
+ allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :available?).and_return(true)
+ allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :compute).and_return(4.00)
+ allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :preferences).and_return({:currency => currency})
+ allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :marked_for_destruction?)
+
+ allow(package).to receive_messages(:shipping_methods => [shipping_method])
+ end
+
+ let(:currency) { "USD" }
+
+ shared_examples_for "shipping rate matches" do
+ it "returns shipping rates" do
+ shipping_rates = subject.shipping_rates(package)
+ expect(shipping_rates.first.cost).to eq 4.00
+ end
+ end
+
+ shared_examples_for "shipping rate doesn't match" do
+ it "does not return shipping rates" do
+ shipping_rates = subject.shipping_rates(package)
+ expect(shipping_rates).to eq([])
+ end
+ end
+
+ context "when the order's ship address is in the same zone" do
+ it_should_behave_like "shipping rate matches"
+ end
+
+ context "when the order's ship address is in a different zone" do
+ before { shipping_method.zones.each{|z| z.members.delete_all} }
+ it_should_behave_like "shipping rate doesn't match"
+ end
+
+ context "when the calculator is not available for that order" do
+ before { allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :available?).and_return(false) }
+ it_should_behave_like "shipping rate doesn't match"
+ end
+
+ context "when the currency is nil" do
+ let(:currency) { nil }
+ it_should_behave_like "shipping rate matches"
+ end
+
+ context "when the currency is an empty string" do
+ let(:currency) { "" }
+ it_should_behave_like "shipping rate matches"
+ end
+
+ context "when the current matches the order's currency" do
+ it_should_behave_like "shipping rate matches"
+ end
+
+ context "if the currency is different than the order's currency" do
+ let(:currency) { "GBP" }
+ it_should_behave_like "shipping rate doesn't match"
+ end
+
+ context "when the shipping method's calculator raises an exception" do
+ before do
+ allow_any_instance_of(ShippingMethod).to receive_message_chain(:calculator, :available?).and_raise(Exception, "Something went wrong!")
+ expect(subject).to receive(:log_calculator_exception)
+ end
+ it_should_behave_like "shipping rate doesn't match"
+ end
+
+ it "sorts shipping rates by cost" do
+ shipping_methods = 3.times.map { create(:shipping_method) }
+ allow(shipping_methods[0]).to receive_message_chain(:calculator, :compute).and_return(5.00)
+ allow(shipping_methods[1]).to receive_message_chain(:calculator, :compute).and_return(3.00)
+ allow(shipping_methods[2]).to receive_message_chain(:calculator, :compute).and_return(4.00)
+
+ allow(subject).to receive(:shipping_methods).and_return(shipping_methods)
+
+ expect(subject.shipping_rates(package).map(&:cost)).to eq %w[3.00 4.00 5.00].map(&BigDecimal.method(:new))
+ end
+
+ context "general shipping methods" do
+ let(:shipping_methods) { 2.times.map { create(:shipping_method) } }
+
+ it "selects the most affordable shipping rate" do
+ allow(shipping_methods[0]).to receive_message_chain(:calculator, :compute).and_return(5.00)
+ allow(shipping_methods[1]).to receive_message_chain(:calculator, :compute).and_return(3.00)
+
+ allow(subject).to receive(:shipping_methods).and_return(shipping_methods)
+
+ expect(subject.shipping_rates(package).sort_by(&:cost).map(&:selected)).to eq [true, false]
+ end
+
+ it "selects the most affordable shipping rate and doesn't raise exception over nil cost" do
+ allow(shipping_methods[0]).to receive_message_chain(:calculator, :compute).and_return(1.00)
+ allow(shipping_methods[1]).to receive_message_chain(:calculator, :compute).and_return(nil)
+
+ allow(subject).to receive(:shipping_methods).and_return(shipping_methods)
+
+ subject.shipping_rates(package)
+ end
+ end
+
+ context "involves backend only shipping methods" do
+ let(:backend_method) { create(:shipping_method, display_on: "back_end") }
+ let(:generic_method) { create(:shipping_method) }
+
+ before do
+ allow(backend_method).to receive_message_chain(:calculator, :compute).and_return(0.00)
+ allow(generic_method).to receive_message_chain(:calculator, :compute).and_return(5.00)
+ allow(subject).to receive(:shipping_methods).and_return([backend_method, generic_method])
+ end
+
+ it "does not return backend rates at all" do
+ expect(subject.shipping_rates(package).map(&:shipping_method_id)).to eq([generic_method.id])
+ end
+
+ # regression for #3287
+ it "doesn't select backend rates even if they're more affordable" do
+ expect(subject.shipping_rates(package).map(&:selected)).to eq [true]
+ end
+ end
+
+ context "includes tax adjustments if applicable" do
+ let!(:tax_rate) { create(:tax_rate, zone: order.tax_zone) }
+
+ before do
+ Spree::ShippingMethod.all.each do |sm|
+ sm.tax_category_id = tax_rate.tax_category_id
+ sm.save
+ end
+ package.shipping_methods.map(&:reload)
+ end
+
+
+ it "links the shipping rate and the tax rate" do
+ shipping_rates = subject.shipping_rates(package)
+ expect(shipping_rates.first.tax_rate).to eq(tax_rate)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/inventory_unit_builder_spec.rb b/core/spec/models/spree/stock/inventory_unit_builder_spec.rb
new file mode 100644
index 00000000000..d37587879d0
--- /dev/null
+++ b/core/spec/models/spree/stock/inventory_unit_builder_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe InventoryUnitBuilder, :type => :model do
+ let(:line_item_1) { build(:line_item) }
+ let(:line_item_2) { build(:line_item, quantity: 2) }
+ let(:order) { build(:order, line_items: [line_item_1, line_item_2]) }
+
+ subject { InventoryUnitBuilder.new(order) }
+
+ describe "#units" do
+ it "returns an inventory unit for each quantity for the order's line items" do
+ units = subject.units
+ expect(units.count).to eq 3
+ expect(units.first.line_item).to eq line_item_1
+ expect(units.first.variant).to eq line_item_1.variant
+
+ expect(units[1].line_item).to eq line_item_2
+ expect(units[1].variant).to eq line_item_2.variant
+
+ expect(units[2].line_item).to eq line_item_2
+ expect(units[2].variant).to eq line_item_2.variant
+ end
+
+ it "builds the inventory units as pending" do
+ expect(subject.units.map(&:pending).uniq).to eq [true]
+ end
+
+ it "associates the inventory units to the order" do
+ expect(subject.units.map(&:order).uniq).to eq [order]
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/package_spec.rb b/core/spec/models/spree/stock/package_spec.rb
new file mode 100644
index 00000000000..7d4b03bd0d7
--- /dev/null
+++ b/core/spec/models/spree/stock/package_spec.rb
@@ -0,0 +1,163 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Package, :type => :model do
+ let(:variant) { build(:variant, weight: 25.0) }
+ let(:stock_location) { build(:stock_location) }
+ let(:order) { build(:order) }
+
+ subject { Package.new(stock_location) }
+
+ def build_inventory_unit
+ build(:inventory_unit, variant: variant)
+ end
+
+ it 'calculates the weight of all the contents' do
+ 4.times { subject.add build_inventory_unit }
+ expect(subject.weight).to eq(100.0)
+ end
+
+ it 'filters by on_hand and backordered' do
+ 4.times { subject.add build_inventory_unit }
+ 3.times { subject.add build_inventory_unit, :backordered }
+ expect(subject.on_hand.count).to eq 4
+ expect(subject.backordered.count).to eq 3
+ end
+
+ it 'calculates the quantity by state' do
+ 4.times { subject.add build_inventory_unit }
+ 3.times { subject.add build_inventory_unit, :backordered }
+
+ expect(subject.quantity).to eq 7
+ expect(subject.quantity(:on_hand)).to eq 4
+ expect(subject.quantity(:backordered)).to eq 3
+ end
+
+ it 'returns nil for content item not found' do
+ unit = build_inventory_unit
+ item = subject.find_item(unit, :on_hand)
+ expect(item).to be_nil
+ end
+
+ it 'finds content item for an inventory unit' do
+ unit = build_inventory_unit
+ subject.add unit
+ item = subject.find_item(unit, :on_hand)
+ expect(item.quantity).to eq 1
+ end
+
+ # Contains regression test for #2804
+ it 'builds a list of shipping methods common to all categories' do
+ category1 = create(:shipping_category)
+ category2 = create(:shipping_category)
+ method1 = create(:shipping_method)
+ method2 = create(:shipping_method)
+ method1.shipping_categories = [category1, category2]
+ method2.shipping_categories = [category1]
+ variant1 = mock_model(Variant, shipping_category: category1)
+ variant2 = mock_model(Variant, shipping_category: category2)
+ variant3 = mock_model(Variant, shipping_category: nil)
+ contents = [ContentItem.new(build(:inventory_unit, variant: variant1)),
+ ContentItem.new(build(:inventory_unit, variant: variant1)),
+ ContentItem.new(build(:inventory_unit, variant: variant2)),
+ ContentItem.new(build(:inventory_unit, variant: variant3))]
+
+ package = Package.new(stock_location, contents)
+ expect(package.shipping_methods).to eq([method1])
+ end
+
+ it 'builds an empty list of shipping methods when no categories' do
+ variant = mock_model(Variant, shipping_category: nil)
+ contents = [ContentItem.new(build(:inventory_unit, variant: variant))]
+ package = Package.new(stock_location, contents)
+ expect(package.shipping_methods).to be_empty
+ end
+
+ it "can convert to a shipment" do
+ 2.times { subject.add build_inventory_unit }
+ subject.add build_inventory_unit, :backordered
+
+ shipping_method = build(:shipping_method)
+ subject.shipping_rates = [ Spree::ShippingRate.new(shipping_method: shipping_method, cost: 10.00, selected: true) ]
+
+ shipment = subject.to_shipment
+ expect(shipment.stock_location).to eq subject.stock_location
+ expect(shipment.inventory_units.size).to eq 3
+
+ first_unit = shipment.inventory_units.first
+ expect(first_unit.variant).to eq variant
+ expect(first_unit.state).to eq 'on_hand'
+ expect(first_unit).to be_pending
+
+ last_unit = shipment.inventory_units.last
+ expect(last_unit.variant).to eq variant
+ expect(last_unit.state).to eq 'backordered'
+
+ expect(shipment.shipping_method).to eq shipping_method
+ end
+
+ it 'does not add an inventory unit to a package twice' do
+ # since inventory units currently don't have a quantity
+ unit = build_inventory_unit
+ subject.add unit
+ subject.add unit
+ expect(subject.quantity).to eq 1
+ expect(subject.contents.first.inventory_unit).to eq unit
+ expect(subject.contents.first.quantity).to eq 1
+ end
+
+ describe "#add_multiple" do
+ it "adds multiple inventory units" do
+ expect { subject.add_multiple [build_inventory_unit, build_inventory_unit] }.to change { subject.quantity }.by(2)
+ end
+
+ it "allows adding with a state" do
+ expect { subject.add_multiple [build_inventory_unit, build_inventory_unit], :backordered }.to change { subject.backordered.count }.by(2)
+ end
+
+ it "defaults to adding with the on hand state" do
+ expect { subject.add_multiple [build_inventory_unit, build_inventory_unit] }.to change { subject.on_hand.count }.by(2)
+ end
+ end
+
+ describe "#remove" do
+ let(:unit) { build_inventory_unit }
+ context "there is a content item for the inventory unit" do
+
+ before { subject.add unit }
+
+ it "removes that content item" do
+ expect { subject.remove(unit) }.to change { subject.quantity }.by(-1)
+ expect(subject.contents.map(&:inventory_unit)).not_to include unit
+ end
+ end
+
+ context "there is no content item for the inventory unit" do
+ it "doesn't change the set of content items" do
+ expect { subject.remove(unit) }.not_to change { subject.quantity }
+ end
+ end
+ end
+
+ describe "#order" do
+ let(:unit) { build_inventory_unit }
+ context "there is an inventory unit" do
+
+ before { subject.add unit }
+
+ it "returns an order" do
+ expect(subject.order).to be_a_kind_of Spree::Order
+ expect(subject.order).to eq unit.order
+ end
+ end
+
+ context "there is no inventory unit" do
+ it "returns nil" do
+ expect(subject.order).to eq nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/packer_spec.rb b/core/spec/models/spree/stock/packer_spec.rb
new file mode 100644
index 00000000000..fdc9ecc0ba1
--- /dev/null
+++ b/core/spec/models/spree/stock/packer_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Packer, :type => :model do
+ let!(:inventory_units) { 5.times.map { build(:inventory_unit) } }
+ let(:stock_location) { create(:stock_location) }
+
+ subject { Packer.new(stock_location, inventory_units) }
+
+ context 'packages' do
+ it 'builds an array of packages' do
+ packages = subject.packages
+ expect(packages.size).to eq 1
+ expect(packages.first.contents.size).to eq 5
+ end
+
+ it 'allows users to set splitters to an empty array' do
+ packages = Packer.new(stock_location, inventory_units, []).packages
+ expect(packages.size).to eq 1
+ end
+ end
+
+ context 'default_package' do
+ it 'contains all the items' do
+ package = subject.default_package
+ expect(package.contents.size).to eq 5
+ end
+
+ it 'variants are added as backordered without enough on_hand' do
+ expect(stock_location).to receive(:fill_status).exactly(5).times.and_return(
+ *(Array.new(3, [1,0]) + Array.new(2, [0,1]))
+ )
+
+ package = subject.default_package
+ expect(package.on_hand.size).to eq 3
+ expect(package.backordered.size).to eq 2
+ end
+
+ context "location doesn't have order items in stock" do
+ let(:stock_location) { create(:stock_location, propagate_all_variants: false) }
+ let(:packer) { Packer.new(stock_location, inventory_units) }
+
+ it "builds an empty package" do
+ expect(packer.default_package.contents).to be_empty
+ end
+ end
+
+ context "doesn't track inventory levels" do
+ let(:variant) { build(:variant) }
+ let(:inventory_units) { 30.times.map { build(:inventory_unit, variant: variant) } }
+
+ before { Config.track_inventory_levels = false }
+
+ it "doesn't bother stock items status in stock location" do
+ expect(subject.stock_location).not_to receive(:fill_status)
+ subject.default_package
+ end
+
+ it "still creates package with proper quantity" do
+ expect(subject.default_package.quantity).to eql 30
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/prioritizer_spec.rb b/core/spec/models/spree/stock/prioritizer_spec.rb
new file mode 100644
index 00000000000..f7fd858e19b
--- /dev/null
+++ b/core/spec/models/spree/stock/prioritizer_spec.rb
@@ -0,0 +1,125 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ describe Prioritizer, :type => :model do
+ let(:order) { mock_model(Order) }
+ let(:stock_location) { build(:stock_location) }
+ let(:variant) { build(:variant) }
+
+ def inventory_units
+ @inventory_units ||= []
+ end
+
+ def build_inventory_unit
+ mock_model(InventoryUnit, variant: variant).tap do |unit|
+ inventory_units << unit
+ end
+ end
+
+ def pack
+ package = Package.new(order)
+ yield(package) if block_given?
+ package
+ end
+
+ it 'keeps a single package' do
+ package1 = pack do |package|
+ package.add build_inventory_unit
+ package.add build_inventory_unit
+ end
+
+ packages = [package1]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+ expect(packages.size).to eq 1
+ end
+
+ it 'removes duplicate packages' do
+ package1 = pack do |package|
+ package.add build_inventory_unit
+ package.add build_inventory_unit
+ end
+
+ package2 = pack do |package|
+ package.add inventory_units.first
+ package.add inventory_units.last
+ end
+
+ packages = [package1, package2]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+ expect(packages.size).to eq 1
+ end
+
+ it 'split over 2 packages' do
+ package1 = pack do |package|
+ package.add build_inventory_unit
+ end
+ package2 = pack do |package|
+ package.add build_inventory_unit
+ end
+
+ packages = [package1, package2]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+ expect(packages.size).to eq 2
+ end
+
+ it '1st has some, 2nd has remaining' do
+ 5.times { build_inventory_unit }
+
+ package1 = pack do |package|
+ 2.times { |i| package.add inventory_units[i] }
+ end
+ package2 = pack do |package|
+ 5.times { |i| package.add inventory_units[i] }
+ end
+
+ packages = [package1, package2]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+ expect(packages.count).to eq 2
+ expect(packages[0].quantity).to eq 2
+ expect(packages[1].quantity).to eq 3
+ end
+
+ it '1st has backorder, 2nd has some' do
+ 5.times { build_inventory_unit }
+
+ package1 = pack do |package|
+ 5.times { |i| package.add inventory_units[i], :backordered }
+ end
+ package2 = pack do |package|
+ 2.times { |i| package.add inventory_units[i] }
+ end
+
+ packages = [package1, package2]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+
+ expect(packages[0].quantity(:backordered)).to eq 3
+ expect(packages[1].quantity(:on_hand)).to eq 2
+ end
+
+ it '1st has backorder, 2nd has all' do
+ 5.times { build_inventory_unit }
+
+ package1 = pack do |package|
+ 3.times { |i| package.add inventory_units[i], :backordered }
+ end
+ package2 = pack do |package|
+ 5.times { |i| package.add inventory_units[i] }
+ end
+
+ packages = [package1, package2]
+ prioritizer = Prioritizer.new(inventory_units, packages)
+ packages = prioritizer.prioritized_packages
+ expect(packages[0]).to eq package2
+ expect(packages[1]).to be_nil
+ expect(packages[0].quantity(:backordered)).to eq 0
+ expect(packages[0].quantity(:on_hand)).to eq 5
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/quantifier_spec.rb b/core/spec/models/spree/stock/quantifier_spec.rb
new file mode 100644
index 00000000000..f06e890c769
--- /dev/null
+++ b/core/spec/models/spree/stock/quantifier_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+
+shared_examples_for 'unlimited supply' do
+ it 'can_supply? any amount' do
+ expect(subject.can_supply?(1)).to be true
+ expect(subject.can_supply?(101)).to be true
+ expect(subject.can_supply?(100_001)).to be true
+ end
+end
+
+module Spree
+ module Stock
+ describe Quantifier, :type => :model do
+
+ let!(:stock_location) { create :stock_location_with_items }
+ let!(:stock_item) { stock_location.stock_items.order(:id).first }
+
+ subject { described_class.new(stock_item.variant) }
+
+ specify { expect(subject.stock_items).to eq([stock_item]) }
+
+
+ context 'with a single stock location/item' do
+ it 'total_on_hand should match stock_item' do
+ expect(subject.total_on_hand).to eq(stock_item.count_on_hand)
+ end
+
+ context 'when track_inventory_levels is false' do
+ before { configure_spree_preferences { |config| config.track_inventory_levels = false } }
+
+ specify { expect(subject.total_on_hand).to eq(Float::INFINITY) }
+
+ it_should_behave_like 'unlimited supply'
+ end
+
+ context 'when variant inventory tracking is off' do
+ before { stock_item.variant.track_inventory = false }
+
+ specify { expect(subject.total_on_hand).to eq(Float::INFINITY) }
+
+ it_should_behave_like 'unlimited supply'
+ end
+
+ context 'when stock item allows backordering' do
+
+ specify { expect(subject.backorderable?).to be true }
+
+ it_should_behave_like 'unlimited supply'
+ end
+
+ context 'when stock item prevents backordering' do
+ before { stock_item.update_attributes(backorderable: false) }
+
+ specify { expect(subject.backorderable?).to be false }
+
+ it 'can_supply? only upto total_on_hand' do
+ expect(subject.can_supply?(1)).to be true
+ expect(subject.can_supply?(10)).to be true
+ expect(subject.can_supply?(11)).to be false
+ end
+ end
+
+ end
+
+ context 'with multiple stock locations/items' do
+ let!(:stock_location_2) { create :stock_location }
+ let!(:stock_location_3) { create :stock_location, active: false }
+
+ before do
+ stock_location_2.stock_items.where(variant_id: stock_item.variant).update_all(count_on_hand: 5, backorderable: false)
+ stock_location_3.stock_items.where(variant_id: stock_item.variant).update_all(count_on_hand: 5, backorderable: false)
+ end
+
+ it 'total_on_hand should total all active stock_items' do
+ expect(subject.total_on_hand).to eq(15)
+ end
+
+ context 'when any stock item allows backordering' do
+ specify { expect(subject.backorderable?).to be true }
+
+ it_should_behave_like 'unlimited supply'
+ end
+
+ context 'when all stock items prevent backordering' do
+ before { stock_item.update_attributes(backorderable: false) }
+
+ specify { expect(subject.backorderable?).to be false }
+
+ it 'can_supply? upto total_on_hand' do
+ expect(subject.can_supply?(1)).to be true
+ expect(subject.can_supply?(15)).to be true
+ expect(subject.can_supply?(16)).to be false
+ end
+ end
+
+ end
+
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/splitter/backordered_spec.rb b/core/spec/models/spree/stock/splitter/backordered_spec.rb
new file mode 100644
index 00000000000..97d9bde96a9
--- /dev/null
+++ b/core/spec/models/spree/stock/splitter/backordered_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ module Splitter
+ describe Backordered, :type => :model do
+ let(:variant) { build(:variant) }
+
+ let(:packer) { build(:stock_packer) }
+
+ subject { Backordered.new(packer) }
+
+ it 'splits packages by status' do
+ package = Package.new(packer.stock_location)
+ 4.times { package.add build(:inventory_unit, variant: variant) }
+ 5.times { package.add build(:inventory_unit, variant: variant), :backordered }
+
+ packages = subject.split([package])
+ expect(packages.count).to eq 2
+ expect(packages.first.quantity).to eq 4
+ expect(packages.first.on_hand.count).to eq 4
+ expect(packages.first.backordered.count).to eq 0
+
+ expect(packages[1].contents.count).to eq 5
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/splitter/base_spec.rb b/core/spec/models/spree/stock/splitter/base_spec.rb
new file mode 100644
index 00000000000..ad64d10c709
--- /dev/null
+++ b/core/spec/models/spree/stock/splitter/base_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ module Splitter
+ describe Base, :type => :model do
+ let(:packer) { build(:stock_packer) }
+
+ it 'continues to splitter chain' do
+ splitter1 = Base.new(packer)
+ splitter2 = Base.new(packer, splitter1)
+ packages = []
+
+ expect(splitter1).to receive(:split).with(packages)
+ splitter2.split(packages)
+ end
+
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/splitter/shipping_category_spec.rb b/core/spec/models/spree/stock/splitter/shipping_category_spec.rb
new file mode 100644
index 00000000000..554dc0862ea
--- /dev/null
+++ b/core/spec/models/spree/stock/splitter/shipping_category_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ module Splitter
+ describe ShippingCategory, :type => :model do
+
+ let(:variant1) { build(:variant) }
+ let(:variant2) { build(:variant) }
+ let(:shipping_category_1) { create(:shipping_category, name: 'A') }
+ let(:shipping_category_2) { create(:shipping_category, name: 'B') }
+
+ def inventory_unit1
+ build(:inventory_unit, variant: variant1).tap do |inventory_unit|
+ inventory_unit.variant.product.shipping_category = shipping_category_1
+ end
+ end
+
+ def inventory_unit2
+ build(:inventory_unit, variant: variant2).tap do |inventory_unit|
+ inventory_unit.variant.product.shipping_category = shipping_category_2
+ end
+ end
+
+ let(:packer) { build(:stock_packer) }
+
+ subject { ShippingCategory.new(packer) }
+
+ it 'splits each package by shipping category' do
+ package1 = Package.new(packer.stock_location)
+ 4.times { package1.add inventory_unit1 }
+ 8.times { package1.add inventory_unit2 }
+
+ package2 = Package.new(packer.stock_location)
+ 6.times { package2.add inventory_unit1 }
+ 9.times { package2.add inventory_unit2, :backordered }
+
+ packages = subject.split([package1, package2])
+ expect(packages[0].quantity).to eq 4
+ expect(packages[1].quantity).to eq 8
+ expect(packages[2].quantity).to eq 6
+ expect(packages[3].quantity).to eq 9
+ end
+
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock/splitter/weight_spec.rb b/core/spec/models/spree/stock/splitter/weight_spec.rb
new file mode 100644
index 00000000000..4d788794625
--- /dev/null
+++ b/core/spec/models/spree/stock/splitter/weight_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+module Spree
+ module Stock
+ module Splitter
+ describe Weight, :type => :model do
+ let(:packer) { build(:stock_packer) }
+ let(:variant) { build(:base_variant, :weight => 100) }
+
+ subject { Weight.new(packer) }
+
+ it 'splits and keeps splitting until all packages are underweight' do
+ package = Package.new(packer.stock_location)
+ 4.times { package.add build(:inventory_unit, variant: variant) }
+ packages = subject.split([package])
+ expect(packages.size).to eq 4
+ end
+
+ it 'handles packages that can not be reduced' do
+ package = Package.new(packer.stock_location)
+ allow(variant).to receive_messages(:weight => 200)
+ 2.times { package.add build(:inventory_unit, variant: variant) }
+ packages = subject.split([package])
+ expect(packages.size).to eq 2
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock_item_spec.rb b/core/spec/models/spree/stock_item_spec.rb
new file mode 100644
index 00000000000..1ae2d04ad32
--- /dev/null
+++ b/core/spec/models/spree/stock_item_spec.rb
@@ -0,0 +1,410 @@
+require 'spec_helper'
+
+describe Spree::StockItem, :type => :model do
+ let(:stock_location) { create(:stock_location_with_items) }
+
+ subject { stock_location.stock_items.order(:id).first }
+
+ it 'maintains the count on hand for a variant' do
+ expect(subject.count_on_hand).to eq 10
+ end
+
+ it "can return the stock item's variant's name" do
+ expect(subject.variant_name).to eq(subject.variant.name)
+ end
+
+ context "available to be included in shipment" do
+ context "has stock" do
+ it { expect(subject).to be_available }
+ end
+
+ context "backorderable" do
+ before { subject.backorderable = true }
+ it { expect(subject).to be_available }
+ end
+
+ context "no stock and not backorderable" do
+ before do
+ subject.backorderable = false
+ allow(subject).to receive_messages(count_on_hand: 0)
+ end
+
+ it { expect(subject).not_to be_available }
+ end
+ end
+
+ describe 'reduce_count_on_hand_to_zero' do
+ context 'when count_on_hand > 0' do
+ before(:each) do
+ subject.update_column('count_on_hand', 4)
+ subject.reduce_count_on_hand_to_zero
+ end
+
+ it { expect(subject.count_on_hand).to eq(0) }
+ end
+
+ context 'when count_on_hand > 0' do
+ before(:each) do
+ subject.update_column('count_on_hand', -4)
+ @count_on_hand = subject.count_on_hand
+ subject.reduce_count_on_hand_to_zero
+ end
+
+ it { expect(subject.count_on_hand).to eq(@count_on_hand) }
+ end
+ end
+
+ context "adjust count_on_hand" do
+ let!(:current_on_hand) { subject.count_on_hand }
+
+ it 'is updated pessimistically' do
+ copy = Spree::StockItem.find(subject.id)
+
+ subject.adjust_count_on_hand(5)
+ expect(subject.count_on_hand).to eq(current_on_hand + 5)
+
+ expect(copy.count_on_hand).to eq(current_on_hand)
+ copy.adjust_count_on_hand(5)
+ expect(copy.count_on_hand).to eq(current_on_hand + 10)
+ end
+
+ context "item out of stock (by two items)" do
+ let(:inventory_unit) { double('InventoryUnit') }
+ let(:inventory_unit_2) { double('InventoryUnit2') }
+
+ before do
+ allow(subject).to receive_messages(:backordered_inventory_units => [inventory_unit, inventory_unit_2])
+ subject.update_column(:count_on_hand, -2)
+ end
+
+ # Regression test for #3755
+ it "processes existing backorders, even with negative stock" do
+ expect(inventory_unit).to receive(:fill_backorder)
+ expect(inventory_unit_2).not_to receive(:fill_backorder)
+ subject.adjust_count_on_hand(1)
+ expect(subject.count_on_hand).to eq(-1)
+ end
+
+ # Test for #3755
+ it "does not process backorders when stock is adjusted negatively" do
+ expect(inventory_unit).not_to receive(:fill_backorder)
+ expect(inventory_unit_2).not_to receive(:fill_backorder)
+ subject.adjust_count_on_hand(-1)
+ expect(subject.count_on_hand).to eq(-3)
+ end
+
+ context "adds new items" do
+ before { allow(subject).to receive_messages(:backordered_inventory_units => [inventory_unit, inventory_unit_2]) }
+
+ it "fills existing backorders" do
+ expect(inventory_unit).to receive(:fill_backorder)
+ expect(inventory_unit_2).to receive(:fill_backorder)
+
+ subject.adjust_count_on_hand(3)
+ expect(subject.count_on_hand).to eq(1)
+ end
+ end
+ end
+ end
+
+ context "set count_on_hand" do
+ let!(:current_on_hand) { subject.count_on_hand }
+
+ it 'is updated pessimistically' do
+ copy = Spree::StockItem.find(subject.id)
+
+ subject.set_count_on_hand(5)
+ expect(subject.count_on_hand).to eq(5)
+
+ expect(copy.count_on_hand).to eq(current_on_hand)
+ copy.set_count_on_hand(10)
+ expect(copy.count_on_hand).to eq(current_on_hand)
+ end
+
+ context "item out of stock (by two items)" do
+ let(:inventory_unit) { double('InventoryUnit') }
+ let(:inventory_unit_2) { double('InventoryUnit2') }
+
+ before { subject.set_count_on_hand(-2) }
+
+ it "doesn't process backorders" do
+ expect(subject).not_to receive(:backordered_inventory_units)
+ end
+
+ context "adds new items" do
+ before { allow(subject).to receive_messages(:backordered_inventory_units => [inventory_unit, inventory_unit_2]) }
+
+ it "fills existing backorders" do
+ expect(inventory_unit).to receive(:fill_backorder)
+ expect(inventory_unit_2).to receive(:fill_backorder)
+
+ subject.set_count_on_hand(1)
+ expect(subject.count_on_hand).to eq(1)
+ end
+ end
+ end
+ end
+
+ context "with stock movements" do
+ before { Spree::StockMovement.create(stock_item: subject, quantity: 1) }
+
+ it "doesnt raise ReadOnlyRecord error" do
+ expect { subject.destroy }.not_to raise_error
+ end
+ end
+
+ context "destroyed" do
+ before { subject.destroy }
+
+ it "recreates stock item just fine" do
+ expect {
+ stock_location.stock_items.create!(variant: subject.variant)
+ }.not_to raise_error
+ end
+
+ it "doesnt allow recreating more than one stock item at once" do
+ stock_location.stock_items.create!(variant: subject.variant)
+
+ expect {
+ stock_location.stock_items.create!(variant: subject.variant)
+ }.to raise_error
+ end
+ end
+
+ describe "#after_save" do
+ before do
+ subject.variant.update_column(:updated_at, 1.day.ago)
+ end
+
+ context "binary_inventory_cache is set to false (default)" do
+ context "in_stock? changes" do
+ it "touches its variant" do
+ expect do
+ subject.adjust_count_on_hand(subject.count_on_hand * -1)
+ end.to change { subject.variant.reload.updated_at }
+ end
+ end
+
+ context "in_stock? does not change" do
+ it "touches its variant" do
+ expect do
+ subject.adjust_count_on_hand((subject.count_on_hand * -1) + 1)
+ end.to change { subject.variant.reload.updated_at }
+ end
+ end
+ end
+
+ context "binary_inventory_cache is set to true" do
+ before { Spree::Config.binary_inventory_cache = true }
+ context "in_stock? changes" do
+ it "touches its variant" do
+ expect do
+ subject.adjust_count_on_hand(subject.count_on_hand * -1)
+ end.to change { subject.variant.reload.updated_at }
+ end
+ end
+
+ context "in_stock? does not change" do
+ it "does not touch its variant" do
+ expect do
+ subject.adjust_count_on_hand((subject.count_on_hand * -1) + 1)
+ end.not_to change { subject.variant.reload.updated_at }
+ end
+ end
+
+ context "when a new stock location is added" do
+ it "touches its variant" do
+ expect do
+ create(:stock_location)
+ end.to change { subject.variant.reload.updated_at }
+ end
+ end
+ end
+ end
+
+ describe "#after_touch" do
+ it "touches its variant" do
+ expect do
+ subject.touch
+ end.to change { subject.variant.updated_at }
+ end
+ end
+
+ # Regression test for #4651
+ context "variant" do
+ it "can be found even if the variant is deleted" do
+ subject.variant.destroy
+ expect(subject.reload.variant).not_to be_nil
+ end
+ end
+
+ describe 'validations' do
+ describe 'count_on_hand' do
+ shared_examples_for 'valid count_on_hand' do
+ before(:each) do
+ subject.save
+ end
+
+ it 'has :no errors_on' do
+ expect(subject.errors_on(:count_on_hand).size).to eq(0)
+ end
+ end
+
+ shared_examples_for 'not valid count_on_hand' do
+ before(:each) do
+ subject.save
+ end
+
+ it 'has 1 error_on' do
+ expect(subject.error_on(:count_on_hand).size).to eq(1)
+ end
+ it { expect(subject.errors[:count_on_hand]).to include 'must be greater than or equal to 0' }
+ end
+
+ context 'when count_on_hand not changed' do
+ context 'when not backorderable' do
+ before(:each) do
+ subject.backorderable = false
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+
+ context 'when backorderable' do
+ before(:each) do
+ subject.backorderable = true
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when count_on_hand changed' do
+ context 'when backorderable' do
+ before(:each) do
+ subject.backorderable = true
+ end
+ context 'when both count_on_hand and count_on_hand_was are positive' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 3)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+
+ context 'when count_on_hand is smaller than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 2)
+ end
+
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand and count_on_hand_was are negative' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, -3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 2)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+
+ context 'when count_on_hand is smaller than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 3)
+ end
+
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand is positive and count_on_hand_was is negative' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, -3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 6)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand is negative and count_on_hand_was is positive' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 6)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+ end
+
+ context 'when not backorderable' do
+ before(:each) do
+ subject.backorderable = false
+ end
+
+ context 'when both count_on_hand and count_on_hand_was are positive' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 3)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+
+ context 'when count_on_hand is smaller than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 2)
+ end
+
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand and count_on_hand_was are negative' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, -3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 2)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+
+ context 'when count_on_hand is smaller than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, -3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 3)
+ end
+
+ it_should_behave_like 'not valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand is positive and count_on_hand_was is negative' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, -3)
+ subject.send(:count_on_hand=, subject.count_on_hand + 6)
+ end
+ it_should_behave_like 'valid count_on_hand'
+ end
+ end
+
+ context 'when both count_on_hand is negative and count_on_hand_was is positive' do
+ context 'when count_on_hand is greater than count_on_hand_was' do
+ before(:each) do
+ subject.update_column(:count_on_hand, 3)
+ subject.send(:count_on_hand=, subject.count_on_hand - 6)
+ end
+ it_should_behave_like 'not valid count_on_hand'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock_location_spec.rb b/core/spec/models/spree/stock_location_spec.rb
new file mode 100644
index 00000000000..9388e4b20f4
--- /dev/null
+++ b/core/spec/models/spree/stock_location_spec.rb
@@ -0,0 +1,248 @@
+require 'spec_helper'
+
+module Spree
+ describe StockLocation, :type => :model do
+ subject { create(:stock_location_with_items, backorderable_default: true) }
+ let(:stock_item) { subject.stock_items.order(:id).first }
+ let(:variant) { stock_item.variant }
+
+ it 'creates stock_items for all variants' do
+ expect(subject.stock_items.count).to eq Variant.count
+ end
+
+ context "handling stock items" do
+ let!(:variant) { create(:variant) }
+
+ context "given a variant" do
+ subject { StockLocation.create(name: "testing", propagate_all_variants: false) }
+
+ context "set up" do
+ it "creates stock item" do
+ expect(subject).to receive(:propagate_variant)
+ subject.set_up_stock_item(variant)
+ end
+
+ context "stock item exists" do
+ let!(:stock_item) { subject.propagate_variant(variant) }
+
+ it "returns existing stock item" do
+ expect(subject.set_up_stock_item(variant)).to eq(stock_item)
+ end
+ end
+ end
+
+ context "propagate variants" do
+ let(:stock_item) { subject.propagate_variant(variant) }
+
+ it "creates a new stock item" do
+ expect {
+ subject.propagate_variant(variant)
+ }.to change{ StockItem.count }.by(1)
+ end
+
+ context "passes backorderable default config" do
+ context "true" do
+ before { subject.backorderable_default = true }
+ it { expect(stock_item.backorderable).to be true }
+ end
+
+ context "false" do
+ before { subject.backorderable_default = false }
+ it { expect(stock_item.backorderable).to be false }
+ end
+ end
+ end
+
+ context "propagate all variants" do
+ subject { StockLocation.new(name: "testing") }
+
+ context "true" do
+ before { subject.propagate_all_variants = true }
+
+ specify do
+ expect(subject).to receive(:propagate_variant).at_least(:once)
+ subject.save!
+ end
+ end
+
+ context "false" do
+ before { subject.propagate_all_variants = false }
+
+ specify do
+ expect(subject).not_to receive(:propagate_variant)
+ subject.save!
+ end
+ end
+ end
+ end
+ end
+
+ it 'finds a stock_item for a variant' do
+ stock_item = subject.stock_item(variant)
+ expect(stock_item.count_on_hand).to eq 10
+ end
+
+ it 'finds a stock_item for a variant by id' do
+ stock_item = subject.stock_item(variant.id)
+ expect(stock_item.variant).to eq variant
+ end
+
+ it 'returns nil when stock_item is not found for variant' do
+ stock_item = subject.stock_item(100)
+ expect(stock_item).to be_nil
+ end
+
+ describe '#stock_item_or_create' do
+ before do
+ variant = create(:variant)
+ variant.stock_items.destroy_all
+ variant.save
+ end
+
+ it 'creates a stock_item if not found for a variant' do
+ stock_item = subject.stock_item_or_create(variant)
+ expect(stock_item.variant).to eq variant
+ end
+
+ it 'creates a stock_item if not found for a variant_id' do
+ stock_item = subject.stock_item_or_create(variant.id)
+ expect(stock_item.variant).to eq variant
+ end
+ end
+
+ it 'finds a count_on_hand for a variant' do
+ expect(subject.count_on_hand(variant)).to eq 10
+ end
+
+ it 'finds determines if you a variant is backorderable' do
+ expect(subject.backorderable?(variant)).to be true
+ end
+
+ it 'restocks a variant with a positive stock movement' do
+ originator = double
+ expect(subject).to receive(:move).with(variant, 5, originator)
+ subject.restock(variant, 5, originator)
+ end
+
+ it 'unstocks a variant with a negative stock movement' do
+ originator = double
+ expect(subject).to receive(:move).with(variant, -5, originator)
+ subject.unstock(variant, 5, originator)
+ end
+
+ it 'it creates a stock_movement' do
+ expect {
+ subject.move variant, 5
+ }.to change { subject.stock_movements.where(stock_item_id: stock_item).count }.by(1)
+ end
+
+ it 'can be deactivated' do
+ create(:stock_location, :active => true)
+ create(:stock_location, :active => false)
+ expect(Spree::StockLocation.active.count).to eq 1
+ end
+
+ it 'ensures only one stock location is default at a time' do
+ first = create(:stock_location, :active => true, :default => true)
+ second = create(:stock_location, :active => true, :default => true)
+
+ expect(first.reload.default).to eq false
+ expect(second.reload.default).to eq true
+
+ first.default = true
+ first.save!
+
+ expect(first.reload.default).to eq true
+ expect(second.reload.default).to eq false
+ end
+
+ context 'fill_status' do
+ it 'all on_hand with no backordered' do
+ on_hand, backordered = subject.fill_status(variant, 5)
+ expect(on_hand).to eq 5
+ expect(backordered).to eq 0
+ end
+
+ it 'some on_hand with some backordered' do
+ on_hand, backordered = subject.fill_status(variant, 20)
+ expect(on_hand).to eq 10
+ expect(backordered).to eq 10
+ end
+
+ it 'zero on_hand with all backordered' do
+ zero_stock_item = mock_model(StockItem,
+ count_on_hand: 0,
+ backorderable?: true)
+ expect(subject).to receive(:stock_item).with(variant).and_return(zero_stock_item)
+
+ on_hand, backordered = subject.fill_status(variant, 20)
+ expect(on_hand).to eq 0
+ expect(backordered).to eq 20
+ end
+
+ context 'when backordering is not allowed' do
+ before do
+ @stock_item = mock_model(StockItem, backorderable?: false)
+ expect(subject).to receive(:stock_item).with(variant).and_return(@stock_item)
+ end
+
+ it 'all on_hand' do
+ allow(@stock_item).to receive_messages(count_on_hand: 10)
+
+ on_hand, backordered = subject.fill_status(variant, 5)
+ expect(on_hand).to eq 5
+ expect(backordered).to eq 0
+ end
+
+ it 'some on_hand' do
+ allow(@stock_item).to receive_messages(count_on_hand: 10)
+
+ on_hand, backordered = subject.fill_status(variant, 20)
+ expect(on_hand).to eq 10
+ expect(backordered).to eq 0
+ end
+
+ it 'zero on_hand' do
+ allow(@stock_item).to receive_messages(count_on_hand: 0)
+
+ on_hand, backordered = subject.fill_status(variant, 20)
+ expect(on_hand).to eq 0
+ expect(backordered).to eq 0
+ end
+ end
+
+ context 'without stock_items' do
+ subject { create(:stock_location) }
+ let(:variant) { create(:base_variant) }
+
+ it 'zero on_hand and backordered' do
+ subject
+ variant.stock_items.destroy_all
+ on_hand, backordered = subject.fill_status(variant, 1)
+ expect(on_hand).to eq 0
+ expect(backordered).to eq 0
+ end
+ end
+ end
+
+ context '#state_text' do
+ context 'state is blank' do
+ subject { StockLocation.create(name: "testing", state: nil, state_name: 'virginia') }
+ specify { expect(subject.state_text).to eq('virginia') }
+ end
+
+ context 'both name and abbr is present' do
+ let(:state) { stub_model(Spree::State, name: 'virginia', abbr: 'va') }
+ subject { StockLocation.create(name: "testing", state: state, state_name: nil) }
+ specify { expect(subject.state_text).to eq('va') }
+ end
+
+ context 'only name is present' do
+ let(:state) { stub_model(Spree::State, name: 'virginia', abbr: nil) }
+ subject { StockLocation.create(name: "testing", state: state, state_name: nil) }
+ specify { expect(subject.state_text).to eq('virginia') }
+ end
+ end
+
+ end
+end
diff --git a/core/spec/models/spree/stock_movement_spec.rb b/core/spec/models/spree/stock_movement_spec.rb
new file mode 100644
index 00000000000..33a178651b3
--- /dev/null
+++ b/core/spec/models/spree/stock_movement_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Spree::StockMovement, :type => :model do
+ let(:stock_location) { create(:stock_location_with_items) }
+ let(:stock_item) { stock_location.stock_items.order(:id).first }
+ subject { build(:stock_movement, stock_item: stock_item) }
+
+ it 'should belong to a stock item' do
+ expect(subject).to respond_to(:stock_item)
+ end
+
+ it 'is readonly unless new' do
+ subject.save
+ expect {
+ subject.save
+ }.to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
+
+ it 'does not update count on hand when track inventory levels is false' do
+ Spree::Config[:track_inventory_levels] = false
+ subject.quantity = 1
+ subject.save
+ stock_item.reload
+ expect(stock_item.count_on_hand).to eq(10)
+ end
+
+ it 'does not update count on hand when variant inventory tracking is off' do
+ stock_item.variant.track_inventory = false
+ subject.quantity = 1
+ subject.save
+ stock_item.reload
+ expect(stock_item.count_on_hand).to eq(10)
+ end
+
+ context "when quantity is negative" do
+ context "after save" do
+ it "should decrement the stock item count on hand" do
+ subject.quantity = -1
+ subject.save
+ stock_item.reload
+ expect(stock_item.count_on_hand).to eq(9)
+ end
+ end
+ end
+
+ context "when quantity is positive" do
+ context "after save" do
+ it "should increment the stock item count on hand" do
+ subject.quantity = 1
+ subject.save
+ stock_item.reload
+ expect(stock_item.count_on_hand).to eq(11)
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/stock_transfer_spec.rb b/core/spec/models/spree/stock_transfer_spec.rb
new file mode 100644
index 00000000000..23f94b942dd
--- /dev/null
+++ b/core/spec/models/spree/stock_transfer_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+module Spree
+ describe StockTransfer, :type => :model do
+ let(:destination_location) { create(:stock_location_with_items) }
+ let(:source_location) { create(:stock_location_with_items) }
+ let(:stock_item) { source_location.stock_items.order(:id).first }
+ let(:variant) { stock_item.variant }
+
+ subject { StockTransfer.create(reference: 'PO123') }
+
+ describe '#reference' do
+ subject { super().reference }
+ it { is_expected.to eq 'PO123' }
+ end
+
+ describe '#to_param' do
+ subject { super().to_param }
+ it { is_expected.to match /T\d+/ }
+ end
+
+ it 'transfers variants between 2 locations' do
+ variants = { variant => 5 }
+
+ subject.transfer(source_location,
+ destination_location,
+ variants)
+
+ expect(source_location.count_on_hand(variant)).to eq 5
+ expect(destination_location.count_on_hand(variant)).to eq 5
+
+ expect(subject.source_location).to eq source_location
+ expect(subject.destination_location).to eq destination_location
+
+ expect(subject.source_movements.first.quantity).to eq -5
+ expect(subject.destination_movements.first.quantity).to eq 5
+ end
+
+ it 'receive new inventory (from a vendor)' do
+ variants = { variant => 5 }
+
+ subject.receive(destination_location, variants)
+
+ expect(destination_location.count_on_hand(variant)).to eq 5
+
+ expect(subject.source_location).to be_nil
+ expect(subject.destination_location).to eq destination_location
+ end
+ end
+end
diff --git a/core/spec/models/spree/store_spec.rb b/core/spec/models/spree/store_spec.rb
new file mode 100644
index 00000000000..60edbf1f38c
--- /dev/null
+++ b/core/spec/models/spree/store_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Spree::Store, :type => :model do
+
+ describe ".by_url" do
+ let!(:store) { create(:store, url: "website1.com\nwww.subdomain.com") }
+ let!(:store_2) { create(:store, url: 'freethewhales.com') }
+
+ it "should find stores by url" do
+ by_domain = Spree::Store.by_url('www.subdomain.com')
+
+ expect(by_domain).to include(store)
+ expect(by_domain).not_to include(store_2)
+ end
+ end
+
+ describe '.current' do
+ # there is a default store created with the test_app rake task.
+ let!(:store_1) { Spree::Store.first || create(:store) }
+
+ let!(:store_2) { create(:store, default: false, url: 'www.subdomain.com') }
+
+ it 'should return default when no domain' do
+ expect(subject.class.current).to eql(store_1)
+ end
+
+ it 'should return store for domain' do
+ expect(subject.class.current('spreecommerce.com')).to eql(store_1)
+ expect(subject.class.current('www.subdomain.com')).to eql(store_2)
+ end
+ end
+
+ describe ".default" do
+ let!(:store) { create(:store) }
+ let!(:store_2) { create(:store, default: true) }
+
+ it "should ensure there is a default if one doesn't exist yet" do
+ expect(store_2.default).to be true
+ end
+
+ it "should ensure there is only one default" do
+ [store, store_2].each(&:reload)
+
+ expect(Spree::Store.where(default: true).count).to eq(1)
+ expect(store_2.default).to be true
+ expect(store.default).not_to be true
+ end
+ end
+
+end
diff --git a/core/spec/models/spree/tax_category_spec.rb b/core/spec/models/spree/tax_category_spec.rb
new file mode 100644
index 00000000000..236e6ee752d
--- /dev/null
+++ b/core/spec/models/spree/tax_category_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Spree::TaxCategory, :type => :model do
+ context 'default tax category' do
+ let(:tax_category) { create(:tax_category) }
+ let(:new_tax_category) { create(:tax_category) }
+
+ before do
+ tax_category.update_column(:is_default, true)
+ end
+
+ it "should undefault the previous default tax category" do
+ new_tax_category.update_attributes({:is_default => true})
+ expect(new_tax_category.is_default).to be true
+
+ tax_category.reload
+ expect(tax_category.is_default).to be false
+ end
+
+ it "should undefault the previous default tax category except when updating the existing default tax category" do
+ tax_category.update_column(:description, "Updated description")
+
+ tax_category.reload
+ expect(tax_category.is_default).to be true
+ end
+ end
+end
diff --git a/core/spec/models/spree/tax_rate_spec.rb b/core/spec/models/spree/tax_rate_spec.rb
new file mode 100644
index 00000000000..74980468433
--- /dev/null
+++ b/core/spec/models/spree/tax_rate_spec.rb
@@ -0,0 +1,379 @@
+require 'spec_helper'
+
+describe Spree::TaxRate, :type => :model do
+ context "match" do
+ let(:order) { create(:order) }
+ let(:country) { create(:country) }
+ let(:tax_category) { create(:tax_category) }
+ let(:calculator) { Spree::Calculator::FlatRate.new }
+
+ it "should return an empty array when tax_zone is nil" do
+ allow(order).to receive_messages :tax_zone => nil
+ expect(Spree::TaxRate.match(order.tax_zone)).to eq([])
+ end
+
+ context "when no rate zones match the tax zone" do
+ before do
+ Spree::TaxRate.create(:amount => 1, :zone => create(:zone))
+ end
+
+ context "when there is no default tax zone" do
+ before do
+ @zone = create(:zone, :name => "Country Zone", :default_tax => false, :zone_members => [])
+ @zone.zone_members.create(:zoneable => country)
+ end
+
+ it "should return an empty array" do
+ allow(order).to receive_messages :tax_zone => @zone
+ expect(Spree::TaxRate.match(order.tax_zone)).to eq([])
+ end
+
+ it "should return the rate that matches the rate zone" do
+ rate = Spree::TaxRate.create(
+ :amount => 1,
+ :zone => @zone,
+ :tax_category => tax_category,
+ :calculator => calculator
+ )
+
+ allow(order).to receive_messages :tax_zone => @zone
+ expect(Spree::TaxRate.match(order.tax_zone)).to eq([rate])
+ end
+
+ it "should return all rates that match the rate zone" do
+ rate1 = Spree::TaxRate.create(
+ :amount => 1,
+ :zone => @zone,
+ :tax_category => tax_category,
+ :calculator => calculator
+ )
+
+ rate2 = Spree::TaxRate.create(
+ :amount => 2,
+ :zone => @zone,
+ :tax_category => tax_category,
+ :calculator => Spree::Calculator::FlatRate.new
+ )
+
+ allow(order).to receive_messages :tax_zone => @zone
+ expect(Spree::TaxRate.match(order.tax_zone)).to match_array([rate1, rate2])
+ end
+
+ context "when the tax_zone is contained within a rate zone" do
+ before do
+ sub_zone = create(:zone, :name => "State Zone", :zone_members => [])
+ sub_zone.zone_members.create(:zoneable => create(:state, :country => country))
+ allow(order).to receive_messages :tax_zone => sub_zone
+ @rate = Spree::TaxRate.create(
+ :amount => 1,
+ :zone => @zone,
+ :tax_category => tax_category,
+ :calculator => calculator
+ )
+ end
+
+ it "should return the rate zone" do
+ expect(Spree::TaxRate.match(order.tax_zone)).to eq([@rate])
+ end
+ end
+ end
+
+ context "when there is a default tax zone" do
+ before do
+ @zone = create(:zone, :name => "Country Zone", :default_tax => true, :zone_members => [])
+ @zone.zone_members.create(:zoneable => country)
+ end
+
+ let(:included_in_price) { false }
+ let!(:rate) do
+ Spree::TaxRate.create(:amount => 1,
+ :zone => @zone,
+ :tax_category => tax_category,
+ :calculator => calculator,
+ :included_in_price => included_in_price)
+ end
+
+ subject { Spree::TaxRate.match(order.tax_zone) }
+
+ context "when the order has the same tax zone" do
+ before do
+ allow(order).to receive_messages :tax_zone => @zone
+ allow(order).to receive_messages :tax_address => tax_address
+ end
+
+ let(:tax_address) { stub_model(Spree::Address) }
+
+ context "when the tax is not a VAT" do
+ it { is_expected.to eq([rate]) }
+ end
+
+ context "when the tax is a VAT" do
+ let(:included_in_price) { true }
+ it { is_expected.to eq([rate]) }
+ end
+ end
+
+ context "when the order has a different tax zone" do
+ before do
+ allow(order).to receive_messages :tax_zone => create(:zone, :name => "Other Zone")
+ allow(order).to receive_messages :tax_address => tax_address
+ end
+
+ context "when the order has a tax_address" do
+ let(:tax_address) { stub_model(Spree::Address) }
+
+ context "when the tax is a VAT" do
+ let(:included_in_price) { true }
+ # The rate should match in this instance because:
+ # 1) It's the default rate (and as such, a negative adjustment should apply)
+ it { is_expected.to eq([rate]) }
+ end
+
+ context "when the tax is not VAT" do
+ it "returns no tax rate" do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ context "when the order does not have a tax_address" do
+ let(:tax_address) { nil}
+
+ context "when the tax is a VAT" do
+ let(:included_in_price) { true }
+ # The rate should match in this instance because:
+ # 1) The order has no tax address by this stage
+ # 2) With no tax address, it has no tax zone
+ # 3) Therefore, we assume the default tax zone
+ # 4) This default zone has a default tax rate.
+ it { is_expected.to eq([rate]) }
+ end
+
+ context "when the tax is not a VAT" do
+ it { is_expected.to be_empty }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context ".adjust" do
+ let(:order) { stub_model(Spree::Order) }
+ let(:tax_category_1) { stub_model(Spree::TaxCategory) }
+ let(:tax_category_2) { stub_model(Spree::TaxCategory) }
+ let(:rate_1) { stub_model(Spree::TaxRate, :tax_category => tax_category_1) }
+ let(:rate_2) { stub_model(Spree::TaxRate, :tax_category => tax_category_2) }
+
+ context "with line items" do
+ let(:line_item) do
+ stub_model(Spree::LineItem,
+ :price => 10.0,
+ :quantity => 1,
+ :tax_category => tax_category_1,
+ :variant => stub_model(Spree::Variant)
+ )
+ end
+
+ let(:line_items) { [line_item] }
+
+ before do
+ allow(Spree::TaxRate).to receive_messages :match => [rate_1, rate_2]
+ end
+
+ it "should apply adjustments for two tax rates to the order" do
+ expect(rate_1).to receive(:adjust)
+ expect(rate_2).not_to receive(:adjust)
+ Spree::TaxRate.adjust(order.tax_zone, line_items)
+ end
+ end
+
+ context "with shipments" do
+ let(:shipments) { [stub_model(Spree::Shipment, :cost => 10.0, :tax_category => tax_category_1)] }
+
+ before do
+ allow(Spree::TaxRate).to receive_messages :match => [rate_1, rate_2]
+ end
+
+ it "should apply adjustments for two tax rates to the order" do
+ expect(rate_1).to receive(:adjust)
+ expect(rate_2).not_to receive(:adjust)
+ Spree::TaxRate.adjust(order.tax_zone, shipments)
+ end
+ end
+ end
+
+ context "#adjust" do
+ before do
+ @country = create(:country)
+ @zone = create(:zone, :name => "Country Zone", :default_tax => true, :zone_members => [])
+ @zone.zone_members.create(:zoneable => @country)
+ @category = Spree::TaxCategory.create :name => "Taxable Foo"
+ @category2 = Spree::TaxCategory.create(:name => "Non Taxable")
+ @rate1 = Spree::TaxRate.create(
+ :amount => 0.10,
+ :calculator => Spree::Calculator::DefaultTax.create,
+ :tax_category => @category,
+ :zone => @zone
+ )
+ @rate2 = Spree::TaxRate.create(
+ :amount => 0.05,
+ :calculator => Spree::Calculator::DefaultTax.create,
+ :tax_category => @category,
+ :zone => @zone
+ )
+ @order = Spree::Order.create!
+ @taxable = create(:product, :tax_category => @category)
+ @nontaxable = create(:product, :tax_category => @category2)
+ end
+
+ context "not taxable line item " do
+ let!(:line_item) { @order.contents.add(@nontaxable.master, 1) }
+
+ it "should not create a tax adjustment" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.tax.charge.count).to eq(0)
+ end
+
+ it "should not create a refund" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.credit.count).to eq(0)
+ end
+ end
+
+ context "taxable line item" do
+ let!(:line_item) { @order.contents.add(@taxable.master, 1) }
+
+ context "when price includes tax" do
+ before do
+ @rate1.update_column(:included_in_price, true)
+ @rate2.update_column(:included_in_price, true)
+ Spree::TaxRate.store_pre_tax_amount(line_item, [@rate1, @rate2])
+ end
+
+ context "when zone is contained by default tax zone" do
+ it "should create two adjustments, one for each tax rate" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.count).to eq(1)
+ end
+
+ it "should not create a tax refund" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.credit.count).to eq(0)
+ end
+ end
+
+ context "when order's zone is neither the default zone, or included in the default zone, but matches the rate's zone" do
+ before do
+ # With no zone members, this zone will not contain anything
+ # Previously:
+ # Zone.stub_chain :default_tax, :contains? => false
+ @zone.zone_members.delete_all
+ end
+ it "should create an adjustment" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.charge.count).to eq(1)
+ end
+
+ it "should not create a tax refund for each tax rate" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.credit.count).to eq(0)
+ end
+ end
+
+ context "when order's zone does not match default zone, is not included in the default zone, AND does not match the rate's zone" do
+ before do
+ @new_zone = create(:zone, :name => "New Zone", :default_tax => false)
+ @new_country = create(:country, :name => "New Country")
+ @new_zone.zone_members.create(:zoneable => @new_country)
+ @order.ship_address = create(:address, :country => @new_country)
+ @order.save
+ @order.reload
+ end
+
+ it "should not create positive adjustments" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.charge.count).to eq(0)
+ end
+
+ it "should create a tax refund for each tax rate" do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.credit.count).to eq(1)
+ end
+ end
+
+ context "when price does not include tax" do
+ before do
+ allow(@order).to receive_messages :tax_zone => @zone
+ [@rate1, @rate2].each do |rate|
+ rate.included_in_price = false
+ rate.zone = @zone
+ rate.save
+ end
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ end
+
+ it "should delete adjustments for open order when taxrate is deleted" do
+ @rate1.destroy!
+ @rate2.destroy!
+ expect(line_item.adjustments.count).to eq(0)
+ end
+
+ it "should not delete adjustments for complete order when taxrate is deleted" do
+ @order.update_column :completed_at, Time.now
+ @rate1.destroy!
+ @rate2.destroy!
+ expect(line_item.adjustments.count).to eq(2)
+ end
+
+ it "should create an adjustment" do
+ expect(line_item.adjustments.count).to eq(2)
+ end
+
+ it "should not create a tax refund" do
+ expect(line_item.adjustments.credit.count).to eq(0)
+ end
+
+ describe 'tax adjustments' do
+ before { Spree::TaxRate.adjust(@order.tax_zone, @order.line_items) }
+
+ it "should apply adjustments when a tax zone is present" do
+ expect(line_item.adjustments.count).to eq(2)
+ end
+
+ describe 'when the tax zone is removed' do
+ before { allow(@order).to receive_messages :tax_zone => nil }
+
+ it 'does not apply any adjustments' do
+ Spree::TaxRate.adjust(@order.tax_zone, @order.line_items)
+ expect(line_item.adjustments.count).to eq(0)
+ end
+ end
+ end
+ end
+
+ context "when two rates apply" do
+ before do
+ @price_before_taxes = line_item.price / (1 + @rate1.amount + @rate2.amount)
+ # Use the same rounding method as in DefaultTax calculator
+ @price_before_taxes = BigDecimal.new(@price_before_taxes).round(2, BigDecimal::ROUND_HALF_UP)
+ line_item.update_column(:pre_tax_amount, @price_before_taxes)
+ # Clear out any previously automatically-applied adjustments
+ @order.all_adjustments.delete_all
+ @rate1.adjust(@order.tax_zone, line_item)
+ @rate2.adjust(@order.tax_zone, line_item)
+ end
+
+ it "should create two price adjustments" do
+ expect(@order.line_item_adjustments.count).to eq(2)
+ end
+
+ it "price adjustments should be accurate" do
+ included_tax = @order.line_item_adjustments.sum(:amount)
+ expect(@price_before_taxes + included_tax).to eq(line_item.price)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/taxon_spec.rb b/core/spec/models/spree/taxon_spec.rb
new file mode 100644
index 00000000000..0a9ee6f1bb1
--- /dev/null
+++ b/core/spec/models/spree/taxon_spec.rb
@@ -0,0 +1,74 @@
+# coding: UTF-8
+
+require 'spec_helper'
+
+describe Spree::Taxon, :type => :model do
+ let(:taxon) { FactoryGirl.build(:taxon, :name => "Ruby on Rails") }
+
+ describe '#to_param' do
+ subject { super().to_param }
+ it { is_expected.to eql taxon.permalink }
+ end
+
+ context "set_permalink" do
+
+ it "should set permalink correctly when no parent present" do
+ taxon.set_permalink
+ expect(taxon.permalink).to eql "ruby-on-rails"
+ end
+
+ it "should support Chinese characters" do
+ taxon.name = "你好"
+ taxon.set_permalink
+ expect(taxon.permalink).to eql 'ni-hao'
+ end
+
+ context "with parent taxon" do
+ let(:parent) { FactoryGirl.build(:taxon, :permalink => "brands") }
+ before { allow(taxon).to receive_messages parent: parent }
+
+ it "should set permalink correctly when taxon has parent" do
+ taxon.set_permalink
+ expect(taxon.permalink).to eql "brands/ruby-on-rails"
+ end
+
+ it "should set permalink correctly with existing permalink present" do
+ taxon.permalink = "b/rubyonrails"
+ taxon.set_permalink
+ expect(taxon.permalink).to eql "brands/rubyonrails"
+ end
+
+ it "should support Chinese characters" do
+ taxon.name = "我"
+ taxon.set_permalink
+ expect(taxon.permalink).to eql "brands/wo"
+ end
+
+ # Regression test for #3390
+ context "setting a new node sibling position via :child_index=" do
+ let(:idx) { rand(0..100) }
+ before { allow(parent).to receive(:move_to_child_with_index) }
+
+ context "taxon is not new" do
+ before { allow(taxon).to receive(:new_record?).and_return(false) }
+
+ it "passes the desired index move_to_child_with_index of :parent " do
+ expect(taxon).to receive(:move_to_child_with_index).with(parent, idx)
+
+ taxon.child_index = idx
+ end
+ end
+ end
+
+ end
+ end
+
+ # Regression test for #2620
+ context "creating a child node using first_or_create" do
+ let(:taxonomy) { create(:taxonomy) }
+
+ it "does not error out" do
+ expect { taxonomy.root.children.unscoped.where(:name => "Some name").first_or_create }.not_to raise_error
+ end
+ end
+end
diff --git a/core/spec/models/taxonomy_spec.rb b/core/spec/models/spree/taxonomy_spec.rb
similarity index 91%
rename from core/spec/models/taxonomy_spec.rb
rename to core/spec/models/spree/taxonomy_spec.rb
index f825f49764d..1aae5a0d871 100644
--- a/core/spec/models/taxonomy_spec.rb
+++ b/core/spec/models/spree/taxonomy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Spree::Taxonomy do
+describe Spree::Taxonomy, :type => :model do
context "#destroy" do
before do
@taxonomy = create(:taxonomy)
diff --git a/core/spec/models/spree/tracker_spec.rb b/core/spec/models/spree/tracker_spec.rb
new file mode 100644
index 00000000000..c7790910436
--- /dev/null
+++ b/core/spec/models/spree/tracker_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Spree::Tracker, :type => :model do
+ describe "current" do
+ before(:each) { @tracker = create(:tracker) }
+
+ it "returns the first active tracker for the environment" do
+ expect(Spree::Tracker.current).to eq(@tracker)
+ end
+
+ it "does not return a tracker with a blank analytics_id" do
+ @tracker.update_attribute(:analytics_id, '')
+ expect(Spree::Tracker.current).to be_nil
+ end
+
+ it "does not return an inactive tracker" do
+ @tracker.update_attribute(:active, false)
+ expect(Spree::Tracker.current).to be_nil
+ end
+ end
+end
diff --git a/core/spec/models/spree/user_spec.rb b/core/spec/models/spree/user_spec.rb
new file mode 100644
index 00000000000..36bb9d40036
--- /dev/null
+++ b/core/spec/models/spree/user_spec.rb
@@ -0,0 +1,130 @@
+require 'spec_helper'
+
+describe Spree::LegacyUser, :type => :model do
+ # Regression test for #2844 + #3346
+ context "#last_incomplete_order" do
+ let!(:user) { create(:user) }
+ let!(:order) { create(:order, bill_address: create(:address), ship_address: create(:address)) }
+
+ let!(:order_1) { create(:order, :created_at => 1.day.ago, :user => user, :created_by => user) }
+ let!(:order_2) { create(:order, :user => user, :created_by => user) }
+ let!(:order_3) { create(:order, :user => user, :created_by => create(:user)) }
+
+ it "returns correct order" do
+ expect(user.last_incomplete_spree_order).to eq order_3
+ end
+
+ context "persists order address" do
+ it "copies over order addresses" do
+ expect {
+ user.persist_order_address(order)
+ }.to change { Spree::Address.count }.by(2)
+
+ expect(user.bill_address).to eq order.bill_address
+ expect(user.ship_address).to eq order.ship_address
+ end
+
+ it "doesnt create new addresses if user has already" do
+ user.update_column(:bill_address_id, create(:address))
+ user.update_column(:ship_address_id, create(:address))
+ user.reload
+
+ expect {
+ user.persist_order_address(order)
+ }.not_to change { Spree::Address.count }
+ end
+
+ it "set both bill and ship address id on subject" do
+ user.persist_order_address(order)
+
+ expect(user.bill_address_id).not_to be_blank
+ expect(user.ship_address_id).not_to be_blank
+ end
+ end
+
+ context "payment source" do
+ let(:payment_method) { create(:credit_card_payment_method) }
+ let!(:cc) do
+ create(:credit_card, user_id: user.id, payment_method: payment_method, gateway_customer_profile_id: "2342343")
+ end
+
+ it "has payment sources" do
+ expect(user.payment_sources.first.gateway_customer_profile_id).not_to be_empty
+ end
+
+ it "drops payment source" do
+ user.drop_payment_source cc
+ expect(cc.gateway_customer_profile_id).to be_nil
+ end
+ end
+ end
+end
+
+describe Spree.user_class, :type => :model do
+ context "reporting" do
+ let(:order_value) { BigDecimal.new("80.94") }
+ let(:order_count) { 4 }
+ let(:orders) { Array.new(order_count, double(total: order_value)) }
+
+ before do
+ allow(orders).to receive(:pluck).with(:total).and_return(orders.map(&:total))
+ allow(orders).to receive(:count).and_return(orders.length)
+ end
+
+ def load_orders
+ allow(subject).to receive(:spree_orders).and_return(double(complete: orders))
+ end
+
+ describe "#lifetime_value" do
+ context "with orders" do
+ before { load_orders }
+ it "returns the total of completed orders for the user" do
+ expect(subject.lifetime_value).to eq (order_count * order_value)
+ end
+ end
+ context "without orders" do
+ it "returns 0.00" do
+ expect(subject.lifetime_value).to eq BigDecimal("0.00")
+ end
+ end
+ end
+
+ describe "#display_lifetime_value" do
+ it "returns a Spree::Money version of lifetime_value" do
+ value = BigDecimal("500.05")
+ allow(subject).to receive(:lifetime_value).and_return(value)
+ expect(subject.display_lifetime_value).to eq Spree::Money.new(value)
+ end
+ end
+
+ describe "#order_count" do
+ before { load_orders }
+ it "returns the count of completed orders for the user" do
+ expect(subject.order_count).to eq BigDecimal(order_count)
+ end
+ end
+
+ describe "#average_order_value" do
+ context "with orders" do
+ before { load_orders }
+ it "returns the average completed order price for the user" do
+ expect(subject.average_order_value).to eq order_value
+ end
+ end
+ context "without orders" do
+ it "returns 0.00" do
+ expect(subject.average_order_value).to eq BigDecimal("0.00")
+ end
+ end
+ end
+
+ describe "#display_average_order_value" do
+ before { load_orders }
+ it "returns a Spree::Money version of average_order_value" do
+ value = BigDecimal("500.05")
+ allow(subject).to receive(:average_order_value).and_return(value)
+ expect(subject.display_average_order_value).to eq Spree::Money.new(value)
+ end
+ end
+ end
+end
diff --git a/core/spec/models/spree/validations/db_maximum_length_validator_spec.rb b/core/spec/models/spree/validations/db_maximum_length_validator_spec.rb
new file mode 100644
index 00000000000..8c9f416843e
--- /dev/null
+++ b/core/spec/models/spree/validations/db_maximum_length_validator_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Spree::Validations::DbMaximumLengthValidator, :type => :model do
+ context 'when Spree::Product' do
+ Spree::Product.class_eval do
+ # Slug currently has no validation for maximum length
+ validates_with Spree::Validations::DbMaximumLengthValidator, field: :slug
+ end
+ let(:limit) { 255 } # The default limit of db.string.
+ let(:product) { create :product }
+ let(:slug) { "x" * (limit + 1)}
+
+ before do
+ product.slug = slug
+ end
+
+ subject { product.valid? }
+
+ it 'should maximum validate slug' do
+ subject
+ expect(product.errors[:slug]).to include(I18n.t("errors.messages.too_long", count: limit))
+ end
+ end
+end
diff --git a/core/spec/models/spree/variant/scopes_spec.rb b/core/spec/models/spree/variant/scopes_spec.rb
new file mode 100644
index 00000000000..0f4288ffbfb
--- /dev/null
+++ b/core/spec/models/spree/variant/scopes_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe "Variant scopes", :type => :model do
+ let!(:product) { create(:product) }
+ let!(:variant_1) { create(:variant, :product => product) }
+ let!(:variant_2) { create(:variant, :product => product) }
+
+ it ".descend_by_popularity" do
+ # Requires a product with at least two variants, where one has a higher number of
+ # orders than the other
+ Spree::LineItem.delete_all # FIXME leaky database - too many line_items
+ create(:line_item, :variant => variant_1)
+ expect(Spree::Variant.descend_by_popularity.first).to eq(variant_1)
+ end
+
+ context "finding by option values" do
+ let!(:option_type) { create(:option_type, :name => "bar") }
+ let!(:option_value_1) do
+ option_value = create(:option_value, :name => "foo", :option_type => option_type)
+ variant_1.option_values << option_value
+ option_value
+ end
+
+ let!(:option_value_2) do
+ option_value = create(:option_value, :name => "fizz", :option_type => option_type)
+ variant_1.option_values << option_value
+ option_value
+ end
+
+ let!(:product_variants) { product.variants_including_master }
+
+ it "by objects" do
+ variants = product_variants.has_option(option_type, option_value_1)
+ expect(variants).to include(variant_1)
+ expect(variants).not_to include(variant_2)
+ end
+
+ it "by names" do
+ variants = product_variants.has_option("bar", "foo")
+ expect(variants).to include(variant_1)
+ expect(variants).not_to include(variant_2)
+ end
+
+ it "by ids" do
+ variants = product_variants.has_option(option_type.id, option_value_1.id)
+ expect(variants).to include(variant_1)
+ expect(variants).not_to include(variant_2)
+ end
+
+ it "by mixed conditions" do
+ variants = product_variants.has_option(option_type.id, "foo", option_value_2)
+ end
+ end
+end
diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb
new file mode 100644
index 00000000000..d7e42b5f3b2
--- /dev/null
+++ b/core/spec/models/spree/variant_spec.rb
@@ -0,0 +1,500 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Spree::Variant, :type => :model do
+ let!(:variant) { create(:variant) }
+
+ it_behaves_like 'default_price'
+
+ context 'sorting' do
+ it 'responds to set_list_position' do
+ expect(variant.respond_to?(:set_list_position)).to eq(true)
+ end
+ end
+
+ context "validations" do
+ it "should validate price is greater than 0" do
+ variant.price = -1
+ expect(variant).to be_invalid
+ end
+
+ it "should validate price is 0" do
+ variant.price = 0
+ expect(variant).to be_valid
+ end
+ end
+
+ context "after create" do
+ let!(:product) { create(:product) }
+
+ it "propagate to stock items" do
+ expect_any_instance_of(Spree::StockLocation).to receive(:propagate_variant)
+ product.variants.create(:name => "Foobar")
+ end
+
+ context "stock location has disable propagate all variants" do
+ before { Spree::StockLocation.update_all propagate_all_variants: false }
+
+ it "propagate to stock items" do
+ expect_any_instance_of(Spree::StockLocation).not_to receive(:propagate_variant)
+ product.variants.create(:name => "Foobar")
+ end
+ end
+
+ describe 'mark_master_out_of_stock' do
+ before do
+ product.master.stock_items.first.set_count_on_hand(5)
+ end
+ context 'when product is created without variants but with stock' do
+ it { expect(product.master).to be_in_stock }
+ end
+
+ context 'when a variant is created' do
+ before(:each) do
+ product.variants.create!(:name => 'any-name')
+ end
+
+ it { expect(product.master).to_not be_in_stock }
+ end
+ end
+ end
+
+ context "product has other variants" do
+ describe "option value accessors" do
+ before {
+ @multi_variant = FactoryGirl.create :variant, :product => variant.product
+ variant.product.reload
+ }
+
+ let(:multi_variant) { @multi_variant }
+
+ it "should set option value" do
+ expect(multi_variant.option_value('media_type')).to be_nil
+
+ multi_variant.set_option_value('media_type', 'DVD')
+ expect(multi_variant.option_value('media_type')).to eql 'DVD'
+
+ multi_variant.set_option_value('media_type', 'CD')
+ expect(multi_variant.option_value('media_type')).to eql 'CD'
+ end
+
+ it "should not duplicate associated option values when set multiple times" do
+ multi_variant.set_option_value('media_type', 'CD')
+
+ expect {
+ multi_variant.set_option_value('media_type', 'DVD')
+ }.to_not change(multi_variant.option_values, :count)
+
+ expect {
+ multi_variant.set_option_value('coolness_type', 'awesome')
+ }.to change(multi_variant.option_values, :count).by(1)
+ end
+ end
+
+ context "product has other variants" do
+ describe "option value accessors" do
+ before {
+ @multi_variant = create(:variant, :product => variant.product)
+ variant.product.reload
+ }
+
+ let(:multi_variant) { @multi_variant }
+
+ it "should set option value" do
+ expect(multi_variant.option_value('media_type')).to be_nil
+
+ multi_variant.set_option_value('media_type', 'DVD')
+ expect(multi_variant.option_value('media_type')).to eql 'DVD'
+
+ multi_variant.set_option_value('media_type', 'CD')
+ expect(multi_variant.option_value('media_type')).to eql 'CD'
+ end
+
+ it "should not duplicate associated option values when set multiple times" do
+ multi_variant.set_option_value('media_type', 'CD')
+
+ expect {
+ multi_variant.set_option_value('media_type', 'DVD')
+ }.to_not change(multi_variant.option_values, :count)
+
+ expect {
+ multi_variant.set_option_value('coolness_type', 'awesome')
+ }.to change(multi_variant.option_values, :count).by(1)
+ end
+ end
+ end
+ end
+
+ context "#cost_price=" do
+ it "should use LocalizedNumber.parse" do
+ expect(Spree::LocalizedNumber).to receive(:parse).with('1,599.99')
+ subject.cost_price = '1,599.99'
+ end
+ end
+
+ context "#price=" do
+ it "should use LocalizedNumber.parse" do
+ expect(Spree::LocalizedNumber).to receive(:parse).with('1,599.99')
+ subject.price = '1,599.99'
+ end
+ end
+
+ context "#weight=" do
+ it "should use LocalizedNumber.parse" do
+ expect(Spree::LocalizedNumber).to receive(:parse).with('1,599.99')
+ subject.weight = '1,599.99'
+ end
+ end
+
+ context "#currency" do
+ it "returns the globally configured currency" do
+ expect(variant.currency).to eql "USD"
+ end
+ end
+
+ context "#display_amount" do
+ it "returns a Spree::Money" do
+ variant.price = 21.22
+ expect(variant.display_amount.to_s).to eql "$21.22"
+ end
+ end
+
+ context "#cost_currency" do
+ context "when cost currency is nil" do
+ before { variant.cost_currency = nil }
+ it "populates cost currency with the default value on save" do
+ variant.save!
+ expect(variant.cost_currency).to eql "USD"
+ end
+ end
+ end
+
+ describe '.price_in' do
+ before do
+ variant.prices << create(:price, :variant => variant, :currency => "EUR", :amount => 33.33)
+ end
+ subject { variant.price_in(currency).display_amount }
+
+ context "when currency is not specified" do
+ let(:currency) { nil }
+
+ it "returns 0" do
+ expect(subject.to_s).to eql "$0.00"
+ end
+ end
+
+ context "when currency is EUR" do
+ let(:currency) { 'EUR' }
+
+ it "returns the value in the EUR" do
+ expect(subject.to_s).to eql "€33.33"
+ end
+ end
+
+ context "when currency is USD" do
+ let(:currency) { 'USD' }
+
+ it "returns the value in the USD" do
+ expect(subject.to_s).to eql "$19.99"
+ end
+ end
+ end
+
+ describe '.amount_in' do
+ before do
+ variant.prices << create(:price, :variant => variant, :currency => "EUR", :amount => 33.33)
+ end
+
+ subject { variant.amount_in(currency) }
+
+ context "when currency is not specified" do
+ let(:currency) { nil }
+
+ it "returns nil" do
+ expect(subject).to be_nil
+ end
+ end
+
+ context "when currency is EUR" do
+ let(:currency) { 'EUR' }
+
+ it "returns the value in the EUR" do
+ expect(subject).to eql 33.33
+ end
+ end
+
+ context "when currency is USD" do
+ let(:currency) { 'USD' }
+
+ it "returns the value in the USD" do
+ expect(subject).to eql 19.99
+ end
+ end
+ end
+
+ # Regression test for #2432
+ describe 'options_text' do
+ let!(:variant) { create(:variant, option_values: []) }
+ let!(:master) { create(:master_variant) }
+
+ before do
+ # Order bar than foo
+ variant.option_values << create(:option_value, {name: 'Foo', presentation: 'Foo', option_type: create(:option_type, position: 2, name: 'Foo Type', presentation: 'Foo Type')})
+ variant.option_values << create(:option_value, {name: 'Bar', presentation: 'Bar', option_type: create(:option_type, position: 1, name: 'Bar Type', presentation: 'Bar Type')})
+ end
+
+ it 'should order by bar than foo' do
+ expect(variant.options_text).to eql 'Bar Type: Bar, Foo Type: Foo'
+ end
+
+ end
+
+ describe 'exchange_name' do
+ let!(:variant) { create(:variant, option_values: []) }
+ let!(:master) { create(:master_variant) }
+
+ before do
+ variant.option_values << create(:option_value, {
+ name: 'Foo',
+ presentation: 'Foo',
+ option_type: create(:option_type, position: 2, name: 'Foo Type', presentation: 'Foo Type')
+ })
+ end
+
+ context 'master variant' do
+ it 'should return name' do
+ expect(master.exchange_name).to eql master.name
+ end
+ end
+
+ context 'variant' do
+ it 'should return options text' do
+ expect(variant.exchange_name).to eql 'Foo Type: Foo'
+ end
+ end
+
+ end
+
+ describe 'exchange_name' do
+ let!(:variant) { create(:variant, option_values: []) }
+ let!(:master) { create(:master_variant) }
+
+ before do
+ variant.option_values << create(:option_value, {
+ name: 'Foo',
+ presentation: 'Foo',
+ option_type: create(:option_type, position: 2, name: 'Foo Type', presentation: 'Foo Type')
+ })
+ end
+
+ context 'master variant' do
+ it 'should return name' do
+ expect(master.exchange_name).to eql master.name
+ end
+ end
+
+ context 'variant' do
+ it 'should return options text' do
+ expect(variant.exchange_name).to eql 'Foo Type: Foo'
+ end
+ end
+
+ end
+
+ describe 'descriptive_name' do
+ let!(:variant) { create(:variant, option_values: []) }
+ let!(:master) { create(:master_variant) }
+
+ before do
+ variant.option_values << create(:option_value, {
+ name: 'Foo',
+ presentation: 'Foo',
+ option_type: create(:option_type, position: 2, name: 'Foo Type', presentation: 'Foo Type')
+ })
+ end
+
+ context 'master variant' do
+ it 'should return name with Master identifier' do
+ expect(master.descriptive_name).to eql master.name + ' - Master'
+ end
+ end
+
+ context 'variant' do
+ it 'should return options text with name' do
+ expect(variant.descriptive_name).to eql variant.name + ' - Foo Type: Foo'
+ end
+ end
+
+ end
+
+ # Regression test for #2744
+ describe "set_position" do
+ it "sets variant position after creation" do
+ variant = create(:variant)
+ expect(variant.position).to_not be_nil
+ end
+ end
+
+ describe '#in_stock?' do
+ before do
+ Spree::Config.track_inventory_levels = true
+ end
+
+ context 'when stock_items are not backorderable' do
+ before do
+ allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable: false)
+ end
+
+ context 'when stock_items in stock' do
+ before do
+ variant.stock_items.first.update_column(:count_on_hand, 10)
+ end
+
+ it 'returns true if stock_items in stock' do
+ expect(variant.in_stock?).to be true
+ end
+ end
+
+ context 'when stock_items out of stock' do
+ before do
+ allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable: false)
+ allow_any_instance_of(Spree::StockItem).to receive_messages(count_on_hand: 0)
+ end
+
+ it 'return false if stock_items out of stock' do
+ expect(variant.in_stock?).to be false
+ end
+ end
+ end
+
+ describe "#can_supply?" do
+ it "calls out to quantifier" do
+ expect(Spree::Stock::Quantifier).to receive(:new).and_return(quantifier = double)
+ expect(quantifier).to receive(:can_supply?).with(10)
+ variant.can_supply?(10)
+ end
+ end
+
+ context 'when stock_items are backorderable' do
+ before do
+ allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable: true)
+ end
+
+ context 'when stock_items out of stock' do
+ before do
+ allow_any_instance_of(Spree::StockItem).to receive_messages(count_on_hand: 0)
+ end
+
+ it 'in_stock? returns false' do
+ expect(variant.in_stock?).to be false
+ end
+
+ it 'can_supply? return true' do
+ expect(variant.can_supply?).to be true
+ end
+ end
+ end
+ end
+
+ describe '#is_backorderable' do
+ let(:variant) { build(:variant) }
+ subject { variant.is_backorderable? }
+
+ it 'should invoke Spree::Stock::Quantifier' do
+ expect_any_instance_of(Spree::Stock::Quantifier).to receive(:backorderable?) { true }
+ subject
+ end
+ end
+
+ describe '#total_on_hand' do
+ it 'should be infinite if track_inventory_levels is false' do
+ Spree::Config[:track_inventory_levels] = false
+ expect(build(:variant).total_on_hand).to eql(Float::INFINITY)
+ end
+
+ it 'should match quantifier total_on_hand' do
+ variant = build(:variant)
+ expect(variant.total_on_hand).to eq(Spree::Stock::Quantifier.new(variant).total_on_hand)
+ end
+ end
+
+ describe '#tax_category' do
+ context 'when tax_category is nil' do
+ let(:product) { build(:product) }
+ let(:variant) { build(:variant, product: product, tax_category_id: nil) }
+ it 'returns the parent products tax_category' do
+ expect(variant.tax_category).to eq(product.tax_category)
+ end
+ end
+
+ context 'when tax_category is set' do
+ let(:tax_category) { create(:tax_category) }
+ let(:variant) { build(:variant, tax_category: tax_category) }
+ it 'returns the tax_category set on itself' do
+ expect(variant.tax_category).to eq(tax_category)
+ end
+ end
+ end
+
+ describe "touching" do
+ it "updates a product" do
+ variant.product.update_column(:updated_at, 1.day.ago)
+ variant.touch
+ expect(variant.product.reload.updated_at).to be_within(3.seconds).of(Time.now)
+ end
+
+ it "clears the in_stock cache key" do
+ expect(Rails.cache).to receive(:delete).with(variant.send(:in_stock_cache_key))
+ variant.touch
+ end
+ end
+
+ describe "#should_track_inventory?" do
+
+ it 'should not track inventory when global setting is off' do
+ Spree::Config[:track_inventory_levels] = false
+
+ expect(build(:variant).should_track_inventory?).to eq(false)
+ end
+
+ it 'should not track inventory when variant is turned off' do
+ Spree::Config[:track_inventory_levels] = true
+
+ expect(build(:on_demand_variant).should_track_inventory?).to eq(false)
+ end
+
+ it 'should track inventory when global and variant are on' do
+ Spree::Config[:track_inventory_levels] = true
+
+ expect(build(:variant).should_track_inventory?).to eq(true)
+ end
+ end
+
+ describe "deleted_at scope" do
+ before { variant.destroy && variant.reload }
+ it "should have a price if deleted" do
+ variant.price = 10
+ expect(variant.price).to eq(10)
+ end
+ end
+
+ describe "stock movements" do
+ let!(:movement) { create(:stock_movement, stock_item: variant.stock_items.first) }
+
+ it "builds out collection just fine through stock items" do
+ expect(variant.stock_movements.to_a).not_to be_empty
+ end
+ end
+
+ describe "in_stock scope" do
+ it "returns all in stock variants" do
+ in_stock_variant = create(:variant)
+ out_of_stock_variant = create(:variant)
+
+ in_stock_variant.stock_items.first.update_column(:count_on_hand, 10)
+
+ expect(Spree::Variant.in_stock).to eq [in_stock_variant]
+ end
+ end
+end
diff --git a/core/spec/models/spree/zone_spec.rb b/core/spec/models/spree/zone_spec.rb
new file mode 100644
index 00000000000..f9a76afcc81
--- /dev/null
+++ b/core/spec/models/spree/zone_spec.rb
@@ -0,0 +1,305 @@
+require 'spec_helper'
+
+describe Spree::Zone, :type => :model do
+ context "#match" do
+ let(:country_zone) { create(:zone, name: 'CountryZone') }
+ let(:country) do
+ country = create(:country)
+ # Create at least one state for this country
+ state = create(:state, country: country)
+ country
+ end
+
+ before { country_zone.members.create(zoneable: country) }
+
+ context "when there is only one qualifying zone" do
+ let(:address) { create(:address, country: country, state: country.states.first) }
+
+ it "should return the qualifying zone" do
+ expect(Spree::Zone.match(address)).to eq(country_zone)
+ end
+ end
+
+ context "when there are two qualified zones with same member type" do
+ let(:address) { create(:address, country: country, state: country.states.first) }
+ let(:second_zone) { create(:zone, name: 'SecondZone') }
+
+ before { second_zone.members.create(zoneable: country) }
+
+ context "when both zones have the same number of members" do
+ it "should return the zone that was created first" do
+ expect(Spree::Zone.match(address)).to eq(country_zone)
+ end
+ end
+
+ context "when one of the zones has fewer members" do
+ let(:country2) { create(:country) }
+
+ before { country_zone.members.create(zoneable: country2) }
+
+ it "should return the zone with fewer members" do
+ expect(Spree::Zone.match(address)).to eq(second_zone)
+ end
+ end
+ end
+
+ context "when there are two qualified zones with different member types" do
+ let(:state_zone) { create(:zone, name: 'StateZone') }
+ let(:address) { create(:address, country: country, state: country.states.first) }
+
+ before { state_zone.members.create(zoneable: country.states.first) }
+
+ it "should return the zone with the more specific member type" do
+ expect(Spree::Zone.match(address)).to eq(state_zone)
+ end
+ end
+
+ context "when there are no qualifying zones" do
+ it "should return nil" do
+ expect(Spree::Zone.match(Spree::Address.new)).to be_nil
+ end
+ end
+ end
+
+ context "#country_list" do
+ let(:state) { create(:state) }
+ let(:country) { state.country }
+
+ context "when zone consists of countries" do
+ let(:country_zone) { create(:zone, name: 'CountryZone') }
+
+ before { country_zone.members.create(zoneable: country) }
+
+ it 'should return a list of countries' do
+ expect(country_zone.country_list).to eq([country])
+ end
+ end
+
+ context "when zone consists of states" do
+ let(:state_zone) { create(:zone, name: 'StateZone') }
+
+ before { state_zone.members.create(zoneable: state) }
+
+ it 'should return a list of countries' do
+ expect(state_zone.country_list).to eq([state.country])
+ end
+ end
+ end
+
+ context "#include?" do
+ let(:state) { create(:state) }
+ let(:country) { state.country }
+ let(:address) { create(:address, state: state) }
+
+ context "when zone is country type" do
+ let(:country_zone) { create(:zone, name: 'CountryZone') }
+ before { country_zone.members.create(zoneable: country) }
+
+ it "should be true" do
+ expect(country_zone.include?(address)).to be true
+ end
+ end
+
+ context "when zone is state type" do
+ let(:state_zone) { create(:zone, name: 'StateZone') }
+ before { state_zone.members.create(zoneable: state) }
+
+ it "should be true" do
+ expect(state_zone.include?(address)).to be true
+ end
+ end
+ end
+
+ context ".default_tax" do
+ context "when there is a default tax zone specified" do
+ before { @foo_zone = create(:zone, name: 'whatever', default_tax: true) }
+
+ it "should be the correct zone" do
+ foo_zone = create(:zone, name: 'foo')
+ expect(Spree::Zone.default_tax).to eq(@foo_zone)
+ end
+ end
+
+ context "when there is no default tax zone specified" do
+ it "should be nil" do
+ expect(Spree::Zone.default_tax).to be_nil
+ end
+ end
+ end
+
+ context "#contains?" do
+ let(:country1) { create(:country) }
+ let(:country2) { create(:country) }
+ let(:country3) { create(:country) }
+
+ before do
+ @source = create(:zone, name: 'source', zone_members: [])
+ @target = create(:zone, name: 'target', zone_members: [])
+ end
+
+ context "when the target has no members" do
+ before { @source.members.create(zoneable: country1) }
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+
+ context "when the source has no members" do
+ before { @target.members.create(zoneable: country1) }
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+
+ context "when both zones are the same zone" do
+ before do
+ @source.members.create(zoneable: country1)
+ @target = @source
+ end
+
+ it "should be true" do
+ expect(@source.contains?(@target)).to be true
+ end
+ end
+
+ context "when both zones are of the same type" do
+ before do
+ @source.members.create(zoneable: country1)
+ @source.members.create(zoneable: country2)
+ end
+
+ context "when all members are included in the zone we check against" do
+ before do
+ @target.members.create(zoneable: country1)
+ @target.members.create(zoneable: country2)
+ end
+
+ it "should be true" do
+ expect(@source.contains?(@target)).to be true
+ end
+ end
+
+ context "when some members are included in the zone we check against" do
+ before do
+ @target.members.create(zoneable: country1)
+ @target.members.create(zoneable: country2)
+ @target.members.create(zoneable: create(:country))
+ end
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+
+ context "when none of the members are included in the zone we check against" do
+ before do
+ @target.members.create(zoneable: create(:country))
+ @target.members.create(zoneable: create(:country))
+ end
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+ end
+
+ context "when checking country against state" do
+ before do
+ @source.members.create(zoneable: create(:state))
+ @target.members.create(zoneable: country1)
+ end
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+
+ context "when checking state against country" do
+ before { @source.members.create(zoneable: country1) }
+
+ context "when all states contained in one of the countries we check against" do
+
+ before do
+ state1 = create(:state, country: country1)
+ @target.members.create(zoneable: state1)
+ end
+
+ it "should be true" do
+ expect(@source.contains?(@target)).to be true
+ end
+ end
+
+ context "when some states contained in one of the countries we check against" do
+
+ before do
+ state1 = create(:state, country: country1)
+ @target.members.create(zoneable: state1)
+ @target.members.create(zoneable: create(:state, country: country2))
+ end
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+
+ context "when none of the states contained in any of the countries we check against" do
+
+ before do
+ @target.members.create(zoneable: create(:state, country: country2))
+ @target.members.create(zoneable: create(:state, country: country2))
+ end
+
+ it "should be false" do
+ expect(@source.contains?(@target)).to be false
+ end
+ end
+ end
+
+ end
+
+ context "#save" do
+ context "when default_tax is true" do
+ it "should clear previous default tax zone" do
+ zone1 = create(:zone, name: 'foo', default_tax: true)
+ zone = create(:zone, name: 'bar', default_tax: true)
+ expect(zone1.reload.default_tax).to be false
+ end
+ end
+
+ context "when a zone member country is added to an existing zone consisting of state members" do
+ it "should remove existing state members" do
+ zone = create(:zone, name: 'foo', zone_members: [])
+ state = create(:state)
+ country = create(:country)
+ zone.members.create(zoneable: state)
+ country_member = zone.members.create(zoneable: country)
+ zone.save
+ expect(zone.reload.members).to eq([country_member])
+ end
+ end
+ end
+
+ context "#kind" do
+ context "when the zone consists of country zone members" do
+ before do
+ @zone = create(:zone, name: 'country', zone_members: [])
+ @zone.members.create(zoneable: create(:country))
+ end
+ it "should return the kind of zone member" do
+ expect(@zone.kind).to eq("country")
+ end
+ end
+
+ context "when the zone consists of state zone members" do
+ before do
+ @zone = create(:zone, name: 'state', zone_members: [])
+ @zone.members.create(zoneable: create(:state))
+ end
+ it "should return the kind of zone member" do
+ expect(@zone.kind).to eq("state")
+ end
+ end
+ end
+end
diff --git a/core/spec/models/state_spec.rb b/core/spec/models/state_spec.rb
deleted file mode 100644
index 4c369d14943..00000000000
--- a/core/spec/models/state_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe Spree::State do
- before(:all) do
- Spree::State.destroy_all
- end
-
- it "can find a state by name or abbr" do
- state = create(:state, :name => "California", :abbr => "CA")
- Spree::State.find_all_by_name_or_abbr("California").should include(state)
- Spree::State.find_all_by_name_or_abbr("CA").should include(state)
- end
-
- it "can find all states group by country id" do
- state = create(:state)
- Spree::State.states_group_by_country_id.should == { state.country_id.to_s => [[state.id, state.name]] }
- end
-end
diff --git a/core/spec/models/tax_category_spec.rb b/core/spec/models/tax_category_spec.rb
deleted file mode 100644
index c9b94c1ef70..00000000000
--- a/core/spec/models/tax_category_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'spec_helper'
-
-describe Spree::TaxCategory do
- context '#mark_deleted!' do
- let(:tax_category) { create(:tax_category) }
-
- it "should set the deleted at column to the current time" do
- tax_category.mark_deleted!
- tax_category.deleted_at.should_not be_nil
- end
- end
-
- context 'default tax category' do
- let(:tax_category) { create(:tax_category) }
- let(:new_tax_category) { create(:tax_category) }
-
- before do
- tax_category.update_column(:is_default, true)
- end
-
- it "should undefault the previous default tax category" do
- new_tax_category.update_attributes({:is_default => true})
- new_tax_category.is_default.should be_true
-
- tax_category.reload
- tax_category.is_default.should be_false
- end
-
- it "should undefault the previous default tax category except when updating the existing default tax category" do
- tax_category.update_column(:description, "Updated description")
-
- tax_category.reload
- tax_category.is_default.should be_true
- end
- end
-end
diff --git a/core/spec/models/tax_rate_spec.rb b/core/spec/models/tax_rate_spec.rb
deleted file mode 100644
index 8b8e31cbb90..00000000000
--- a/core/spec/models/tax_rate_spec.rb
+++ /dev/null
@@ -1,309 +0,0 @@
-require 'spec_helper'
-
-describe Spree::TaxRate do
- context "match" do
- let(:order) { create(:order) }
- let(:country) { create(:country) }
- let(:tax_category) { create(:tax_category) }
- let(:calculator) { Spree::Calculator::FlatRate.new }
-
-
- it "should return an empty array when tax_zone is nil" do
- order.stub :tax_zone => nil
- Spree::TaxRate.match(order).should == []
- end
-
- context "when no rate zones match the tax zone" do
- before do
- Spree::TaxRate.create({:amount => 1, :zone => create(:zone, :name => 'other_zone')}, :without_protection => true)
- end
-
- context "when there is no default tax zone" do
- before do
- @zone = create(:zone, :name => "Country Zone", :default_tax => false, :zone_members => [])
- @zone.zone_members.create(:zoneable => country)
- end
-
- it "should return an empty array" do
- order.stub :tax_zone => @zone
- Spree::TaxRate.match(order).should == []
- end
-
- it "should return the rate that matches the rate zone" do
- rate = Spree::TaxRate.create({ :amount => 1, :zone => @zone, :tax_category => tax_category,
- :calculator => calculator }, :without_protection => true)
-
- order.stub :tax_zone => @zone
- Spree::TaxRate.match(order).should == [rate]
- end
-
- it "should return all rates that match the rate zone" do
- rate1 = Spree::TaxRate.create({:amount => 1, :zone => @zone, :tax_category => tax_category,
- :calculator => calculator}, :without_protection => true)
- rate2 = Spree::TaxRate.create({:amount => 2, :zone => @zone, :tax_category => tax_category,
- :calculator => Spree::Calculator::FlatRate.new}, :without_protection => true)
-
- order.stub :tax_zone => @zone
- Spree::TaxRate.match(order).should == [rate1, rate2]
- end
-
- context "when the tax_zone is contained within a rate zone" do
- before do
- sub_zone = create(:zone, :name => "State Zone", :zone_members => [])
- sub_zone.zone_members.create(:zoneable => create(:state, :country => country))
- order.stub :tax_zone => sub_zone
- @rate = Spree::TaxRate.create({:amount => 1, :zone => @zone, :tax_category => tax_category,
- :calculator => calculator}, :without_protection => true)
- end
-
- it "should return the rate zone" do
- Spree::TaxRate.match(order).should == [@rate]
- end
- end
-
- end
-
- context "when there is a default tax zone" do
- before do
- @zone = create(:zone, :name => "Country Zone", :default_tax => true, :zone_members => [])
- @zone.zone_members.create(:zoneable => country)
- end
-
- context "when there order has a different tax zone" do
- before { order.stub :tax_zone => create(:zone, :name => "Other Zone") }
-
- it "should return the rates associated with the default tax zone" do
- rate = Spree::TaxRate.create({:amount => 1, :zone => @zone, :tax_category => tax_category,
- :calculator => calculator}, :without_protection => true)
-
- Spree::TaxRate.match(order).should == [rate]
- end
- end
- end
- end
- end
-
- context "adjust" do
- let(:order) { stub_model(Spree::Order) }
- let(:rate_1) { stub_model(Spree::TaxRate) }
- let(:rate_2) { stub_model(Spree::TaxRate) }
-
- before do
- Spree::TaxRate.stub :match => [rate_1, rate_2]
- end
-
- it "should apply adjustments for two tax rates to the order" do
- rate_1.should_receive(:adjust)
- rate_2.should_receive(:adjust)
- Spree::TaxRate.adjust(order)
- end
- end
-
- context "default" do
- let(:tax_category) { create(:tax_category) }
- let(:country) { create(:country) }
- let(:calculator) { Spree::Calculator::FlatRate.new }
-
- context "when there is no default tax_category" do
- before { tax_category.is_default = false }
-
- it "should return 0" do
- Spree::TaxRate.default.should == 0
- end
- end
-
- context "when there is a default tax_category" do
- before { tax_category.update_column :is_default, true }
-
- context "when the default category has tax rates in the default tax zone" do
- before(:each) do
- Spree::Config[:default_country_id] = country.id
- @zone = create(:zone, :name => "Country Zone", :default_tax => true)
- @zone.zone_members.create(:zoneable => country)
- rate = Spree::TaxRate.create({:amount => 1, :zone => @zone, :tax_category => tax_category, :calculator => calculator}, :without_protection => true)
- end
-
- it "should return the correct tax_rate" do
- Spree::TaxRate.default.to_f.should == 1.0
- end
- end
-
- context "when the default category has no tax rates in the default tax zone" do
- it "should return 0" do
- Spree::TaxRate.default.should == 0
- end
- end
- end
- end
-
- context "#adjust" do
- before do
- @category = Spree::TaxCategory.create :name => "Taxable Foo"
- @category2 = Spree::TaxCategory.create(:name => "Non Taxable")
- @calculator = Spree::Calculator::DefaultTax.new
- @rate = Spree::TaxRate.create({:amount => 0.10, :calculator => @calculator, :tax_category => @category}, :without_protection => true)
- @order = Spree::Order.create!
- @taxable = create(:product, :tax_category => @category)
- @nontaxable = create(:product, :tax_category => @category2)
- end
-
- context "when order has no taxable line items" do
- before { @order.add_variant @nontaxable.master }
-
- it "should not create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 0
- end
-
- it "should not create a price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 0
- end
-
- it "should not create a refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 0
- end
- end
-
- context "when order has one taxable line item" do
- before { @order.add_variant @taxable.master }
-
- context "when price includes tax" do
- before { @rate.included_in_price = true }
-
- context "when zone is contained by default tax zone" do
- before { Spree::Zone.stub_chain :default_tax, :contains? => true }
-
- it "should create one price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 1
- end
-
- it "should not create a tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 0
- end
-
- it "should not create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 0
- end
- end
-
- context "when zone is not contained by default tax zone" do
- before { Spree::Zone.stub_chain :default_tax, :contains? => false }
-
- it "should not create a price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 0
- end
-
- it "should create a tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 1
- end
-
- it "should not create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 0
- end
- end
-
- end
-
- context "when price does not include tax" do
- before { @rate.included_in_price = false }
-
- it "should not create price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 0
- end
-
- it "should not create a tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 0
- end
-
- it "should create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 1
- end
- end
-
- end
-
- context "when order has multiple taxable line items" do
- before do
- @taxable2 = create(:product, :tax_category => @category)
- @order.add_variant @taxable.master
- @order.add_variant @taxable2.master
- end
-
- context "when price includes tax" do
- before { @rate.included_in_price = true }
-
- context "when zone is contained by default tax zone" do
- before { Spree::Zone.stub_chain :default_tax, :contains? => true }
-
- it "should create multiple price adjustments" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 2
- end
-
- it "should not create a tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 0
- end
-
- it "should not create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 0
- end
- end
-
- context "when zone is not contained by default tax zone" do
- before { Spree::Zone.stub_chain :default_tax, :contains? => false }
-
- it "should not create a price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 0
- end
-
- it "should create a single tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 1
- end
-
- it "should not create a tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 0
- end
- end
-
- end
-
- context "when price does not include tax" do
- before { @rate.included_in_price = false }
-
- it "should not create a price adjustment" do
- @rate.adjust(@order)
- @order.price_adjustments.count.should == 0
- end
-
- it "should not create a tax refund" do
- @rate.adjust(@order)
- @order.adjustments.credit.count.should == 0
- end
-
- it "should create a single tax adjustment" do
- @rate.adjust(@order)
- @order.adjustments.tax.charge.count.should == 1
- end
- end
-
- end
-
- end
-
-end
diff --git a/core/spec/models/taxon_spec.rb b/core/spec/models/taxon_spec.rb
deleted file mode 100644
index 9e79a5526a5..00000000000
--- a/core/spec/models/taxon_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# coding: UTF-8
-
-require 'spec_helper'
-
-describe Spree::Taxon do
- let(:taxon) { Spree::Taxon.new(:name => "Ruby on Rails") }
-
- context "set_permalink" do
-
- it "should set permalink correctly when no parent present" do
- taxon.set_permalink
- taxon.permalink.should == "ruby-on-rails"
- end
-
- it "should support Chinese characters" do
- taxon.name = "你好"
- taxon.set_permalink
- taxon.permalink.should == 'ni-hao'
- end
-
- context "with parent taxon" do
- before do
- taxon.stub(:parent_id => 123)
- Spree::Taxon.should_receive(:find).with(123).and_return(mock_model(Spree::Taxon, :permalink => "brands"))
- end
-
- it "should set permalink correctly when taxon has parent" do
- taxon.set_permalink
- taxon.permalink.should == "brands/ruby-on-rails"
- end
-
- it "should set permalink correctly with existing permalink present" do
- taxon.permalink = "b/rubyonrails"
- taxon.set_permalink
- taxon.permalink.should == "brands/rubyonrails"
- end
-
- it "should support Chinese characters" do
- taxon.name = "我"
- taxon.set_permalink
- taxon.permalink.should == "brands/wo"
- end
-
- end
-
- end
-
-end
diff --git a/core/spec/models/tracker_spec.rb b/core/spec/models/tracker_spec.rb
deleted file mode 100644
index b4b97ad1980..00000000000
--- a/core/spec/models/tracker_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Tracker do
- describe "current" do
- before(:each) { @tracker = create(:tracker) }
-
- it "returns the first active tracker for the environment" do
- Spree::Tracker.current.should == @tracker
- end
-
- it "does not return a tracker with a blank analytics_id" do
- @tracker.update_attribute(:analytics_id, '')
- Spree::Tracker.current.should == nil
- end
-
- it "does not return an inactive tracker" do
- @tracker.update_attribute(:active, false)
- Spree::Tracker.current.should == nil
- end
- end
-end
diff --git a/core/spec/models/variant/scopes_spec.rb b/core/spec/models/variant/scopes_spec.rb
deleted file mode 100644
index 9ffdd1c92cf..00000000000
--- a/core/spec/models/variant/scopes_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'spec_helper'
-
-describe "Variant scopes" do
- let!(:product) { create(:product) }
- let!(:variant_1) { create(:variant, :product => product) }
- let!(:variant_2) { create(:variant, :product => product) }
-
- it ".descend_by_popularity" do
- # Requires a product with at least two variants, where one has a higher number of orders than the other
- create(:line_item, :variant => variant_1)
- Spree::Variant.descend_by_popularity.first.should == variant_1
- end
-
- context "finding by option values" do
- let!(:option_type) { create(:option_type, :name => "bar") }
- let!(:option_value_1) do
- option_value = create(:option_value, :name => "foo", :option_type => option_type)
- variant_1.option_values << option_value
- option_value
- end
-
- let!(:option_value_2) do
- option_value = create(:option_value, :name => "fizz", :option_type => option_type)
- variant_1.option_values << option_value
- option_value
- end
-
- let!(:product_variants) { product.variants_including_master }
-
- it "by objects" do
- variants = product_variants.has_option(option_type, option_value_1)
- variants.should include(variant_1)
- variants.should_not include(variant_2)
- end
-
- it "by names" do
- variants = product_variants.has_option("bar", "foo")
- variants.should include(variant_1)
- variants.should_not include(variant_2)
- end
-
- it "by ids" do
- variants = product_variants.has_option(option_type.id, option_value_1.id)
- variants.should include(variant_1)
- variants.should_not include(variant_2)
- end
-
- it "by mixed conditions" do
- variants = product_variants.has_option(option_type.id, "foo", option_value_2)
- end
- end
-end
diff --git a/core/spec/models/variant_spec.rb b/core/spec/models/variant_spec.rb
deleted file mode 100644
index c59b2a7fd12..00000000000
--- a/core/spec/models/variant_spec.rb
+++ /dev/null
@@ -1,361 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-
-describe Spree::Variant do
- let!(:variant) { create(:variant, :count_on_hand => 95) }
-
- before(:each) do
- reset_spree_preferences
- end
-
- context "validations" do
- it "should validate price is greater than 0" do
- variant.price = -1
- variant.should be_invalid
- end
-
- it "should validate price is 0" do
- variant.price = 0
- variant.should be_valid
- end
- end
-
- # Regression test for #1778
- it "recalculates product's count_on_hand when saved" do
- Spree::Config[:track_inventory_levels] = true
- variant.stub :is_master? => true
- variant.product.should_receive(:on_hand).and_return(3)
- variant.product.should_receive(:update_column).with(:count_on_hand, 3)
- variant.run_callbacks(:save)
- end
-
- it "lock_version should prevent stale updates" do
- copy = Spree::Variant.find(variant.id)
-
- copy.count_on_hand = 200
- copy.save!
-
- variant.count_on_hand = 100
- expect { variant.save }.to raise_error ActiveRecord::StaleObjectError
-
- variant.reload.count_on_hand.should == 200
- variant.count_on_hand = 100
- variant.save
-
- variant.reload.count_on_hand.should == 100
- end
-
- context "on_hand=" do
- before { variant.stub(:inventory_units => mock('inventory-units')) }
-
- context "when :track_inventory_levels is true" do
- before { Spree::Config.set :track_inventory_levels => true }
-
- context "and count is increased" do
- before { variant.inventory_units.stub(:with_state).and_return([]) }
- let(:inventory_unit) { mock_model(Spree::InventoryUnit, :state => "backordered") }
-
- it "should change count_on_hand to given value" do
- variant.on_hand = 100
- variant.count_on_hand.should == 100
- end
-
- it "should check for backordered units" do
- variant.save!
- variant.inventory_units.should_receive(:with_state).with("backordered")
- variant.on_hand = 100
- variant.save!
- end
-
- it "should fill 1 backorder when count_on_hand is zero" do
- variant.count_on_hand = 0
- variant.save!
- variant.inventory_units.stub(:with_state).and_return([inventory_unit])
- inventory_unit.should_receive(:fill_backorder)
- variant.on_hand = 100
- variant.save!
- variant.count_on_hand.should == 99
- end
-
- it "should fill multiple backorders when count_on_hand is negative" do
- variant.count_on_hand = -5
- variant.save!
- variant.inventory_units.stub(:with_state).and_return(Array.new(5, inventory_unit))
- inventory_unit.should_receive(:fill_backorder).exactly(5).times
- variant.on_hand = 100
- variant.save!
- variant.count_on_hand.should == 95
- end
-
- it "should keep count_on_hand negative when count is not enough to fill backorders" do
- variant.count_on_hand = -10
- variant.save!
- variant.inventory_units.stub(:with_state).and_return(Array.new(10, inventory_unit))
- inventory_unit.should_receive(:fill_backorder).exactly(5).times
- variant.on_hand = 5
- variant.save!
- variant.count_on_hand.should == -5
- end
-
- end
-
- context "and count is negative" do
- before { variant.inventory_units.stub(:with_state).and_return([]) }
-
- it "should change count_on_hand to given value" do
- variant.on_hand = 10
- variant.count_on_hand.should == 10
- end
-
- it "should not check for backordered units" do
- variant.inventory_units.should_not_receive(:with_state)
- variant.on_hand = 10
- end
-
- end
-
- end
-
- context "when :track_inventory_levels is false" do
- before { Spree::Config.set :track_inventory_levels => false }
-
- it "should raise an exception" do
- lambda { variant.on_hand = 100 }.should raise_error
- end
-
- end
-
- end
-
- context "on_hand" do
- context "when :track_inventory_levels is true" do
- before { Spree::Config.set :track_inventory_levels => true }
-
- it "should return count_on_hand" do
- variant.on_hand.should == variant.count_on_hand
- end
- end
-
- context "when :track_inventory_levels is false" do
- before { Spree::Config.set :track_inventory_levels => false }
-
- it "should return nil" do
- variant.on_hand.should eql(1.0/0) # Infinity
- end
-
- end
-
- end
-
- context "in_stock?" do
- context "when :track_inventory_levels is true" do
- before { Spree::Config.set :track_inventory_levels => true }
-
- it "should be true when count_on_hand is positive" do
- variant.in_stock?.should be_true
- end
-
- it "should be false when count_on_hand is zero" do
- variant.stub(:count_on_hand => 0)
- variant.in_stock?.should be_false
- end
-
- it "should be false when count_on_hand is negative" do
- variant.stub(:count_on_hand => -10)
- variant.in_stock?.should be_false
- end
- end
-
- context "when :track_inventory_levels is false" do
- before { Spree::Config.set :track_inventory_levels => false }
-
- it "should be true" do
- variant.in_stock?.should be_true
- end
-
- end
-
- context "product has other variants" do
- describe "option value accessors" do
- before {
- @multi_variant = FactoryGirl.create :variant, :product => variant.product
- variant.product.reload
- }
-
- let(:multi_variant) { @multi_variant }
-
- it "should set option value" do
- multi_variant.option_value('media_type').should be_nil
-
- multi_variant.set_option_value('media_type', 'DVD')
- multi_variant.option_value('media_type').should == 'DVD'
-
- multi_variant.set_option_value('media_type', 'CD')
- multi_variant.option_value('media_type').should == 'CD'
- end
-
- it "should not duplicate associated option values when set multiple times" do
- multi_variant.set_option_value('media_type', 'CD')
-
- expect {
- multi_variant.set_option_value('media_type', 'DVD')
- }.to_not change(multi_variant.option_values, :count)
-
- expect {
- multi_variant.set_option_value('coolness_type', 'awesome')
- }.to change(multi_variant.option_values, :count).by(1)
- end
- end
- end
-
- end
-
- context "price parsing" do
- before(:each) do
- I18n.locale = I18n.default_locale
- I18n.backend.store_translations(:de, { :number => { :currency => { :format => { :delimiter => '.', :separator => ',' } } } })
- end
-
- after do
- I18n.locale = I18n.default_locale
- end
-
- context "price=" do
- context "with decimal point" do
- it "captures the proper amount for a formatted price" do
- variant.price = '1,599.99'
- variant.price.should == 1599.99
- end
- end
-
- context "with decimal comma" do
- it "captures the proper amount for a formatted price" do
- I18n.locale = :de
- variant.price = '1.599,99'
- variant.price.should == 1599.99
- end
- end
-
- context "with a numeric price" do
- it "uses the price as is" do
- I18n.locale = :de
- variant.price = 1599.99
- variant.price.should == 1599.99
- end
- end
- end
-
- context "cost_price=" do
- context "with decimal point" do
- it "captures the proper amount for a formatted price" do
- variant.cost_price = '1,599.99'
- variant.cost_price.should == 1599.99
- end
- end
-
- context "with decimal comma" do
- it "captures the proper amount for a formatted price" do
- I18n.locale = :de
- variant.cost_price = '1.599,99'
- variant.cost_price.should == 1599.99
- end
- end
-
- context "with a numeric price" do
- it "uses the price as is" do
- I18n.locale = :de
- variant.cost_price = 1599.99
- variant.cost_price.should == 1599.99
- end
- end
- end
- end
-
- context "#currency" do
- it "returns the globally configured currency" do
- variant.currency.should == "USD"
- end
- end
-
- context "#display_amount" do
- it "retuns a Spree::Money" do
- variant.price = 21.22
- variant.display_amount.should == "$21.22"
- end
- end
-
- context "#cost_currency" do
- context "when cost currency is nil" do
- before { variant.cost_currency = nil }
- it "populates cost currency with the default value on save" do
- variant.save!
- variant.cost_currency.should == "USD"
- end
- end
- end
-
- describe '.price_in' do
- before do
- variant.prices << create(:price, :variant => variant, :currency => "EUR", :amount => 33.33)
- end
-
- subject { variant.price_in(currency).display_amount }
-
- context "when currency is not specified" do
- let(:currency) { nil }
-
- it "returns nil" do
- subject.should be_nil
- end
- end
-
- context "when currency is EUR" do
- let(:currency) { 'EUR' }
-
- it "returns the value in the EUR" do
- subject.should == "€33.33"
- end
- end
-
- context "when currency is USD" do
- let(:currency) { 'USD' }
-
- it "returns the value in the USD" do
- subject.should == "$19.99"
- end
- end
- end
-
- describe '.amount_in' do
- before do
- variant.prices << create(:price, :variant => variant, :currency => "EUR", :amount => 33.33)
- end
-
- subject { variant.amount_in(currency) }
-
- context "when currency is not specified" do
- let(:currency) { nil }
-
- it "returns nil" do
- subject.should be_nil
- end
- end
-
- context "when currency is EUR" do
- let(:currency) { 'EUR' }
-
- it "returns the value in the EUR" do
- subject.should == 33.33
- end
- end
-
- context "when currency is USD" do
- let(:currency) { 'USD' }
-
- it "returns the value in the USD" do
- subject.should == 19.99
- end
- end
- end
-end
diff --git a/core/spec/models/zone_spec.rb b/core/spec/models/zone_spec.rb
deleted file mode 100644
index 098f9402c09..00000000000
--- a/core/spec/models/zone_spec.rb
+++ /dev/null
@@ -1,305 +0,0 @@
-require 'spec_helper'
-
-describe Spree::Zone do
- context "#match" do
- let(:country_zone) { create(:zone, :name => "CountryZone") }
- let(:country) do
- country = create(:country)
- # Create at least one state for this country
- state = create(:state, :country => country)
- country
- end
-
- before { country_zone.members.create(:zoneable => country) }
-
- context "when there is only one qualifying zone" do
- let(:address) { create(:address, :country => country, :state => country.states.first) }
-
- it "should return the qualifying zone" do
- Spree::Zone.match(address).should == country_zone
- end
- end
-
- context "when there are two qualified zones with same member type" do
- let(:address) { create(:address, :country => country, :state => country.states.first) }
- let(:second_zone) { create(:zone, :name => "SecondZone") }
-
- before { second_zone.members.create(:zoneable => country) }
-
- context "when both zones have the same number of members" do
- it "should return the zone that was created first" do
- Spree::Zone.match(address).should == country_zone
- end
- end
-
- context "when one of the zones has fewer members" do
- let(:country2) { create(:country) }
-
- before { country_zone.members.create(:zoneable => country2) }
-
- it "should return the zone with fewer members" do
- Spree::Zone.match(address).should == second_zone
- end
- end
- end
-
- context "when there are two qualified zones with different member types" do
- let(:state_zone) { create(:zone, :name => "StateZone") }
- let(:address) { create(:address, :country => country, :state => country.states.first) }
-
- before { state_zone.members.create(:zoneable => country.states.first) }
-
- it "should return the zone with the more specific member type" do
- Spree::Zone.match(address).should == state_zone
- end
- end
-
- context "when there are no qualifying zones" do
- it "should return nil" do
- Spree::Zone.match(Spree::Address.new).should be_nil
- end
- end
- end
-
- context "#country_list" do
- let(:state) { create(:state) }
- let(:country) { state.country }
-
- context "when zone consists of countries" do
- let(:country_zone) { create(:zone, :name => "CountryZone") }
-
- before { country_zone.members.create(:zoneable => country) }
-
- it 'should return a list of countries' do
- country_zone.country_list.should == [country]
- end
- end
-
- context "when zone consists of states" do
- let(:state_zone) { create(:zone, :name => "StateZone") }
-
- before { state_zone.members.create(:zoneable => state) }
-
- it 'should return a list of countries' do
- state_zone.country_list.should == [state.country]
- end
- end
- end
-
- context "#include?" do
- let(:state) { create(:state) }
- let(:country) { state.country }
- let(:address) { create(:address, :state => state) }
-
- context "when zone is country type" do
- let(:country_zone) { create(:zone, :name => "CountryZone") }
- before { country_zone.members.create(:zoneable => country) }
-
- it "should be true" do
- country_zone.include?(address).should be_true
- end
- end
-
- context "when zone is state type" do
- let(:state_zone) { create(:zone, :name => "StateZone") }
- before { state_zone.members.create(:zoneable => state) }
-
- it "should be true" do
- state_zone.include?(address).should be_true
- end
- end
- end
-
- context ".default_tax" do
- context "when there is a default tax zone specified" do
- before { @foo_zone = create(:zone, :name => "whatever", :default_tax => true) }
-
- it "should be the correct zone" do
- foo_zone = create(:zone, :name => "foo")
- Spree::Zone.default_tax.should == @foo_zone
- end
- end
-
- context "when there is no default tax zone specified" do
- it "should be nil" do
- Spree::Zone.default_tax.should be_nil
- end
- end
- end
-
- context "#contains?" do
- let(:country1) { create(:country) }
- let(:country2) { create(:country) }
- let(:country3) { create(:country) }
-
- before do
- @source = create(:zone, :name => "source", :zone_members => [])
- @target = create(:zone, :name => "target", :zone_members => [])
- end
-
- context "when the target has no members" do
- before { @source.members.create(:zoneable => country1) }
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
-
- context "when the source has no members" do
- before { @target.members.create(:zoneable => country1) }
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
-
- context "when both zones are the same zone" do
- before do
- @source.members.create(:zoneable => country1)
- @target = @source
- end
-
- it "should be true" do
- @source.contains?(@target).should be_true
- end
- end
-
- context "when both zones are of the same type" do
- before do
- @source.members.create(:zoneable => country1)
- @source.members.create(:zoneable => country2)
- end
-
- context "when all members are included in the zone we check against" do
- before do
- @target.members.create(:zoneable => country1)
- @target.members.create(:zoneable => country2)
- end
-
- it "should be true" do
- @source.contains?(@target).should be_true
- end
- end
-
- context "when some members are included in the zone we check against" do
- before do
- @target.members.create(:zoneable => country1)
- @target.members.create(:zoneable => country2)
- @target.members.create(:zoneable => create(:country))
- end
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
-
- context "when none of the members are included in the zone we check against" do
- before do
- @target.members.create(:zoneable => create(:country))
- @target.members.create(:zoneable => create(:country))
- end
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
- end
-
- context "when checking country against state" do
- before do
- @source.members.create(:zoneable => create(:state))
- @target.members.create(:zoneable => country1)
- end
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
-
- context "when checking state against country" do
- before { @source.members.create(:zoneable => country1) }
-
- context "when all states contained in one of the countries we check against" do
-
- before do
- state1 = create(:state, :country => country1)
- @target.members.create(:zoneable => state1)
- end
-
- it "should be true" do
- @source.contains?(@target).should be_true
- end
- end
-
- context "when some states contained in one of the countries we check against" do
-
- before do
- state1 = create(:state, :country => country1)
- @target.members.create(:zoneable => state1)
- @target.members.create(:zoneable => create(:state, :country => country2))
- end
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
-
- context "when none of the states contained in any of the countries we check against" do
-
- before do
- @target.members.create(:zoneable => create(:state, :country => country2))
- @target.members.create(:zoneable => create(:state, :country => country2))
- end
-
- it "should be false" do
- @source.contains?(@target).should be_false
- end
- end
- end
-
- end
-
- context "#save" do
- context "when default_tax is true" do
- it "should clear previous default tax zone" do
- zone1 = create(:zone, :name => "foo", :default_tax => true)
- zone = create(:zone, :name => "bar", :default_tax => true)
- zone1.reload.default_tax.should == false
- end
- end
-
- context "when a zone member country is added to an existing zone consisting of state members" do
- it "should remove existing state members" do
- zone = create(:zone, :name => "foo", :zone_members => [])
- state = create(:state)
- country = create(:country)
- zone.members.create(:zoneable => state)
- country_member = zone.members.create(:zoneable => country)
- zone.save
- zone.reload.members.should == [country_member]
- end
- end
- end
-
- context "#kind" do
- context "when the zone consists of country zone members" do
- before do
- @zone = create(:zone, :name => "country", :zone_members => [])
- @zone.members.create(:zoneable => create(:country))
- end
- it "should return the kind of zone member" do
- @zone.kind.should == "country"
- end
- end
-
- context "when the zone consists of state zone members" do
- before do
- @zone = create(:zone, :name => "state", :zone_members => [])
- @zone.members.create(:zoneable => create(:state))
- end
- it "should return the kind of zone member" do
- @zone.kind.should == "state"
- end
- end
- end
-end
diff --git a/core/spec/requests/address_spec.rb b/core/spec/requests/address_spec.rb
deleted file mode 100644
index 7946c665d0a..00000000000
--- a/core/spec/requests/address_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe "Address" do
- before do
- @product = create(:product, :name => "RoR Mug", :on_hand => 1)
- @product.save
-
- @order = create(:order_with_totals, :state => 'cart')
- @order.stub(:available_payment_methods => [create(:bogus_payment_method, :environment => 'test') ])
-
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
- Spree::Order.last.update_column(:email, "funk@groove.com")
-
- address = "order_bill_address_attributes"
- @country_css = "#{address}_country_id"
- @state_select_css = "##{address}_state_id"
- @state_name_css = "##{address}_state_name"
- end
-
- it "shows the state collection selection for a country having states", :js => true do
- canada = create(:country, :name => "Canada", :states_required => true)
- Factory(:state, :name => "Ontario", :country => canada)
-
- click_button "Checkout"
- select canada.name, :from => @country_css
- page.find(@state_select_css).should be_visible
- page.find(@state_name_css).should_not be_visible
- end
-
- it "shows the state input field for a country with states required but for which states are not defined", :js => true do
- italy = create(:country, :name => "Italy", :states_required => true)
- click_button "Checkout"
-
- select italy.name, :from => @country_css
- page.find(@state_select_css).should_not be_visible
- page.find(@state_name_css).should be_visible
- page.should_not have_selector("input#{@state_name_css}[disabled]")
- end
-
- it "shows a disabled state input field for a country where states are not required", :js => true do
- france = create(:country, :name => "France", :states_required => false)
- click_button "Checkout"
-
- select france.name, :from => @country_css
- page.find(@state_select_css).should_not be_visible
- page.find(@state_name_css).should_not be_visible
- end
-
- it "should clear the state name when selecting a country without states required", :js =>true do
- italy = create(:country, :name => "Italy", :states_required => true)
- france = create(:country, :name => "France", :states_required => false)
-
- click_button "Checkout"
- select italy.name, :from => @country_css
- page.find(@state_name_css).set("Toscana")
-
- select france.name, :from => @country_css
- page.find(@state_name_css).should have_content('')
- end
-end
\ No newline at end of file
diff --git a/core/spec/requests/admin/configuration/analytics_tracker_spec.rb b/core/spec/requests/admin/configuration/analytics_tracker_spec.rb
deleted file mode 100644
index 2e07f69319d..00000000000
--- a/core/spec/requests/admin/configuration/analytics_tracker_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe "Analytics Tracker" do
- stub_authorization!
-
- context "index" do
- before(:each) do
- 2.times { create(:tracker, :environment => "test") }
- visit spree.admin_path
- click_link "Configuration"
- click_link "Analytics Tracker"
- end
-
- it "should have the right content" do
- page.should have_content("Analytics Trackers")
- end
-
- it "should have the right tabular values displayed" do
- within_row(1) do
- column_text(1).should == "A100"
- column_text(2).should == "Test"
- column_text(3).should == "Yes"
- end
-
- within_row(2) do
- column_text(1).should == "A100"
- column_text(2).should == "Test"
- column_text(3).should == "Yes"
- end
- end
- end
-
- context "create" do
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- click_link "Analytics Tracker"
- end
-
- it "should be able to create a new analytics tracker" do
- click_link "admin_new_tracker_link"
- fill_in "tracker_analytics_id", :with => "A100"
- select "Test", :from => "tracker-env"
- click_button "Create"
-
- page.should have_content("successfully created!")
- within_row(1) do
- column_text(1).should == "A100"
- column_text(2).should == "Test"
- column_text(3).should == "Yes"
- end
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/general_settings_spec.rb b/core/spec/requests/admin/configuration/general_settings_spec.rb
deleted file mode 100644
index 2e47963b450..00000000000
--- a/core/spec/requests/admin/configuration/general_settings_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-describe "General Settings" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- click_link "General Settings"
- end
-
- context "visiting general settings (admin)" do
- it "should have the right content" do
- page.should have_content("General Settings")
- find("#site_name").value.should == "Spree Demo Site"
- find("#site_url").value.should == "demo.spreecommerce.com"
- end
- end
-
- context "editing general settings (admin)" do
- it "should be able to update the site name" do
- fill_in "site_name", :with => "Spree Demo Site99"
- click_button "Update"
-
- assert_successful_update_message(:general_settings)
- find("#site_name").value.should == "Spree Demo Site99"
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/inventory_settings_spec.rb b/core/spec/requests/admin/configuration/inventory_settings_spec.rb
deleted file mode 100644
index 06335630971..00000000000
--- a/core/spec/requests/admin/configuration/inventory_settings_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe "Inventory Settings" do
- stub_authorization!
-
- context "changing settings" do
- before(:each) do
- reset_spree_preferences do |config|
- config.allow_backorders = true
- end
-
- visit spree.admin_path
- click_link "Configuration"
- click_link "Inventory Settings"
- end
-
- it "should have the right content" do
- page.should have_content("Inventory Settings")
- page.should have_content("Show out-of-stock products")
- page.should have_content("Allow Backorders")
- end
-
- it "should be able to toggle displaying zero stock products" do
- uncheck "preferences_show_zero_stock_products"
- click_button "Update"
- assert_successful_update_message(:inventory_settings)
- assert_preference_unset(:show_zero_stock_products)
- end
-
- it "should be able to toggle allowing backorders" do
- uncheck "preferences_allow_backorders"
- click_button "Update"
- assert_successful_update_message(:inventory_settings)
- assert_preference_unset(:allow_backorders)
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/mail_methods_spec.rb b/core/spec/requests/admin/configuration/mail_methods_spec.rb
deleted file mode 100644
index 11f3b9d7b4e..00000000000
--- a/core/spec/requests/admin/configuration/mail_methods_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'spec_helper'
-
-describe "Mail Methods" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "index" do
- before(:each) do
- create(:mail_method)
- click_link "Mail Methods"
- end
-
- it "should be able to display information about existing mail methods" do
- within_row(1) do
- column_text(1).should == "Test"
- column_text(2).should == "Yes"
- end
- end
- end
-
- context "create" do
- it "should be able to create a new mail method" do
- click_link "Mail Methods"
- click_link "admin_new_mail_method_link"
- page.should have_content("New Mail Method")
- click_button "Create"
- page.should have_content("successfully created!")
- end
- end
-
- context "edit" do
- let!(:mail_method) { create(:mail_method, :preferred_smtp_password => "haxme") }
-
- before do
- click_link "Mail Methods"
- end
-
- it "should be able to edit an existing mail method" do
- within_row(1) { click_icon :edit }
-
- fill_in "mail_method_preferred_mail_bcc", :with => "spree@example.com99"
- click_button "Update"
- page.should have_content("successfully updated!")
-
- within_row(1) { click_icon :edit }
- find_field("mail_method_preferred_mail_bcc").value.should == "spree@example.com99"
- end
-
- # Regression test for #2094
- it "does not clear password if not provided" do
- mail_method.preferred_smtp_password.should == "haxme"
- within_row(1) { click_icon :edit }
- click_button "Update"
- page.should have_content("successfully updated!")
-
- mail_method.reload
- mail_method.preferred_smtp_password.should_not be_blank
- end
-
- end
-end
diff --git a/core/spec/requests/admin/configuration/payment_methods_spec.rb b/core/spec/requests/admin/configuration/payment_methods_spec.rb
deleted file mode 100644
index 748eabf4481..00000000000
--- a/core/spec/requests/admin/configuration/payment_methods_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'spec_helper'
-
-describe "Payment Methods" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "admin visiting payment methods listing page" do
- it "should display existing payment methods" do
- create(:payment_method)
- click_link "Payment Methods"
-
- within("table#listing_payment_methods") do
- find('th:nth-child(1)').text.should == "Name"
- find('th:nth-child(2)').text.should == "Provider"
- find('th:nth-child(3)').text.should == "Environment"
- find('th:nth-child(4)').text.should == "Active"
- end
-
- within('table#listing_payment_methods') do
- page.should have_content("Spree::PaymentMethod::Check")
- end
- end
- end
-
- context "admin creating a new payment method" do
- it "should be able to create a new payment method" do
- click_link "Payment Methods"
- click_link "admin_new_payment_methods_link"
- page.should have_content("New Payment Method")
- fill_in "payment_method_name", :with => "check90"
- fill_in "payment_method_description", :with => "check90 desc"
- select "PaymentMethod::Check", :from => "gtwy-type"
- click_button "Create"
- page.should have_content("successfully created!")
- end
- end
-
- context "admin editing a payment method" do
- before(:each) do
- create(:payment_method)
- click_link "Payment Methods"
- within("table#listing_payment_methods") do
- click_icon(:edit)
- end
- end
-
- it "should be able to edit an existing payment method" do
- fill_in "payment_method_name", :with => "Payment 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- find_field("payment_method_name").value.should == "Payment 99"
- end
-
- it "should display validation errors" do
- fill_in "payment_method_name", :with => ""
- click_button "Update"
- page.should have_content("Name can't be blank")
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/shipping_methods_spec.rb b/core/spec/requests/admin/configuration/shipping_methods_spec.rb
deleted file mode 100644
index 5017783654a..00000000000
--- a/core/spec/requests/admin/configuration/shipping_methods_spec.rb
+++ /dev/null
@@ -1,239 +0,0 @@
-require 'spec_helper'
-require 'active_record/fixtures'
-
-describe "Shipping Methods" do
- stub_authorization!
-
- let!(:address) { create(:address, :state => create(:state)) }
- let!(:zone) { Spree::Zone.find_by_name("GlobalZone") || create(:global_zone) }
- let!(:shipping_method) { create(:shipping_method, :zone => zone) }
-
- before(:each) do
- # HACK: To work around no email prompting on check out
- Spree::Order.any_instance.stub(:require_email => false)
- create(:payment_method, :environment => 'test')
- @product = create(:product, :name => "Mug")
-
- visit spree.admin_path
- click_link "Configuration"
- end
-
-
- context "show" do
- it "should display exisiting shipping methods" do
- click_link "Shipping Methods"
-
- within_row(1) do
- column_text(1).should == shipping_method.name
- column_text(2).should == zone.name
- column_text(3).should == "Flat Rate (per order)"
- column_text(4).should == "Both"
- end
- end
- end
-
- context "create" do
- it "should be able to create a new shipping method" do
- click_link "Shipping Methods"
- click_link "admin_new_shipping_method_link"
- page.should have_content("New Shipping Method")
- fill_in "shipping_method_name", :with => "bullock cart"
- click_button "Create"
- page.should have_content("successfully created!")
- page.should have_content("Editing Shipping Method")
- end
- end
-
- # Regression test for #1331
- context "update" do
- it "can change the calculator", :js => true do
- click_link "Shipping Methods"
- within("#listing_shipping_methods") do
- click_icon :edit
- end
-
- click_button "Update"
- page.should_not have_content("Shipping method is not found")
- end
- end
-
- context "availability", :js => true do
- before(:each) do
- @shipping_category = create(:shipping_category, :name => "Default")
- click_link "Shipping Methods"
- click_link "admin_new_shipping_method_link"
- end
-
- context "when rule is no products match" do
- context "when match rules are satisfied" do
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select shipping_method.zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_none"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should have_content("Standard")
- end
- end
-
- context "when match rules aren't satisfied" do
- before { @product.shipping_category = @shipping_category; @product.save }
-
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select shipping_method.zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_none"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should_not have_content("Standard")
- end
- end
- end
-
- context "when rule is all products match" do
- context "when match rules are satisfied" do
- before { @product.shipping_category = @shipping_category; @product.save }
-
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select shipping_method.zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_all"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should have_content("Standard")
- end
- end
-
- context "when match rules aren't satisfied" do
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select shipping_method.zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_all"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should_not have_content("Standard")
- end
- end
- end
-
- context "when rule is at least one products match" do
- before(:each) do
- create(:product, :name => "Shirt")
- end
-
- context "when match rules are satisfied" do
- before { @product.shipping_category = @shipping_category; @product.save }
-
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_one"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_link "Home"
- click_link "Shirt"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should have_content("Standard")
- end
- end
-
- context "when match rules aren't satisfied" do
- it "shows the right shipping method on checkout" do
- fill_in "shipping_method_name", :with => "Standard"
- select zone.name, :from => "shipping_method_zone_id"
- select @shipping_category.name, :from => "shipping_method_shipping_category_id"
- check "shipping_method_match_one"
- click_button "Create"
-
- visit spree.root_path
- click_link "Mug"
- click_button "Add To Cart"
- click_link "Home"
- click_link "Shirt"
- click_button "Add To Cart"
- click_button "Checkout"
-
- str_addr = "bill_address"
- select address.country.name, :from => "order_#{str_addr}_attributes_country_id"
- ['firstname', 'lastname', 'address1', 'city', 'zipcode', 'phone'].each do |field|
- fill_in "order_#{str_addr}_attributes_#{field}", :with => "#{address.send(field)}"
- end
- select "#{address.state.name}", :from => "order_#{str_addr}_attributes_state_id"
- check "order_use_billing"
- click_button "Save and Continue"
- page.should_not have_content("Standard")
- end
- end
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/states_spec.rb b/core/spec/requests/admin/configuration/states_spec.rb
deleted file mode 100644
index 38a8afd18d5..00000000000
--- a/core/spec/requests/admin/configuration/states_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe "States" do
- stub_authorization!
-
- let!(:country) { create(:country) }
-
- before(:each) do
- Spree::Config[:default_country_id] = country.id
-
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "admin visiting states listing" do
- let!(:state) { create(:state, :country => country) }
-
- it "should correctly display the states" do
- click_link "States"
- page.should have_content(state.name)
- end
- end
-
- context "creating and editing states" do
- it "should allow an admin to edit existing states", :js => true do
- click_link "States"
- wait_until do
- page.should have_selector('#country', :visible => true)
- end
- select country.name, :from => "Country"
- click_link "new_state_link"
- fill_in "state_name", :with => "Calgary"
- fill_in "Abbreviation", :with => "CL"
- click_button "Create"
- page.should have_content("successfully created!")
- page.should have_content("Calgary")
- end
-
- it "should show validation errors", :js => true do
- click_link "States"
- select country.name, :from => "country"
-
- wait_until do
- page.should have_selector("#new_state_link", :visible => true)
- end
- click_link "new_state_link"
-
- fill_in "state_name", :with => ""
- fill_in "Abbreviation", :with => ""
- click_button "Create"
- page.should have_content("Name can't be blank")
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/tax_categories_spec.rb b/core/spec/requests/admin/configuration/tax_categories_spec.rb
deleted file mode 100644
index d4ddfcaa22f..00000000000
--- a/core/spec/requests/admin/configuration/tax_categories_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'spec_helper'
-
-describe "Tax Categories" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "admin visiting tax categories list" do
- it "should display the existing tax categories" do
- create(:tax_category, :name => "Clothing", :description => "For Clothing")
- click_link "Tax Categories"
- page.should have_content("Listing Tax Categories")
- within_row(1) do
- column_text(1).should == "Clothing"
- column_text(2).should == "For Clothing"
- column_text(3).should == "False"
- end
- end
- end
-
- context "admin creating new tax category" do
- before(:each) do
- click_link "Tax Categories"
- click_link "admin_new_tax_categories_link"
- end
-
- it "should be able to create new tax category" do
- page.should have_content("New Tax Category")
- fill_in "tax_category_name", :with => "sports goods"
- fill_in "tax_category_description", :with => "sports goods desc"
- click_button "Create"
- page.should have_content("successfully created!")
- end
-
- it "should show validation errors if there are any" do
- click_button "Create"
- page.should have_content("Name can't be blank")
- end
- end
-
- context "admin editing a tax category" do
- it "should be able to update an existing tax category" do
- create(:tax_category)
- click_link "Tax Categories"
- within_row(1) { click_icon :edit }
- fill_in "tax_category_description", :with => "desc 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("desc 99")
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/tax_rates_spec.rb b/core/spec/requests/admin/configuration/tax_rates_spec.rb
deleted file mode 100644
index ee60ce405bd..00000000000
--- a/core/spec/requests/admin/configuration/tax_rates_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'spec_helper'
-
-describe "Tax Rates" do
- stub_authorization!
-
- let!(:tax_rate) { create(:tax_rate, :calculator => stub_model(Spree::Calculator)) }
-
- before do
- visit spree.admin_path
- click_link "Configuration"
- end
-
- # Regression test for #535
- it "can see a tax rate in the list if the tax category has been deleted" do
- tax_rate.tax_category.mark_deleted!
- lambda { click_link "Tax Rates" }.should_not raise_error
- within("table tbody td:nth-child(3)") do
- page.should have_content("N/A")
- end
- end
-
- # Regression test for #1422
- it "can create a new tax rate" do
- click_link "Tax Rates"
- click_link "New Tax Rate"
- fill_in "Rate", :with => "0.05"
- click_button "Create"
- page.should have_content("Tax Rate has been successfully created!")
- end
-end
diff --git a/core/spec/requests/admin/configuration/taxonomies_spec.rb b/core/spec/requests/admin/configuration/taxonomies_spec.rb
deleted file mode 100644
index f6e260a31af..00000000000
--- a/core/spec/requests/admin/configuration/taxonomies_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'spec_helper'
-
-describe "Taxonomies" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "show" do
- it "should display existing taxonomies" do
- create(:taxonomy, :name => 'Brand')
- create(:taxonomy, :name => 'Categories')
- click_link "Taxonomies"
- within_row(1) { page.should have_content("Brand") }
- within_row(2) { page.should have_content("Categories") }
- end
- end
-
- context "create" do
- before(:each) do
- click_link "Taxonomies"
- click_link "admin_new_taxonomy_link"
- end
-
- it "should allow an admin to create a new taxonomy" do
- page.should have_content("New Taxonomy")
- fill_in "taxonomy_name", :with => "sports"
- click_button "Create"
- page.should have_content("successfully created!")
- end
-
- it "should display validation errors" do
- fill_in "taxonomy_name", :with => ""
- click_button "Create"
- page.should have_content("can't be blank")
- end
- end
-
- context "edit" do
- it "should allow an admin to update an existing taxonomy" do
- create(:taxonomy)
- click_link "Taxonomies"
- within_row(1) { click_icon :edit }
- fill_in "taxonomy_name", :with => "sports 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("sports 99")
- end
- end
-end
diff --git a/core/spec/requests/admin/configuration/zones_spec.rb b/core/spec/requests/admin/configuration/zones_spec.rb
deleted file mode 100644
index 7c5c8b4cc5c..00000000000
--- a/core/spec/requests/admin/configuration/zones_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe "Zones" do
- stub_authorization!
-
- before(:each) do
- Spree::Zone.delete_all
- visit spree.admin_path
- click_link "Configuration"
- end
-
- context "show" do
- it "should display existing zones" do
- create(:zone, :name => "eastern", :description => "zone is eastern")
- create(:zone, :name => "western", :description => "cool san fran")
- click_link "Zones"
-
- within_row(1) { page.should have_content("eastern") }
- within_row(2) { page.should have_content("western") }
-
- click_link "zones_order_by_description_title"
-
- within_row(1) { page.should have_content("western") }
- within_row(2) { page.should have_content("eastern") }
- end
- end
-
- context "create" do
- it "should allow an admin to create a new zone" do
- click_link "Zones"
- click_link "admin_new_zone_link"
- page.should have_content("New Zone")
- fill_in "zone_name", :with => "japan"
- fill_in "zone_description", :with => "japanese time zone"
- click_button "Create"
- page.should have_content("successfully created!")
- end
- end
-end
diff --git a/core/spec/requests/admin/homepage_spec.rb b/core/spec/requests/admin/homepage_spec.rb
deleted file mode 100644
index 28accd602b3..00000000000
--- a/core/spec/requests/admin/homepage_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'spec_helper'
-
-describe "Homepage" do
- stub_authorization!
-
- context "visiting the homepage" do
- before(:each) do
- visit spree.admin_path
- end
-
- it "should have the header text 'Listing Orders'" do
- within('h1') { page.should have_content("Listing Orders") }
- end
-
- it "should have a link to overview" do
- page.find_link("Overview")['/admin']
- end
-
- it "should have a link to orders" do
- page.find_link("Orders")['/admin/orders']
- end
-
- it "should have a link to products" do
- page.find_link("Products")['/admin/products']
- end
-
- it "should have a link to reports" do
- page.find_link("Reports")['/admin/reports']
- end
-
- it "should have a link to configuration" do
- page.find_link("Configuration")['/admin/configurations']
- end
- end
-
- context "visiting the products tab" do
- before(:each) do
- visit spree.admin_products_path
- end
-
- it "should have a link to products" do
- within('#sub-menu') { page.find_link("Products")['/admin/products'] }
- end
-
- it "should have a link to option types" do
- within('#sub-menu') { page.find_link("Option Types")['/admin/option_types'] }
- end
-
- it "should have a link to properties" do
- within('#sub-menu') { page.find_link("Properties")['/admin/properties'] }
- end
-
- it "should have a link to prototypes" do
- within('#sub-menu') { page.find_link("Prototypes")['/admin/prototypes'] }
- end
-
- end
-end
diff --git a/core/spec/requests/admin/orders/adjustments_spec.rb b/core/spec/requests/admin/orders/adjustments_spec.rb
deleted file mode 100644
index e86253d5ded..00000000000
--- a/core/spec/requests/admin/orders/adjustments_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'spec_helper'
-
-describe "Adjustments" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- order = create(:order, :completed_at => "2011-02-01 12:36:15", :number => "R100")
- create(:adjustment, :adjustable => order)
- click_link "Orders"
- within_row(1) { click_icon :edit }
- click_link "Adjustments"
- end
-
- context "admin managing adjustments" do
- it "should display the correct values for existing order adjustments" do
- within_row(1) do
- column_text(2).should == "Shipping"
- column_text(3).should == "$100.00"
- end
- end
- end
-
- context "admin creating a new adjustment" do
- before(:each) do
- click_link "New Adjustment"
- end
-
- context "successfully" do
- it "should create a new adjustment" do
- fill_in "adjustment_amount", :with => "10"
- fill_in "adjustment_label", :with => "rebate"
- click_button "Continue"
- page.should have_content("successfully created!")
- end
- end
-
- context "with validation errors" do
- it "should not create a new adjustment" do
- fill_in "adjustment_amount", :with => ""
- fill_in "adjustment_label", :with => ""
- click_button "Continue"
- page.should have_content("Label can't be blank")
- page.should have_content("Amount is not a number")
- end
- end
- end
-
- context "admin editing an adjustment" do
- before(:each) do
- within_row(1) { click_icon :edit }
- end
-
- context "successfully" do
- it "should update the adjustment" do
- fill_in "adjustment_amount", :with => "99"
- fill_in "adjustment_label", :with => "rebate 99"
- click_button "Continue"
- page.should have_content("successfully updated!")
- page.should have_content("rebate 99")
- page.should have_content("$99.00")
- end
- end
-
- context "with validation errors" do
- it "should not update the adjustment" do
- fill_in "adjustment_amount", :with => ""
- fill_in "adjustment_label", :with => ""
- click_button "Continue"
- page.should have_content("Label can't be blank")
- page.should have_content("Amount is not a number")
- end
- end
- end
-end
diff --git a/core/spec/requests/admin/orders/customer_details_spec.rb b/core/spec/requests/admin/orders/customer_details_spec.rb
deleted file mode 100644
index 61340018b8b..00000000000
--- a/core/spec/requests/admin/orders/customer_details_spec.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-require 'spec_helper'
-
-describe "Customer Details" do
- stub_authorization!
-
- let(:shipping_method) { create(:shipping_method, :display_on => "front_end") }
- let(:order) { create(:completed_order_with_totals) }
- let(:country) do
- create(:country, :name => "Kangaland")
- end
-
- let(:state) do
- create(:state, :name => "Alabama", :country => country)
- end
-
- before do
- reset_spree_preferences do |config|
- config.default_country_id = country.id
- config.company = true
- end
-
- create(:shipping_method, :display_on => "front_end")
- create(:order_with_inventory_unit_shipped, :completed_at => "2011-02-01 12:36:15")
- ship_address = create(:address, :country => country, :state => state)
- bill_address = create(:address, :country => country, :state => state)
- create(:user, :email => 'foobar@example.com',
- :ship_address => ship_address,
- :bill_address => bill_address)
-
- visit spree.admin_path
- click_link "Orders"
- within('table#listing_orders') { click_icon(:edit) }
- end
-
- context "editing an order", :js => true do
- it "should be able to populate customer details for an existing order" do
- click_link "Customer Details"
- fill_in "customer_search", :with => "foobar"
- sleep(3)
- page.execute_script %Q{ $('.ui-menu-item a:contains("foobar@example.com")').trigger("mouseenter").click(); }
-
- ["ship_address", "bill_address"].each do |address|
- find_field("order_#{address}_attributes_firstname").value.should == "John"
- find_field("order_#{address}_attributes_lastname").value.should == "Doe"
- find_field("order_#{address}_attributes_company").value.should == "Company"
- find_field("order_#{address}_attributes_address1").value.should == "10 Lovely Street"
- find_field("order_#{address}_attributes_address2").value.should == "Northwest"
- find_field("order_#{address}_attributes_city").value.should == "Herndon"
- find_field("order_#{address}_attributes_zipcode").value.should == "20170"
- find_field("order_#{address}_attributes_state_id").value.should == state.id.to_s
- find_field("order_#{address}_attributes_country_id").value.should == country.id.to_s
- find_field("order_#{address}_attributes_phone").value.should == "123-456-7890"
- end
- end
-
- it "should be able to update customer details for an existing order" do
- order.ship_address = create(:address)
- order.save!
-
- click_link "Customer Details"
- ["ship", "bill"].each do |type|
- fill_in "order_#{type}_address_attributes_firstname", :with => "John 99"
- fill_in "order_#{type}_address_attributes_lastname", :with => "Doe"
- fill_in "order_#{type}_address_attributes_lastname", :with => "Company"
- fill_in "order_#{type}_address_attributes_address1", :with => "100 first lane"
- fill_in "order_#{type}_address_attributes_address2", :with => "#101"
- fill_in "order_#{type}_address_attributes_city", :with => "Bethesda"
- fill_in "order_#{type}_address_attributes_zipcode", :with => "20170"
- select "Alabama", :from => "order_#{type}_address_attributes_state_id"
- fill_in "order_#{type}_address_attributes_phone", :with => "123-456-7890"
- end
-
- click_button "Continue"
-
- click_link "Customer Details"
- find_field('order_ship_address_attributes_firstname').value.should == "John 99"
- end
- end
-
- it "should show validation errors" do
- click_link "Customer Details"
- click_button "Continue"
- page.should have_content("Shipping address first name can't be blank")
- end
-
-
- # Regression test for #942
- context "errors when no shipping methods are available" do
- before do
- Spree::ShippingMethod.delete_all
- end
-
- specify do
- click_link "Customer Details"
- # Need to fill in valid information so it passes validations
- fill_in "order_ship_address_attributes_firstname", :with => "John 99"
- fill_in "order_ship_address_attributes_lastname", :with => "Doe"
- fill_in "order_ship_address_attributes_lastname", :with => "Company"
- fill_in "order_ship_address_attributes_address1", :with => "100 first lane"
- fill_in "order_ship_address_attributes_address2", :with => "#101"
- fill_in "order_ship_address_attributes_city", :with => "Bethesda"
- fill_in "order_ship_address_attributes_zipcode", :with => "20170"
- fill_in "order_ship_address_attributes_state_name", :with => "Alabama"
- fill_in "order_ship_address_attributes_phone", :with => "123-456-7890"
- lambda { click_button "Continue" }.should_not raise_error(NoMethodError)
- end
-
-
- end
-
-end
diff --git a/core/spec/requests/admin/orders/listing_spec.rb b/core/spec/requests/admin/orders/listing_spec.rb
deleted file mode 100644
index 1c2203d6ecf..00000000000
--- a/core/spec/requests/admin/orders/listing_spec.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require 'spec_helper'
-
-describe "Orders Listing" do
- stub_authorization!
-
- before(:each) do
- create(:order, :created_at => Time.now + 1.day, :completed_at => Time.now + 1.day, :number => "R100")
- create(:order, :created_at => Time.now - 1.day, :completed_at => Time.now - 1.day, :number => "R200")
- visit spree.admin_path
- end
-
- context "listing orders" do
- before(:each) do
- click_link "Orders"
- end
-
- it "should list existing orders" do
- within_row(1) do
- column_text(2).should == "R100"
- column_text(3).should == "cart"
- end
-
- within_row(2) do
- column_text(2).should == "R200"
- end
- end
-
- it "should be able to sort the orders listing" do
- # default is completed_at desc
- within_row(1) { page.should have_content("R100") }
- within_row(2) { page.should have_content("R200") }
-
- click_link "Completed At"
-
- # Completed at desc
- within_row(1) { page.should have_content("R200") }
- within_row(2) { page.should have_content("R100") }
-
- within('table#listing_orders thead') { click_link "Number" }
-
- # number asc
- within_row(1) { page.should have_content("R100") }
- within_row(2) { page.should have_content("R200") }
- end
- end
-
- context "searching orders" do
- before(:each) do
- click_link "Orders"
- end
-
- it "should be able to search orders" do
- fill_in "q_number_cont", :with => "R200"
- click_icon :search
- within_row(1) do
- page.should have_content("R200")
- end
-
- # Ensure that the other order doesn't show up
- within("table#listing_orders") { page.should_not have_content("R100") }
- end
-
- it "should be able to search orders using only completed at input" do
- fill_in "q_created_at_gt", :with => Date.today
- click_icon :search
- within_row(1) { page.should have_content("R100") }
-
- # Ensure that the other order doesn't show up
- within("table#listing_orders") { page.should_not have_content("R200") }
- end
- end
-end
diff --git a/core/spec/requests/admin/orders/order_details_spec.rb b/core/spec/requests/admin/orders/order_details_spec.rb
deleted file mode 100644
index afa31cd41a0..00000000000
--- a/core/spec/requests/admin/orders/order_details_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-describe "Order Details" do
- stub_authorization!
-
- context "edit order page" do
-
- before do
- reset_spree_preferences do |config|
- config.allow_backorders = true
- end
- create(:country)
- end
-
- after(:each) { I18n.reload! }
-
- let(:product) { create(:product, :name => 'spree t-shirt', :on_hand => 5, :price => 19.99) }
- let(:order) { create(:order, :completed_at => "2011-02-01 12:36:15", :number => "R100") }
-
- it "should allow me to edit order details", :js => true do
- order.add_variant(product.master, 2)
- order.inventory_units.each do |iu|
- iu.update_attribute_without_callbacks('state', 'sold')
- end
-
- visit spree.admin_path
- click_link "Orders"
-
- within_row(1) do
- click_link "R100"
- end
-
- page.should have_content("spree t-shirt")
- page.should have_content("$39.98")
- click_link "Edit"
- fill_in "order_line_items_attributes_0_quantity", :with => "1"
- click_button "Update"
- page.should have_content("Total: $19.99")
- end
-
- it "should render details properly" do
- order.state = :complete
- order.currency = 'GBP'
- order.save!
-
- visit spree.edit_admin_order_path(order)
-
- find(".page-title").text.strip.should == "Order #R100"
-
- within ".additional-info" do
- find(".state").text.should == "complete"
- find("#shipment_status").text.should == "none"
- find("#payment_status").text.should == "none"
- end
-
- I18n.backend.store_translations I18n.locale,
- :shipment_states => { :missing => 'some text' },
- :payment_states => { :missing => 'other text' }
-
- visit spree.edit_admin_order_path(order)
-
- within ".additional-info" do
- find("#order_total").text.should == "£0.00"
- find("#shipment_status").text.should == "some text"
- find("#payment_status").text.should == "other text"
- end
-
- end
- end
-end
diff --git a/core/spec/requests/admin/orders/payments_spec.rb b/core/spec/requests/admin/orders/payments_spec.rb
deleted file mode 100644
index 871ff746be4..00000000000
--- a/core/spec/requests/admin/orders/payments_spec.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-require 'spec_helper'
-
-describe "Payments" do
- stub_authorization!
-
- before(:each) do
-
- reset_spree_preferences do |config|
- config.allow_backorders = true
- end
-
- @order = create(:completed_order_with_totals, :number => "R100", :state => "complete")
- product = create(:product, :name => 'spree t-shirt', :on_hand => 5)
- product.master.count_on_hand = 5
- product.master.save
- @order.add_variant(product.master, 2)
- @order.update!
-
- @order.inventory_units.each do |iu|
- iu.update_attribute_without_callbacks('state', 'sold')
- end
- @order.update!
-
- end
-
- context "payment methods" do
-
- before(:each) do
- create(:payment, :order => @order, :amount => @order.outstanding_balance, :payment_method => create(:bogus_payment_method, :environment => 'test'))
- visit spree.admin_path
- click_link "Orders"
- within_row(1) do
- click_link "R100"
- end
- end
-
- it "should be able to list and create payment methods for an order", :js => true do
-
- click_link "Payments"
- find("#payment_status").text.should == "BALANCE DUE"
- within_row(1) do
- column_text(2).should == "$49.98"
- column_text(3).should == "Credit Card"
- column_text(4).should == "PENDING"
- end
-
- click_icon :void
- find("#payment_status").text.should == "BALANCE DUE"
- page.should have_content("Payment Updated")
-
- within_row(1) do
- column_text(2).should == "$49.98"
- column_text(3).should == "Credit Card"
- column_text(4).should == "VOID"
- end
-
- click_on "New Payment"
- page.should have_content("New Payment")
- click_button "Update"
- page.should have_content("successfully created!")
-
- click_icon(:capture)
- find("#payment_status").text.should == "PAID"
-
- page.should_not have_css('#new_payment_section')
- end
-
- # Regression test for #1269
- it "cannot create a payment for an order with no payment methods" do
- Spree::PaymentMethod.delete_all
- @order.payments.delete_all
-
- visit spree.new_admin_order_payment_path(@order)
- page.should have_content("You cannot create a payment for an order without any payment methods defined.")
- page.should have_content("Please define some payment methods first.")
- end
-
- # Regression tests for #1453
- context "with a check payment" do
- before do
- @order.payments.delete_all
- create(:payment, :order => @order,
- :state => "checkout",
- :amount => @order.outstanding_balance,
- :payment_method => create(:bogus_payment_method, :environment => 'test'))
- end
-
- it "capturing a check payment from a new order" do
- visit spree.admin_order_payments_path(@order)
- click_icon(:capture)
- page.should_not have_content("Cannot perform requested operation")
- page.should have_content("Payment Updated")
- end
-
- it "voids a check payment from a new order" do
- visit spree.admin_order_payments_path(@order)
- click_icon(:void)
- page.should have_content("Payment Updated")
- end
- end
-
- end
-end
diff --git a/core/spec/requests/admin/orders/return_authorizations_spec.rb b/core/spec/requests/admin/orders/return_authorizations_spec.rb
deleted file mode 100644
index 79f189bd80b..00000000000
--- a/core/spec/requests/admin/orders/return_authorizations_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe "return authorizations" do
- stub_authorization!
-
- let!(:order) { create(:completed_order_with_totals) }
-
- before do
- order.inventory_units.update_all("state = 'shipped'")
- create(:return_authorization,
- :order => order,
- :state => 'authorized',
- :inventory_units => order.inventory_units)
- end
-
- # Regression test for #1107
- it "doesn't blow up when receiving a return authorization" do
- visit spree.admin_path
- click_link "Orders"
- click_link order.number
- click_link "Return Authorizations"
- click_link "Edit"
- lambda { click_button "receive" }.should_not raise_error(ActiveRecord::UnknownAttributeError)
- end
-
-end
diff --git a/core/spec/requests/admin/orders/shipments_spec.rb b/core/spec/requests/admin/orders/shipments_spec.rb
deleted file mode 100644
index 1b1e29fb68a..00000000000
--- a/core/spec/requests/admin/orders/shipments_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-describe "Shipments" do
- stub_authorization!
-
- let!(:order) { OrderWalkthrough.up_to(:complete) }
-
- before(:each) do
- # Clear all the shipments and then re-create them in this test
-
- order.shipments.delete_all
- reset_spree_preferences do |config|
- config.allow_backorders = true
- end
-
- visit spree.admin_path
- click_link "Orders"
- within_row(1) { click_link order.number }
- end
-
- it "should be able to create and list shipments for an order", :js => true do
-
- click_link "Shipments"
-
- click_on "New Shipment"
- check "inventory_units_1"
- click_button "Create"
- page.should have_content("successfully created!")
- order.reload
- order.shipments.count.should == 1
-
- click_link "Shipments"
- shipment = order.shipments.last
-
- within_row(1) do
- column_text(1).should == shipment.number
- column_text(5).should == "Pending"
- click_icon(:edit)
- end
-
- page.should have_content("##{shipment.number}")
- end
-
-end
diff --git a/core/spec/requests/admin/products/edit/images_spec.rb b/core/spec/requests/admin/products/edit/images_spec.rb
deleted file mode 100644
index 084b6ac2255..00000000000
--- a/core/spec/requests/admin/products/edit/images_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe "Product Images" do
- stub_authorization!
-
- context "uploading and editing an image", :js => true do
- it "should allow an admin to upload and edit an image for a product" do
- Spree::Image.attachment_definitions[:attachment].delete :storage
-
- create(:product)
-
- visit spree.admin_path
- click_link "Products"
- click_icon(:edit)
- click_link "Images"
- click_link "new_image_link"
- absolute_path = Rails.root + "../../spec/support/ror_ringer.jpeg"
- attach_file('image_attachment', absolute_path)
- click_button "Update"
- page.should have_content("successfully created!")
- click_icon(:edit)
- fill_in "image_alt", :with => "ruby on rails t-shirt"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("ruby on rails t-shirt")
- end
- end
-end
diff --git a/core/spec/requests/admin/products/edit/products_spec.rb b/core/spec/requests/admin/products/edit/products_spec.rb
deleted file mode 100644
index 2c7287803c7..00000000000
--- a/core/spec/requests/admin/products/edit/products_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# encoding: UTF-8
-require 'spec_helper'
-
-describe 'Product Details' do
- stub_authorization!
-
- context 'editing a product' do
- let(:available_on) { Time.now }
- it 'should list the product details' do
- create(:product, :name => 'Bún thịt nướng', :permalink => 'bun-thit-nuong', :sku => 'A100',
- :description => 'lorem ipsum', :available_on => available_on, :count_on_hand => 10)
-
- visit spree.admin_path
- click_link 'Products'
- within_row(1) { click_icon :edit }
-
- click_link 'Product Details'
-
- find('.page-title').text.strip.should == 'Editing Product “Bún thịt nướng”'
- find('input#product_name').value.should == 'Bún thịt nướng'
- find('input#product_permalink').value.should == 'bun-thit-nuong'
- find('textarea#product_description').text.strip.should == 'lorem ipsum'
- find('input#product_price').value.should == '19.99'
- find('input#product_cost_price').value.should == '17.00'
- find('input#product_available_on').value.should_not be_blank
- find('input#product_sku').value.should == 'A100'
- end
-
- it "should handle permalink changes" do
- create(:product, :name => 'Bún thịt nướng', :permalink => 'bun-thit-nuong', :sku => 'A100',
- :description => 'lorem ipsum', :available_on => '2011-01-01 01:01:01', :count_on_hand => 10)
-
- visit spree.admin_path
- click_link 'Products'
- within('table.index tbody tr:nth-child(1)') do
- click_icon(:edit)
- end
-
- fill_in "product_permalink", :with => 'random-permalink-value'
- click_button "Update"
- page.should have_content("successfully updated!")
-
- fill_in "product_permalink", :with => ''
- click_button "Update"
- within('#product_permalink_field') { page.should have_content("can't be blank") }
-
- click_button "Update"
- within('#product_permalink_field') { page.should have_content("can't be blank") }
-
- fill_in "product_permalink", :with => 'another-random-permalink-value'
- click_button "Update"
- page.should have_content("successfully updated!")
-
- end
- end
-end
diff --git a/core/spec/requests/admin/products/edit/taxons_spec.rb b/core/spec/requests/admin/products/edit/taxons_spec.rb
deleted file mode 100644
index ee405bfe4c9..00000000000
--- a/core/spec/requests/admin/products/edit/taxons_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe "Product Taxons" do
- stub_authorization!
-
- context "managing taxons" do
- def selected_taxons
- find("#product_taxon_ids").value.split(',').map(&:to_i)
- end
-
- it "should allow an admin to manage taxons", :js => true do
- taxon_1 = create(:taxon)
- taxon_2 = create(:taxon, :name => 'Clothing')
- product = create(:product)
- product.taxons << taxon_1
-
- visit spree.admin_path
- click_link "Products"
- within("table.index") do
- click_icon :edit
- end
-
- find(".select2-search-choice").text.should == taxon_1.name
- selected_taxons.should =~ [taxon_1.id]
- select2("#product_taxons_field", "Clothing")
- click_button "Update"
- selected_taxons.should =~ [taxon_1.id, taxon_2.id]
-
- # Regression test for #2139
- all("#s2id_product_taxon_ids .select2-search-choice").count.should == 2
- end
- end
-end
diff --git a/core/spec/requests/admin/products/edit/variants_spec.rb b/core/spec/requests/admin/products/edit/variants_spec.rb
deleted file mode 100644
index 862013da678..00000000000
--- a/core/spec/requests/admin/products/edit/variants_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'spec_helper'
-
-describe "Product Variants" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- end
-
- context "editing variant option types", :js => true do
- it "should allow an admin to create option types for a variant" do
- create(:product)
-
- click_link "Products"
-
- within_row(1) { click_icon :edit }
-
- within('#sidebar') { click_link "Variants" }
- page.should have_content("To add variants, you must first define")
- end
-
- it "should allow an admin to create a variant if there are option types" do
- click_link "Products"
- click_link "Option Types"
- click_link "new_option_type_link"
- fill_in "option_type_name", :with => "shirt colors"
- fill_in "option_type_presentation", :with => "colors"
- click_button "Create"
- page.should have_content("successfully created!")
-
- within('#new_add_option_value') { click_link "Add Option Value" }
- page.find('table tr:last td.name input').set('color')
- page.find('table tr:last td.presentation input').set('black')
- click_button "Update"
- page.should have_content("successfully updated!")
-
- create(:product)
-
- visit spree.admin_path
- click_link "Products"
- within('table.index tbody tr:nth-child(1)') do
- click_icon :edit
- end
-
- select "color", :from => "Option Types"
- click_button "Update"
- page.should have_content("successfully updated!")
-
- within('#sidebar') { click_link "Variants" }
- click_link "New Variant"
- fill_in "variant_sku", :with => "A100"
- click_button "Create"
- page.should have_content("successfully created!")
- within(".index") do
- page.should have_content("19.99")
- page.should have_content("A100")
- end
- end
- end
-end
diff --git a/core/spec/requests/admin/products/option_types_spec.rb b/core/spec/requests/admin/products/option_types_spec.rb
deleted file mode 100644
index 8eb2cde0504..00000000000
--- a/core/spec/requests/admin/products/option_types_spec.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'spec_helper'
-
-describe "Option Types" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Products"
- end
-
- context "listing option types" do
- it "should list existing option types" do
- create(:option_type, :name => "tshirt-color", :presentation => "Color")
- create(:option_type, :name => "tshirt-size", :presentation => "Size")
-
- click_link "Option Types"
- within("table#listing_option_types") do
- page.should have_content("Color")
- page.should have_content("tshirt-color")
- page.should have_content("Size")
- page.should have_content("tshirt-size")
- end
- end
- end
-
- context "creating a new option type" do
- it "should allow an admin to create a new option type", :js => true do
- click_link "Option Types"
- click_link "new_option_type_link"
- page.should have_content("New Option Type")
- fill_in "option_type_name", :with => "shirt colors"
- fill_in "option_type_presentation", :with => "colors"
- click_button "Create"
- page.should have_content("successfully created!")
-
- click_link "Add Option Value"
- page.find('table tr:last td.name input').set('color')
- page.find('table tr:last td.presentation input').set('black')
- click_button "Update"
- page.should have_content("successfully updated!")
- end
- end
-
- context "editing an existing option type" do
- it "should allow an admin to update an existing option type" do
- create(:option_type, :name => "tshirt-color", :presentation => "Color")
- create(:option_type, :name => "tshirt-size", :presentation => "Size")
- click_link "Option Types"
- within('table#listing_option_types') { click_link "Edit" }
- fill_in "option_type_name", :with => "foo-size 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("foo-size 99")
- end
- end
-end
diff --git a/core/spec/requests/admin/products/products_spec.rb b/core/spec/requests/admin/products/products_spec.rb
deleted file mode 100644
index 2230e97be99..00000000000
--- a/core/spec/requests/admin/products/products_spec.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-require 'spec_helper'
-
-describe "Products" do
- stub_authorization!
-
- context "as admin user" do
- before(:each) do
- visit spree.admin_path
- end
-
- context "listing products" do
- context "sorting" do
- before do
- create(:product, :name => 'apache baseball cap', :price => 10)
- create(:product, :name => 'zomg shirt', :price => 5)
- end
-
- it "should list existing products with correct sorting by name" do
- click_link "Products"
- # Name ASC
- within_row(1) { page.should have_content('apache baseball cap') }
- within_row(2) { page.should have_content("zomg shirt") }
-
- # Name DESC
- click_link "admin_products_listing_name_title"
- within_row(1) { page.should have_content("zomg shirt") }
- within_row(2) { page.should have_content('apache baseball cap') }
- end
-
- it "should list existing products with correct sorting by price" do
- click_link "Products"
-
- # Name ASC (default)
- within_row(1) { page.should have_content('apache baseball cap') }
- within_row(2) { page.should have_content("zomg shirt") }
-
- # Price DESC
- click_link "admin_products_listing_price_title"
- within_row(1) { page.should have_content("zomg shirt") }
- within_row(2) { page.should have_content('apache baseball cap') }
- end
- end
- end
-
- context "searching products" do
- it "should be able to search deleted products", :js => true do
- create(:product, :name => 'apache baseball cap', :deleted_at => "2011-01-06 18:21:13")
- create(:product, :name => 'zomg shirt')
-
- click_link "Products"
- page.should have_content("zomg shirt")
- page.should_not have_content("apache baseball cap")
- check "Show Deleted"
- click_icon :search
- page.should have_content("zomg shirt")
- page.should have_content("apache baseball cap")
- uncheck "Show Deleted"
- click_icon :search
- page.should have_content("zomg shirt")
- page.should_not have_content("apache baseball cap")
- end
-
- it "should be able to search products by their properties" do
- create(:product, :name => 'apache baseball cap', :sku => "A100")
- create(:product, :name => 'apache baseball cap2', :sku => "B100")
- create(:product, :name => 'zomg shirt')
-
- click_link "Products"
- fill_in "q_name_cont", :with => "ap"
- click_icon :search
- page.should have_content("apache baseball cap")
- page.should have_content("apache baseball cap2")
- page.should_not have_content("zomg shirt")
-
- fill_in "q_variants_including_master_sku_cont", :with => "A1"
- click_icon :search
- page.should have_content("apache baseball cap")
- page.should_not have_content("apache baseball cap2")
- page.should_not have_content("zomg shirt")
- end
- end
- context "creating a new product from a prototype" do
-
- include_context "product prototype"
-
- before(:each) do
- @option_type_prototype = prototype
- @property_prototype = create(:prototype, :name => "Random")
- click_link "Products"
- click_link "admin_new_product"
- within('#new_product') do
- page.should have_content("SKU")
- end
- end
-
- it "should allow an admin to create a new product and variants from a prototype", :js => true do
- fill_in "product_name", :with => "Baseball Cap"
- fill_in "product_sku", :with => "B100"
- fill_in "product_price", :with => "100"
- fill_in "product_available_on", :with => "2012/01/24"
- select "Size", :from => "Prototype"
- check "Large"
- click_button "Create"
- page.should have_content("successfully created!")
- Spree::Product.last.variants.length.should == 1
- end
-
- it "should not display variants when prototype does not contain option types", :js => true do
- select "Random", :from => "Prototype"
-
- fill_in "product_name", :with => "Baseball Cap"
-
- page.should_not have_content("Variants")
- end
-
- it "should keep option values selected if validation fails", :js => true do
- select "Size", :from => "Prototype"
- check "Large"
- click_button "Create"
- page.should have_content("Name can't be blank")
- field_labeled("Size").should be_checked
- field_labeled("Large").should be_checked
- field_labeled("Small").should_not be_checked
- end
-
- end
-
- context "creating a new product" do
- before(:each) do
- click_link "Products"
- click_link "admin_new_product"
- within('#new_product') do
- page.should have_content("SKU")
- end
- end
-
- it "should allow an admin to create a new product", :js => true do
- fill_in "product_name", :with => "Baseball Cap"
- fill_in "product_sku", :with => "B100"
- fill_in "product_price", :with => "100"
- fill_in "product_available_on", :with => "2012/01/24"
- click_button "Create"
- page.should have_content("successfully created!")
- fill_in "product_on_hand", :with => "100"
- click_button "Update"
- page.should have_content("successfully updated!")
- end
-
- it "should show validation errors", :js => true do
- click_button "Create"
- page.should have_content("Name can't be blank")
- end
-
- # Regression test for #2097
- it "can set the count on hand to a null value", :js => true do
- fill_in "product_name", :with => "Baseball Cap"
- fill_in "product_price", :with => "100"
- click_button "Create"
- page.should have_content("successfully created!")
- fill_in "product_on_hand", :with => ""
- click_button "Update"
- page.should_not have_content("spree_products.count_on_hand may not be NULL")
- page.should have_content("successfully updated!")
- end
- end
-
- context "cloning a product", :js => true do
- it "should allow an admin to clone a product" do
- create(:product)
-
- click_link "Products"
- within_row(1) do
- click_icon :copy
- end
-
- page.should have_content("Product has been cloned")
- end
-
- context "cloning a deleted product" do
- it "should allow an admin to clone a deleted product" do
- create(:product, :name => "apache baseball cap")
-
- click_link "Products"
- check "Show Deleted"
- click_button "Search"
-
- page.should have_content("apache baseball cap")
-
- within_row(1) do
- click_icon :copy
- end
-
- page.should have_content("Product has been cloned")
- end
- end
- end
-
- context 'updating a product', :js => true do
- let(:product) { create(:product) }
-
- it 'should parse correctly available_on' do
- visit spree.admin_product_path(product)
- fill_in "product_available_on", :with => "2012/12/25"
- click_button "Update"
- page.should have_content("successfully updated!")
- Spree::Product.last.available_on.should == 'Tue, 25 Dec 2012 00:00:00 UTC +00:00'
- end
- end
-
- end
-end
diff --git a/core/spec/requests/admin/products/properties_spec.rb b/core/spec/requests/admin/products/properties_spec.rb
deleted file mode 100644
index 9bd3c14d639..00000000000
--- a/core/spec/requests/admin/products/properties_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe "Properties" do
- stub_authorization!
-
- before(:each) do
- visit spree.admin_path
- click_link "Products"
- end
-
- context "listing product properties" do
- it "should list the existing product properties" do
- create(:property, :name => 'shirt size', :presentation => 'size')
- create(:property, :name => 'shirt fit', :presentation => 'fit')
-
- click_link "Properties"
- within_row(1) do
- column_text(1).should == "shirt size"
- column_text(2).should == "size"
- end
-
- within_row(2) do
- column_text(1).should == "shirt fit"
- column_text(2).should == "fit"
- end
- end
- end
-
- context "creating a property" do
- it "should allow an admin to create a new product property", :js => true do
- click_link "Properties"
- click_link "new_property_link"
- within('#new_property') { page.should have_content("New Property") }
-
- fill_in "property_name", :with => "color of band"
- fill_in "property_presentation", :with => "color"
- click_button "Create"
- page.should have_content("successfully created!")
- end
- end
-
- context "editing a property" do
- before(:each) do
- create(:property)
- click_link "Properties"
- within_row(1) { click_icon :edit }
- end
-
- it "should allow an admin to edit an existing product property" do
- fill_in "property_name", :with => "model 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("model 99")
- end
-
- it "should show validation errors" do
- fill_in "property_name", :with => ""
- click_button "Update"
- page.should have_content("Name can't be blank")
- end
- end
-end
diff --git a/core/spec/requests/admin/products/prototypes_spec.rb b/core/spec/requests/admin/products/prototypes_spec.rb
deleted file mode 100644
index 8d141e2e1a3..00000000000
--- a/core/spec/requests/admin/products/prototypes_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'spec_helper'
-
-describe "Prototypes" do
- stub_authorization!
-
- context "listing prototypes" do
- it "should be able to list existing prototypes" do
- create(:property, :name => "model", :presentation => "Model")
- create(:property, :name => "brand", :presentation => "Brand")
- create(:property, :name => "shirt_fabric", :presentation => "Fabric")
- create(:property, :name => "shirt_sleeve_length", :presentation => "Sleeve")
- create(:property, :name => "mug_type", :presentation => "Type")
- create(:property, :name => "bag_type", :presentation => "Type")
- create(:property, :name => "manufacturer", :presentation => "Manufacturer")
- create(:property, :name => "bag_size", :presentation => "Size")
- create(:property, :name => "mug_size", :presentation => "Size")
- create(:property, :name => "gender", :presentation => "Gender")
- create(:property, :name => "shirt_fit", :presentation => "Fit")
- create(:property, :name => "bag_material", :presentation => "Material")
- create(:property, :name => "shirt_type", :presentation => "Type")
- p = create(:prototype, :name => "Shirt")
- %w( brand gender manufacturer model shirt_fabric shirt_fit shirt_sleeve_length shirt_type ).each do |prop|
- p.properties << Spree::Property.find_by_name(prop)
- end
- p = create(:prototype, :name => "Mug")
- %w( mug_size mug_type ).each do |prop|
- p.properties << Spree::Property.find_by_name(prop)
- end
- p = create(:prototype, :name => "Bag")
- %w( bag_type bag_material ).each do |prop|
- p.properties << Spree::Property.find_by_name(prop)
- end
-
- visit spree.admin_path
- click_link "Products"
- click_link "Prototypes"
-
- within_row(1) { column_text(1).should == "Shirt" }
- within_row(2) { column_text(1).should == "Mug" }
- within_row(3) { column_text(1).should == "Bag" }
- end
- end
-
- context "creating a prototype" do
- it "should allow an admin to create a new product prototype", :js => true do
- visit spree.admin_path
- click_link "Products"
- click_link "Prototypes"
- click_link "new_prototype_link"
- within('#new_prototype') { page.should have_content("New Prototype") }
- fill_in "prototype_name", :with => "male shirts"
- click_button "Create"
- page.should have_content("successfully created!")
- click_link "Prototypes"
- within_row(1) { click_icon :edit }
- fill_in "prototype_name", :with => "Shirt 99"
- click_button "Update"
- page.should have_content("successfully updated!")
- page.should have_content("Shirt 99")
- end
- end
-
- context "editing a prototype" do
- it "should allow to empty its properties" do
- model_property = create(:property, :name => "model", :presentation => "Model")
- brand_property = create(:property, :name => "brand", :presentation => "Brand")
-
- shirt_prototype = create(:prototype, :name => "Shirt", :properties => [])
- %w( brand model ).each do |prop|
- shirt_prototype.properties << Spree::Property.find_by_name(prop)
- end
-
- visit spree.admin_path
- click_link "Products"
- click_link "Prototypes"
-
- click_on "Edit"
- property_ids = find_field("prototype_property_ids").value.map(&:to_i)
- property_ids.should =~ [model_property.id, brand_property.id]
-
- unselect "Brand", :from => "prototype_property_ids"
- unselect "Model", :from => "prototype_property_ids"
-
- click_button 'Update'
-
- click_on "Edit"
-
- find_field("prototype_property_ids").value.should be_empty
- end
- end
-end
diff --git a/core/spec/requests/admin/products/variant_spec.rb b/core/spec/requests/admin/products/variant_spec.rb
deleted file mode 100644
index 2c4b34dd2a4..00000000000
--- a/core/spec/requests/admin/products/variant_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-
-describe "Variants" do
- stub_authorization!
-
- context "creating a new variant" do
- it "should allow an admin to create a new variant" do
- product = create(:product_with_option_types, :price => "1.99", :cost_price => "1.00", :weight => "2.5", :height => "3.0", :width => "1.0", :depth => "1.5")
-
- product.options.each do |option|
- create(:option_value, :option_type => option.option_type)
- end
-
- visit spree.admin_path
- click_link "Products"
- within_row(1) { click_icon :edit }
- click_link "Variants"
- click_on "New Variant"
- find('input#variant_price').value.should == "1.99"
- find('input#variant_cost_price').value.should == "1.00"
- find('input#variant_weight').value.should == "2.50"
- find('input#variant_height').value.should == "3.00"
- find('input#variant_width').value.should == "1.00"
- find('input#variant_depth').value.should == "1.50"
- end
- end
-end
diff --git a/core/spec/requests/admin/reports_spec.rb b/core/spec/requests/admin/reports_spec.rb
deleted file mode 100644
index c1ffec731d1..00000000000
--- a/core/spec/requests/admin/reports_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'spec_helper'
-
-describe "Reports" do
- stub_authorization!
-
- context "visiting the admin reports page" do
- it "should have the right content" do
- visit spree.admin_path
- click_link "Reports"
- click_link "Sales Total"
-
- page.should have_content("Sales Totals")
- page.should have_content("Item Total")
- page.should have_content("Adjustment Total")
- page.should have_content("Sales Total")
- end
- end
-
- context "searching the admin reports page" do
- before do
- order = create(:order)
- order.update_attributes_without_callbacks({:adjustment_total => 100})
- order.completed_at = Time.now
- order.save!
-
- order = create(:order)
- order.update_attributes_without_callbacks({:adjustment_total => 200})
- order.completed_at = Time.now
- order.save!
-
- #incomplete order
- order = create(:order)
- order.update_attributes_without_callbacks({:adjustment_total => 50})
- order.save!
-
- order = create(:order)
- order.update_attributes_without_callbacks({:adjustment_total => 200})
- order.completed_at = 3.years.ago
- order.created_at = 3.years.ago
- order.save!
-
- order = create(:order)
- order.update_attributes_without_callbacks({:adjustment_total => 200})
- order.completed_at = 3.years.from_now
- order.created_at = 3.years.from_now
- order.save!
- end
-
- it "should allow me to search for reports" do
- visit spree.admin_path
- click_link "Reports"
- click_link "Sales Total"
-
- fill_in "q_created_at_gt", :with => 1.week.ago
- fill_in "q_created_at_lt", :with => 1.week.from_now
- click_button "Search"
-
- page.should have_content("$300.00")
- end
- end
-end
diff --git a/core/spec/requests/cart_spec.rb b/core/spec/requests/cart_spec.rb
deleted file mode 100644
index 81be9c406f2..00000000000
--- a/core/spec/requests/cart_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'spec_helper'
-
-describe "Cart" do
- it "shows cart icon on non-cart pages" do
- visit spree.root_path
- lambda { find("li#link-to-cart a") }.should_not raise_error(Capybara::ElementNotFound)
- end
-
- it "hides cart icon on cart page" do
- visit spree.cart_path
- lambda { find("li#link-to-cart a") }.should raise_error(Capybara::ElementNotFound)
- end
-
- it "prevents double clicking the remove button on cart", :js => true do
- @product = create(:product, :name => "RoR Mug", :on_hand => 1)
-
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
-
- # prevent form submit to verify button is disabled
- page.execute_script("$('#update-cart').submit(function(){return false;})")
-
- page.should_not have_selector('button#update-button[disabled]')
- page.find(:css, '.delete img').click
- page.should have_selector('button#update-button[disabled]')
- end
-
- # Regression test for #2006
- it "does not error out with a 404 when GET'ing to /orders/populate" do
- visit '/orders/populate'
- within(".error") do
- page.should have_content(I18n.t(:populate_get_error))
- end
- end
-
- it 'allows you to remove an item from the cart', :js => true do
- create(:product, :name => "RoR Mug", :on_hand => 1)
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
- within("#line_items") do
- click_link "delete_line_item_1"
- end
- page.should_not have_content("Line items quantity must be an integer")
- end
-end
diff --git a/core/spec/requests/checkout_spec.rb b/core/spec/requests/checkout_spec.rb
deleted file mode 100644
index 55170b644af..00000000000
--- a/core/spec/requests/checkout_spec.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'spec_helper'
-
-describe "Checkout" do
- let(:country) { create(:country, :name => "Kangaland",:states_required => true) }
- before do
- create(:state, :name => "Victoria", :country => country)
- end
-
- context "visitor makes checkout as guest without registration" do
- before(:each) do
- Spree::Product.delete_all
- @product = create(:product, :name => "RoR Mug")
- @product.on_hand = 1
- @product.save
- create(:zone)
- end
-
- context "when backordering is disabled" do
- before(:each) do
- reset_spree_preferences do |config|
- config.allow_backorders = false
- end
- end
-
- it "should warn the user about out of stock items" do
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
-
- @product.on_hand = 0
- @product.save
-
- click_button "Checkout"
-
- within(:css, "span.out-of-stock") { page.should have_content("Out of Stock") }
- end
- end
-
- context "defaults to use billing address" do
- before do
- shipping_method = create(:shipping_method)
- shipping_method.zone.zone_members << Spree::ZoneMember.create(:zoneable => country)
-
- @order = OrderWalkthrough.up_to(:address)
- @order.stub(:available_payment_methods => [ create(:bogus_payment_method, :environment => 'test') ])
-
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
- Spree::Order.last.update_column(:email, "ryan@spreecommerce.com")
- click_button "Checkout"
- end
-
- it "should default checkbox to checked" do
- find('input#order_use_billing').should be_checked
- end
-
- it "should remain checked when used and visitor steps back to address step", :js => true do
- address = "order_bill_address_attributes"
- fill_in "#{address}_firstname", :with => "Ryan"
- fill_in "#{address}_lastname", :with => "Bigg"
- fill_in "#{address}_address1", :with => "143 Swan Street"
- fill_in "#{address}_city", :with => "Richmond"
- select "Kangaland", :from => "#{address}_country_id"
- select "Victoria", :from => "#{address}_state_id"
- fill_in "#{address}_zipcode", :with => "12345"
- fill_in "#{address}_phone", :with => "(555) 5555-555"
- click_button "Save and Continue"
- click_link "Address"
-
- find('input#order_use_billing').should be_checked
- end
- end
-
- context "and likes to double click buttons" do
- before(:each) do
- order = OrderWalkthrough.up_to(:delivery)
- order.stub :confirmation_required? => true
-
- order.reload
- order.update!
-
- Spree::CheckoutController.any_instance.stub(:current_order => order)
- Spree::CheckoutController.any_instance.stub(:skip_state_validation? => true)
- end
-
- it "prevents double clicking the payment button on checkout", :js => true do
- visit spree.checkout_state_path(:payment)
-
- # prevent form submit to verify button is disabled
- page.execute_script("$('#checkout_form_payment').submit(function(){return false;})")
-
- page.should_not have_selector('input.button[disabled]')
- click_button "Save and Continue"
- page.should have_selector('input.button[disabled]')
- end
-
- it "prevents double clicking the confirm button on checkout", :js => true do
- visit spree.checkout_state_path(:confirm)
-
- # prevent form submit to verify button is disabled
- page.execute_script("$('#checkout_form_confirm').submit(function(){return false;})")
-
- page.should_not have_selector('input.button[disabled]')
- click_button "Place Order"
- page.should have_selector('input.button[disabled]')
- end
-
- # Regression test for #1596
- context "full checkout" do
- before do
- create(:payment_method)
- Spree::ShippingMethod.delete_all
- shipping_method = create(:shipping_method)
- calculator = Spree::Calculator::PerItem.create!({:calculable => shipping_method}, :without_protection => true)
- shipping_method.calculator = calculator
- shipping_method.save
-
- @product.shipping_category = shipping_method.shipping_category
- @product.save!
- end
-
- it "does not break the per-item shipping method calculator", :js => true do
- visit spree.root_path
- click_link "RoR Mug"
- click_button "add-to-cart-button"
- click_button "Checkout"
- Spree::Order.last.update_column(:email, "ryan@spreecommerce.com")
-
- address = "order_bill_address_attributes"
- fill_in "#{address}_firstname", :with => "Ryan"
- fill_in "#{address}_lastname", :with => "Bigg"
- fill_in "#{address}_address1", :with => "143 Swan Street"
- fill_in "#{address}_city", :with => "Richmond"
- select "Kangaland", :from => "#{address}_country_id"
- select "Victoria", :from => "#{address}_state_id"
- fill_in "#{address}_zipcode", :with => "12345"
- fill_in "#{address}_phone", :with => "(555) 5555-555"
-
- click_button "Save and Continue"
- page.should_not have_content("undefined method `promotion'")
- end
- end
- end
- end
-end
diff --git a/core/spec/requests/order_spec.rb b/core/spec/requests/order_spec.rb
deleted file mode 100644
index e4ded6072a8..00000000000
--- a/core/spec/requests/order_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe 'orders' do
- let(:order) { OrderWalkthrough.up_to(:complete) }
-
- it "can visit an order" do
- # Regression test for current_user call on orders/show
- lambda { visit spree.order_path(order) }.should_not raise_error
- end
-
- it "should have credit card info if paid with credit card" do
- create(:payment, :order => order)
- visit spree.order_path(order)
- within '.payment-info' do
- page.should have_content "Ending in 1111"
- end
- end
-
- it "should have payment method name visible if not paid with credit card" do
- create(:check_payment, :order => order)
- visit spree.order_path(order)
- within '.payment-info' do
- page.should have_content "Check"
- end
- end
-end
diff --git a/core/spec/requests/products_spec.rb b/core/spec/requests/products_spec.rb
deleted file mode 100644
index de1073eec19..00000000000
--- a/core/spec/requests/products_spec.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'spec_helper'
-
-describe "Visiting Products" do
- include_context "custom products"
-
- before(:each) do
- visit spree.root_path
- end
-
- it "should be able to show the shopping cart after adding a product to it" do
- click_link "Ruby on Rails Ringer T-Shirt"
-
- page.should have_content("$19.99")
-
- click_button 'add-to-cart-button'
- page.should have_content("Shopping Cart")
- end
-
- it "should be able to search for a product" do
- fill_in "keywords", :with => "shirt"
- click_button "Search"
-
- page.all('ul.product-listing li').size.should == 1
- end
-
- it "should be able to visit brand Ruby on Rails" do
- within(:css, '#taxonomies') { click_link "Ruby on Rails" }
-
- page.all('ul.product-listing li').size.should == 7
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- array = ["Ruby on Rails Bag",
- "Ruby on Rails Baseball Jersey",
- "Ruby on Rails Jr. Spaghetti",
- "Ruby on Rails Mug",
- "Ruby on Rails Ringer T-Shirt",
- "Ruby on Rails Stein",
- "Ruby on Rails Tote"]
- tmp.sort!.should == array
- end
-
- it "should be able to visit brand Ruby" do
- within(:css, '#taxonomies') { click_link "Ruby" }
-
- page.all('ul.product-listing li').size.should == 1
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Ruby Baseball Jersey"]
- end
-
- it "should be able to visit brand Apache" do
- within(:css, '#taxonomies') { click_link "Apache" }
-
- page.all('ul.product-listing li').size.should == 1
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Apache Baseball Jersey"]
- end
-
- it "should be able to visit category Clothing" do
- click_link "Clothing"
-
- page.all('ul.product-listing li').size.should == 5
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Apache Baseball Jersey",
- "Ruby Baseball Jersey",
- "Ruby on Rails Baseball Jersey",
- "Ruby on Rails Jr. Spaghetti",
- "Ruby on Rails Ringer T-Shirt"]
- end
-
- it "should be able to visit category Mugs" do
- click_link "Mugs"
-
- page.all('ul.product-listing li').size.should == 2
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Ruby on Rails Mug", "Ruby on Rails Stein"]
- end
-
- it "should be able to visit category Bags" do
- click_link "Bags"
-
- page.all('ul.product-listing li').size.should == 2
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Ruby on Rails Bag", "Ruby on Rails Tote"]
- end
-
- it "should be able to display products priced under 10 dollars" do
- within(:css, '#taxonomies') { click_link "Ruby on Rails" }
- check "Price_Range_Under_$10.00"
- within(:css, '#sidebar_products_search') { click_button "Search" }
- page.should have_content("No products found")
- end
-
- it "should be able to display products priced between 15 and 18 dollars" do
- within(:css, '#taxonomies') { click_link "Ruby on Rails" }
- check "Price_Range_$15.00_-_$18.00"
- within(:css, '#sidebar_products_search') { click_button "Search" }
-
- page.all('ul.product-listing li').size.should == 3
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Ruby on Rails Mug", "Ruby on Rails Stein", "Ruby on Rails Tote"]
- end
-
- it "should be able to display products priced between 15 and 18 dollars across multiple pages" do
- Spree::Config.products_per_page = 2
- within(:css, '#taxonomies') { click_link "Ruby on Rails" }
- check "Price_Range_$15.00_-_$18.00"
- within(:css, '#sidebar_products_search') { click_button "Search" }
-
- page.all('ul.product-listing li').size.should == 2
- products = page.all('ul.product-listing li a[itemprop=name]')
- products.count.should == 2
-
- find('nav.pagination .next a').click
- products = page.all('ul.product-listing li a[itemprop=name]')
- products.count.should == 1
- end
-
- it "should be able to display products priced 18 dollars and above" do
- within(:css, '#taxonomies') { click_link "Ruby on Rails" }
- check "Price_Range_$18.00_-_$20.00"
- check "Price_Range_$20.00_or_over"
- within(:css, '#sidebar_products_search') { click_button "Search" }
-
- page.all('ul.product-listing li').size.should == 4
- tmp = page.all('ul.product-listing li a').map(&:text).flatten.compact
- tmp.delete("")
- tmp.sort!.should == ["Ruby on Rails Bag",
- "Ruby on Rails Baseball Jersey",
- "Ruby on Rails Jr. Spaghetti",
- "Ruby on Rails Ringer T-Shirt"]
- end
-
- it "should be able to put a product without a description in the cart" do
- product = FactoryGirl.create(:simple_product, :description => nil, :name => 'Sample', :price => '19.99')
- visit spree.product_path(product)
- page.should have_content "This product has no description"
- click_button 'add-to-cart-button'
- page.should have_content "This product has no description"
- end
-end
diff --git a/core/spec/requests/taxons_spec.rb b/core/spec/requests/taxons_spec.rb
deleted file mode 100644
index e7ca5ea740f..00000000000
--- a/core/spec/requests/taxons_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe "viewing products" do
- let!(:taxonomy) { create(:taxonomy, :name => "Category") }
- let!(:clothing) { taxonomy.root.children.create(:name => "Clothing") }
- let!(:t_shirts) { clothing.children.create(:name => "T-Shirts") }
- let!(:xxl) { t_shirts.children.create(:name => "XXL") }
- let!(:product) do
- product = create(:product, :name => "Superman T-Shirt")
- product.taxons << t_shirts
- end
-
- # Regression test for #1796
- it "can see a taxon's products, even if that taxon has child taxons" do
- visit '/t/category/clothing/t-shirts'
- page.should have_content("Superman T-Shirt")
- end
-end
diff --git a/core/spec/requests/template_rendering_spec.rb b/core/spec/requests/template_rendering_spec.rb
deleted file mode 100644
index 37a49624510..00000000000
--- a/core/spec/requests/template_rendering_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe "Template rendering" do
-
- context "with layout option set to 'application' in the configuration" do
-
- before do
- @app_layout = Rails.root.join('app/views/layouts', 'application.html.erb')
- File.open(@app_layout, 'w') do |app_layout|
- app_layout.puts "I am the application layout"
- end
- Spree::Config.set(:layout => 'application')
- end
-
- it "should render application layout" do
- visit spree.root_path
- page.should_not have_content('Spree Demo Site')
- page.should have_content('I am the application layout')
- end
-
- after do
- FileUtils.rm(@app_layout)
- end
-
- end
-
- context "without any layout option" do
-
- it "should render default layout" do
- visit spree.root_path
- page.should_not have_content('I am the application layout')
- page.should have_content('Spree Demo Site')
- end
-
- end
-
-end
diff --git a/core/spec/spec_helper.rb b/core/spec/spec_helper.rb
index c7b03b069b1..5662d25af49 100644
--- a/core/spec/spec_helper.rb
+++ b/core/spec/spec_helper.rb
@@ -1,126 +1,59 @@
+if ENV["COVERAGE"]
+ # Run Coverage report
+ require 'simplecov'
+ SimpleCov.start do
+ add_group 'Controllers', 'app/controllers'
+ add_group 'Helpers', 'app/helpers'
+ add_group 'Mailers', 'app/mailers'
+ add_group 'Models', 'app/models'
+ add_group 'Views', 'app/views'
+ add_group 'Jobs', 'app/jobs'
+ add_group 'Libraries', 'lib'
+ end
+end
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
# from the project root directory.
ENV["RAILS_ENV"] ||= 'test'
-require File.expand_path("../dummy/config/environment", __FILE__)
-require 'rspec/rails'
-# Requires supporting files with custom matchers and macros, etc,
-# in ./support/ and its subdirectories.
-Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
+begin
+ require File.expand_path("../dummy/config/environment", __FILE__)
+rescue LoadError
+ puts "Could not load dummy application. Please ensure you have run `bundle exec rake test_app`"
+end
+require 'rspec/rails'
require 'database_cleaner'
+require 'ffaker'
+
+Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
-require 'spree/core/testing_support/factories'
-require 'spree/core/testing_support/controller_requests'
-require 'spree/core/testing_support/authorization_helpers'
-require 'spree/core/testing_support/preferences'
-require 'spree/core/testing_support/flash'
+if ENV["CHECK_TRANSLATIONS"]
+ require "spree/testing_support/i18n"
+end
-require 'spree/core/url_helpers'
-require 'paperclip/matchers'
+require 'spree/testing_support/factories'
+require 'spree/testing_support/preferences'
RSpec.configure do |config|
+ config.color = true
+ config.infer_spec_type_from_file_location!
config.mock_with :rspec
- config.fixture_path = "#{::Rails.root}/spec/fixtures"
+ config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures")
- #config.include Devise::TestHelpers, :type => :controller
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, comment the following line or assign false
# instead of true.
- config.use_transactional_fixtures = false
+ config.use_transactional_fixtures = true
- config.before(:each) do
- if example.metadata[:js]
- DatabaseCleaner.strategy = :truncation
- else
- DatabaseCleaner.strategy = :transaction
- end
- end
-
- config.before(:each) do
- DatabaseCleaner.start
+ config.before :each do
+ Rails.cache.clear
reset_spree_preferences
end
- config.after(:each) do
- DatabaseCleaner.clean
- end
-
config.include FactoryGirl::Syntax::Methods
- config.include Spree::Core::UrlHelpers
- config.include Spree::Core::TestingSupport::ControllerRequests
- config.include Spree::Core::TestingSupport::Preferences
- config.include Spree::Core::TestingSupport::Flash
-
- config.include Paperclip::Shoulda::Matchers
-end
-
-shared_context "custom products" do
- before(:each) do
- reset_spree_preferences do |config|
- config.allow_backorders = true
- end
-
- taxonomy = FactoryGirl.create(:taxonomy, :name => 'Categories')
- root = taxonomy.root
- clothing_taxon = FactoryGirl.create(:taxon, :name => 'Clothing', :parent_id => root.id)
- bags_taxon = FactoryGirl.create(:taxon, :name => 'Bags', :parent_id => root.id)
- mugs_taxon = FactoryGirl.create(:taxon, :name => 'Mugs', :parent_id => root.id)
-
- taxonomy = FactoryGirl.create(:taxonomy, :name => 'Brands')
- root = taxonomy.root
- apache_taxon = FactoryGirl.create(:taxon, :name => 'Apache', :parent_id => root.id)
- rails_taxon = FactoryGirl.create(:taxon, :name => 'Ruby on Rails', :parent_id => root.id)
- ruby_taxon = FactoryGirl.create(:taxon, :name => 'Ruby', :parent_id => root.id)
+ config.include Spree::TestingSupport::Preferences
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Ringer T-Shirt', :price => '19.99', :taxons => [rails_taxon, clothing_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Mug', :price => '15.99', :taxons => [rails_taxon, mugs_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Tote', :price => '15.99', :taxons => [rails_taxon, bags_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Bag', :price => '22.99', :taxons => [rails_taxon, bags_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Baseball Jersey', :price => '19.99', :taxons => [rails_taxon, clothing_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Stein', :price => '16.99', :taxons => [rails_taxon, mugs_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby on Rails Jr. Spaghetti', :price => '19.99', :taxons => [rails_taxon, clothing_taxon])
- FactoryGirl.create(:custom_product, :name => 'Ruby Baseball Jersey', :price => '19.99', :taxons => [ruby_taxon, clothing_taxon])
- FactoryGirl.create(:custom_product, :name => 'Apache Baseball Jersey', :price => '19.99', :taxons => [apache_taxon, clothing_taxon])
- end
+ config.fail_fast = ENV['FAIL_FAST'] || false
end
-
-
-
-shared_context "product prototype" do
-
- def build_option_type_with_values(name, values)
- ot = FactoryGirl.create(:option_type, :name => name)
- values.each do |val|
- ot.option_values.create({:name => val.downcase, :presentation => val}, :without_protection => true)
- end
- ot
- end
-
- let(:product_attributes) do
- # FactoryGirl.attributes_for is un-deprecated!
- # https://github.com/thoughtbot/factory_girl/issues/274#issuecomment-3592054
- FactoryGirl.attributes_for(:simple_product)
- end
-
- let(:prototype) do
- size = build_option_type_with_values("size", %w(Small Medium Large))
- FactoryGirl.create(:prototype, :name => "Size", :option_types => [ size ])
- end
-
- let(:option_values_hash) do
- hash = {}
- prototype.option_types.each do |i|
- hash[i.id.to_s] = i.option_value_ids
- end
- hash
- end
-
-end
-
-
-
-PAYMENT_STATES = Spree::Payment.state_machine.states.keys unless defined? PAYMENT_STATES
-SHIPMENT_STATES = Spree::Shipment.state_machine.states.keys unless defined? SHIPMENT_STATES
-ORDER_STATES = Spree::Order.state_machine.states.keys unless defined? ORDER_STATES
diff --git a/core/spec/support/big_decimal.rb b/core/spec/support/big_decimal.rb
new file mode 100644
index 00000000000..48bac649622
--- /dev/null
+++ b/core/spec/support/big_decimal.rb
@@ -0,0 +1,5 @@
+class BigDecimal
+ def inspect
+ "#"
+ end
+end
diff --git a/core/spec/support/capybara_ext.rb b/core/spec/support/capybara_ext.rb
deleted file mode 100644
index abf0ca2cd71..00000000000
--- a/core/spec/support/capybara_ext.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-module CapybaraExt
- def page!
- save_and_open_page
- end
-
- def click_icon(type)
- find(".icon-#{type}").click
- end
-
- def within_row(num, &block)
- within("table.index tbody tr:nth-child(#{num})", &block)
- end
-
- def column_text(num)
- find("td:nth-child(#{num})").text
- end
-
- def select2(within, value)
- script = %Q{
- $('#{within} .select2-search-field input').val('#{value}')
- $('#{within} .select2-search-field input').keydown();
- }
- page.execute_script(script)
-
- # Wait for list to populate...
- wait_until do
- page.find(".select2-highlighted").visible?
- end
- page.execute_script("$('.select2-highlighted').mouseup();")
- end
-end
-
-RSpec.configure do |c|
- c.include CapybaraExt
-end
diff --git a/core/spec/support/concerns/default_price_spec.rb b/core/spec/support/concerns/default_price_spec.rb
new file mode 100644
index 00000000000..352a83315e5
--- /dev/null
+++ b/core/spec/support/concerns/default_price_spec.rb
@@ -0,0 +1,28 @@
+shared_examples_for "default_price" do
+ let(:model) { described_class }
+ subject(:instance) { FactoryGirl.build(model.name.demodulize.downcase.to_sym) }
+
+ describe '.has_one :default_price' do
+ let(:default_price_association) { model.reflect_on_association(:default_price) }
+
+ it 'should be a has one association' do
+ expect(default_price_association.macro).to eql :has_one
+ end
+
+ it 'should have a dependent destroy' do
+ expect(default_price_association.options[:dependent]).to eql :destroy
+ end
+
+ it 'should have the class name of Spree::Price' do
+ expect(default_price_association.options[:class_name]).to eql 'Spree::Price'
+ end
+ end
+
+ describe '#default_price' do
+ subject { instance.default_price }
+
+ its(:class) { should eql Spree::Price }
+ end
+
+ its(:has_default_price?) { should be_truthy }
+end
diff --git a/core/spec/support/order_walkthrough.rb b/core/spec/support/order_walkthrough.rb
deleted file mode 100644
index 2deb2d445a5..00000000000
--- a/core/spec/support/order_walkthrough.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-class OrderWalkthrough
- def self.up_to(state)
- # A payment method must exist for an order to proceed through the Address state
- unless Spree::PaymentMethod.exists?
- Factory(:payment_method)
- end
-
- # A payment method must exist for an order to proceed through the Address state
- unless Spree::ShippingMethod.exists?
- Factory(:shipping_method)
- end
-
- order = Spree::Order.create!(:email => "spree@example.com")
- add_line_item!(order)
- order.next!
-
- end_state_position = states.index(state.to_sym)
- states[0..end_state_position].each do |state|
- send(state, order)
- end
-
- order
- end
-
- private
-
- def self.add_line_item!(order)
- order.line_items << FactoryGirl.create(:line_item)
- order.save
- end
-
- def self.address(order)
-
- order.bill_address = FactoryGirl.create(:address)
- order.ship_address = FactoryGirl.create(:address)
- order.next!
- end
-
- def self.delivery(order)
- order.shipping_method = Spree::ShippingMethod.first
- order.next!
- end
-
- def self.payment(order)
- order.payments.create!({:payment_method => Spree::PaymentMethod.first, :amount => order.total}, :without_protection => true)
- # TODO: maybe look at some way of making this payment_state change automatic
- order.payment_state = 'paid'
- order.next!
- end
-
- def self.complete(order)
- #noop?
- end
-
- def self.states
- [:address, :delivery, :payment, :complete]
- end
-
-end
-
diff --git a/core/spec/support/rake.rb b/core/spec/support/rake.rb
new file mode 100644
index 00000000000..e9807c6706d
--- /dev/null
+++ b/core/spec/support/rake.rb
@@ -0,0 +1,13 @@
+require "rake"
+
+shared_context "rake" do
+ let(:task_name) { self.class.top_level_description }
+ let(:task_path) { "lib/tasks/#{task_name.split(":").first}" }
+ subject { Rake::Task[task_name] }
+
+ before do
+ Rake::Task.define_task(:environment)
+ load File.expand_path(Rails.root + "../../#{task_path}.rake")
+ subject.reenable
+ end
+end
diff --git a/core/spec/support/ror_ringer.jpeg b/core/spec/support/ror_ringer.jpeg
deleted file mode 100644
index 3fcf764bf4d..00000000000
Binary files a/core/spec/support/ror_ringer.jpeg and /dev/null differ
diff --git a/core/spree_core.gemspec b/core/spree_core.gemspec
index 0da923b9c41..b158b291e48 100644
--- a/core/spree_core.gemspec
+++ b/core/spree_core.gemspec
@@ -5,37 +5,40 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'spree_core'
s.version = version
- s.summary = 'Core e-commerce functionality for the Spree project.'
- s.description = 'Required dependency for Spree'
+ s.summary = 'The bare bones necessary for Spree.'
+ s.description = 'The bare bones necessary for Spree.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'Sean Schofield'
s.email = 'sean@spreecommerce.com'
s.homepage = 'http://spreecommerce.com'
- s.rubyforge_project = 'spree_core'
+ s.license = %q{BSD-3}
s.files = Dir['LICENSE', 'README.md', 'app/**/*', 'config/**/*', 'lib/**/*', 'db/**/*', 'vendor/**/*']
s.require_path = 'lib'
- s.requirements << 'none'
- s.add_dependency 'acts_as_list', '= 0.1.4'
- s.add_dependency 'awesome_nested_set', '2.1.5'
+ s.add_dependency 'activemerchant', '~> 1.47.0'
+ s.add_dependency 'acts_as_list', '~> 0.3'
+ s.add_dependency 'awesome_nested_set', '~> 3.0.1'
+ s.add_dependency 'carmen', '~> 1.0.0'
+ s.add_dependency 'cancancan', '~> 1.9.2'
+ s.add_dependency 'deface', '~> 1.0.0'
+ s.add_dependency 'ffaker', '~> 1.16'
+ s.add_dependency 'font-awesome-rails', '~> 4.0'
+ s.add_dependency 'friendly_id', '~> 5.0.4'
+ s.add_dependency 'highline', '~> 1.6.18' # Necessary for the install generator
+ s.add_dependency 'json', '~> 1.7'
+ s.add_dependency 'kaminari', '~> 0.15', '>= 0.15.1'
+ s.add_dependency 'monetize', '~> 1.1'
+ s.add_dependency 'paperclip', '~> 4.2.0'
+ s.add_dependency 'paranoia', '~> 2.1.0'
+ s.add_dependency 'premailer-rails'
+ s.add_dependency 'rails', '~> 4.1.11'
+ s.add_dependency 'ransack', '~> 1.4.1'
+ s.add_dependency 'state_machine', '1.2.0'
+ s.add_dependency 'stringex', '~> 1.5.1'
+ s.add_dependency 'truncate_html', '0.9.2'
+ s.add_dependency 'twitter_cldr', '~> 3.0'
- s.add_dependency 'jquery-rails', '~> 2.0'
- s.add_dependency 'select2-rails', '~> 3.2'
-
- s.add_dependency 'highline', '= 1.6.11'
- s.add_dependency 'state_machine', '= 1.1.2'
- s.add_dependency 'ffaker', '~> 1.12.0'
- s.add_dependency 'paperclip', '~> 2.8'
- s.add_dependency 'aws-sdk', '~> 1.3.4'
- s.add_dependency 'ransack', '~> 0.7.0'
- s.add_dependency 'activemerchant', '= 1.28.0'
- s.add_dependency 'rails', '~> 3.2.9'
- s.add_dependency 'kaminari', '0.13.0'
- s.add_dependency 'deface', '>= 0.9.0'
- s.add_dependency 'stringex', '~> 1.3.2'
- s.add_dependency 'cancan', '1.6.7'
- s.add_dependency 'money', '5.0.0'
- s.add_dependency 'rabl', '0.7.2'
+ s.add_development_dependency 'email_spec', '~> 1.6'
end
diff --git a/core/vendor/assets/fonts/fontawesome-webfont.eot b/core/vendor/assets/fonts/fontawesome-webfont.eot
deleted file mode 100755
index 89070c1e63c..00000000000
Binary files a/core/vendor/assets/fonts/fontawesome-webfont.eot and /dev/null differ
diff --git a/core/vendor/assets/fonts/fontawesome-webfont.svg b/core/vendor/assets/fonts/fontawesome-webfont.svg
deleted file mode 100755
index 1245f92c2ed..00000000000
--- a/core/vendor/assets/fonts/fontawesome-webfont.svg
+++ /dev/null
@@ -1,255 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/core/vendor/assets/fonts/fontawesome-webfont.ttf b/core/vendor/assets/fonts/fontawesome-webfont.ttf
deleted file mode 100755
index c17e9f8d100..00000000000
Binary files a/core/vendor/assets/fonts/fontawesome-webfont.ttf and /dev/null differ
diff --git a/core/vendor/assets/fonts/fontawesome-webfont.woff b/core/vendor/assets/fonts/fontawesome-webfont.woff
deleted file mode 100755
index 09f2469a1f7..00000000000
Binary files a/core/vendor/assets/fonts/fontawesome-webfont.woff and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ad.png b/core/vendor/assets/images/flags/ad.png
deleted file mode 100755
index 625ca84f9ec..00000000000
Binary files a/core/vendor/assets/images/flags/ad.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ae.png b/core/vendor/assets/images/flags/ae.png
deleted file mode 100755
index ef3a1ecfccd..00000000000
Binary files a/core/vendor/assets/images/flags/ae.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/af.png b/core/vendor/assets/images/flags/af.png
deleted file mode 100755
index a4742e299f5..00000000000
Binary files a/core/vendor/assets/images/flags/af.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ag.png b/core/vendor/assets/images/flags/ag.png
deleted file mode 100755
index 556d5504dc2..00000000000
Binary files a/core/vendor/assets/images/flags/ag.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ai.png b/core/vendor/assets/images/flags/ai.png
deleted file mode 100755
index 74ed29d9261..00000000000
Binary files a/core/vendor/assets/images/flags/ai.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/al.png b/core/vendor/assets/images/flags/al.png
deleted file mode 100755
index 92354cb6e25..00000000000
Binary files a/core/vendor/assets/images/flags/al.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/am.png b/core/vendor/assets/images/flags/am.png
deleted file mode 100755
index 344a2a86c43..00000000000
Binary files a/core/vendor/assets/images/flags/am.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/an.png b/core/vendor/assets/images/flags/an.png
deleted file mode 100755
index 633e4b89fde..00000000000
Binary files a/core/vendor/assets/images/flags/an.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ar.png b/core/vendor/assets/images/flags/ar.png
deleted file mode 100755
index e5ef8f1fcdd..00000000000
Binary files a/core/vendor/assets/images/flags/ar.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/as.png b/core/vendor/assets/images/flags/as.png
deleted file mode 100755
index 32f30e4ce4e..00000000000
Binary files a/core/vendor/assets/images/flags/as.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/at.png b/core/vendor/assets/images/flags/at.png
deleted file mode 100755
index 0f15f34f288..00000000000
Binary files a/core/vendor/assets/images/flags/at.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/au.png b/core/vendor/assets/images/flags/au.png
deleted file mode 100755
index a01389a745d..00000000000
Binary files a/core/vendor/assets/images/flags/au.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/aw.png b/core/vendor/assets/images/flags/aw.png
deleted file mode 100755
index a3579c2d621..00000000000
Binary files a/core/vendor/assets/images/flags/aw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ax.png b/core/vendor/assets/images/flags/ax.png
deleted file mode 100755
index 1eea80a7b73..00000000000
Binary files a/core/vendor/assets/images/flags/ax.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/az.png b/core/vendor/assets/images/flags/az.png
deleted file mode 100755
index 4ee9fe5ced2..00000000000
Binary files a/core/vendor/assets/images/flags/az.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ba.png b/core/vendor/assets/images/flags/ba.png
deleted file mode 100755
index c77499249c9..00000000000
Binary files a/core/vendor/assets/images/flags/ba.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bb.png b/core/vendor/assets/images/flags/bb.png
deleted file mode 100755
index 0df19c71d20..00000000000
Binary files a/core/vendor/assets/images/flags/bb.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bd.png b/core/vendor/assets/images/flags/bd.png
deleted file mode 100755
index 076a8bf87c0..00000000000
Binary files a/core/vendor/assets/images/flags/bd.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/be.png b/core/vendor/assets/images/flags/be.png
deleted file mode 100755
index d86ebc800a6..00000000000
Binary files a/core/vendor/assets/images/flags/be.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bf.png b/core/vendor/assets/images/flags/bf.png
deleted file mode 100755
index ab5ce8fe123..00000000000
Binary files a/core/vendor/assets/images/flags/bf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bg.png b/core/vendor/assets/images/flags/bg.png
deleted file mode 100755
index 0469f0607dc..00000000000
Binary files a/core/vendor/assets/images/flags/bg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bh.png b/core/vendor/assets/images/flags/bh.png
deleted file mode 100755
index ea8ce68761b..00000000000
Binary files a/core/vendor/assets/images/flags/bh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bi.png b/core/vendor/assets/images/flags/bi.png
deleted file mode 100755
index 5cc2e30cfc4..00000000000
Binary files a/core/vendor/assets/images/flags/bi.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bj.png b/core/vendor/assets/images/flags/bj.png
deleted file mode 100755
index 1cc8b458a4c..00000000000
Binary files a/core/vendor/assets/images/flags/bj.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bm.png b/core/vendor/assets/images/flags/bm.png
deleted file mode 100755
index c0c7aead8df..00000000000
Binary files a/core/vendor/assets/images/flags/bm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bn.png b/core/vendor/assets/images/flags/bn.png
deleted file mode 100755
index 8fb09849e9b..00000000000
Binary files a/core/vendor/assets/images/flags/bn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bo.png b/core/vendor/assets/images/flags/bo.png
deleted file mode 100755
index ce7ba522aa7..00000000000
Binary files a/core/vendor/assets/images/flags/bo.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/br.png b/core/vendor/assets/images/flags/br.png
deleted file mode 100755
index 9b1a5538b26..00000000000
Binary files a/core/vendor/assets/images/flags/br.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bs.png b/core/vendor/assets/images/flags/bs.png
deleted file mode 100755
index 639fa6cfa9c..00000000000
Binary files a/core/vendor/assets/images/flags/bs.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bt.png b/core/vendor/assets/images/flags/bt.png
deleted file mode 100755
index 1d512dfff42..00000000000
Binary files a/core/vendor/assets/images/flags/bt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bv.png b/core/vendor/assets/images/flags/bv.png
deleted file mode 100755
index 160b6b5b79d..00000000000
Binary files a/core/vendor/assets/images/flags/bv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bw.png b/core/vendor/assets/images/flags/bw.png
deleted file mode 100755
index fcb10394152..00000000000
Binary files a/core/vendor/assets/images/flags/bw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/by.png b/core/vendor/assets/images/flags/by.png
deleted file mode 100755
index 504774ec10e..00000000000
Binary files a/core/vendor/assets/images/flags/by.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/bz.png b/core/vendor/assets/images/flags/bz.png
deleted file mode 100755
index be63ee1c623..00000000000
Binary files a/core/vendor/assets/images/flags/bz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ca.png b/core/vendor/assets/images/flags/ca.png
deleted file mode 100755
index 1f204193ae5..00000000000
Binary files a/core/vendor/assets/images/flags/ca.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/catalonia.png b/core/vendor/assets/images/flags/catalonia.png
deleted file mode 100644
index 5041e308e3a..00000000000
Binary files a/core/vendor/assets/images/flags/catalonia.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cc.png b/core/vendor/assets/images/flags/cc.png
deleted file mode 100755
index aed3d3b4e44..00000000000
Binary files a/core/vendor/assets/images/flags/cc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cd.png b/core/vendor/assets/images/flags/cd.png
deleted file mode 100644
index 5e489424884..00000000000
Binary files a/core/vendor/assets/images/flags/cd.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cf.png b/core/vendor/assets/images/flags/cf.png
deleted file mode 100755
index da687bdce92..00000000000
Binary files a/core/vendor/assets/images/flags/cf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cg.png b/core/vendor/assets/images/flags/cg.png
deleted file mode 100755
index a859792ef32..00000000000
Binary files a/core/vendor/assets/images/flags/cg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ch.png b/core/vendor/assets/images/flags/ch.png
deleted file mode 100755
index 242ec01aaf5..00000000000
Binary files a/core/vendor/assets/images/flags/ch.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ci.png b/core/vendor/assets/images/flags/ci.png
deleted file mode 100755
index 3f2c62eb4d7..00000000000
Binary files a/core/vendor/assets/images/flags/ci.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ck.png b/core/vendor/assets/images/flags/ck.png
deleted file mode 100755
index 746d3d6f758..00000000000
Binary files a/core/vendor/assets/images/flags/ck.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cl.png b/core/vendor/assets/images/flags/cl.png
deleted file mode 100755
index 29c6d61bd4f..00000000000
Binary files a/core/vendor/assets/images/flags/cl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cm.png b/core/vendor/assets/images/flags/cm.png
deleted file mode 100755
index f65c5bd5a79..00000000000
Binary files a/core/vendor/assets/images/flags/cm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cn.png b/core/vendor/assets/images/flags/cn.png
deleted file mode 100755
index 89144146219..00000000000
Binary files a/core/vendor/assets/images/flags/cn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/co.png b/core/vendor/assets/images/flags/co.png
deleted file mode 100755
index a118ff4a146..00000000000
Binary files a/core/vendor/assets/images/flags/co.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cr.png b/core/vendor/assets/images/flags/cr.png
deleted file mode 100755
index c7a37317940..00000000000
Binary files a/core/vendor/assets/images/flags/cr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cs.png b/core/vendor/assets/images/flags/cs.png
deleted file mode 100755
index 8254790ca72..00000000000
Binary files a/core/vendor/assets/images/flags/cs.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cu.png b/core/vendor/assets/images/flags/cu.png
deleted file mode 100755
index 083f1d611c9..00000000000
Binary files a/core/vendor/assets/images/flags/cu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cv.png b/core/vendor/assets/images/flags/cv.png
deleted file mode 100755
index a63f7eaf63c..00000000000
Binary files a/core/vendor/assets/images/flags/cv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cx.png b/core/vendor/assets/images/flags/cx.png
deleted file mode 100755
index 48e31adbf4c..00000000000
Binary files a/core/vendor/assets/images/flags/cx.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cy.png b/core/vendor/assets/images/flags/cy.png
deleted file mode 100755
index 5b1ad6c0788..00000000000
Binary files a/core/vendor/assets/images/flags/cy.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/cz.png b/core/vendor/assets/images/flags/cz.png
deleted file mode 100755
index c8403dd21fd..00000000000
Binary files a/core/vendor/assets/images/flags/cz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/de.png b/core/vendor/assets/images/flags/de.png
deleted file mode 100755
index ac4a9773627..00000000000
Binary files a/core/vendor/assets/images/flags/de.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/dj.png b/core/vendor/assets/images/flags/dj.png
deleted file mode 100755
index 582af364f8a..00000000000
Binary files a/core/vendor/assets/images/flags/dj.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/dk.png b/core/vendor/assets/images/flags/dk.png
deleted file mode 100755
index e2993d3c59a..00000000000
Binary files a/core/vendor/assets/images/flags/dk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/dm.png b/core/vendor/assets/images/flags/dm.png
deleted file mode 100755
index 5fbffcba3cb..00000000000
Binary files a/core/vendor/assets/images/flags/dm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/do.png b/core/vendor/assets/images/flags/do.png
deleted file mode 100755
index 5a04932d879..00000000000
Binary files a/core/vendor/assets/images/flags/do.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/dz.png b/core/vendor/assets/images/flags/dz.png
deleted file mode 100755
index 335c2391d39..00000000000
Binary files a/core/vendor/assets/images/flags/dz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ec.png b/core/vendor/assets/images/flags/ec.png
deleted file mode 100755
index 0caa0b1e785..00000000000
Binary files a/core/vendor/assets/images/flags/ec.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ee.png b/core/vendor/assets/images/flags/ee.png
deleted file mode 100755
index 0c82efb7dde..00000000000
Binary files a/core/vendor/assets/images/flags/ee.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/eg.png b/core/vendor/assets/images/flags/eg.png
deleted file mode 100755
index 8a3f7a10b57..00000000000
Binary files a/core/vendor/assets/images/flags/eg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/eh.png b/core/vendor/assets/images/flags/eh.png
deleted file mode 100755
index 90a1195b47a..00000000000
Binary files a/core/vendor/assets/images/flags/eh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/england.png b/core/vendor/assets/images/flags/england.png
deleted file mode 100755
index 3a7311d5617..00000000000
Binary files a/core/vendor/assets/images/flags/england.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/er.png b/core/vendor/assets/images/flags/er.png
deleted file mode 100755
index 13065ae99cc..00000000000
Binary files a/core/vendor/assets/images/flags/er.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/es.png b/core/vendor/assets/images/flags/es.png
deleted file mode 100755
index c2de2d7111e..00000000000
Binary files a/core/vendor/assets/images/flags/es.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/et.png b/core/vendor/assets/images/flags/et.png
deleted file mode 100755
index 2e893fa056c..00000000000
Binary files a/core/vendor/assets/images/flags/et.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/europeanunion.png b/core/vendor/assets/images/flags/europeanunion.png
deleted file mode 100644
index d6d87115808..00000000000
Binary files a/core/vendor/assets/images/flags/europeanunion.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fam.png b/core/vendor/assets/images/flags/fam.png
deleted file mode 100755
index cf50c759eb2..00000000000
Binary files a/core/vendor/assets/images/flags/fam.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fi.png b/core/vendor/assets/images/flags/fi.png
deleted file mode 100755
index 14ec091b802..00000000000
Binary files a/core/vendor/assets/images/flags/fi.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fj.png b/core/vendor/assets/images/flags/fj.png
deleted file mode 100755
index cee998892eb..00000000000
Binary files a/core/vendor/assets/images/flags/fj.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fk.png b/core/vendor/assets/images/flags/fk.png
deleted file mode 100755
index ceaeb27decb..00000000000
Binary files a/core/vendor/assets/images/flags/fk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fm.png b/core/vendor/assets/images/flags/fm.png
deleted file mode 100755
index 066bb247389..00000000000
Binary files a/core/vendor/assets/images/flags/fm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fo.png b/core/vendor/assets/images/flags/fo.png
deleted file mode 100755
index cbceb809eb9..00000000000
Binary files a/core/vendor/assets/images/flags/fo.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/fr.png b/core/vendor/assets/images/flags/fr.png
deleted file mode 100755
index 8332c4ec23c..00000000000
Binary files a/core/vendor/assets/images/flags/fr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ga.png b/core/vendor/assets/images/flags/ga.png
deleted file mode 100755
index 0e0d434363a..00000000000
Binary files a/core/vendor/assets/images/flags/ga.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gb.png b/core/vendor/assets/images/flags/gb.png
deleted file mode 100644
index ff701e19f6d..00000000000
Binary files a/core/vendor/assets/images/flags/gb.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gd.png b/core/vendor/assets/images/flags/gd.png
deleted file mode 100755
index 9ab57f5489b..00000000000
Binary files a/core/vendor/assets/images/flags/gd.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ge.png b/core/vendor/assets/images/flags/ge.png
deleted file mode 100755
index 728d97078df..00000000000
Binary files a/core/vendor/assets/images/flags/ge.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gf.png b/core/vendor/assets/images/flags/gf.png
deleted file mode 100755
index 8332c4ec23c..00000000000
Binary files a/core/vendor/assets/images/flags/gf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gh.png b/core/vendor/assets/images/flags/gh.png
deleted file mode 100755
index 4e2f8965914..00000000000
Binary files a/core/vendor/assets/images/flags/gh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gi.png b/core/vendor/assets/images/flags/gi.png
deleted file mode 100755
index e76797f62fe..00000000000
Binary files a/core/vendor/assets/images/flags/gi.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gl.png b/core/vendor/assets/images/flags/gl.png
deleted file mode 100755
index ef12a73bf96..00000000000
Binary files a/core/vendor/assets/images/flags/gl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gm.png b/core/vendor/assets/images/flags/gm.png
deleted file mode 100755
index 0720b667aff..00000000000
Binary files a/core/vendor/assets/images/flags/gm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gn.png b/core/vendor/assets/images/flags/gn.png
deleted file mode 100755
index ea660b01fae..00000000000
Binary files a/core/vendor/assets/images/flags/gn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gp.png b/core/vendor/assets/images/flags/gp.png
deleted file mode 100755
index dbb086d0012..00000000000
Binary files a/core/vendor/assets/images/flags/gp.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gq.png b/core/vendor/assets/images/flags/gq.png
deleted file mode 100755
index ebe20a28de0..00000000000
Binary files a/core/vendor/assets/images/flags/gq.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gr.png b/core/vendor/assets/images/flags/gr.png
deleted file mode 100755
index 8651ade7cbe..00000000000
Binary files a/core/vendor/assets/images/flags/gr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gs.png b/core/vendor/assets/images/flags/gs.png
deleted file mode 100755
index 7ef0bf598d9..00000000000
Binary files a/core/vendor/assets/images/flags/gs.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gt.png b/core/vendor/assets/images/flags/gt.png
deleted file mode 100755
index c43a70d3642..00000000000
Binary files a/core/vendor/assets/images/flags/gt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gu.png b/core/vendor/assets/images/flags/gu.png
deleted file mode 100755
index 92f37c05330..00000000000
Binary files a/core/vendor/assets/images/flags/gu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gw.png b/core/vendor/assets/images/flags/gw.png
deleted file mode 100755
index b37bcf06bf2..00000000000
Binary files a/core/vendor/assets/images/flags/gw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/gy.png b/core/vendor/assets/images/flags/gy.png
deleted file mode 100755
index 22cbe2f5914..00000000000
Binary files a/core/vendor/assets/images/flags/gy.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/hk.png b/core/vendor/assets/images/flags/hk.png
deleted file mode 100755
index d5c380ca9d8..00000000000
Binary files a/core/vendor/assets/images/flags/hk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/hm.png b/core/vendor/assets/images/flags/hm.png
deleted file mode 100755
index a01389a745d..00000000000
Binary files a/core/vendor/assets/images/flags/hm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/hn.png b/core/vendor/assets/images/flags/hn.png
deleted file mode 100755
index 96f838859fd..00000000000
Binary files a/core/vendor/assets/images/flags/hn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/hr.png b/core/vendor/assets/images/flags/hr.png
deleted file mode 100755
index 696b515460d..00000000000
Binary files a/core/vendor/assets/images/flags/hr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ht.png b/core/vendor/assets/images/flags/ht.png
deleted file mode 100755
index 416052af772..00000000000
Binary files a/core/vendor/assets/images/flags/ht.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/hu.png b/core/vendor/assets/images/flags/hu.png
deleted file mode 100755
index 7baafe44ddc..00000000000
Binary files a/core/vendor/assets/images/flags/hu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/id.png b/core/vendor/assets/images/flags/id.png
deleted file mode 100755
index c6bc0fafac7..00000000000
Binary files a/core/vendor/assets/images/flags/id.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ie.png b/core/vendor/assets/images/flags/ie.png
deleted file mode 100755
index 26baa31e182..00000000000
Binary files a/core/vendor/assets/images/flags/ie.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/il.png b/core/vendor/assets/images/flags/il.png
deleted file mode 100755
index 2ca772d0b79..00000000000
Binary files a/core/vendor/assets/images/flags/il.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/in.png b/core/vendor/assets/images/flags/in.png
deleted file mode 100755
index e4d7e81a98d..00000000000
Binary files a/core/vendor/assets/images/flags/in.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/io.png b/core/vendor/assets/images/flags/io.png
deleted file mode 100755
index 3e74b6a3164..00000000000
Binary files a/core/vendor/assets/images/flags/io.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/iq.png b/core/vendor/assets/images/flags/iq.png
deleted file mode 100755
index 878a351403a..00000000000
Binary files a/core/vendor/assets/images/flags/iq.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ir.png b/core/vendor/assets/images/flags/ir.png
deleted file mode 100755
index c5fd136aee5..00000000000
Binary files a/core/vendor/assets/images/flags/ir.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/is.png b/core/vendor/assets/images/flags/is.png
deleted file mode 100755
index b8f6d0f0667..00000000000
Binary files a/core/vendor/assets/images/flags/is.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/it.png b/core/vendor/assets/images/flags/it.png
deleted file mode 100755
index 89692f74f05..00000000000
Binary files a/core/vendor/assets/images/flags/it.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ja.png b/core/vendor/assets/images/flags/ja.png
deleted file mode 100755
index 325fbad3ffd..00000000000
Binary files a/core/vendor/assets/images/flags/ja.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/jm.png b/core/vendor/assets/images/flags/jm.png
deleted file mode 100755
index 7be119e03d2..00000000000
Binary files a/core/vendor/assets/images/flags/jm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/jo.png b/core/vendor/assets/images/flags/jo.png
deleted file mode 100755
index 11bd4972b6d..00000000000
Binary files a/core/vendor/assets/images/flags/jo.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ke.png b/core/vendor/assets/images/flags/ke.png
deleted file mode 100755
index 51879adf17c..00000000000
Binary files a/core/vendor/assets/images/flags/ke.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kg.png b/core/vendor/assets/images/flags/kg.png
deleted file mode 100755
index 0a818f67ea3..00000000000
Binary files a/core/vendor/assets/images/flags/kg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kh.png b/core/vendor/assets/images/flags/kh.png
deleted file mode 100755
index 30f6bb1b9b6..00000000000
Binary files a/core/vendor/assets/images/flags/kh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ki.png b/core/vendor/assets/images/flags/ki.png
deleted file mode 100755
index 2dcce4b33ff..00000000000
Binary files a/core/vendor/assets/images/flags/ki.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/km.png b/core/vendor/assets/images/flags/km.png
deleted file mode 100755
index 812b2f56c5a..00000000000
Binary files a/core/vendor/assets/images/flags/km.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kn.png b/core/vendor/assets/images/flags/kn.png
deleted file mode 100755
index febd5b486f3..00000000000
Binary files a/core/vendor/assets/images/flags/kn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kp.png b/core/vendor/assets/images/flags/kp.png
deleted file mode 100755
index d3d509aa874..00000000000
Binary files a/core/vendor/assets/images/flags/kp.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kr.png b/core/vendor/assets/images/flags/kr.png
deleted file mode 100755
index 9c0a78eb942..00000000000
Binary files a/core/vendor/assets/images/flags/kr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kw.png b/core/vendor/assets/images/flags/kw.png
deleted file mode 100755
index 96546da328a..00000000000
Binary files a/core/vendor/assets/images/flags/kw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ky.png b/core/vendor/assets/images/flags/ky.png
deleted file mode 100755
index 15c5f8e4775..00000000000
Binary files a/core/vendor/assets/images/flags/ky.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/kz.png b/core/vendor/assets/images/flags/kz.png
deleted file mode 100755
index 45a8c887424..00000000000
Binary files a/core/vendor/assets/images/flags/kz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/la.png b/core/vendor/assets/images/flags/la.png
deleted file mode 100755
index e28acd018a2..00000000000
Binary files a/core/vendor/assets/images/flags/la.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lb.png b/core/vendor/assets/images/flags/lb.png
deleted file mode 100755
index d0d452bf868..00000000000
Binary files a/core/vendor/assets/images/flags/lb.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lc.png b/core/vendor/assets/images/flags/lc.png
deleted file mode 100644
index a47d065541b..00000000000
Binary files a/core/vendor/assets/images/flags/lc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/li.png b/core/vendor/assets/images/flags/li.png
deleted file mode 100755
index 6469909c013..00000000000
Binary files a/core/vendor/assets/images/flags/li.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lk.png b/core/vendor/assets/images/flags/lk.png
deleted file mode 100755
index 088aad6db95..00000000000
Binary files a/core/vendor/assets/images/flags/lk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lr.png b/core/vendor/assets/images/flags/lr.png
deleted file mode 100755
index 89a5bc7e707..00000000000
Binary files a/core/vendor/assets/images/flags/lr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ls.png b/core/vendor/assets/images/flags/ls.png
deleted file mode 100755
index 33fdef101f7..00000000000
Binary files a/core/vendor/assets/images/flags/ls.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lt.png b/core/vendor/assets/images/flags/lt.png
deleted file mode 100755
index c8ef0da0919..00000000000
Binary files a/core/vendor/assets/images/flags/lt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lu.png b/core/vendor/assets/images/flags/lu.png
deleted file mode 100755
index 4cabba98ae7..00000000000
Binary files a/core/vendor/assets/images/flags/lu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/lv.png b/core/vendor/assets/images/flags/lv.png
deleted file mode 100755
index 49b69981085..00000000000
Binary files a/core/vendor/assets/images/flags/lv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ly.png b/core/vendor/assets/images/flags/ly.png
deleted file mode 100755
index b163a9f8a06..00000000000
Binary files a/core/vendor/assets/images/flags/ly.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ma.png b/core/vendor/assets/images/flags/ma.png
deleted file mode 100755
index f386770280b..00000000000
Binary files a/core/vendor/assets/images/flags/ma.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mc.png b/core/vendor/assets/images/flags/mc.png
deleted file mode 100755
index 1aa830f121a..00000000000
Binary files a/core/vendor/assets/images/flags/mc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/md.png b/core/vendor/assets/images/flags/md.png
deleted file mode 100755
index 4e92c189044..00000000000
Binary files a/core/vendor/assets/images/flags/md.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/me.png b/core/vendor/assets/images/flags/me.png
deleted file mode 100644
index ac7253558ab..00000000000
Binary files a/core/vendor/assets/images/flags/me.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mg.png b/core/vendor/assets/images/flags/mg.png
deleted file mode 100755
index d2715b3d0e1..00000000000
Binary files a/core/vendor/assets/images/flags/mg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mh.png b/core/vendor/assets/images/flags/mh.png
deleted file mode 100755
index fb523a8c39d..00000000000
Binary files a/core/vendor/assets/images/flags/mh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mk.png b/core/vendor/assets/images/flags/mk.png
deleted file mode 100755
index db173aaff21..00000000000
Binary files a/core/vendor/assets/images/flags/mk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ml.png b/core/vendor/assets/images/flags/ml.png
deleted file mode 100755
index 2cec8ba440b..00000000000
Binary files a/core/vendor/assets/images/flags/ml.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mm.png b/core/vendor/assets/images/flags/mm.png
deleted file mode 100755
index f464f67ffb4..00000000000
Binary files a/core/vendor/assets/images/flags/mm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mn.png b/core/vendor/assets/images/flags/mn.png
deleted file mode 100755
index 9396355db45..00000000000
Binary files a/core/vendor/assets/images/flags/mn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mo.png b/core/vendor/assets/images/flags/mo.png
deleted file mode 100755
index deb801dda24..00000000000
Binary files a/core/vendor/assets/images/flags/mo.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mp.png b/core/vendor/assets/images/flags/mp.png
deleted file mode 100755
index 298d588b14b..00000000000
Binary files a/core/vendor/assets/images/flags/mp.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mq.png b/core/vendor/assets/images/flags/mq.png
deleted file mode 100755
index 010143b3867..00000000000
Binary files a/core/vendor/assets/images/flags/mq.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mr.png b/core/vendor/assets/images/flags/mr.png
deleted file mode 100755
index 319546b1008..00000000000
Binary files a/core/vendor/assets/images/flags/mr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ms.png b/core/vendor/assets/images/flags/ms.png
deleted file mode 100755
index d4cbb433d8f..00000000000
Binary files a/core/vendor/assets/images/flags/ms.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mt.png b/core/vendor/assets/images/flags/mt.png
deleted file mode 100755
index 00af94871de..00000000000
Binary files a/core/vendor/assets/images/flags/mt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mu.png b/core/vendor/assets/images/flags/mu.png
deleted file mode 100755
index b7fdce1bdd7..00000000000
Binary files a/core/vendor/assets/images/flags/mu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mv.png b/core/vendor/assets/images/flags/mv.png
deleted file mode 100755
index 5073d9ec47c..00000000000
Binary files a/core/vendor/assets/images/flags/mv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mw.png b/core/vendor/assets/images/flags/mw.png
deleted file mode 100755
index 13886e9f8bf..00000000000
Binary files a/core/vendor/assets/images/flags/mw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mx.png b/core/vendor/assets/images/flags/mx.png
deleted file mode 100755
index 5bc58ab3e35..00000000000
Binary files a/core/vendor/assets/images/flags/mx.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/my.png b/core/vendor/assets/images/flags/my.png
deleted file mode 100755
index 9034cbab2c0..00000000000
Binary files a/core/vendor/assets/images/flags/my.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/mz.png b/core/vendor/assets/images/flags/mz.png
deleted file mode 100755
index 76405e063d4..00000000000
Binary files a/core/vendor/assets/images/flags/mz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/na.png b/core/vendor/assets/images/flags/na.png
deleted file mode 100755
index 63358c67df9..00000000000
Binary files a/core/vendor/assets/images/flags/na.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nc.png b/core/vendor/assets/images/flags/nc.png
deleted file mode 100755
index 2cad2837823..00000000000
Binary files a/core/vendor/assets/images/flags/nc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ne.png b/core/vendor/assets/images/flags/ne.png
deleted file mode 100755
index d85f424f38d..00000000000
Binary files a/core/vendor/assets/images/flags/ne.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nf.png b/core/vendor/assets/images/flags/nf.png
deleted file mode 100755
index f9bcdda12ca..00000000000
Binary files a/core/vendor/assets/images/flags/nf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ng.png b/core/vendor/assets/images/flags/ng.png
deleted file mode 100755
index 3eea2e02075..00000000000
Binary files a/core/vendor/assets/images/flags/ng.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ni.png b/core/vendor/assets/images/flags/ni.png
deleted file mode 100755
index 3969aaaaee4..00000000000
Binary files a/core/vendor/assets/images/flags/ni.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nl.png b/core/vendor/assets/images/flags/nl.png
deleted file mode 100755
index fe44791e32b..00000000000
Binary files a/core/vendor/assets/images/flags/nl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/no.png b/core/vendor/assets/images/flags/no.png
deleted file mode 100755
index 160b6b5b79d..00000000000
Binary files a/core/vendor/assets/images/flags/no.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/np.png b/core/vendor/assets/images/flags/np.png
deleted file mode 100755
index aeb058b7ea8..00000000000
Binary files a/core/vendor/assets/images/flags/np.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nr.png b/core/vendor/assets/images/flags/nr.png
deleted file mode 100755
index 705fc337ccd..00000000000
Binary files a/core/vendor/assets/images/flags/nr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nu.png b/core/vendor/assets/images/flags/nu.png
deleted file mode 100755
index c3ce4aedda9..00000000000
Binary files a/core/vendor/assets/images/flags/nu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/nz.png b/core/vendor/assets/images/flags/nz.png
deleted file mode 100755
index 10d6306d174..00000000000
Binary files a/core/vendor/assets/images/flags/nz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/om.png b/core/vendor/assets/images/flags/om.png
deleted file mode 100755
index 2ffba7e8c43..00000000000
Binary files a/core/vendor/assets/images/flags/om.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pa.png b/core/vendor/assets/images/flags/pa.png
deleted file mode 100755
index 9b2ee9a7809..00000000000
Binary files a/core/vendor/assets/images/flags/pa.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pe.png b/core/vendor/assets/images/flags/pe.png
deleted file mode 100755
index 62a04977fb2..00000000000
Binary files a/core/vendor/assets/images/flags/pe.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pf.png b/core/vendor/assets/images/flags/pf.png
deleted file mode 100755
index 771a0f65225..00000000000
Binary files a/core/vendor/assets/images/flags/pf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pg.png b/core/vendor/assets/images/flags/pg.png
deleted file mode 100755
index 10d6233496c..00000000000
Binary files a/core/vendor/assets/images/flags/pg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ph.png b/core/vendor/assets/images/flags/ph.png
deleted file mode 100755
index b89e15935d9..00000000000
Binary files a/core/vendor/assets/images/flags/ph.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pk.png b/core/vendor/assets/images/flags/pk.png
deleted file mode 100755
index e9df70ca4d6..00000000000
Binary files a/core/vendor/assets/images/flags/pk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pl.png b/core/vendor/assets/images/flags/pl.png
deleted file mode 100755
index d413d010b5b..00000000000
Binary files a/core/vendor/assets/images/flags/pl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pm.png b/core/vendor/assets/images/flags/pm.png
deleted file mode 100755
index ba91d2c7a0d..00000000000
Binary files a/core/vendor/assets/images/flags/pm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pn.png b/core/vendor/assets/images/flags/pn.png
deleted file mode 100755
index aa9344f575b..00000000000
Binary files a/core/vendor/assets/images/flags/pn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pr.png b/core/vendor/assets/images/flags/pr.png
deleted file mode 100755
index 82d9130da45..00000000000
Binary files a/core/vendor/assets/images/flags/pr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ps.png b/core/vendor/assets/images/flags/ps.png
deleted file mode 100755
index f5f547762ed..00000000000
Binary files a/core/vendor/assets/images/flags/ps.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pt.png b/core/vendor/assets/images/flags/pt.png
deleted file mode 100755
index ece79801506..00000000000
Binary files a/core/vendor/assets/images/flags/pt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/pw.png b/core/vendor/assets/images/flags/pw.png
deleted file mode 100755
index 6178b254a5d..00000000000
Binary files a/core/vendor/assets/images/flags/pw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/py.png b/core/vendor/assets/images/flags/py.png
deleted file mode 100755
index cb8723c0640..00000000000
Binary files a/core/vendor/assets/images/flags/py.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/qa.png b/core/vendor/assets/images/flags/qa.png
deleted file mode 100755
index ed4c621fa71..00000000000
Binary files a/core/vendor/assets/images/flags/qa.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/re.png b/core/vendor/assets/images/flags/re.png
deleted file mode 100755
index 8332c4ec23c..00000000000
Binary files a/core/vendor/assets/images/flags/re.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ro.png b/core/vendor/assets/images/flags/ro.png
deleted file mode 100755
index 57e74a6510d..00000000000
Binary files a/core/vendor/assets/images/flags/ro.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/rs.png b/core/vendor/assets/images/flags/rs.png
deleted file mode 100644
index 9439a5b605d..00000000000
Binary files a/core/vendor/assets/images/flags/rs.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ru.png b/core/vendor/assets/images/flags/ru.png
deleted file mode 100755
index 47da4214fd9..00000000000
Binary files a/core/vendor/assets/images/flags/ru.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/rw.png b/core/vendor/assets/images/flags/rw.png
deleted file mode 100755
index 535649178a8..00000000000
Binary files a/core/vendor/assets/images/flags/rw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sa.png b/core/vendor/assets/images/flags/sa.png
deleted file mode 100755
index b4641c7e8b0..00000000000
Binary files a/core/vendor/assets/images/flags/sa.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sb.png b/core/vendor/assets/images/flags/sb.png
deleted file mode 100755
index a9937ccf091..00000000000
Binary files a/core/vendor/assets/images/flags/sb.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sc.png b/core/vendor/assets/images/flags/sc.png
deleted file mode 100755
index 39ee37184e0..00000000000
Binary files a/core/vendor/assets/images/flags/sc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/scotland.png b/core/vendor/assets/images/flags/scotland.png
deleted file mode 100755
index a0e57b4122a..00000000000
Binary files a/core/vendor/assets/images/flags/scotland.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sd.png b/core/vendor/assets/images/flags/sd.png
deleted file mode 100755
index eaab69eb787..00000000000
Binary files a/core/vendor/assets/images/flags/sd.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/se.png b/core/vendor/assets/images/flags/se.png
deleted file mode 100755
index 1994653dac1..00000000000
Binary files a/core/vendor/assets/images/flags/se.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sg.png b/core/vendor/assets/images/flags/sg.png
deleted file mode 100755
index dd34d612107..00000000000
Binary files a/core/vendor/assets/images/flags/sg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sh.png b/core/vendor/assets/images/flags/sh.png
deleted file mode 100755
index 4b1d2a29107..00000000000
Binary files a/core/vendor/assets/images/flags/sh.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/si.png b/core/vendor/assets/images/flags/si.png
deleted file mode 100755
index bb1476ff5fe..00000000000
Binary files a/core/vendor/assets/images/flags/si.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sj.png b/core/vendor/assets/images/flags/sj.png
deleted file mode 100755
index 160b6b5b79d..00000000000
Binary files a/core/vendor/assets/images/flags/sj.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sk.png b/core/vendor/assets/images/flags/sk.png
deleted file mode 100755
index 7ccbc8274ad..00000000000
Binary files a/core/vendor/assets/images/flags/sk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sl.png b/core/vendor/assets/images/flags/sl.png
deleted file mode 100755
index 12d812d29fa..00000000000
Binary files a/core/vendor/assets/images/flags/sl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sm.png b/core/vendor/assets/images/flags/sm.png
deleted file mode 100755
index 3df2fdcf8c0..00000000000
Binary files a/core/vendor/assets/images/flags/sm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sn.png b/core/vendor/assets/images/flags/sn.png
deleted file mode 100755
index eabb71db4e8..00000000000
Binary files a/core/vendor/assets/images/flags/sn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/so.png b/core/vendor/assets/images/flags/so.png
deleted file mode 100755
index 4a1ea4b29b3..00000000000
Binary files a/core/vendor/assets/images/flags/so.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sr.png b/core/vendor/assets/images/flags/sr.png
deleted file mode 100755
index 5eff9271d28..00000000000
Binary files a/core/vendor/assets/images/flags/sr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/st.png b/core/vendor/assets/images/flags/st.png
deleted file mode 100755
index 2978557b19d..00000000000
Binary files a/core/vendor/assets/images/flags/st.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sv.png b/core/vendor/assets/images/flags/sv.png
deleted file mode 100755
index 24987990b73..00000000000
Binary files a/core/vendor/assets/images/flags/sv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sy.png b/core/vendor/assets/images/flags/sy.png
deleted file mode 100755
index f5ce30dcb79..00000000000
Binary files a/core/vendor/assets/images/flags/sy.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/sz.png b/core/vendor/assets/images/flags/sz.png
deleted file mode 100755
index 914ee861d41..00000000000
Binary files a/core/vendor/assets/images/flags/sz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tc.png b/core/vendor/assets/images/flags/tc.png
deleted file mode 100755
index 8fc1156bec3..00000000000
Binary files a/core/vendor/assets/images/flags/tc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/td.png b/core/vendor/assets/images/flags/td.png
deleted file mode 100755
index 667f21fd9d5..00000000000
Binary files a/core/vendor/assets/images/flags/td.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tf.png b/core/vendor/assets/images/flags/tf.png
deleted file mode 100755
index 80529a43619..00000000000
Binary files a/core/vendor/assets/images/flags/tf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tg.png b/core/vendor/assets/images/flags/tg.png
deleted file mode 100755
index 3aa00ad4dfa..00000000000
Binary files a/core/vendor/assets/images/flags/tg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/th.png b/core/vendor/assets/images/flags/th.png
deleted file mode 100755
index dd8ba91719b..00000000000
Binary files a/core/vendor/assets/images/flags/th.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tj.png b/core/vendor/assets/images/flags/tj.png
deleted file mode 100755
index 617bf6455f6..00000000000
Binary files a/core/vendor/assets/images/flags/tj.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tk.png b/core/vendor/assets/images/flags/tk.png
deleted file mode 100755
index 67b8c8cb519..00000000000
Binary files a/core/vendor/assets/images/flags/tk.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tl.png b/core/vendor/assets/images/flags/tl.png
deleted file mode 100755
index 77da181e9c5..00000000000
Binary files a/core/vendor/assets/images/flags/tl.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tm.png b/core/vendor/assets/images/flags/tm.png
deleted file mode 100755
index 828020ecd0f..00000000000
Binary files a/core/vendor/assets/images/flags/tm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tn.png b/core/vendor/assets/images/flags/tn.png
deleted file mode 100755
index 183cdd3dc98..00000000000
Binary files a/core/vendor/assets/images/flags/tn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/to.png b/core/vendor/assets/images/flags/to.png
deleted file mode 100755
index f89b8ba755f..00000000000
Binary files a/core/vendor/assets/images/flags/to.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tr.png b/core/vendor/assets/images/flags/tr.png
deleted file mode 100755
index be32f77e991..00000000000
Binary files a/core/vendor/assets/images/flags/tr.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tt.png b/core/vendor/assets/images/flags/tt.png
deleted file mode 100755
index 2a11c1e20ac..00000000000
Binary files a/core/vendor/assets/images/flags/tt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tv.png b/core/vendor/assets/images/flags/tv.png
deleted file mode 100755
index 28274c5fb40..00000000000
Binary files a/core/vendor/assets/images/flags/tv.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tw.png b/core/vendor/assets/images/flags/tw.png
deleted file mode 100755
index f31c654c99c..00000000000
Binary files a/core/vendor/assets/images/flags/tw.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/tz.png b/core/vendor/assets/images/flags/tz.png
deleted file mode 100755
index c00ff796142..00000000000
Binary files a/core/vendor/assets/images/flags/tz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ua.png b/core/vendor/assets/images/flags/ua.png
deleted file mode 100755
index 09563a21941..00000000000
Binary files a/core/vendor/assets/images/flags/ua.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ug.png b/core/vendor/assets/images/flags/ug.png
deleted file mode 100755
index 33f4affadee..00000000000
Binary files a/core/vendor/assets/images/flags/ug.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/um.png b/core/vendor/assets/images/flags/um.png
deleted file mode 100755
index c1dd9654b07..00000000000
Binary files a/core/vendor/assets/images/flags/um.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/us.png b/core/vendor/assets/images/flags/us.png
deleted file mode 100755
index 10f451fe85c..00000000000
Binary files a/core/vendor/assets/images/flags/us.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/uy.png b/core/vendor/assets/images/flags/uy.png
deleted file mode 100755
index 31d948a067f..00000000000
Binary files a/core/vendor/assets/images/flags/uy.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/uz.png b/core/vendor/assets/images/flags/uz.png
deleted file mode 100755
index fef5dc1709d..00000000000
Binary files a/core/vendor/assets/images/flags/uz.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/va.png b/core/vendor/assets/images/flags/va.png
deleted file mode 100755
index b31eaf225d6..00000000000
Binary files a/core/vendor/assets/images/flags/va.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/vc.png b/core/vendor/assets/images/flags/vc.png
deleted file mode 100755
index 8fa17b0612b..00000000000
Binary files a/core/vendor/assets/images/flags/vc.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ve.png b/core/vendor/assets/images/flags/ve.png
deleted file mode 100755
index 00c90f9aff0..00000000000
Binary files a/core/vendor/assets/images/flags/ve.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/vg.png b/core/vendor/assets/images/flags/vg.png
deleted file mode 100755
index 41569079865..00000000000
Binary files a/core/vendor/assets/images/flags/vg.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/vi.png b/core/vendor/assets/images/flags/vi.png
deleted file mode 100755
index ed26915a323..00000000000
Binary files a/core/vendor/assets/images/flags/vi.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/vn.png b/core/vendor/assets/images/flags/vn.png
deleted file mode 100755
index ec7cd48a346..00000000000
Binary files a/core/vendor/assets/images/flags/vn.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/vu.png b/core/vendor/assets/images/flags/vu.png
deleted file mode 100755
index b3397bc63d7..00000000000
Binary files a/core/vendor/assets/images/flags/vu.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/wales.png b/core/vendor/assets/images/flags/wales.png
deleted file mode 100755
index e0d7cee1107..00000000000
Binary files a/core/vendor/assets/images/flags/wales.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/wf.png b/core/vendor/assets/images/flags/wf.png
deleted file mode 100755
index 9f9558734f0..00000000000
Binary files a/core/vendor/assets/images/flags/wf.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ws.png b/core/vendor/assets/images/flags/ws.png
deleted file mode 100755
index c16950802ea..00000000000
Binary files a/core/vendor/assets/images/flags/ws.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/ye.png b/core/vendor/assets/images/flags/ye.png
deleted file mode 100755
index 468dfad0386..00000000000
Binary files a/core/vendor/assets/images/flags/ye.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/yt.png b/core/vendor/assets/images/flags/yt.png
deleted file mode 100755
index c298f378bee..00000000000
Binary files a/core/vendor/assets/images/flags/yt.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/za.png b/core/vendor/assets/images/flags/za.png
deleted file mode 100755
index 57c58e2119f..00000000000
Binary files a/core/vendor/assets/images/flags/za.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/zm.png b/core/vendor/assets/images/flags/zm.png
deleted file mode 100755
index c25b07beef8..00000000000
Binary files a/core/vendor/assets/images/flags/zm.png and /dev/null differ
diff --git a/core/vendor/assets/images/flags/zw.png b/core/vendor/assets/images/flags/zw.png
deleted file mode 100755
index 53c97259b9b..00000000000
Binary files a/core/vendor/assets/images/flags/zw.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png
deleted file mode 100755
index 5b5dab2ab7b..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png
deleted file mode 100755
index e44f861be1c..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png
deleted file mode 100755
index ac8b229af95..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png
deleted file mode 100755
index ac8b229af95..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png b/core/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png
deleted file mode 100755
index 42ccba269b6..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png
deleted file mode 100755
index 5dcfaa9a016..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png
deleted file mode 100755
index 7226bdbbbde..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png b/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png
deleted file mode 100755
index b47a4da5243..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-icons_0073ea_256x240.png b/core/vendor/assets/images/jquery-ui/ui-icons_0073ea_256x240.png
deleted file mode 100755
index 6b852b618ad..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-icons_0073ea_256x240.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-icons_454545_256x240.png b/core/vendor/assets/images/jquery-ui/ui-icons_454545_256x240.png
deleted file mode 100755
index 59bd45b907c..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-icons_454545_256x240.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-icons_666666_256x240.png b/core/vendor/assets/images/jquery-ui/ui-icons_666666_256x240.png
deleted file mode 100755
index f87de1ca1dc..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-icons_666666_256x240.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-icons_ff0084_256x240.png b/core/vendor/assets/images/jquery-ui/ui-icons_ff0084_256x240.png
deleted file mode 100755
index 938307146b3..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-icons_ff0084_256x240.png and /dev/null differ
diff --git a/core/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png b/core/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png
deleted file mode 100755
index 42f8f992c72..00000000000
Binary files a/core/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png and /dev/null differ
diff --git a/core/vendor/assets/javascripts/handlebars.js b/core/vendor/assets/javascripts/handlebars.js
deleted file mode 100644
index 05346370a20..00000000000
--- a/core/vendor/assets/javascripts/handlebars.js
+++ /dev/null
@@ -1,1920 +0,0 @@
-// lib/handlebars/base.js
-
-/*jshint eqnull:true*/
-this.Handlebars = {};
-
-(function(Handlebars) {
-
-Handlebars.VERSION = "1.0.rc.1";
-
-Handlebars.helpers = {};
-Handlebars.partials = {};
-
-Handlebars.registerHelper = function(name, fn, inverse) {
- if(inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
-};
-
-Handlebars.registerPartial = function(name, str) {
- this.partials[name] = str;
-};
-
-Handlebars.registerHelper('helperMissing', function(arg) {
- if(arguments.length === 2) {
- return undefined;
- } else {
- throw new Error("Could not find property '" + arg + "'");
- }
-});
-
-var toString = Object.prototype.toString, functionType = "[object Function]";
-
-Handlebars.registerHelper('blockHelperMissing', function(context, options) {
- var inverse = options.inverse || function() {}, fn = options.fn;
-
-
- var ret = "";
- var type = toString.call(context);
-
- if(type === functionType) { context = context.call(this); }
-
- if(context === true) {
- return fn(this);
- } else if(context === false || context == null) {
- return inverse(this);
- } else if(type === "[object Array]") {
- if(context.length > 0) {
- return Handlebars.helpers.each(context, options);
- } else {
- return inverse(this);
- }
- } else {
- return fn(context);
- }
-});
-
-Handlebars.K = function() {};
-
-Handlebars.createFrame = Object.create || function(object) {
- Handlebars.K.prototype = object;
- var obj = new Handlebars.K();
- Handlebars.K.prototype = null;
- return obj;
-};
-
-Handlebars.registerHelper('each', function(context, options) {
- var fn = options.fn, inverse = options.inverse;
- var ret = "", data;
-
- if (options.data) {
- data = Handlebars.createFrame(options.data);
- }
-
- if(context && context.length > 0) {
- for(var i=0, j=context.length; i 2) {
- expected.push("'" + this.terminals_[p] + "'");
- }
- if (this.lexer.showPosition) {
- errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
- } else {
- errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
- }
- this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
- }
- }
- if (action[0] instanceof Array && action.length > 1) {
- throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
- }
- switch (action[0]) {
- case 1:
- stack.push(symbol);
- vstack.push(this.lexer.yytext);
- lstack.push(this.lexer.yylloc);
- stack.push(action[1]);
- symbol = null;
- if (!preErrorSymbol) {
- yyleng = this.lexer.yyleng;
- yytext = this.lexer.yytext;
- yylineno = this.lexer.yylineno;
- yyloc = this.lexer.yylloc;
- if (recovering > 0)
- recovering--;
- } else {
- symbol = preErrorSymbol;
- preErrorSymbol = null;
- }
- break;
- case 2:
- len = this.productions_[action[1]][1];
- yyval.$ = vstack[vstack.length - len];
- yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
- if (ranges) {
- yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
- }
- r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
- if (typeof r !== "undefined") {
- return r;
- }
- if (len) {
- stack = stack.slice(0, -1 * len * 2);
- vstack = vstack.slice(0, -1 * len);
- lstack = lstack.slice(0, -1 * len);
- }
- stack.push(this.productions_[action[1]][0]);
- vstack.push(yyval.$);
- lstack.push(yyval._$);
- newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
- stack.push(newState);
- break;
- case 3:
- return true;
- }
- }
- return true;
-}
-};
-/* Jison generated lexer */
-var lexer = (function(){
-var lexer = ({EOF:1,
-parseError:function parseError(str, hash) {
- if (this.yy.parser) {
- this.yy.parser.parseError(str, hash);
- } else {
- throw new Error(str);
- }
- },
-setInput:function (input) {
- this._input = input;
- this._more = this._less = this.done = false;
- this.yylineno = this.yyleng = 0;
- this.yytext = this.matched = this.match = '';
- this.conditionStack = ['INITIAL'];
- this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
- if (this.options.ranges) this.yylloc.range = [0,0];
- this.offset = 0;
- return this;
- },
-input:function () {
- var ch = this._input[0];
- this.yytext += ch;
- this.yyleng++;
- this.offset++;
- this.match += ch;
- this.matched += ch;
- var lines = ch.match(/(?:\r\n?|\n).*/g);
- if (lines) {
- this.yylineno++;
- this.yylloc.last_line++;
- } else {
- this.yylloc.last_column++;
- }
- if (this.options.ranges) this.yylloc.range[1]++;
-
- this._input = this._input.slice(1);
- return ch;
- },
-unput:function (ch) {
- var len = ch.length;
- var lines = ch.split(/(?:\r\n?|\n)/g);
-
- this._input = ch + this._input;
- this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
- //this.yyleng -= len;
- this.offset -= len;
- var oldLines = this.match.split(/(?:\r\n?|\n)/g);
- this.match = this.match.substr(0, this.match.length-1);
- this.matched = this.matched.substr(0, this.matched.length-1);
-
- if (lines.length-1) this.yylineno -= lines.length-1;
- var r = this.yylloc.range;
-
- this.yylloc = {first_line: this.yylloc.first_line,
- last_line: this.yylineno+1,
- first_column: this.yylloc.first_column,
- last_column: lines ?
- (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
- this.yylloc.first_column - len
- };
-
- if (this.options.ranges) {
- this.yylloc.range = [r[0], r[0] + this.yyleng - len];
- }
- return this;
- },
-more:function () {
- this._more = true;
- return this;
- },
-less:function (n) {
- this.unput(this.match.slice(n));
- },
-pastInput:function () {
- var past = this.matched.substr(0, this.matched.length - this.match.length);
- return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
- },
-upcomingInput:function () {
- var next = this.match;
- if (next.length < 20) {
- next += this._input.substr(0, 20-next.length);
- }
- return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
- },
-showPosition:function () {
- var pre = this.pastInput();
- var c = new Array(pre.length + 1).join("-");
- return pre + this.upcomingInput() + "\n" + c+"^";
- },
-next:function () {
- if (this.done) {
- return this.EOF;
- }
- if (!this._input) this.done = true;
-
- var token,
- match,
- tempMatch,
- index,
- col,
- lines;
- if (!this._more) {
- this.yytext = '';
- this.match = '';
- }
- var rules = this._currentRules();
- for (var i=0;i < rules.length; i++) {
- tempMatch = this._input.match(this.rules[rules[i]]);
- if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
- match = tempMatch;
- index = i;
- if (!this.options.flex) break;
- }
- }
- if (match) {
- lines = match[0].match(/(?:\r\n?|\n).*/g);
- if (lines) this.yylineno += lines.length;
- this.yylloc = {first_line: this.yylloc.last_line,
- last_line: this.yylineno+1,
- first_column: this.yylloc.last_column,
- last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
- this.yytext += match[0];
- this.match += match[0];
- this.matches = match;
- this.yyleng = this.yytext.length;
- if (this.options.ranges) {
- this.yylloc.range = [this.offset, this.offset += this.yyleng];
- }
- this._more = false;
- this._input = this._input.slice(match[0].length);
- this.matched += match[0];
- token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
- if (this.done && this._input) this.done = false;
- if (token) return token;
- else return;
- }
- if (this._input === "") {
- return this.EOF;
- } else {
- return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
- {text: "", token: null, line: this.yylineno});
- }
- },
-lex:function lex() {
- var r = this.next();
- if (typeof r !== 'undefined') {
- return r;
- } else {
- return this.lex();
- }
- },
-begin:function begin(condition) {
- this.conditionStack.push(condition);
- },
-popState:function popState() {
- return this.conditionStack.pop();
- },
-_currentRules:function _currentRules() {
- return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
- },
-topState:function () {
- return this.conditionStack[this.conditionStack.length-2];
- },
-pushState:function begin(condition) {
- this.begin(condition);
- }});
-lexer.options = {};
-lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
-
-var YYSTATE=YY_START
-switch($avoiding_name_collisions) {
-case 0:
- if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
- if(yy_.yytext) return 14;
-
-break;
-case 1: return 14;
-break;
-case 2:
- if(yy_.yytext.slice(-1) !== "\\") this.popState();
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
- return 14;
-
-break;
-case 3: return 24;
-break;
-case 4: return 16;
-break;
-case 5: return 20;
-break;
-case 6: return 19;
-break;
-case 7: return 19;
-break;
-case 8: return 23;
-break;
-case 9: return 23;
-break;
-case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
-break;
-case 11: return 22;
-break;
-case 12: return 35;
-break;
-case 13: return 34;
-break;
-case 14: return 34;
-break;
-case 15: return 37;
-break;
-case 16: /*ignore whitespace*/
-break;
-case 17: this.popState(); return 18;
-break;
-case 18: this.popState(); return 18;
-break;
-case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
-break;
-case 20: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 29;
-break;
-case 21: yy_.yytext = yy_.yytext.substr(1); return 27;
-break;
-case 22: return 31;
-break;
-case 23: return 31;
-break;
-case 24: return 30;
-break;
-case 25: return 34;
-break;
-case 26: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 34;
-break;
-case 27: return 'INVALID';
-break;
-case 28: return 5;
-break;
-}
-};
-lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
-lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,28],"inclusive":true}};
-return lexer;})()
-parser.lexer = lexer;
-function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
-return new Parser;
-})();
-if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
-exports.parser = handlebars;
-exports.Parser = handlebars.Parser;
-exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
-exports.main = function commonjsMain(args) {
- if (!args[1])
- throw new Error('Usage: '+args[0]+' FILE');
- var source, cwd;
- if (typeof process !== 'undefined') {
- source = require('fs').readFileSync(require('path').resolve(args[1]), "utf8");
- } else {
- source = require("file").path(require("file").cwd()).join(args[1]).read({charset: "utf-8"});
- }
- return exports.parser.parse(source);
-}
-if (typeof module !== 'undefined' && require.main === module) {
- exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
-}
-};
-;
-// lib/handlebars/compiler/base.js
-Handlebars.Parser = handlebars;
-
-Handlebars.parse = function(string) {
- Handlebars.Parser.yy = Handlebars.AST;
- return Handlebars.Parser.parse(string);
-};
-
-Handlebars.print = function(ast) {
- return new Handlebars.PrintVisitor().accept(ast);
-};
-
-Handlebars.logger = {
- DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
-
- // override in the host environment
- log: function(level, str) {}
-};
-
-Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
-;
-// lib/handlebars/compiler/ast.js
-(function() {
-
- Handlebars.AST = {};
-
- Handlebars.AST.ProgramNode = function(statements, inverse) {
- this.type = "program";
- this.statements = statements;
- if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
- };
-
- Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
- this.type = "mustache";
- this.escaped = !unescaped;
- this.hash = hash;
-
- var id = this.id = rawParams[0];
- var params = this.params = rawParams.slice(1);
-
- // a mustache is an eligible helper if:
- // * its id is simple (a single part, not `this` or `..`)
- var eligibleHelper = this.eligibleHelper = id.isSimple;
-
- // a mustache is definitely a helper if:
- // * it is an eligible helper, and
- // * it has at least one parameter or hash segment
- this.isHelper = eligibleHelper && (params.length || hash);
-
- // if a mustache is an eligible helper but not a definite
- // helper, it is ambiguous, and will be resolved in a later
- // pass or at runtime.
- };
-
- Handlebars.AST.PartialNode = function(id, context) {
- this.type = "partial";
-
- // TODO: disallow complex IDs
-
- this.id = id;
- this.context = context;
- };
-
- var verifyMatch = function(open, close) {
- if(open.original !== close.original) {
- throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
- }
- };
-
- Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
- verifyMatch(mustache.id, close);
- this.type = "block";
- this.mustache = mustache;
- this.program = program;
- this.inverse = inverse;
-
- if (this.inverse && !this.program) {
- this.isInverse = true;
- }
- };
-
- Handlebars.AST.ContentNode = function(string) {
- this.type = "content";
- this.string = string;
- };
-
- Handlebars.AST.HashNode = function(pairs) {
- this.type = "hash";
- this.pairs = pairs;
- };
-
- Handlebars.AST.IdNode = function(parts) {
- this.type = "ID";
- this.original = parts.join(".");
-
- var dig = [], depth = 0;
-
- for(var i=0,l=parts.length; i": ">",
- '"': """,
- "'": "'",
- "`": "`"
- };
-
- var badChars = /[&<>"'`]/g;
- var possible = /[&<>"'`]/;
-
- var escapeChar = function(chr) {
- return escape[chr] || "&";
- };
-
- Handlebars.Utils = {
- escapeExpression: function(string) {
- // don't escape SafeStrings, since they're already safe
- if (string instanceof Handlebars.SafeString) {
- return string.toString();
- } else if (string == null || string === false) {
- return "";
- }
-
- if(!possible.test(string)) { return string; }
- return string.replace(badChars, escapeChar);
- },
-
- isEmpty: function(value) {
- if (typeof value === "undefined") {
- return true;
- } else if (value === null) {
- return true;
- } else if (value === false) {
- return true;
- } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
- return true;
- } else {
- return false;
- }
- }
- };
-})();;
-// lib/handlebars/compiler/compiler.js
-
-/*jshint eqnull:true*/
-Handlebars.Compiler = function() {};
-Handlebars.JavaScriptCompiler = function() {};
-
-(function(Compiler, JavaScriptCompiler) {
- // the foundHelper register will disambiguate helper lookup from finding a
- // function in a context. This is necessary for mustache compatibility, which
- // requires that context functions in blocks are evaluated by blockHelperMissing,
- // and then proceed as if the resulting value was provided to blockHelperMissing.
-
- Compiler.prototype = {
- compiler: Compiler,
-
- disassemble: function() {
- var opcodes = this.opcodes, opcode, out = [], params, param;
-
- for (var i=0, l=opcodes.length; i 0) {
- this.source[1] = this.source[1] + ", " + locals.join(", ");
- }
-
- // Generate minimizer alias mappings
- if (!this.isChild) {
- var aliases = [];
- for (var alias in this.context.aliases) {
- this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
- }
- }
-
- if (this.source[1]) {
- this.source[1] = "var " + this.source[1].substring(2) + ";";
- }
-
- // Merge children
- if (!this.isChild) {
- this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
- }
-
- if (!this.environment.isSimple) {
- this.source.push("return buffer;");
- }
-
- var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
-
- for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
- return "stack" + this.stackSlot;
- },
-
- popStack: function() {
- var item = this.compileStack.pop();
-
- if (item instanceof Literal) {
- return item.value;
- } else {
- this.stackSlot--;
- return item;
- }
- },
-
- topStack: function() {
- var item = this.compileStack[this.compileStack.length - 1];
-
- if (item instanceof Literal) {
- return item.value;
- } else {
- return item;
- }
- },
-
- quotedString: function(str) {
- return '"' + str
- .replace(/\\/g, '\\\\')
- .replace(/"/g, '\\"')
- .replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r') + '"';
- },
-
- setupHelper: function(paramSize, name) {
- var params = [];
- this.setupParams(paramSize, params);
- var foundHelper = this.nameLookup('helpers', name, 'helper');
-
- return {
- params: params,
- name: foundHelper,
- callParams: ["depth0"].concat(params).join(", "),
- helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ")
- };
- },
-
- // the params and contexts arguments are passed in arrays
- // to fill in
- setupParams: function(paramSize, params) {
- var options = [], contexts = [], param, inverse, program;
-
- options.push("hash:" + this.popStack());
-
- inverse = this.popStack();
- program = this.popStack();
-
- // Avoid setting fn and inverse if neither are set. This allows
- // helpers to do a check for `if (options.fn)`
- if (program || inverse) {
- if (!program) {
- this.context.aliases.self = "this";
- program = "self.noop";
- }
-
- if (!inverse) {
- this.context.aliases.self = "this";
- inverse = "self.noop";
- }
-
- options.push("inverse:" + inverse);
- options.push("fn:" + program);
- }
-
- for(var i=0; i)[^>]*|#([\w\-]*))$/;
+
+// $(html) "looks like html" rule change
+jQuery.fn.init = function( selector, context, rootjQuery ) {
+ var match;
+
+ if ( selector && typeof selector === "string" && !jQuery.isPlainObject( context ) &&
+ (match = rquickExpr.exec( selector )) && match[1] ) {
+ // This is an HTML string according to the "old" rules; is it still?
+ if ( selector.charAt( 0 ) !== "<" ) {
+ migrateWarn("$(html) HTML strings must start with '<' character");
+ }
+ // Now process using loose rules; let pre-1.8 play too
+ if ( context && context.context ) {
+ // jQuery object as context; parseHTML expects a DOM object
+ context = context.context;
+ }
+ if ( jQuery.parseHTML ) {
+ return oldInit.call( this, jQuery.parseHTML( jQuery.trim(selector), context, true ),
+ context, rootjQuery );
+ }
+ }
+ return oldInit.apply( this, arguments );
+};
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+}
+
+// Chrome is Webkit, but Webkit is also Safari.
+if ( browser.chrome ) {
+ browser.webkit = true;
+} else if ( browser.webkit ) {
+ browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+// Warn if the code tries to get jQuery.browser
+migrateWarnProp( jQuery, "browser", browser, "jQuery.browser is deprecated" );
+
+jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ migrateWarn( "jQuery.sub() is deprecated" );
+ return jQuerySub;
+};
+
+
+var oldFnData = jQuery.fn.data;
+
+jQuery.fn.data = function( name ) {
+ var ret, evt,
+ elem = this[0];
+
+ // Handles 1.7 which has this behavior and 1.8 which doesn't
+ if ( elem && name === "events" && arguments.length === 1 ) {
+ ret = jQuery.data( elem, name );
+ evt = jQuery._data( elem, name );
+ if ( ( ret === undefined || ret === evt ) && evt !== undefined ) {
+ migrateWarn("Use of jQuery.fn.data('events') is deprecated");
+ return evt;
+ }
+ }
+ return oldFnData.apply( this, arguments );
+};
+
+
+var rscriptType = /\/(java|ecma)script/i,
+ oldSelf = jQuery.fn.andSelf || jQuery.fn.addBack,
+ oldFragment = jQuery.buildFragment;
+
+jQuery.fn.andSelf = function() {
+ migrateWarn("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()");
+ return oldSelf.apply( this, arguments );
+};
+
+// Since jQuery.clean is used internally on older versions, we only shim if it's missing
+if ( !jQuery.clean ) {
+ jQuery.clean = function( elems, context, fragment, scripts ) {
+ // Set context per 1.8 logic
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ migrateWarn("jQuery.clean() is deprecated");
+
+ var i, elem, handleScript, jsTags,
+ ret = [];
+
+ jQuery.merge( ret, jQuery.buildFragment( elems, context ).childNodes );
+
+ // Complex logic lifted directly from jQuery 1.8
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+jQuery.buildFragment = function( elems, context, scripts, selection ) {
+ var ret,
+ warning = "jQuery.buildFragment() is deprecated";
+
+ // Set context per 1.8 logic
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ try {
+ ret = oldFragment.call( jQuery, elems, context, scripts, selection );
+
+ // jQuery < 1.8 required arrayish context; jQuery 1.9 fails on it
+ } catch( x ) {
+ ret = oldFragment.call( jQuery, elems, context.nodeType ? [ context ] : context[ 0 ], scripts, selection );
+
+ // Success from tweaking context means buildFragment was called by the user
+ migrateWarn( warning );
+ }
+
+ // jQuery < 1.9 returned an object instead of the fragment itself
+ if ( !ret.fragment ) {
+ migrateWarnProp( ret, "fragment", ret, warning );
+ migrateWarnProp( ret, "cacheable", false, warning );
+ }
+
+ return ret;
+};
+
+var eventAdd = jQuery.event.add,
+ eventRemove = jQuery.event.remove,
+ eventTrigger = jQuery.event.trigger,
+ oldToggle = jQuery.fn.toggle,
+ oldLive = jQuery.fn.live,
+ oldDie = jQuery.fn.die,
+ ajaxEvents = "ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",
+ rajaxEvent = new RegExp( "\\b(?:" + ajaxEvents + ")\\b" ),
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ hoverHack = function( events ) {
+ if ( typeof( events ) != "string" || jQuery.event.special.hover ) {
+ return events;
+ }
+ if ( rhoverHack.test( events ) ) {
+ migrateWarn("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'");
+ }
+ return events && events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+// Event props removed in 1.9, put them back if needed; no practical way to warn them
+if ( jQuery.event.props && jQuery.event.props[ 0 ] !== "attrChange" ) {
+ jQuery.event.props.unshift( "attrChange", "attrName", "relatedNode", "srcElement" );
+}
+
+// Undocumented jQuery.event.handle was "deprecated" in jQuery 1.7
+migrateWarnProp( jQuery.event, "handle", jQuery.event.dispatch, "jQuery.event.handle is undocumented and deprecated" );
+
+// Support for 'hover' pseudo-event and ajax event warnings
+jQuery.event.add = function( elem, types, handler, data, selector ){
+ if ( elem !== document && rajaxEvent.test( types ) ) {
+ migrateWarn( "AJAX events should be attached to document: " + types );
+ }
+ eventAdd.call( this, elem, hoverHack( types || "" ), handler, data, selector );
+};
+jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ){
+ eventRemove.call( this, elem, hoverHack( types ) || "", handler, selector, mappedTypes );
+};
+
+jQuery.fn.error = function() {
+ var args = Array.prototype.slice.call( arguments, 0);
+ migrateWarn("jQuery.fn.error() is deprecated");
+ args.splice( 0, 0, "error" );
+ if ( arguments.length ) {
+ return this.bind.apply( this, args );
+ }
+ // error event should not bubble to window, although it does pre-1.7
+ this.triggerHandler.apply( this, args );
+ return this;
+};
+
+jQuery.fn.toggle = function( fn, fn2 ) {
+
+ // Don't mess with animation or css toggles
+ if ( !jQuery.isFunction( fn ) || !jQuery.isFunction( fn2 ) ) {
+ return oldToggle.apply( this, arguments );
+ }
+ migrateWarn("jQuery.fn.toggle(handler, handler...) is deprecated");
+
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+};
+
+jQuery.fn.live = function( types, data, fn ) {
+ migrateWarn("jQuery.fn.live() is deprecated");
+ if ( oldLive ) {
+ return oldLive.apply( this, arguments );
+ }
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+};
+
+jQuery.fn.die = function( types, fn ) {
+ migrateWarn("jQuery.fn.die() is deprecated");
+ if ( oldDie ) {
+ return oldDie.apply( this, arguments );
+ }
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+};
+
+// Turn global events into document-triggered events
+jQuery.event.trigger = function( event, data, elem, onlyHandlers ){
+ if ( !elem & !rajaxEvent.test( event ) ) {
+ migrateWarn( "Global events are undocumented and deprecated" );
+ }
+ return eventTrigger.call( this, event, data, elem || document, onlyHandlers );
+};
+jQuery.each( ajaxEvents.split("|"),
+ function( _, name ) {
+ jQuery.event.special[ name ] = {
+ setup: function() {
+ var elem = this;
+
+ // The document needs no shimming; must be !== for oldIE
+ if ( elem !== document ) {
+ jQuery.event.add( document, name + "." + jQuery.guid, function() {
+ jQuery.event.trigger( name, null, elem, true );
+ });
+ jQuery._data( this, name, jQuery.guid++ );
+ }
+ return false;
+ },
+ teardown: function() {
+ if ( this !== document ) {
+ jQuery.event.remove( document, name + "." + jQuery._data( this, name ) );
+ }
+ return false;
+ }
+ };
+ }
+);
+
+
+})( jQuery, window );
diff --git a/core/vendor/assets/javascripts/jquery.alerts/images/important.gif b/core/vendor/assets/javascripts/jquery.alerts/images/important.gif
deleted file mode 100755
index 41d49438fd0..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.alerts/images/important.gif and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.alerts/images/info.gif b/core/vendor/assets/javascripts/jquery.alerts/images/info.gif
deleted file mode 100755
index c81828d1c22..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.alerts/images/info.gif and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.alerts/images/title.gif b/core/vendor/assets/javascripts/jquery.alerts/images/title.gif
deleted file mode 100755
index f92b59665df..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.alerts/images/title.gif and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.horizontalNav.js b/core/vendor/assets/javascripts/jquery.horizontalNav.js
deleted file mode 100755
index a008355a25f..00000000000
--- a/core/vendor/assets/javascripts/jquery.horizontalNav.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/**
- * jQuery Horizontal Navigation 1.0
- * https://github.com/sebnitu/horizontalNav
- *
- * By Sebastian Nitu - Copyright 2012 - All rights reserved
- * Author URL: http://sebnitu.com
- */
-(function($) {
-
- $.fn.horizontalNav = function(options) {
-
- // Extend our default options with those provided.
- var opts = $.extend({}, $.fn.horizontalNav.defaults, options);
-
- return this.each(function () {
-
- // Save our object
- var $this = $(this);
-
- // Build element specific options
- // This lets me access options with this syntax: o.optionName
- var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
-
- // Save the wrapper. The wrapper is the element that
- // we figure out what the full width should be
- if ($this.is('ul')) {
- var ul_wrap = $this.parent();
- } else {
- var ul_wrap = $this;
- }
-
- // let's append a clearfixing element to the ul wrapper
- ul_wrap.css({ 'zoom' : '1' }).append('
');
- $('.clearHorizontalNav').css({
- 'display' : 'block',
- 'overflow' : 'hidden',
- 'visibility' : 'hidden',
- 'width' : 0,
- 'height' : 0,
- 'clear' : 'both'
- });
-
- // Grab elements we'll need and add some default styles
- var ul = $this.is('ul') ? $this : ul_wrap.find('> ul'), // The unordered list element
- li = ul.find('> li'), // All list items
- li_last = li.last(), // Last list item
- li_count = li.size(), // The number of navigation elements
- li_a = li.find('> a'); // Remove padding from the links
-
- // If set to responsive, re-construct after every browser resize
- if ( o.responsive === true ) {
- // Only need to do this for IE7 and below
- // or if we set tableDisplay to false
- if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) {
- resizeTrigger( _construct, o.responsiveDelay );
- }
- }
-
- // Initiate the plugin
- _construct();
-
- // Returns the true inner width of an element
- // Essentially it's the inner width without padding.
- function trueInnerWidth(element) {
- return element.innerWidth() - (
- parseInt(element.css('padding-left')) + parseInt(element.css('padding-right'))
- );
- }
-
- // Call funcion on browser resize
- function resizeTrigger(callback, delay) {
- // Delay before function is called
- delay = delay || 100;
- // Call function on resize
- var resizeTimer;
- $(window).resize(function() {
- clearTimeout(resizeTimer);
- resizeTimer = setTimeout(function() {
- callback();
- }, delay);
- });
- }
-
- // The heavy lifting of this plugin. This is where we
- // find and set the appropriate widths for list items
- function _construct() {
-
- if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) {
-
- // IE7 doesn't support the "display: table" method
- // so we need to do it the hard way.
-
- // Add some styles
- ul.css({ 'float' : 'left' });
- li.css({ 'float' : 'left', 'width' : 'auto' });
- li_a.css({ 'padding-left' : 0, 'padding-right' : 0 });
-
- // Grabbing widths and doing some math
- var ul_width = trueInnerWidth(ul),
- ul_width_outer = ul.outerWidth(true),
- ul_width_extra = ul_width_outer - ul_width,
-
- full_width = trueInnerWidth(ul_wrap),
- extra_width = (full_width - ul_width_extra) - ul_width,
- li_padding = Math.floor( extra_width / li_count );
-
- // Cycle through the list items and give them widths
- li.each(function(index) {
- var li_width = trueInnerWidth( $(this) );
- $(this).css({ 'width' : (li_width + li_padding) + 'px' });
- });
-
- // Get the leftover pixels after we set every itms width
- var li_last_width = trueInnerWidth(li_last) + ( (full_width - ul_width_extra) - trueInnerWidth(ul) );
- // I hate to do this but for some reason Firefox (v13.0) and IE are always
- // one pixel off when rendering. So this is a quick fix for that.
- if ($.browser.mozilla || $.browser.msie) {
- li_last_width = li_last_width - 1;
- }
- // Add the leftovers to the last navigation item
- li_last.css({ 'width' : li_last_width + 'px' });
-
- } else {
- // Every modern browser supports the "display: table" method
- // so this is the best way to do it for them.
- ul.css({ 'display' : 'table', 'float' : 'none', 'width' : '100%' });
- li.css({ 'display' : 'table-cell', 'float' : 'none' });
- }
- }
-
- }); // @end of return this.each()
-
- };
-
- $.fn.horizontalNav.defaults = {
- responsive : true,
- responsiveDelay : 100,
- tableDisplay : true
- };
-
-})(jQuery);
\ No newline at end of file
diff --git a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg b/core/vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg
deleted file mode 100755
index 3aad05d8fad..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/d.png b/core/vendor/assets/javascripts/jquery.jstree/themes/apple/d.png
deleted file mode 100755
index 2463ba6df91..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/d.png and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/style.css b/core/vendor/assets/javascripts/jquery.jstree/themes/apple/style.css
deleted file mode 100755
index 0ce803ccc34..00000000000
--- a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/style.css
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * jsTree apple theme 1.0
- * Supported features: dots/no-dots, icons/no-icons, focused, loading
- * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
- */
-
-.jstree-apple > ul { background:url("bg.jpg") left top repeat; }
-.jstree-apple li,
-.jstree-apple ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
-.jstree-apple li { background-position:-90px 0; background-repeat:repeat-y; }
-.jstree-apple li.jstree-last { background:transparent; }
-.jstree-apple .jstree-open > ins { background-position:-72px 0; }
-.jstree-apple .jstree-closed > ins { background-position:-54px 0; }
-.jstree-apple .jstree-leaf > ins { background-position:-36px 0; }
-
-.jstree-apple a { border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px; text-shadow:1px 1px 1px white; }
-.jstree-apple .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 3px 0 1px; text-shadow:1px 1px 1px silver; }
-.jstree-apple .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 3px 0 1px; }
-.jstree-apple a .jstree-icon { background-position:-56px -20px; }
-.jstree-apple a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
-
-.jstree-apple.jstree-focused { background:white; }
-
-.jstree-apple .jstree-no-dots li,
-.jstree-apple .jstree-no-dots .jstree-leaf > ins { background:transparent; }
-.jstree-apple .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
-.jstree-apple .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
-
-.jstree-apple .jstree-no-icons a .jstree-icon { display:none; }
-
-.jstree-apple .jstree-search { font-style:italic; }
-
-.jstree-apple .jstree-no-icons .jstree-checkbox { display:inline-block; }
-.jstree-apple .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
-.jstree-apple .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
-.jstree-apple .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
-.jstree-apple .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
-.jstree-apple .jstree-checked > a > .checkbox:hover { background-position:-38px -37px; }
-.jstree-apple .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
-.jstree-apple .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
-
-#vakata-dragged.jstree-apple ins { background:transparent !important; }
-/*#vakata-dragged.jstree-apple .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }*/
-/*#vakata-dragged.jstree-apple .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }*/
-/*#jstree-marker.jstree-apple { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }*/
-
-.jstree-apple a.jstree-search { color:aqua; }
-.jstree-apple .jstree-locked a { color:silver; cursor:default; }
-
-#vakata-contextmenu.jstree-apple-context,
-#vakata-contextmenu.jstree-apple-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
-#vakata-contextmenu.jstree-apple-context li { }
-#vakata-contextmenu.jstree-apple-context a { color:black; }
-#vakata-contextmenu.jstree-apple-context a:hover,
-#vakata-contextmenu.jstree-apple-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
-#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a,
-#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
-#vakata-contextmenu.jstree-apple-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
-#vakata-contextmenu.jstree-apple-context li ul { margin-left:-4px; }
-
-/* TODO: IE6 support - the `>` selectors */
\ No newline at end of file
diff --git a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif b/core/vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif
deleted file mode 100755
index 5b33f7e54f4..00000000000
Binary files a/core/vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif and /dev/null differ
diff --git a/core/vendor/assets/javascripts/jquery.payment.js b/core/vendor/assets/javascripts/jquery.payment.js
new file mode 100644
index 00000000000..5bc7ef0adb6
--- /dev/null
+++ b/core/vendor/assets/javascripts/jquery.payment.js
@@ -0,0 +1,497 @@
+// Generated by CoffeeScript 1.4.0
+(function() {
+ var $, cardFromNumber, cardFromType, cards, defaultFormat, formatBackCardNumber, formatBackExpiry, formatCardNumber, formatExpiry, formatForwardExpiry, formatForwardSlash, hasTextSelected, luhnCheck, reFormatCardNumber, restrictCVC, restrictCardNumber, restrictExpiry, restrictNumeric, setCardType,
+ __slice = [].slice,
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
+ _this = this;
+
+ $ = jQuery;
+
+ $.payment = {};
+
+ $.payment.fn = {};
+
+ $.fn.payment = function() {
+ var args, method;
+ method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+ return $.payment.fn[method].apply(this, args);
+ };
+
+ defaultFormat = /(\d{1,4})/g;
+
+ cards = [
+ {
+ type: 'maestro',
+ pattern: /^(5018|5020|5038|6304|6759|676[1-3])/,
+ format: defaultFormat,
+ length: [12, 13, 14, 15, 16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'dinersclub',
+ pattern: /^(36|38|30[0-5])/,
+ format: defaultFormat,
+ length: [14],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'laser',
+ pattern: /^(6706|6771|6709)/,
+ format: defaultFormat,
+ length: [16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'jcb',
+ pattern: /^35/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'unionpay',
+ pattern: /^62/,
+ format: defaultFormat,
+ length: [16, 17, 18, 19],
+ cvcLength: [3],
+ luhn: false
+ }, {
+ type: 'discover',
+ pattern: /^(6011|65|64[4-9]|622)/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'mastercard',
+ pattern: /^5[1-5]/,
+ format: defaultFormat,
+ length: [16],
+ cvcLength: [3],
+ luhn: true
+ }, {
+ type: 'amex',
+ pattern: /^3[47]/,
+ format: /(\d{1,4})(\d{1,6})?(\d{1,5})?/,
+ length: [15],
+ cvcLength: [3, 4],
+ luhn: true
+ }, {
+ type: 'visa',
+ pattern: /^4/,
+ format: defaultFormat,
+ length: [13, 14, 15, 16],
+ cvcLength: [3],
+ luhn: true
+ }
+ ];
+
+ cardFromNumber = function(num) {
+ var card, _i, _len;
+ num = (num + '').replace(/\D/g, '');
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ if (card.pattern.test(num)) {
+ return card;
+ }
+ }
+ };
+
+ cardFromType = function(type) {
+ var card, _i, _len;
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ if (card.type === type) {
+ return card;
+ }
+ }
+ };
+
+ luhnCheck = function(num) {
+ var digit, digits, odd, sum, _i, _len;
+ odd = true;
+ sum = 0;
+ digits = (num + '').split('').reverse();
+ for (_i = 0, _len = digits.length; _i < _len; _i++) {
+ digit = digits[_i];
+ digit = parseInt(digit, 10);
+ if ((odd = !odd)) {
+ digit *= 2;
+ }
+ if (digit > 9) {
+ digit -= 9;
+ }
+ sum += digit;
+ }
+ return sum % 10 === 0;
+ };
+
+ hasTextSelected = function($target) {
+ var _ref;
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== $target.prop('selectionEnd')) {
+ return true;
+ }
+ if (typeof document !== "undefined" && document !== null ? (_ref = document.selection) != null ? typeof _ref.createRange === "function" ? _ref.createRange().text : void 0 : void 0 : void 0) {
+ return true;
+ }
+ return false;
+ };
+
+ reFormatCardNumber = function(e) {
+ var _this = this;
+ return setTimeout(function() {
+ var $target, value;
+ $target = $(e.currentTarget);
+ value = $target.val();
+ value = $.payment.formatCardNumber(value);
+ return $target.val(value);
+ });
+ };
+
+ formatCardNumber = function(e) {
+ var $target, card, digit, length, re, upperLength, value;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ value = $target.val();
+ card = cardFromNumber(value + digit);
+ length = (value.replace(/\D/g, '') + digit).length;
+ upperLength = 16;
+ if (card) {
+ upperLength = card.length[card.length.length - 1];
+ }
+ if (length >= upperLength) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (card && card.type === 'amex') {
+ re = /^(\d{4}|\d{4}\s\d{6})$/;
+ } else {
+ re = /(?:^|\s)(\d{4})$/;
+ }
+ if (re.test(value)) {
+ e.preventDefault();
+ return $target.val(value + ' ' + digit);
+ } else if (re.test(value + digit)) {
+ e.preventDefault();
+ return $target.val(value + digit + ' ');
+ }
+ };
+
+ formatBackCardNumber = function(e) {
+ var $target, value;
+ $target = $(e.currentTarget);
+ value = $target.val();
+ if (e.meta) {
+ return;
+ }
+ if (e.which !== 8) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (/\d\s$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\d\s$/, ''));
+ } else if (/\s\d?$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\s\d?$/, ''));
+ }
+ };
+
+ formatExpiry = function(e) {
+ var $target, digit, val;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val() + digit;
+ if (/^\d$/.test(val) && (val !== '0' && val !== '1')) {
+ e.preventDefault();
+ return $target.val("0" + val + " / ");
+ } else if (/^\d\d$/.test(val)) {
+ e.preventDefault();
+ return $target.val("" + val + " / ");
+ }
+ };
+
+ formatForwardExpiry = function(e) {
+ var $target, digit, val;
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val();
+ if (/^\d\d$/.test(val)) {
+ return $target.val("" + val + " / ");
+ }
+ };
+
+ formatForwardSlash = function(e) {
+ var $target, slash, val;
+ slash = String.fromCharCode(e.which);
+ if (slash !== '/') {
+ return;
+ }
+ $target = $(e.currentTarget);
+ val = $target.val();
+ if (/^\d$/.test(val) && val !== '0') {
+ return $target.val("0" + val + " / ");
+ }
+ };
+
+ formatBackExpiry = function(e) {
+ var $target, value;
+ if (e.meta) {
+ return;
+ }
+ $target = $(e.currentTarget);
+ value = $target.val();
+ if (e.which !== 8) {
+ return;
+ }
+ if (($target.prop('selectionStart') != null) && $target.prop('selectionStart') !== value.length) {
+ return;
+ }
+ if (/\d(\s|\/)+$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\d(\s|\/)*$/, ''));
+ } else if (/\s\/\s?\d?$/.test(value)) {
+ e.preventDefault();
+ return $target.val(value.replace(/\s\/\s?\d?$/, ''));
+ }
+ };
+
+ restrictNumeric = function(e) {
+ var input;
+ if (e.metaKey || e.ctrlKey) {
+ return true;
+ }
+ if (e.which === 32) {
+ return false;
+ }
+ if (e.which === 0) {
+ return true;
+ }
+ if (e.which < 33) {
+ return true;
+ }
+ input = String.fromCharCode(e.which);
+ return !!/[\d\s]/.test(input);
+ };
+
+ restrictCardNumber = function(e) {
+ var $target, card, digit, value;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ if (hasTextSelected($target)) {
+ return;
+ }
+ value = ($target.val() + digit).replace(/\D/g, '');
+ card = cardFromNumber(value);
+ if (card) {
+ return value.length <= card.length[card.length.length - 1];
+ } else {
+ return value.length <= 16;
+ }
+ };
+
+ restrictExpiry = function(e) {
+ var $target, digit, value;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ if (hasTextSelected($target)) {
+ return;
+ }
+ value = $target.val() + digit;
+ value = value.replace(/\D/g, '');
+ if (value.length > 6) {
+ return false;
+ }
+ };
+
+ restrictCVC = function(e) {
+ var $target, digit, val;
+ $target = $(e.currentTarget);
+ digit = String.fromCharCode(e.which);
+ if (!/^\d+$/.test(digit)) {
+ return;
+ }
+ val = $target.val() + digit;
+ return val.length <= 4;
+ };
+
+ setCardType = function(e) {
+ var $target, allTypes, card, cardType, val;
+ $target = $(e.currentTarget);
+ val = $target.val();
+ cardType = $.payment.cardType(val) || 'unknown';
+ if (!$target.hasClass(cardType)) {
+ allTypes = (function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = cards.length; _i < _len; _i++) {
+ card = cards[_i];
+ _results.push(card.type);
+ }
+ return _results;
+ })();
+ $target.removeClass('unknown');
+ $target.removeClass(allTypes.join(' '));
+ $target.addClass(cardType);
+ $target.toggleClass('identified', cardType !== 'unknown');
+ return $target.trigger('payment.cardType', cardType);
+ }
+ };
+
+ $.payment.fn.formatCardCVC = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictCVC);
+ return this;
+ };
+
+ $.payment.fn.formatCardExpiry = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictExpiry);
+ this.on('keypress', formatExpiry);
+ this.on('keypress', formatForwardSlash);
+ this.on('keypress', formatForwardExpiry);
+ this.on('keydown', formatBackExpiry);
+ return this;
+ };
+
+ $.payment.fn.formatCardNumber = function() {
+ this.payment('restrictNumeric');
+ this.on('keypress', restrictCardNumber);
+ this.on('keypress', formatCardNumber);
+ this.on('keydown', formatBackCardNumber);
+ this.on('keyup', setCardType);
+ this.on('paste', reFormatCardNumber);
+ return this;
+ };
+
+ $.payment.fn.restrictNumeric = function() {
+ this.on('keypress', restrictNumeric);
+ return this;
+ };
+
+ $.payment.fn.cardExpiryVal = function() {
+ return $.payment.cardExpiryVal($(this).val());
+ };
+
+ $.payment.cardExpiryVal = function(value) {
+ var month, prefix, year, _ref;
+ value = value.replace(/\s/g, '');
+ _ref = value.split('/', 2), month = _ref[0], year = _ref[1];
+ if ((year != null ? year.length : void 0) === 2 && /^\d+$/.test(year)) {
+ prefix = (new Date).getFullYear();
+ prefix = prefix.toString().slice(0, 2);
+ year = prefix + year;
+ }
+ month = parseInt(month, 10);
+ year = parseInt(year, 10);
+ return {
+ month: month,
+ year: year
+ };
+ };
+
+ $.payment.validateCardNumber = function(num) {
+ var card, _ref;
+ num = (num + '').replace(/\s+|-/g, '');
+ if (!/^\d+$/.test(num)) {
+ return false;
+ }
+ card = cardFromNumber(num);
+ if (!card) {
+ return false;
+ }
+ return (_ref = num.length, __indexOf.call(card.length, _ref) >= 0) && (card.luhn === false || luhnCheck(num));
+ };
+
+ $.payment.validateCardExpiry = function(month, year) {
+ var currentTime, expiry, prefix, _ref;
+ if (typeof month === 'object' && 'month' in month) {
+ _ref = month, month = _ref.month, year = _ref.year;
+ }
+ if (!(month && year)) {
+ return false;
+ }
+ month = $.trim(month);
+ year = $.trim(year);
+ if (!/^\d+$/.test(month)) {
+ return false;
+ }
+ if (!/^\d+$/.test(year)) {
+ return false;
+ }
+ if (!(parseInt(month, 10) <= 12)) {
+ return false;
+ }
+ if (year.length === 2) {
+ prefix = (new Date).getFullYear();
+ prefix = prefix.toString().slice(0, 2);
+ year = prefix + year;
+ }
+ expiry = new Date(year, month);
+ currentTime = new Date;
+ expiry.setMonth(expiry.getMonth() - 1);
+ expiry.setMonth(expiry.getMonth() + 1, 1);
+ return expiry > currentTime;
+ };
+
+ $.payment.validateCardCVC = function(cvc, type) {
+ var _ref, _ref1;
+ cvc = $.trim(cvc);
+ if (!/^\d+$/.test(cvc)) {
+ return false;
+ }
+ if (type) {
+ return _ref = cvc.length, __indexOf.call((_ref1 = cardFromType(type)) != null ? _ref1.cvcLength : void 0, _ref) >= 0;
+ } else {
+ return cvc.length >= 3 && cvc.length <= 4;
+ }
+ };
+
+ $.payment.cardType = function(num) {
+ var _ref;
+ if (!num) {
+ return null;
+ }
+ return ((_ref = cardFromNumber(num)) != null ? _ref.type : void 0) || null;
+ };
+
+ $.payment.formatCardNumber = function(num) {
+ var card, groups, upperLength, _ref;
+ card = cardFromNumber(num);
+ if (!card) {
+ return num;
+ }
+ upperLength = card.length[card.length.length - 1];
+ num = num.replace(/\D/g, '');
+ num = num.slice(0, +upperLength + 1 || 9e9);
+ if (card.format.global) {
+ return (_ref = num.match(card.format)) != null ? _ref.join(' ') : void 0;
+ } else {
+ groups = card.format.exec(num);
+ if (groups != null) {
+ groups.shift();
+ }
+ return groups != null ? groups.join(' ') : void 0;
+ }
+ };
+
+}).call(this);
\ No newline at end of file
diff --git a/core/vendor/assets/javascripts/jquery.tokeninput.js b/core/vendor/assets/javascripts/jquery.tokeninput.js
deleted file mode 100755
index 87641a57a54..00000000000
--- a/core/vendor/assets/javascripts/jquery.tokeninput.js
+++ /dev/null
@@ -1,860 +0,0 @@
-/*
- * jQuery Plugin: Tokenizing Autocomplete Text Entry
- * Version 1.6.0
- *
- * Copyright (c) 2009 James Smith (http://loopj.com)
- * Licensed jointly under the GPL and MIT licenses,
- * choose which one suits your project best!
- *
- */
-
-(function ($) {
-// Default settings
-var DEFAULT_SETTINGS = {
- // Search settings
- method: "GET",
- contentType: "json",
- queryParam: "q",
- searchDelay: 300,
- minChars: 1,
- propertyToSearch: "name",
- jsonContainer: null,
-
- // Display settings
- hintText: "Type in a search term",
- noResultsText: "No results",
- searchingText: "Searching...",
- deleteText: "×",
- animateDropdown: true,
-
- // Tokenization settings
- tokenLimit: null,
- tokenDelimiter: ",",
- preventDuplicates: false,
-
- // Output settings
- tokenValue: "id",
-
- // Prepopulation settings
- prePopulate: null,
- processPrePopulate: false,
-
- // Manipulation settings
- idPrefix: "token-input-",
-
- // Formatters
- resultsFormatter: function(item){ return "
" + item[this.propertyToSearch]+ "
" },
- tokenFormatter: function(item) { return "
" + item[this.propertyToSearch] + "
" },
-
- // Callbacks
- onResult: null,
- onAdd: null,
- onDelete: null,
- onReady: null
-};
-
-// Default classes to use when theming
-var DEFAULT_CLASSES = {
- tokenList: "token-input-list",
- token: "token-input-token",
- tokenDelete: "token-input-delete-token",
- selectedToken: "token-input-selected-token",
- highlightedToken: "token-input-highlighted-token",
- dropdown: "token-input-dropdown",
- dropdownItem: "token-input-dropdown-item",
- dropdownItem2: "token-input-dropdown-item2",
- selectedDropdownItem: "token-input-selected-dropdown-item",
- inputToken: "token-input-input-token"
-};
-
-// Input box position "enum"
-var POSITION = {
- BEFORE: 0,
- AFTER: 1,
- END: 2
-};
-
-// Keys "enum"
-var KEY = {
- BACKSPACE: 8,
- TAB: 9,
- ENTER: 13,
- ESCAPE: 27,
- SPACE: 32,
- PAGE_UP: 33,
- PAGE_DOWN: 34,
- END: 35,
- HOME: 36,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- NUMPAD_ENTER: 108,
- COMMA: 188
-};
-
-// Additional public (exposed) methods
-var methods = {
- init: function(url_or_data_or_function, options) {
- var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
-
- return this.each(function () {
- $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
- });
- },
- clear: function() {
- this.data("tokenInputObject").clear();
- return this;
- },
- add: function(item) {
- this.data("tokenInputObject").add(item);
- return this;
- },
- remove: function(item) {
- this.data("tokenInputObject").remove(item);
- return this;
- },
- get: function() {
- return this.data("tokenInputObject").getTokens();
- }
-}
-
-// Expose the .tokenInput function to jQuery as a plugin
-$.fn.tokenInput = function (method) {
- // Method calling and initialization logic
- if(methods[method]) {
- return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
- } else {
- return methods.init.apply(this, arguments);
- }
-};
-
-// TokenList class for each input
-$.TokenList = function (input, url_or_data, settings) {
- //
- // Initialization
- //
-
- // Configure the data source
- if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
- // Set the url to query against
- settings.url = url_or_data;
-
- // If the URL is a function, evaluate it here to do our initalization work
- var url = computeURL();
-
- // Make a smart guess about cross-domain if it wasn't explicitly specified
- if(settings.crossDomain === undefined) {
- if(url.indexOf("://") === -1) {
- settings.crossDomain = false;
- } else {
- settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
- }
- }
- } else if(typeof(url_or_data) === "object") {
- // Set the local data to search through
- settings.local_data = url_or_data;
- }
-
- // Build class names
- if(settings.classes) {
- // Use custom class names
- settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
- } else if(settings.theme) {
- // Use theme-suffixed default class names
- settings.classes = {};
- $.each(DEFAULT_CLASSES, function(key, value) {
- settings.classes[key] = value + "-" + settings.theme;
- });
- } else {
- settings.classes = DEFAULT_CLASSES;
- }
-
-
- // Save the tokens
- var saved_tokens = [];
-
- // Keep track of the number of tokens in the list
- var token_count = 0;
-
- // Basic cache to save on db hits
- var cache = new $.TokenList.Cache();
-
- // Keep track of the timeout, old vals
- var timeout;
- var input_val;
-
- // Create a new text input an attach keyup events
- var input_box = $("")
- .css({
- outline: "none"
- })
- .attr("id", settings.idPrefix + input.id)
- .focus(function () {
- if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
- show_dropdown_hint();
- }
- })
- .blur(function () {
- hide_dropdown();
- $(this).val("");
- })
- .bind("keyup keydown blur update", resize_input)
- .keydown(function (event) {
- var previous_token;
- var next_token;
-
- switch(event.keyCode) {
- case KEY.LEFT:
- case KEY.RIGHT:
- case KEY.UP:
- case KEY.DOWN:
- if(!$(this).val()) {
- previous_token = input_token.prev();
- next_token = input_token.next();
-
- if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
- // Check if there is a previous/next token and it is selected
- if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
- deselect_token($(selected_token), POSITION.BEFORE);
- } else {
- deselect_token($(selected_token), POSITION.AFTER);
- }
- } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
- // We are moving left, select the previous token if it exists
- select_token($(previous_token.get(0)));
- } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
- // We are moving right, select the next token if it exists
- select_token($(next_token.get(0)));
- }
- } else {
- var dropdown_item = null;
-
- if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
- dropdown_item = $(selected_dropdown_item).next();
- } else {
- dropdown_item = $(selected_dropdown_item).prev();
- }
-
- if(dropdown_item.length) {
- select_dropdown_item(dropdown_item);
- }
- return false;
- }
- break;
-
- case KEY.BACKSPACE:
- previous_token = input_token.prev();
-
- if(!$(this).val().length) {
- if(selected_token) {
- delete_token($(selected_token));
- hidden_input.change();
- } else if(previous_token.length) {
- select_token($(previous_token.get(0)));
- }
-
- return false;
- } else if($(this).val().length === 1) {
- hide_dropdown();
- } else {
- // set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search();}, 5);
- }
- break;
-
- case KEY.TAB:
- case KEY.ENTER:
- case KEY.NUMPAD_ENTER:
- case KEY.COMMA:
- if(selected_dropdown_item) {
- add_token($(selected_dropdown_item).data("tokeninput"));
- hidden_input.change();
- return false;
- }
- break;
-
- case KEY.ESCAPE:
- hide_dropdown();
- return true;
-
- default:
- if(String.fromCharCode(event.which)) {
- // set a timeout just long enough to let this function finish.
- setTimeout(function(){do_search();}, 5);
- }
- break;
- }
- });
-
- // Keep a reference to the original input box
- var hidden_input = $(input)
- .hide()
- .val("")
- .focus(function () {
- input_box.focus();
- })
- .blur(function () {
- input_box.blur();
- });
-
- // Keep a reference to the selected token and dropdown item
- var selected_token = null;
- var selected_token_index = 0;
- var selected_dropdown_item = null;
-
- // The list to store the token items in
- var token_list = $("
")
- .addClass(settings.classes.tokenList)
- .click(function (event) {
- var li = $(event.target).closest("li");
- if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
- toggle_select_token(li);
- } else {
- // Deselect selected token
- if(selected_token) {
- deselect_token($(selected_token), POSITION.END);
- }
-
- // Focus input box
- input_box.focus();
- }
- })
- .mouseover(function (event) {
- var li = $(event.target).closest("li");
- if(li && selected_token !== this) {
- li.addClass(settings.classes.highlightedToken);
- }
- })
- .mouseout(function (event) {
- var li = $(event.target).closest("li");
- if(li && selected_token !== this) {
- li.removeClass(settings.classes.highlightedToken);
- }
- })
- .insertBefore(hidden_input);
-
- // The token holding the input box
- var input_token = $("")
- .addClass(settings.classes.inputToken)
- .appendTo(token_list)
- .append(input_box);
-
- // The list to store the dropdown items in
- var dropdown = $("
")
- .addClass(settings.classes.dropdown)
- .appendTo("body")
- .hide();
-
- // Magic element to help us resize the text input
- var input_resizer = $("")
- .insertAfter(input_box)
- .css({
- position: "absolute",
- top: -9999,
- left: -9999,
- width: "auto",
- fontSize: input_box.css("fontSize"),
- fontFamily: input_box.css("fontFamily"),
- fontWeight: input_box.css("fontWeight"),
- letterSpacing: input_box.css("letterSpacing"),
- whiteSpace: "nowrap"
- });
-
- // Pre-populate list if items exist
- hidden_input.val("");
- var li_data = settings.prePopulate || hidden_input.data("pre");
- if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
- li_data = settings.onResult.call(hidden_input, li_data);
- }
- if(li_data && li_data.length) {
- $.each(li_data, function (index, value) {
- insert_token(value);
- checkTokenLimit();
- });
- }
-
- // Initialization is done
- if($.isFunction(settings.onReady)) {
- settings.onReady.call();
- }
-
- //
- // Public functions
- //
-
- this.clear = function() {
- token_list.children("li").each(function() {
- if ($(this).children("input").length === 0) {
- delete_token($(this));
- }
- });
- }
-
- this.add = function(item) {
- add_token(item);
- }
-
- this.remove = function(item) {
- token_list.children("li").each(function() {
- if ($(this).children("input").length === 0) {
- var currToken = $(this).data("tokeninput");
- var match = true;
- for (var prop in item) {
- if (item[prop] !== currToken[prop]) {
- match = false;
- break;
- }
- }
- if (match) {
- delete_token($(this));
- }
- }
- });
- }
-
- this.getTokens = function() {
- return saved_tokens;
- }
-
- //
- // Private functions
- //
-
- function checkTokenLimit() {
- if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
- input_box.hide();
- hide_dropdown();
- return;
- }
- }
-
- function resize_input() {
- if(input_val === (input_val = input_box.val())) {return;}
-
- // Enter new content into resizer and resize input accordingly
- var escaped = input_val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>');
- input_resizer.html(escaped);
- input_box.width(input_resizer.width() + 30);
- }
-
- function is_printable_character(keycode) {
- return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
- (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
- (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
- (keycode >= 219 && keycode <= 222)); // ( \ ) '
- }
-
- // Inner function to a token to the list
- function insert_token(item) {
- var this_token = settings.tokenFormatter(item);
- this_token = $(this_token)
- .addClass(settings.classes.token)
- .insertBefore(input_token);
-
- // The 'delete token' button
- $("" + settings.deleteText + "")
- .addClass(settings.classes.tokenDelete)
- .appendTo(this_token)
- .click(function () {
- delete_token($(this).parent());
- hidden_input.change();
- return false;
- });
-
- // Store data on the token
- var token_data = {"id": item.id};
- token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
- $.data(this_token.get(0), "tokeninput", item);
-
- // Save this token for duplicate checking
- saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
- selected_token_index++;
-
- // Update the hidden input
- update_hidden_input(saved_tokens, hidden_input);
-
- token_count += 1;
-
- // Check the token limit
- if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
- input_box.hide();
- hide_dropdown();
- }
-
- return this_token;
- }
-
- // Add a token to the token list based on user input
- function add_token (item) {
- var callback = settings.onAdd;
-
- // See if the token already exists and select it if we don't want duplicates
- if(token_count > 0 && settings.preventDuplicates) {
- var found_existing_token = null;
- token_list.children().each(function () {
- var existing_token = $(this);
- var existing_data = $.data(existing_token.get(0), "tokeninput");
- if(existing_data && existing_data.id === item.id) {
- found_existing_token = existing_token;
- return false;
- }
- });
-
- if(found_existing_token) {
- select_token(found_existing_token);
- input_token.insertAfter(found_existing_token);
- input_box.focus();
- return;
- }
- }
-
- // Insert the new tokens
- if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
- insert_token(item);
- checkTokenLimit();
- }
-
- // Clear input box
- input_box.val("");
-
- // Don't show the help dropdown, they've got the idea
- hide_dropdown();
-
- // Execute the onAdd callback if defined
- if($.isFunction(callback)) {
- callback.call(hidden_input,item);
- }
- }
-
- // Select a token in the token list
- function select_token (token) {
- token.addClass(settings.classes.selectedToken);
- selected_token = token.get(0);
-
- // Hide input box
- input_box.val("");
-
- // Hide dropdown if it is visible (eg if we clicked to select token)
- hide_dropdown();
- }
-
- // Deselect a token in the token list
- function deselect_token (token, position) {
- token.removeClass(settings.classes.selectedToken);
- selected_token = null;
-
- if(position === POSITION.BEFORE) {
- input_token.insertBefore(token);
- selected_token_index--;
- } else if(position === POSITION.AFTER) {
- input_token.insertAfter(token);
- selected_token_index++;
- } else {
- input_token.appendTo(token_list);
- selected_token_index = token_count;
- }
-
- // Show the input box and give it focus again
- input_box.focus();
- }
-
- // Toggle selection of a token in the token list
- function toggle_select_token(token) {
- var previous_selected_token = selected_token;
-
- if(selected_token) {
- deselect_token($(selected_token), POSITION.END);
- }
-
- if(previous_selected_token === token.get(0)) {
- deselect_token(token, POSITION.END);
- } else {
- select_token(token);
- }
- }
-
- // Delete a token from the token list
- function delete_token (token) {
- // Remove the id from the saved list
- var token_data = $.data(token.get(0), "tokeninput");
- var callback = settings.onDelete;
-
- var index = token.prevAll().length;
- if(index > selected_token_index) index--;
-
- // Delete the token
- token.remove();
- selected_token = null;
-
- // Show the input box and give it focus again
- input_box.focus();
-
- // Remove this token from the saved list
- saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
- if(index < selected_token_index) selected_token_index--;
-
- // Update the hidden input
- update_hidden_input(saved_tokens, hidden_input);
-
- token_count -= 1;
-
- if(settings.tokenLimit !== null) {
- input_box
- .show()
- .val("")
- .focus();
- }
-
- // Execute the onDelete callback if defined
- if($.isFunction(callback)) {
- callback.call(hidden_input,token_data);
- }
- }
-
- // Update the hidden input box value
- function update_hidden_input(saved_tokens, hidden_input) {
- var token_values = $.map(saved_tokens, function (el) {
- return el[settings.tokenValue];
- });
- hidden_input.val(token_values.join(settings.tokenDelimiter));
-
- }
-
- // Hide and clear the results dropdown
- function hide_dropdown () {
- dropdown.hide().empty();
- selected_dropdown_item = null;
- }
-
- function show_dropdown() {
- dropdown
- .css({
- position: "absolute",
- top: $(token_list).offset().top + $(token_list).outerHeight(),
- left: $(token_list).offset().left,
- zindex: 999
- })
- .show();
- }
-
- function show_dropdown_searching () {
- if(settings.searchingText) {
- dropdown.html("
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+ <% if order.adjustments.nonzero.eligible.exists? %>
+
+ <% order.adjustments.nonzero.eligible.each do |adjustment| %>
+ <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
+
+
<%= adjustment.label %>:
+
<%= adjustment.display_amount.to_html %>
+
+ <% end %>
+
+ <% end %>
+
+
+
<%= Spree.t(:order_total) %>:
+
<%= order.display_total.to_html %>
+
+
+
diff --git a/frontend/app/views/spree/checkout/edit.html.erb b/frontend/app/views/spree/checkout/edit.html.erb
new file mode 100644
index 00000000000..eb52d227cd4
--- /dev/null
+++ b/frontend/app/views/spree/checkout/edit.html.erb
@@ -0,0 +1,37 @@
+
+
+<%= hidden_field_tag "#{param_prefix}[cc_type]", '', :id => "cc_type", :class => 'ccType' %>
diff --git a/core/app/views/spree/content/cvv.html.erb b/frontend/app/views/spree/content/cvv.html.erb
similarity index 96%
rename from core/app/views/spree/content/cvv.html.erb
rename to frontend/app/views/spree/content/cvv.html.erb
index 09c26fd2272..bd063cc8767 100644
--- a/core/app/views/spree/content/cvv.html.erb
+++ b/frontend/app/views/spree/content/cvv.html.erb
@@ -1,5 +1,5 @@
-
<%= t(:what_is_a_cvv) %>
+
<%= Spree.t(:what_is_a_cvv) %>
For Visa, MasterCard, and Discover cards, the card code is the last 3 digit number located on the back of your card on or above your signature line. For an American Express card, it is the 4 digits on the FRONT above the end of your card number.
To help reduce fraud in the card-not-present environment, credit card companies have introduced a card code program. Visa calls this code Card Verification Value (CVV); MasterCard calls it Card Validation Code (CVC); Discover calls it Card ID (CID). The card code is a three- or four- digit security code that is printed on the back of cards. The number typically appears at the end of the signature panel.
Visa
diff --git a/frontend/app/views/spree/content/test.html.erb b/frontend/app/views/spree/content/test.html.erb
new file mode 100644
index 00000000000..1588c8ca646
--- /dev/null
+++ b/frontend/app/views/spree/content/test.html.erb
@@ -0,0 +1 @@
+Nothing to see here. Move along now.
\ No newline at end of file
diff --git a/frontend/app/views/spree/home/index.html.erb b/frontend/app/views/spree/home/index.html.erb
new file mode 100644
index 00000000000..a79dcd918ab
--- /dev/null
+++ b/frontend/app/views/spree/home/index.html.erb
@@ -0,0 +1,12 @@
+<% content_for :sidebar do %>
+