mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge branch 'laura_and_will'
This commit is contained in:
@@ -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 = ->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "cart", ->
|
||||
# Toggles visibility of the "cart" popover
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr)->
|
||||
scope.open = false
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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}%"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
|
||||
# Builds selector for shipping types
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
templateUrl: 'shipping_type_selector.html'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.directive "shopVariant", ->
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
templateUrl: 'shop_variant.html'
|
||||
scope:
|
||||
variant: '='
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
Darkswarm.factory 'CurrentOrder', (currentOrder) ->
|
||||
new class CurrentOrder
|
||||
order: currentOrder
|
||||
empty: =>
|
||||
@order.line_items.length == 0
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
%a{"ng-click" => "expanded = !expanded"}
|
||||
Price graph
|
||||
%i.ofn-i_006-caret-up
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%button.graph-button{"ng-class" => "{open: tt_isOpen}"}
|
||||
%i.ofn-i-058-graph
|
||||
@@ -0,0 +1,5 @@
|
||||
.progress
|
||||
.right Fees
|
||||
.meter
|
||||
Cost
|
||||
|
||||
@@ -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'"}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -100,8 +100,10 @@
|
||||
h3
|
||||
font-size: 1.5rem
|
||||
margin: 0
|
||||
a h3
|
||||
color: black
|
||||
h3 a
|
||||
color: #222
|
||||
&:hover, &:focus, &:active
|
||||
color: black
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
88
lib/open_food_network/enterprise_fee_calculator.rb
Normal file
88
lib/open_food_network/enterprise_fee_calculator.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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', [])
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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"
|
||||
}, {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
159
spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
Normal file
159
spec/lib/open_food_network/enterprise_fee_calculator_spec.rb
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user