Merging master into require_standard_variant

This commit is contained in:
Rob Harrington
2015-04-17 21:18:24 +10:00
93 changed files with 1185 additions and 565 deletions

View File

@@ -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'

View File

@@ -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!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,7 @@
Darkswarm.filter 'propertiesOf', ->
(objects)->
properties = {}
for object in objects
for property in object.properties
properties[property.id] = property
properties

View 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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}" } }

View File

@@ -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 }}

View File

@@ -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'"}

View File

@@ -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'"}

View File

@@ -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

View File

@@ -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 }}

View File

@@ -1,3 +0,0 @@
%active-selector{"ng-repeat" => "selector in selectors()"}
%render-svg{path: "{{selector.taxon.icon}}"}
%span {{ selector.taxon.name }}

View 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)

View File

@@ -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

View File

@@ -0,0 +1,6 @@
.product-header
h1, h2, h3, h4, h5, h6
margin: 0
hr
margin: 0.5em 0

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,4 +1,6 @@
Spree::UserSessionsController.class_eval do
before_filter :set_checkout_redirect, only: :create
def create
authenticate_spree_user!

View File

@@ -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

View File

@@ -1,4 +1,5 @@
class UserRegistrationsController < Spree::UserRegistrationsController
before_filter :set_checkout_redirect, only: :create
# POST /resource/sign_up
def create

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
class Api::IdNameSerializer < ActiveModel::Serializer
attributes :id, :name
end

View File

@@ -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

View File

@@ -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
&nbsp;
= f.label :charges_sales_tax, "Yes", :value => "true"
.five.columns.omega
= f.radio_button :charges_sales_tax, false
&nbsp;
= f.label :charges_sales_tax, "No", :value => "false"

View File

@@ -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"}

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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"}

View File

@@ -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 }" }

View File

@@ -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,

View 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 &nbsp;

View 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 &nbsp;

View 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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)}

View File

@@ -0,0 +1,7 @@
- if @order.special_instructions.present?
%br
%p
%small
%strong Your notes:
%br
#{@order.special_instructions}

View File

@@ -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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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

View File

@@ -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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
- 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 &nbsp;
= render 'shared/mailers/signoff'

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -0,0 +1 @@
Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log'))

View 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

View File

@@ -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?

View File

@@ -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?

View File

@@ -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
View 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 $@

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}"

View File

@@ -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]

View File

@@ -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

View 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

View File

@@ -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") }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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']

View File

@@ -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}]

View File

@@ -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]

View File

@@ -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) }

View 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

View File

@@ -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

View 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

View File

@@ -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