Merging master into require_standard_variant
2
Gemfile
@@ -28,7 +28,7 @@ gem 'angularjs-rails', '1.2.13'
|
||||
gem 'bugsnag'
|
||||
gem 'newrelic_rpm'
|
||||
gem 'haml'
|
||||
gem 'sass', "~> 3.2"
|
||||
gem 'sass', "~> 3.3"
|
||||
gem 'sass-rails', '~> 3.2.3', groups: [:default, :assets]
|
||||
gem 'aws-sdk'
|
||||
gem 'db2fog'
|
||||
|
||||
33
Gemfile.lock
@@ -177,7 +177,7 @@ GEM
|
||||
celluloid (0.15.2)
|
||||
timers (~> 1.1.0)
|
||||
chronic (0.10.2)
|
||||
chunky_png (1.3.0)
|
||||
chunky_png (1.3.4)
|
||||
climate_control (0.0.3)
|
||||
activesupport (>= 3.0)
|
||||
cliver (0.3.2)
|
||||
@@ -197,12 +197,22 @@ GEM
|
||||
active_link_to (~> 1.0.0)
|
||||
paperclip (>= 2.3.0)
|
||||
rails (>= 3.0.0)
|
||||
compass (0.12.4)
|
||||
compass (1.0.3)
|
||||
chunky_png (~> 1.2)
|
||||
fssm (>= 0.2.7)
|
||||
sass (~> 3.2.17)
|
||||
compass-rails (1.0.3)
|
||||
compass (>= 0.12.2, < 0.14)
|
||||
compass-core (~> 1.0.2)
|
||||
compass-import-once (~> 1.0.5)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
sass (>= 3.3.13, < 3.5)
|
||||
compass-core (1.0.3)
|
||||
multi_json (~> 1.0)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
compass-import-once (1.0.5)
|
||||
sass (>= 3.2, < 3.5)
|
||||
compass-rails (2.0.4)
|
||||
compass (~> 1.0.0)
|
||||
sass-rails (<= 5.0.1)
|
||||
sprockets (< 2.13)
|
||||
crack (0.4.1)
|
||||
safe_yaml (~> 0.9.0)
|
||||
css_parser (1.3.5)
|
||||
@@ -267,10 +277,9 @@ GEM
|
||||
foundation-icons-sass-rails (3.0.0)
|
||||
railties (>= 3.1.1)
|
||||
sass-rails (>= 3.1.1)
|
||||
foundation-rails (5.2.2.0)
|
||||
foundation-rails (5.5.0.0)
|
||||
railties (>= 3.1.0)
|
||||
sass (>= 3.2.0)
|
||||
fssm (0.2.10)
|
||||
sass (>= 3.2.0, < 3.4)
|
||||
fuubar (1.3.3)
|
||||
rspec (>= 2.14.0, < 3.1.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
@@ -338,7 +347,7 @@ GEM
|
||||
railties (>= 3.1)
|
||||
money (5.1.1)
|
||||
i18n (~> 0.6.0)
|
||||
multi_json (1.10.1)
|
||||
multi_json (1.11.0)
|
||||
multi_xml (0.5.5)
|
||||
net-scp (1.1.2)
|
||||
net-ssh (>= 2.6.5)
|
||||
@@ -448,7 +457,7 @@ GEM
|
||||
ruby-hmac (0.4.0)
|
||||
ruby-progressbar (1.7.1)
|
||||
safe_yaml (0.9.5)
|
||||
sass (3.2.19)
|
||||
sass (3.3.14)
|
||||
sass-rails (3.2.6)
|
||||
railties (~> 3.2.0)
|
||||
sass (>= 3.1.10)
|
||||
@@ -577,7 +586,7 @@ DEPENDENCIES
|
||||
representative_view
|
||||
roadie-rails (~> 1.0.3)
|
||||
rspec-rails
|
||||
sass (~> 3.2)
|
||||
sass (~> 3.3)
|
||||
sass-rails (~> 3.2.3)
|
||||
shoulda-matchers
|
||||
simple_form!
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -36,4 +36,4 @@
|
||||
$ ->
|
||||
# Hacky fix for issue - http://foundation.zurb.com/forum/posts/2112-foundation-5100-syntax-error-in-js
|
||||
Foundation.set_namespace ""
|
||||
#$(document).foundation()
|
||||
$(document).foundation()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart) ->
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) ->
|
||||
$scope.Products = Products
|
||||
$scope.Cart = Cart
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
@@ -9,6 +9,9 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
|
||||
$scope.limit = 3
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
|
||||
$scope.$watch "Products.loading", (newValue, oldValue) ->
|
||||
$scope.$broadcast("loadFilterSelectors") if !newValue
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < Products.products.length
|
||||
$scope.limit = $scope.limit + 1
|
||||
@@ -17,3 +20,17 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
|
||||
code = e.keyCode || e.which
|
||||
if code == 13
|
||||
e.preventDefault()
|
||||
|
||||
$scope.appliedTaxonsList = ->
|
||||
$scope.activeTaxons.map( (taxon_id) ->
|
||||
Taxons.taxons_by_id[taxon_id].name
|
||||
).join(" & ") if $scope.activeTaxons?
|
||||
|
||||
$scope.appliedPropertiesList = ->
|
||||
$scope.activeProperties.map( (property_id) ->
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join(" & ") if $scope.activeProperties?
|
||||
|
||||
$scope.clearAll = ->
|
||||
$scope.query = ""
|
||||
FilterSelectorsService.clearAll()
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
|
||||
Darkswarm.directive "filterSelector", (FilterSelectorsService)->
|
||||
# Automatically builds activeSelectors for taxons
|
||||
# Lots of magic here
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
scope:
|
||||
objects: "&"
|
||||
results: "="
|
||||
templateUrl: "taxon_selector.html"
|
||||
activeSelectors: "="
|
||||
allSelectors: "=?" # Optional
|
||||
templateUrl: "filter_selector.html"
|
||||
|
||||
link: (scope, elem, attr)->
|
||||
selectors_by_id = {}
|
||||
selectors = null # To get scoping/closure right
|
||||
|
||||
scope.emit = ->
|
||||
scope.results = selectors.filter (selector)->
|
||||
scope.activeSelectors = selectors.filter (selector)->
|
||||
selector.active
|
||||
.map (selector)->
|
||||
selector.taxon.id
|
||||
selector.object.id
|
||||
|
||||
# Build hash of unique taxons, each of which gets an ActiveSelector
|
||||
# This can be called from a parent scope
|
||||
# when data has been loaded, in order to pass
|
||||
# selectors up
|
||||
scope.$on 'loadFilterSelectors', ->
|
||||
scope.allSelectors = scope.selectors()
|
||||
|
||||
scope.$watchCollection "selectors()", (newValue, oldValue) ->
|
||||
scope.allSelectors = scope.selectors()
|
||||
|
||||
# Build a list of selectors
|
||||
scope.selectors = ->
|
||||
taxons = {}
|
||||
selectors = []
|
||||
for object in scope.objects()
|
||||
for taxon in object.taxons
|
||||
taxons[taxon.id] = taxon
|
||||
if object.supplied_taxons
|
||||
for taxon in object.supplied_taxons
|
||||
taxons[taxon.id] = taxon
|
||||
|
||||
# Generate a selector for each taxon.
|
||||
# Generate a selector for each object.
|
||||
# NOTE: THESE ARE MEMOIZED to stop new selectors from being created constantly, otherwise function always returns non-identical results
|
||||
# This means the $digest cycle can never close and times out
|
||||
# See http://stackoverflow.com/questions/19306452/how-to-fix-10-digest-iterations-reached-aborting-error-in-angular-1-2-fil
|
||||
for id, taxon of taxons
|
||||
selectors = []
|
||||
for id, object of scope.objects()
|
||||
if selector = selectors_by_id[id]
|
||||
selectors.push selector
|
||||
else
|
||||
selector = selectors_by_id[id] = FilterSelectorsService.new
|
||||
taxon: taxon
|
||||
object: object
|
||||
selectors.push selector
|
||||
selectors
|
||||
|
||||
scope.ifDefined = (value, if_undefined) ->
|
||||
if angular.isDefined(value)
|
||||
value
|
||||
else
|
||||
if_undefined
|
||||
@@ -0,0 +1,72 @@
|
||||
Darkswarm.directive 'singleLineSelectors', ($timeout, $filter) ->
|
||||
restrict: 'E'
|
||||
templateUrl: "single_line_selectors.html"
|
||||
scope:
|
||||
objects: "&"
|
||||
activeSelectors: "="
|
||||
link: (scope,element,attrs) ->
|
||||
scope.fitting = false
|
||||
|
||||
scope.overFlowSelectors = ->
|
||||
return [] unless scope.allSelectors?
|
||||
$filter('filter')(scope.allSelectors, { fits: false })
|
||||
|
||||
scope.selectedOverFlowSelectors = ->
|
||||
$filter('filter')(scope.overFlowSelectors(), { active: true })
|
||||
|
||||
# had to duplicate this to make overflow selectors work
|
||||
scope.emit = ->
|
||||
scope.activeSelectors = scope.allSelectors.filter (selector)->
|
||||
selector.active
|
||||
.map (selector)->
|
||||
selector.object.id
|
||||
|
||||
# From: http://stackoverflow.com/questions/4298612/jquery-how-to-call-resize-event-only-once-its-finished-resizing
|
||||
debouncer = (func, timeout) ->
|
||||
timeoutID = undefined
|
||||
timeout = timeout or 50
|
||||
->
|
||||
subject = this
|
||||
args = arguments
|
||||
clearTimeout timeoutID
|
||||
timeoutID = setTimeout(->
|
||||
func.apply subject, Array::slice.call(args)
|
||||
, timeout)
|
||||
|
||||
loadWidths = ->
|
||||
$(element).find("li").not(".more").each (i) ->
|
||||
scope.allSelectors[i].width = $(this).outerWidth(true)
|
||||
return null # So we don't exit the loop weirdly
|
||||
|
||||
|
||||
fit = ->
|
||||
used = $(element).find("li.more").outerWidth(true)
|
||||
used += selector.width for selector in scope.allSelectors when selector.fits
|
||||
available = $(element).parent(".filter-shopfront").innerWidth() - used
|
||||
if available > 0
|
||||
for selector in scope.allSelectors when !selector.fits
|
||||
available -= selector.width
|
||||
selector.fits = true if available > 0
|
||||
else
|
||||
if scope.allSelectors.length > 0
|
||||
for i in [scope.allSelectors.length-1..0]
|
||||
selector = scope.allSelectors[i]
|
||||
if !selector.fits
|
||||
continue
|
||||
else
|
||||
if available < 0
|
||||
selector.fits = false
|
||||
available += selector.width
|
||||
scope.fitting = false
|
||||
|
||||
scope.$watchCollection "allSelectors", ->
|
||||
if scope.allSelectors?
|
||||
scope.fitting = true
|
||||
selector.fits = true for selector in scope.allSelectors
|
||||
$timeout(loadWidths, 0, true).then ->
|
||||
$timeout fit, 0, true
|
||||
|
||||
$(window).resize debouncer (e) ->
|
||||
scope.fitting = true
|
||||
if scope.allSelectors?
|
||||
$timeout fit, 0, true
|
||||
@@ -0,0 +1,16 @@
|
||||
Darkswarm.filter 'properties', ()->
|
||||
# Filter anything that responds to object.properties
|
||||
(objects, ids) ->
|
||||
objects ||= []
|
||||
ids ?= []
|
||||
if ids.length == 0
|
||||
# No properties selected, pass all objects through.
|
||||
objects
|
||||
else
|
||||
objects.filter (obj)->
|
||||
properties = obj.properties
|
||||
# Combine object properties with supplied properties, if they exist.
|
||||
# properties = properties.concat obj.supplied_properties if obj.supplied_properties
|
||||
# Match property array.
|
||||
properties.some (property)->
|
||||
property.id in ids
|
||||
@@ -0,0 +1,7 @@
|
||||
Darkswarm.filter 'propertiesOf', ->
|
||||
(objects)->
|
||||
properties = {}
|
||||
for object in objects
|
||||
for property in object.properties
|
||||
properties[property.id] = property
|
||||
properties
|
||||
10
app/assets/javascripts/darkswarm/filters/taxons_of.js.coffee
Normal file
@@ -0,0 +1,10 @@
|
||||
Darkswarm.filter 'taxonsOf', ->
|
||||
(objects)->
|
||||
taxons = {}
|
||||
for object in objects
|
||||
for taxon in object.taxons
|
||||
taxons[taxon.id] = taxon
|
||||
if object.supplied_taxons
|
||||
for taxon in object.supplied_taxons
|
||||
taxons[taxon.id] = taxon
|
||||
taxons
|
||||
@@ -25,7 +25,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
|
||||
active: Navigation.active
|
||||
|
||||
close: ->
|
||||
if location.pathname == "/"
|
||||
if location.pathname in ["/", "/checkout"]
|
||||
Navigation.navigate "/"
|
||||
else
|
||||
Loading.message = "Taking you back to the home page"
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Cart, Variants) ->
|
||||
Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Properties, Cart, Variants) ->
|
||||
new class Products
|
||||
constructor: ->
|
||||
@update()
|
||||
|
||||
|
||||
# TODO: don't need to scope this into object
|
||||
# Already on object as far as controller scope is concerned
|
||||
products: null
|
||||
loading: true
|
||||
|
||||
update: =>
|
||||
@loading = true
|
||||
@loading = true
|
||||
@products = $resource("/shop/products").query (products)=>
|
||||
@extend() && @dereference()
|
||||
@registerVariants()
|
||||
@registerVariants()
|
||||
@registerVariantsWithCart()
|
||||
@loading = false
|
||||
@
|
||||
|
||||
|
||||
dereference: ->
|
||||
for product in @products
|
||||
product.supplier = Enterprises.enterprises_by_id[product.supplier.id]
|
||||
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
|
||||
|
||||
Dereferencer.dereference product.properties, Properties.properties_by_id
|
||||
|
||||
# May return different objects! If the variant has already been registered
|
||||
# by another service, we fetch those
|
||||
registerVariants: ->
|
||||
@@ -45,7 +46,7 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
|
||||
prices = (v.price for v in product.variants)
|
||||
product.price = Math.min.apply(null, prices)
|
||||
product.hasVariants = product.variants?.length > 0
|
||||
|
||||
|
||||
product.primaryImage = product.images[0]?.small_url if product.images
|
||||
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
Darkswarm.factory "Properties", (properties)->
|
||||
new class Properties
|
||||
# Populate ProductProperties.properties from json in page.
|
||||
properties: properties
|
||||
properties_by_id: {}
|
||||
constructor: ->
|
||||
# Map properties to id/object pairs for lookup.
|
||||
for property in @properties
|
||||
@properties_by_id[property.id] = property
|
||||
@@ -1,2 +1,2 @@
|
||||
%li{"ng-class" => "{active: selector.active}"}
|
||||
%a{"ng-transclude" => true}
|
||||
%li{ ng: { class: "{active: selector.active}" } }
|
||||
%a{ ng: { transclude: true, class: "{active: selector.active}" } }
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%active-selector{ ng: { repeat: "selector in selectors()", show: "ifDefined(selector.fits, true)" } }
|
||||
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
|
||||
%span {{ selector.object.name }}
|
||||
@@ -1,9 +1,28 @@
|
||||
.row{bindonce: true}
|
||||
.row{bindonce: true}
|
||||
.small-12.large-8.columns
|
||||
%div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
|
||||
/ TODO: Rob add logic for taxons and properties too:
|
||||
/ %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
|
||||
%div
|
||||
%p.modal-header About
|
||||
.about-container
|
||||
%img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
|
||||
/ TODO: Rob - add in taxons and properties and property pop-overs
|
||||
|
||||
-# TODO: Add producer taxons and properties here
|
||||
-# %div
|
||||
-# %span.filter-shopfront.taxon-selectors
|
||||
-# %ul.inline-block
|
||||
-# %li
|
||||
-# %a.button.tiny.disabled Grains
|
||||
-# %li
|
||||
-# %a.button.tiny.disabled Dairy
|
||||
-#
|
||||
-# %span.filter-shopfront.property-selectors.pad-top
|
||||
-# %ul.inline-block
|
||||
-# %li
|
||||
-# %a.button.tiny Organic certified
|
||||
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
|
||||
-#
|
||||
.about-container.pad-top
|
||||
%img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
|
||||
%p.text-small{"ng-bind-html" => "enterprise.long_description"}
|
||||
.small-12.large-4.columns
|
||||
%ng-include{src: "'partials/contact.html'"}
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
.row
|
||||
|
||||
.columns.small-12.large-6.product-header
|
||||
%h3 {{product.name}}
|
||||
%span
|
||||
%em from
|
||||
%span.avenir {{ enterprise.name }}
|
||||
|
||||
|
||||
-# TODO: Add product taxons and properties here
|
||||
-# / TODO: Rob - add in taxons and properties and property pop-overs
|
||||
-# / %render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
-# .pad-top
|
||||
-# %span.filter-shopfront.taxon-selectors
|
||||
-# %ul.inline-block
|
||||
-# %li
|
||||
-# %a.button.tiny.disabled Grains
|
||||
-# %li
|
||||
-# %a.button.tiny.disabled Dairy
|
||||
-#
|
||||
-# %span.filter-shopfront.property-selectors.pad-top
|
||||
-# %ul.inline-block
|
||||
-# %li
|
||||
-# %a.button.tiny Organic certified
|
||||
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
|
||||
|
||||
|
||||
%div{"ng-if" => "product.description"}
|
||||
%hr
|
||||
%p.text-small {{product.description}}
|
||||
%hr
|
||||
|
||||
.columns.small-12.large-6
|
||||
%img.product-img{"ng-src" => "{{product.largeImage}}", "ng-if" => "product.largeImage"}
|
||||
.columns.small-12.large-6.product-header
|
||||
%h2
|
||||
/ %render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
{{product.name}}
|
||||
%p {{product.description}}
|
||||
%img.product-img.placeholder{"ng-src" => "/assets/noimage/large.png", "ng-if" => "!product.largeImage"}
|
||||
|
||||
%ng-include{src: "'partials/close.html'"}
|
||||
|
||||
@@ -40,6 +40,16 @@
|
||||
.field
|
||||
%label{ for: 'enterprise_acn' } ACN:
|
||||
%input.chunky{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } }
|
||||
.row
|
||||
.small-12.columns
|
||||
.field
|
||||
%label{ for: 'enterprise_charges_sales_tax' }= t(:charges_sales_tax)
|
||||
%input{ id: 'enterprise_charges_sales_tax_true', type: 'radio', name: 'charges_sales_tax', value: 'true', required: true, ng: { model: 'enterprise.charges_sales_tax' } }
|
||||
%label{ for: 'enterprise_charges_sales_tax_true' } Yes
|
||||
%input{ id: 'enterprise_charges_sales_tax_false', type: 'radio', name: 'charges_sales_tax', value: 'false', required: true, ng: { model: 'enterprise.charges_sales_tax' } }
|
||||
%label{ for: 'enterprise_charges_sales_tax_false' } No
|
||||
%span.error.small-12.columns{ ng: { show: "about.charges_sales_tax.$error.required && submitted" } }
|
||||
You need to make a selection.
|
||||
|
||||
.row.buttons.pad-top
|
||||
.small-12.columns
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
%ul
|
||||
-# In order for the single-line-selector scope to have access to the available selectors,
|
||||
%filter-selector{objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
|
||||
|
||||
%li.more{ ng: { show: "overFlowSelectors().length > 0 || fitting" } }
|
||||
%a.dropdown{ data: { dropdown: "show-more" }, ng: { class: "{active: selectedOverFlowSelectors().length > 0}" } }
|
||||
%span
|
||||
+ {{ overFlowSelectors().length }} more
|
||||
%i.ofn-i_052-point-down
|
||||
.f-dropdown.text-right.content#show-more
|
||||
%ul
|
||||
%active-selector{ ng: { repeat: "selector in overFlowSelectors()", hide: "selector.fits" } }
|
||||
%render-svg{path: "{{selector.object.icon}}"}
|
||||
%span {{ selector.object.name }}
|
||||
@@ -1,3 +0,0 @@
|
||||
%active-selector{"ng-repeat" => "selector in selectors()"}
|
||||
%render-svg{path: "{{selector.taxon.icon}}"}
|
||||
%span {{ selector.taxon.name }}
|
||||
122
app/assets/stylesheets/darkswarm/_shop-filters.css.sass
Normal file
@@ -0,0 +1,122 @@
|
||||
@import mixins
|
||||
@import branding
|
||||
@import big-input
|
||||
@import animations
|
||||
|
||||
@mixin filter-selector($base-clr, $border-clr, $hover-clr)
|
||||
ul.inline-block
|
||||
display: inline-block
|
||||
|
||||
li
|
||||
display: inline-block
|
||||
@include border-radius(0)
|
||||
padding: 0
|
||||
margin: 0 0 0.25rem 0.25rem
|
||||
&:hover, &:focus
|
||||
background: transparent
|
||||
&.active
|
||||
box-shadow: none
|
||||
|
||||
a, a.button
|
||||
display: block
|
||||
padding-top: 0.5rem
|
||||
@include border-radius(0.5em)
|
||||
border: 1px solid $border-clr
|
||||
padding: 0.5em 0.625em
|
||||
font-size: 0.875em
|
||||
color: $base-clr
|
||||
font-size: 0.75em
|
||||
background: white
|
||||
margin: 0
|
||||
i
|
||||
padding-left: 0.25rem
|
||||
|
||||
render-svg
|
||||
&, & svg
|
||||
width: 1rem
|
||||
height: 1rem
|
||||
float: left
|
||||
padding-right: 0.25rem
|
||||
path
|
||||
@include csstrans
|
||||
fill: $base-clr
|
||||
|
||||
&:hover, &:focus
|
||||
border-color: $hover-clr
|
||||
color: $hover-clr
|
||||
render-svg
|
||||
svg
|
||||
path
|
||||
fill: $hover-clr
|
||||
|
||||
&.disabled
|
||||
opacity: 0.6
|
||||
|
||||
&:hover, &:focus
|
||||
border-color: $border-clr
|
||||
color: $base-clr
|
||||
render-svg
|
||||
svg
|
||||
path
|
||||
fill: $base-clr
|
||||
|
||||
|
||||
&.active, &.active:hover, &.active:focus
|
||||
border: 1px solid $base-clr
|
||||
background: $base-clr
|
||||
color: white
|
||||
render-svg
|
||||
svg
|
||||
path
|
||||
fill: white
|
||||
|
||||
|
||||
// Alert when search, taxon, filter is triggered
|
||||
|
||||
.alert-box.search-alert
|
||||
background-color: $clr-yellow-light
|
||||
border-color: $clr-yellow-light
|
||||
color: #777
|
||||
font-size: 0.75rem
|
||||
padding: 0.5rem 0.75rem
|
||||
|
||||
span.applied-properties
|
||||
color: #333
|
||||
|
||||
span.applied-taxons
|
||||
color: $clr-blue
|
||||
|
||||
span.applied-search
|
||||
color: $clr-brick
|
||||
|
||||
span.filter-label
|
||||
opacity: 0.75
|
||||
|
||||
.filter-shopfront.taxon-selectors, .filter-shopfront.property-selectors
|
||||
background: transparent
|
||||
|
||||
single-line-selectors
|
||||
overflow-x: hidden
|
||||
white-space: nowrap
|
||||
|
||||
.f-dropdown
|
||||
overflow-x: auto
|
||||
white-space: normal
|
||||
|
||||
ul
|
||||
margin: 0
|
||||
ul, ul li
|
||||
list-style: none
|
||||
|
||||
|
||||
.filter-shopfront
|
||||
|
||||
// Shopfront taxons
|
||||
&.taxon-selectors
|
||||
@include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright)
|
||||
|
||||
// Shopfront properties
|
||||
&.property-selectors
|
||||
@include filter-selector(#666, #ccc, #777)
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
@include placeholder(rgba(0,0,0,0.4), #777)
|
||||
|
||||
input#search
|
||||
@include big-input(rgba(0,0,0,0.3), #777, $clr-brick)
|
||||
@include big-input-static
|
||||
@include medium-input(rgba(0,0,0,0.3), #777, $clr-brick)
|
||||
|
||||
// ordering
|
||||
product
|
||||
|
||||
6
app/assets/stylesheets/darkswarm/_shop-modals.css.sass
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
.product-header
|
||||
h1, h2, h3, h4, h5, h6
|
||||
margin: 0
|
||||
hr
|
||||
margin: 0.5em 0
|
||||
@@ -8,7 +8,7 @@
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
|
||||
.row.filter-box:first-child, .row.filter-box.filter-box-shopfront
|
||||
.row.filter-box:first-child
|
||||
border: 1px solid $clr-blue-light
|
||||
@include border-radius(0.25em)
|
||||
margin-top: 2px
|
||||
@@ -19,13 +19,9 @@
|
||||
background: transparent
|
||||
margin-top: 1em
|
||||
|
||||
.row.filter-box.filter-box-shopfront
|
||||
margin-top: 0
|
||||
|
||||
products .filter-box
|
||||
background: #f7f7f7
|
||||
|
||||
|
||||
.filter-box
|
||||
background: rgba(245,245,245,0.6)
|
||||
.tdhead
|
||||
@@ -40,7 +36,6 @@ products .filter-box
|
||||
[class*="block-grid-"] > li
|
||||
padding-bottom: 0.5rem !important
|
||||
|
||||
|
||||
li
|
||||
@include border-radius(12px)
|
||||
padding-top: 0.5rem
|
||||
@@ -105,16 +100,6 @@ products .filter-box
|
||||
path
|
||||
fill: #666
|
||||
|
||||
.filter-box.filter-box-shopfront
|
||||
.tdhead
|
||||
margin-top: 0rem
|
||||
margin-bottom: 0.75rem
|
||||
padding: 0.5rem 0
|
||||
h5
|
||||
color: $clr-blue
|
||||
.button.tiny
|
||||
margin-bottom: 0rem
|
||||
|
||||
.button.filterbtn
|
||||
margin-bottom: 0 !important
|
||||
min-width: 160px
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
// ANIMATION FUNCTIONS
|
||||
|
||||
|
||||
//
|
||||
@-webkit-keyframes slideInDown
|
||||
0%
|
||||
opacity: 0
|
||||
@@ -22,6 +24,8 @@
|
||||
-ms-transform: translateY(0)
|
||||
transform: translateY(0)
|
||||
|
||||
|
||||
|
||||
@-webkit-keyframes slideOutUp
|
||||
0%
|
||||
-webkit-transform: translateY(0)
|
||||
@@ -160,6 +164,51 @@ product.animate-repeat
|
||||
-webkit-animation-fill-mode: both
|
||||
animation-fill-mode: both
|
||||
|
||||
//
|
||||
|
||||
.animate-slide
|
||||
max-height: 500px
|
||||
opacity: 1 !important
|
||||
-webkit-transition: all 300ms ease-in-out
|
||||
-moz-transition: all 300ms ease-in-out
|
||||
-o-transition: all 300ms ease-in-out
|
||||
transition: all 300ms ease-in-out
|
||||
|
||||
&.ng-hide
|
||||
overflow: hidden
|
||||
max-height: 0
|
||||
opacity: 0 !important
|
||||
|
||||
// &.ng-hide-add-active, &.ng-hide-remove-active
|
||||
|
||||
&.ng-hide-add, &.ng-hide-remove
|
||||
/* IMPORTANT: this needs to be here to make it visible during the animation
|
||||
since the .ng-hide class is already on the element rendering
|
||||
it as hidden. */
|
||||
display: block !important
|
||||
|
||||
|
||||
.animate-show
|
||||
opacity: 1 !important
|
||||
-webkit-transition: all 300ms ease-in-out
|
||||
-moz-transition: all 300ms ease-in-out
|
||||
-o-transition: all 300ms ease-in-out
|
||||
transition: all 300ms ease-in-out
|
||||
|
||||
&.ng-hide
|
||||
opacity: 0 !important
|
||||
|
||||
// &.ng-hide-add-active, &.ng-hide-remove-active
|
||||
|
||||
&.ng-hide-add, &.ng-hide-remove
|
||||
/* IMPORTANT: this needs to be here to make it visible during the animation
|
||||
since the .ng-hide class is already on the element rendering
|
||||
it as hidden. */
|
||||
display: block !important
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@mixin csstrans
|
||||
-webkit-transition: all 300ms ease
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
border: 2px solid $input
|
||||
font-size: 2rem
|
||||
box-shadow: 0
|
||||
padding: 0.75rem 1rem 0.35rem 1rem
|
||||
padding: 0.75rem 1rem 0.35rem
|
||||
height: auto
|
||||
width: 100%
|
||||
margin-bottom: 0.5rem
|
||||
@@ -33,8 +33,9 @@
|
||||
background: white
|
||||
background: rgba(255,255,255,0.5)
|
||||
text-shadow: 0 0 10px #ffffff
|
||||
padding: 1.5rem 1rem 1rem 1rem
|
||||
padding: 1.5rem 1rem 1rem
|
||||
letter-spacing: 0.02rem
|
||||
outline: none
|
||||
|
||||
@mixin big-input-static
|
||||
outline: 0
|
||||
@@ -42,6 +43,34 @@
|
||||
padding: 0.75rem 1rem 0.35rem 1rem
|
||||
letter-spacing: 0
|
||||
|
||||
@mixin medium-input($input, $inputhvr, $inputactv)
|
||||
@include avenir
|
||||
@include csstrans
|
||||
@include border-radius(0.5rem)
|
||||
background: rgba(255,255,255,0.1)
|
||||
border: 2px solid $input
|
||||
font-size: 0.875rem
|
||||
box-shadow: 0
|
||||
padding: 0.5rem 0.625rem 0.375rem
|
||||
height: auto
|
||||
width: 100%
|
||||
margin-bottom: 0.5rem
|
||||
box-shadow: none
|
||||
color: $inputactv
|
||||
|
||||
&:hover
|
||||
@include box-shadow(0 1px 1px 0 rgba(255,255,255,0.25))
|
||||
border: 2px solid $inputhvr
|
||||
color: $inputactv
|
||||
|
||||
&:active, &:focus, &.active
|
||||
border: 2px solid $inputactv
|
||||
color: $inputactv
|
||||
background: white
|
||||
background: rgba(255,255,255,0.5)
|
||||
text-shadow: 0 0 10px #ffffff
|
||||
outline: none
|
||||
|
||||
@mixin placeholder($placeholder, $placeholderhvr)
|
||||
::-webkit-input-placeholder
|
||||
color: $placeholder
|
||||
|
||||
@@ -15,6 +15,8 @@ $clr-blue: #0096ad
|
||||
$clr-blue-light: #85d9e5
|
||||
$clr-blue-bright: #14b6cc
|
||||
|
||||
$clr-yellow-light: #faf6c7
|
||||
|
||||
$disabled-light: #e5e5e5
|
||||
$disabled-bright: #ccc
|
||||
$disabled-med: #b3b3b3
|
||||
@@ -25,3 +27,4 @@ $med-drk-grey: #444
|
||||
$dark-grey: #333
|
||||
$light-grey: #ddd
|
||||
$black: #000
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
margin-bottom: 10px
|
||||
outline: 1px solid #ccc
|
||||
@include box-shadow(0 1px 2px 1px rgba(0,0,0,0.15))
|
||||
// placeholder for when no product images
|
||||
&.placeholder
|
||||
opacity: 0.35
|
||||
@media all and (max-width: 1024px)
|
||||
display: none
|
||||
|
||||
.hero-img
|
||||
outline: 1px solid $disabled-bright
|
||||
|
||||
@@ -8,6 +8,7 @@ dialog, .reveal-modal
|
||||
border-bottom: 30px solid white
|
||||
overflow-y: scroll
|
||||
overflow-x: hidden
|
||||
min-height: 260px
|
||||
// Not working yet - want a nice gradient shadow when there is overflow - needs JS too
|
||||
// &:after
|
||||
// @include elipse-shadow(0 0 40px rgba(0, 0, 0, 0.8))
|
||||
@@ -32,6 +33,7 @@ dialog, .reveal-modal
|
||||
|
||||
.reveal-modal-bg
|
||||
background-color: rgba(0,0,0,0.85)
|
||||
position: fixed
|
||||
|
||||
dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
|
||||
right: 0.25rem
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
|
||||
products
|
||||
display: block
|
||||
padding-top: 2.3em
|
||||
padding-top: 20px
|
||||
@media all and (max-width: 768px)
|
||||
padding-top: 1em
|
||||
|
||||
input.button.right
|
||||
float: left
|
||||
|
||||
|
||||
@@ -20,21 +20,27 @@
|
||||
right: 22px !important
|
||||
left: auto
|
||||
|
||||
ul, li
|
||||
list-style: none
|
||||
margin-left: 0
|
||||
table
|
||||
width: 100%
|
||||
border: none
|
||||
border-spacing: 0px
|
||||
margin-bottom: 5px
|
||||
|
||||
li
|
||||
float: none
|
||||
|
||||
.row .columns
|
||||
padding-left: 0.25rem
|
||||
padding-right: 0.25rem
|
||||
|
||||
li.total-cart
|
||||
background-color: #424242
|
||||
li.product-cart
|
||||
border-top: 1px solid #424242
|
||||
tr.total-cart
|
||||
color: #fff
|
||||
background-color: #424242
|
||||
td
|
||||
color: #fff
|
||||
tr.product-cart
|
||||
background-color: #333333
|
||||
border-top: 1px solid #424242
|
||||
td
|
||||
padding: 4px 12px
|
||||
color: #fff
|
||||
.buttons
|
||||
.button
|
||||
height: auto
|
||||
top: 0px
|
||||
|
||||
// Shopping cart
|
||||
#cart-detail
|
||||
|
||||
@@ -8,15 +8,12 @@ class ApplicationController < ActionController::Base
|
||||
super(options, response_status)
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
def set_checkout_redirect
|
||||
if request.referer and referer_path = URI(request.referer).path
|
||||
[main_app.checkout_path].include?(referer_path) ? referer_path : root_path
|
||||
else
|
||||
root_path
|
||||
session["spree_user_return_to"] = [main_app.checkout_path].include?(referer_path) ? referer_path : root_path
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def require_distributor_chosen
|
||||
|
||||
@@ -32,6 +32,24 @@ class EnterpriseConfirmationsController < DeviseController
|
||||
set_flash_message(:error, :not_confirmed) if is_navigational_format?
|
||||
end
|
||||
|
||||
respond_with_navigational(resource){ redirect_to spree.admin_path }
|
||||
respond_with_navigational(resource){ redirect_to redirect_path(resource) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_user_reset_path(resource)
|
||||
password = Devise.friendly_token.first(8)
|
||||
user = Spree::User.create(email: resource.email, password: password, password_confirmation: password)
|
||||
user.send_reset_password_instructions
|
||||
resource.users << user
|
||||
spree.edit_spree_user_password_path(user, :reset_password_token => user.reset_password_token, return_to: spree.admin_path)
|
||||
end
|
||||
|
||||
def redirect_path(resource)
|
||||
if resource.persisted? && !Spree::User.exists?(email: resource.email)
|
||||
new_user_reset_path(resource)
|
||||
else
|
||||
spree.admin_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Spree::UserSessionsController.class_eval do
|
||||
before_filter :set_checkout_redirect, only: :create
|
||||
|
||||
def create
|
||||
authenticate_spree_user!
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class UserPasswordsController < Spree::UserPasswordsController
|
||||
layout 'darkswarm'
|
||||
|
||||
before_filter :set_admin_redirect, only: :edit
|
||||
|
||||
def create
|
||||
self.resource = resource_class.send_reset_password_instructions(params[resource_name])
|
||||
|
||||
@@ -18,4 +20,10 @@ class UserPasswordsController < Spree::UserPasswordsController
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_admin_redirect
|
||||
session["spree_user_return_to"] = params[:return_to] if params[:return_to]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class UserRegistrationsController < Spree::UserRegistrationsController
|
||||
before_filter :set_checkout_redirect, only: :create
|
||||
|
||||
# POST /resource/sign_up
|
||||
def create
|
||||
|
||||
@@ -18,20 +18,24 @@ module CheckoutHelper
|
||||
end
|
||||
|
||||
def display_checkout_admin_and_handling_adjustments_total_for(order)
|
||||
adjustments = order.adjustments.eligible.where('originator_type = ? AND source_type != ? ', 'EnterpriseFee', 'Spree::LineItem' )
|
||||
Spree::Money.new( adjustments.sum( &:amount ) , { :currency => order.currency })
|
||||
adjustments = order.adjustments.eligible.where('originator_type = ? AND source_type != ? ', 'EnterpriseFee', 'Spree::LineItem')
|
||||
Spree::Money.new adjustments.sum(&:amount) , currency: order.currency
|
||||
end
|
||||
|
||||
def checkout_line_item_adjustments(order)
|
||||
order.adjustments.eligible.where( source_type: "Spree::LineItem")
|
||||
order.adjustments.eligible.where(source_type: "Spree::LineItem")
|
||||
end
|
||||
|
||||
def checkout_subtotal(order)
|
||||
order.item_total + checkout_line_item_adjustments(order).sum( &:amount )
|
||||
order.item_total + checkout_line_item_adjustments(order).sum(&:amount)
|
||||
end
|
||||
|
||||
def display_checkout_subtotal(order)
|
||||
Spree::Money.new( checkout_subtotal(order) , { :currency => order.currency })
|
||||
Spree::Money.new checkout_subtotal(order) , currency: order.currency
|
||||
end
|
||||
|
||||
def display_checkout_tax_total(order)
|
||||
Spree::Money.new order.total_tax, currency: order.currency
|
||||
end
|
||||
|
||||
def checkout_state_options(source_address)
|
||||
|
||||
@@ -21,6 +21,10 @@ module InjectionHelper
|
||||
inject_json_ams "taxons", Spree::Taxon.all, Api::TaxonSerializer
|
||||
end
|
||||
|
||||
def inject_properties
|
||||
inject_json_ams "properties", Spree::Property.all, Api::IdNameSerializer
|
||||
end
|
||||
|
||||
def inject_currency_config
|
||||
inject_json_ams "currencyConfig", {}, Api::CurrencyConfigSerializer
|
||||
end
|
||||
|
||||
@@ -77,7 +77,7 @@ class Enterprise < ActiveRecord::Base
|
||||
after_rollback :restore_permalink
|
||||
|
||||
scope :by_name, order('name')
|
||||
scope :visible, where(:visible => true)
|
||||
scope :visible, where(visible: true)
|
||||
scope :confirmed, where('confirmed_at IS NOT NULL')
|
||||
scope :unconfirmed, where('confirmed_at IS NULL')
|
||||
scope :activated, where("confirmed_at IS NOT NULL AND sells != 'unspecified'")
|
||||
@@ -320,6 +320,11 @@ class Enterprise < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Based on a devise method, but without adding errors
|
||||
def pending_any_confirmation?
|
||||
!confirmed? || pending_reconfirmation?
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def devise_mailer
|
||||
|
||||
@@ -3,7 +3,11 @@ module Spree
|
||||
def ensure_correct_adjustment_with_included_tax
|
||||
ensure_correct_adjustment_without_included_tax
|
||||
|
||||
adjustment.set_included_tax! Config.shipping_tax_rate if Config.shipment_inc_vat
|
||||
if Config.shipment_inc_vat && (order.distributor.nil? || order.distributor.charges_sales_tax)
|
||||
adjustment.set_included_tax! Config.shipping_tax_rate
|
||||
else
|
||||
adjustment.set_included_tax! 0
|
||||
end
|
||||
end
|
||||
|
||||
alias_method_chain :ensure_correct_adjustment, :included_tax
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
Spree::TaxRate.class_eval do
|
||||
class << self
|
||||
def match_with_sales_tax_registration(order)
|
||||
return [] if order.distributor && !order.distributor.charges_sales_tax
|
||||
match_without_sales_tax_registration(order)
|
||||
end
|
||||
alias_method_chain :match, :sales_tax_registration
|
||||
end
|
||||
|
||||
|
||||
def adjust_with_included_tax(order)
|
||||
adjust_without_included_tax(order)
|
||||
|
||||
@@ -7,6 +16,5 @@ Spree::TaxRate.class_eval do
|
||||
a.set_absolute_included_tax! a.amount
|
||||
end
|
||||
end
|
||||
|
||||
alias_method_chain :adjust, :included_tax
|
||||
end
|
||||
|
||||
3
app/serializers/api/id_name_serializer.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Api::IdNameSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name
|
||||
end
|
||||
@@ -35,7 +35,7 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
has_many :taxons, serializer: Api::IdSerializer
|
||||
has_many :properties, serializer: Api::PropertySerializer
|
||||
has_many :properties, serializer: Api::IdSerializer
|
||||
has_many :images, serializer: Api::ImageSerializer
|
||||
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
@@ -3,8 +3,21 @@
|
||||
= f.label :abn, 'ABN'
|
||||
.omega.eight.columns
|
||||
= f.text_field :abn, { placeholder: "eg. 99 123 456 789"}
|
||||
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :acn, 'ACN'
|
||||
.omega.eight.columns
|
||||
= f.text_field :acn, { placeholder: "eg. 123 456 789"}
|
||||
= f.text_field :acn, { placeholder: "eg. 123 456 789"}
|
||||
|
||||
.row
|
||||
.three.columns.alpha
|
||||
%label= t('charges_sales_tax')
|
||||
.two.columns
|
||||
= f.radio_button :charges_sales_tax, true
|
||||
|
||||
= f.label :charges_sales_tax, "Yes", :value => "true"
|
||||
.five.columns.omega
|
||||
= f.radio_button :charges_sales_tax, false
|
||||
|
||||
= f.label :charges_sales_tax, "No", :value => "false"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
-if @enterprise.unconfirmed_email
|
||||
-if @enterprise.pending_any_confirmation?
|
||||
.alert-box
|
||||
Email change is pending.
|
||||
- email = @enterprise.confirmed? ? @enterprise.unconfirmed_email : @enterprise.email
|
||||
Email confirmation is pending.
|
||||
We've sent a confirmation email to
|
||||
%strong= "#{@enterprise.unconfirmed_email}."
|
||||
= link_to('Resend', main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: @enterprise.unconfirmed_email } ), method: :post)
|
||||
%strong= "#{email}."
|
||||
= link_to('Resend', main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post)
|
||||
%a.close{ href: "#" } ×
|
||||
.row
|
||||
.alpha.three.columns
|
||||
@@ -30,4 +31,4 @@
|
||||
.alpha.three.columns
|
||||
= f.label :website
|
||||
.omega.eight.columns
|
||||
= f.text_field :website, { placeholder: "eg. www.truffles.com"}
|
||||
= f.text_field :website, { placeholder: "eg. www.truffles.com"}
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
%tr
|
||||
%th Total
|
||||
%td.total.text-right {{ Checkout.cartTotal() | localizeCurrency }}
|
||||
- if current_order.price_adjustment_totals.present?
|
||||
- current_order.price_adjustment_totals.each do |label, total|
|
||||
%tr
|
||||
%th= label
|
||||
%td= total
|
||||
|
||||
//= f.submit "Purchase", class: "button", "ofn-focus" => "accordion['payment']"
|
||||
%a.button.secondary{href: cart_url}
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
.light Filter by
|
||||
Type
|
||||
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5
|
||||
%taxon-selector{objects: "Enterprises.hubs | searchEnterprises:query",
|
||||
results: "activeTaxons"}
|
||||
%filter-selector{objects: "Enterprises.hubs | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
.small-12.large-3.columns
|
||||
%h5.tdhead
|
||||
.light Filter by
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
= inject_json "user", "current_user"
|
||||
= inject_json "railsFlash", "flash"
|
||||
= inject_taxons
|
||||
= inject_properties
|
||||
= inject_current_order
|
||||
= inject_currency_config
|
||||
|
||||
@@ -37,6 +38,6 @@
|
||||
|
||||
%section{ role: "main" }
|
||||
= yield
|
||||
|
||||
|
||||
#footer
|
||||
%loading
|
||||
|
||||
@@ -11,7 +11,5 @@
|
||||
.light Filter by
|
||||
Type
|
||||
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6
|
||||
%taxon-selector{objects: "Enterprises.producers | searchEnterprises:query ",
|
||||
results: "activeTaxons"}
|
||||
%filter-selector{objects: "Enterprises.producers | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
= render partial: 'shared/components/filter_box'
|
||||
|
||||
|
||||
@@ -10,38 +10,33 @@
|
||||
%span.joyride-nub.top
|
||||
.joyride-content-wrapper
|
||||
%h5 Your shopping cart
|
||||
%ul
|
||||
%li.product-cart{"ng-repeat" => "line_item in Cart.line_items_present()",
|
||||
%table
|
||||
%tr.product-cart{"ng-repeat" => "line_item in Cart.line_items_present()",
|
||||
"ng-controller" => "LineItemCtrl", "id" => "cart-variant-{{ line_item.variant.id }}"}
|
||||
.row
|
||||
.columns.small-7
|
||||
%small
|
||||
/ %strong {{ line_item.variant.name_to_display }}
|
||||
/ %em {{ line_item.variant.unit_to_display }}
|
||||
/ - if {{ line_item.product.name }} == {{ line_item.variant.name_to_display }}
|
||||
%strong
|
||||
{{ line_item.variant.extended_name }}
|
||||
%td
|
||||
%small
|
||||
%strong
|
||||
{{ line_item.variant.extended_name }}
|
||||
%td.text-right
|
||||
%small
|
||||
%span.quantity {{ line_item.quantity }}
|
||||
%i.ofn-i_009-close
|
||||
%span.price {{ line_item.variant.price_with_fees | localizeCurrency }}
|
||||
|
||||
.columns.small-3.text-right
|
||||
%small
|
||||
%span.quantity {{ line_item.quantity }}
|
||||
%i.ofn-i_009-close
|
||||
%span.price {{ line_item.variant.price_with_fees | localizeCurrency }}
|
||||
%td
|
||||
%small
|
||||
\=
|
||||
%strong
|
||||
.total-price.right {{ line_item.variant.totalPrice() | localizeCurrency }}
|
||||
|
||||
.columns.small-2
|
||||
%small
|
||||
\=
|
||||
%strong
|
||||
.total-price.right {{ line_item.variant.totalPrice() | localizeCurrency }}
|
||||
%table{"ng-show" => "Cart.line_items_present().length > 0"}
|
||||
%tr.total-cart
|
||||
%td
|
||||
%em Total:
|
||||
%td.text-right
|
||||
%strong {{ Cart.total() | localizeCurrency }}
|
||||
|
||||
%li.total-cart{"ng-show" => "Cart.line_items_present().length > 0"}
|
||||
.row
|
||||
.columns.small-6
|
||||
%em Total:
|
||||
.columns.small-6.text-right
|
||||
%strong {{ Cart.total() | localizeCurrency }}
|
||||
|
||||
.text-right
|
||||
.buttons.text-right
|
||||
%a.button.secondary.tiny.add_to_cart{ href: cart_path, type: :submit, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
|
||||
{{ Cart.dirty ? 'Updating cart...' : (Cart.empty() ? 'Cart empty' : 'Edit your cart' ) }}
|
||||
%a.button.primary.tiny{href: checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"} Checkout now
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
.row.animate-show{"ng-hide" => "filtersActive"}
|
||||
.small-12.columns
|
||||
= render partial: 'shared/components/filter_controls_shopfront'
|
||||
|
||||
.row.filter-box.filter-box-shopfront.animate-hide{"ng-show" => "filtersActive"}
|
||||
.small-12.columns
|
||||
.row.tdhead
|
||||
.small-12.medium-6.columns
|
||||
%h5
|
||||
.light Filter by
|
||||
Category
|
||||
.small-12.medium-6.columns.text-right
|
||||
= render partial: 'shared/components/filter_box_shopfront'
|
||||
= render partial: 'shared/components/filter_controls_shopfront'
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
%ul.small-block-grid-2.medium-block-grid-3.large-block-grid-4
|
||||
%taxon-selector{objects: "Products.products | products:query | products:showProfiles",
|
||||
results: "activeTaxons"}
|
||||
.filter-shopfront.taxon-selectors.text-right
|
||||
%single-line-selectors{ objects: "Products.products | products:query | properties: activeProperties | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
|
||||
.filter-shopfront.property-selectors.text-right
|
||||
%single-line-selectors{ objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"}
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
%products.small-12.columns{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null",
|
||||
"infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1"}
|
||||
|
||||
// TODO: Needs an ng-show to slide content down
|
||||
.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
|
||||
.small-12.columns
|
||||
.alert-box.search-alert.ng-scope
|
||||
%a.right{"ng-click" => "clearAll()"}
|
||||
Clear all
|
||||
%i.ofn-i_009-close
|
||||
%span.filter-label
|
||||
Showing:
|
||||
%span.applied-properties
|
||||
{{ appliedPropertiesList() }}
|
||||
%span.applied-taxons
|
||||
{{ appliedTaxonsList() }}
|
||||
%span{ ng: { hide: "!query"} }
|
||||
%span{ "ng-show" => "appliedPropertiesList() || appliedTaxonsList()" }
|
||||
with
|
||||
%span.applied-search "{{ query }}"
|
||||
.row
|
||||
.small-12.medium-8.large-9.columns
|
||||
.small-12.medium-6.large-5.columns
|
||||
%input#search.text{"ng-model" => "query",
|
||||
placeholder: "Search by product or producer",
|
||||
"ng-debounce" => "100",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
.small-12.medium-6.large-6.large-offset-1.columns
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
%form{action: cart_path}
|
||||
.small-12.medium-4.large-3.columns
|
||||
%i.ofn-i_011-spinner.cart-spinner{"ng-show" => "Cart.dirty"}
|
||||
%input.small.button.primary.right.add_to_cart{type: :submit, value: "{{ Cart.dirty ? 'Updating cart...' : (Cart.empty() ? 'Cart empty' : 'Edit your cart' ) }}", "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
|
||||
|
||||
%div.pad-top{bindonce: true}
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl",
|
||||
"ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons) track by product.id ", "id" => "product-{{ product.id }}"}
|
||||
|
||||
"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"
|
||||
%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 }}"}
|
||||
@@ -43,4 +55,3 @@
|
||||
%form{action: cart_path}
|
||||
%i.ofn-i_011-spinner.cart-spinner{"ng-show" => "Cart.dirty"}
|
||||
%input.small.button.primary.right.add_to_cart{type: :submit, value: "{{ Cart.dirty ? 'Updating cart...' : (Cart.empty() ? 'Cart empty' : 'Edit your cart' ) }}", "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
.row
|
||||
%tabset
|
||||
-# Build all tabs.
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 4],
|
||||
producers: ["Producers",3],
|
||||
groups: ["Groups",2],
|
||||
contact: ["Contact",3]}
|
||||
- for name, heading_cols in { about: ["About #{current_distributor.name}", 6],
|
||||
producers: ["Producers",2],
|
||||
contact: ["Contact",2],
|
||||
groups: ["Groups",2]}
|
||||
-# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl.
|
||||
- heading, cols = heading_cols
|
||||
%tab.columns{heading: heading,
|
||||
|
||||
53
app/views/spree/order_mailer/_order_summary.html.haml
Normal file
@@ -0,0 +1,53 @@
|
||||
%table.order-summary{:width => "100%"}
|
||||
%thead
|
||||
%tr
|
||||
%th{:align => "left"}
|
||||
%h4 Item
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Qty
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Price
|
||||
%tbody
|
||||
- @order.line_items.each do |item|
|
||||
%tr
|
||||
%td
|
||||
- if item.variant.product.name == item.variant.name_to_display
|
||||
%strong= "#{raw(item.variant.product.name)}"
|
||||
- else
|
||||
%strong
|
||||
%span= "#{raw(item.variant.product.name)}"
|
||||
%span= "- " + "#{raw(item.variant.name_to_display)}"
|
||||
- if item.variant.options_text
|
||||
= "(" + "#{raw(item.variant.options_text)}" + ")"
|
||||
%br
|
||||
%small
|
||||
%em= raw(item.variant.product.supplier.name)
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
= item.display_amount_with_adjustments
|
||||
%tfoot
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
Subtotal:
|
||||
%td{:align => "right"}
|
||||
= display_checkout_subtotal(@order)
|
||||
- checkout_adjustments_for(@order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
= "#{raw(adjustment.label)}:"
|
||||
%td{:align => "right"}
|
||||
= adjustment.display_amount
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong Total:
|
||||
%td{:align => "right"}
|
||||
%strong= @order.display_total
|
||||
|
||||
- if @order.total_tax > 0
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
(includes tax):
|
||||
%td{:align => "right"}
|
||||
= display_checkout_tax_total(@order)
|
||||
%p
|
||||
14
app/views/spree/order_mailer/_payment.html.haml
Normal file
@@ -0,0 +1,14 @@
|
||||
- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check" and @order.payments.first.andand.payment_method.andand.description
|
||||
%p.callout
|
||||
%span{:style => "float:right;"}
|
||||
- if @order.paid?
|
||||
PAID
|
||||
- else
|
||||
NOT PAID
|
||||
%strong Payment summary
|
||||
%h4
|
||||
Paying via:
|
||||
%strong= @order.payments.first.andand.payment_method.andand.name.andand.html_safe
|
||||
%p
|
||||
%em= @order.payments.first.andand.payment_method.andand.description.andand.html_safe
|
||||
%p
|
||||
59
app/views/spree/order_mailer/_shipping.html.haml
Normal file
@@ -0,0 +1,59 @@
|
||||
- if @order.shipping_method.andand.require_ship_address
|
||||
/ Delivery details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Delivery details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor)
|
||||
%h4
|
||||
Delivery on:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
- if @order.shipping_method.andand.description
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address
|
||||
%h4 Delivery address:
|
||||
%p
|
||||
#{@order.ship_address.full_name}
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
%br
|
||||
#{@order.ship_address.phone}
|
||||
%br
|
||||
|
||||
|
||||
- else
|
||||
/ Collection details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Collection details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor).present?
|
||||
%h4
|
||||
Ready for collection:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
|
||||
- if @order.shipping_method.andand.description.present?
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address.full_address
|
||||
%p
|
||||
%strong Collecting from:
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
|
||||
- if @order.order_cycle.andand.pickup_instructions_for(@order.distributor).present?
|
||||
%p
|
||||
%strong Collection instructions:
|
||||
%br
|
||||
#{@order.order_cycle.pickup_instructions_for(@order.distributor)}
|
||||
@@ -0,0 +1,7 @@
|
||||
- if @order.special_instructions.present?
|
||||
%br
|
||||
%p
|
||||
%small
|
||||
%strong Your notes:
|
||||
%br
|
||||
#{@order.special_instructions}
|
||||
@@ -23,135 +23,10 @@
|
||||
Here are your order details from
|
||||
%strong= "#{@order.distributor.name}:"
|
||||
|
||||
%table.order-summary{:width => "100%"}
|
||||
%thead
|
||||
%tr
|
||||
%th{:align => "left"}
|
||||
%h4 Item
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Qty
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Price
|
||||
%tbody
|
||||
- @order.line_items.each do |item|
|
||||
%tr
|
||||
%td
|
||||
- if item.variant.product.name == item.variant.name_to_display
|
||||
%strong= "#{raw(item.variant.product.name)}"
|
||||
- else
|
||||
%strong
|
||||
%span= "#{raw(item.variant.product.name)}"
|
||||
%span= "- " + "#{raw(item.variant.name_to_display)}"
|
||||
- if item.variant.options_text
|
||||
= "(" + "#{raw(item.variant.options_text)}" + ")"
|
||||
%br
|
||||
%small
|
||||
%em= raw(item.variant.product.supplier.name)
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
= item.display_amount_with_adjustments
|
||||
%tfoot
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
Subtotal:
|
||||
%td{:align => "right"}
|
||||
= display_checkout_subtotal(@order)
|
||||
- checkout_adjustments_for(@order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
= "#{raw(adjustment.label)}:"
|
||||
%td{:align => "right"}
|
||||
= adjustment.display_amount
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong Total:
|
||||
%td{:align => "right"}
|
||||
%strong= @order.display_total
|
||||
%p
|
||||
|
||||
- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check" and @order.payments.first.andand.payment_method.andand.description
|
||||
%p.callout
|
||||
%span{:style => "float:right;"}
|
||||
- if @order.paid?
|
||||
PAID
|
||||
- else
|
||||
NOT PAID
|
||||
%strong Payment summary
|
||||
%h4
|
||||
Paying via:
|
||||
%strong= @order.payments.first.andand.payment_method.andand.name.andand.html_safe
|
||||
%p
|
||||
%em= @order.payments.first.andand.payment_method.andand.description.andand.html_safe
|
||||
%p
|
||||
|
||||
- if @order.shipping_method.andand.require_ship_address
|
||||
/ Delivery details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Delivery details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor)
|
||||
%h4
|
||||
Delivery on:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
- if @order.shipping_method.andand.description
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address
|
||||
%h4 Delivery address:
|
||||
%p
|
||||
#{@order.ship_address.full_name}
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
%br
|
||||
#{@order.ship_address.phone}
|
||||
%br
|
||||
|
||||
|
||||
- else
|
||||
/ Collection details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Collection details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor).present?
|
||||
%h4
|
||||
Ready for collection:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
|
||||
- if @order.shipping_method.andand.description.present?
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address.full_address
|
||||
%p
|
||||
%strong Collecting from:
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
|
||||
- if @order.order_cycle.andand.pickup_instructions_for(@order.distributor).present?
|
||||
%p
|
||||
%strong Collection instructions:
|
||||
%br
|
||||
#{@order.order_cycle.pickup_instructions_for(@order.distributor)}
|
||||
|
||||
- if @order.special_instructions.present?
|
||||
%br
|
||||
%p
|
||||
%small
|
||||
%strong Your notes:
|
||||
%br
|
||||
#{@order.special_instructions}
|
||||
= render 'order_summary'
|
||||
= render 'payment'
|
||||
= render 'shipping'
|
||||
= render 'special_instructions'
|
||||
|
||||
%br
|
||||
%p.callout
|
||||
|
||||
@@ -23,134 +23,10 @@
|
||||
%strong= "#{@order.bill_address.firstname} #{@order.bill_address.lastname}"
|
||||
completed the following order at your shopfront:
|
||||
|
||||
%table.order-summary{:width => "100%"}
|
||||
%thead
|
||||
%tr
|
||||
%th{:align => "left"}
|
||||
%h4 Item
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Qty
|
||||
%th{:align => "right", :width => "25%"}
|
||||
%h4 Price
|
||||
%tbody
|
||||
- @order.line_items.each do |item|
|
||||
%tr
|
||||
%td
|
||||
- if item.variant.product.name == item.variant.name_to_display
|
||||
%strong= "#{raw(item.variant.product.name)}"
|
||||
- else
|
||||
%strong
|
||||
%span= "#{raw(item.variant.product.name)}"
|
||||
%span= "- " + "#{raw(item.variant.name_to_display)}"
|
||||
- if item.variant.options_text
|
||||
= "(" + "#{raw(item.variant.options_text)}" + ")"
|
||||
%br
|
||||
%small
|
||||
%em= raw(item.variant.product.supplier.name)
|
||||
%td{:align => "right"}
|
||||
= item.quantity
|
||||
%td{:align => "right"}
|
||||
= item.display_amount_with_adjustments
|
||||
%tfoot
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
Subtotal:
|
||||
%td{:align => "right"}
|
||||
= display_checkout_subtotal(@order)
|
||||
- checkout_adjustments_for(@order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
= "#{raw(adjustment.label)}:"
|
||||
%td{:align => "right"}
|
||||
= adjustment.display_amount
|
||||
%tr
|
||||
%td{:align => "right", :colspan => "2"}
|
||||
%strong Total:
|
||||
%td{:align => "right"}
|
||||
%strong= @order.display_total
|
||||
%p
|
||||
|
||||
- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check" and @order.payments.first.andand.payment_method.andand.description
|
||||
%p.callout
|
||||
%span{:style => "float:right;"}
|
||||
- if @order.paid?
|
||||
PAID
|
||||
- else
|
||||
NOT PAID
|
||||
%strong Payment summary
|
||||
%h4
|
||||
Paying via:
|
||||
%strong= @order.payments.first.andand.payment_method.andand.name.andand.html_safe
|
||||
%p
|
||||
%em= @order.payments.first.andand.payment_method.andand.description.andand.html_safe
|
||||
%p
|
||||
|
||||
- if @order.shipping_method.andand.require_ship_address
|
||||
/ Delivery details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Delivery details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor)
|
||||
%h4
|
||||
Delivery on:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
- if @order.shipping_method.andand.description
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address
|
||||
%h4 Delivery address:
|
||||
%p
|
||||
#{@order.ship_address.full_name}
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
%br
|
||||
#{@order.ship_address.phone}
|
||||
%br
|
||||
|
||||
- else
|
||||
/ Collection details
|
||||
%p.callout
|
||||
%strong
|
||||
- if @order.shipping_method.andand.name
|
||||
#{@order.shipping_method.name.html_safe}
|
||||
- else
|
||||
Collection details
|
||||
|
||||
- if @order.order_cycle.andand.pickup_time_for(@order.distributor).present?
|
||||
%h4
|
||||
Ready for collection:
|
||||
%strong #{@order.order_cycle.pickup_time_for(@order.distributor)}
|
||||
|
||||
- if @order.shipping_method.andand.description.present?
|
||||
%p
|
||||
%em #{@order.shipping_method.description.html_safe}
|
||||
%br
|
||||
|
||||
- if @order.ship_address.full_address
|
||||
%p
|
||||
%strong Collecting from:
|
||||
%br
|
||||
#{@order.ship_address.full_address}
|
||||
|
||||
- if @order.order_cycle.andand.pickup_instructions_for(@order.distributor).present?
|
||||
%p
|
||||
%strong Collection instructions:
|
||||
%br
|
||||
#{@order.order_cycle.pickup_instructions_for(@order.distributor)}
|
||||
|
||||
- if @order.special_instructions.present?
|
||||
%br
|
||||
%p
|
||||
%small
|
||||
%strong Customer notes:
|
||||
%br
|
||||
#{@order.special_instructions}
|
||||
= render 'order_summary'
|
||||
= render 'payment'
|
||||
= render 'shipping'
|
||||
= render 'special_instructions'
|
||||
|
||||
%p
|
||||
= render 'shared/mailers/signoff'
|
||||
|
||||
@@ -56,3 +56,10 @@
|
||||
%td.text-right
|
||||
%h5.order-total.grand-total= @order.display_total
|
||||
%td
|
||||
|
||||
- if @order.total_tax > 0
|
||||
%tr
|
||||
%td.text-right{colspan:"3"} (includes tax)
|
||||
%td.text-right
|
||||
%span.order-total.tax-total= display_checkout_tax_total(@order)
|
||||
%td
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- else
|
||||
= @order.distributor.next_collection_at
|
||||
|
||||
= render partial: "shopping_shared/details"
|
||||
= render "shopping_shared/details"
|
||||
|
||||
%fieldset#order_summary{"data-hook" => ""}
|
||||
.row
|
||||
@@ -22,7 +22,7 @@
|
||||
- if params.has_key? :checkout_complete
|
||||
%h1= t(:thank_you_for_your_order)
|
||||
|
||||
= render :partial => 'spree/shared/order_details', :locals => { :order => @order }
|
||||
= render 'spree/shared/order_details', order: @order
|
||||
|
||||
.row
|
||||
.columns.large-12
|
||||
|
||||
@@ -130,37 +130,36 @@
|
||||
%td.text-right.total{"data-hook" => "order_item_total"}
|
||||
%span= item.display_amount_with_adjustments.to_html
|
||||
|
||||
%tfoot#order-total{"data-hook" => "order_details_total"}
|
||||
%tr.total
|
||||
%td.text-right{colspan: "3"}
|
||||
%h5
|
||||
Total
|
||||
%td.text-right.total
|
||||
%h5#order_total= order.display_total.to_html
|
||||
|
||||
- if order.price_adjustment_totals.present?
|
||||
%tfoot#price-adjustments{"data-hook" => "order_details_price_adjustments"}
|
||||
- order.price_adjustment_totals.each do |key, total|
|
||||
%tr.total
|
||||
%td.text-right{colspan: "3"}
|
||||
%strong
|
||||
= key
|
||||
%td.text-right.total
|
||||
%span= total
|
||||
|
||||
%tfoot#subtotal{"data-hook" => "order_details_subtotal"}
|
||||
%tr#subtotal-row.total
|
||||
%td.text-right{colspan: "3"}
|
||||
%strong
|
||||
Produce
|
||||
%td.text-right.total
|
||||
%span= display_checkout_subtotal(order)
|
||||
|
||||
%tfoot#order-charges{"data-hook" => "order_details_adjustments"}
|
||||
- checkout_adjustments_for(order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
|
||||
%tr.total
|
||||
%td.text-right{:colspan => "3"}
|
||||
%tfoot
|
||||
#subtotal{"data-hook" => "order_details_subtotal"}
|
||||
%tr#subtotal-row.total
|
||||
%td.text-right{colspan: "3"}
|
||||
%strong
|
||||
= adjustment.label
|
||||
Produce
|
||||
%td.text-right.total
|
||||
%span= adjustment.display_amount.to_html
|
||||
%span= display_checkout_subtotal(order)
|
||||
|
||||
#order-charges{"data-hook" => "order_details_adjustments"}
|
||||
- checkout_adjustments_for(order, exclude: [:line_item]).reject{ |a| a.amount == 0 }.reverse_each do |adjustment|
|
||||
%tr.total
|
||||
%td.text-right{:colspan => "3"}
|
||||
%strong
|
||||
= adjustment.label
|
||||
%td.text-right.total
|
||||
%span= adjustment.display_amount.to_html
|
||||
|
||||
#order-total{"data-hook" => "order_details_total"}
|
||||
%tr.total
|
||||
%td.text-right{colspan: "3"}
|
||||
%h5
|
||||
Total
|
||||
%td.text-right.total
|
||||
%h5#order_total= order.display_total.to_html
|
||||
|
||||
- if order.total_tax > 0
|
||||
#tax{"data-hook" => "order_details_tax"}
|
||||
%tr#tax-row.total
|
||||
%td.text-right{colspan: "3"}
|
||||
(includes tax)
|
||||
%td.text-right.total
|
||||
%span= display_checkout_tax_total(order)
|
||||
|
||||
1
config/initializers/delayed_job.rb
Normal file
@@ -0,0 +1 @@
|
||||
Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log'))
|
||||
13
config/initializers/js_template_helpers.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# Make helpers (#t in particular) available to javascript templates
|
||||
# https://github.com/pitr/angular-rails-templates/issues/45#issuecomment-43229086
|
||||
|
||||
Rails.application.assets.context_class.class_eval do
|
||||
# include ApplicationHelper
|
||||
# include ActionView::Helpers
|
||||
# include Rails.application.routes.url_helpers
|
||||
|
||||
# Including all of the helpers (above) has caused some intermittent CSS include issues
|
||||
# (not finding mixins from an @include in sass). Therefore, we're only including the
|
||||
# bare minimum here.
|
||||
include ActionView::Helpers::TranslationHelper
|
||||
end
|
||||
@@ -21,3 +21,4 @@ en-GB:
|
||||
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?
|
||||
|
||||
@@ -18,3 +18,4 @@ en:
|
||||
search_by_name: Search by name or suburb...
|
||||
producers: Aussie Producers
|
||||
producers_join: Australian producers are now welcome to join the Open Food Network.
|
||||
charges_sales_tax: Charges GST?
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddChargesSalesTaxToEnterprises < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :enterprises, :charges_sales_tax, :boolean, null: false, default: false
|
||||
end
|
||||
end
|
||||
6
script/delayed_job.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export HOME="/home/openfoodweb"
|
||||
export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
|
||||
|
||||
$HOME/apps/openfoodweb/current/script/delayed_job $@
|
||||
@@ -272,24 +272,19 @@ module Admin
|
||||
|
||||
context "setting 'sells' to 'own'" do
|
||||
before do
|
||||
enterprise.sells = 'own'
|
||||
enterprise.sells = 'none'
|
||||
enterprise.save!
|
||||
end
|
||||
|
||||
context "if the trial has finished" do
|
||||
before do
|
||||
enterprise.shop_trial_start_date = (Date.today - 30.days).to_time
|
||||
enterprise.save!
|
||||
end
|
||||
|
||||
it "is disallowed" do
|
||||
Timecop.freeze(Time.zone.local(2015, 4, 16, 14, 0, 0)) do
|
||||
enterprise.update_attribute(:shop_trial_start_date, 30.days.ago.beginning_of_day)
|
||||
spree_post :set_sells, { id: enterprise, sells: 'own' }
|
||||
expect(response).to redirect_to spree.admin_path
|
||||
trial_expiry = Date.today.strftime("%Y-%m-%d")
|
||||
expect(flash[:error]).to eq "Sorry, but you've already had a trial. Expired on: #{trial_expiry}"
|
||||
expect(enterprise.reload.sells).to eq 'own'
|
||||
expect(enterprise.reload.shop_trial_start_date).to eq (Date.today - 30.days).to_time
|
||||
expect(enterprise.reload.sells).to eq 'none'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,25 +4,58 @@ describe EnterpriseConfirmationsController do
|
||||
include AuthenticationWorkflow
|
||||
let!(:user) { create_enterprise_user( enterprise_limit: 10 ) }
|
||||
let!(:unconfirmed_enterprise) { create(:distributor_enterprise, confirmed_at: nil, owner: user) }
|
||||
let!(:confirmed_enterprise) { create(:distributor_enterprise, owner: user) }
|
||||
let!(:confirmed_enterprise) { create(:distributor_enterprise, confirmed_at: nil, owner: user) }
|
||||
let!(:confirmed_token) { confirmed_enterprise.confirmation_token }
|
||||
let!(:unowned_enterprise) { create(:distributor_enterprise) }
|
||||
|
||||
before do
|
||||
controller.stub spree_current_user: user
|
||||
@request.env["devise.mapping"] = Devise.mappings[:enterprise]
|
||||
confirmed_enterprise.confirm!
|
||||
end
|
||||
|
||||
context "confirming an enterprise" do
|
||||
it "that has already been confirmed" do
|
||||
spree_get :show, confirmation_token: confirmed_enterprise.confirmation_token
|
||||
expect(response).to redirect_to spree.admin_path
|
||||
expect(flash[:error]).to eq I18n.t('devise.enterprise_confirmations.enterprise.not_confirmed')
|
||||
context "that has already been confirmed" do
|
||||
|
||||
before do
|
||||
spree_get :show, confirmation_token: confirmed_token
|
||||
end
|
||||
|
||||
it "redirects the user to admin" do
|
||||
expect(response).to redirect_to spree.admin_path
|
||||
expect(flash[:error]).to eq I18n.t('devise.enterprise_confirmations.enterprise.not_confirmed')
|
||||
end
|
||||
end
|
||||
|
||||
it "that has not already been confirmed" do
|
||||
spree_get :show, confirmation_token: unconfirmed_enterprise.confirmation_token
|
||||
expect(response).to redirect_to spree.admin_path
|
||||
expect(flash[:success]).to eq I18n.t('devise.enterprise_confirmations.enterprise.confirmed')
|
||||
context "that has not been confirmed" do
|
||||
context "where the enterprise contact email maps to an existing user account" do
|
||||
before do
|
||||
unconfirmed_enterprise.update_attribute(:email, user.email)
|
||||
end
|
||||
|
||||
it "redirects the user to admin" do
|
||||
spree_get :show, confirmation_token: unconfirmed_enterprise.confirmation_token
|
||||
expect(response).to redirect_to spree.admin_path
|
||||
expect(flash[:success]).to eq I18n.t('devise.enterprise_confirmations.enterprise.confirmed')
|
||||
end
|
||||
end
|
||||
|
||||
context "where the enterprise contact email doesn't map to an existing user account" do
|
||||
let(:new_user) { create_enterprise_user }
|
||||
|
||||
before do
|
||||
unconfirmed_enterprise.update_attribute(:email, 'random@email.com')
|
||||
allow(Spree::User).to receive(:create) { new_user }
|
||||
allow(new_user).to receive(:reset_password_token) { "token" }
|
||||
end
|
||||
|
||||
it "redirects to the user to reset their password" do
|
||||
spree_get :show, confirmation_token: unconfirmed_enterprise.confirmation_token
|
||||
expect(response).to redirect_to spree.edit_spree_user_password_path(new_user, :reset_password_token => "token", return_to: spree.admin_path)
|
||||
expect(flash[:success]).to eq I18n.t('devise.enterprise_confirmations.enterprise.confirmed')
|
||||
expect(unconfirmed_enterprise.users(:reload)).to include new_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,4 +72,4 @@ describe EnterpriseConfirmationsController do
|
||||
expect(flash[:error]).to eq "Authorization Failure"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
31
spec/controllers/spree/user_sessions_controller_spec.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Spree::UserSessionsController do
|
||||
include AuthenticationWorkflow
|
||||
|
||||
let(:user) { create_enterprise_user }
|
||||
|
||||
before do
|
||||
@request.env["devise.mapping"] = Devise.mappings[:spree_user]
|
||||
end
|
||||
|
||||
describe "create" do
|
||||
context "succeed" do
|
||||
context "when referer is not '/checkout'" do
|
||||
it "redirects to root" do
|
||||
spree_post :create, spree_user: {email: user.email, password: user.password }, :use_route => :spree
|
||||
response.should redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
context "when referer is '/checkout'" do
|
||||
before { @request.env['HTTP_REFERER'] = 'http://test.com/checkout' }
|
||||
|
||||
it "redirects to checkout" do
|
||||
spree_post :create, spree_user: { email: user.email, password: user.password }, :use_route => :spree
|
||||
response.should redirect_to checkout_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,15 +9,26 @@ describe UserPasswordsController do
|
||||
ActionMailer::Base.default_url_options[:host] = "test.host"
|
||||
end
|
||||
|
||||
it "returns errors" do
|
||||
spree_post :create, spree_user: {}
|
||||
response.should be_success
|
||||
response.should render_template "spree/user_passwords/new"
|
||||
describe "create" do
|
||||
it "returns errors" do
|
||||
spree_post :create, spree_user: {}
|
||||
response.should be_success
|
||||
response.should render_template "spree/user_passwords/new"
|
||||
end
|
||||
|
||||
it "redirects to login when data is valid" do
|
||||
spree_post :create, spree_user: { email: user.email}
|
||||
response.should be_redirect
|
||||
end
|
||||
end
|
||||
|
||||
it "redirects to login when data is valid" do
|
||||
spree_post :create, spree_user: { email: user.email}
|
||||
response.should be_redirect
|
||||
describe "edit" do
|
||||
context "when given a redirect" do
|
||||
it "stores the redirect path in 'spree_user_return_to'" do
|
||||
spree_post :edit, reset_password_token: "token", return_to: "/return_path"
|
||||
expect(session["spree_user_return_to"]).to eq "/return_path"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "renders Darkswarm" do
|
||||
|
||||
@@ -6,7 +6,7 @@ describe UserRegistrationsController do
|
||||
before do
|
||||
@request.env["devise.mapping"] = Devise.mappings[:spree_user]
|
||||
end
|
||||
|
||||
|
||||
describe "via ajax" do
|
||||
render_views
|
||||
it "returns errors when registration fails" do
|
||||
@@ -25,15 +25,31 @@ describe UserRegistrationsController do
|
||||
end
|
||||
end
|
||||
|
||||
it "renders new when registration fails" do
|
||||
spree_post :create, spree_user: {}
|
||||
response.status.should == 200
|
||||
response.should render_template "spree/user_registrations/new"
|
||||
context "when registration fails" do
|
||||
it "renders new" do
|
||||
spree_post :create, spree_user: {}
|
||||
response.status.should == 200
|
||||
response.should render_template "spree/user_registrations/new"
|
||||
end
|
||||
end
|
||||
|
||||
it "redirects when registration succeeds" do
|
||||
spree_post :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree
|
||||
response.should be_redirect
|
||||
assigns[:user].email.should == "test@test.com"
|
||||
context "when registration succeeds" do
|
||||
context "when referer is not '/checkout'" do
|
||||
it "redirects to root" do
|
||||
spree_post :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree
|
||||
response.should redirect_to root_path
|
||||
assigns[:user].email.should == "test@test.com"
|
||||
end
|
||||
end
|
||||
|
||||
context "when referer is '/checkout'" do
|
||||
before { @request.env['HTTP_REFERER'] = 'http://test.com/checkout' }
|
||||
|
||||
it "redirects to checkout" do
|
||||
spree_post :create, spree_user: {email: "test@test.com", password: "testy123", password_confirmation: "testy123"}, :use_route => :spree
|
||||
response.should redirect_to checkout_path
|
||||
assigns[:user].email.should == "test@test.com"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -200,6 +200,7 @@ FactoryGirl.define do
|
||||
tax_category { create(:tax_category) }
|
||||
|
||||
after(:create) do |product, proxy|
|
||||
raise "taxed_product factory requires a zone" unless proxy.zone
|
||||
create(:tax_rate, amount: proxy.tax_rate_amount, tax_category: product.tax_category, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, zone: proxy.zone)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -218,6 +218,7 @@ feature %q{
|
||||
click_link "Business Details"
|
||||
fill_in 'enterprise_abn', :with => '09812309823'
|
||||
fill_in 'enterprise_acn', :with => ''
|
||||
choose 'Yes' # enterprise_charges_sales_tax
|
||||
|
||||
click_link "Address"
|
||||
fill_in 'enterprise_address_attributes_address1', :with => '35 Ballantyne St'
|
||||
@@ -237,6 +238,9 @@ feature %q{
|
||||
@enterprise.reload
|
||||
expect(@enterprise.owner).to eq user
|
||||
|
||||
click_link "Business Details"
|
||||
page.should have_checked_field "enterprise_charges_sales_tax_true"
|
||||
|
||||
click_link "Payment Methods"
|
||||
page.should have_checked_field "enterprise_payment_method_ids_#{payment_method.id}"
|
||||
|
||||
|
||||
@@ -107,10 +107,10 @@ feature %q{
|
||||
|
||||
page.should have_content 'Payment State'
|
||||
end
|
||||
|
||||
describe "Sales tax report" do
|
||||
let(:distributor1) { create(:distributor_enterprise, with_payment_and_shipping: true) }
|
||||
let(:distributor2) { create(:distributor_enterprise, with_payment_and_shipping: true) }
|
||||
|
||||
describe "sales tax report" do
|
||||
let(:distributor1) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:distributor2) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:user1) { create_enterprise_user enterprises: [distributor1] }
|
||||
let(:user2) { create_enterprise_user enterprises: [distributor2] }
|
||||
let(:shipping_method) { create(:shipping_method, name: "Shipping", description: "Expensive", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 100.55)) }
|
||||
@@ -140,7 +140,7 @@ feature %q{
|
||||
click_link "Reports"
|
||||
click_link "Sales Tax"
|
||||
end
|
||||
|
||||
|
||||
it "reports" do
|
||||
# Then it should give me access only to managed enterprises
|
||||
page.should have_select 'q_distributor_id_eq', with_options: [user1.enterprises.first.name]
|
||||
|
||||
@@ -60,15 +60,17 @@ feature "Registration", js: true do
|
||||
fill_in 'enterprise_long_desc', with: 'Long description'
|
||||
fill_in 'enterprise_abn', with: '12345'
|
||||
fill_in 'enterprise_acn', with: '54321'
|
||||
choose 'Yes' # enterprise_charges_sales_tax
|
||||
click_button 'Continue'
|
||||
|
||||
# Enterprise should be update
|
||||
# Enterprise should be updated
|
||||
expect(page).to have_content "Let's upload some pretty pictures so your profile looks great!"
|
||||
e.reload
|
||||
expect(e.description).to eq "Short description"
|
||||
expect(e.long_description).to eq "Long description"
|
||||
expect(e.abn).to eq '12345'
|
||||
expect(e.acn).to eq '54321'
|
||||
expect(e.charges_sales_tax).to be_true
|
||||
|
||||
# Images
|
||||
# Move from logo page
|
||||
|
||||
32
spec/features/consumer/shopping/cart_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
require 'spec_helper'
|
||||
|
||||
feature "full-page cart", js: true do
|
||||
include AuthenticationWorkflow
|
||||
include WebHelper
|
||||
include ShopWorkflow
|
||||
include UIComponentHelper
|
||||
|
||||
describe "viewing the cart" do
|
||||
describe "tax" 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(: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) }
|
||||
|
||||
before do
|
||||
add_enterprise_fee enterprise_fee
|
||||
set_order order
|
||||
add_product_to_cart
|
||||
visit spree.cart_path
|
||||
end
|
||||
|
||||
it "shows the total tax for the order, including product tax and tax on fees" do
|
||||
save_screenshot '/home/rohan/ss.png', full: true
|
||||
page.should have_selector '.tax-total', text: '11.00' # 10 + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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)) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) }
|
||||
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") }
|
||||
|
||||
@@ -8,24 +8,23 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
include WebHelper
|
||||
include UIComponentHelper
|
||||
|
||||
let(:distributor) { create(:distributor_enterprise) }
|
||||
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(:enterprise_fee) { create(:enterprise_fee, amount: 1.23) }
|
||||
let(:product) { create(:simple_product, supplier: supplier) }
|
||||
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(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) }
|
||||
|
||||
before do
|
||||
Spree::Config.shipment_inc_vat = true
|
||||
Spree::Config.shipping_tax_rate = 0.25
|
||||
|
||||
add_enterprise_fee enterprise_fee
|
||||
set_order order
|
||||
add_product_to_cart
|
||||
end
|
||||
|
||||
it "shows the current distributor on checkout" do
|
||||
visit checkout_path
|
||||
page.should have_content distributor.name
|
||||
end
|
||||
|
||||
describe "with shipping and payment methods" do
|
||||
let(:sm1) { create(:shipping_method, require_ship_address: true, name: "Frogs", description: "yellow", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 0.00)) }
|
||||
let(:sm2) { create(:shipping_method, require_ship_address: false, name: "Donkeys", description: "blue", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 4.56)) }
|
||||
@@ -50,6 +49,11 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
checkout_as_guest
|
||||
end
|
||||
|
||||
it "shows the current distributor" do
|
||||
visit checkout_path
|
||||
page.should have_content distributor.name
|
||||
end
|
||||
|
||||
it "shows a breakdown of the order price" do
|
||||
toggle_shipping
|
||||
choose sm2.name
|
||||
@@ -57,6 +61,11 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
page.should have_selector 'orderdetails .cart-total', text: "$11.23"
|
||||
page.should have_selector 'orderdetails .shipping', text: "$4.56"
|
||||
page.should have_selector 'orderdetails .total', text: "$15.79"
|
||||
|
||||
# Tax should not be displayed in checkout, as the customer's choice of shipping method
|
||||
# affects the tax and we haven't written code to live-update the tax amount when they
|
||||
# make a change.
|
||||
page.should_not have_content product.tax_category.name
|
||||
end
|
||||
|
||||
it "shows all shipping methods, but doesn't show ship address when not needed" do
|
||||
@@ -117,14 +126,25 @@ feature "As a consumer I want to check out my cart", js: true do
|
||||
choose pm1.name
|
||||
end
|
||||
|
||||
|
||||
expect do
|
||||
place_order
|
||||
page.should have_content "Your order has been processed successfully"
|
||||
end.to enqueue_job ConfirmOrderJob
|
||||
|
||||
# And the order's special instructions should be set
|
||||
o = Spree::Order.complete.first
|
||||
expect(o.special_instructions).to eq "SpEcIaL NoTeS"
|
||||
|
||||
# And the Spree tax summary should not be displayed
|
||||
page.should_not have_content product.tax_category.name
|
||||
|
||||
# And the total tax for the order, including shipping and fee tax, should be displayed
|
||||
# product tax ($10.00 @ 10% = $0.91)
|
||||
# + fee tax ($ 1.23 @ 10% = $0.11)
|
||||
# + shipping tax ($ 4.56 @ 25% = $0.91)
|
||||
# = $1.93
|
||||
page.should have_content "(includes tax)"
|
||||
page.should have_content "$1.93"
|
||||
end
|
||||
|
||||
context "with basic details filled" do
|
||||
|
||||
@@ -125,25 +125,6 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
let(:variant) { product.variants.first }
|
||||
let(:product2) { create(:simple_product, group_buy: false) }
|
||||
|
||||
describe "without variants" do
|
||||
before do
|
||||
add_product_to_order_cycle(exchange, product)
|
||||
set_order_cycle(order, oc1)
|
||||
visit shop_path
|
||||
end
|
||||
|
||||
# TODO move to controller test
|
||||
pending "adding a product with a max quantity less than quantity results in max_quantity==quantity" do
|
||||
fill_in "variants[#{variant.id}]", with: 5
|
||||
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 1
|
||||
add_to_cart
|
||||
page.should have_content product.name
|
||||
li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
|
||||
li.max_quantity.should == 5
|
||||
li.quantity.should == 5
|
||||
end
|
||||
end
|
||||
|
||||
describe "with variants on the product" do
|
||||
let(:variant) { create(:variant, product: product, on_hand: 10 ) }
|
||||
before do
|
||||
|
||||
@@ -12,4 +12,12 @@ describe CheckoutHelper do
|
||||
|
||||
helper.validated_input("test", "foo", type: :email)
|
||||
end
|
||||
|
||||
describe "displaying the tax total for an order" do
|
||||
let(:order) { double(:order, total_tax: 123.45, currency: 'AUD') }
|
||||
|
||||
it "retrieves the total tax on the order" do
|
||||
helper.display_checkout_tax_total(order).should == Spree::Money.new(123.45, currency: 'AUD')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,19 +4,25 @@ describe 'ProductsCtrl', ->
|
||||
event = null
|
||||
Products = null
|
||||
Cart = {}
|
||||
Taxons = null
|
||||
Properties = null
|
||||
|
||||
beforeEach ->
|
||||
module('Darkswarm')
|
||||
Products =
|
||||
Products =
|
||||
all: ->
|
||||
update: ->
|
||||
products: ["testy mctest"]
|
||||
loading: false
|
||||
OrderCycle =
|
||||
order_cycle: {}
|
||||
|
||||
inject ($controller) ->
|
||||
scope = {}
|
||||
ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle, Cart: Cart}
|
||||
Taxons:
|
||||
taxons: []
|
||||
Properties: {}
|
||||
|
||||
inject ($rootScope, $controller) ->
|
||||
scope = $rootScope
|
||||
ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle, Cart: Cart, Taxons: Taxons, Properties: Properties}
|
||||
|
||||
it 'fetches products from Products', ->
|
||||
expect(scope.Products.products).toEqual ['testy mctest']
|
||||
|
||||
@@ -4,13 +4,15 @@ describe 'Products service', ->
|
||||
Enterprises = null
|
||||
Variants = null
|
||||
Cart = null
|
||||
CurrentHubMock = {}
|
||||
CurrentHubMock = {}
|
||||
currentOrder = null
|
||||
product = null
|
||||
productWithImage = null
|
||||
properties = null
|
||||
taxons = null
|
||||
|
||||
beforeEach ->
|
||||
product =
|
||||
product =
|
||||
test: "cats"
|
||||
supplier:
|
||||
id: 9
|
||||
@@ -27,16 +29,23 @@ describe 'Products service', ->
|
||||
]
|
||||
currentOrder =
|
||||
line_items: []
|
||||
properties =
|
||||
{ id: 1, name: "some property" }
|
||||
taxons =
|
||||
{ id: 2, name: "some taxon" }
|
||||
|
||||
module 'Darkswarm'
|
||||
module ($provide)->
|
||||
$provide.value "CurrentHub", CurrentHubMock
|
||||
$provide.value "currentOrder", currentOrder
|
||||
$provide.value "CurrentHub", CurrentHubMock
|
||||
$provide.value "currentOrder", currentOrder
|
||||
$provide.value "taxons", taxons
|
||||
$provide.value "properties", properties
|
||||
null
|
||||
|
||||
inject ($injector, _$httpBackend_)->
|
||||
Products = $injector.get("Products")
|
||||
Enterprises = $injector.get("Enterprises")
|
||||
Properties = $injector.get("Properties")
|
||||
Variants = $injector.get("Variants")
|
||||
Cart = $injector.get("Cart")
|
||||
$httpBackend = _$httpBackend_
|
||||
@@ -44,20 +53,32 @@ describe 'Products service', ->
|
||||
it "Fetches products from the backend on init", ->
|
||||
$httpBackend.expectGET("/shop/products").respond([product])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].test).toEqual "cats"
|
||||
expect(Products.products[0].test).toEqual "cats"
|
||||
|
||||
it "dereferences suppliers", ->
|
||||
Enterprises.enterprises_by_id =
|
||||
Enterprises.enterprises_by_id =
|
||||
{id: 9, name: "test"}
|
||||
$httpBackend.expectGET("/shop/products").respond([{supplier : {id: 9}, master: {}}])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].supplier).toBe Enterprises.enterprises_by_id["9"]
|
||||
|
||||
it "dereferences taxons", ->
|
||||
product.taxons = [2]
|
||||
$httpBackend.expectGET("/shop/products").respond([product])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].taxons[1]).toBe taxons[0]
|
||||
|
||||
it "dereferences properties", ->
|
||||
product.properties = [1]
|
||||
$httpBackend.expectGET("/shop/products").respond([product])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].properties[1]).toBe properties[0]
|
||||
|
||||
it "registers variants with Variants service", ->
|
||||
product.variants = [{id: 1}]
|
||||
$httpBackend.expectGET("/shop/products").respond([product])
|
||||
$httpBackend.flush()
|
||||
expect(Products.products[0].variants[0]).toBe Variants.variants[1]
|
||||
expect(Products.products[0].variants[0]).toBe Variants.variants[1]
|
||||
|
||||
it "registers variants with the Cart", ->
|
||||
product.variants = [{id: 8}]
|
||||
@@ -0,0 +1,16 @@
|
||||
describe "Properties service", ->
|
||||
Properties = null
|
||||
properties = [
|
||||
{id: 1, name: "Property1"}
|
||||
{id: 2, name: "Property2"}
|
||||
]
|
||||
|
||||
beforeEach ->
|
||||
module('Darkswarm')
|
||||
angular.module('Darkswarm').value 'properties', properties
|
||||
|
||||
inject ($injector)->
|
||||
Properties = $injector.get("Properties")
|
||||
|
||||
it "caches properties in an id-referenced hash", ->
|
||||
expect(Properties.properties_by_id[1]).toBe properties[0]
|
||||
@@ -30,7 +30,8 @@ module Spree
|
||||
end
|
||||
|
||||
describe "Shipment adjustments" do
|
||||
let!(:order) { create(:order, shipping_method: shipping_method) }
|
||||
let!(:order) { create(:order, distributor: hub, shipping_method: shipping_method) }
|
||||
let(:hub) { create(:distributor_enterprise, charges_sales_tax: true) }
|
||||
let!(:line_item) { create(:line_item, order: order) }
|
||||
let(:shipping_method) { create(:shipping_method, calculator: Calculator::FlatRate.new(preferred_amount: 50.0)) }
|
||||
let(:adjustment) { order.adjustments(:reload).shipping.first }
|
||||
@@ -80,6 +81,13 @@ module Spree
|
||||
|
||||
adjustment.included_tax.should == 0
|
||||
end
|
||||
|
||||
it "records 0% tax on shipments when the distributor does not charge sales tax" do
|
||||
order.distributor.update_attributes! charges_sales_tax: false
|
||||
order.reload.create_shipment!
|
||||
|
||||
adjustment.included_tax.should == 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,7 +96,7 @@ module Spree
|
||||
let(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Calculator::DefaultTax.new, zone: zone, amount: 0.1) }
|
||||
let(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) }
|
||||
|
||||
let(:coordinator) { create(:distributor_enterprise) }
|
||||
let(:coordinator) { create(:distributor_enterprise, charges_sales_tax: true) }
|
||||
let(:variant) { create(:variant) }
|
||||
let(:order_cycle) { create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: [enterprise_fee], distributors: [coordinator], variants: [variant]) }
|
||||
let!(:order) { create(:order, order_cycle: order_cycle, distributor: coordinator) }
|
||||
|
||||
33
spec/models/spree/tax_rate_spec.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
module Spree
|
||||
describe TaxRate do
|
||||
describe "selecting tax rates to apply to an order" do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let!(:order) { create(:order, distributor: hub, bill_address: create(:address)) }
|
||||
let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Calculator::FlatRate.new(preferred_amount: 0.1), zone: zone) }
|
||||
|
||||
describe "when the order's hub charges sales tax" do
|
||||
let(:hub) { create(:distributor_enterprise, charges_sales_tax: true) }
|
||||
|
||||
it "selects all tax rates" do
|
||||
TaxRate.match(order).should == [tax_rate]
|
||||
end
|
||||
end
|
||||
|
||||
describe "when the order's hub does not charge sales tax" do
|
||||
let(:hub) { create(:distributor_enterprise, charges_sales_tax: false) }
|
||||
|
||||
it "selects no tax rates" do
|
||||
TaxRate.match(order).should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "when the order does not have a hub" do
|
||||
let!(:order) { create(:order, distributor: nil, bill_address: create(:address)) }
|
||||
|
||||
it "selects all tax rates" do
|
||||
TaxRate.match(order).should == [tax_rate]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,7 +33,7 @@ require 'capybara/poltergeist'
|
||||
Capybara.javascript_driver = :poltergeist
|
||||
|
||||
Capybara.register_driver :poltergeist do |app|
|
||||
options = {phantomjs_options: ['--load-images=no'], window_size: [1280, 800]}
|
||||
options = {phantomjs_options: ['--load-images=no'], window_size: [1280, 800], timeout: 1.minute}
|
||||
# Extend poltergeist's timeout to allow ample time to use pry in browser thread
|
||||
#options.merge! {timeout: 5.minutes}
|
||||
# Enable the remote inspector: Use page.driver.debug to open a remote debugger in chrome
|
||||
@@ -92,6 +92,7 @@ RSpec.configure do |config|
|
||||
config.include OpenFoodNetwork::FeatureToggleHelper
|
||||
config.include OpenFoodNetwork::EnterpriseGroupsHelper
|
||||
config.include OpenFoodNetwork::DistributionHelper
|
||||
config.include OpenFoodNetwork::HtmlHelper
|
||||
config.include ActionView::Helpers::DateHelper
|
||||
config.include OpenFoodNetwork::DelayedJobHelper
|
||||
|
||||
|
||||
10
spec/support/html_helper.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module OpenFoodNetwork
|
||||
module HtmlHelper
|
||||
def save_and_open(html)
|
||||
require "launchy"
|
||||
file = Tempfile.new('html')
|
||||
file.write html
|
||||
Launchy.open(file.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,11 +17,10 @@ module ShopWorkflow
|
||||
end
|
||||
|
||||
def add_product_to_cart
|
||||
create(:line_item, variant: product.master, order: order)
|
||||
order.reload
|
||||
populator = Spree::OrderPopulator.new(order, order.currency)
|
||||
populator.populate(variants: {product.master.id => 1})
|
||||
|
||||
# Recalculate totals
|
||||
order.save!
|
||||
# Recalculate fee totals
|
||||
order.update_distribution_charge!
|
||||
end
|
||||
|
||||
|
||||