Merge branch 'laura_and_will'

This commit is contained in:
Will Marshall
2014-07-31 16:06:39 +10:00
63 changed files with 652 additions and 389 deletions

View File

@@ -6,7 +6,8 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.limit = 3
$scope.ordering = {order: "name"}
$scope.ordering =
order: "primary_taxon.name"
$scope.order_cycle = OrderCycle.order_cycle
$scope.incrementLimit = ->

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "activeSelector", ->
# A generic selector that allows an object/scope to be toggled between active and inactive
# Used in the filters, but hypothetically useable anywhere
restrict: 'E'
transclude: true
replace: true
@@ -8,5 +10,6 @@ Darkswarm.directive "activeSelector", ->
elem.bind "click", ->
scope.$apply ->
scope.selector.active = !scope.selector.active
scope.emit()
# This function is a convention, e.g. a callback on the scope applied when active changes
scope.emit() if scope.emit

View File

@@ -1,11 +1,12 @@
Darkswarm.directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
# Change the text of the hub link based on CurrentHub
# To be used with ofnEmptiesCart
# Takes "change" and "shop" as text string attributes
restrict: "A"
scope:
hub: '=activeTableHubLink'
template: "{{action}}"
link: (scope, elm, attr)->
# Swap out the text of the hub link depending on whether it'll change current hub
# To be used with ofnEmptiesCart
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id
scope.action = attr.change
else

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "cart", ->
# Toggles visibility of the "cart" popover
restrict: 'A'
link: (scope, elem, attr)->
scope.open = false

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "ngDebounce", ($timeout) ->
# Slows down ng-model updates, only triggering binding ngDebounce milliseconds
# after the last change. Used to prevent squirrely UI
restrict: "A"
require: "ngModel"
priority: 99

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "ofnDisableEnter", ()->
# Stops enter from doing normal enter things
restrict: 'A'
link: (scope, element, attrs)->
element.bind "keydown keypress", (e)->

View File

@@ -1,6 +1,7 @@
Darkswarm.directive "ofnDisableScroll", ()->
# Stops scrolling from incrementing or decrementing input value
# Useful for number inputs
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'focus', ->
element.bind 'mousewheel', (e)->

View File

@@ -1,12 +1,13 @@
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, CurrentOrder, Navigation, storage) ->
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, Cart, Navigation, storage) ->
# Compares scope.hub with CurrentHub. Will trigger an confirmation if they are different,
# and Cart isn't empty
restrict: "A"
scope:
hub: "=ofnEmptiesCart"
link: (scope, elm, attr)->
hub = scope.$eval(attr.ofnEmptiesCart)
# A hub is selected, we're changing to a different hub, and the cart isn't empty
if CurrentHub.hub?.id and CurrentHub.hub.id isnt hub.id
unless CurrentOrder.empty()
elm.bind 'click', (ev)->
ev.preventDefault()
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
storage.clearAll() # One day this will have to be moar GRANULAR
Navigation.go scope.hub.path
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id and !Cart.empty()
elm.bind 'click', (ev)->
ev.preventDefault()
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
storage.clearAll() # One day this will have to be moar GRANULAR
Navigation.go scope.hub.path

View File

@@ -1,10 +1,9 @@
Darkswarm.directive "fillVertical", ($window)->
# Makes something fill the window vertically. Used on the Google Map.
restrict: 'A'
link: (scope, element, attrs)->
setSize = ->
element.css "height", ($window.innerHeight - element.offset().top)
setSize()
angular.element($window).bind "resize", ->
setSize()

View File

@@ -1,5 +1,6 @@
Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
# Mappings between flash types (left) and Foundation classes
# Our own flash class. Uses the "flash" service (third party), and a directive
# called RailsFlashLoader to render
typePairings =
info: "info"
error: "alert"
@@ -13,6 +14,8 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
link: ($scope, element, attr) ->
$scope.flashes = []
# Callback when a new flash message is pushed to flash service
show = (message, type)=>
if message
$scope.flashes.push({message: message, type: typePairings[type]})
@@ -21,5 +24,6 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
$scope.delete = ->
$scope.flashes.shift()
# Register our callback (above) with flash service
flash.subscribe(show)
RailsFlashLoader.initFlash()

View File

@@ -1,9 +1,9 @@
Darkswarm.directive "ofnFocus", ->
# Takes an expression attrs.ofnFocus
# Watches value of expression, triggers element.focus() when value is truthy
# Used to automatically focus on specific inputs in various circumstances
restrict: "A"
link: (scope, element, attrs) ->
scope.$watch attrs.ofnFocus, ((focus) ->
focus and element.focus()
return
), true
return

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "loading", (Loading)->
# Triggers a screen-wide "loading" thing when Ajaxy stuff is happening
scope: {}
restrict: 'E'
templateUrl: 'loading.html'
@@ -6,5 +7,3 @@ Darkswarm.directive "loading", (Loading)->
$scope.Loading = Loading
$scope.show = ->
$scope.Loading.message?
link: ($scope, element, attr)->

View File

@@ -3,7 +3,7 @@ Darkswarm.directive 'mapSearch', ($timeout)->
restrict: 'E'
require: '^googleMap'
replace: true
template: '<input id="pac-input"></input>'
template: '<input id="pac-input" placeholder="Type in a location..."></input>'
link: (scope, elem, attrs, ctrl)->
$timeout =>
map = ctrl.getMap()

View File

@@ -0,0 +1,6 @@
Darkswarm.directive "max", ->
restrict: 'A'
link: (scope, elem, attr)->
elem.bind 'input', ->
if elem.val() > +attr.max
elem.val attr.max

View File

@@ -1,16 +1,19 @@
Darkswarm.directive "ofnModal", ($modal)->
# Generic modal! Uses transclusion so designer-types can do stuff like:
# %ofn-modal
# CONTENT
# Only works for simple cases, so roll your own when necessary!
restrict: 'E'
replace: true
transclude: true
scope: {}
scope: true
template: "<a>{{title}}</a>"
# Instead of using ng-transclude we compile the transcluded template to a string
# This compiled template is sent to the $modal service! Such magic!
# In theory we could compile the template directly inside link rather than onclick, but it's performant so meh!
link: (scope, elem, attrs, ctrl, transclude)->
scope.title = attrs.title
contents = null
elem.on "click", =>
# We're using an isolate scope, which is a child of the original scope
# We have to compile the transclude against the original scope, not the isolate
transclude scope.$parent, (clone)->
contents = clone
scope.modalInstance = $modal.open(controller: ctrl, template: contents, scope: scope.$parent)
transclude scope, (clone)->
scope.modalInstance = $modal.open(controller: ctrl, template: clone, scope: scope)

View File

@@ -1,11 +1,20 @@
Darkswarm.directive "priceBreakdown", ($tooltip)->
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
# We use the $tooltip service from Angular foundation to give us boilerplate
# Subsequently we patch the scope, template and restrictions
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
tooltip.scope =
variant: "="
tooltip.templateUrl = "price_breakdown_button.html"
tooltip.replace = true
tooltip.restrict = 'E'
tooltip
# This is automatically referenced via naming convention in $tooltip
Darkswarm.directive 'priceBreakdownPopup', ->
restrict: 'EA'
replace: true
templateUrl: 'price_breakdown.html'
scope: true
scope: false
link: (scope, elem, attrs) ->
scope.expanded = false unless scope.expanded?

View File

@@ -0,0 +1,10 @@
Darkswarm.directive "pricePercentage", ->
restrict: 'E'
replace: true
templateUrl: 'price_percentage.html'
scope:
percentage: '='
link: (scope, elem, attrs) ->
elem.find(".meter").css
width: "#{scope.percentage}%"

View File

@@ -1,7 +1,11 @@
Darkswarm.directive "renderSvg", ()->
# Magical directive that'll render SVGs from URLs
# If only there were a neater way of doing this
restrict: 'E'
priority: 99
template: "<svg-wrapper></svg-wrapper>"
# Fetch SVG via ajax, inject into page using DOM
link: (scope, elem, attr)->
if /.svg/.test attr.path # Only do this if we've got an svg
$.ajax

View File

@@ -1,4 +1,5 @@
Darkswarm.directive 'scrollAfterLoad', ($timeout, $location, $document)->
# Scroll to an element on page load
restrict: "A"
link: (scope, element, attr) ->
if scope.$last is true

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "ofnScrollTo", ($location, $anchorScroll)->
# Onclick sets $location.hash to attrs.ofnScrollTo
# Then triggers anchorScroll
restrict: 'A'
link: (scope, element, attrs)->
element.bind 'click', (ev)->

View File

@@ -1,4 +1,5 @@
Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
# Builds selector for shipping types
restrict: 'E'
replace: true
templateUrl: 'shipping_type_selector.html'

View File

@@ -0,0 +1,6 @@
Darkswarm.directive "shopVariant", ->
restrict: 'E'
replace: true
templateUrl: 'shop_variant.html'
scope:
variant: '='

View File

@@ -1,4 +1,6 @@
Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
# Automatically builds activeSelectors for taxons
# Lots of magic here
restrict: 'E'
replace: true
scope:
@@ -8,7 +10,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
link: (scope, elem, attr)->
selectors_by_id = {}
selectors = ["foo"]
selectors = null # To get scoping/closure right
scope.emit = ->
scope.results = selectors.filter (selector)->
@@ -16,6 +18,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
.map (selector)->
selector.taxon.id
# Build hash of unique taxons, each of which gets an ActiveSelector
scope.selectors = ->
taxons = {}
selectors = []
@@ -25,7 +28,11 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
if object.supplied_taxons
for taxon in object.supplied_taxons
taxons[taxon.id] = taxon
# Generate a selector for each taxon.
# 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
if selector = selectors_by_id[id]
selectors.push selector

View File

@@ -19,7 +19,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
$http.post('/orders/populate', @data()).success (data, status)=>
@saved()
.error (response, status)=>
alert "There was an error on the server! Please refresh the page"
# TODO what shall we do here?
data: =>
variants = {}
@@ -43,6 +43,9 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
@line_items.filter (li)->
li.quantity > 0
empty: =>
@line_items_present().length == 0
total: =>
@line_items_present().map (li)->
li.variant.getPrice()
@@ -57,6 +60,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
create_line_item: (variant)->
variant.line_item =
variant: variant
quantity: 0
quantity: null
max_quantity: null
@line_items.push variant.line_item

View File

@@ -1,5 +1,3 @@
Darkswarm.factory 'CurrentOrder', (currentOrder) ->
new class CurrentOrder
order: currentOrder
empty: =>
@order.line_items.length == 0

View File

@@ -28,6 +28,8 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
for product in @products
if product.variants
product.variants = (Variants.register variant for variant in product.variants)
variant.product = product for variant in product.variants
product.master.product = product
product.master = Variants.register product.master if product.master
registerVariantsWithCart: ->
@@ -44,5 +46,5 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
product.price = Math.min.apply(null, prices)
product.hasVariants = product.variants?.length > 0
product.primaryImage = product.images[0]?.small_url
product.primaryImage = product.images[0]?.small_url if product.images
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"

View File

@@ -7,4 +7,5 @@ Darkswarm.factory 'Variants', ->
extend: (variant)->
variant.getPrice = ->
variant.price * variant.line_item.quantity
variant.basePricePercentage = Math.round(variant.base_price / variant.price * 100)
variant

View File

@@ -1,4 +1,35 @@
.joyride-tip-guide{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.bottom
.joyride-tip-guide{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.right
.joyride-content-wrapper
{{ variant.id }}
.collapsed{"ng-show" => "!expanded"}
%price-percentage{percentage: 'variant.basePricePercentage'}
%a{"ng-click" => "expanded = !expanded"}
Full price breakdown
%i.ofn-i_005-caret-down
.expanded{"ng-show" => "expanded"}
%ul
%li.cost
.right {{ variant.base_price | currency }}
Cost
%li{"bo-if" => "variant.fees.admin"}
.right {{ variant.fees.admin | currency }}
Admin fee
%li{"bo-if" => "variant.fees.sales"}
.right {{ variant.fees.sales | currency }}
Sales fee
%li{"bo-if" => "variant.fees.packing"}
.right {{ variant.fees.packing | currency }}
Packing fee
%li{"bo-if" => "variant.fees.transport"}
.right {{ variant.fees.transport | currency }}
Transport fee
%li
%strong
.right = {{ variant.price | currency }}
&nbsp;
%a{"ng-click" => "expanded = !expanded"}
Price graph
%i.ofn-i_006-caret-up

View File

@@ -0,0 +1,2 @@
%button.graph-button{"ng-class" => "{open: tt_isOpen}"}
%i.ofn-i-058-graph

View File

@@ -0,0 +1,5 @@
.progress
.right Fees
.meter
Cost

View File

@@ -3,7 +3,7 @@
%img.product-img{"ng-src" => "{{product.primaryImage}}", "ng-if" => "product.primaryImage"}
.columns.small-12.large-6.product-header
%h2
%render-svg{path: "{{product.primary_taxon.icon}}"}
/ %render-svg{path: "{{product.primary_taxon.icon}}"}
{{product.name}}
%p {{product.description}}
%ng-include{src: "'partials/close.html'"}

View File

@@ -1,16 +1,15 @@
.row.variants{bindonce: true,
"ng-repeat" => "variant in product.variants track by variant.id"}
.variants.row
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.inline {{ variant.name_to_display }}
.bulk-buy.inline{"bo-if" => "product.group_buy"}
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ Bulk
-# WITHOUT GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
%input{type: :number,
value: nil,
min: 0,
@@ -22,7 +21,7 @@
-# WITH GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
@@ -33,7 +32,7 @@
"ofn-disable-scroll" => true,
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
%span.bulk-input{"bo-if" => "product.group_buy"}
%span.bulk-input{"bo-if" => "variant.product.group_buy"}
%input.bulk.second{type: :number,
min: 0,
"ng-model" => "variant.line_item.max_quantity",
@@ -51,8 +50,11 @@
%i.ofn-i_009-close
{{ variant.price | currency }}
/ %button.graph-button{popover: "This is the popover text", "popover-title" => "The title.", "popover-animation" => "true", "popover-trigger" =>"mouseenter", "popover-placement" => "top", "tabindex" => "-1"}
/ %i.ofn-i-058-graph
-# Now in a template in app/assets/javascripts/templates !
%price-breakdown{"price-breakdown" => "_", variant: "variant",
"price-breakdown-append-to-body" => "true",
"price-breakdown-placement" => "left",
"price-breakdown-animation" => true}
.small-12.medium-2.large-2.columns.total-price.text-right
.table-cell

View File

@@ -1,37 +1,100 @@
@import mixins
.darkswarm
product
// Pop over
// .darkswarm
// product
// Foundation overrides
.joyride-tip-guide
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
background-color: #ebebeb
border: 1px solid #a5a5a5
color: #1f1f1f
h1, h2, h3, h4, h5, h6
color: #1f1f1f
.joyride-nub.bottom
border-color: #a5a5a5 !important
border-bottom-color: transparent !important
border-left-color: transparent !important
border-right-color: transparent !important
// Pop over
// Foundation overrides
.joyride-tip-guide
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
background-color: #999
color: #1f1f1f
@include box-shadow(0 1px 2px 0 rgba(0,0,0,0.7))
button.graph-button
padding: 0
.joyride-content-wrapper
padding: 1.125rem 1.25rem 1.5rem
padding: 1rem
margin: 1%
width: 98%
background-color: white
h1, h2, h3, h4, h5, h6
color: #1f1f1f
.joyride-nub.right
top: 40px
border-color: #999 !important
border-top-color: transparent !important
border-right-color: transparent !important
border-bottom-color: transparent !important
.progress
background-color: #13bf85
padding: 0
border: none
color: white
font-size: 0.75rem
font-style: oblique
line-height: 1
height: auto
.right
padding: 0.5rem 0.25rem 0 0
.meter
background-color: #0b8c61
padding: 0.5rem 0.25rem
border-right: 1px solid #539f92
.expanded
ul, li
list-style: none
margin: 0
@include border-radius(0)
display: inline
background: none
font-size: 0.875rem
li
background-color: #13bf85
padding: 0 0.25rem
margin-bottom: 2px
color: white
li.cost
background-color: #0b8c61
li:last-child
margin-bottom: 0.75rem
button.graph-button
padding: 0
margin: 0
@include border-radius(99999)
display: inline
background-color: rgba(255,255,255,0.5)
padding: 0.2rem
&:focus
background-color: rgba(255,255,255,0.5)
i.ofn-i-058-graph
color: #999
&:hover, &:active
background-color: rgba(255,255,255,1)
i.ofn-i-058-graph
color: $clr-brick-bright
i.ofn-i-058-graph
color: #999
margin: 0
padding: 0
font-size: 1rem
button.graph-button.open
background-color: #999
&:hover, &:active, &:focus
background-color: #b2b2b2
i.ofn-i-058-graph
color: $clr-brick-bright
i.ofn-i-058-graph
color: $clr-brick
i.ofn-i-058-graph
color: #999
margin: 0
padding: 0
font-size: 1rem
&:hover, &:focus, &:active, &.active
color: #444

View File

@@ -100,8 +100,10 @@
h3
font-size: 1.5rem
margin: 0
a h3
color: black
h3 a
color: #222
&:hover, &:focus, &:active
color: black

View File

@@ -13,7 +13,7 @@
float: left
display: block
z-index: 999999
background-color: #999
background-color: white
overflow: hidden
@media all and (max-width: 768px)

View File

@@ -11,9 +11,9 @@
@include box-shadow(0 1px 2px 1px rgba(0,0,0,0.25))
.hero-img
background-color: #333
border-bottom: 1px solid $disabled-bright
width: 100%
min-height: 160px
min-height: 56px
height: inherit
max-height: 260px
overflow: hidden

View File

@@ -1,6 +1,8 @@
// Place all the styles related to the map controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
@import big-input
.map-container
width: 100%
map, .angular-google-map-container, google-map, .angular-google-map
@@ -11,6 +13,14 @@
max-width: none
height: auto
#pac-input
padding: 4px
font-size: 2em
#pac-input
@include big-input(#888, #333, $clr-brick)
@include big-input-static
font-size: 1.5rem
background: rgba(255,255,255,0.85)
width: 50%
margin-top: 1.2rem
@media all and (max-width: 768px)
width: 80%
&:active, &:focus, &.active
background: rgba(255,255,255, 1)

View File

@@ -5,45 +5,56 @@ dialog, .reveal-modal
border: none
outline: none
padding: 1rem
div
overflow: scroll
@media only screen and (min-width: 40.063em)
max-height: 580px
@media all and (max-width: 768px)
max-height: 440px
@media all and (max-width: 640px)
max-height: 400px
@media all and (max-width: 640px)
max-height: inherit
overflow: scroll
// TO DO: look at bigger issue scrolling on mobile device
overflow-y: scroll
@media only screen and (min-width: 40.063em)
max-height: 580px
@media all and (max-width: 768px)
max-height: 440px
@media all and (max-width: 640px)
max-height: 400px
@media all and (max-width: 640px)
max-height: inherit
overflow-y: scroll
.reveal-modal-bg
background-color: rgba(0,0,0,0.65)
dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
top: -2.5rem
right: -2.5rem
font-size: 2rem
color: white
dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
top: 0.45rem
right: 0.4rem
background-color: rgba(235,235,235,0.85)
text-shadow: none
padding: 0.25rem
padding: 0.3rem
@include border-radius(999999)
border: 1px solid transparent
&:hover, &:active, &:focus
text-shadow: 0 1px 3px #333
border: 1px solid white
background-color: rgba(235,235,235,1)
color: #333
@media all and (max-width: 640px)
top: 0.5rem
right: 0.5rem
font-size: 2rem
color: white
text-shadow: none
padding: 0.25rem
background-color: rgba(150,150,150,0.85)
@include border-radius(999999)
border: 1px solid transparent
&:hover, &:active, &:focus
text-shadow: 0 1px 3px #333
border: 1px solid white
// dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
// top: -2.5rem
// right: -2.5rem
// font-size: 2rem
// color: white
// text-shadow: none
// padding: 0.25rem
// @include border-radius(999999)
// border: 1px solid transparent
// &:hover, &:active, &:focus
// text-shadow: 0 1px 3px #333
// border: 1px solid white
// @media all and (max-width: 640px)
// top: 0.5rem
// right: 0.5rem
// font-size: 2rem
// color: white
// text-shadow: none
// padding: 0.25rem
// background-color: rgba(150,150,150,0.85)
// @include border-radius(999999)
// border: 1px solid transparent
// &:hover, &:active, &:focus
// text-shadow: 0 1px 3px #333
// border: 1px solid white

View File

@@ -13,13 +13,20 @@
right: 10px
top: 55px
width: 400px
@media screen and (max-width: 640px)
width: 96%
.joyride-nub
right: 22px !important
left: auto
ul, li
list-style: none
margin-left: 0
li
float: none
.row .columns
padding-left: 0.25rem
padding-right: 0.25rem

View File

@@ -46,6 +46,7 @@
.button, button
@include border-radius(0.5em)
outline: none // Turn off blue highlight on chrome
.button.primary, button.primary
font-family: 'Open Sans', Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif

View File

@@ -1,5 +1,3 @@
require 'open_food_network/enterprise_fee_applicator'
class OrderCycle < ActiveRecord::Base
belongs_to :coordinator, :class_name => 'Enterprise'
has_and_belongs_to_many :coordinator_fees, :class_name => 'EnterpriseFee', :join_table => 'coordinator_fees'
@@ -165,77 +163,17 @@ class OrderCycle < ActiveRecord::Base
exchange_for_distributor(distributor).andand.pickup_instructions
end
# -- Fees
# TODO: The boundary of this class is ill-defined here. OrderCycle should not know about
# EnterpriseFeeApplicator. Clients should be able to query it for relevant EnterpriseFees.
# This logic would fit better in another service object.
def fees_for(variant, distributor)
per_item_enterprise_fee_applicators_for(variant, distributor).sum do |applicator|
# Spree's Calculator interface accepts Orders or LineItems,
# so we meet that interface with a struct.
# Amount is faked, this is a method on LineItem
line_item = OpenStruct.new variant: variant, quantity: 1, amount: variant.price
applicator.enterprise_fee.compute_amount(line_item)
end
def exchanges_carrying(variant, distributor)
exchanges.supplying_to(distributor).with_variant(variant)
end
def create_line_item_adjustments_for(line_item)
variant = line_item.variant
distributor = line_item.order.distributor
per_item_enterprise_fee_applicators_for(variant, distributor).each do |applicator|
applicator.create_line_item_adjustment(line_item)
end
end
def create_order_adjustments_for(order)
per_order_enterprise_fee_applicators_for(order).each do |applicator|
applicator.create_order_adjustment(order)
end
def exchanges_supplying(order)
exchanges.supplying_to(order.distributor).with_any_variant(order.variants)
end
private
# -- Fees
def per_item_enterprise_fee_applicators_for(variant, distributor)
fees = []
exchanges_carrying(variant, distributor).each do |exchange|
exchange.enterprise_fees.per_item.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role)
end
end
coordinator_fees.per_item.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator')
end
fees
end
def per_order_enterprise_fee_applicators_for(order)
fees = []
exchanges_supplying(order).each do |exchange|
exchange.enterprise_fees.per_order.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role)
end
end
coordinator_fees.per_order.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator')
end
fees
end
# -- Misc
# If a product without variants is added to an order cycle, and then some variants are added
# to that product, then the master variant is still part of the order cycle, but customers
# should not be able to purchase it.
@@ -246,12 +184,4 @@ class OrderCycle < ActiveRecord::Base
distributed_variants.include?(product.master) &&
(product.variants & distributed_variants).empty?
end
def exchanges_carrying(variant, distributor)
exchanges.supplying_to(distributor).with_variant(variant)
end
def exchanges_supplying(order)
exchanges.supplying_to(order.distributor).with_any_variant(order.variants)
end
end

View File

@@ -1,5 +1,6 @@
require 'open_food_network/feature_toggle'
require 'open_food_network/enterprise_fee_calculator'
require 'open_food_network/distribution_change_validator'
require 'open_food_network/feature_toggle'
ActiveSupport::Notifications.subscribe('spree.order.contents_changed') do |name, start, finish, id, payload|
payload[:order].reload.update_distribution_charge!
@@ -133,7 +134,7 @@ Spree::Order.class_eval do
line_items.each do |line_item|
if provided_by_order_cycle? line_item
order_cycle.create_line_item_adjustments_for line_item
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_line_item_adjustments_for line_item
else
pd = product_distribution_for line_item
@@ -141,7 +142,9 @@ Spree::Order.class_eval do
end
end
order_cycle.create_order_adjustments_for self if order_cycle
if order_cycle
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_order_adjustments_for self
end
end
def set_variant_attributes(variant, attributes)

View File

@@ -1,3 +1,4 @@
require 'open_food_network/enterprise_fee_calculator'
require 'open_food_network/option_value_namer'
Spree::Variant.class_eval do
@@ -44,7 +45,11 @@ Spree::Variant.class_eval do
end
def fees_for(distributor, order_cycle)
order_cycle.fees_for(self, distributor)
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_for self
end
def fees_by_type_for(distributor, order_cycle)
OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for self
end

View File

@@ -1,8 +1,16 @@
class Api::VariantSerializer < ActiveModel::Serializer
attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display,
:on_demand, :price
:on_demand, :price, :fees, :base_price
def price
object.price_with_fees(options[:current_distributor], options[:current_order_cycle])
end
def base_price
object.price
end
def fees
object.fees_by_type_for(options[:current_distributor], options[:current_order_cycle])
end
end

View File

@@ -1,4 +1,4 @@
%span.cart-span{"ng-controller" => "CartCtrl"}
%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty }"}
%a#cart.icon{cart: true}
%span.nav-branded
%i.ofn-i_027-shopping-cart

View File

@@ -20,12 +20,8 @@
"ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | orderBy:ordering.order) track by product.id "}
= render partial: "shop/products/summary"
%span{"bo-if" => "product.hasVariants"}
= render partial: "shop/products/variants"
.variants.row{"bo-if" => "!product.hasVariants"}
= render partial: "shop/products/master"
%shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants"}
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id"}
%product{"ng-show" => "Products.loading"}
.row.summary

View File

@@ -1,58 +0,0 @@
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.inline {{ product.master.name_to_display }}
.bulk-buy.inline{"bo-if" => "product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ Bulk
-# WITHOUT GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!product.group_buy"}
%input{type: :number,
min: 0,
placeholder: "0",
"ofn-disable-scroll" => true,
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
name: "variants[{{product.master.id}}]",
"ng-model" => "product.master.line_item.quantity",
id: "variants_{{product.master.id}}"}
-# WITH GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
min: 0,
"ng-model" => "product.master.line_item.quantity",
placeholder: "min",
"ofn-disable-scroll" => true,
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
name: "variants[{{product.master.id}}]",
id: "variants_{{product.master.id}}"}
%span.bulk-input{"bo-if" => "product.group_buy"}
%input.bulk.second{type: :number,
min: 0,
"ng-model" => "product.master.line_item.max_quantity",
placeholder: "max",
"ofn-disable-scroll" => true,
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
name: "variant_attributes[{{product.master.id}}][max_quantity]"}
.small-3.medium-1.large-1.columns.variant-unit
.table-cell
%em {{ product.master.unit_to_display }}
.small-4.medium-2.large-2.columns.variant-price
.table-cell
%i.ofn-i_009-close
{{ product.master.price | currency }}
-#%button.graph-button{"price-breakdown" => "_",
-#"variant" => "product.master",
-#"price-breakdown-animation" => "true"}
-#%i.ofn-i-058-graph
.small-12.medium-2.large-2.columns.total-price.text-right
.table-cell
%strong
{{ product.master.getPrice() | currency }}

View File

@@ -4,8 +4,9 @@
.row.summary
.small-9.medium-10.large-11.columns.summary-header
%a{"ng-click" => "triggerProductModal()"}
%h3 {{ product.name }}
%h3
%a{"ng-click" => "triggerProductModal()"}
{{ product.name }}
%em from
%span

View File

@@ -8,7 +8,6 @@ module.exports = function(config) {
APPLICATION_SPEC,
'app/assets/javascripts/shared/jquery-1.8.0.js', // TODO: Can we link to Rails' jquery?
'app/assets/javascripts/shared/jquery.timeago.js',
'app/assets/javascripts/shared/mm-foundation-tpls-0.2.0-SNAPSHOT.js',
'app/assets/javascripts/shared/angular-local-storage.js',
'app/assets/javascripts/shared/bindonce.min.js',
'app/assets/javascripts/shared/ng-infinite-scroll.min.js',

View File

@@ -0,0 +1,88 @@
require 'open_food_network/enterprise_fee_applicator'
module OpenFoodNetwork
class EnterpriseFeeCalculator
def initialize(distributor=nil, order_cycle=nil)
@distributor = distributor
@order_cycle = order_cycle
end
def fees_for(variant)
per_item_enterprise_fee_applicators_for(variant).sum do |applicator|
calculate_fee_for variant, applicator
end
end
def fees_by_type_for(variant)
per_item_enterprise_fee_applicators_for(variant).inject({}) do |fees, applicator|
fees[applicator.enterprise_fee.fee_type.to_sym] ||= 0
fees[applicator.enterprise_fee.fee_type.to_sym] += calculate_fee_for variant, applicator
fees
end.select { |fee_type, amount| amount > 0 }
end
def create_line_item_adjustments_for(line_item)
variant = line_item.variant
@distributor = line_item.order.distributor
@order_cycle = line_item.order.order_cycle
per_item_enterprise_fee_applicators_for(variant).each do |applicator|
applicator.create_line_item_adjustment(line_item)
end
end
def create_order_adjustments_for(order)
@distributor = order.distributor
@order_cycle = order.order_cycle
per_order_enterprise_fee_applicators_for(order).each do |applicator|
applicator.create_order_adjustment(order)
end
end
private
def calculate_fee_for(variant, applicator)
# Spree's Calculator interface accepts Orders or LineItems,
# so we meet that interface with a struct.
# Amount is faked, this is a method on LineItem
line_item = OpenStruct.new variant: variant, quantity: 1, amount: variant.price
applicator.enterprise_fee.compute_amount(line_item)
end
def per_item_enterprise_fee_applicators_for(variant)
fees = []
@order_cycle.exchanges_carrying(variant, @distributor).each do |exchange|
exchange.enterprise_fees.per_item.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, exchange.role)
end
end
@order_cycle.coordinator_fees.per_item.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, variant, 'coordinator')
end
fees
end
def per_order_enterprise_fee_applicators_for(order)
fees = []
@order_cycle.exchanges_supplying(order).each do |exchange|
exchange.enterprise_fees.per_order.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, exchange.role)
end
end
@order_cycle.coordinator_fees.per_order.each do |enterprise_fee|
fees << OpenFoodNetwork::EnterpriseFeeApplicator.new(enterprise_fee, nil, 'coordinator')
end
fees
end
end
end

View File

@@ -139,7 +139,9 @@ feature "As a consumer I want to shop with a distributor", js: true do
it "should save group buy data to ze cart" do
fill_in "variants[#{product.master.id}]", with: 5
fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 9
sleep 5
wait_until { !cart_dirty }
li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
li.max_quantity.should == 9
li.quantity.should == 5

View File

@@ -8,6 +8,7 @@
//= require angular-backstretch.js
//= require lodash.underscore.js
//= require angular-flash.min.js
//= require shared/mm-foundation-tpls-0.2.2.min.js
//= require moment
angular.module('templates', [])

View File

@@ -3,6 +3,7 @@ describe 'ProductsCtrl', ->
scope = null
event = null
Products = null
Cart = {}
beforeEach ->
module('Darkswarm')
@@ -15,7 +16,7 @@ describe 'ProductsCtrl', ->
inject ($controller) ->
scope = {}
ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle}
ctrl = $controller 'ProductsCtrl', {$scope: scope, Products: Products, OrderCycle: OrderCycle, Cart: Cart}
it 'fetches products from Products', ->
expect(scope.Products.products).toEqual ['testy mctest']

View File

@@ -2,7 +2,7 @@ describe "filtering Groups", ->
filterGroups = null
groups = [{
name: "test"
long_description: "roger"
description: "roger"
enterprises: [{
name: "kittens"
}, {
@@ -10,7 +10,7 @@ describe "filtering Groups", ->
}]
}, {
name: "blankness"
long_description: "in the sky"
description: "in the sky"
enterprises: [{
name: "ponies"
}, {

View File

@@ -6,11 +6,8 @@ describe 'filtering urls', ->
inject ($filter) ->
filter = $filter('stripUrl')
it "removes http and www", ->
expect(filter("http://www.footle.com")).toEqual "footle.com"
it "removes http", ->
expect(filter("http://footle.com")).toEqual "footle.com"
it "removes https and www", ->
expect(filter("https://www.footle.com")).toEqual "footle.com"
it "removes just www", ->
expect(filter("www.footle.com")).toEqual "footle.com"
it "removes https", ->
expect(filter("https://www.footle.com")).toEqual "www.footle.com"

View File

@@ -55,6 +55,12 @@ describe 'Products service', ->
$httpBackend.flush()
expect(Cart.line_items[0].variant).toBe Products.products[0].variants[0]
it "sets primaryImageOrMissing when no images are provided", ->
$httpBackend.expectGET("/shop/products").respond([product])
$httpBackend.flush()
expect(Products.products[0].primaryImage).toBeUndefined()
expect(Products.products[0].primaryImageOrMissing).toEqual "/assets/noimage/small.png"
describe "determining the price to display for a product", ->
it "displays the product price when the product does not have variants", ->
$httpBackend.expectGET("/shop/products").respond([product])
@@ -66,4 +72,3 @@ describe 'Products service', ->
$httpBackend.expectGET("/shop/products").respond([product])
$httpBackend.flush()
expect(Products.products[0].price).toEqual 22

View File

@@ -5,6 +5,8 @@ describe 'Variants service', ->
beforeEach ->
variant =
id: 1
base_price: 80.5
price: 100
module 'Darkswarm'
inject ($injector)->
Variants = $injector.get("Variants")
@@ -19,3 +21,5 @@ describe 'Variants service', ->
it "will return the same object as passed", ->
expect(Variants.register(variant)).toBe variant
it "initialises base price percentage", ->
expect(Variants.register(variant).basePricePercentage).toEqual 81

View File

@@ -27,7 +27,6 @@ module OpenFoodNetwork
it "creates an adjustment for an order" do
order = create(:order)
#line_item = create(:line_item)
enterprise_fee = create(:enterprise_fee)
product = create(:simple_product)

View File

@@ -0,0 +1,159 @@
require 'open_food_network/enterprise_fee_calculator'
module OpenFoodNetwork
describe EnterpriseFeeCalculator do
describe "integration" do
let(:coordinator) { create(:distributor_enterprise) }
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:product) { create(:simple_product, price: 10.00) }
describe "calculating fees for a variant" do
it "sums all the per-item fees for the variant in the specified hub + order cycle" do
enterprise_fee1 = create(:enterprise_fee, amount: 20)
enterprise_fee2 = create(:enterprise_fee, amount: 3)
enterprise_fee3 = create(:enterprise_fee,
calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2))
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product.master])
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_for(product.master).should == 23
end
it "sums percentage fees for the variant" do
enterprise_fee1 = create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20))
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1], variants: [product.master])
product.master.price.should == 10.00
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_for(product.master).should == 2.00
end
end
describe "calculating fees by type" do
let!(:ef_admin) { create(:enterprise_fee, fee_type: 'admin', amount: 1.23) }
let!(:ef_sales) { create(:enterprise_fee, fee_type: 'sales', amount: 4.56) }
let!(:ef_packing) { create(:enterprise_fee, fee_type: 'packing', amount: 7.89) }
let!(:ef_transport) { create(:enterprise_fee, fee_type: 'transport', amount: 0.12) }
let!(:exchange) { create(:exchange, order_cycle: order_cycle,
sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [ef_admin, ef_sales, ef_packing, ef_transport],
variants: [product.master]) }
it "returns a breakdown of fees" do
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {admin: 1.23, sales: 4.56, packing: 7.89, transport: 0.12}
end
it "filters out zero fees" do
ef_admin.calculator.update_attribute :preferred_amount, 0
EnterpriseFeeCalculator.new(distributor, order_cycle).fees_by_type_for(product.master).should == {sales: 4.56, packing: 7.89, transport: 0.12}
end
end
describe "creating adjustments" do
let(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
let!(:line_item) { create(:line_item, order: order, variant: product.master) }
let(:enterprise_fee_line_item) { create(:enterprise_fee) }
let(:enterprise_fee_order) { create(:enterprise_fee, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2)) }
let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false, variants: [product.master]) }
before { order.reload }
it "creates adjustments for a line item" do
exchange.enterprise_fees << enterprise_fee_line_item
EnterpriseFeeCalculator.new.create_line_item_adjustments_for line_item
a = Spree::Adjustment.last
a.metadata.fee_name.should == enterprise_fee_line_item.name
end
it "creates adjustments for an order" do
exchange.enterprise_fees << enterprise_fee_order
EnterpriseFeeCalculator.new.create_order_adjustments_for order
a = Spree::Adjustment.last
a.metadata.fee_name.should == enterprise_fee_order.name
end
end
end
describe "creating adjustments for a line item" do
let(:oc) { OrderCycle.new }
let(:variant) { double(:variant) }
let(:distributor) { double(:distributor) }
let(:order) { double(:order, distributor: distributor, order_cycle: oc) }
let(:line_item) { double(:line_item, variant: variant, order: order) }
it "creates an adjustment for each fee" do
applicator = double(:enterprise_fee_applicator)
applicator.should_receive(:create_line_item_adjustment).with(line_item)
efc = EnterpriseFeeCalculator.new
efc.should_receive(:per_item_enterprise_fee_applicators_for).with(variant) { [applicator] }
efc.create_line_item_adjustments_for line_item
end
it "makes fee applicators for a line item" do
distributor = double(:distributor)
ef1 = double(:enterprise_fee)
ef2 = double(:enterprise_fee)
ef3 = double(:enterprise_fee)
incoming_exchange = double(:exchange, role: 'supplier')
outgoing_exchange = double(:exchange, role: 'distributor')
incoming_exchange.stub_chain(:enterprise_fees, :per_item) { [ef1] }
outgoing_exchange.stub_chain(:enterprise_fees, :per_item) { [ef2] }
oc.stub(:exchanges_carrying) { [incoming_exchange, outgoing_exchange] }
oc.stub_chain(:coordinator_fees, :per_item) { [ef3] }
efc = EnterpriseFeeCalculator.new(distributor, oc)
efc.send(:per_item_enterprise_fee_applicators_for, line_item.variant).should ==
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, line_item.variant, 'supplier'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, line_item.variant, 'distributor'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, line_item.variant, 'coordinator')]
end
end
describe "creating adjustments for an order" do
let(:oc) { OrderCycle.new }
let(:distributor) { double(:distributor) }
let(:order) { double(:order, distributor: distributor, order_cycle: oc) }
it "creates an adjustment for each fee" do
applicator = double(:enterprise_fee_applicator)
applicator.should_receive(:create_order_adjustment).with(order)
efc = EnterpriseFeeCalculator.new
efc.should_receive(:per_order_enterprise_fee_applicators_for).with(order) { [applicator] }
efc.create_order_adjustments_for order
end
it "makes fee applicators for an order" do
distributor = double(:distributor)
ef1 = double(:enterprise_fee)
ef2 = double(:enterprise_fee)
ef3 = double(:enterprise_fee)
incoming_exchange = double(:exchange, role: 'supplier')
outgoing_exchange = double(:exchange, role: 'distributor')
incoming_exchange.stub_chain(:enterprise_fees, :per_order) { [ef1] }
outgoing_exchange.stub_chain(:enterprise_fees, :per_order) { [ef2] }
oc.stub(:exchanges_supplying) { [incoming_exchange, outgoing_exchange] }
oc.stub_chain(:coordinator_fees, :per_order) { [ef3] }
efc = EnterpriseFeeCalculator.new(distributor, oc)
efc.send(:per_order_enterprise_fee_applicators_for, order).should ==
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, nil, 'supplier'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, nil, 'distributor'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, nil, 'coordinator')]
end
end
end
end

View File

@@ -368,107 +368,6 @@ describe OrderCycle do
end
end
describe "calculating fees for a variant via a particular distributor" do
it "sums all the per-item fees for the variant in the specified hub + order cycle" do
coordinator = create(:distributor_enterprise)
distributor = create(:distributor_enterprise)
order_cycle = create(:simple_order_cycle)
enterprise_fee1 = create(:enterprise_fee, amount: 20)
enterprise_fee2 = create(:enterprise_fee, amount: 3)
enterprise_fee3 = create(:enterprise_fee,
calculator: Spree::Calculator::FlatRate.new(preferred_amount: 2))
product = create(:simple_product)
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1, enterprise_fee2, enterprise_fee3], variants: [product.master])
order_cycle.fees_for(product.master, distributor).should == 23
end
it "sums percentage fees for the variant" do
coordinator = create(:distributor_enterprise)
distributor = create(:distributor_enterprise)
order_cycle = create(:simple_order_cycle)
enterprise_fee1 = create(:enterprise_fee, amount: 20, fee_type: "admin", calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20))
product = create(:simple_product, price: 10.00)
create(:exchange, order_cycle: order_cycle, sender: coordinator, receiver: distributor, incoming: false,
enterprise_fees: [enterprise_fee1], variants: [product.master])
product.master.price.should == 10.00
order_cycle.fees_for(product.master, distributor).should == 2.00
end
end
describe "creating adjustments for a line item" do
let(:oc) { OrderCycle.new }
let(:variant) { double(:variant) }
let(:distributor) { double(:distributor) }
let(:order) { double(:order, distributor: distributor) }
let(:line_item) { double(:line_item, variant: variant, order: order) }
it "creates an adjustment for each fee" do
applicator = double(:enterprise_fee_applicator)
applicator.should_receive(:create_line_item_adjustment).with(line_item)
oc.should_receive(:per_item_enterprise_fee_applicators_for).with(variant, distributor) { [applicator] }
oc.send(:create_line_item_adjustments_for, line_item)
end
it "makes fee applicators for a line item" do
distributor = double(:distributor)
ef1 = double(:enterprise_fee)
ef2 = double(:enterprise_fee)
ef3 = double(:enterprise_fee)
incoming_exchange = double(:exchange, role: 'supplier')
outgoing_exchange = double(:exchange, role: 'distributor')
incoming_exchange.stub_chain(:enterprise_fees, :per_item) { [ef1] }
outgoing_exchange.stub_chain(:enterprise_fees, :per_item) { [ef2] }
oc.stub(:exchanges_carrying) { [incoming_exchange, outgoing_exchange] }
oc.stub_chain(:coordinator_fees, :per_item) { [ef3] }
oc.send(:per_item_enterprise_fee_applicators_for, line_item.variant, distributor).should ==
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, line_item.variant, 'supplier'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, line_item.variant, 'distributor'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, line_item.variant, 'coordinator')]
end
end
describe "creating adjustments for an order" do
let(:oc) { OrderCycle.new }
let(:distributor) { double(:distributor) }
let(:order) { double(:order, distributor: distributor) }
it "creates an adjustment for each fee" do
applicator = double(:enterprise_fee_applicator)
applicator.should_receive(:create_order_adjustment).with(order)
oc.should_receive(:per_order_enterprise_fee_applicators_for).with(order) { [applicator] }
oc.send(:create_order_adjustments_for, order)
end
it "makes fee applicators for an order" do
distributor = double(:distributor)
ef1 = double(:enterprise_fee)
ef2 = double(:enterprise_fee)
ef3 = double(:enterprise_fee)
incoming_exchange = double(:exchange, role: 'supplier')
outgoing_exchange = double(:exchange, role: 'distributor')
incoming_exchange.stub_chain(:enterprise_fees, :per_order) { [ef1] }
outgoing_exchange.stub_chain(:enterprise_fees, :per_order) { [ef2] }
oc.stub(:exchanges_supplying) { [incoming_exchange, outgoing_exchange] }
oc.stub_chain(:coordinator_fees, :per_order) { [ef3] }
oc.send(:per_order_enterprise_fee_applicators_for, order).should ==
[OpenFoodNetwork::EnterpriseFeeApplicator.new(ef1, nil, 'supplier'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef2, nil, 'distributor'),
OpenFoodNetwork::EnterpriseFeeApplicator.new(ef3, nil, 'coordinator')]
end
end
describe "finding recently closed order cycles" do
it "should give the most recently closed order cycle for a distributor" do
distributor = create(:distributor_enterprise)

View File

@@ -82,8 +82,10 @@ describe Spree::Order do
subject.stub(:provided_by_order_cycle?) { true }
order_cycle = double(:order_cycle)
order_cycle.should_receive(:create_line_item_adjustments_for).with(line_item)
order_cycle.stub(:create_order_adjustments_for)
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.
should_receive(:create_line_item_adjustments_for).
with(line_item)
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.stub(:create_order_adjustments_for)
subject.stub(:order_cycle) { order_cycle }
subject.update_distribution_charge!
@@ -94,7 +96,10 @@ describe Spree::Order do
subject.stub(:line_items) { [] }
order_cycle = double(:order_cycle)
order_cycle.should_receive(:create_order_adjustments_for).with(subject)
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.
should_receive(:create_order_adjustments_for).
with(subject)
subject.stub(:order_cycle) { order_cycle }
subject.update_distribution_charge!

View File

@@ -82,17 +82,32 @@ module Spree
describe "calculating the fees" do
it "delegates to order cycle" do
it "delegates to EnterpriseFeeCalculator" do
distributor = double(:distributor)
order_cycle = double(:order_cycle)
variant = Variant.new
order_cycle.should_receive(:fees_for).with(variant, distributor) { 23 }
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.should_receive(:fees_for).with(variant) { 23 }
variant.fees_for(distributor, order_cycle).should == 23
end
end
describe "calculating fees broken down by fee type" do
it "delegates to EnterpriseFeeCalculator" do
distributor = double(:distributor)
order_cycle = double(:order_cycle)
variant = Variant.new
fees = double(:fees)
OpenFoodNetwork::EnterpriseFeeCalculator.any_instance.should_receive(:fees_by_type_for).with(variant) { fees }
variant.fees_by_type_for(distributor, order_cycle).should == fees
end
end
context "when the product has variants" do
let!(:product) { create(:simple_product) }
let!(:variant) { create(:variant, product: product) }

View File

@@ -68,6 +68,10 @@ module UIComponentHelper
find("#cart").click
end
def cart_dirty
page.find("span.cart-span")[:class].include? 'dirty'
end
def wait_for_ajax
counter = 0
while page.execute_script("return $.active").to_i > 0