diff --git a/app/assets/images/collapse.png b/app/assets/images/collapse.png
new file mode 100644
index 0000000000..1ac2122d8a
Binary files /dev/null and b/app/assets/images/collapse.png differ
diff --git a/app/assets/images/expand.png b/app/assets/images/expand.png
new file mode 100644
index 0000000000..aa3fc20f39
Binary files /dev/null and b/app/assets/images/expand.png differ
diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee
index 320293f364..c6028f21aa 100644
--- a/app/assets/javascripts/admin/bulk_product_update.js.coffee
+++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee
@@ -227,14 +227,19 @@ productEditModule.controller "AdminProductEditCtrl", [
$scope.updateOnHand = (product) ->
- product.on_hand = $scope.onHand(product)
+ on_demand_variants = []
+ if product.variants
+ on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
+
+ unless product.on_demand || on_demand_variants.length > 0
+ product.on_hand = $scope.onHand(product)
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
- angular.forEach product.variants, (variant) ->
- onHand = parseInt(onHand) + parseInt((if variant.on_hand > 0 then variant.on_hand else 0))
+ for id, variant of product.variants
+ onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
@@ -311,6 +316,10 @@ productEditModule.controller "AdminProductEditCtrl", [
Object.keys(product.variants).length > 0
+ $scope.hasOnDemandVariants = (product) ->
+ (variant for id, variant of product.variants when variant.on_demand).length > 0
+
+
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
@@ -336,12 +345,10 @@ productEditModule.controller "AdminProductEditCtrl", [
$scope.submitProducts = ->
- # Pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server,
- # then pack $scope.products, so they will match the list returned from the server
- angular.forEach $scope.dirtyProducts, (product) ->
- $scope.packProduct product
- angular.forEach $scope.products, (product) ->
- $scope.packProduct product
+ # Pack pack $scope.products, so they will match the list returned from the server,
+ # then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
+ $scope.packProduct product for id, product of $scope.products
+ $scope.packProduct product for id, product of $scope.dirtyProducts
productsToSubmit = filterSubmitProducts($scope.dirtyProducts)
if productsToSubmit.length > 0
diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee
index 96486a4a69..32580e15ee 100644
--- a/app/assets/javascripts/darkswarm/all.js.coffee
+++ b/app/assets/javascripts/darkswarm/all.js.coffee
@@ -4,6 +4,7 @@
#= require spin
#= require ../shared/angular
#= require ../shared/angular-resource
+#= require ../shared/jquery.timeago
#= require foundation
#= require ./shop
#= require_tree .
diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
index 8f21390a7f..f1fdb224b0 100644
--- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee
@@ -1,13 +1,4 @@
-angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product) ->
+angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) ->
$scope.data = Product.data
+ $scope.order_cycle = OrderCycle.order_cycle
Product.update()
-
-
- #$scope.order_cycle = OrderCycle.order_cycle
- #$scope.updateProducts = ->
- #$scope.products = Product.all()
- #$scope.$watch "order_cycle.order_cycle_id", $scope.updateProducts
- #$scope.updateProducts()
-
-
-
diff --git a/app/assets/javascripts/darkswarm/shop.js.coffee b/app/assets/javascripts/darkswarm/shop.js.coffee
index 85c94211e6..1163859bc9 100644
--- a/app/assets/javascripts/darkswarm/shop.js.coffee
+++ b/app/assets/javascripts/darkswarm/shop.js.coffee
@@ -1,7 +1,6 @@
window.Shop = angular.module("Shop", ["ngResource", "filters"]).config ($httpProvider) ->
$httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
-#angular.module('Shop', ['filters'])
angular.module("filters", []).filter "truncate", ->
(text, length, end) ->
@@ -12,3 +11,8 @@ angular.module("filters", []).filter "truncate", ->
text
else
String(text).substring(0, length - end.length) + end
+
+$.timeago.settings.allowFuture = true;
+angular.module("filters").filter "date_in_words", ->
+ (date) ->
+ $.timeago(date)
diff --git a/app/assets/javascripts/shared/jquery.timeago.js b/app/assets/javascripts/shared/jquery.timeago.js
new file mode 100644
index 0000000000..cecfaeb87d
--- /dev/null
+++ b/app/assets/javascripts/shared/jquery.timeago.js
@@ -0,0 +1,202 @@
+/**
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * @name timeago
+ * @version 1.3.1
+ * @requires jQuery v1.2.3+
+ * @author Ryan McGeary
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
+ */
+
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+ $.timeago = function(timestamp) {
+ if (timestamp instanceof Date) {
+ return inWords(timestamp);
+ } else if (typeof timestamp === "string") {
+ return inWords($.timeago.parse(timestamp));
+ } else if (typeof timestamp === "number") {
+ return inWords(new Date(timestamp));
+ } else {
+ return inWords($.timeago.datetime(timestamp));
+ }
+ };
+ var $t = $.timeago;
+
+ $.extend($.timeago, {
+ settings: {
+ refreshMillis: 60000,
+ allowFuture: false,
+ localeTitle: false,
+ cutoff: 0,
+ strings: {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "ago",
+ suffixFromNow: "from now",
+ seconds: "less than a minute",
+ minute: "about a minute",
+ minutes: "%d minutes",
+ hour: "about an hour",
+ hours: "about %d hours",
+ day: "a day",
+ days: "%d days",
+ month: "about a month",
+ months: "%d months",
+ year: "about a year",
+ years: "%d years",
+ wordSeparator: " ",
+ numbers: []
+ }
+ },
+ inWords: function(distanceMillis) {
+ var $l = this.settings.strings;
+ var prefix = $l.prefixAgo;
+ var suffix = $l.suffixAgo;
+ if (this.settings.allowFuture) {
+ if (distanceMillis < 0) {
+ prefix = $l.prefixFromNow;
+ suffix = $l.suffixFromNow;
+ }
+ }
+
+ var seconds = Math.abs(distanceMillis) / 1000;
+ var minutes = seconds / 60;
+ var hours = minutes / 60;
+ var days = hours / 24;
+ var years = days / 365;
+
+ function substitute(stringOrFunction, number) {
+ var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+ var value = ($l.numbers && $l.numbers[number]) || number;
+ return string.replace(/%d/i, value);
+ }
+
+ var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+ seconds < 90 && substitute($l.minute, 1) ||
+ minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+ minutes < 90 && substitute($l.hour, 1) ||
+ hours < 24 && substitute($l.hours, Math.round(hours)) ||
+ hours < 42 && substitute($l.day, 1) ||
+ days < 30 && substitute($l.days, Math.round(days)) ||
+ days < 45 && substitute($l.month, 1) ||
+ days < 365 && substitute($l.months, Math.round(days / 30)) ||
+ years < 1.5 && substitute($l.year, 1) ||
+ substitute($l.years, Math.round(years));
+
+ var separator = $l.wordSeparator || "";
+ if ($l.wordSeparator === undefined) { separator = " "; }
+ return $.trim([prefix, words, suffix].join(separator));
+ },
+ parse: function(iso8601) {
+ var s = $.trim(iso8601);
+ s = s.replace(/\.\d+/,""); // remove milliseconds
+ s = s.replace(/-/,"/").replace(/-/,"/");
+ s = s.replace(/T/," ").replace(/Z/," UTC");
+ s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+ s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900
+ return new Date(s);
+ },
+ datetime: function(elem) {
+ var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
+ return $t.parse(iso8601);
+ },
+ isTime: function(elem) {
+ // jQuery's `is()` doesn't play well with HTML5 in IE
+ return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+ }
+ });
+
+ // functions that can be called via $(el).timeago('action')
+ // init is default when no action is given
+ // functions are called with context of a single element
+ var functions = {
+ init: function(){
+ var refresh_el = $.proxy(refresh, this);
+ refresh_el();
+ var $s = $t.settings;
+ if ($s.refreshMillis > 0) {
+ this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis);
+ }
+ },
+ update: function(time){
+ var parsedTime = $t.parse(time);
+ $(this).data('timeago', { datetime: parsedTime });
+ if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString());
+ refresh.apply(this);
+ },
+ updateFromDOM: function(){
+ $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) });
+ refresh.apply(this);
+ },
+ dispose: function () {
+ if (this._timeagoInterval) {
+ window.clearInterval(this._timeagoInterval);
+ this._timeagoInterval = null;
+ }
+ }
+ };
+
+ $.fn.timeago = function(action, options) {
+ var fn = action ? functions[action] : functions.init;
+ if(!fn){
+ throw new Error("Unknown function name '"+ action +"' for timeago");
+ }
+ // each over objects here and call the requested function
+ this.each(function(){
+ fn.call(this, options);
+ });
+ return this;
+ };
+
+ function refresh() {
+ var data = prepareData(this);
+ var $s = $t.settings;
+
+ if (!isNaN(data.datetime)) {
+ if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) {
+ $(this).text(inWords(data.datetime));
+ }
+ }
+ return this;
+ }
+
+ function prepareData(element) {
+ element = $(element);
+ if (!element.data("timeago")) {
+ element.data("timeago", { datetime: $t.datetime(element) });
+ var text = $.trim(element.text());
+ if ($t.settings.localeTitle) {
+ element.attr("title", element.data('timeago').datetime.toLocaleString());
+ } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
+ element.attr("title", text);
+ }
+ }
+ return element.data("timeago");
+ }
+
+ function inWords(date) {
+ return $t.inWords(distance(date));
+ }
+
+ function distance(date) {
+ return (new Date().getTime() - date.getTime());
+ }
+
+ // fix for IE6 suckage
+ document.createElement("abbr");
+ document.createElement("time");
+}));
diff --git a/app/assets/stylesheets/darkswarm/all.scss b/app/assets/stylesheets/darkswarm/all.scss
index b5d1542d11..aaec952184 100644
--- a/app/assets/stylesheets/darkswarm/all.scss
+++ b/app/assets/stylesheets/darkswarm/all.scss
@@ -4,7 +4,8 @@
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require_self
-
*= require foundation
*= require_tree .
*/
+
+
diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass
index 0d6137fb3a..334c3ee6ef 100644
--- a/app/assets/stylesheets/darkswarm/shop.css.sass
+++ b/app/assets/stylesheets/darkswarm/shop.css.sass
@@ -16,7 +16,7 @@ shop
distributor.details
box-sizing: border-box
display: block
- height: 150px
+ min-height: 150px
padding: 40px 0px 0px
select
width: 200px
@@ -29,11 +29,18 @@ shop
location
font-family: "AvenirBla_IE", "AvenirBla"
padding-right: 16px
- ordercycle
+
+ #distributor_title
+ float: left
display: block
- position: absolute
- right: 0px
- top: 40px
+ min-width: 350px
+
+ ordercycle
+ @media all and (max-width: 768px)
+ float: left
+ padding-bottom: 12px
+ display: block
+ float: right
form.custom
width: 400px
text-align: right
@@ -65,6 +72,10 @@ shop
& > .content
background: none
border: none
+ img
+ margin: 0px 0px 0px 40px
+ p
+ max-width: 555px
& > .title, &.active > .title
text-transform: uppercase
line-height: 50px
@@ -78,11 +89,18 @@ shop
display: block
padding-top: 36px
table
+ table-layout: fixed
width: 100%
border-collapse: collapse
border: none
th
line-height: 50px
+ &.name
+ width: 265px
+ &.notes
+ width: 280px
+ &.variant
+ width: 150px
.notes
max-width: 300px
td, th
@@ -92,12 +110,12 @@ shop
border-right: 0px
td
padding: 20px 0px
+ &.name img
+ float: left
+ margin-right: 20px
input[type=number]
width: 60px
margin: 0px
-
-
-
display: block
float: right
padding-top: 14px
diff --git a/app/assets/stylesheets/darkswarm/tables.css.sass b/app/assets/stylesheets/darkswarm/tables.css.sass
new file mode 100644
index 0000000000..25c089e22d
--- /dev/null
+++ b/app/assets/stylesheets/darkswarm/tables.css.sass
@@ -0,0 +1,6 @@
+table thead tr, table tbody tr
+ th, td
+ box-sizing: border-box
+ padding-left: 12px
+ padding-right: 12px
+ overflow: hidden
diff --git a/app/assets/stylesheets/darkswarm/typography.css.sass b/app/assets/stylesheets/darkswarm/typography.css.sass
index 0bf0b39e44..a36fbbd828 100644
--- a/app/assets/stylesheets/darkswarm/typography.css.sass
+++ b/app/assets/stylesheets/darkswarm/typography.css.sass
@@ -22,6 +22,10 @@ h1, h2, h3, h4, h5, h6, .avenir
font-family: "AvenirBla_IE", "AvenirBla"
padding: 0px
+
+.light-grey
+ color: #666666
+
strong.avenir
font-weight: normal // Avenir is basically bold anyway
diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb
index dde9af99f4..ca002fe08a 100644
--- a/app/controllers/shop_controller.rb
+++ b/app/controllers/shop_controller.rb
@@ -10,7 +10,9 @@ class ShopController < BaseController
end
def products
- unless @products = current_order_cycle.andand.products_distributed_by(@distributor)
+ unless @products = current_order_cycle.andand
+ .products_distributed_by(@distributor).andand
+ .select(&:has_stock?)
render json: "", status: 404
end
end
diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb
index 83b599b04c..e69d1489b4 100644
--- a/app/models/enterprise.rb
+++ b/app/models/enterprise.rb
@@ -18,6 +18,8 @@ class Enterprise < ActiveRecord::Base
delegate :latitude, :longitude, :city, :state_name, :to => :address
accepts_nested_attributes_for :address
+ has_attached_file :logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
+ has_attached_file :promo_image, :styles => { :large => "570x380>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
validates_presence_of :name
validates_presence_of :address
@@ -41,6 +43,7 @@ class Enterprise < ActiveRecord::Base
.uniq
}
+
scope :with_distributed_products_outer,
joins('LEFT OUTER JOIN product_distributions ON product_distributions.distributor_id = enterprises.id').
joins('LEFT OUTER JOIN spree_products ON spree_products.id = product_distributions.product_id')
diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb
index 1377ba97ba..8d54edc189 100644
--- a/app/models/spree/product_decorator.rb
+++ b/app/models/spree/product_decorator.rb
@@ -92,6 +92,11 @@ Spree::Product.class_eval do
def product_distribution_for(distributor)
self.product_distributions.find_by_distributor_id(distributor)
end
+
+ # overriding to check self.on_demand as well
+ def has_stock?
+ has_variants? ? variants.any?(&:in_stock?) : (on_demand || master.in_stock?)
+ end
# Build a product distribution for each distributor
def build_product_distributions_for_user user
diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb
index f430970ddc..e3850d5c78 100644
--- a/app/models/spree/variant_decorator.rb
+++ b/app/models/spree/variant_decorator.rb
@@ -12,6 +12,15 @@ Spree::Variant.class_eval do
after_save :update_units
+ # Copied and modified from Spree::Variant
+ def options_text
+ values = self.option_values.joins(:option_type).order("#{Spree::OptionType.table_name}.position asc")
+
+ values.map! &:presentation # This line changed
+
+ values.to_sentence({ :words_connector => ", ", :two_words_connector => ", " })
+ end
+
def delete_unit_option_values
ovs = self.option_values.where(option_type_id: Spree::Product.all_variant_unit_option_types)
self.option_values.destroy ovs
@@ -75,7 +84,7 @@ Spree::Variant.class_eval do
# Find the largest available unit where unit_value comes to >= 1 when expressed in it.
# If there is none available where this is true, use the smallest available unit.
unit = units[self.product.variant_unit].select { |scale, unit_name|
- unit_value / scale > 1
+ unit_value / scale >= 1
}.to_a.last
unit = units[self.product.variant_unit].first if unit.nil?
diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml
index 1d4194533f..46e7494355 100644
--- a/app/views/admin/enterprises/_form.html.haml
+++ b/app/views/admin/enterprises/_form.html.haml
@@ -41,6 +41,16 @@
%tr{"data-hook" => "acn"}
%td ACN:
%td= f.text_field :acn
+ %tr{"data-hook" => "logo"}
+ %td Logo:
+ %td
+ = f.file_field :logo
+ = image_tag @object.logo.url
+ %tr{"data-hook" => "promo"}
+ %td Promo Image:
+ %td
+ = f.file_field :promo_image
+ = image_tag @object.promo_image.url
%fieldset
%legend Address
%table
diff --git a/app/views/shared/_menu.html.haml b/app/views/shared/_menu.html.haml
index 0531afcdcb..65973942b3 100644
--- a/app/views/shared/_menu.html.haml
+++ b/app/views/shared/_menu.html.haml
@@ -1,11 +1,6 @@
%nav.top-bar
%section.top-bar-section
%ul.left
- %li= link_to image_tag("ofn_logo_small.png"), new_landing_page_path
+ %li= link_to image_tag("ofn_logo_small.png"), root_path
%li.divider
= render partial: "shared/login"
-
- %ul.right
- %li= link_to "Distributors", "#", :data => { "reveal-id" => "become-distributor" }
- %li.divider
- %li= link_to "Farmers", "#", :data => { "reveal-id" => "become-farmer" }
diff --git a/app/views/shop/_order_cycles.html.haml b/app/views/shop/_order_cycles.html.haml
index 60d5997cf3..5c2986bedf 100644
--- a/app/views/shop/_order_cycles.html.haml
+++ b/app/views/shop/_order_cycles.html.haml
@@ -20,9 +20,8 @@
"ng-change" => "changeOrderCycle()",
"ng-options" => "oc.id as oc.time for oc in #{@order_cycles.map {|oc| {time: pickup_time(oc), id: oc.id}}.to_json}"}
-
%closing
-#%img{src: "/icon/goes/here"}
Orders close
- %strong {{ order_cycle.orders_close_at | date:'EEEE MM'}}
+ %strong {{ order_cycle.orders_close_at | date_in_words }}
diff --git a/app/views/shop/_products.html.haml b/app/views/shop/_products.html.haml
index 3019d22967..d332057931 100644
--- a/app/views/shop/_products.html.haml
+++ b/app/views/shop/_products.html.haml
@@ -1,48 +1,69 @@
-%products{"ng-controller" => "ProductsCtrl"}
+%products{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null"}
+ %h5 Check out when you have selected everything you want
= form_for :order, :url => populate_orders_path, html: {:class => "custom"} do
%input#search.text{"ng-model" => "query", placeholder: "Search"}
%input.button.right{type: :submit, value: "Check Out"}
%table
%thead
- %th{colspan: 2} Item
+ %th.name Item
%th.notes Notes
- %th Variant
+ %th.variant Variant
%th QTY
%th Bulk
%th Price
%tbody{"ng-repeat" => "product in data.products | filter:query"}
%tr.product
- %td
+ %td.name
%img{"ng-src" => "{{ product.master.images[0].small_url }}"}
- {{product.master.images[0].alt}}
- %td
- %h5
- {{ product.name }}
- {{ product.supplier.name }}
+ %div
+ %h5
+ {{ product.name }}
+ {{ product.supplier.name }}
%td.notes {{ product.description | truncate:80 }}
- %td {{ product.master.options_text }}
%td
- %input{type: :number, value: 0, min: 0, name: "variants[{{product.master.id}}]"}
+ {{ product.master.options_text }}
+ %span{"ng-show" => "product.variants.length > 0"}
+ %img.collapse{src: "/assets/collapse.png",
+ "ng-show" => "product.show_variants",
+ "ng-click" => "product.show_variants = !product.show_variants"}
+
+ %img.expand{src: "/assets/expand.png",
+ "ng-show" => "!product.show_variants",
+ "ng-click" => "product.show_variants = !product.show_variants"}
+ %td
+ %span{"ng-show" => "(product.variants.length == 0)"}
+ %input{type: :number,
+ value: 0,
+ min: 0,
+ max: "{{product.on_demand && 9999 || product.count_on_hand }}",
+ name: "variants[{{product.master.id}}]",
+ id: "variants_{{product.master.id}}"}
%td.group_buy
- %span{"ng-show" => "product.group_buy"}
- Available
- %span{"ng-hide" => "product.group_buy"}
- Not available
+ %span{"ng-show" => "product.group_buy && (product.variants.length == 0)"}
+ %input{type: :number,
+ min: 0,
+ max: "{{product.on_demand && 9999 || product.count_on_hand }}",
+ name: "variant_attributes[{{product.master.id}}][max_quantity]"}
%td.price
- %small from
+ %small{"ng-show" => "(product.variants.length > 0)"} from
${{ product.price }}
- %tr{"ng-repeat" => "variant in product.variants"}
- %td{colspan: 3}
+ %tr{"ng-repeat" => "variant in product.variants", "ng-show" => "product.show_variants"}
+ %td{colspan: 2}
%td {{variant.options_text}}
%td
- %input{type: :number, value: 0, min: 0, name: "variants[{{variant.id}}]"}
+ %input{type: :number,
+ value: 0,
+ min: 0,
+ max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
+ name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
%td.group_buy
%span{"ng-show" => "product.group_buy"}
- Available
- %span{"ng-hide" => "product.group_buy"}
- Not available
+ %input{type: :number,
+ min: 0,
+ max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
+ name: "variant_attributes[{{variant.id}}][max_quantity]"}
%td.price
- %small from ${{variant.price }}
+ %small ${{variant.price }}
%input.button.right{type: :submit, value: "Check Out"}
-#%pre {{ data.products | json }}
diff --git a/app/views/shop/products.rabl b/app/views/shop/products.rabl
index 7d245447e9..265c02a08b 100644
--- a/app/views/shop/products.rabl
+++ b/app/views/shop/products.rabl
@@ -1,20 +1,25 @@
collection @products
-attributes :id, :name, :description, :price, :permalink
+attributes :id, :name, :price, :permalink, :count_on_hand, :on_demand, :group_buy
+node do |product|
+ {description: strip_tags(product.description)}
+end
-child :supplier do
+child :supplier => :supplier do
attributes :id, :name, :description
end
+
child :master => :master do
- attributes :id, :is_master, :count_on_hand, :options_text
- child :images => :images do
- attributes :id, :alt
- node do |img|
- {:small_url => img.attachment.url(:small, false)}
- end
- end
-end
-child :variants => :variants do |variant|
- attributes :id, :is_master, :count_on_hand, :options_text
+ attributes :id, :is_master, :count_on_hand, :options_text, :count_on_hand, :on_demand
+ child :images => :images do
+ attributes :id, :alt
+ node do |img|
+ {:small_url => img.attachment.url(:small, false)}
+ end
+ end
+end
+
+child :variants => :variants do |variant|
+ attributes :id, :is_master, :price, :count_on_hand, :options_text, :count_on_hand, :on_demand, :group_buy
child :images => :images do
attributes :id, :alt
node do |img|
diff --git a/app/views/shop/show.html.haml b/app/views/shop/show.html.haml
index a4d54423b6..93d50ab1b2 100644
--- a/app/views/shop/show.html.haml
+++ b/app/views/shop/show.html.haml
@@ -1,23 +1,26 @@
%shop{"ng-app" => "Shop"}
%navigation
%distributor.details.row
- %img.left{src: ""}
- %h4
- = @distributor.name
- %location= @distributor.address.city
- %small
- %a{href: "/"} Change location
+ #distributor_title
+ %img.left{src: @distributor.logo.url(:thumb)}
+ %h4
+ = @distributor.name
+ %location= @distributor.address.city
+ %small
+ %a{href: "/"} Change location
+
= render partial: "shop/order_cycles"
-#%description
-
+
%tabs
.row
.section-container.auto{"data-section" => "", "data-options" => "one_up: false"}
- %section
+ %section#about{class: current_order_cycle ? nil : "active" }
%p.title.avenir{"data-section-title" => ""}
%a{href: "#about"} About Us
.content{"data-section-content" => ""}
+ %img.about.right{src: @distributor.promo_image.url(:large)}
%p= @distributor.long_description.andand.html_safe
%section
@@ -40,7 +43,6 @@
.content{"data-section-content" => ""}
%p Contact
-
%products.row
= render partial: "shop/products"
#footer
diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml
index 970bc47961..e2ae2dcee4 100644
--- a/app/views/spree/admin/products/bulk_edit.html.haml
+++ b/app/views/spree/admin/products/bulk_edit.html.haml
@@ -121,8 +121,8 @@
%td{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text' }
%td{ 'ng-show' => 'columns.on_hand.visible' }
- %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => 'hasVariants(product)' }
- %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-show' => '!hasVariants(product)', :type => 'number' }
+ %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
+ %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' }
%td{ 'ng-show' => 'columns.available_on.visible' }
%input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ofn-track-product' => 'available_on', 'datetimepicker' => 'product.available_on', type: "text" }
%td.actions
@@ -142,7 +142,8 @@
%td{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' }
%td{ 'ng-show' => 'columns.on_hand.visible' }
- %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
+ %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
+ %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' }
%td{ 'ng-show' => 'columns.available_on.visible' }
%td.actions
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text" }
diff --git a/app/views/spree/api/products/bulk_show.v1.rabl b/app/views/spree/api/products/bulk_show.v1.rabl
index 534dd87846..1f07d3d91f 100644
--- a/app/views/spree/api/products/bulk_show.v1.rabl
+++ b/app/views/spree/api/products/bulk_show.v1.rabl
@@ -1,5 +1,8 @@
object @product
-attributes :id, :name, :price, :on_hand, :variant_unit, :variant_unit_scale, :variant_unit_name
+attributes :id, :name, :price, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand
+
+# Infinity is not a valid JSON object, but Rails encodes it anyway
+node( :on_hand ) { |p| p.on_hand.to_f.finite? ? p.on_hand : "On demand" }
node( :available_on ) { |p| p.available_on.blank? ? "" : p.available_on.strftime("%F %T") }
node( :permalink_live ) { |p| p.permalink }
@@ -9,4 +12,3 @@ end
node( :variants ) do |p|
partial 'spree/api/variants/bulk_index', :object => p.variants.order('id ASC')
end
-
diff --git a/app/views/spree/api/variants/bulk_show.v1.rabl b/app/views/spree/api/variants/bulk_show.v1.rabl
index fa487068fa..ee069c600c 100644
--- a/app/views/spree/api/variants/bulk_show.v1.rabl
+++ b/app/views/spree/api/variants/bulk_show.v1.rabl
@@ -1,3 +1,6 @@
object @variant
-attributes :id, :options_text, :price, :unit_value, :unit_description, :on_hand
\ No newline at end of file
+attributes :id, :options_text, :price, :unit_value, :unit_description, :on_demand
+
+# Infinity is not a valid JSON object, but Rails encodes it anyway
+node( :on_hand ) { |p| p.on_hand.to_f.finite? ? p.on_hand : "On demand" }
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 2e08f8e029..a780f486fb 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -45,6 +45,7 @@ Openfoodnetwork::Application.configure do
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
+ require 'uglifier'
config.assets.js_compressor = Uglifier.new(mangle: false)
# Disable delivery errors, bad email addresses will be ignored
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
index bee008922f..ee6fa16e8a 100644
--- a/config/environments/staging.rb
+++ b/config/environments/staging.rb
@@ -45,6 +45,7 @@ Openfoodnetwork::Application.configure do
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
+ require 'uglifier'
config.assets.js_compressor = Uglifier.new(mangle: false)
# Disable delivery errors, bad email addresses will be ignored
diff --git a/db/migrate/20140116030500_add_attachment_logo_to_enterprise.rb b/db/migrate/20140116030500_add_attachment_logo_to_enterprise.rb
new file mode 100644
index 0000000000..0f3c6e114f
--- /dev/null
+++ b/db/migrate/20140116030500_add_attachment_logo_to_enterprise.rb
@@ -0,0 +1,15 @@
+class AddAttachmentLogoToEnterprise < ActiveRecord::Migration
+ def self.up
+ add_column :enterprises, :logo_file_name, :string
+ add_column :enterprises, :logo_content_type, :string
+ add_column :enterprises, :logo_file_size, :integer
+ add_column :enterprises, :logo_updated_at, :datetime
+ end
+
+ def self.down
+ remove_column :enterprises, :logo_file_name
+ remove_column :enterprises, :logo_content_type
+ remove_column :enterprises, :logo_file_size
+ remove_column :enterprises, :logo_updated_at
+ end
+end
diff --git a/db/migrate/20140121050239_add_attachment_promo_image_to_enterprise.rb b/db/migrate/20140121050239_add_attachment_promo_image_to_enterprise.rb
new file mode 100644
index 0000000000..d875f111b2
--- /dev/null
+++ b/db/migrate/20140121050239_add_attachment_promo_image_to_enterprise.rb
@@ -0,0 +1,15 @@
+class AddAttachmentPromoImageToEnterprise < ActiveRecord::Migration
+ def self.up
+ add_column :enterprises, :promo_image_file_name, :string
+ add_column :enterprises, :promo_image_content_type, :string
+ add_column :enterprises, :promo_image_file_size, :integer
+ add_column :enterprises, :promo_image_updated_at, :datetime
+ end
+
+ def self.down
+ remove_column :enterprises, :promo_image_file_name
+ remove_column :enterprises, :promo_image_content_type
+ remove_column :enterprises, :promo_image_file_size
+ remove_column :enterprises, :promo_image_updated_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6b0e614cf4..d430638311 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20140110040238) do
+ActiveRecord::Schema.define(:version => 20140121050239) do
create_table "adjustment_metadata", :force => true do |t|
t.integer "adjustment_id"
@@ -202,9 +202,17 @@ ActiveRecord::Schema.define(:version => 20140110040238) do
t.integer "address_id"
t.string "pickup_times"
t.string "next_collection_at"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
t.text "distributor_info"
+ t.string "logo_file_name"
+ t.string "logo_content_type"
+ t.integer "logo_file_size"
+ t.datetime "logo_updated_at"
+ t.string "promo_image_file_name"
+ t.string "promo_image_content_type"
+ t.integer "promo_image_file_size"
+ t.datetime "promo_image_updated_at"
end
create_table "exchange_fees", :force => true do |t|
@@ -481,9 +489,9 @@ ActiveRecord::Schema.define(:version => 20140110040238) do
t.string "email"
t.text "special_instructions"
t.integer "distributor_id"
- t.integer "order_cycle_id"
t.string "currency"
t.string "last_ip_address"
+ t.integer "order_cycle_id"
t.integer "cart_id"
end
diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb
index 71f776163a..03e544ca19 100644
--- a/spec/controllers/shop_controller_spec.rb
+++ b/spec/controllers/shop_controller_spec.rb
@@ -54,10 +54,8 @@ describe ShopController do
spree_get :order_cycle
response.body.should have_content oc1.id
end
-
end
-
it "should not allow the user to select an invalid order cycle" do
oc1 = create(:order_cycle, distributors: [d])
oc2 = create(:order_cycle, distributors: [d])
@@ -110,13 +108,27 @@ describe ShopController do
context "RABL tests" do
render_views
- it "only returns products for the current order cycle" do
+ before do
controller.stub(:current_order_cycle).and_return order_cycle
+ end
+ it "only returns products for the current order cycle" do
xhr :get, :products
response.body.should have_content product.name
end
- end
+ it "doesn't return products not in stock" do
+ product.update_attribute(:on_demand, false)
+ product.master.update_attribute(:count_on_hand, 0)
+ xhr :get, :products
+ response.body.should_not have_content product.name
+ end
+ it "strips html from description" do
+ product.update_attribute(:description, "turtles frogs")
+ xhr :get, :products
+ response.body.should have_content "frogs"
+ response.body.should_not have_content " 'Thursday, 22nd Feb, 6 - 9 PM'
click_button 'Create'
-
flash_message.should == 'Enterprise "Eaterprises" has been successfully created!'
end
@@ -224,5 +223,12 @@ feature %q{
supplier2.reload.next_collection_at.should be_nil
distributor2.reload.next_collection_at.should be_nil
end
+
+ scenario "Editing images for an enterprise" do
+ click_link 'Enterprises'
+ first(".edit").click
+ page.should have_content "Logo"
+ page.should have_content "Promo"
+ end
end
end
diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb
index 05ec22ef41..51c9f83e07 100644
--- a/spec/features/admin/reports_spec.rb
+++ b/spec/features/admin/reports_spec.rb
@@ -144,8 +144,8 @@ feature %q{
table.sort.should == [
["Supplier", "Producer Suburb", "Product", "Product Properties", "Variant Value", "Price", "Group Buy Unit Quantity", "Amount"],
- [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), "Size: Test", "100.0", product_1.group_buy_unit_size.to_s, ""],
- [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), "Size: S", "80.0", product_1.group_buy_unit_size.to_s, ""],
+ [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), "Test", "100.0", product_1.group_buy_unit_size.to_s, ""],
+ [product_1.supplier.name, product_1.supplier.address.city, "Product Name", product_1.properties.join(", "), "S", "80.0", product_1.group_buy_unit_size.to_s, ""],
[product_2.supplier.name, product_1.supplier.address.city, "Product 2", product_1.properties.join(", "), "", "99.0", product_1.group_buy_unit_size.to_s, ""]
].sort
end
diff --git a/spec/features/consumer/shopping_spec.rb b/spec/features/consumer/shopping_spec.rb
index bbf127f1cb..a840e2f92b 100644
--- a/spec/features/consumer/shopping_spec.rb
+++ b/spec/features/consumer/shopping_spec.rb
@@ -17,6 +17,12 @@ feature "As a consumer I want to shop with a distributor", js: true do
page.should have_text distributor.name
end
+ it "shows distributor images" do
+ visit shop_path
+ first("distributor img")['src'].should == distributor.logo.url(:thumb)
+ first("#about img")['src'].should == distributor.promo_image.url(:large)
+ end
+
describe "With products in order cycles" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:product, supplier: supplier) }
@@ -32,37 +38,48 @@ feature "As a consumer I want to shop with a distributor", js: true do
click_link "Our Producers"
page.should have_content supplier.name
end
-
end
-
describe "selecting an order cycle" do
it "selects an order cycle if only one is open" do
# create order cycle
oc1 = create(:simple_order_cycle, distributors: [distributor])
exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id)
exchange.update_attribute :pickup_time, "turtles"
-
visit shop_path
page.should have_selector "option[selected]", text: 'turtles'
end
describe "with multiple order cycles" do
- let(:oc1) {create(:simple_order_cycle, distributors: [distributor])}
- let(:oc2) {create(:simple_order_cycle, distributors: [distributor])}
+ let(:oc1) {create(:simple_order_cycle, distributors: [distributor], orders_close_at: 2.days.from_now)}
+ let(:oc2) {create(:simple_order_cycle, distributors: [distributor], orders_close_at: 3.days.from_now)}
before do
exchange = Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id)
exchange.update_attribute :pickup_time, "frogs"
exchange = Exchange.find(oc2.exchanges.to_enterprises(distributor).outgoing.first.id)
exchange.update_attribute :pickup_time, "turtles"
+ visit shop_path
end
it "shows a select with all order cycles" do
- visit shop_path
page.should have_selector "option", text: 'frogs'
page.should have_selector "option", text: 'turtles'
end
+ it "shows the About Us by default if no order cycle is selected" do
+ page.should have_content "Hello, world!"
+ end
+
+ it "doesn't show the table before an order cycle is selected" do
+ page.should_not have_selector("input.button.right", visible: true)
+ end
+
+ pending "shows the table after an order cycle is selected" do
+ select "frogs", :from => "order_cycle_id"
+ save_and_open_page
+ #page.should have_selector("input.button.right", visible: true)
+ end
+
describe "with products in our order cycle" do
let(:product) { create(:simple_product) }
before do
@@ -75,7 +92,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
select "frogs", :from => "order_cycle_id"
Spree::Order.last.order_cycle.should == nil
page.should have_selector "products"
- page.should have_content "Orders close #{oc1.orders_close_at.strftime('%A %m')}"
+ page.should have_content "Orders close 2 days from now"
Spree::Order.last.order_cycle.should == oc1
end
@@ -89,8 +106,126 @@ feature "As a consumer I want to shop with a distributor", js: true do
end
it "updates the orders close note when order cycle is changed" do
- select "frogs", :from => "order_cycle_id"
- page.should have_content "Orders close #{oc1.orders_close_at.strftime('%A %m')}"
+ oc1.stub(:orders_close_at).and_return 3.days.from_now
+ select "turtles", :from => "order_cycle_id"
+ page.should have_content "Orders close 3 days from now"
+ end
+ end
+ end
+
+ describe "After selecting an order cycle with products visible" do
+ let(:oc) { create(:simple_order_cycle, distributors: [distributor]) }
+ let(:product) { create(:simple_product) }
+ let(:variant) { create(:variant, product: product) }
+
+ before do
+ exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id)
+ exchange.update_attribute :pickup_time, "frogs"
+ exchange.variants << product.master
+ exchange.variants << variant
+ visit shop_path
+ select "frogs", :from => "order_cycle_id"
+ exchange
+ end
+
+ it "should not show quantity field for product with variants" do
+ page.should_not have_selector("#variants_#{product.master.id}", visible: true)
+ end
+
+ it "collapses variants by default" do
+ page.should_not have_text variant.options_text
+ end
+
+ it "expands variants" do
+ find(".expand").trigger "click"
+ page.should have_text variant.options_text
+ find(".collapse").trigger "click"
+ page.should_not have_text variant.options_text
+ end
+ it "allows the user to expand variants"
+ end
+
+ describe "Filtering on hand and on demand products" do
+ let(:oc) { create(:simple_order_cycle, distributors: [distributor]) }
+ let(:p1) { create(:simple_product, on_demand: false) }
+ let(:p2) { create(:simple_product, on_demand: true) }
+ let(:p3) { create(:simple_product, on_demand: false) }
+ let(:p4) { create(:simple_product, on_demand: false) }
+ let(:v1) { create(:variant, product: p4) }
+
+ before do
+ p1.master.count_on_hand = 1
+ p2.master.count_on_hand = 0
+ p1.master.update_attribute(:count_on_hand, 1)
+ p2.master.update_attribute(:count_on_hand, 0)
+ p3.master.update_attribute(:count_on_hand, 0)
+ v1.update_attribute(:count_on_hand, 1)
+ exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id)
+ exchange.update_attribute :pickup_time, "frogs"
+ exchange.variants << p1.master
+ exchange.variants << p2.master
+ exchange.variants << p3.master
+ exchange.variants << v1
+ visit shop_path
+ select "frogs", :from => "order_cycle_id"
+ exchange
+ end
+
+ it "shows on hand products" do
+ page.should have_content p1.name
+ page.should have_content p4.name
+ end
+ it "shows on demand products" do
+ page.should have_content p2.name
+ end
+ it "does not show products that are neither on hand or on demand" do
+ page.should_not have_content p3.name
+ end
+ end
+
+ describe "group buy products" do
+ let(:oc) { create(:simple_order_cycle, distributors: [distributor]) }
+ let(:product) { create(:simple_product, group_buy: true) }
+
+ describe "without variants" do
+ before do
+ build_and_select_order_cycle
+ end
+
+ it "should show group buy input" do
+ page.should have_field "variant_attributes[#{product.master.id}][max_quantity]", :visible => true
+ end
+
+ it "should save group buy data to ze cart" do
+ fill_in "variants[#{product.master.id}]", with: 5
+ fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 9
+ first("form.custom > input.button.right").click
+ page.should have_content product.name
+ li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
+ li.max_quantity.should == 9
+ li.quantity.should == 5
+ end
+ end
+
+ describe "with variants on the product" do
+ let(:variant) { create(:variant, product: product) }
+ before do
+ build_and_select_order_cycle_with_variants
+ find(".expand").trigger "click"
+ end
+
+ it "should show group buy input" do
+ page.should have_field "variant_attributes[#{variant.id}][max_quantity]", :visible => true
+ end
+
+ it "should save group buy data to ze cart" do
+ fill_in "variants[#{variant.id}]", with: 6
+ fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7
+ first("form.custom > input.button.right").click
+ page.should have_content product.name
+ li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
+ li.max_quantity.should == 7
+ li.quantity.should == 6
end
end
end
@@ -100,14 +235,10 @@ feature "As a consumer I want to shop with a distributor", js: true do
let(:product) { create(:simple_product) }
let(:variant) { create(:variant, product: product) }
before do
- exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id)
- exchange.update_attribute :pickup_time, "frogs"
- exchange.variants << product.master
- exchange.variants << variant
- visit shop_path
- select "frogs", :from => "order_cycle_id"
+ build_and_select_order_cycle_with_variants
end
it "should let us add products to our cart" do
+ find(".expand").trigger "click"
fill_in "variants[#{variant.id}]", with: "1"
first("form.custom > input.button.right").click
current_path.should == "/cart"
@@ -134,3 +265,23 @@ feature "As a consumer I want to shop with a distributor", js: true do
end
end
end
+
+def build_and_select_order_cycle
+ exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id)
+ exchange.update_attribute :pickup_time, "frogs"
+ exchange.variants << product.master
+ visit shop_path
+ select "frogs", :from => "order_cycle_id"
+ exchange
+end
+
+
+def build_and_select_order_cycle_with_variants
+ exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id)
+ exchange.update_attribute :pickup_time, "frogs"
+ exchange.variants << product.master
+ exchange.variants << variant
+ visit shop_path
+ select "frogs", :from => "order_cycle_id"
+ exchange
+end
diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee
index 72096f6fb1..ef8a7c3cb2 100644
--- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee
+++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee
@@ -466,6 +466,35 @@ describe "AdminProductEditCtrl", ->
expect(scope.variantUnitValue(product, variant)).toEqual null
+ describe "updating the product on hand count", ->
+ it "updates when product is not available on demand", ->
+ spyOn(scope, "onHand").andReturn 123
+ product = {on_demand: false}
+ scope.updateOnHand(product)
+ expect(product.on_hand).toEqual 123
+
+ it "updates when product's variants are not available on demand", ->
+ spyOn(scope, "onHand").andReturn 123
+ product = {on_demand: false, variants: [{on_demand: false}]}
+ scope.updateOnHand(product)
+ expect(product.on_hand).toEqual 123
+
+ it "does nothing when the product is available on demand", ->
+ product = {on_demand: true}
+ scope.updateOnHand(product)
+ expect(product.on_hand).toBeUndefined()
+
+ it "does nothing when one of the variants is available on demand", ->
+ product =
+ on_demand: false
+ variants: [
+ {on_demand: false, on_hand: 10}
+ {on_demand: true, on_hand: Infinity}
+ ]
+ scope.updateOnHand(product)
+ expect(product.on_hand).toBeUndefined()
+
+
describe "getting on_hand counts when products have variants", ->
p1 = undefined
p2 = undefined
@@ -526,6 +555,24 @@ describe "AdminProductEditCtrl", ->
expect(scope.onHand(not_variants: [])).toEqual "error"
+ describe "determining whether a product has variants that are available on demand", ->
+ it "returns true when at least one variant does", ->
+ product =
+ variants: [
+ {on_demand: false}
+ {on_demand: true}
+ ]
+ expect(scope.hasOnDemandVariants(product)).toBe(true)
+
+ it "returns false otherwise", ->
+ product =
+ variants: [
+ {on_demand: false}
+ {on_demand: false}
+ ]
+ expect(scope.hasOnDemandVariants(product)).toBe(false)
+
+
describe "submitting products to be updated", ->
describe "packing products", ->
it "extracts variant_unit_with_scale into variant_unit and variant_unit_scale", ->
diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb
index 2b7a9b8c69..6c7dfb9091 100644
--- a/spec/models/spree/product_spec.rb
+++ b/spec/models/spree/product_spec.rb
@@ -411,5 +411,13 @@ module Spree
Spree::Product.all_variant_unit_option_types.sort.should == [ot1, ot2, ot3].sort
end
end
+
+ describe "Stock filtering" do
+ it "considers products that are on_demand as being in stock" do
+ product = create(:simple_product, on_demand: true)
+ product.master.update_attribute(:count_on_hand, 0)
+ product.has_stock?.should == true
+ end
+ end
end
end
diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb
index f64db7c6d3..2075912afc 100644
--- a/spec/models/spree/variant_spec.rb
+++ b/spec/models/spree/variant_spec.rb
@@ -182,6 +182,14 @@ module Spree
v.send(:option_value_value_unit).should == [123.45, 'g']
end
+ it "returns a value of 1 when unit value equals the scale" do
+ p = double(:product, variant_unit: 'weight', variant_unit_scale: 1000.0)
+ v.stub(:product) { p }
+ v.stub(:unit_value) { 1000.0 }
+
+ v.send(:option_value_value_unit).should == [1, 'kg']
+ end
+
it "generates values for all weight scales" do
[[1.0, 'g'], [1000.0, 'kg'], [1000000.0, 'T']].each do |scale, unit|
p = double(:product, variant_unit: 'weight', variant_unit_scale: scale)