diff --git a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee index 1e43c17465..3883eca8d5 100644 --- a/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/enterprises_controller.js.coffee @@ -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 diff --git a/app/assets/javascripts/darkswarm/controllers/line_item_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/line_item_controller.js.coffee index ea62163868..864f25177a 100644 --- a/app/assets/javascripts/darkswarm/controllers/line_item_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/line_item_controller.js.coffee @@ -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 \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/filters/distance_within_km.js.coffee b/app/assets/javascripts/darkswarm/filters/distance_within_km.js.coffee new file mode 100644 index 0000000000..c6a37a2b40 --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/distance_within_km.js.coffee @@ -0,0 +1,5 @@ +Darkswarm.filter 'distanceWithinKm', -> + (enterprises, range) -> + enterprises ||= [] + enterprises.filter (enterprise) -> + enterprise.distance / 1000 <= range diff --git a/app/assets/javascripts/darkswarm/filters/enterpriseMatchesNameQuery.js.coffee b/app/assets/javascripts/darkswarm/filters/enterpriseMatchesNameQuery.js.coffee new file mode 100644 index 0000000000..6e786a9f62 --- /dev/null +++ b/app/assets/javascripts/darkswarm/filters/enterpriseMatchesNameQuery.js.coffee @@ -0,0 +1,4 @@ +Darkswarm.filter 'enterpriseMatchesNameQuery', -> + (enterprises, matches_name_query) -> + enterprises.filter (enterprise) -> + enterprise.matches_name_query == matches_name_query diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index bbb0b5fab4..286b22c770 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -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 diff --git a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee index 6040f1d150..2dddfbc7ef 100644 --- a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee @@ -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 diff --git a/app/assets/javascripts/darkswarm/services/geo.js.erb.coffee b/app/assets/javascripts/darkswarm/services/geo.js.erb.coffee new file mode 100644 index 0000000000..2f96722b08 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/geo.js.erb.coffee @@ -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 \ No newline at end of file diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js old mode 100755 new mode 100644 diff --git a/app/assets/stylesheets/darkswarm/animations.sass b/app/assets/stylesheets/darkswarm/animations.sass index 62e87e7224..d452b69d39 100644 --- a/app/assets/stylesheets/darkswarm/animations.sass +++ b/app/assets/stylesheets/darkswarm/animations.sass @@ -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 - - - - diff --git a/app/assets/stylesheets/darkswarm/hubs.css.sass b/app/assets/stylesheets/darkswarm/hubs.css.sass index da597b5ee7..a351170d99 100644 --- a/app/assets/stylesheets/darkswarm/hubs.css.sass +++ b/app/assets/stylesheets/darkswarm/hubs.css.sass @@ -5,3 +5,6 @@ background-color: lighten($ofn-grey, 43%) @include panepadding @include sidepaddingSm + + .name-matches, .distance-matches + margin-top: 4em \ No newline at end of file diff --git a/app/controllers/spree/admin/reports_controller_decorator.rb b/app/controllers/spree/admin/reports_controller_decorator.rb index 81975bb250..78026fad1e 100644 --- a/app/controllers/spree/admin/reports_controller_decorator.rb +++ b/app/controllers/spree/admin/reports_controller_decorator.rb @@ -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 } diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index bca6395030..61bb36ef65 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -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) diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index a5543fa2a3..8d857d03f7 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -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"}   .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 diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml index 365d87d8c2..21719b04b7 100644 --- a/app/views/home/_filters.html.haml +++ b/app/views/home/_filters.html.haml @@ -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 diff --git a/app/views/home/_hubs.html.haml b/app/views/home/_hubs.html.haml index fd30050b90..40074ce0bb 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/home/_hubs.html.haml @@ -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 }} diff --git a/app/views/home/_hubs_table.html.haml b/app/views/home/_hubs_table.html.haml new file mode 100644 index 0000000000..8842079f56 --- /dev/null +++ b/app/views/home/_hubs_table.html.haml @@ -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" diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index c69e4beaaa..266ece2470 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -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 - diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 0e2d1fa0f6..33a3d6148a 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -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" diff --git a/app/views/shared/components/_enterprise_no_results.html.haml b/app/views/shared/components/_enterprise_no_results.html.haml index 2e6edd0875..1c8af802a2 100644 --- a/app/views/shared/components/_enterprise_no_results.html.haml +++ b/app/views/shared/components/_enterprise_no_results.html.haml @@ -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}}. diff --git a/app/views/shared/components/_enterprise_search.html.haml b/app/views/shared/components/_enterprise_search.html.haml index 62dcec3ea6..4bb7ca446b 100644 --- a/app/views/shared/components/_enterprise_search.html.haml +++ b/app/views/shared/components/_enterprise_search.html.haml @@ -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} diff --git a/app/views/spree/admin/reports/_packing_description.html.haml b/app/views/spree/admin/reports/_packing_description.html.haml new file mode 100644 index 0000000000..3d7855cb89 --- /dev/null +++ b/app/views/spree/admin/reports/_packing_description.html.haml @@ -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]}" diff --git a/app/views/spree/admin/reports/packing.html.haml b/app/views/spree/admin/reports/packing.html.haml new file mode 100644 index 0000000000..2aa8d8c8b9 --- /dev/null +++ b/app/views/spree/admin/reports/packing.html.haml @@ -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) diff --git a/config/routes.rb b/config/routes.rb index 8ff703e2ba..578f7cfec1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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] diff --git a/lib/open_food_network/packing_report.rb b/lib/open_food_network/packing_report.rb new file mode 100644 index 0000000000..fcc5df5e88 --- /dev/null +++ b/lib/open_food_network/packing_report.rb @@ -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 diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb index 2cf79ee28d..80396ffa17 100644 --- a/lib/open_food_network/scope_variant_to_hub.rb +++ b/lib/open_food_network/scope_variant_to_hub.rb @@ -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 diff --git a/spec/features/admin/reports_spec.rb b/spec/features/admin/reports_spec.rb index 4f820bb004..503a52762e 100644 --- a/spec/features/admin/reports_spec.rb +++ b/spec/features/admin/reports_spec.rb @@ -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' diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 2ab060f66f..5f2ae3d6c4 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -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 diff --git a/spec/features/consumer/shopping/variant_overrides_spec.rb b/spec/features/consumer/shopping/variant_overrides_spec.rb index 44e8e38732..a6855fc6d1 100644 --- a/spec/features/consumer/shopping/variant_overrides_spec.rb +++ b/spec/features/consumer/shopping/variant_overrides_spec.rb @@ -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 diff --git a/spec/javascripts/unit/darkswarm/filters/active_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/active_spec.js.coffee index 337121b1ae..25c6c1d003 100644 --- a/spec/javascripts/unit/darkswarm/filters/active_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/filters/active_spec.js.coffee @@ -1,12 +1,8 @@ describe 'filtering by active', -> filterByActive = null objects = [ - { - active: true - } - { - active: false - } + {active: true} + {active: false} ] diff --git a/spec/javascripts/unit/darkswarm/filters/distance_within_km_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/distance_within_km_spec.js.coffee new file mode 100644 index 0000000000..afbc31ee84 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/filters/distance_within_km_spec.js.coffee @@ -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 [] diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 4be1a13dd8..cc65f043cd 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -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'} diff --git a/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee index 94dd7d39d2..67774aecbd 100644 --- a/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee @@ -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() \ No newline at end of file diff --git a/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee index abd9c5c617..50e2159338 100644 --- a/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee @@ -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] diff --git a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee index 3ef21705d3..4252000460 100644 --- a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee @@ -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 diff --git a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee index 590156260d..778e701c7d 100644 --- a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee @@ -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_)-> diff --git a/spec/lib/open_food_network/packing_report_spec.rb b/spec/lib/open_food_network/packing_report_spec.rb new file mode 100644 index 0000000000..40e3d44c9e --- /dev/null +++ b/spec/lib/open_food_network/packing_report_spec.rb @@ -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 diff --git a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb index dbdf5c074e..2a249f9c7a 100644 --- a/spec/lib/open_food_network/scope_variant_to_hub_spec.rb +++ b/spec/lib/open_food_network/scope_variant_to_hub_spec.rb @@ -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