mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-09 03:20:21 +00:00
Merge current master
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, storage)->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -28,15 +28,39 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
|
||||
update: =>
|
||||
@update_running = true
|
||||
|
||||
$http.post('/orders/populate', @data()).success (data, status)=>
|
||||
@saved()
|
||||
@update_running = false
|
||||
|
||||
@compareAndNotifyStockLevels data.stock_levels
|
||||
|
||||
@popQueue() if @update_enqueued
|
||||
|
||||
.error (response, status)=>
|
||||
@scheduleRetry(status)
|
||||
@update_running = false
|
||||
|
||||
compareAndNotifyStockLevels: (stockLevels) =>
|
||||
scope = $rootScope.$new(true)
|
||||
scope.variants = []
|
||||
|
||||
# TODO: These changes to quantity/max_quantity trigger another cart update, which
|
||||
# is unnecessary.
|
||||
|
||||
for li in @line_items_present()
|
||||
if stockLevels[li.variant.id]?
|
||||
li.variant.count_on_hand = stockLevels[li.variant.id].on_hand
|
||||
if li.quantity > li.variant.count_on_hand
|
||||
li.quantity = li.variant.count_on_hand
|
||||
scope.variants.push li.variant
|
||||
if li.max_quantity > li.variant.count_on_hand
|
||||
li.max_quantity = li.variant.count_on_hand
|
||||
scope.variants.push(li.variant) unless li.variant in scope.variants
|
||||
|
||||
if scope.variants.length > 0
|
||||
$modal.open(templateUrl: "out_of_stock.html", scope: scope, windowClass: 'out-of-stock-modal')
|
||||
|
||||
popQueue: =>
|
||||
@update_enqueued = false
|
||||
@scheduleUpdate()
|
||||
|
||||
@@ -10,9 +10,12 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h
|
||||
$http.put('/checkout', {order: @preprocess()}).success (data, status)=>
|
||||
Navigation.go data.path
|
||||
.error (response, status)=>
|
||||
Loading.clear()
|
||||
@errors = response.errors
|
||||
RailsFlashLoader.loadFlash(response.flash)
|
||||
if response.path
|
||||
Navigation.go response.path
|
||||
else
|
||||
Loading.clear()
|
||||
@errors = response.errors
|
||||
RailsFlashLoader.loadFlash(response.flash)
|
||||
|
||||
# Rails wants our Spree::Address data to be provided with _attributes
|
||||
preprocess: ->
|
||||
|
||||
13
app/assets/javascripts/templates/out_of_stock.html.haml
Normal file
13
app/assets/javascripts/templates/out_of_stock.html.haml
Normal file
@@ -0,0 +1,13 @@
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
|
||||
%h3 Reduced stock available
|
||||
|
||||
%p While you've been shopping, the stock levels for one or more of the products in your cart have reduced. Here's what's changed:
|
||||
|
||||
%p{'ng-repeat' => "v in variants"}
|
||||
%em {{ v.name_to_display }} - {{ v.unit_to_display }}
|
||||
%span{'ng-if' => "v.count_on_hand == 0"}
|
||||
is now out of stock.
|
||||
%span{'ng-if' => "v.count_on_hand > 0"}
|
||||
now only has {{ v.count_on_hand }} remaining.
|
||||
@@ -0,0 +1,12 @@
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
|
||||
|
||||
%input{type: :number,
|
||||
integer: true,
|
||||
value: nil,
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
"ng-disabled" => "!variant.on_demand && variant.count_on_hand == 0",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
@@ -0,0 +1,23 @@
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
|
||||
%span.bulk-input-container
|
||||
%span.bulk-input
|
||||
%input.bulk.first{type: :number,
|
||||
value: nil,
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
placeholder: "{{'shop_variant_quantity_min' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
%span.bulk-input
|
||||
%input.bulk.second{type: :number,
|
||||
"ng-disabled" => "!variant.line_item.quantity",
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
placeholder: "{{'shop_variant_quantity_max' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variant_attributes[{{variant.id}}][max_quantity]",
|
||||
id: "variants_{{variant.id}}_max"}
|
||||
@@ -1,61 +1,28 @@
|
||||
.variants.row
|
||||
.small-12.medium-4.large-4.columns.variant-name
|
||||
.table-cell
|
||||
.table-cell
|
||||
.inline {{ variant.name_to_display }}
|
||||
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
|
||||
%i.ofn-i_056-bulk><
|
||||
%em><
|
||||
\ {{'bulk' | t}}
|
||||
|
||||
-# WITHOUT GROUP BUY
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
|
||||
|
||||
%input{type: :number,
|
||||
integer: true,
|
||||
value: nil,
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
|
||||
%ng-include{src: "'partials/shop_variant_no_group_buy.html'"}
|
||||
%ng-include{src: "'partials/shop_variant_with_group_buy.html'"}
|
||||
|
||||
-# WITH GROUP BUY
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
|
||||
%span.bulk-input-container
|
||||
%span.bulk-input
|
||||
%input.bulk.first{type: :number,
|
||||
value: nil,
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.quantity",
|
||||
placeholder: "{{'shop_variant_quantity_min' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
%span.bulk-input
|
||||
%input.bulk.second{type: :number,
|
||||
"ng-disabled" => "!variant.line_item.quantity",
|
||||
integer: true,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
placeholder: "{{'shop_variant_quantity_max' | t}}",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variant_attributes[{{variant.id}}][max_quantity]"}
|
||||
|
||||
.small-3.medium-1.large-1.columns.variant-unit
|
||||
.table-cell
|
||||
.table-cell
|
||||
%em {{ variant.unit_to_display }}
|
||||
|
||||
.small-4.medium-2.large-2.columns.variant-price
|
||||
.table-cell.price
|
||||
%i.ofn-i_009-close
|
||||
%i.ofn-i_009-close
|
||||
{{ variant.price_with_fees | localizeCurrency }}
|
||||
|
||||
-# Now in a template in app/assets/javascripts/templates !
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
"price-breakdown-append-to-body" => "true",
|
||||
"price-breakdown-placement" => "left",
|
||||
"price-breakdown-animation" => true}
|
||||
@@ -63,4 +30,4 @@
|
||||
.small-12.medium-2.large-2.columns.total-price.text-right
|
||||
.table-cell
|
||||
%strong{"ng-class" => "{filled: variant.totalPrice()}"}
|
||||
{{ variant.totalPrice() | localizeCurrency }}
|
||||
{{ variant.totalPrice() | localizeCurrency }}
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
padding-bottom: 0em
|
||||
display: table
|
||||
line-height: 1.1
|
||||
// outline: 1px solid red
|
||||
// outline: 1px solid red
|
||||
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 0.875rem
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 0.875rem
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
font-size: 0.75rem
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
font-size: 0.75rem
|
||||
|
||||
.table-cell
|
||||
display: table-cell
|
||||
vertical-align: middle
|
||||
height: 37px
|
||||
|
||||
// ROW VARIANTS
|
||||
// ROW VARIANTS
|
||||
.row.variants
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
@@ -35,7 +35,10 @@
|
||||
background-color: #f9f9f9
|
||||
&:hover, &:focus, &:active
|
||||
background-color: $clr-brick-ultra-light
|
||||
|
||||
|
||||
&.out-of-stock
|
||||
opacity: 0.2
|
||||
|
||||
// Variant name
|
||||
.variant-name
|
||||
padding-left: 7.9375rem
|
||||
@@ -52,7 +55,7 @@
|
||||
height: 27px
|
||||
|
||||
// Variant unit
|
||||
.variant-unit
|
||||
.variant-unit
|
||||
padding-left: 0rem
|
||||
padding-right: 0rem
|
||||
color: #888
|
||||
@@ -88,18 +91,18 @@
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
background: #fff
|
||||
|
||||
|
||||
.columns
|
||||
padding-top: 1em
|
||||
padding-bottom: 1em
|
||||
line-height: 1
|
||||
|
||||
|
||||
@media all and (max-width: 768px)
|
||||
padding-top: 0.65rem
|
||||
padding-bottom: 0.65rem
|
||||
|
||||
.summary-header
|
||||
padding-left: 7.9375rem
|
||||
padding-left: 7.9375rem
|
||||
@media all and (max-width: 768px)
|
||||
padding-left: 4.9375rem
|
||||
@media all and (max-width: 640px)
|
||||
@@ -118,4 +121,3 @@
|
||||
color: $clr-brick
|
||||
i
|
||||
font-size: 0.8em
|
||||
|
||||
|
||||
@@ -26,6 +26,23 @@ module Admin
|
||||
end
|
||||
end
|
||||
|
||||
# copy of Spree::Admin::ResourceController without flash notice
|
||||
def destroy
|
||||
invoke_callbacks(:destroy, :before)
|
||||
if @object.destroy
|
||||
invoke_callbacks(:destroy, :after)
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
|
||||
@@ -151,8 +151,15 @@ class CheckoutController < Spree::CheckoutController
|
||||
|
||||
# Overriding Spree's methods
|
||||
def raise_insufficient_quantity
|
||||
flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity)
|
||||
redirect_to main_app.shop_path
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to cart_path
|
||||
end
|
||||
|
||||
format.json do
|
||||
render json: {path: cart_path}, status: 400
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_to_paypal_express_form_if_needed
|
||||
|
||||
@@ -5,6 +5,8 @@ class EnterprisesController < BaseController
|
||||
|
||||
# These prepended filters are in the reverse order of execution
|
||||
prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop
|
||||
before_filter :check_stock_levels, only: :shop
|
||||
|
||||
before_filter :clean_permalink, only: :check_permalink
|
||||
|
||||
respond_to :js, only: :permalink_checker
|
||||
@@ -21,17 +23,24 @@ class EnterprisesController < BaseController
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def clean_permalink
|
||||
params[:permalink] = params[:permalink].parameterize
|
||||
end
|
||||
|
||||
def check_stock_levels
|
||||
if current_order(true).insufficient_stock_lines.present?
|
||||
redirect_to spree.cart_path
|
||||
end
|
||||
end
|
||||
|
||||
def reset_order
|
||||
distributor = Enterprise.is_distributor.find_by_permalink(params[:id]) || Enterprise.is_distributor.find(params[:id])
|
||||
order = current_order(true)
|
||||
|
||||
if order.distributor and order.distributor != distributor
|
||||
if order.distributor && order.distributor != distributor
|
||||
order.empty!
|
||||
order.set_order_cycle! nil
|
||||
end
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
require 'spree/core/controller_helpers/order_decorator'
|
||||
|
||||
Spree::OrdersController.class_eval do
|
||||
after_filter :populate_variant_attributes, :only => :populate
|
||||
before_filter :update_distribution, :only => :update
|
||||
before_filter :filter_order_params, :only => :update
|
||||
after_filter :populate_variant_attributes, only: :populate
|
||||
before_filter :update_distribution, only: :update
|
||||
before_filter :filter_order_params, only: :update
|
||||
|
||||
prepend_before_filter :require_order_cycle, only: :edit
|
||||
prepend_before_filter :require_distributor_chosen, only: :edit
|
||||
@@ -12,13 +12,19 @@ Spree::OrdersController.class_eval do
|
||||
include OrderCyclesHelper
|
||||
layout 'darkswarm'
|
||||
|
||||
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@order = current_order(true)
|
||||
|
||||
if @order.line_items.empty?
|
||||
redirect_to main_app.shop_path
|
||||
else
|
||||
associate_user
|
||||
|
||||
if @order.insufficient_stock_lines.present?
|
||||
flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,19 +36,53 @@ Spree::OrdersController.class_eval do
|
||||
|
||||
Spree::Adjustment.without_callbacks do
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
|
||||
if populator.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
|
||||
current_order.cap_quantity_at_stock!
|
||||
current_order.update!
|
||||
|
||||
render json: true, status: 200
|
||||
variant_ids = variant_ids_in(populator.variants_h)
|
||||
|
||||
render json: {error: false, stock_levels: stock_levels(current_order, variant_ids)},
|
||||
status: 200
|
||||
|
||||
else
|
||||
render json: false, status: 402
|
||||
render json: {error: true}, status: 412
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Report the stock levels in the order for all variant ids requested
|
||||
def stock_levels(order, variant_ids)
|
||||
stock_levels = li_stock_levels(order)
|
||||
|
||||
li_variant_ids = stock_levels.keys
|
||||
(variant_ids - li_variant_ids).each do |variant_id|
|
||||
stock_levels[variant_id] = {quantity: 0, max_quantity: 0,
|
||||
on_hand: Spree::Variant.find(variant_id).on_hand}
|
||||
end
|
||||
|
||||
stock_levels
|
||||
end
|
||||
|
||||
def variant_ids_in(variants_h)
|
||||
variants_h.map { |v| v[:variant_id].to_i }
|
||||
end
|
||||
|
||||
def li_stock_levels(order)
|
||||
Hash[
|
||||
order.line_items.map do |li|
|
||||
[li.variant.id,
|
||||
{quantity: li.quantity,
|
||||
max_quantity: li.max_quantity,
|
||||
on_hand: wrap_json_infinity(li.variant.on_hand)}]
|
||||
end
|
||||
]
|
||||
end
|
||||
|
||||
def update_distribution
|
||||
@order = current_order(true)
|
||||
|
||||
@@ -121,4 +161,9 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
# Rails to_json encodes Float::INFINITY as Infinity, which is not valid JSON
|
||||
# Return it as a large integer (max 32 bit signed int)
|
||||
def wrap_json_infinity(n)
|
||||
n == Float::INFINITY ? 2147483647 : n
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,6 +43,16 @@ Spree::LineItem.class_eval do
|
||||
where('spree_adjustments.id IS NULL')
|
||||
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
attrs = {}
|
||||
|
||||
attrs[:quantity] = variant.on_hand if quantity > variant.on_hand
|
||||
attrs[:max_quantity] = variant.on_hand if (max_quantity || 0) > variant.on_hand
|
||||
|
||||
update_attributes!(attrs) if attrs.any?
|
||||
end
|
||||
|
||||
|
||||
def has_tax?
|
||||
adjustments.included_tax.any?
|
||||
end
|
||||
|
||||
@@ -99,7 +99,7 @@ Spree::Order.class_eval do
|
||||
def remove_variant(variant)
|
||||
line_items(:reload)
|
||||
current_item = find_line_item_by_variant(variant)
|
||||
current_item.destroy
|
||||
current_item.andand.destroy
|
||||
end
|
||||
|
||||
|
||||
@@ -144,6 +144,11 @@ Spree::Order.class_eval do
|
||||
current_item
|
||||
end
|
||||
|
||||
def cap_quantity_at_stock!
|
||||
line_items.each &:cap_quantity_at_stock!
|
||||
end
|
||||
|
||||
|
||||
def set_distributor!(distributor)
|
||||
self.distributor = distributor
|
||||
self.order_cycle = nil unless self.order_cycle.andand.has_distributor? distributor
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
require 'open_food_network/scope_variant_to_hub'
|
||||
|
||||
Spree::OrderPopulator.class_eval do
|
||||
attr_reader :variants_h
|
||||
|
||||
def populate(from_hash, overwrite = false)
|
||||
@distributor, @order_cycle = distributor_and_order_cycle
|
||||
# Refactor: We may not need this validation - we can't change distribution here, so
|
||||
@@ -11,8 +13,7 @@ Spree::OrderPopulator.class_eval do
|
||||
|
||||
if valid?
|
||||
@order.with_lock do
|
||||
variants = read_products_hash(from_hash) +
|
||||
read_variants_hash(from_hash)
|
||||
variants = read_variants from_hash
|
||||
|
||||
variants.each do |v|
|
||||
if varies_from_cart(v)
|
||||
@@ -31,6 +32,11 @@ Spree::OrderPopulator.class_eval do
|
||||
valid?
|
||||
end
|
||||
|
||||
def read_variants(data)
|
||||
@variants_h = read_products_hash(data) +
|
||||
read_variants_hash(data)
|
||||
end
|
||||
|
||||
def read_products_hash(data)
|
||||
(data[:products] || []).map do |product_id, variant_id|
|
||||
{variant_id: variant_id, quantity: data[:quantity]}
|
||||
@@ -49,17 +55,34 @@ Spree::OrderPopulator.class_eval do
|
||||
|
||||
def attempt_cart_add(variant_id, quantity, max_quantity = nil)
|
||||
quantity = quantity.to_i
|
||||
max_quantity = max_quantity.to_i if max_quantity
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
OpenFoodNetwork::ScopeVariantToHub.new(@distributor).scope(variant)
|
||||
if quantity > 0
|
||||
if check_stock_levels(variant, quantity) &&
|
||||
check_order_cycle_provided_for(variant) &&
|
||||
check_variant_available_under_distribution(variant)
|
||||
@order.add_variant(variant, quantity, max_quantity, currency)
|
||||
if quantity > 0 &&
|
||||
check_order_cycle_provided_for(variant) &&
|
||||
check_variant_available_under_distribution(variant)
|
||||
|
||||
quantity_to_add, max_quantity_to_add = quantities_to_add(variant, quantity, max_quantity)
|
||||
|
||||
if quantity_to_add > 0
|
||||
@order.add_variant(variant, quantity_to_add, max_quantity_to_add, currency)
|
||||
else
|
||||
@order.remove_variant variant
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def quantities_to_add(variant, quantity, max_quantity)
|
||||
# If not enough stock is available, add as much as we can to the cart
|
||||
on_hand = variant.on_hand
|
||||
on_hand = [quantity, max_quantity].compact.max if Spree::Config.allow_backorders
|
||||
quantity_to_add = [quantity, on_hand].min
|
||||
max_quantity_to_add = [max_quantity, on_hand].min if max_quantity
|
||||
|
||||
[quantity_to_add, max_quantity_to_add]
|
||||
end
|
||||
|
||||
|
||||
def cart_remove(variant_id)
|
||||
variant = Spree::Variant.find(variant_id)
|
||||
@order.remove_variant(variant)
|
||||
|
||||
@@ -15,7 +15,6 @@ Spree.user_class.class_eval do
|
||||
accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true
|
||||
|
||||
attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit
|
||||
after_create :associate_customers
|
||||
after_create :send_signup_confirmation
|
||||
|
||||
validate :limit_owned_enterprises
|
||||
@@ -42,10 +41,6 @@ Spree.user_class.class_eval do
|
||||
customers.of(enterprise).first
|
||||
end
|
||||
|
||||
def associate_customers
|
||||
Customer.update_all({ user_id: id }, { user_id: nil, email: email })
|
||||
end
|
||||
|
||||
def send_signup_confirmation
|
||||
Delayed::Job.enqueue ConfirmSignupJob.new(id)
|
||||
end
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
%div.pad-top{bindonce: true}
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl",
|
||||
"ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"}
|
||||
= render partial: "shop/products/summary"
|
||||
= render "shop/products/summary"
|
||||
%shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants", "id" => "variant-{{ product.master.id }}"}
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}"}
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.count_on_hand == 0}"}
|
||||
|
||||
%product{"ng-show" => "Products.loading"}
|
||||
.row.summary
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
%em
|
||||
= t :products_from
|
||||
%span
|
||||
%enterprise-modal
|
||||
%i.ofn-i_036-producers{"bo-text" => "enterprise.name"}
|
||||
%enterprise-modal
|
||||
%i.ofn-i_036-producers
|
||||
%span{"bo-bind" => "enterprise.name"}
|
||||
.small-2.medium-2.large-1.columns.text-center
|
||||
.taxon-flag
|
||||
%render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
|
||||
@@ -21,18 +21,129 @@ en-GB:
|
||||
invalid: |
|
||||
Invalid email or password.
|
||||
Were you a guest last time? Perhaps you need to create an account or reset your password.
|
||||
enterprise_confirmations:
|
||||
enterprise:
|
||||
confirmed: Thankyou, your email address has been confirmed.
|
||||
not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step?
|
||||
confirmation_sent: "Confirmation email sent!"
|
||||
confirmation_not_sent: "Could not send a confirmation email."
|
||||
home: "OFN"
|
||||
title: Open Food Network
|
||||
welcome_to: 'Welcome to '
|
||||
site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…"
|
||||
search_by_name: Search by name...
|
||||
producers: UK Producers
|
||||
producers_join: UK producers are now welcome to join Open Food Network UK.
|
||||
charges_sales_tax: Charges sales tax?
|
||||
print: "Print"
|
||||
charges_sales_tax: Charges VAT?
|
||||
print_invoice: "Print Invoice"
|
||||
send_invoice: "Send Invoice"
|
||||
resend_confirmation: "Resend Confirmation"
|
||||
view_order: "View Order"
|
||||
edit_order: "Edit Order"
|
||||
ship_order: "Ship Order"
|
||||
cancel_order: "Cancel Order"
|
||||
confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?"
|
||||
confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?"
|
||||
invoice: "Invoice"
|
||||
percentage_of_sales: "%{percentage} of sales"
|
||||
percentage_of_turnover: "Percentage of turnover"
|
||||
monthly_cap_excl_tax: "monthly cap (excl. VAT)"
|
||||
capped_at_cap: "capped at %{cap}"
|
||||
per_month: "per month"
|
||||
free: "free"
|
||||
plus_tax: "plus GST"
|
||||
total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)"
|
||||
say_no: "No"
|
||||
say_yes: "Yes"
|
||||
|
||||
logo: "Logo (640x130)"
|
||||
logo_mobile: "Mobile logo (75x26)"
|
||||
logo_mobile_svg: "Mobile logo (SVG)"
|
||||
sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By"
|
||||
|
||||
|
||||
admin:
|
||||
# General form elements
|
||||
quick_search: Quick Search
|
||||
clear_all: Clear All
|
||||
producer: Producer
|
||||
shop: Shop
|
||||
product: Product
|
||||
variant: Variant
|
||||
|
||||
columns: Columns
|
||||
actions: Actions
|
||||
viewing: "Viewing: %{current_view_name}"
|
||||
|
||||
whats_this: What's this?
|
||||
|
||||
customers:
|
||||
index:
|
||||
add_customer: "Add customer"
|
||||
customer_placeholder: "customer@example.org"
|
||||
inventory:
|
||||
title: Inventory
|
||||
description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
|
||||
sku: SKU
|
||||
price: Price
|
||||
on_hand: On Hand
|
||||
on_demand: On Demand?
|
||||
enable_reset: Enable Stock Level Reset?
|
||||
inherit: Inherit?
|
||||
add: Add
|
||||
hide: Hide
|
||||
select_a_shop: Select A Shop
|
||||
review_now: Review Now
|
||||
new_products_alert_message: There are %{new_product_count} new products available to add to your inventory.
|
||||
currently_empty: Your inventory is currently empty
|
||||
no_matching_products: No matching products found in your inventory
|
||||
no_hidden_products: No products have been hidden from this inventory
|
||||
no_matching_hidden_products: No hidden products match your search criteria
|
||||
no_new_products: No new products are available to add to this inventory
|
||||
no_matching_new_products: No new products match your search criteria
|
||||
inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown.
|
||||
hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory.
|
||||
new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later!
|
||||
|
||||
|
||||
order_cycle:
|
||||
choose_products_from: "Choose Products From:"
|
||||
|
||||
enterprise:
|
||||
select_outgoing_oc_products_from: Select outgoing OC products from
|
||||
|
||||
enterprises:
|
||||
form:
|
||||
primary_details:
|
||||
shopfront_requires_login: "Shopfront requires login?"
|
||||
shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront."
|
||||
shopfront_requires_login_false: "Public"
|
||||
shopfront_requires_login_true: "Require customers to login"
|
||||
|
||||
home:
|
||||
hubs:
|
||||
show_closed_shops: "Show closed shops"
|
||||
hide_closed_shops: "Hide closed shops"
|
||||
show_on_map: "Show all on the map"
|
||||
shared:
|
||||
register_call:
|
||||
selling_on_ofn: "Interested in getting on the Open Food Network?"
|
||||
register: "Register here"
|
||||
shop:
|
||||
messages:
|
||||
login: "login"
|
||||
register: "register"
|
||||
contact: "contact"
|
||||
require_customer_login: "This shop is for customers only."
|
||||
require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer."
|
||||
require_customer_html: "Please %{contact} %{enterprise} to become a customer."
|
||||
|
||||
# Printable Invoice Columns
|
||||
invoice_column_item: "Item"
|
||||
invoice_column_qty: "Qty"
|
||||
invoice_column_tax: "VAT"
|
||||
invoice_column_price: "Price"
|
||||
|
||||
logo: "Logo (640x130)" #FIXME
|
||||
logo_mobile: "Mobile logo (75x26)" #FIXME
|
||||
logo_mobile_svg: "Mobile logo (SVG)" #FIXME
|
||||
home_hero: "Hero image"
|
||||
home_show_stats: "Show statistics"
|
||||
footer_logo: "Logo (220x76)"
|
||||
@@ -46,11 +157,10 @@ en-GB:
|
||||
footer_links_md: "Links"
|
||||
footer_about_url: "About URL"
|
||||
footer_tos_url: "Terms of Service URL"
|
||||
invoice: "Invoice"
|
||||
|
||||
name: Name
|
||||
first_name: First name
|
||||
last_name: Last name
|
||||
first_name: First Name
|
||||
last_name: Last Name
|
||||
email: Email
|
||||
phone: Phone
|
||||
next: Next
|
||||
@@ -90,9 +200,9 @@ en-GB:
|
||||
cart_empty: "Cart empty"
|
||||
cart_edit: "Edit your cart"
|
||||
|
||||
card_number: Card number
|
||||
card_securitycode: "Security code"
|
||||
card_expiry_date: Expiry date
|
||||
card_number: Card Number
|
||||
card_securitycode: "Security Code"
|
||||
card_expiry_date: Expiry Date
|
||||
|
||||
ofn_cart_headline: "Current cart for:"
|
||||
ofn_cart_distributor: "Distributor:"
|
||||
@@ -187,7 +297,7 @@ en-GB:
|
||||
checkout_cart_total: Cart total
|
||||
checkout_shipping_price: Shipping
|
||||
checkout_total_price: Total
|
||||
checkout_back_to_cart: "Back to cart"
|
||||
checkout_back_to_cart: "Back to Cart"
|
||||
|
||||
order_paid: PAID
|
||||
order_not_paid: NOT PAID
|
||||
@@ -197,12 +307,32 @@ en-GB:
|
||||
order_delivery_on: Delivery on
|
||||
order_delivery_address: Delivery address
|
||||
order_special_instructions: "Your notes:"
|
||||
order_pickup_instructions: Collection instructions
|
||||
order_pickup_time: Ready for collection
|
||||
order_pickup_instructions: Collection Instructions
|
||||
order_produce: Produce
|
||||
order_total_price: Total
|
||||
order_includes_tax: (includes tax)
|
||||
order_payment_paypal_successful: Your payment via PayPal has been processed successfully.
|
||||
order_hub_info: Hub info
|
||||
order_hub_info: Hub Info
|
||||
|
||||
bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
|
||||
bom_shared: "Shared Resource?"
|
||||
bom_page_title: "Bulk Order Management"
|
||||
bom_no: "Order no."
|
||||
bom_date: "Order date"
|
||||
bom_cycle: "Order cycle"
|
||||
bom_max: "Max"
|
||||
bom_hub: "Hub"
|
||||
bom_variant: "Product: Unit"
|
||||
bom_final_weigth_volume: "Weight/Volume"
|
||||
bom_quantity: "Quantity"
|
||||
bom_actions_delete: "Delete Selected"
|
||||
bom_loading: "Loading orders"
|
||||
bom_no_results: "No orders found."
|
||||
bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors."
|
||||
|
||||
unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue."
|
||||
unsaved_changes_error: "Fields with red borders contain errors."
|
||||
|
||||
products: "Products"
|
||||
products_in: "in %{oc}"
|
||||
@@ -307,6 +437,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
products_cart_empty: "Cart empty"
|
||||
products_edit_cart: "Edit your cart"
|
||||
products_from: from
|
||||
products_change: "No changes to save."
|
||||
products_update_error: "Saving failed with the following error(s):"
|
||||
products_update_error_msg: "Saving failed."
|
||||
products_update_error_data: "Save failed due to invalid data:"
|
||||
products_changes_saved: "Changes saved."
|
||||
|
||||
search_no_results_html: "Sorry, no results found for %{query}. Try another search?"
|
||||
|
||||
@@ -317,9 +452,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
|
||||
groups_title: Groups
|
||||
groups_headline: Groups / regions
|
||||
groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you."
|
||||
groups_search: "Search name or keyword"
|
||||
groups_no_groups: "No groups found"
|
||||
groups_about: "About Us"
|
||||
|
||||
groups_producers: "Our producers"
|
||||
groups_hubs: "Our hubs"
|
||||
groups_contact_web: Contact
|
||||
@@ -384,9 +521,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
ocs_close_time: "ORDERS CLOSE"
|
||||
ocs_when_headline: When do you want your order?
|
||||
ocs_when_text: No products are displayed until you select a date.
|
||||
ocs_when_closing: "Closing on"
|
||||
ocs_when_closing: "Closing On"
|
||||
ocs_when_choose: "Choose Order Cycle"
|
||||
ocs_list: "List view"
|
||||
ocs_list: "List View"
|
||||
|
||||
producers_about: About us
|
||||
producers_buy: Shop for
|
||||
@@ -407,13 +544,26 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
producers_signup_cta_headline: Join now!
|
||||
producers_signup_cta_action: Join now
|
||||
producers_signup_detail: Here's the detail.
|
||||
producer: Producer
|
||||
|
||||
products_item: Item
|
||||
products_description: Description
|
||||
products_variant: Variant
|
||||
products_quantity: Quantity
|
||||
products_availabel: Available?
|
||||
products_price: Price
|
||||
products_producer: "Producer"
|
||||
products_price: "Price"
|
||||
products_sku: "SKU"
|
||||
products_name: "name"
|
||||
products_unit: "unit"
|
||||
products_on_hand: "on hand"
|
||||
products_on_demand: "On demand?"
|
||||
products_category: "Category"
|
||||
products_tax_category: "tax category"
|
||||
products_available_on: "Available On"
|
||||
products_inherit: "Inherit?"
|
||||
products_inherits_properties: "Inherits Properties?"
|
||||
products_stock_level_reset: "Enable Stock Level Reset?"
|
||||
|
||||
register_title: Register
|
||||
|
||||
@@ -431,7 +581,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
shops_signup_detail: Here's the detail.
|
||||
|
||||
orders_fees: Fees...
|
||||
orders_edit_title: Shopping cart
|
||||
orders_edit_title: Shopping Cart
|
||||
orders_edit_headline: Your shopping cart
|
||||
orders_edit_time: Order ready for
|
||||
orders_edit_continue: Continue shopping
|
||||
@@ -509,18 +659,17 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
confirm_password: "Confirm password"
|
||||
action_signup: "Sign up now"
|
||||
welcome_to_ofn: "Welcome to the Open Food Network!"
|
||||
signup_or_login: "Start By signing up (or logging in)"
|
||||
signup_or_login: "Start By Signing Up (or logging in)"
|
||||
have_an_account: "Already have an account?"
|
||||
action_login: "Log in now."
|
||||
forgot_password: "Forgot password?"
|
||||
forgot_password: "Forgot Password?"
|
||||
password_reset_sent: "An email with instructions on resetting your password has been sent!"
|
||||
reset_password: "Reset password"
|
||||
registration_greeting: "Greetings!"
|
||||
who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?"
|
||||
enterprise_contact: "Primary contact"
|
||||
enterprise_contact: "Primary Contact"
|
||||
enterprise_contact_required: "You need to enter a primary contact."
|
||||
enterprise_email: "Email address"
|
||||
enterprise_email_required: "You need to enter valid email address."
|
||||
enterprise_email_address: "Email address"
|
||||
enterprise_phone: "Phone number"
|
||||
back: "Back"
|
||||
continue: "Continue"
|
||||
@@ -528,20 +677,20 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
limit_reached_message: "You have reached the limit!"
|
||||
limit_reached_text: "You have reached the limit for the number of enterprises you are allowed to own on the"
|
||||
limit_reached_action: "Return to the homepage"
|
||||
select_promo_image: "Step 3. Select promo image"
|
||||
select_promo_image: "Step 3. Select Promo Image"
|
||||
promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px"
|
||||
promo_image_label: "Choose a promo image"
|
||||
action_or: "OR"
|
||||
promo_image_drag: "Drag and drop your promo here"
|
||||
review_promo_image: "Step 4. Review your promo banner"
|
||||
review_promo_image: "Step 4. Review Your Promo Banner"
|
||||
review_promo_image_tip: "Tip: for best results, your promo image should fill the available space"
|
||||
promo_image_placeholder: "Your logo will appear here for review once uploaded"
|
||||
uploading: "Uploading..."
|
||||
select_logo: "Step 1. Select logo image"
|
||||
select_logo: "Step 1. Select Logo Image"
|
||||
logo_tip: "Tip: Square images will work best, preferably at least 300×300px"
|
||||
logo_label: "Choose a logo image"
|
||||
logo_drag: "Drag and drop your logo here"
|
||||
review_logo: "Step 2. Review your logo"
|
||||
review_logo: "Step 2. Review Your Logo"
|
||||
review_logo_tip: "Tip: for best results, your logo should fill the available space"
|
||||
logo_placeholder: "Your logo will appear here for review once uploaded"
|
||||
enterprise_about_headline: "Nice one!"
|
||||
@@ -598,14 +747,14 @@ Please follow the instructions there to make your enterprise visible on the Open
|
||||
registration_type_error: "Please choose one. Are you are producer?"
|
||||
registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it."
|
||||
registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other."
|
||||
create_profile: "Create profile"
|
||||
create_profile: "Create Profile"
|
||||
registration_images_headline: "Thanks!"
|
||||
registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)"
|
||||
registration_detail_headline: "Let's get started"
|
||||
registration_detail_headline: "Let's Get Started"
|
||||
registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:"
|
||||
registration_detail_producer: "Woot! First we need to know a little bit about your farm:"
|
||||
registration_detail_name_enterprise: "Enterprise name:"
|
||||
registration_detail_name_producer: "Farm name:"
|
||||
registration_detail_name_enterprise: "Enterprise Name:"
|
||||
registration_detail_name_producer: "Farm Name:"
|
||||
registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm"
|
||||
registration_detail_name_error: "Please choose a unique name for your enterprise"
|
||||
registration_detail_address1: "Address line 1:"
|
||||
@@ -641,6 +790,222 @@ Please follow the instructions there to make your enterprise visible on the Open
|
||||
price_graph: "Price graph"
|
||||
included_tax: "Included tax"
|
||||
remove_tax: "Remove tax"
|
||||
balance: "Balance"
|
||||
transaction: "Transaction"
|
||||
transaction_date: "Date" #Transaction is only in key to avoid conflict with :date
|
||||
payment_state: "Payment status"
|
||||
shipping_state: "Shipping status"
|
||||
value: "Value"
|
||||
balance_due: "Balance due"
|
||||
credit: "Credit"
|
||||
Paid: "Paid"
|
||||
Ready: "Ready"
|
||||
you_have_no_orders_yet: "You have no orders yet"
|
||||
running_balance: "Running balance"
|
||||
outstanding_balance: "Outstanding balance"
|
||||
admin_entreprise_relationships: "Enterprise Relationships"
|
||||
admin_entreprise_relationships_everything: "Everything"
|
||||
admin_entreprise_relationships_permits: "permits"
|
||||
admin_entreprise_relationships_seach_placeholder: "Search"
|
||||
admin_entreprise_relationships_button_create: "Create"
|
||||
admin_entreprise_groups: "Enterprise Groups"
|
||||
admin_entreprise_groups_name: "Name"
|
||||
admin_entreprise_groups_owner: "Owner"
|
||||
admin_entreprise_groups_on_front_page: "On front page ?"
|
||||
admin_entreprise_groups_entreprise: "Enterprises"
|
||||
admin_entreprise_groups_primary_details: "Primary Details"
|
||||
admin_entreprise_groups_data_powertip: "The primary user responsible for this group."
|
||||
admin_entreprise_groups_data_powertip_logo: "This is the logo for the group"
|
||||
admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile"
|
||||
admin_entreprise_groups_about: "About"
|
||||
admin_entreprise_groups_images: "Images"
|
||||
admin_entreprise_groups_contact: "Contact"
|
||||
admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210"
|
||||
admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street"
|
||||
admin_entreprise_groups_contact_city: "Suburb"
|
||||
admin_entreprise_groups_contact_city_placeholder: "eg. Northcote"
|
||||
admin_entreprise_groups_contact_zipcode: "Postcode"
|
||||
admin_entreprise_groups_contact_zipcode_placeholder: "eg. 3070"
|
||||
admin_entreprise_groups_contact_state_id: "State"
|
||||
admin_entreprise_groups_contact_country_id: "Country"
|
||||
admin_entreprise_groups_web: "Web Resources"
|
||||
admin_entreprise_groups_web_twitter: "eg. @the_prof"
|
||||
admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com"
|
||||
admin_order_cycles: "Admin Order Cycles"
|
||||
open: "Open"
|
||||
close: "Close"
|
||||
supplier: "Supplier"
|
||||
coordinator: "Coordinator"
|
||||
distributor: "Distributor"
|
||||
product: "Products"
|
||||
enterprise_fees: "Enterprise Fees"
|
||||
fee_type: "Fee Type"
|
||||
tax_category: "Tax Category"
|
||||
calculator: "Calculator"
|
||||
calculator_values: "Calculator values"
|
||||
new_order_cycles: "New Order Cycles"
|
||||
select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle"
|
||||
edit_order_cycle: "Edit Order Cycle"
|
||||
roles: "Roles"
|
||||
update: "Update"
|
||||
add_producer_property: "Add producer property"
|
||||
admin_settings: "Settings"
|
||||
update_invoice: "Update Invoices"
|
||||
finalise_invoice: "Finalise Invoices"
|
||||
finalise_user_invoices: "Finalise User Invoices"
|
||||
finalise_user_invoice_explained: "Use this button to finalize all invoices in the system for the previous calendar month. This task can be set up to run automatically once a month."
|
||||
manually_run_task: "Manually Run Task "
|
||||
update_user_invoices: "Update User Invoices"
|
||||
update_user_invoice_explained: "Use this button to immediately update invoices for the month to date for each enterprise user in the system. This task can be set up to run automatically every night."
|
||||
auto_finalise_invoices: "Auto-finalise invoices monthly on the 2nd at 1:30am"
|
||||
auto_update_invoices: "Auto-update invoices nightly at 1:00am"
|
||||
in_progress: "In Progress"
|
||||
started_at: "Started at"
|
||||
queued: "Queued"
|
||||
scheduled_for: "Scheduled for"
|
||||
customers: "Customers"
|
||||
please_select_hub: "Please select a Hub"
|
||||
loading_customers: "Loading Customers"
|
||||
no_customers_found: "No customers found"
|
||||
go: "Go"
|
||||
hub: "Hub"
|
||||
accounts_administration_distributor: "accounts administration distributor"
|
||||
accounts_and_billing: "Accounts & Billing"
|
||||
producer: "Producer"
|
||||
product: "Product"
|
||||
price: "Price"
|
||||
on_hand: "On hand"
|
||||
save_changes: "Save Changes"
|
||||
spree_admin_overview_enterprises_header: "My Enterprises"
|
||||
spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES"
|
||||
spree_admin_enterprises_hubs_name: "Name"
|
||||
spree_admin_enterprises_create_new: "CREATE NEW"
|
||||
spree_admin_enterprises_shipping_methods: "Shipping Methods"
|
||||
spree_admin_enterprises_fees: "Enterprise Fees"
|
||||
spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE"
|
||||
spree_admin_enterprises_none_text: "You don't have any enterprises yet"
|
||||
spree_admin_enterprises_producers_name: "Name"
|
||||
spree_admin_enterprises_producers_total_products: "Total Products"
|
||||
spree_admin_enterprises_producers_active_products: "Active Products"
|
||||
spree_admin_enterprises_producers_order_cycles: "Products in OCs"
|
||||
spree_admin_enterprises_producers_order_cycles_title: ""
|
||||
spree_admin_enterprises_tabs_hubs: "HUBS"
|
||||
spree_admin_enterprises_tabs_producers: "PRODUCERS"
|
||||
spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES"
|
||||
spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS"
|
||||
spree_admin_enterprises_producers_orders_cycle_text: "You don't have any active order cycles."
|
||||
spree_admin_enterprises_any_active_products_text: "You don't have any active products."
|
||||
spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT"
|
||||
spree_admin_order_cycles: "Order Cycles"
|
||||
spree_admin_order_cycles_tip: "Order cycles determine when and where your products are available to customers."
|
||||
dashbord: "Dashboard"
|
||||
spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for"
|
||||
spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to"
|
||||
spree_admin_overview_action_required: "Action Required"
|
||||
spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!"
|
||||
change_package: "Change Package"
|
||||
spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under"
|
||||
your_profil_live: "Your profile live"
|
||||
on_ofn_map: "on the Open Food Network map"
|
||||
see: "See"
|
||||
live: "live"
|
||||
manage: "Manage"
|
||||
resend: "Resend"
|
||||
add_and_manage_products: "Add & manage products"
|
||||
add_and_manage_order_cycles: "Add & manage order cycles"
|
||||
manage_order_cycles: "Manage order cycles"
|
||||
manage_products: "Manage products"
|
||||
edit_profile_details: "Edit profile details"
|
||||
edit_profile_details_etc: "Change your profile description, images, etc."
|
||||
start_date: "Start Date"
|
||||
end_date: "End Date"
|
||||
order_cycle: "Order Cycle"
|
||||
group_buy_unit_size: "Group Buy Unit Size"
|
||||
total_qtt_ordered: "Total Quantity Ordered"
|
||||
max_qtt_ordered: "Max Quantity Ordered"
|
||||
current_fulfilled_units: "Current Fulfilled Units"
|
||||
max_fulfilled_units: "Max Fulfilled Units"
|
||||
bulk_management_warning: "WARNING: Some variants do not have a unit value"
|
||||
ask: "Ask?"
|
||||
no_orders_found: "No orders found."
|
||||
order_no: "Order No."
|
||||
weight_volume: "Weight/Volume"
|
||||
remove_tax: "Remove tax"
|
||||
tax_settings: "Tax Settings"
|
||||
products_require_tax_category: "products require tax category"
|
||||
admin_shared_address_1: "Address"
|
||||
admin_shared_address_2: "Address (cont.)"
|
||||
admin_share_city: "City"
|
||||
admin_share_zipcode: "Postcode"
|
||||
admin_share_country: "Country"
|
||||
admin_share_state: "State"
|
||||
hub_sidebar_hubs: "Hubs"
|
||||
hub_sidebar_none_available: "None Available"
|
||||
hub_sidebar_manage: "Manage"
|
||||
hub_sidebar_at_least: "At least one hub must be selected"
|
||||
hub_sidebar_blue: "blue"
|
||||
hub_sidebar_red: "red"
|
||||
shop_trial_in_progress: "Your shopfront trial expires in %{days}."
|
||||
shop_trial_expired: "Good news! We have decided to extend shopfront trials until further notice (probably around March 2015)." #FIXME
|
||||
report_customers_distributor: "Distributor"
|
||||
report_customers_supplier: "Supplier"
|
||||
report_customers_cycle: "Order Cycle"
|
||||
report_customers_type: "Report Type"
|
||||
report_customers_csv: "Download as csv"
|
||||
report_producers: "Producers: "
|
||||
report_type: "Report Type: "
|
||||
report_hubs: "Hubs: "
|
||||
report_payment: "Payment Methods: "
|
||||
report_distributor: "Distributor: "
|
||||
report_payment_by: 'Payments By Type'
|
||||
report_itemised_payment: 'Itemised Payment Totals'
|
||||
report_payment_totals: 'Payment Totals'
|
||||
report_all: 'all'
|
||||
report_order_cycle: "Order Cycle: "
|
||||
report_entreprises: "Enterprises: "
|
||||
report_users: "Users: "
|
||||
initial_invoice_number: "Initial invoice number:"
|
||||
invoice_date: "Invoice date:"
|
||||
due_date: "Due date:"
|
||||
account_code: "Account code:"
|
||||
equals: "Equals"
|
||||
contains: "contains"
|
||||
discount: "Discount"
|
||||
filter_products: "Filter Products"
|
||||
delete_product_variant: "The last variant cannot be deleted!"
|
||||
progress: "progress"
|
||||
saving: "Saving.."
|
||||
success: "success"
|
||||
failure: "failure"
|
||||
unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?"
|
||||
one_product_unsaved: "Changes to one product remain unsaved."
|
||||
products_unsaved: "Changes to %{n} products remain unsaved."
|
||||
add_manager: "Add a manager"
|
||||
is_already_manager: "is already a manager!"
|
||||
no_change_to_save: " No change to save"
|
||||
add_manager: "Add a manager"
|
||||
users: "Users"
|
||||
about: "About"
|
||||
images: "Images"
|
||||
contact: "Contact"
|
||||
web: "Web"
|
||||
primary_details: "Primary Details"
|
||||
adrdress: "Address"
|
||||
contact: "Contact"
|
||||
social: "Social"
|
||||
business_details: "Business Details"
|
||||
properties: "Properties"
|
||||
shipping_methods: "Shipping Methods"
|
||||
payment_methods: "Payment Methods"
|
||||
enterprise_fees: "Enterprise Fees"
|
||||
inventory_settings: "Inventory Settings"
|
||||
tag_rules: "Tag Rules"
|
||||
shop_preferences: "Shop Preferences"
|
||||
validation_msg_relationship_already_established: "^That relationship is already established."
|
||||
validation_msg_at_least_one_hub: "^At least one hub must be selected"
|
||||
validation_msg_product_category_cant_be_blank: "^Product Category cant be blank"
|
||||
validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank"
|
||||
validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer"
|
||||
spree:
|
||||
shipment_states:
|
||||
backorder: backorder
|
||||
|
||||
@@ -34,13 +34,13 @@ describe CheckoutController do
|
||||
flash[:info].should == "The hub you have selected is temporarily closed for orders. Please try again later."
|
||||
end
|
||||
|
||||
it "redirects to the shop when no line items are present" do
|
||||
it "redirects to the cart when some items are out of stock" do
|
||||
controller.stub(:current_distributor).and_return(distributor)
|
||||
controller.stub(:current_order_cycle).and_return(order_cycle)
|
||||
controller.stub(:current_order).and_return(order)
|
||||
order.stub_chain(:insufficient_stock_lines, :present?).and_return true
|
||||
get :edit
|
||||
response.should redirect_to shop_path
|
||||
response.should redirect_to spree.cart_path
|
||||
end
|
||||
|
||||
it "renders when both distributor and order cycle is selected" do
|
||||
|
||||
@@ -2,13 +2,14 @@ require 'spec_helper'
|
||||
|
||||
describe EnterprisesController do
|
||||
describe "shopping for a distributor" do
|
||||
let(:order) { controller.current_order(true) }
|
||||
|
||||
before(:each) do
|
||||
@current_distributor = create(:distributor_enterprise, with_payment_and_shipping: true)
|
||||
@distributor = create(:distributor_enterprise, with_payment_and_shipping: true)
|
||||
@order_cycle1 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 2.days.ago, orders_close_at: 3.days.from_now )
|
||||
@order_cycle2 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 3.days.ago, orders_close_at: 4.days.from_now )
|
||||
controller.current_order(true).distributor = @current_distributor
|
||||
order.set_distributor! @current_distributor
|
||||
end
|
||||
|
||||
it "sets the shop as the distributor on the order when shopping for the distributor" do
|
||||
@@ -52,6 +53,27 @@ describe EnterprisesController do
|
||||
controller.current_order.line_items.size.should == 1
|
||||
end
|
||||
|
||||
describe "when an out of stock item is in the cart" do
|
||||
let(:variant) { create(:variant, on_demand: false, on_hand: 10) }
|
||||
let(:line_item) { create(:line_item, variant: variant) }
|
||||
let(:order_cycle) { create(:simple_order_cycle, distributors: [@distributor], variants: [variant]) }
|
||||
|
||||
before do
|
||||
order.set_distribution! @current_distributor, order_cycle
|
||||
order.line_items << line_item
|
||||
|
||||
Spree::Config.set allow_backorders: false
|
||||
variant.on_hand = 0
|
||||
variant.save!
|
||||
end
|
||||
|
||||
it "redirects to the cart" do
|
||||
spree_get :shop, {id: @current_distributor}
|
||||
|
||||
response.should redirect_to spree.cart_path
|
||||
end
|
||||
end
|
||||
|
||||
it "sets order cycle if only one is available at the chosen distributor" do
|
||||
@order_cycle2.destroy
|
||||
|
||||
|
||||
@@ -69,17 +69,6 @@ describe ShopController do
|
||||
end
|
||||
|
||||
|
||||
describe "producers/suppliers" do
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let(:product) { create(:product, supplier: supplier) }
|
||||
let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
|
||||
|
||||
before do
|
||||
exchange = order_cycle.exchanges.to_enterprises(distributor).outgoing.first
|
||||
exchange.variants << product.master
|
||||
end
|
||||
end
|
||||
|
||||
describe "returning products" do
|
||||
let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
|
||||
let(:exchange) { order_cycle.exchanges.to_enterprises(distributor).outgoing.first }
|
||||
|
||||
@@ -42,6 +42,88 @@ describe Spree::OrdersController do
|
||||
flash[:info].should == "The hub you have selected is temporarily closed for orders. Please try again later."
|
||||
end
|
||||
|
||||
describe "when an item has insufficient stock" do
|
||||
let(:order) { subject.current_order(true) }
|
||||
let(:oc) { create(:simple_order_cycle, distributors: [d], variants: [variant]) }
|
||||
let(:d) { create(:distributor_enterprise, shipping_methods: [create(:shipping_method)], payment_methods: [create(:payment_method)]) }
|
||||
let(:variant) { create(:variant, on_demand: false, on_hand: 5) }
|
||||
let(:line_item) { order.line_items.last }
|
||||
|
||||
before do
|
||||
Spree::Config.allow_backorders = false
|
||||
order.set_distribution! d, oc
|
||||
order.add_variant variant, 5
|
||||
variant.update_attributes! on_hand: 3
|
||||
end
|
||||
|
||||
it "displays a flash message when we view the cart" do
|
||||
spree_get :edit
|
||||
expect(response.status).to eq 200
|
||||
flash[:error].should == "An item in your cart has become unavailable."
|
||||
end
|
||||
end
|
||||
|
||||
describe "returning stock levels in JSON on success" do
|
||||
let(:product) { create(:simple_product) }
|
||||
|
||||
it "returns stock levels as JSON" do
|
||||
controller.stub(:variant_ids_in) { [123] }
|
||||
controller.stub(:stock_levels) { 'my_stock_levels' }
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
|
||||
data = JSON.parse(response.body)
|
||||
data['stock_levels'].should == 'my_stock_levels'
|
||||
end
|
||||
|
||||
describe "generating stock levels" do
|
||||
let!(:order) { create(:order) }
|
||||
let!(:li) { create(:line_item, order: order, variant: v, quantity: 2, max_quantity: 3) }
|
||||
let!(:v) { create(:variant, count_on_hand: 4) }
|
||||
let!(:v2) { create(:variant, count_on_hand: 2) }
|
||||
|
||||
before do
|
||||
order.reload
|
||||
controller.stub(:current_order) { order }
|
||||
end
|
||||
|
||||
it "returns a hash with variant id, quantity, max_quantity and stock on hand" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes all line items, even when the variant_id is not specified" do
|
||||
controller.stock_levels(order, []).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4}}
|
||||
end
|
||||
|
||||
it "includes an empty quantity entry for variants that aren't in the order" do
|
||||
controller.stock_levels(order, [v.id, v2.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 4},
|
||||
v2.id => {quantity: 0, max_quantity: 0, on_hand: 2}}
|
||||
end
|
||||
|
||||
describe "encoding Infinity" do
|
||||
let!(:v) { create(:variant, on_demand: true, count_on_hand: 0) }
|
||||
|
||||
it "encodes Infinity as a large, finite integer" do
|
||||
controller.stock_levels(order, [v.id]).should ==
|
||||
{v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "extracts variant ids from the populator" do
|
||||
variants_h = [{:variant_id=>"900", :quantity=>2, :max_quantity=>nil},
|
||||
{:variant_id=>"940", :quantity=>3, :max_quantity=>3}]
|
||||
|
||||
controller.variant_ids_in(variants_h).should == [900, 940]
|
||||
end
|
||||
end
|
||||
|
||||
context "adding a group buy product to the cart" do
|
||||
it "sets a variant attribute for the max quantity" do
|
||||
distributor_product = create(:distributor_enterprise)
|
||||
@@ -59,7 +141,8 @@ describe Spree::OrdersController do
|
||||
|
||||
it "returns HTTP success when successful" do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate).and_return true
|
||||
populator.stub(:populate) { true }
|
||||
populator.stub(:variants_h) { {} }
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 200
|
||||
end
|
||||
@@ -68,7 +151,7 @@ describe Spree::OrdersController do
|
||||
Spree::OrderPopulator.stub(:new).and_return(populator = double())
|
||||
populator.stub(:populate).and_return false
|
||||
xhr :post, :populate, use_route: :spree, format: :json
|
||||
response.status.should == 402
|
||||
response.status.should == 412
|
||||
end
|
||||
|
||||
it "tells populator to overwrite" do
|
||||
@@ -78,11 +161,11 @@ describe Spree::OrdersController do
|
||||
end
|
||||
end
|
||||
|
||||
context "removing line items from cart" do
|
||||
describe "removing line items from cart" do
|
||||
describe "when I pass params that includes a line item no longer in our cart" do
|
||||
it "should silently ignore the missing line item" do
|
||||
order = subject.current_order(true)
|
||||
li = order.add_variant(create(:simple_product, on_hand: 110).master)
|
||||
li = order.add_variant(create(:simple_product, on_hand: 110).variants.first)
|
||||
spree_get :update, order: { line_items_attributes: {
|
||||
"0" => {quantity: "0", id: "9999"},
|
||||
"1" => {quantity: "99", id: li.id}
|
||||
|
||||
@@ -11,7 +11,7 @@ feature "full-page cart", js: true do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) }
|
||||
let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
|
||||
@@ -9,7 +9,7 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
|
||||
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) }
|
||||
let(:product) { create(:simple_product, supplier: supplier) }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
let(:address) { create(:address, firstname: "Foo", lastname: "Bar") }
|
||||
@@ -23,7 +23,7 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
|
||||
it "does not not render the login form when logged in" do
|
||||
quick_login_as user
|
||||
visit checkout_path
|
||||
visit checkout_path
|
||||
within "section[role='main']" do
|
||||
page.should_not have_content "Login"
|
||||
page.should have_checkout_details
|
||||
@@ -31,7 +31,7 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
end
|
||||
|
||||
it "renders the login buttons when logged out" do
|
||||
visit checkout_path
|
||||
visit checkout_path
|
||||
within "section[role='main']" do
|
||||
page.should have_content "Login"
|
||||
click_button "Login"
|
||||
@@ -53,9 +53,8 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
end
|
||||
|
||||
it "allows user to checkout as guest" do
|
||||
visit checkout_path
|
||||
visit checkout_path
|
||||
checkout_as_guest
|
||||
page.should have_checkout_details
|
||||
page.should have_checkout_details
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [variant]) }
|
||||
let(:enterprise_fee) { create(:enterprise_fee, amount: 1.23, tax_category: product.tax_category) }
|
||||
let(:product) { create(:taxed_product, supplier: supplier, price: 10, zone: zone, tax_rate_amount: 0.1) }
|
||||
let(:variant) { product.variants.first }
|
||||
let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
|
||||
before do
|
||||
@@ -45,6 +46,22 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
distributor.shipping_methods << sm3
|
||||
end
|
||||
|
||||
describe "when I have an out of stock product in my cart" do
|
||||
before do
|
||||
Spree::Config.set allow_backorders: false
|
||||
variant.on_hand = 0
|
||||
variant.save!
|
||||
end
|
||||
|
||||
it "returns me to the cart with an error message" do
|
||||
visit checkout_path
|
||||
|
||||
page.should_not have_selector 'closing', text: "Checkout now"
|
||||
page.should have_selector 'closing', text: "Your shopping cart"
|
||||
page.should have_content "An item in your cart has become unavailable"
|
||||
end
|
||||
end
|
||||
|
||||
context "on the checkout page" do
|
||||
before do
|
||||
visit checkout_path
|
||||
@@ -213,6 +230,18 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
page.should have_content "Your order has been processed successfully"
|
||||
end
|
||||
|
||||
it "takes us to the cart page with an error when a product becomes out of stock just before we purchase", js: true do
|
||||
Spree::Config.set allow_backorders: false
|
||||
variant.on_hand = 0
|
||||
variant.save!
|
||||
|
||||
place_order
|
||||
|
||||
page.should_not have_content "Your order has been processed successfully"
|
||||
page.should have_selector 'closing', text: "Your shopping cart"
|
||||
page.should have_content "Out of Stock"
|
||||
end
|
||||
|
||||
context "when we are charged a shipping fee" do
|
||||
before { choose sm2.name }
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
describe "with variants on the product" do
|
||||
let(:variant) { create(:variant, product: product, on_hand: 10 ) }
|
||||
before do
|
||||
add_product_and_variant_to_order_cycle(exchange, product, variant)
|
||||
add_variant_to_order_cycle(exchange, variant)
|
||||
set_order_cycle(order, oc1)
|
||||
visit shop_path
|
||||
end
|
||||
@@ -215,9 +215,11 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) }
|
||||
let(:product) { create(:simple_product) }
|
||||
let(:variant) { create(:variant, product: product) }
|
||||
let(:variant2) { create(:variant, product: product) }
|
||||
|
||||
before do
|
||||
add_product_and_variant_to_order_cycle(exchange, product, variant)
|
||||
add_variant_to_order_cycle(exchange, variant)
|
||||
add_variant_to_order_cycle(exchange, variant2)
|
||||
set_order_cycle(order, oc1)
|
||||
visit shop_path
|
||||
end
|
||||
@@ -235,6 +237,98 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
|
||||
Spree::LineItem.where(id: li).should be_empty
|
||||
end
|
||||
|
||||
describe "when a product goes out of stock just before it's added to the cart" do
|
||||
it "stops the attempt, shows an error message and refreshes the products asynchronously" do
|
||||
variant.update_attributes! on_hand: 0
|
||||
|
||||
# -- Messaging
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
within(".out-of-stock-modal") do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock."
|
||||
end
|
||||
|
||||
# -- Page updates
|
||||
# Update amount in cart
|
||||
page.should have_field "variants[#{variant.id}]", with: '0', disabled: true
|
||||
page.should have_field "variants[#{variant2.id}]", with: ''
|
||||
|
||||
# Update amount available in product list
|
||||
# If amount falls to zero, variant should be greyed out and input disabled
|
||||
page.should have_selector "#variant-#{variant.id}.out-of-stock"
|
||||
page.should have_selector "#variants_#{variant.id}[max='0']"
|
||||
page.should have_selector "#variants_#{variant.id}[disabled='disabled']"
|
||||
end
|
||||
|
||||
context "group buy products" do
|
||||
let(:product) { create(:simple_product, group_buy: true) }
|
||||
|
||||
it "does the same" do
|
||||
# -- Place in cart so we can set max_quantity, then make out of stock
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
variant.update_attributes! on_hand: 0
|
||||
|
||||
# -- Messaging
|
||||
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
within(".out-of-stock-modal") do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock."
|
||||
end
|
||||
|
||||
# -- Page updates
|
||||
# Update amount in cart
|
||||
page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: '0', disabled: true
|
||||
|
||||
# Update amount available in product list
|
||||
# If amount falls to zero, variant should be greyed out and input disabled
|
||||
page.should have_selector "#variant-#{variant.id}.out-of-stock"
|
||||
page.should have_selector "#variants_#{variant.id}_max[max='0']"
|
||||
page.should have_selector "#variants_#{variant.id}_max[disabled='disabled']"
|
||||
end
|
||||
end
|
||||
|
||||
context "when the update is for another product" do
|
||||
it "updates quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
variant.update_attributes! on_hand: 0
|
||||
|
||||
fill_in "variants[#{variant2.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
within(".out-of-stock-modal") do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock."
|
||||
end
|
||||
end
|
||||
|
||||
context "group buy products" do
|
||||
let(:product) { create(:simple_product, group_buy: true) }
|
||||
|
||||
it "updates max_quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: '1'
|
||||
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '2'
|
||||
wait_until { !cart_dirty }
|
||||
variant.update_attributes! on_hand: 1
|
||||
|
||||
fill_in "variants[#{variant2.id}]", with: '1'
|
||||
wait_until { !cart_dirty }
|
||||
|
||||
within(".out-of-stock-modal") do
|
||||
page.should have_content "stock levels for one or more of the products in your cart have reduced"
|
||||
page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no order cycles are available" do
|
||||
@@ -260,7 +354,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
let(:variant) { create(:variant, product: product) }
|
||||
|
||||
before do
|
||||
add_product_and_variant_to_order_cycle(exchange, product, variant)
|
||||
add_variant_to_order_cycle(exchange, variant)
|
||||
set_order_cycle(order, oc1)
|
||||
distributor.require_login = true
|
||||
distributor.save!
|
||||
|
||||
@@ -126,6 +126,85 @@ describe 'Cart service', ->
|
||||
$httpBackend.flush()
|
||||
expect(Cart.scheduleRetry).toHaveBeenCalled()
|
||||
|
||||
describe "verifying stock levels after update", ->
|
||||
describe "when an item is out of stock", ->
|
||||
it "reduces the quantity in the cart", ->
|
||||
li = {variant: {id: 1}, quantity: 5}
|
||||
stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.quantity).toEqual 0
|
||||
expect(li.max_quantity).toBeUndefined()
|
||||
|
||||
it "reduces the max_quantity in the cart", ->
|
||||
li = {variant: {id: 1}, quantity: 5, max_quantity: 6}
|
||||
stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.max_quantity).toEqual 0
|
||||
|
||||
it "resets the count on hand available", ->
|
||||
li = {variant: {id: 1, count_on_hand: 10}, quantity: 5}
|
||||
stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.variant.count_on_hand).toEqual 0
|
||||
|
||||
describe "when the quantity available is less than that requested", ->
|
||||
it "reduces the quantity in the cart", ->
|
||||
li = {variant: {id: 1}, quantity: 6}
|
||||
stockLevels = {1: {quantity: 5, on_hand: 5}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.quantity).toEqual 5
|
||||
expect(li.max_quantity).toBeUndefined()
|
||||
|
||||
it "reduces the max_quantity in the cart", ->
|
||||
li = {variant: {id: 1}, quantity: 6, max_quantity: 7}
|
||||
stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.max_quantity).toEqual 5
|
||||
|
||||
it "resets the count on hand available", ->
|
||||
li = {variant: {id: 1}, quantity: 6}
|
||||
stockLevels = {1: {quantity: 5, on_hand: 6}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.variant.count_on_hand).toEqual 6
|
||||
|
||||
describe "when the client-side quantity has been increased during the request", ->
|
||||
it "does not reset the quantity", ->
|
||||
li = {variant: {id: 1}, quantity: 6}
|
||||
stockLevels = {1: {quantity: 5, on_hand: 6}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.quantity).toEqual 6
|
||||
expect(li.max_quantity).toBeUndefined()
|
||||
|
||||
it "does not reset the max_quantity", ->
|
||||
li = {variant: {id: 1}, quantity: 5, max_quantity: 7}
|
||||
stockLevels = {1: {quantity: 5, max_quantity: 6, on_hand: 7}}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels stockLevels
|
||||
expect(li.quantity).toEqual 5
|
||||
expect(li.max_quantity).toEqual 7
|
||||
|
||||
describe "when the client-side quantity has been changed from 0 to 1 during the request", ->
|
||||
it "does not reset the quantity", ->
|
||||
li = {variant: {id: 1}, quantity: 1}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels {}
|
||||
expect(li.quantity).toEqual 1
|
||||
expect(li.max_quantity).toBeUndefined()
|
||||
|
||||
it "does not reset the max_quantity", ->
|
||||
li = {variant: {id: 1}, quantity: 1, max_quantity: 1}
|
||||
spyOn(Cart, 'line_items_present').andReturn [li]
|
||||
Cart.compareAndNotifyStockLevels {}
|
||||
expect(li.quantity).toEqual 1
|
||||
expect(li.max_quantity).toEqual 1
|
||||
|
||||
it "pops the queue", ->
|
||||
Cart.update_enqueued = true
|
||||
spyOn(Cart, 'scheduleUpdate')
|
||||
|
||||
@@ -5,7 +5,7 @@ describe 'Checkout service', ->
|
||||
Navigation = null
|
||||
flash = null
|
||||
scope = null
|
||||
FlashLoaderMock =
|
||||
FlashLoaderMock =
|
||||
loadFlash: (arg)->
|
||||
paymentMethods = [{
|
||||
id: 99
|
||||
@@ -41,10 +41,10 @@ describe 'Checkout service', ->
|
||||
|
||||
module 'Darkswarm'
|
||||
module ($provide)->
|
||||
$provide.value "RailsFlashLoader", FlashLoaderMock
|
||||
$provide.value "currentOrder", orderData
|
||||
$provide.value "shippingMethods", shippingMethods
|
||||
$provide.value "paymentMethods", paymentMethods
|
||||
$provide.value "RailsFlashLoader", FlashLoaderMock
|
||||
$provide.value "currentOrder", orderData
|
||||
$provide.value "shippingMethods", shippingMethods
|
||||
$provide.value "paymentMethods", paymentMethods
|
||||
null
|
||||
|
||||
inject ($injector, _$httpBackend_, $rootScope)->
|
||||
@@ -80,26 +80,33 @@ describe 'Checkout service', ->
|
||||
it 'Gets the current payment method', ->
|
||||
expect(Checkout.paymentMethod()).toEqual null
|
||||
Checkout.order.payment_method_id = 99
|
||||
expect(Checkout.paymentMethod()).toEqual paymentMethods[0]
|
||||
expect(Checkout.paymentMethod()).toEqual paymentMethods[0]
|
||||
|
||||
it "Posts the Checkout to the server", ->
|
||||
$httpBackend.expectPUT("/checkout", {order: Checkout.preprocess()}).respond 200, {path: "test"}
|
||||
Checkout.submit()
|
||||
$httpBackend.flush()
|
||||
|
||||
it "sends flash messages to the flash service", ->
|
||||
spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location
|
||||
$httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}}
|
||||
Checkout.submit()
|
||||
describe "when there is an error", ->
|
||||
it "redirects when a redirect is given", ->
|
||||
$httpBackend.expectPUT("/checkout").respond 400, {path: 'path'}
|
||||
Checkout.submit()
|
||||
$httpBackend.flush()
|
||||
expect(Navigation.go).toHaveBeenCalledWith 'path'
|
||||
|
||||
$httpBackend.flush()
|
||||
expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"}
|
||||
it "sends flash messages to the flash service", ->
|
||||
spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location
|
||||
$httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}}
|
||||
Checkout.submit()
|
||||
|
||||
it "puts errors into the scope", ->
|
||||
$httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}}
|
||||
Checkout.submit()
|
||||
$httpBackend.flush()
|
||||
expect(Checkout.errors).toEqual {error: "frogs"}
|
||||
$httpBackend.flush()
|
||||
expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"}
|
||||
|
||||
it "puts errors into the scope", ->
|
||||
$httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}}
|
||||
Checkout.submit()
|
||||
$httpBackend.flush()
|
||||
expect(Checkout.errors).toEqual {error: "frogs"}
|
||||
|
||||
describe "data preprocessing", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -42,6 +42,41 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
describe "capping quantity at stock level" do
|
||||
let!(:v) { create(:variant, on_demand: false, on_hand: 10) }
|
||||
let!(:li) { create(:line_item, variant: v, quantity: 10, max_quantity: 10) }
|
||||
|
||||
before do
|
||||
v.update_attributes! on_hand: 5
|
||||
end
|
||||
|
||||
it "caps quantity" do
|
||||
li.cap_quantity_at_stock!
|
||||
li.reload.quantity.should == 5
|
||||
end
|
||||
|
||||
it "caps max_quantity" do
|
||||
li.cap_quantity_at_stock!
|
||||
li.reload.max_quantity.should == 5
|
||||
end
|
||||
|
||||
it "works for products without max_quantity" do
|
||||
li.update_column :max_quantity, nil
|
||||
li.cap_quantity_at_stock!
|
||||
li.reload
|
||||
li.quantity.should == 5
|
||||
li.max_quantity.should be_nil
|
||||
end
|
||||
|
||||
it "does nothing for on_demand items" do
|
||||
v.update_attributes! on_demand: true
|
||||
li.cap_quantity_at_stock!
|
||||
li.reload
|
||||
li.quantity.should == 10
|
||||
li.max_quantity.should == 10
|
||||
end
|
||||
end
|
||||
|
||||
describe "calculating price with adjustments" do
|
||||
it "does not return fractional cents" do
|
||||
li = LineItem.new
|
||||
|
||||
@@ -149,13 +149,15 @@ module Spree
|
||||
end
|
||||
|
||||
describe "attempt_cart_add" do
|
||||
it "performs additional validations" do
|
||||
variant = double(:variant)
|
||||
quantity = 123
|
||||
let(:variant) { double(:variant, on_hand: 250) }
|
||||
let(:quantity) { 123 }
|
||||
|
||||
before do
|
||||
Spree::Variant.stub(:find).and_return(variant)
|
||||
VariantOverride.stub(:for).and_return(nil)
|
||||
end
|
||||
|
||||
op.should_receive(:check_stock_levels).with(variant, quantity).and_return(true)
|
||||
it "performs additional validations" do
|
||||
op.should_receive(:check_order_cycle_provided_for).with(variant).and_return(true)
|
||||
op.should_receive(:check_variant_available_under_distribution).with(variant).
|
||||
and_return(true)
|
||||
@@ -163,8 +165,76 @@ module Spree
|
||||
|
||||
op.attempt_cart_add(333, quantity.to_s)
|
||||
end
|
||||
|
||||
it "filters quantities through #quantities_to_add" do
|
||||
op.should_receive(:quantities_to_add).with(variant, 123, 123).
|
||||
and_return([5, 5])
|
||||
|
||||
op.stub(:check_order_cycle_provided_for) { true }
|
||||
op.stub(:check_variant_available_under_distribution) { true }
|
||||
|
||||
order.should_receive(:add_variant).with(variant, 5, 5, currency)
|
||||
|
||||
op.attempt_cart_add(333, quantity.to_s, quantity.to_s)
|
||||
end
|
||||
|
||||
it "removes variants which have become out of stock" do
|
||||
op.should_receive(:quantities_to_add).with(variant, 123, 123).
|
||||
and_return([0, 0])
|
||||
|
||||
op.stub(:check_order_cycle_provided_for) { true }
|
||||
op.stub(:check_variant_available_under_distribution) { true }
|
||||
|
||||
order.should_receive(:remove_variant).with(variant)
|
||||
order.should_receive(:add_variant).never
|
||||
|
||||
op.attempt_cart_add(333, quantity.to_s, quantity.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe "quantities_to_add" do
|
||||
let(:v) { double(:variant, on_hand: 10) }
|
||||
|
||||
context "when backorders are not allowed" do
|
||||
before { Spree::Config.allow_backorders = false }
|
||||
|
||||
context "when max_quantity is not provided" do
|
||||
it "returns full amount when available" do
|
||||
op.quantities_to_add(v, 5, nil).should == [5, nil]
|
||||
end
|
||||
|
||||
it "returns a limited amount when not entirely available" do
|
||||
op.quantities_to_add(v, 15, nil).should == [10, nil]
|
||||
end
|
||||
end
|
||||
|
||||
context "when max_quantity is provided" do
|
||||
it "returns full amount when available" do
|
||||
op.quantities_to_add(v, 5, 6).should == [5, 6]
|
||||
end
|
||||
|
||||
it "returns a limited amount when not entirely available" do
|
||||
op.quantities_to_add(v, 15, 16).should == [10, 10]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when backorders are allowed" do
|
||||
around do |example|
|
||||
Spree::Config.allow_backorders = true
|
||||
example.run
|
||||
Spree::Config.allow_backorders = false
|
||||
end
|
||||
|
||||
it "does not limit quantity" do
|
||||
op.quantities_to_add(v, 15, nil).should == [15, nil]
|
||||
end
|
||||
|
||||
it "does not limit max_quantity" do
|
||||
op.quantities_to_add(v, 15, 16).should == [15, 16]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
describe "determining if distributor can supply products in cart" do
|
||||
|
||||
@@ -366,6 +366,7 @@ describe Spree::Order do
|
||||
let(:order) { create(:order) }
|
||||
let(:v1) { create(:variant) }
|
||||
let(:v2) { create(:variant) }
|
||||
let(:v3) { create(:variant) }
|
||||
|
||||
before do
|
||||
order.add_variant v1
|
||||
@@ -376,6 +377,12 @@ describe Spree::Order do
|
||||
order.remove_variant v1
|
||||
order.line_items(:reload).map(&:variant).should == [v2]
|
||||
end
|
||||
|
||||
it "does nothing when there is no matching line item" do
|
||||
expect do
|
||||
order.remove_variant v3
|
||||
end.to change(order.line_items(:reload), :count).by(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe "emptying the order" do
|
||||
|
||||
@@ -53,23 +53,6 @@ describe Spree.user_class do
|
||||
create(:user)
|
||||
end.to enqueue_job ConfirmSignupJob
|
||||
end
|
||||
|
||||
it "should not create a customer" do
|
||||
expect do
|
||||
create(:user)
|
||||
end.to change(Customer, :count).by(0)
|
||||
end
|
||||
|
||||
describe "when a customer record exists" do
|
||||
let!(:customer) { create(:customer, user: nil) }
|
||||
|
||||
it "should not create a customer" do
|
||||
expect(customer.user).to be nil
|
||||
user = create(:user, email: customer.email)
|
||||
customer.reload
|
||||
expect(customer.user).to eq user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "known_users" do
|
||||
|
||||
@@ -18,7 +18,7 @@ module ShopWorkflow
|
||||
|
||||
def add_product_to_cart
|
||||
populator = Spree::OrderPopulator.new(order, order.currency)
|
||||
populator.populate(variants: {product.master.id => 1})
|
||||
populator.populate(variants: {product.variants.first.id => 1})
|
||||
|
||||
# Recalculate fee totals
|
||||
order.update_distribution_charge!
|
||||
@@ -28,15 +28,10 @@ module ShopWorkflow
|
||||
find("dd a", text: name).trigger "click"
|
||||
end
|
||||
|
||||
def add_product_to_order_cycle(exchange, product)
|
||||
exchange.variants << product.master
|
||||
def add_variant_to_order_cycle(exchange, variant)
|
||||
exchange.variants << variant
|
||||
end
|
||||
|
||||
def add_product_and_variant_to_order_cycle(exchange, product, variant)
|
||||
exchange.variants << product.master
|
||||
exchange.variants << variant
|
||||
end
|
||||
|
||||
def set_order_cycle(order, order_cycle)
|
||||
order.update_attribute(:order_cycle, order_cycle)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user