diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index 04a6d234c6..d705cc720c 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -5,8 +5,10 @@ module Admin class OrderCyclesController < ResourceController include OrderCyclesHelper - before_filter :load_order_cycle_set, :only => :index + before_filter :load_data_for_index, :only => :index before_filter :require_coordinator, only: :new + around_filter :protect_invalid_destroy, only: :destroy + def show respond_to do |format| @@ -73,18 +75,20 @@ module Admin protected - def collection + def collection(show_more=false) ocs = OrderCycle.managed_by(spree_current_user) ocs.undated + ocs.soonest_closing + ocs.soonest_opening + - ocs.most_recently_closed + (show_more ? ocs.closed : ocs.recently_closed) end private - def load_order_cycle_set - @order_cycle_set = OrderCycleSet.new :collection => collection + def load_data_for_index + @show_more = !!params[:show_more] + @order_cycle_enterprises = OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises + @order_cycle_set = OrderCycleSet.new :collection => collection(@show_more) end def require_coordinator @@ -104,5 +108,14 @@ module Admin render :set_coordinator end end + + def protect_invalid_destroy + begin + yield + rescue ActiveRecord::InvalidForeignKey + redirect_to main_app.admin_order_cycles_url + flash[:error] = "That order cycle has been selected by a customer and cannot be deleted. To prevent customers from accessing it, please close it instead." + end + end end end diff --git a/app/helpers/order_cycles_helper.rb b/app/helpers/order_cycles_helper.rb index b870f9c985..fb523f2c98 100644 --- a/app/helpers/order_cycles_helper.rb +++ b/app/helpers/order_cycles_helper.rb @@ -3,10 +3,6 @@ module OrderCyclesHelper @current_order_cycle ||= current_order(false).andand.order_cycle end - def order_cycle_permitted_in(enterprises) - enterprises.merge(order_cycle_permitted_enterprises) - end - def order_cycle_permitted_enterprises OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises end @@ -79,6 +75,10 @@ module OrderCyclesHelper order_cycle.exchanges.to_enterprises(current_distributor).outgoing.first.pickup_time end + def can_delete?(order_cycle) + Spree::Order.where(order_cycle_id: order_cycle).none? + end + private def validated_enterprise_options(enterprises, options={}) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 134af04c91..adcd596a8c 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -19,7 +19,14 @@ class OrderCycle < ActiveRecord::Base scope :undated, where(orders_open_at: nil, orders_close_at: nil) scope :soonest_closing, lambda { active.order('order_cycles.orders_close_at ASC') } + # TODO This method returns all the closed orders. So maybe we can replace it with :recently_closed. scope :most_recently_closed, lambda { closed.order('order_cycles.orders_close_at DESC') } + + scope :recently_closed, -> { + closed. + where("order_cycles.orders_close_at >= ?", 31.days.ago). + order("order_cycles.orders_close_at DESC") } + scope :soonest_opening, lambda { upcoming.order('order_cycles.orders_open_at ASC') } scope :distributing_product, lambda { |product| diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 145d8310de..5866a50c3f 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -132,7 +132,7 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization can [:create], OrderCycle - can [:admin, :index, :read, :edit, :update, :bulk_update, :clone], OrderCycle do |order_cycle| + can [:admin, :index, :read, :edit, :update, :bulk_update, :clone, :destroy], OrderCycle do |order_cycle| user.enterprises.include? order_cycle.coordinator end can [:for_order_cycle], Enterprise diff --git a/app/views/admin/order_cycles/_row.html.haml b/app/views/admin/order_cycles/_row.html.haml index 8c7ce2bbbd..588ebdd745 100644 --- a/app/views/admin/order_cycles/_row.html.haml +++ b/app/views/admin/order_cycles/_row.html.haml @@ -1,5 +1,6 @@ - order_cycle = order_cycle_form.object - klass = "order-cycle-#{order_cycle.id} #{order_cycle_status_class order_cycle}" + %tr{class: klass} %td= link_to order_cycle.name, main_app.edit_admin_order_cycle_path(order_cycle) %td= order_cycle_form.text_field :orders_open_at, :class => 'datetimepicker', :value => order_cycle.orders_open_at @@ -7,7 +8,7 @@ - unless order_cycles_simple_index %td.suppliers - - suppliers = order_cycle_permitted_in(order_cycle.suppliers) + - suppliers = order_cycle.suppliers.merge(@order_cycle_enterprises) - supplier_list = suppliers.map(&:name).sort.join ', ' - if suppliers.count > 3 %span.with-tip{'data-powertip' => supplier_list} @@ -17,7 +18,7 @@ = supplier_list %td= order_cycle.coordinator.name %td.distributors - - distributors = order_cycle_permitted_in(order_cycle.distributors) + - distributors = order_cycle.distributors.merge(@order_cycle_enterprises) - distributor_list = distributors.map(&:name).sort.join ', ' - if distributors.count > 3 %span.with-tip{'data-powertip' => distributor_list} @@ -25,16 +26,14 @@ distributors - else = distributor_list - %br/ %td.products - - variant_images = capture do - - order_cycle.variants.each do |v| - = image_tag(v.images.first.attachment.url(:mini)) if v.images.present? - %br/ - %span.with-tip{'data-powertip' => variant_images}= "#{order_cycle.variants.count} variants" + %span= "#{order_cycle.variants.count} variants" %td.actions = link_to '', main_app.edit_admin_order_cycle_path(order_cycle), class: 'edit-order-cycle icon-edit no-text' %td.actions - = link_to '', main_app.clone_admin_order_cycle_path(order_cycle), class: 'clone-order-cycle icon-copy no-text' + = link_to '', main_app.clone_admin_order_cycle_path(order_cycle), class: 'clone-order-cycle icon-copy no-text' + - if can_delete?(order_cycle) + %td.actions + = link_to '', main_app.admin_order_cycle_path(order_cycle), class: 'delete-order-cycle icon-trash no-text', :method => :delete, data: { confirm: "Are you sure?" } diff --git a/app/views/admin/order_cycles/index.html.haml b/app/views/admin/order_cycles/index.html.haml index 995cfdd2c3..e39fdde12a 100644 --- a/app/views/admin/order_cycles/index.html.haml +++ b/app/views/admin/order_cycles/index.html.haml @@ -4,6 +4,12 @@ = content_for :page_actions do %li#new_order_cycle_link = button_link_to "New Order Cycle", main_app.new_admin_order_cycle_path, :icon => 'icon-plus', :id => 'admin_new_order_cycle_link' + - if @show_more + %li + = button_link_to "Show less", main_app.admin_order_cycles_path + - else + %li + = button_link_to "Show more", main_app.admin_order_cycles_path(params: { show_more: true }) = form_for @order_cycle_set, :url => main_app.bulk_update_admin_order_cycles_path do |f| %table.index#listing_order_cycles @@ -18,6 +24,7 @@ %col %col %col + %col %thead %tr @@ -25,12 +32,13 @@ %th Open %th Close - unless order_cycles_simple_index - %th Suppliers + %th Supplier %th Coordinator %th Distributors %th Products %th.actions %th.actions + %th.actions %tbody = f.fields_for :collection do |order_cycle_form| diff --git a/config/routes.rb b/config/routes.rb index 401ecbeaf3..517d78e602 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,9 @@ Openfoodnetwork::Application.routes.draw do root :to => 'home#index' + get "/#/login", to: "home#index", as: :spree_login + get "/login", to: redirect("/#/login") get "/map", to: "map#index", as: :map diff --git a/spec/controllers/admin/order_cycles_controller_spec.rb b/spec/controllers/admin/order_cycles_controller_spec.rb index 598059b5f4..b58884086c 100644 --- a/spec/controllers/admin/order_cycles_controller_spec.rb +++ b/spec/controllers/admin/order_cycles_controller_spec.rb @@ -56,5 +56,21 @@ module Admin end end end + + describe "destroy" do + let!(:distributor) { create(:distributor_enterprise, owner: distributor_owner) } + + describe "when an order cycle becomes non-deletable, and we attempt to delete it" do + let!(:oc) { create(:simple_order_cycle, coordinator: distributor) } + let!(:order) { create(:order, order_cycle: oc) } + + before { spree_get :destroy, id: oc.id } + + it "displays an error message" do + expect(response).to redirect_to admin_order_cycles_path + expect(flash[:error]).to eq "That order cycle has been selected by a customer and cannot be deleted. To prevent customers from accessing it, please close it instead." + end + end + end end end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index b1ad3a8843..4bfd0b683c 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -83,8 +83,8 @@ feature %q{ # And I fill in the basic fields fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00' + fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' + fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' select 'My coordinator', from: 'order_cycle_coordinator_id' # And I add a coordinator fee @@ -127,8 +127,8 @@ feature %q{ page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2012-11-06 06:00:00 +1100']" - page.should have_selector "input[value='2012-11-13 17:00:00 +1100']" + page.should have_selector "input[value='2040-11-06 06:00:00 +1100']" + page.should have_selector "input[value='2040-11-13 17:00:00 +1100']" page.should have_content 'My coordinator' page.should have_selector 'td.suppliers', text: 'My supplier' @@ -272,8 +272,8 @@ feature %q{ # And I update it fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00' + fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' + fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' select 'My coordinator', from: 'order_cycle_coordinator_id' # And I configure some coordinator fees @@ -336,8 +336,8 @@ feature %q{ page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2012-11-06 06:00:00 +1100']" - page.should have_selector "input[value='2012-11-13 17:00:00 +1100']" + page.should have_selector "input[value='2040-11-06 06:00:00 +1100']" + page.should have_selector "input[value='2040-11-13 17:00:00 +1100']" page.should have_content 'My coordinator' page.should have_selector 'td.suppliers', text: 'My supplier' @@ -374,18 +374,18 @@ feature %q{ # And I fill in some new opening/closing times and save them within("tr.order-cycle-#{oc1.id}") do - all('input').first.set '2012-12-01 12:00:00' - all('input').last.set '2012-12-01 12:00:01' + all('input').first.set '2040-12-01 12:00:00' + all('input').last.set '2040-12-01 12:00:01' end within("tr.order-cycle-#{oc2.id}") do - all('input').first.set '2012-12-01 12:00:02' - all('input').last.set '2012-12-01 12:00:03' + all('input').first.set '2040-12-01 12:00:02' + all('input').last.set '2040-12-01 12:00:03' end within("tr.order-cycle-#{oc3.id}") do - all('input').first.set '2012-12-01 12:00:04' - all('input').last.set '2012-12-01 12:00:05' + all('input').first.set '2040-12-01 12:00:04' + all('input').last.set '2040-12-01 12:00:05' end click_button 'Update' @@ -529,8 +529,8 @@ feature %q{ save_screenshot '/Users/rob/Desktop/ss1.png' fill_in 'order_cycle_name', with: 'My order cycle' - fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00' + fill_in 'order_cycle_orders_open_at', with: '2040-11-06 06:00:00' + fill_in 'order_cycle_orders_close_at', with: '2040-11-13 17:00:00' select 'Managed supplier', from: 'new_supplier_id' click_button 'Add supplier' @@ -651,8 +651,8 @@ feature %q{ # And I fill in the basic fields fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2014-10-17 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2014-10-24 17:00:00' + fill_in 'order_cycle_orders_open_at', with: '2040-10-17 06:00:00' + fill_in 'order_cycle_orders_close_at', with: '2040-10-24 17:00:00' fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time' fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions' @@ -677,8 +677,8 @@ feature %q{ # Then my order cycle should have been created page.should have_content 'Your order cycle has been created.' page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2014-10-17 06:00:00 +1100']" - page.should have_selector "input[value='2014-10-24 17:00:00 +1100']" + page.should have_selector "input[value='2040-10-17 06:00:00 +1100']" + page.should have_selector "input[value='2040-10-24 17:00:00 +1100']" # And it should have some variants selected oc = OrderCycle.last @@ -738,8 +738,8 @@ feature %q{ # And I fill in the basic fields fill_in 'order_cycle_name', with: 'Plums & Avos' - fill_in 'order_cycle_orders_open_at', with: '2014-10-17 06:00:00' - fill_in 'order_cycle_orders_close_at', with: '2014-10-24 17:00:00' + fill_in 'order_cycle_orders_open_at', with: '2040-10-17 06:00:00' + fill_in 'order_cycle_orders_close_at', with: '2040-10-24 17:00:00' fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'xy' fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' @@ -760,8 +760,8 @@ feature %q{ # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' page.should have_selector 'a', text: 'Plums & Avos' - page.should have_selector "input[value='2014-10-17 06:00:00 +1100']" - page.should have_selector "input[value='2014-10-24 17:00:00 +1100']" + page.should have_selector "input[value='2040-10-17 06:00:00 +1100']" + page.should have_selector "input[value='2040-10-24 17:00:00 +1100']" # And it should have a variant selected oc = OrderCycle.last @@ -778,6 +778,15 @@ feature %q{ end end + scenario "deleting an order cycle" do + create(:simple_order_cycle, name: "Translusent Berries") + login_to_admin_section + click_link 'Order Cycles' + page.should have_content("Translusent Berries") + first('a.delete-order-cycle').click + page.should_not have_content("Translusent Berries") + end + private diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index f8b405374e..2aefb42db5 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' feature "Authentication", js: true do include UIComponentHelper + describe "login" do let(:user) { create(:user, password: "password", password_confirmation: "password") } @@ -32,7 +33,7 @@ feature "Authentication", js: true do scenario "failing to login" do fill_in "Email", with: user.email click_login_button - page.should have_content "Invalid email or password" + page.should have_content "Invalid email or password" end scenario "logging in successfully" do @@ -70,7 +71,7 @@ feature "Authentication", js: true do ActionMailer::Base.deliveries.clear select_login_tab "Forgot Password?" end - + scenario "failing to reset password" do fill_in "Your email", with: "notanemail@myemail.com" click_reset_password_button @@ -78,7 +79,7 @@ feature "Authentication", js: true do end scenario "resetting password" do - fill_in "Your email", with: user.email + fill_in "Your email", with: user.email click_reset_password_button page.should have_reset_password ActionMailer::Base.deliveries.last.subject.should =~ /Password Reset/ @@ -90,29 +91,17 @@ feature "Authentication", js: true do browse_as_medium end scenario "showing login" do - open_off_canvas + open_off_canvas open_login_modal page.should have_login_modal end end end - describe "oldskool" do - scenario "with valid credentials" do - visit "/login" - fill_in "Email", with: user.email - fill_in "Password", with: "password" - click_button "Login" - current_path.should == "/" - end - - scenario "with invalid credentials" do - visit "/login" - fill_in "Email", with: user.email - fill_in "Password", with: "this isn't my password" - click_button "Login" - page.should have_content "Invalid email or password" - end + scenario "Loggin by typing login/ redirects to /#/login" do + visit "/login" + uri = URI.parse(current_url) + (uri.path + "#" + uri.fragment).should == '/#/login' end end end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 7ba2b66589..817d2e8e93 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -90,6 +90,17 @@ describe OrderCycle do end end + describe "#recently_closed" do + it "finds the orders closed in the last 30 days sorted in descending order" do + create(:simple_order_cycle, orders_close_at: 3.days.from_now) + oc1 = create(:simple_order_cycle, orders_close_at: 1.day.ago) + oc2 = create(:simple_order_cycle, orders_close_at: 30.days.ago) + create(:simple_order_cycle, orders_close_at: 31.days.ago) + + OrderCycle.recently_closed.should == [oc1 , oc2] + end + end + it "finds the most recently closed order cycles" do oc1 = create(:simple_order_cycle, orders_close_at: 2.hours.ago) oc2 = create(:simple_order_cycle, orders_close_at: 1.hour.ago) diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 19006cbcd1..4023961eef 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -371,11 +371,11 @@ module Spree let(:oc2) { create(:simple_order_cycle) } it "should be able to read/write OrderCycles they are the co-ordinator of" do - should have_ability([:admin, :index, :read, :edit, :update, :clone], for: oc1) + should have_ability([:admin, :index, :read, :edit, :update, :clone, :destroy], for: oc1) end it "should not be able to read/write OrderCycles they are not the co-ordinator of" do - should_not have_ability([:admin, :index, :read, :create, :edit, :update, :clone], for: oc2) + should_not have_ability([:admin, :index, :read, :create, :edit, :update, :clone, :destroy], for: oc2) end it "should be able to create OrderCycles" do