Auto-merge from CI [skip ci]

This commit is contained in:
Continuous Integration
2015-07-17 11:48:16 +10:00
37 changed files with 750 additions and 79 deletions

View File

@@ -1,18 +1,69 @@
Darkswarm.controller "EnterprisesCtrl", ($scope, Enterprises, Search, $document, $rootScope, HashNavigation, FilterSelectorsService, EnterpriseModal) ->
Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterprises, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) ->
$scope.Enterprises = Enterprises
$scope.totalActive = FilterSelectorsService.totalActive
$scope.clearAll = FilterSelectorsService.clearAll
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.totalActive = FilterSelectorsService.totalActive
$scope.clearAll = FilterSelectorsService.clearAll
$scope.filterText = FilterSelectorsService.filterText
$scope.FilterSelectorsService = FilterSelectorsService
$scope.query = Search.search()
$scope.openModal = EnterpriseModal.open
$scope.activeTaxons = []
$scope.show_profiles = false
$scope.filtersActive = false
$scope.distanceMatchesShown = false
$scope.$watch "query", (query)->
Enterprises.flagMatching query
Search.search query
$rootScope.$broadcast 'enterprisesChanged'
$scope.distanceMatchesShown = false
$timeout ->
Enterprises.calculateDistance query, $scope.firstNameMatch()
$rootScope.$broadcast 'enterprisesChanged'
$rootScope.$on "enterprisesChanged", ->
$scope.filterEnterprises()
$scope.updateVisibleMatches()
# When filter settings change, this could change which name match is at the top, or even
# result in no matches. This affects the reference point that the distance matches are
# calculated from, so we need to recalculate distances.
$scope.$watch '[activeTaxons, shippingTypes, show_profiles]', ->
$timeout ->
Enterprises.calculateDistance $scope.query, $scope.firstNameMatch()
$rootScope.$broadcast 'enterprisesChanged'
, true
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
if HashNavigation.active "hubs"
$document.scrollTo $("#hubs"), 100, 200
$scope.filterEnterprises = ->
es = Enterprises.hubs
$scope.nameMatches = enterpriseMatchesNameQueryFilter(es, true)
$scope.distanceMatches = enterpriseMatchesNameQueryFilter(es, false)
$scope.distanceMatches = distanceWithinKmFilter($scope.distanceMatches, 50)
$scope.updateVisibleMatches = ->
$scope.visibleMatches = if $scope.nameMatches.length == 0 || $scope.distanceMatchesShown
$scope.nameMatches.concat $scope.distanceMatches
else
$scope.nameMatches
$scope.showDistanceMatches = ->
$scope.distanceMatchesShown = true
$scope.updateVisibleMatches()
$scope.firstNameMatch = ->
if $scope.nameMatchesFiltered?
$scope.nameMatchesFiltered[0]
else
undefined

View File

@@ -1,4 +1,5 @@
Darkswarm.controller "LineItemCtrl", ($scope)->
$scope.$watch "line_item.quantity", (newValue, oldValue)->
$scope.$watch '[line_item.quantity, line_item.max_quantity]', (newValue, oldValue)->
if newValue != oldValue
$scope.Cart.orderChanged()
, true

View File

@@ -0,0 +1,5 @@
Darkswarm.filter 'distanceWithinKm', ->
(enterprises, range) ->
enterprises ||= []
enterprises.filter (enterprise) ->
enterprise.distance / 1000 <= range

View File

@@ -0,0 +1,4 @@
Darkswarm.filter 'enterpriseMatchesNameQuery', ->
(enterprises, matches_name_query) ->
enterprises.filter (enterprise) ->
enterprise.matches_name_query == matches_name_query

View File

@@ -20,7 +20,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
$http.post('/orders/populate', @data()).success (data, status)=>
@saved()
.error (response, status)=>
# TODO what shall we do here?
@scheduleRetry()
data: =>
variants = {}
@@ -30,6 +30,12 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
max_quantity: li.max_quantity
{variants: variants}
scheduleRetry: =>
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
$timeout =>
console.log "Retrying cart update"
@orderChanged()
, 3000
saved: =>
@dirty = false

View File

@@ -1,4 +1,4 @@
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter)->
Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer, visibleFilter, Matcher, Geo, $rootScope)->
new class Enterprises
enterprises_by_id: {}
constructor: ->
@@ -28,3 +28,36 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Taxons, Dereferencer,
Dereferencer.dereference enterprise.taxons, Taxons.taxons_by_id
Dereferencer.dereference enterprise.supplied_taxons, Taxons.taxons_by_id
flagMatching: (query) ->
for enterprise in @enterprises
enterprise.matches_name_query = if query? && query.length > 0
Matcher.match([enterprise.name], query)
else
false
calculateDistance: (query, firstMatching) ->
if query?.length > 0
if firstMatching?
@setDistanceFrom firstMatching
else
@calculateDistanceGeo query
else
@resetDistance()
calculateDistanceGeo: (query) ->
Geo.geocode query, (results, status) =>
$rootScope.$apply =>
if status == Geo.OK
#console.log "Geocoded #{query} -> #{results[0].geometry.location}."
@setDistanceFrom results[0].geometry.location
else
console.log "Geocoding failed for the following reason: #{status}"
@resetDistance()
setDistanceFrom: (locatable) ->
for enterprise in @enterprises
enterprise.distance = Geo.distanceBetween enterprise, locatable
$rootScope.$broadcast 'enterprisesChanged'
resetDistance: ->
enterprise.distance = null for enterprise in @enterprises

View File

@@ -0,0 +1,23 @@
Darkswarm.service "Geo", ->
new class Geo
OK: google.maps.GeocoderStatus.OK
# Usage:
# Geo.geocode address, (results, status) ->
# if status == Geo.OK
# console.log results[0].geometry.location
# else
# console.log "Error: #{status}"
geocode: (address, callback) ->
geocoder = new google.maps.Geocoder()
geocoder.geocode {'address': address, 'region': "<%= Spree::Country.find_by_id(Spree::Config[:default_country_id]).iso %>"}, callback
distanceBetween: (src, dst) ->
google.maps.geometry.spherical.computeDistanceBetween @toLatLng(src), @toLatLng(dst)
# Wrap an object in a google.maps.LatLng if it has not been already
toLatLng: (locatable) ->
if locatable.lat?
locatable
else
new google.maps.LatLng locatable.latitude, locatable.longitude

0
app/assets/javascripts/shared/ng-tags-input.min.js vendored Executable file → Normal file
View File

View File

@@ -55,7 +55,7 @@
100%
opacity: 1
@-webkit-keyframes spin
@-webkit-keyframes spin
0%
-webkit-transform: rotate(0deg)
transform: rotate(0deg)
@@ -104,12 +104,10 @@
.animate-repeat
-webkit-transform: translateZ(0)
transform: translateZ(0)
&.ng-move, &.ng-enter, &.ng-leave
-webkit-transition: all 300ms linear
transition: all 300ms linear
-webkit-transition: all 300ms linear
transition: all 300ms linear
&.ng-leave
opacity: 1
&.ng-leave-active
@@ -178,7 +176,7 @@ product.animate-repeat
overflow: hidden
max-height: 0
opacity: 0 !important
// &.ng-hide-add-active, &.ng-hide-remove-active
&.ng-hide-add, &.ng-hide-remove
@@ -197,7 +195,7 @@ product.animate-repeat
&.ng-hide
opacity: 0 !important
// &.ng-hide-add-active, &.ng-hide-remove-active
&.ng-hide-add, &.ng-hide-remove
@@ -206,8 +204,8 @@ product.animate-repeat
it as hidden. */
display: block !important
@mixin csstrans
@@ -217,7 +215,3 @@ product.animate-repeat
-o-transition: all 300ms ease
transition: all 300ms ease
-webkit-transform-style: preserve-3d

View File

@@ -5,3 +5,6 @@
background-color: lighten($ofn-grey, 43%)
@include panepadding
@include sidepaddingSm
.name-matches, .distance-matches
margin-top: 4em

View File

@@ -6,6 +6,7 @@ require 'open_food_network/order_grouper'
require 'open_food_network/customers_report'
require 'open_food_network/users_and_enterprises_report'
require 'open_food_network/order_cycle_management_report'
require 'open_food_network/packing_report'
require 'open_food_network/sales_tax_report'
require 'open_food_network/xero_invoices_report'
@@ -31,11 +32,15 @@ Spree::Admin::ReportsController.class_eval do
order_cycle_management: [
["Payment Methods Report", :payment_methods],
["Delivery Report", :delivery]
],
packing: [
["Pack By Customer", :pack_by_customer],
["Pack By Supplier", :pack_by_supplier]
]
}
# Fetches user's distributors, suppliers and order_cycles
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management]
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management, :packing]
# Render a partial for orders and fulfillment description
respond_override :index => { :html => { :success => lambda {
@@ -47,7 +52,9 @@ Spree::Admin::ReportsController.class_eval do
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
@reports[:order_cycle_management][:description] =
render_to_string(partial: 'order_cycle_management_description', layout: false, locals: {report_types: REPORT_TYPES[:order_cycle_management]}).html_safe
} } }
@reports[:packing][:description] =
render_to_string(partial: 'packing_description', layout: false, locals: {report_types: REPORT_TYPES[:packing]}).html_safe
} } }
# Overide spree reports list.
@@ -75,6 +82,40 @@ Spree::Admin::ReportsController.class_eval do
render_report(@report.header, @report.table, params[:csv], "order_cycle_management_#{timestamp}.csv")
end
def packing
# -- Prepare parameters
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
# -- Prepare form options
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
# My distributors and any distributors distributing products I supply
@distributors = my_distributors | Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
# My suppliers and any suppliers supplying products I distribute
@suppliers = my_suppliers | my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
@report_types = REPORT_TYPES[:packing]
@report_type = params[:report_type]
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::PackingReport.new spree_current_user, params
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
@table = order_grouper.table(@report.table_items)
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
render_report(@report.header, @table, params[:csv], "packing_#{timestamp}.csv")
end
def orders_and_distributors
params[:q] ||= {}
@@ -732,9 +773,9 @@ Spree::Admin::ReportsController.class_eval do
:sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" },
:users_and_enterprises => { :name => "Users & Enterprises", :description => "Enterprise Ownership & Status" },
:order_cycle_management => {:name => "Order Cycle Management", :description => ''},
:packing => {:name => "Packing Reports", :description => ''},
:sales_tax => { :name => "Sales Tax", :description => "Sales Tax For Orders" },
:xero_invoices => { :name => "Xero Invoices", :description => 'Invoices for import into Xero' }
}
# Return only reports the user is authorized to view.
reports.select { |action| can? action, :report }

View File

@@ -123,7 +123,7 @@ class AbilityDecorator
can [:admin, :index, :read, :create, :edit], Spree::Classification
# Reports page
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :packing], :report
end
def add_order_cycle_management_abilities(user)

View File

@@ -1,21 +1,21 @@
.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"}
.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}", bindonce: true}
.columns.small-12.medium-6.large-5.fat
%div{"bo-if" => "hub.taxons"}
%label Shop for
.trans-sentence
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
%render-svg{path: "{{taxon.icon}}"}
%span{"bo-text" => "taxon.name"}
%span{"bo-text" => "taxon.name"}
%div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"}
&nbsp;
.columns.small-12.medium-3.large-2.fat
%div{"bo-if" => "hub.pickup || hub.delivery"}
%label Delivery options
%ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1
%li.pickup{"bo-if" => "hub.pickup"}
%li.pickup{"bo-if" => "hub.pickup"}
%i.ofn-i_038-takeaway
Pickup
%li.delivery{"bo-if" => "hub.delivery"}
%li.delivery{"bo-if" => "hub.delivery"}
%i.ofn-i_039-delivery
Delivery
.columns.small-12.medium-3.large-5.fat

View File

@@ -10,7 +10,7 @@
%h5.tdhead
.light Filter by
Type
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "Enterprises.hubs | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons" }
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" }
.small-12.large-3.columns
%h5.tdhead
.light Filter by

View File

@@ -5,18 +5,22 @@
.small-12.columns
%h1{"scroll-after-load" => (spree_current_user ? true : nil)} Shop in your local area
= render partial: "shared/components/enterprise_search"
= render partial: "home/filters"
= render "shared/components/enterprise_search"
= render "home/filters"
.row{bindonce: true}
.row
.small-12.columns
.active_table
%hub.active_table_node.row.animate-repeat{"ng-repeat" => "hub in filteredEnterprises = (Enterprises.hubs | visible | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])",
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
"ng-controller" => "HubNodeCtrl",
id: "{{hub.hash}}"}
.small-12.columns
= render partial: 'home/skinny'
= render partial: 'home/fat'
.name-matches{"ng-show" => "nameMatchesFiltered.length > 0"}
%h2 Did you mean?
= render "home/hubs_table", enterprises: "nameMatches"
= render partial: 'shared/components/enterprise_no_results'
.distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"}
%h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"}
Closest to
%span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}...
%span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}...
= render "home/hubs_table", enterprises: "distanceMatches"
.show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
%a{href: "", "ng-click" => "showDistanceMatches()"} Show me shops near {{ nameMatchesFiltered[0].name }}

View File

@@ -0,0 +1,10 @@
.active_table
%hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | visible | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])",
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
"ng-controller" => "HubNodeCtrl",
id: "{{hub.hash}}"}
.small-12.columns
= render 'home/skinny'
= render 'home/fat'
= render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered"

View File

@@ -1,5 +1,4 @@
.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true}
.columns.small-12.medium-5.large-5.skinny-head
%a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-empties-cart" => "hub", "data-is-link" => "true"}
%i{bo: {class: "hub.icon_font"}}
@@ -9,6 +8,7 @@
%span.margin-top{"bo-text" => "hub.address.city"}
.columns.small-2.medium-1.large-1
%span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"}
%span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km)
.columns.small-4.medium-3.large-3.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"}
@@ -26,10 +26,10 @@
%span.margin-top{ bo: { if: "!current()" } } Orders closed
.columns.small-2.medium-1.large-1.text-right
%span.margin-top
%span.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"}
.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed", bindonce: true}
.columns.small-12.medium-6.large-5.skinny-head
%a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"}
%i{ng: {class: "hub.icon_font"}}
@@ -43,4 +43,3 @@
.columns.small-6.medium-3.large-4.text-right
%span.margin-top{ bo: { if: "!current()" } }
%em Profile only

View File

@@ -11,7 +11,7 @@
%link{href: "https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700", rel: "stylesheet", type: "text/css"}
= yield :scripts
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places&sensor=false"}
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry&sensor=false"}
= split_stylesheet_link_tag "darkswarm/all"
= javascript_include_tag "darkswarm/all"

View File

@@ -1,4 +1,5 @@
%producer.row{"ng-show" => "filteredEnterprises.length == 0"}
- enterprises ||= 'filteredEnterprises'
%producer.row{"ng-show" => "#{enterprises}.length == 0"}
%p.no-results
Sorry, no results found for
%strong {{query}}.

View File

@@ -3,5 +3,5 @@
%input{type: :text,
"ng-model" => "query",
placeholder: t('search_by_name'),
"ng-debounce" => "150",
"ng-debounce" => "500",
"ofn-disable-enter" => true}

View File

@@ -0,0 +1,4 @@
%ul{style: "margin-left: 12pt"}
- report_types.each do |report_type|
%li
= link_to report_type[0], "#{packing_admin_reports_url}?report_type=#{report_type[1]}"

View File

@@ -0,0 +1,42 @@
= form_for @report.search, :url => spree.packing_admin_reports_path do |f|
= render 'date_range_form', f: f
.row
.alpha.two.columns= label_tag nil, "Hubs: "
.omega.fourteen.columns= f.collection_select(:distributor_id_in, @distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true})
.row
.alpha.two.columns= label_tag nil, "Producers: "
.omega.fourteen.columns= select_tag(:supplier_id_in, options_from_collection_for_select(@suppliers, :id, :name, params[:supplier_id_in]), {class: "select2 fullwidth", multiple: true})
.row
.alpha.two.columns= label_tag nil, "Order Cycles: "
.omega.fourteen.columns
= f.select(:order_cycle_id_in, report_order_cycle_options(@order_cycles), {selected: params[:q][:order_cycle_id_in]}, {class: "select2 fullwidth", multiple: true})
.row
.alpha.two.columns= label_tag nil, "Report Type: "
.omega.fourteen.columns= select_tag(:report_type, options_for_select(@report_types, @report_type))
.row
= check_box_tag :csv
= label_tag :csv, "Download as csv"
.row
= button t(:search)
%br
%br
%table#listing_orders.index
%thead
%tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody
- @table.each do |row|
%tr
- row.each do |column|
%td= column
- if @table.empty?
%tr
%td{:colspan => "2"}= t(:none)

View File

@@ -145,6 +145,7 @@ end
Spree::Core::Engine.routes.prepend do
match '/admin/reports/orders_and_distributors' => 'admin/reports#orders_and_distributors', :as => "orders_and_distributors_admin_reports", :via => [:get, :post]
match '/admin/reports/order_cycle_management' => 'admin/reports#order_cycle_management', :as => "order_cycle_management_admin_reports", :via => [:get, :post]
match '/admin/reports/packing' => 'admin/reports#packing', :as => "packing_admin_reports", :via => [:get, :post]
match '/admin/reports/group_buys' => 'admin/reports#group_buys', :as => "group_buys_admin_reports", :via => [:get, :post]
match '/admin/reports/bulk_coop' => 'admin/reports#bulk_coop', :as => "bulk_coop_admin_reports", :via => [:get, :post]
match '/admin/reports/payments' => 'admin/reports#payments', :as => "payments_admin_reports", :via => [:get, :post]

View File

@@ -0,0 +1,124 @@
module OpenFoodNetwork
class PackingReport
attr_reader :params
def initialize(user, params = {})
@params = params
@user = user
end
def header
if is_by_customer?
["Hub", "Code", "First Name", "Last Name", "Supplier", "Product", "Variant", "Quantity", "TempControlled?"]
else
["Hub", "Supplier", "Code", "First Name", "Last Name", "Product", "Variant", "Quantity", "TempControlled?"]
end
end
def search
Spree::Order.complete.not_state(:canceled).managed_by(@user).search(params[:q])
end
def orders
search.result
end
def table_items
@line_items = orders.map do |o|
lis = o.line_items.managed_by(@user)
lis = lis.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present?
lis
end.flatten
end
def rules
if is_by_customer?
# customer_rows orders
# table_items = @line_items
[
{ group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.bill_address.lastname },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL ITEMS" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.product.name } } ]
else
# supplier_rows orders
# table_items = supplier_rows orders
#
[ { group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL ITEMS" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.product.name } } ]
end
end
def columns
if is_by_customer?
[ proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| customer_code(line_items.first.order.email) },
proc { |line_items| line_items.first.order.bill_address.firstname },
proc { |line_items| line_items.first.order.bill_address.lastname },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| is_temperature_controlled?(line_items.first) }
]
else
[
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| customer_code(line_items.first.order.email) },
proc { |line_items| line_items.first.order.bill_address.firstname },
proc { |line_items| line_items.first.order.bill_address.lastname },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| is_temperature_controlled?(line_items.first) }
]
end
end
private
def is_temperature_controlled?(line_item)
if line_item.product.shipping_category.andand.temperature_controlled
"Yes"
else
"No"
end
end
def is_by_customer?
params[:report_type] == "pack_by_customer"
end
def customer_code(email)
customer = Customer.where(email: email).first
customer.nil? ? "" : customer.code
end
end
end

View File

@@ -25,6 +25,16 @@ module OpenFoodNetwork
@variant_override.andand.count_on_hand || super
end
def on_demand
if @variant_override.andand.count_on_hand.present?
# If we're overriding the stock level of an on_demand variant, show it as not
# on_demand, so our stock control can take effect.
false
else
super
end
end
def decrement!(attribute, by=1)
if attribute == :count_on_hand && @variant_override.andand.stock_overridden?
@variant_override.decrement_stock! by

View File

@@ -84,6 +84,66 @@ feature %q{
end
end
describe "Packing reports" do
before do
login_to_admin_section
click_link "Reports"
end
let(:bill_address1) { create(:address, lastname: "Aman") }
let(:bill_address2) { create(:address, lastname: "Bman") }
let(:distributor_address) { create(:address, :address1 => "distributor address", :city => 'The Shire', :zipcode => "1234") }
let(:distributor) { create(:distributor_enterprise, :address => distributor_address) }
let(:order1) { create(:order, distributor: distributor, bill_address: bill_address1) }
let(:order2) { create(:order, distributor: distributor, bill_address: bill_address2) }
let(:supplier) { create(:supplier_enterprise, name: "Supplier") }
let(:product_1) { create(:simple_product, name: "Product 1", supplier: supplier ) }
let(:variant_1) { create(:variant, product: product_1, unit_description: "Big") }
let(:variant_2) { create(:variant, product: product_1, unit_description: "Small") }
let(:product_2) { create(:simple_product, name: "Product 2", supplier: supplier) }
before do
Timecop.travel(Time.zone.local(2013, 4, 25, 14, 0, 0)) { order1.finalize! }
Timecop.travel(Time.zone.local(2013, 4, 25, 15, 0, 0)) { order2.finalize! }
create(:line_item, variant: variant_1, quantity: 1, order: order1)
create(:line_item, variant: variant_2, quantity: 3, order: order1)
create(:line_item, variant: product_2.master, quantity: 3, order: order2)
end
scenario "Pack By Customer" do
click_link "Pack By Customer"
fill_in 'q_completed_at_gt', with: '2013-04-25 13:00:00'
fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00'
#select 'Pack By Customer', from: 'report_type'
click_button 'Search'
rows = find("table#listing_orders.index").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
table.sort.should == [
["Hub", "Code", "First Name", "Last Name", "Supplier", "Product", "Variant", "Quantity", "TempControlled?"]
].sort
all('table#listing_orders tbody tr').count.should == 5 # Totals row per order
end
scenario "Pack By Supplier" do
click_link "Pack By Supplier"
fill_in 'q_completed_at_gt', with: '2013-04-25 13:00:00'
fill_in 'q_completed_at_lt', with: '2013-04-25 16:00:00'
#select 'Pack By Customer', from: 'report_type'
click_button 'Search'
rows = find("table#listing_orders").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
table.sort.should == [
["Hub", "Supplier", "Code", "First Name", "Last Name", "Product", "Variant", "Quantity", "TempControlled?"]
].sort
all('table#listing_orders tbody tr').count.should == 4 # Totals row per supplier
end
end
scenario "orders and distributors report" do
login_to_admin_section
click_link 'Reports'

View File

@@ -134,15 +134,20 @@ feature "As a consumer I want to shop with a distributor", js: true do
end
it "should save group buy data to the cart" do
# -- Quantity
fill_in "variants[#{variant.id}]", with: 6
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7
page.should have_in_cart product.name
wait_until { !cart_dirty }
li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
li.quantity.should == 6
# -- Max quantity
fill_in "variant_attributes[#{variant.id}][max_quantity]", with: 7
wait_until { !cart_dirty }
li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last
li.max_quantity.should == 7
li.quantity.should == 6
end
end
end

View File

@@ -15,18 +15,23 @@ feature "shopping with variant overrides defined", js: true do
let(:pm) { hub.payment_methods.first }
let(:p1) { create(:simple_product, supplier: producer) }
let(:p2) { create(:simple_product, supplier: producer) }
let(:p3) { create(:simple_product, supplier: producer, on_demand: true) }
let(:v1) { create(:variant, product: p1, price: 11.11, unit_value: 1) }
let(:v2) { create(:variant, product: p1, price: 22.22, unit_value: 2) }
let(:v3) { create(:variant, product: p2, price: 33.33, unit_value: 3) }
let(:v4) { create(:variant, product: p1, price: 44.44, unit_value: 4) }
let(:v5) { create(:variant, product: p3, price: 55.55, unit_value: 5, on_demand: true) }
let(:v6) { create(:variant, product: p3, price: 66.66, unit_value: 6, on_demand: true) }
let!(:vo1) { create(:variant_override, hub: hub, variant: v1, price: 55.55, count_on_hand: nil) }
let!(:vo2) { create(:variant_override, hub: hub, variant: v2, count_on_hand: 0) }
let!(:vo3) { create(:variant_override, hub: hub, variant: v3, count_on_hand: 0) }
let!(:vo4) { create(:variant_override, hub: hub, variant: v4, count_on_hand: 3) }
let!(:vo5) { create(:variant_override, hub: hub, variant: v5, count_on_hand: 0) }
let!(:vo6) { create(:variant_override, hub: hub, variant: v6, count_on_hand: 6) }
let(:ef) { create(:enterprise_fee, enterprise: hub, fee_type: 'packing', calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)) }
before do
outgoing_exchange.variants = [v1, v2, v3, v4]
outgoing_exchange.variants = [v1, v2, v3, v4, v5, v6]
outgoing_exchange.enterprise_fees << ef
sm.calculator.preferred_amount = 0
visit shops_path
@@ -46,6 +51,9 @@ feature "shopping with variant overrides defined", js: true do
# Entire product should not appear - no stock
page.should_not have_content p2.name
page.should_not have_content v3.options_text
# On-demand product with VO of no stock should NOT appear
page.should_not have_content v5.options_text
end
it "calculates fees correctly" do
@@ -127,6 +135,19 @@ feature "shopping with variant overrides defined", js: true do
end.to change { vo4.reload.count_on_hand }.by(-2)
end
it "subtracts stock from stock-overridden on_demand variants" do
fill_in "variants[#{v6.id}]", with: "2"
show_cart
wait_until_enabled 'li.cart a.button'
click_link 'Checkout now'
expect do
expect do
complete_checkout
end.to change { v6.reload.count_on_hand }.by(0)
end.to change { vo6.reload.count_on_hand }.by(-2)
end
it "does not subtract stock from overrides that do not override count_on_hand" do
fill_in "variants[#{v1.id}]", with: "2"
show_cart

View File

@@ -1,12 +1,8 @@
describe 'filtering by active', ->
filterByActive = null
objects = [
{
active: true
}
{
active: false
}
{active: true}
{active: false}
]

View File

@@ -0,0 +1,17 @@
describe "filtering enterprises to those within a certain radius", ->
filter = null
enterprises = [
{distance: 25000}
{distance: 75000}
]
beforeEach ->
module 'Darkswarm'
inject ($filter) ->
filter = $filter('distanceWithinKm')
it "filters to those enterprises within a distance", ->
expect(filter(enterprises, 50)).toEqual [enterprises[0]]
it "returns empty array when enterprises array is null", ->
expect(filter(null, 50)).toEqual []

View File

@@ -3,6 +3,8 @@ describe 'Cart service', ->
Variants = null
variant = null
order = null
$httpBackend = null
$timeout = null
beforeEach ->
module 'Darkswarm'
@@ -16,9 +18,11 @@ describe 'Cart service', ->
]
}
angular.module('Darkswarm').value('currentOrder', order)
inject ($injector)->
inject ($injector, _$httpBackend_, _$timeout_)->
Variants = $injector.get("Variants")
Cart = $injector.get("Cart")
$httpBackend = _$httpBackend_
$timeout = _$timeout_
it "backreferences line items", ->
expect(Cart.line_items[0].variant.line_item).toBe Cart.line_items[0]
@@ -44,6 +48,29 @@ describe 'Cart service', ->
order.line_items[0].quantity = 2
expect(Cart.total_item_count()).toEqual 2
describe "updating the cart", ->
data = {variants: {}}
it "marks the form as saved on success", ->
spyOn(Cart, 'saved')
$httpBackend.expectPOST("/orders/populate", data).respond 200, {}
Cart.update()
$httpBackend.flush()
expect(Cart.saved).toHaveBeenCalled()
it "retries the update on failure", ->
spyOn(Cart, 'scheduleRetry')
$httpBackend.expectPOST("/orders/populate", data).respond 404, {}
Cart.update()
$httpBackend.flush()
expect(Cart.scheduleRetry).toHaveBeenCalled()
it "schedules retries of updates", ->
spyOn(Cart, 'orderChanged')
Cart.scheduleRetry()
$timeout.flush()
expect(Cart.orderChanged).toHaveBeenCalled()
describe "generating an extended variant name", ->
it "returns the product name when it is the same as the variant name", ->
variant = {product_name: 'product_name', name_to_display: 'product_name'}

View File

@@ -1,24 +1,37 @@
describe "Enterprises service", ->
Enterprises = null
CurrentHubMock = {}
Geo =
OK: 'ok'
succeed: true
geocode: (query, callback) ->
if @succeed
results = [{geometry: {location: "location"}}]
callback(results, @OK)
else
callback(results, 'Oops')
distanceBetween: (locatable, location) ->
123
taxons = [
{id: 1, name: "test"}
]
enterprises = [
{id: 1, visible: true, category: "hub", producers: [{id: 5}], taxons: [{id: 1}]},
{id: 2, visible: true, category: "hub", producers: [{id: 6}]}
{id: 3, visible: true, category: "hub_profile"}
{id: 4, visible: false, category: "hub", producers: [{id: 7}]}
{id: 5, visible: true, category: "producer_hub", hubs: [{id: 1}]},
{id: 6, visible: true, category: "producer_shop", hubs: [{id: 2}]},
{id: 7, visible: true, category: "producer", hubs: [{id: 2}]}
{id: 8, visible: false, category: "producer", hubs: [{id: 2}]}
{id: 1, visible: true, name: 'a', category: "hub", producers: [{id: 5}], taxons: [{id: 1}]},
{id: 2, visible: true, name: 'b', category: "hub", producers: [{id: 6}]}
{id: 3, visible: true, name: 'c', category: "hub_profile"}
{id: 4, visible: false,name: 'd', category: "hub", producers: [{id: 7}]}
{id: 5, visible: true, name: 'e', category: "producer_hub", hubs: [{id: 1}]},
{id: 6, visible: true, name: 'f', category: "producer_shop", hubs: [{id: 2}]},
{id: 7, visible: true, name: 'g', category: "producer", hubs: [{id: 2}]}
{id: 8, visible: false,name: 'h', category: "producer", hubs: [{id: 2}]}
]
H1: 0
beforeEach ->
module 'Darkswarm'
module ($provide)->
$provide.value "CurrentHub", CurrentHubMock
$provide.value "Geo", Geo
null
angular.module('Darkswarm').value('enterprises', enterprises)
angular.module('Darkswarm').value('taxons', taxons)
@@ -73,3 +86,70 @@ describe "Enterprises service", ->
expect(Enterprises.producers).toContain Enterprises.enterprises[4]
expect(Enterprises.producers).toContain Enterprises.enterprises[5]
expect(Enterprises.producers).toContain Enterprises.enterprises[6]
describe "flagging enterprises with names matching a query", ->
it "flags enterprises when a query is provided", ->
Enterprises.flagMatching 'c'
expect(e.matches_name_query).toBe true for e in enterprises when e.name == 'c'
expect(e.matches_name_query).toBe false for e in enterprises when e.name != 'c'
it "clears flags when query is null", ->
Enterprises.flagMatching null
expect(e.matches_name_query).toBe false for e in enterprises
it "clears flags when query is blank", ->
Enterprises.flagMatching ''
expect(e.matches_name_query).toBe false for e in enterprises
describe "calculating the distance of enterprises from a location", ->
describe "when a query is provided", ->
it "sets the distance from the enterprise when a name match is available", ->
spyOn(Enterprises, "setDistanceFrom")
Enterprises.calculateDistance "asdf", 'match'
expect(Enterprises.setDistanceFrom).toHaveBeenCalledWith('match')
it "calculates the distance from the geocoded query otherwise", ->
spyOn(Enterprises, "calculateDistanceGeo")
Enterprises.calculateDistance "asdf", undefined
expect(Enterprises.calculateDistanceGeo).toHaveBeenCalledWith("asdf")
it "resets the distance when query is null", ->
spyOn(Enterprises, "resetDistance")
Enterprises.calculateDistance null
expect(Enterprises.resetDistance).toHaveBeenCalled()
it "resets the distance when query is blank", ->
spyOn(Enterprises, "resetDistance")
Enterprises.calculateDistance ""
expect(Enterprises.resetDistance).toHaveBeenCalled()
describe "calculating the distance of enterprises from a location by geocoding", ->
beforeEach ->
spyOn(Enterprises, "setDistanceFrom")
it "calculates distance for all enterprises when geocoding succeeds", ->
Geo.succeed = true
Enterprises.calculateDistanceGeo('query')
expect(Enterprises.setDistanceFrom).toHaveBeenCalledWith("location")
it "resets distance when geocoding fails", ->
Geo.succeed = false
spyOn(Enterprises, "resetDistance")
Enterprises.calculateDistanceGeo('query')
expect(Enterprises.setDistanceFrom).not.toHaveBeenCalled()
expect(Enterprises.resetDistance).toHaveBeenCalled()
describe "setting the distance of each enterprise from a central location", ->
it "sets the distances", ->
Enterprises.setDistanceFrom 'location'
for e in Enterprises.enterprises
expect(e.distance).toEqual 123
describe "resetting the distance measurement of all enterprises", ->
beforeEach ->
e.distance = 123 for e in Enterprises.enterprises
it "resets the distance", ->
Enterprises.resetDistance()
for e in Enterprises.enterprises
expect(e.distance).toBeNull()

View File

@@ -1,7 +1,8 @@
describe "Groups service", ->
Groups = null
Enterprises = null
CurrentHubMock = {}
CurrentHubMock = {}
Geo = {}
groups = [{
id: 1
name: "Test Group"
@@ -17,17 +18,18 @@ describe "Groups service", ->
beforeEach ->
module 'Darkswarm'
angular.module('Darkswarm').value('groups', groups)
angular.module('Darkswarm').value('enterprises', enterprises)
angular.module('Darkswarm').value('groups', groups)
angular.module('Darkswarm').value('enterprises', enterprises)
module ($provide)->
$provide.value "CurrentHub", CurrentHubMock
$provide.value "CurrentHub", CurrentHubMock
$provide.value "Geo", Geo
null
inject (_Groups_, _Enterprises_)->
Groups = _Groups_
Enterprises = _Enterprises_
Groups = _Groups_
Enterprises = _Enterprises_
it "dereferences group enterprises", ->
expect(Groups.groups[0].enterprises[0]).toBe enterprises[0]
it "dereferences enterprise groups", ->
expect(Enterprises.enterprises[0].groups[0]).toBe groups[0]

View File

@@ -1,6 +1,7 @@
describe "Hubs service", ->
OfnMap = null
CurrentHubMock = {}
CurrentHubMock = {}
Geo = {}
enterprises = [
{
id: 2
@@ -13,12 +14,13 @@ describe "Hubs service", ->
beforeEach ->
module 'Darkswarm'
angular.module('Darkswarm').value('enterprises', enterprises)
angular.module('Darkswarm').value('enterprises', enterprises)
module ($provide)->
$provide.value "CurrentHub", CurrentHubMock
$provide.value "CurrentHub", CurrentHubMock
$provide.value "Geo", Geo
null
inject ($injector)->
OfnMap = $injector.get("OfnMap")
OfnMap = $injector.get("OfnMap")
it "builds MapMarkers from enterprises", ->
expect(OfnMap.enterprises[0].id).toBe enterprises[0].id

View File

@@ -10,6 +10,7 @@ describe 'Products service', ->
productWithImage = null
properties = null
taxons = null
Geo = {}
beforeEach ->
product =
@@ -40,6 +41,7 @@ describe 'Products service', ->
$provide.value "currentOrder", currentOrder
$provide.value "taxons", taxons
$provide.value "properties", properties
$provide.value "Geo", Geo
null
inject ($injector, _$httpBackend_)->

View File

@@ -0,0 +1,81 @@
require 'spec_helper'
include AuthenticationWorkflow
module OpenFoodNetwork
describe PackingReport do
context "as a site admin" do
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by_name!("admin")
user
end
subject { PackingReport.new user }
describe "fetching orders" do
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago)
subject.orders.should == [o2]
end
it "does not show cancelled orders" do
o1 = create(:order, state: "canceled", completed_at: 1.day.ago)
o2 = create(:order, completed_at: 1.day.ago)
subject.orders.should == [o2]
end
end
end
context "as an enterprise user" do
let!(:user) { create_enterprise_user }
subject { PackingReport.new user }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:d1) { create(:distributor_enterprise) }
let(:oc1) { create(:simple_order_cycle) }
let(:order) { create(:order, completed_at: 1.day.ago, order_cycle: oc1, distributor: d1) }
before do
d1.enterprise_roles.create!(user: user)
end
it "only shows orders managed by the current user" do
d2 = create(:distributor_enterprise)
d2.enterprise_roles.create!(user: create(:user))
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
subject.orders.should == [order]
end
it "only shows the selected order cycle" do
oc2 = create(:simple_order_cycle)
order2 = create(:order, order_cycle: oc2)
subject.stub(:params).and_return(order_cycle_id_in: oc1.id)
subject.orders.should == [order]
end
it "only shows product line items that I am supplying" do
d2 = create(:distributor_enterprise)
create(:enterprise_relationship, parent: supplier, child: d1, permissions_list: [:add_to_order_cycle])
d2.enterprise_roles.create!(user: create(:user))
s2 = create(:supplier_enterprise)
p2 = create(:simple_product, supplier: s2)
li1 = create(:line_item, product: product)
li2 = create(:line_item, product: p2)
o1 = create(:order, distributor: d1, completed_at: 1.day.ago)
o1.line_items << li1
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
o2.line_items << li2
subject.orders.map{ |o| o.line_items}.flatten.should include li1
subject.orders.map{ |o| o.line_items}.flatten.should_not include li2
end
end
end
end
end

View File

@@ -5,6 +5,7 @@ module OpenFoodNetwork
let(:hub) { create(:distributor_enterprise) }
let(:v) { create(:variant, price: 11.11, count_on_hand: 1) }
let(:vo) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: 2) }
let(:vo_price_only) { create(:variant_override, hub: hub, variant: v, price: 22.22, count_on_hand: nil) }
let(:scoper) { ScopeVariantToHub.new(hub) }
describe "overriding price" do
@@ -44,6 +45,27 @@ module OpenFoodNetwork
scoper.scope v
v.count_on_hand.should == 1
end
describe "overriding stock on an on_demand variant" do
let(:v) { create(:variant, price: 11.11, on_demand: true) }
it "clears on_demand when the stock is overridden" do
vo
scoper.scope v
v.on_demand.should be_false
end
it "does not clear on_demand when only the price is overridden" do
vo_price_only
scoper.scope v
v.on_demand.should be_true
end
it "does not clear on_demand when there is no override" do
scoper.scope v
v.on_demand.should be_true
end
end
end
end
end