Merge branch 'master' into raf_bugfix

Conflicts:
	app/views/spree/order_mailer/confirm_email.text.haml
This commit is contained in:
Rafael Schouten
2014-10-02 19:06:30 +10:00
41 changed files with 467 additions and 213 deletions

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "HubsCtrl", ($scope, Hubs, Search, $document, $rootScope, HashNavigation, FilterSelectorsService) ->
Darkswarm.controller "HubsCtrl", ($scope, Hubs, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, MapModal) ->
$scope.Hubs = Hubs
$scope.hubs = Hubs.visible
$scope.totalActive = FilterSelectorsService.totalActive
@@ -6,6 +6,8 @@ Darkswarm.controller "HubsCtrl", ($scope, Hubs, Search, $document, $rootScope, H
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.query = Search.search()
$scope.show_profiles = false
$scope.openModal = MapModal.open
$scope.$watch "query", (query)->
Search.search query

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "ProducersCtrl", ($scope, Producers, $filter, FilterSelectorsService, Search) ->
Darkswarm.controller "ProducersCtrl", ($scope, Producers, $filter, FilterSelectorsService, Search, MapModal) ->
$scope.Producers = Producers
$scope.totalActive = FilterSelectorsService.totalActive
$scope.clearAll = FilterSelectorsService.clearAll
@@ -7,6 +7,8 @@ Darkswarm.controller "ProducersCtrl", ($scope, Producers, $filter, FilterSelecto
$scope.filtersActive = false
$scope.activeTaxons = []
$scope.query = Search.search()
$scope.show_profiles = false
$scope.openModal = MapModal.open
$scope.$watch "query", (query)->
Search.search query

View File

@@ -0,0 +1,13 @@
Darkswarm.directive "ofnRegistrationLimitModal", (Navigation, $modal, Loading) ->
restrict: 'A'
link: (scope, elem, attr)->
scope.modalInstance = $modal.open
templateUrl: 'registration/limit_reached.html'
windowClass: "login-modal large"
backdrop: 'static'
scope.modalInstance.result.then scope.close, scope.close
scope.close = ->
Loading.message = "Taking you back to the home page"
Navigation.go "/"

View File

@@ -0,0 +1,7 @@
Darkswarm.filter 'showProfiles', ()->
(enterprises, show_profiles) ->
enterprises ||= []
show_profiles ?= true
enterprises.filter (enterprise)=>
show_profiles or enterprise.has_shopfront

View File

@@ -2,7 +2,7 @@ Darkswarm.factory 'Hubs', ($filter, Enterprises, visibleFilter) ->
new class Hubs
constructor: ->
@hubs = @order Enterprises.enterprises.filter (hub)->
hub.is_distributor && hub.has_shopfront
hub.has_hub_listing
@visible = visibleFilter @hubs
order: (hubs)->

View File

@@ -1,12 +1,13 @@
.highlight
.highlight-top
%p.right
{{ [enterprise.address.city, enterprise.address.state_name] | printArray}}
%h3{"ng-if" => "enterprise.is_distributor"}
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true}
%i.ofn-i_040-hub
.highlight{"ng-class" => "{'has_shopfront' : enterprise.has_shopfront}"}
.highlight-top.row
.small-12.medium-7.large-8.columns
%h3{"ng-if" => "enterprise.has_shopfront"}
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true}
%i{"ng-class" => "enterprise.icon_font"}
%span {{ enterprise.name }}
%h3{"ng-if" => "!enterprise.has_shopfront", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "enterprise.icon_font"}
%span {{ enterprise.name }}
%h3{"ng-if" => "!enterprise.is_distributor"}
%i.ofn-i_036-producers
%span {{ enterprise.name }}
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
%p {{ [enterprise.address.city, enterprise.address.state_name] | printArray}}
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}

View File

@@ -28,8 +28,8 @@
.row
.small-12.columns
%label{ for: 'enterprise_long_desc' } Long Description:
%textarea.chunky.small-12.columns{ id: 'enterprise_long_desc', placeholder: "We recommend keeping your description to under 600 characters or 150 words. Why? Cus people are lazy, and don't like to read too much text online. ;)", ng: { model: 'enterprise.long_description' } }
%small {{ enterprise.long_description.length }} characters used
%textarea.chunky.small-12.columns{ id: 'enterprise_long_desc', rows: 6, placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words.", ng: { model: 'enterprise.long_description' } }
%small {{ enterprise.long_description.length }} characters / up to 600 recommended
.small-12.large-4.columns
.row
.small-12.columns

View File

@@ -0,0 +1,15 @@
%div
.header.center
%h2 Oh no!
%h4 You have reached the limit!
.row
.small-12.medium-3.large-2.columns.text-right.hide-for-small-only
%img{:src => "/assets/potatoes.png"}
.small-12.medium-9.large-10.columns
%p
You have reached the limit for the number of enterprises you are allowed to own on the
%strong Open Food Network.
.row
.small-12.columns
%hr
%input.button.primary{ type: "button", value: "Return to the homepage", ng: { click: "close()" } }

View File

@@ -17,7 +17,9 @@ $clr-blue-bright: #14b6cc
$disabled-light: #e5e5e5
$disabled-bright: #ccc
$disabled-med: #b3b3b3
$disabled-dark: #999
$disabled-v-dark: #808080
$med-grey: #666
$dark-grey: #333
$black: #000

View File

@@ -87,6 +87,8 @@
&.inactive
&.closed, &.open
&, & *
color: $disabled-med
a, a *
color: $disabled-dark
&.closed
.active_table_row, .active_table_row:first-child, .active_table_row:last-child
@@ -126,3 +128,13 @@
.active_table_row:first-child .skinny-head
background-color: $disabled-light
//Is Profile - profile node
&.inactive.is_profile
&.closed, &.open
.active_table_row
&:hover, &:active, &:focus
border-color: transparent
cursor: auto
@media all and (max-width: 640px)
border-color: transparent

View File

@@ -11,12 +11,16 @@
@include box-shadow(0 1px 2px 1px rgba(0,0,0,0.25))
.hero-img
border-bottom: 1px solid $disabled-bright
outline: 1px solid $disabled-bright
border-color: transparent
@include box-shadow(none)
width: 100%
min-height: 56px
min-height: 80px
height: inherit
max-height: 260px
overflow: hidden
@media all and (max-width: 640px)
min-height: 68px
.hero-img-small
background-color: #333

View File

@@ -16,6 +16,20 @@
-webkit-box-shadow: $box-shadow
box-shadow: $box-shadow
@mixin elipse-shadow($elipse-shadow)
content: ""
position: absolute
z-index: -1
-webkit-box-shadow: $elipse-shadow
box-shadow: $elipse-shadow
bottom: -12%
left: 10%
right: 10%
width: 80%
height: 10%
-moz-border-radius: 100%
border-radius: 100%
@mixin border-radius($border-radius)
-webkit-border-radius: $border-radius
border-radius: $border-radius

View File

@@ -25,23 +25,39 @@
position: relative
.highlight-top
padding: 0.75rem 0.9375rem
width: 100%
overflow: hidden
padding-top: 0.75rem
padding-bottom: 0.75rem
background-color: rgba(255,255,255,0.65)
position: absolute
bottom: 0
width: 100%
border: 0
outline: 0
@media only screen and (max-width: 640px)
padding-top: 0.5rem
padding-bottom: 0.35rem
h3, p
margin-top: 0
margin-bottom: 0
padding-bottom: 0
line-height: 1
h3 > i
color: $clr-brick
p
line-height: 2
line-height: 2.4
@media all and (max-width: 640px)
line-height: 1.4
h3 a:hover span
border-bottom: 1px solid $clr-brick-bright
.is_producer
&, & *
color: $clr-turquoise
// ABOUT Enterprise

View File

@@ -1,6 +1,13 @@
// Styling for login modal to style tabs
.reveal-modal.login-modal
border-bottom-color: #efefef
.login-modal
background: #efefef
.tabs-content
background: #fff
background: #fff
padding-top: 10px

View File

@@ -4,8 +4,14 @@
dialog, .reveal-modal
border: none
outline: none
padding: 1rem
padding: 30px 20px 0 20px
border-bottom: 30px solid white
overflow-y: scroll
overflow-x: hidden
// Not working yet - want a nice gradient shadow when there is overflow - needs JS too
// &:after
// @include elipse-shadow(0 0 40px rgba(0, 0, 0, 0.8))
// Reveal.js break point:
// @media only screen and (max-width: 40.063em)
@@ -25,14 +31,18 @@ dialog, .reveal-modal
max-height: 80%
.reveal-modal-bg
background-color: rgba(0,0,0,0.65)
background-color: rgba(0,0,0,0.85)
dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
right: 0.4rem
background-color: rgba(235,235,235,0.85)
right: 0.25rem
top: 0.25rem
background-color: rgba(205,205,205,0.65)
text-shadow: none
padding: 0.3rem
font-size: 2rem
padding: 0.45rem
color: #666
z-index: 9999999
@include border-radius(999999rem)
&:hover, &:active, &:focus
background-color: rgba(235,235,235,1)
background-color: rgba(205,205,205,1)
color: #333

View File

@@ -32,6 +32,11 @@
span
text-decoration: underline
&.has_shopfront, &.has_shopfront i.ofn-i_059-producer, &.has_shopfront i.ofn-i_060-producer-reversed
color: $clr-brick
&:hover, &:active, &:focus
color: $clr-brick-bright
a.cta-hub
&:hover, &:focus, &:active
&.secondary
@@ -51,9 +56,11 @@
.fat-taxons
background-color: $clr-turquoise-light
.producer-name
color: $clr-turquoise
//Open row
&.open
.active_table_row
border-left: 1px solid $clr-turquoise-bright
border-right: 1px solid $clr-turquoise-bright

View File

@@ -20,6 +20,8 @@ class RegistrationController < BaseController
def check_user
if spree_current_user.nil?
redirect_to registration_auth_path(anchor: "signup?after_login=#{request.env['PATH_INFO']}")
elsif !spree_current_user.can_own_more_enterprises?
render :limit_reached
end
end
end

View File

@@ -51,9 +51,9 @@ class Enterprise < ActiveRecord::Base
validates :address, presence: true, associated: true
validates :email, presence: true
validates_presence_of :owner
validate :enforce_ownership_limit, if: lambda { owner_id_changed? }
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? }
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
before_validation :set_unused_address_fields
after_validation :geocode_address
@@ -211,6 +211,39 @@ class Enterprise < ActiveRecord::Base
Spree::Variant.joins(:product => :product_distributions).where('product_distributions.distributor_id=?', self.id)
end
# Replaces currententerprse type field.
def sells
# Type: full - single - profile becomes Sells: all - own - none
# Remove this return later.
return "none" if !is_distributor || type == "profile"
return "own" if type == "single" || suppliers == [self]
"all"
end
# Simplify enterprise categories for frontend logic and icons, and maybe other things.
def enterprise_category
# Make this crazy logic human readable so we can argue about it sanely.
# This can be simplified later, it's like this for readablitlty during changes.
category = is_primary_producer ? "producer_" : "non_producer_"
category << "sell_" + sells
# Map backend cases to front end cases.
case category
when "producer_sell_all"
"producer_hub" # Producer hub who sells own and others produce and supplies other hubs.
when "producer_sell_own"
"producer_shop" # Producer with shopfront and supplies other hubs.
when "producer_sell_none"
"producer" # Producer only supplies through others.
when "non_producer_sell_all"
"hub" # Hub selling others products in order cycles.
when "non_producer_sell_own"
"hub" # Wholesaler selling through own shopfront?
when "non_producer_sell_none"
"hub_profile" # Hub selling outside the system.
end
end
# Return all taxons for all distributed products
def distributed_taxons
Spree::Taxon.
@@ -227,7 +260,6 @@ class Enterprise < ActiveRecord::Base
select('DISTINCT spree_taxons.*')
end
private
def send_creation_email

View File

@@ -5,6 +5,7 @@ class AbilityDecorator
add_base_abilities user if is_new_user? user
add_enterprise_management_abilities user if can_manage_enterprises? user
add_product_management_abilities user if can_manage_products? user
add_order_management_abilities user if can_manage_orders? user
add_relationship_management_abilities user if can_manage_relationships? user
end
@@ -17,11 +18,13 @@ class AbilityDecorator
user.enterprises.present?
end
def can_manage_products?(user)
( user.enterprises.map(&:type) & %w(single full) ).any?
can_manage_enterprises? user
end
def can_manage_orders?(user)
( user.enterprises.map(&:type) & %w(single full) ).any?
end
def can_manage_relationships?(user)
can_manage_enterprises? user
@@ -46,7 +49,6 @@ class AbilityDecorator
end
end
def add_product_management_abilities(user)
# Enterprise User can only access products that they are a supplier for
can [:create], Spree::Product
@@ -64,7 +66,9 @@ class AbilityDecorator
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
end
def add_order_management_abilities(user)
# Enterprise User can only access orders that they are a distributor for
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend], Spree::Order do |order|

View File

@@ -4,7 +4,7 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer
end
private
def cached_serializer_hash
Api::CachedEnterpriseSerializer.new(object, @options).serializable_hash
end
@@ -18,7 +18,7 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
attributes :orders_close_at, :active
#TODO: Remove these later
attributes :icon, :has_shopfront, :can_aggregate
attributes :icon, :icon_font, :producer_icon_font, :has_shopfront, :has_hub_listing, :enterprise_category
def orders_close_at
OrderCycle.first_closing_for(object).andand.orders_close_at
@@ -28,41 +28,61 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer
@options[:active_distributors].andand.include? object
end
# TODO: Move this back to uncached section when relavant properties are defined on the Enterprise model
def icon
# TODO: Replace with object.has_shopfront when this property exists
if has_shopfront
if can_aggregate
"/assets/map_005-hub.svg"
else
if object.is_distributor
"/assets/map_003-producer-shop.svg"
else
"/assets/map_001-producer-only.svg"
end
end
else
if can_aggregate
"/assets/map_006-hub-profile.svg"
else
if object.is_distributor
"/assets/map_004-producer-shop-profile.svg"
else
"/assets/map_002-producer-only-profile.svg"
end
end
end
def enterprise_category
object.enterprise_category
end
# TODO: Remove this when flags on enterprises are switched over
def has_shopfront
object.type != 'profile'
object.is_distributor && object.type != 'profile'
end
# TODO: Remove this when flags on enterprises are switched over
def can_aggregate
object.is_distributor && object.suppliers != [object]
# Used to select enterprises for hub listing
def has_hub_listing
has_shopfront || object.enterprise_category == "hub_profile"
end
# Map svg icons.
def icon
icons = {
"hub" => "/assets/map_005-hub.svg",
"hub_profile" => "/assets/map_006-hub-profile.svg",
"producer_hub" => "/assets/map_005-hub.svg",
"producer_shop" => "/assets/map_003-producer-shop.svg",
"producer" => "/assets/map_001-producer-only.svg",
"producer_profile" => "/assets/map_002-producer-only-profile.svg",
}
icons[object.enterprise_category]
end
# Choose regular icon font for enterprises.
def icon_font
icon_fonts = {
"hub" => "ofn-i_063-hub",
"hub_profile" => "ofn-i_064-hub-reversed",
"producer_hub" => "ofn-i_063-hub",
"producer_shop" => "ofn-i_059-producer",
"producer" => "ofn-i_059-producer",
"producer_profile" => "ofn-i_060-producer-reversed",
}
icon_fonts[object.enterprise_category]
end
# Choose producer page icon font - yes, sadly its got to be different.
# This duplicates some code but covers the producer page edge case where
# producer-hub has a producer icon without needing to duplicate the category logic in angular.
def producer_icon_font
icon_fonts = {
"hub" => "",
"hub_profile" => "",
"producer_hub" => "ofn-i_059-producer",
"producer_shop" => "ofn-i_059-producer",
"producer" => "ofn-i_059-producer",
"producer_profile" => "ofn-i_060-producer-reversed",
"empty" => "",
}
icon_fonts[object.enterprise_category]
end
end
class Api::CachedEnterpriseSerializer < ActiveModel::Serializer

View File

@@ -1,23 +1,22 @@
= render partial: 'shared/components/filter_controls'
.row
= render partial: 'shared/components/filter_controls'
= render partial: 'shared/components/show_profiles'
.row.animate-show{"ng-show" => "filtersActive"}
.small-12.columns
.small-12.columns
.row.filter-box
.small-12.large-9.columns
%h5.tdhead
%h5.tdhead
.light Filter by
Type
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5
%taxon-selector{objects: "hubs | hubs:query",
%taxon-selector{objects: "hubs | hubs:query",
results: "activeTaxons"}
.small-12.large-3.columns
%h5.tdhead
%h5.tdhead
.light Filter by
Delivery
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2
%shipping-type-selector{results: "shippingTypes"}
.row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
.small-12.columns
%a.button.secondary.small.expand{"ng-click" => "clearAll()"}
%i.ofn-i_009-close
Clear all filters
%shipping-type-selector{results: "shippingTypes"}
= render partial: 'shared/components/filter_box'

View File

@@ -1,19 +1,13 @@
= inject_enterprises
= inject_enterprises
#hubs.hubs{"ng-controller" => "HubsCtrl"}
.row
.small-12.columns
%h1 Shop your local area
/ %div
/ Shop a
/ %ofn-modal{title: "food hub"}
/ = render partial: "modals/food_hub"
/ from the list below:
%h1 Shop in your local area
#active-table-search.row.pad-top
.small-12.columns
/ %i.ofn-i_020-search
%input{type: :text,
"ng-model" => "query",
%input{type: :text,
"ng-model" => "query",
placeholder: "Search by name or suburb...",
"ng-debounce" => "150",
"ofn-disable-enter" => true}
@@ -23,11 +17,11 @@
.row{bindonce: true}
.small-12.columns
.active_table
%hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredHubs = (hubs | hubs:query | taxons:activeTaxons | shipping:shippingTypes)",
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
%hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredHubs = (hubs | hubs:query | taxons:activeTaxons | shipping:shippingTypes | showProfiles:show_profiles )",
"ng-class" => "{'is_profile' : !hub.has_shopfront, 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
"scroll-after-load" => true,
"ng-controller" => "HubNodeCtrl",
id: "{{hub.hash}}"}
id: "{{hub.hash}}"}
.small-12.columns
= render partial: 'home/skinny'
= render partial: 'home/fat'

View File

@@ -1,12 +1,10 @@
.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}", bindonce: true}
.row.active_table_row{"ng-if" => "hub.has_shopfront", "ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'has_shopfront' : producer.has_shopfront}", bindonce: true}
.columns.small-12.medium-6.large-5.skinny-head
%a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
%i{ ng: { class: "{ 'ofn-i_063-hub': hub.can_aggregate && hub.has_shopfront,
'ofn-i_059-producer': !hub.can_aggregate && hub.has_shopfront,
'ofn-i_060-producer-reversed': !hub.can_aggregate && !hub.has_shopfront,
'ofn-i_064-hub-reversed': hub.can_aggregate && !hub.has_shopfront }" } }
/ %i.ofn-i_063-hub
%i{ng: {class: "hub.icon_font"}}
%span.margin-top.hub-name-listing {{ hub.name | truncate:40}}
.columns.small-4.medium-2.large-2
%span.margin-top {{ hub.address.city }}
.columns.small-2.medium-1.large-1
@@ -15,16 +13,28 @@
.columns.small-6.medium-3.large-4.text-right{"bo-if" => "hub.active"}
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
%i.ofn-i_033-open-sign
%span.margin-top{ bo: { if: "current()" } }
%span.margin-top{ bo: { if: "current()" } }
%em Shopping here
%span.margin-top{ bo: { if: "!current()" } } {{ hub.orders_close_at | sensible_timeframe }}
.columns.small-6.medium-3.large-4.text-right{"bo-if" => "!hub.active"}
%a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
%i.ofn-i_032-closed-sign
%span.margin-top{ bo: { if: "current()" } }
%span.margin-top{ bo: { if: "current()" } }
%em Shopping here
%span.margin-top{ bo: { if: "!current()" } } Orders closed
.row.active_table_row{"ng-if" => "!hub.has_shopfront", "ng-class" => "closed"}
.columns.small-12.medium-6.large-5.skinny-head
%a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub"}
%i{ng: {class: "hub.icon_font"}}
%span.margin-top.hub-name-listing {{ hub.name | truncate:40}}
.columns.small-4.medium-2.large-2
%span.margin-top {{ hub.address.city }}
.columns.small-2.medium-1.large-1
%span.margin-top {{ hub.address.state_name | uppercase }}
.columns.small-6.medium-3.large-4.text-right
%span.margin-top{ bo: { if: "!current()" } }
%em Profile only

View File

@@ -1,18 +1,15 @@
= render partial: 'shared/components/filter_controls'
.row
= render partial: 'shared/components/filter_controls'
.small-12.medium-6.columns.text-right
&nbsp;
.row.animate-show{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.columns
%h5.tdhead
%h5.tdhead
.light Filter by
Type
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6
%taxon-selector{objects: "Producers.visible | filterProducers:query",
%taxon-selector{objects: "Producers.visible | filterProducers:query ",
results: "activeTaxons"}
.row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
.small-12.columns
%a.button.secondary.small.expand{"ng-click" => "clearAll()"}
%i.ofn-i_009-close
Clear all filters

View File

@@ -1,9 +1,16 @@
.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open()}"}
.row.active_table_row{"ng-click" => "toggle()", "ng-class" => "{'closed' : !open(), 'has_shopfront' : producer.has_shopfront}"}
.columns.small-12.medium-4.large-4.skinny-head
/ This needs logic to show profile only icon when available %i.ofn-i_060-producer-reversed
%i.ofn-i_059-producer
%span.margin-top
%strong {{ producer.name }}
%span{"bo-if" => "producer.has_shopfront" }
%a.has_shopfront{"bo-href" => "producer.path" }
%i{ng: {class: "producer.producer_icon_font"}}
%span.margin-top
%strong {{ producer.name }}
%span.producer-name{"bo-if" => "!producer.has_shopfront" }
%i{ng: {class: "producer.producer_icon_font"}}
%span.margin-top
%strong {{ producer.name }}
.columns.small-6.medium-3.large-3
%span.margin-top {{ producer.address.city }}
.columns.small-4.medium-3.large-4

View File

@@ -1,18 +1,18 @@
= inject_enterprises
= inject_enterprises
.producers.pad-top{"ng-controller" => "ProducersCtrl"}
.row
.small-12.columns.pad-top
%h1 Find local producers
/ %div
/ Find a
/ %div
/ Find a
/ %ofn-modal{title: "producer"}
/ = render partial: "modals/producers"
/ from the list below:
#active-table-search.row
.small-12.columns
%input.animate-show{type: :text,
"ng-model" => "query",
%input.animate-show{type: :text,
"ng-model" => "query",
placeholder: "Search by producer or suburb...",
"ng-debounce" => "150",
"ofn-disable-enter" => true}

View File

@@ -0,0 +1,2 @@
/ Directive which loads the modal
%div{ "ofn-registration-limit-modal" => true }

View File

@@ -0,0 +1,5 @@
.row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
.small-12.columns
%a.button.secondary.small.expand{"ng-click" => "clearAll()"}
%i.ofn-i_009-close
Clear all filters

View File

@@ -1,19 +1,9 @@
.row
.small-12.medium-6.columns
%a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive",
"ng-show" => "FilterSelectorsService.selectors.length > 0"}
{{ filterText(filtersActive) }}
%i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"}
%i.ofn-i_006-caret-up{"ng-show" => "filtersActive"}
.small-12.medium-6.columns
%a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive",
"ng-show" => "FilterSelectorsService.selectors.length > 0"}
{{ filterText(filtersActive) }}
%i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"}
%i.ofn-i_006-caret-up{"ng-show" => "filtersActive"}
%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"}
No filters
.small-12.medium-6.columns.text-right
.profile-checkbox
/ Hide until we're ready to work on this:
/ %input{type: "checkbox", name: "profile"}><
/ %label Show profiles
/ %button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but they may have their own physical or online shop elsewhere", "popover-placement" => "left"}><
/ %i.ofn-i_013-help
%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"}
No filters

View File

@@ -0,0 +1,6 @@
.small-12.medium-6.columns.text-right
.profile-checkbox
%input{"ng-model" => "show_profiles", type: "checkbox", name: "profile"}><
%label Show profiles
%button.button.secondary.tiny.help-btn.ng-scope{:popover => "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere", "popover-placement" => "left"}><
%i.ofn-i_013-help

View File

@@ -1,18 +1,17 @@
= render partial: 'shared/components/filter_controls'
.row
= render partial: 'shared/components/filter_controls'
.small-12.medium-6.columns.text-right
&nbsp;
.row.animate-show{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.columns
%h5.tdhead
%h5.tdhead
.light Filter by
Type
%ul.small-block-grid-2.medium-block-grid-3.large-block-grid-4
%taxon-selector{objects: "Products.products | products:query",
%taxon-selector{objects: "Products.products | products:query | products:showProfiles",
results: "activeTaxons"}
.row.filter-box.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
.small-12.columns
%a.button.secondary.small.expand{"ng-click" => "clearAll()"}
%i.ofn-i_009-close
Clear all filters
= render partial: 'shared/components/filter_box'

View File

@@ -13,7 +13,7 @@ Subtotal: #{number_to_currency checkout_cart_total_with_adjustments(@order)}
- checkout_adjustments_for_summary(@order, exclude: [:distribution]).each do |adjustment|
#{raw(adjustment.label)} #{adjustment.display_amount}
Order Total: #{@order.display_total}
- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check"
- if @order.payments.first.andand.payment_method.andand.type == "Spree::PaymentMethod::Check" and @order.payments.first.andand.payment_method.andand.description
\
============================================================
Payment Details
@@ -28,6 +28,9 @@ Order Total: #{@order.display_total}
Your order will be delivered to:
#{@order.ship_address.to_s}
- if @order.shipping_method.andand.description
#{@order.shipping_method.description.html_safe}
- if @order.order_cycle.andand.pickup_time_for(@order.distributor)
Delivery on: #{@order.order_cycle.pickup_time_for(@order.distributor)}

View File

@@ -1,6 +1,7 @@
require 'spec_helper'
describe RegistrationController do
include AuthenticationWorkflow
describe "redirecting when user not logged in" do
it "index" do
get :index
@@ -13,26 +14,38 @@ describe RegistrationController do
end
end
describe "loading data when user is logged in" do
let!(:user) { double(:user) }
describe "redirecting when user has reached enterprise ownership limit" do
let!(:user) { create_enterprise_user( enterprise_limit: 1 ) }
let!(:enterprise) { create(:distributor_enterprise, owner: user) }
before do
controller.stub spree_current_user: user
end
it "index" do
get :index
response.should render_template :limit_reached
end
end
describe "loading data when user is logged in" do
let!(:user) { create_enterprise_user }
before do
controller.stub spree_current_user: user
user.stub spree_api_key: '12345'
user.stub last_incomplete_spree_order: nil
end
describe "index" do
it "loads the spree api key" do
get :index
expect(assigns(:spree_api_key)).to eq '12345'
expect(assigns(:spree_api_key)).to eq user.spree_api_key
end
end
describe "store" do
it "loads the spree api key" do
get :store
expect(assigns(:spree_api_key)).to eq '12345'
expect(assigns(:spree_api_key)).to eq user.spree_api_key
end
end
end

View File

@@ -89,7 +89,7 @@ feature %q{
let!(:d1) { create(:distributor_enterprise) }
let!(:d2) { create(:distributor_enterprise) }
let!(:d3) { create(:distributor_enterprise) }
let(:enterprise_user) { create_enterprise_user([d1]) }
let(:enterprise_user) { create_enterprise_user( enterprises: [d1] ) }
let!(:er1) { create(:enterprise_relationship, parent: d1, child: d2) }
let!(:er2) { create(:enterprise_relationship, parent: d2, child: d1) }

View File

@@ -64,7 +64,7 @@ feature %q{
page.should have_admin_menu_item 'Dashboard'
page.should have_admin_menu_item 'Enterprises'
['Orders', 'Products', 'Reports', 'Configuration', 'Promotions', 'Users', 'Order Cycles'].each do |menu_item_name|
['Orders', 'Reports', 'Configuration', 'Promotions', 'Users', 'Order Cycles'].each do |menu_item_name|
page.should_not have_admin_menu_item menu_item_name
end
end
@@ -79,15 +79,15 @@ feature %q{
end
end
it "does not show me product management controls" do
page.should_not have_selector '#products'
it "shows me product management controls, but not order_cycle controls" do
page.should have_selector '#products'
page.should_not have_selector '#order_cycles'
end
it "does not show me enterprise product info, payment methods, shipping methods or enterprise fees" do
it "shows me enterprise product info but not payment methods, shipping methods or enterprise fees" do
# Producer product info
page.should_not have_selector '.producers_tab span', text: 'Total Products'
page.should_not have_selector '.producers_tab span', text: 'Active Products'
page.should have_selector '.producers_tab span', text: 'Total Products'
page.should have_selector '.producers_tab span', text: 'Active Products'
page.should_not have_selector '.producers_tab span', text: 'Products in OCs'
# Payment methods, shipping methods, enterprise fees

View File

@@ -10,8 +10,8 @@ feature %q{
context "Permissions for different reports" do
context "As an enterprise user" do
let(:user) do
create_enterprise_user([
create(:distributor_enterprise)
create_enterprise_user(enterprises: [
create(:distributor_enterprise)
])
end
it "should not show the Sales Total report" do
@@ -99,7 +99,7 @@ feature %q{
let(:shipping_instructions) { "pick up on thursday please!" }
let(:order1) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) }
let(:order2) { create(:order, :distributor => distributor, :bill_address => bill_address, :special_instructions => shipping_instructions) }
before do
Timecop.travel(Time.zone.local(2013, 4, 25, 14, 0, 0)) { order1.finalize! }
Timecop.travel(Time.zone.local(2013, 4, 25, 16, 0, 0)) { order2.finalize! }
@@ -144,7 +144,7 @@ feature %q{
variant_2.update_column(:count_on_hand, 20)
product_2.master.update_column(:count_on_hand, 9)
variant_1.option_values = [create(:option_value, :presentation => "Test")]
login_to_admin_section
click_link 'Reports'
@@ -165,4 +165,3 @@ feature %q{
end
end
end

View File

@@ -5,6 +5,7 @@
//= require angular-mocks
//= require angular-cookies
//= require angular-backstretch.js
//= require angularjs-file-upload
//= require lodash.underscore.js
//= require angular-flash.min.js
//= require shared/mm-foundation-tpls-0.2.2.min.js

View File

@@ -1,46 +1,45 @@
describe "Hubs service", ->
Hubs = null
Enterprises = null
CurrentHubMock = {}
CurrentHubMock = {}
hubs = [
{
id: 2
active: false
orders_close_at: new Date()
is_distributor: true
has_shopfront: true
has_hub_listing: true
}
{
id: 3
active: false
orders_close_at: new Date()
is_distributor: true
has_shopfront: true
has_hub_listing: true
}
{
id: 1
active: true
orders_close_at: new Date()
is_distributor: true
has_shopfront: true
has_hub_listing: true
}
]
beforeEach ->
module 'Darkswarm'
angular.module('Darkswarm').value('enterprises', hubs)
angular.module('Darkswarm').value('enterprises', hubs)
module ($provide)->
$provide.value "CurrentHub", CurrentHubMock
$provide.value "CurrentHub", CurrentHubMock
null
inject ($injector)->
Enterprises = $injector.get("Enterprises")
Enterprises = $injector.get("Enterprises")
Hubs = $injector.get("Hubs")
it "filters Enterprise.hubs into a new array", ->
expect(Hubs.hubs[0]).toBe Enterprises.enterprises[2]
# Because the $filter is a new sorted array
# Because the $filter is a new sorted array
# We check to see the objects in both arrays are still the same
Enterprises.enterprises[2].active = false
Enterprises.enterprises[2].active = false
expect(Hubs.hubs[0].active).toBe false

View File

@@ -501,4 +501,65 @@ describe Enterprise do
supplier.producer_properties.first.property.presentation.should == 'Organic Certified'
end
end
pending "provide enterprise category" do
# Swap type values full > sell_all, single > sell_own profile > sell_none
# swap is_distributor for new can_supply flag.
let(:producer_sell_all_can_supply) {
create(:enterprise, is_primary_producer: true, type: "full", is_distributor: true)
}
let(:producer_sell_all_cant_supply) {
create(:enterprise, is_primary_producer: true, type: "full", is_distributor: false)
}
let(:producer_sell_own_can_supply) {
create(:enterprise, is_primary_producer: true, type: "single", is_distributor: true)
}
let(:producer_sell_own_cant_supply) {
create(:enterprise, is_primary_producer: true, type: "single", is_distributor: false)
}
let(:producer_sell_none_can_supply) {
create(:enterprise, is_primary_producer: true, type: "profile", is_distributor: true)
}
let(:producer_sell_none_cant_supply) {
create(:enterprise, is_primary_producer: true, type: "profile", is_distributor: false)
}
let(:non_producer_sell_all_can_supply) {
create(:enterprise, is_primary_producer: true, type: "full", is_distributor: true)
}
let(:non_producer_sell_all_cant_supply) {
create(:enterprise, is_primary_producer: true, type: "full", is_distributor: false)
}
let(:non_producer_sell_own_can_supply) {
create(:enterprise, is_primary_producer: true, type: "single", is_distributor: true)
}
let(:non_producer_sell_own_cant_supply) {
create(:enterprise, is_primary_producer: true, type: "single", is_distributor: false)
}
let(:non_producer_sell_none_can_supply) {
create(:enterprise, is_primary_producer: false, type: "profile", is_distributor: true)
}
let(:non_producer_sell_none_cant_supply) {
create(:enterprise, is_primary_producer: false, type: "profile", is_distributor: false)
}
it "should output enterprise categories" do
producer_sell_all_can_supply.is_primary_producer.should == true
producer_sell_all_can_supply.supplies.should == true
producer_sell_all_can_supply.type.should == "full"
producer_sell_all_can_supply.enterprise_category.should == "producer_hub"
producer_sell_all_cant_supply.enterprise_category.should == "producer_hub"
producer_sell_own_can_supply.enterprise_category.should == "producer_shop"
producer_sell_own_cant_supply.enterprise_category.should == "producer_shop"
producer_sell_none_can_supply.enterprise_category.should == "producer"
producer_sell_none_cant_supply.enterprise_category.should == "producer_profile"
non_producer_sell_all_can_supply.enterprise_category.should == "hub"
non_producer_sell_all_cant_supply.enterprise_category.should == "hub"
non_producer_sell_own_can_supply.enterprise_category.should == "hub"
non_producer_sell_own_cant_supply.enterprise_category.should == "hub"
non_producer_sell_none_can_supply.enterprise_category.should == "hub_profile"
non_producer_sell_none_cant_supply.enterprise_category.should == "hub_profile"
end
end
end

View File

@@ -13,44 +13,46 @@ module Spree
let(:enterprise_single) { create(:enterprise, type: 'single') }
let(:enterprise_profile) { create(:enterprise, type: 'profile') }
describe "creating enterprises" do
context "as manager of a 'full' type enterprise" do
before do
user.enterprise_roles.create! enterprise: enterprise_full
end
it { subject.can_manage_products?(user).should be_true }
it { subject.can_manage_enterprises?(user).should be_true }
it { subject.can_manage_orders?(user).should be_true }
end
context "as manager of a 'single' type enterprise" do
before do
user.enterprise_roles.create! enterprise: enterprise_single
end
it { subject.can_manage_products?(user).should be_true }
it { subject.can_manage_enterprises?(user).should be_true }
it { subject.can_manage_orders?(user).should be_true }
end
context "as manager of a 'profile' type enterprise" do
before do
user.enterprise_roles.create! enterprise: enterprise_profile
end
it { subject.can_manage_products?(user).should be_true }
it { subject.can_manage_enterprises?(user).should be_true }
it { subject.can_manage_orders?(user).should be_false }
end
context "as a new user with no enterprises" do
it { subject.can_manage_products?(user).should be_false }
it { subject.can_manage_enterprises?(user).should be_false }
it { subject.can_manage_orders?(user).should be_false }
it "can create enterprises straight off the bat" do
subject.is_new_user?(user).should be_true
expect(user).to have_ability :create, for: Enterprise
end
end
describe "managing enterprises" do
it "can manage enterprises when the user has at least one enterprise assigned" do
user.enterprise_roles.create! enterprise: enterprise_full
subject.can_manage_enterprises?(user).should be_true
end
it "can't otherwise" do
subject.can_manage_enterprises?(user).should be_false
end
end
describe "managing products" do
it "can when a user manages a 'full' type enterprise" do
user.enterprise_roles.create! enterprise: enterprise_full
subject.can_manage_products?(user).should be_true
end
it "can when a user manages a 'single' type enterprise" do
user.enterprise_roles.create! enterprise: enterprise_single
subject.can_manage_products?(user).should be_true
end
it "can't when a user manages a 'profile' type enterprise" do
user.enterprise_roles.create! enterprise: enterprise_profile
subject.can_manage_products?(user).should be_false
end
it "can't when the user manages no enterprises" do
subject.can_manage_products?(user).should be_false
end
end
end
describe 'Roles' do

View File

@@ -37,12 +37,9 @@ module AuthenticationWorkflow
visit spree.admin_path
end
def create_enterprise_user(enterprises = [])
new_user = create(:user, password: 'blahblah', :password_confirmation => 'blahblah')
def create_enterprise_user( attrs = {} )
new_user = create(:user, attrs)
new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default.
for enterprise in enterprises do
new_user.enterprise_roles.build(enterprise: enterprise).save
end
new_user.save
new_user
end